summaryrefslogtreecommitdiffstats
path: root/sound/isa
diff options
context:
space:
mode:
Diffstat (limited to 'sound/isa')
-rw-r--r--sound/isa/Kconfig473
-rw-r--r--sound/isa/Makefile29
-rw-r--r--sound/isa/ad1816a/Makefile10
-rw-r--r--sound/isa/ad1816a/ad1816a.c268
-rw-r--r--sound/isa/ad1816a/ad1816a_lib.c923
-rw-r--r--sound/isa/ad1848/Makefile11
-rw-r--r--sound/isa/ad1848/ad1848.c149
-rw-r--r--sound/isa/adlib.c99
-rw-r--r--sound/isa/als100.c340
-rw-r--r--sound/isa/azt2320.c319
-rw-r--r--sound/isa/cmi8328.c460
-rw-r--r--sound/isa/cmi8330.c752
-rw-r--r--sound/isa/cs423x/Makefile14
-rw-r--r--sound/isa/cs423x/cs4231.c168
-rw-r--r--sound/isa/cs423x/cs4236.c647
-rw-r--r--sound/isa/cs423x/cs4236_lib.c1074
-rw-r--r--sound/isa/es1688/Makefile12
-rw-r--r--sound/isa/es1688/es1688.c330
-rw-r--r--sound/isa/es1688/es1688_lib.c992
-rw-r--r--sound/isa/es18xx.c2342
-rw-r--r--sound/isa/galaxy/Makefile11
-rw-r--r--sound/isa/galaxy/azt1605.c78
-rw-r--r--sound/isa/galaxy/azt2316.c98
-rw-r--r--sound/isa/galaxy/galaxy.c615
-rw-r--r--sound/isa/gus/Makefile25
-rw-r--r--sound/isa/gus/gus_dma.c236
-rw-r--r--sound/isa/gus/gus_dram.c87
-rw-r--r--sound/isa/gus/gus_io.c525
-rw-r--r--sound/isa/gus/gus_irq.c131
-rw-r--r--sound/isa/gus/gus_main.c459
-rw-r--r--sound/isa/gus/gus_mem.c337
-rw-r--r--sound/isa/gus/gus_mem_proc.c87
-rw-r--r--sound/isa/gus/gus_mixer.c177
-rw-r--r--sound/isa/gus/gus_pcm.c885
-rw-r--r--sound/isa/gus/gus_reset.c394
-rw-r--r--sound/isa/gus/gus_tables.h75
-rw-r--r--sound/isa/gus/gus_timer.c188
-rw-r--r--sound/isa/gus/gus_uart.c245
-rw-r--r--sound/isa/gus/gus_volume.c204
-rw-r--r--sound/isa/gus/gusclassic.c207
-rw-r--r--sound/isa/gus/gusextreme.c337
-rw-r--r--sound/isa/gus/gusmax.c344
-rw-r--r--sound/isa/gus/interwave-stb.c2
-rw-r--r--sound/isa/gus/interwave.c912
-rw-r--r--sound/isa/msnd/Makefile10
-rw-r--r--sound/isa/msnd/msnd.c694
-rw-r--r--sound/isa/msnd/msnd.h295
-rw-r--r--sound/isa/msnd/msnd_classic.c3
-rw-r--r--sound/isa/msnd/msnd_classic.h116
-rw-r--r--sound/isa/msnd/msnd_midi.c167
-rw-r--r--sound/isa/msnd/msnd_pinnacle.c1166
-rw-r--r--sound/isa/msnd/msnd_pinnacle.h168
-rw-r--r--sound/isa/msnd/msnd_pinnacle_mixer.c333
-rw-r--r--sound/isa/opl3sa2.c928
-rw-r--r--sound/isa/opti9xx/Makefile16
-rw-r--r--sound/isa/opti9xx/miro.c1624
-rw-r--r--sound/isa/opti9xx/opti92x-ad1848.c1171
-rw-r--r--sound/isa/opti9xx/opti92x-cs4231.c2
-rw-r--r--sound/isa/opti9xx/opti93x.c3
-rw-r--r--sound/isa/sb/Makefile29
-rw-r--r--sound/isa/sb/emu8000.c1127
-rw-r--r--sound/isa/sb/emu8000_callback.c534
-rw-r--r--sound/isa/sb/emu8000_local.h32
-rw-r--r--sound/isa/sb/emu8000_patch.c292
-rw-r--r--sound/isa/sb/emu8000_pcm.c655
-rw-r--r--sound/isa/sb/emu8000_synth.c116
-rw-r--r--sound/isa/sb/jazz16.c370
-rw-r--r--sound/isa/sb/sb16.c657
-rw-r--r--sound/isa/sb/sb16_csp.c1187
-rw-r--r--sound/isa/sb/sb16_main.c870
-rw-r--r--sound/isa/sb/sb8.c220
-rw-r--r--sound/isa/sb/sb8_main.c592
-rw-r--r--sound/isa/sb/sb8_midi.c267
-rw-r--r--sound/isa/sb/sb_common.c261
-rw-r--r--sound/isa/sb/sb_mixer.c951
-rw-r--r--sound/isa/sb/sbawe.c2
-rw-r--r--sound/isa/sc6000.c680
-rw-r--r--sound/isa/sscape.c1291
-rw-r--r--sound/isa/wavefront/Makefile10
-rw-r--r--sound/isa/wavefront/wavefront.c642
-rw-r--r--sound/isa/wavefront/wavefront_fx.c272
-rw-r--r--sound/isa/wavefront/wavefront_midi.c576
-rw-r--r--sound/isa/wavefront/wavefront_synth.c2222
-rw-r--r--sound/isa/wss/Makefile11
-rw-r--r--sound/isa/wss/wss_lib.c2202
85 files changed, 38835 insertions, 0 deletions
diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig
new file mode 100644
index 0000000000..f8159179e3
--- /dev/null
+++ b/sound/isa/Kconfig
@@ -0,0 +1,473 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# ALSA ISA drivers
+
+config SND_WSS_LIB
+ tristate
+ select SND_PCM
+ select SND_TIMER
+
+config SND_SB_COMMON
+ tristate
+
+config SND_SB8_DSP
+ tristate
+ select SND_PCM
+ select SND_SB_COMMON
+
+config SND_SB16_DSP
+ tristate
+ select SND_PCM
+ select SND_SB_COMMON
+
+menuconfig SND_ISA
+ bool "ISA sound devices"
+ depends on ISA || COMPILE_TEST
+ depends on ISA_DMA_API
+ depends on HAS_IOPORT
+ default y
+ help
+ Support for sound devices connected via the ISA bus.
+
+if SND_ISA
+
+config SND_ADLIB
+ tristate "AdLib FM card"
+ select SND_OPL3_LIB
+ help
+ Say Y here to include support for AdLib FM cards.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-adlib.
+
+config SND_AD1816A
+ tristate "Analog Devices SoundPort AD1816A"
+ depends on PNP
+ select ISAPNP
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_PCM
+ select SND_TIMER
+ help
+ Say Y here to include support for Analog Devices SoundPort
+ AD1816A or compatible sound chips.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-ad1816a.
+
+config SND_AD1848
+ tristate "Generic AD1848/CS4248 driver"
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for AD1848 (Analog Devices) or
+ CS4248 (Cirrus Logic - Crystal Semiconductors) chips.
+
+ For newer chips from Cirrus Logic, use the CS4231 or CS4232+
+ drivers.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-ad1848.
+
+config SND_ALS100
+ tristate "Diamond Tech. DT-019x and Avance Logic ALSxxx"
+ depends on PNP
+ select ISAPNP
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_SB16_DSP
+ help
+ Say Y here to include support for soundcards based on the
+ Diamond Technologies DT-019X or Avance Logic chips: ALS007,
+ ALS100, ALS110, ALS120 and ALS200 chips.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-als100.
+
+config SND_AZT1605
+ tristate "Aztech AZT1605 Driver"
+ depends on SND
+ select SND_WSS_LIB
+ select SND_MPU401_UART
+ select SND_OPL3_LIB
+ help
+ Say Y here to include support for Aztech Sound Galaxy cards
+ based on the AZT1605 chipset.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-azt1605.
+
+config SND_AZT2316
+ tristate "Aztech AZT2316 Driver"
+ depends on SND
+ select SND_WSS_LIB
+ select SND_MPU401_UART
+ select SND_OPL3_LIB
+ help
+ Say Y here to include support for Aztech Sound Galaxy cards
+ based on the AZT2316 chipset.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-azt2316.
+
+config SND_AZT2320
+ tristate "Aztech Systems AZT2320"
+ depends on PNP
+ select ISAPNP
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for soundcards based on the
+ Aztech Systems AZT2320 chip.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-azt2320.
+
+config SND_CMI8328
+ tristate "C-Media CMI8328"
+ select SND_WSS_LIB
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ help
+ Say Y here to include support for soundcards based on the
+ C-Media CMI8328 chip.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-cmi8328.
+
+config SND_CMI8330
+ tristate "C-Media CMI8330"
+ select SND_WSS_LIB
+ select SND_SB16_DSP
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ help
+ Say Y here to include support for soundcards based on the
+ C-Media CMI8330 chip.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-cmi8330.
+
+config SND_CS4231
+ tristate "Generic Cirrus Logic CS4231 driver"
+ select SND_MPU401_UART
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for CS4231 chips from Cirrus
+ Logic - Crystal Semiconductors.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-cs4231.
+
+config SND_CS4236
+ tristate "Generic Cirrus Logic CS4232/CS4236+ driver"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_WSS_LIB
+ help
+ Say Y to include support for CS4232,CS4235,CS4236,CS4237B,
+ CS4238B,CS4239 chips from Cirrus Logic - Crystal
+ Semiconductors.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-cs4236.
+
+config SND_ES1688
+ tristate "Generic ESS ES688/ES1688 and ES968 PnP driver"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_PCM
+ help
+ Say Y here to include support for ESS AudioDrive ES688 or
+ ES1688 chips. Also, this module support cards with ES968 PnP chip.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-es1688.
+
+config SND_ES18XX
+ tristate "Generic ESS ES18xx driver"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_PCM
+ help
+ Say Y here to include support for ESS AudioDrive ES18xx chips.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-es18xx.
+
+config SND_SC6000
+ tristate "Gallant SC-6000/6600/7000 and Audio Excel DSP 16"
+ depends on HAS_IOPORT_MAP
+ select SND_WSS_LIB
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ help
+ Say Y here to include support for Gallant SC-6000, SC-6600, SC-7000
+ cards and clones:
+ Audio Excel DSP 16 and Zoltrix AV302.
+
+ These cards are based on CompuMedia ASC-9308 or ASC-9408 chips.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-sc6000.
+
+config SND_GUSCLASSIC
+ tristate "Gravis UltraSound Classic"
+ select SND_RAWMIDI
+ select SND_PCM
+ select SND_TIMER
+ help
+ Say Y here to include support for Gravis UltraSound Classic
+ soundcards.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-gusclassic.
+
+config SND_GUSEXTREME
+ tristate "Gravis UltraSound Extreme"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_PCM
+ select SND_TIMER
+ help
+ Say Y here to include support for Gravis UltraSound Extreme
+ soundcards.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-gusextreme.
+
+config SND_GUSMAX
+ tristate "Gravis UltraSound MAX"
+ select SND_RAWMIDI
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for Gravis UltraSound MAX
+ soundcards.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-gusmax.
+
+config SND_INTERWAVE
+ tristate "AMD InterWave, Gravis UltraSound PnP"
+ depends on PNP
+ select SND_RAWMIDI
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for AMD InterWave based
+ soundcards (Gravis UltraSound Plug & Play, STB SoundRage32,
+ MED3210, Dynasonic Pro, Panasonic PCA761AW).
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-interwave.
+
+config SND_INTERWAVE_STB
+ tristate "AMD InterWave + TEA6330T (UltraSound 32-Pro)"
+ depends on PNP
+ select SND_RAWMIDI
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for AMD InterWave based
+ soundcards with a TEA6330T bass and treble regulator
+ (UltraSound 32-Pro).
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-interwave-stb.
+
+config SND_JAZZ16
+ tristate "Media Vision Jazz16 card and compatibles"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_SB8_DSP
+ help
+ Say Y here to include support for soundcards based on the
+ Media Vision Jazz16 chipset: digital chip MVD1216 (Jazz16),
+ codec MVA416 (CS4216) and mixer MVA514 (ICS2514).
+ Media Vision's Jazz16 cards were sold under names Pro Sonic 16,
+ Premium 3-D and Pro 3-D. There were also OEMs cards with the
+ Jazz16 chipset.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-jazz16.
+
+config SND_OPL3SA2
+ tristate "Yamaha OPL3-SA2/SA3"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for Yamaha OPL3-SA2 and OPL3-SA3
+ chips.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-opl3sa2.
+
+config SND_OPTI92X_AD1848
+ tristate "OPTi 82C92x - AD1848"
+ select SND_OPL3_LIB
+ select SND_OPL4_LIB
+ select SND_MPU401_UART
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for soundcards based on Opti
+ 82C92x or OTI-601 chips and using an AD1848 codec.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-opti92x-ad1848.
+
+config SND_OPTI92X_CS4231
+ tristate "OPTi 82C92x - CS4231"
+ select SND_OPL3_LIB
+ select SND_OPL4_LIB
+ select SND_MPU401_UART
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for soundcards based on Opti
+ 82C92x chips and using a CS4231 codec.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-opti92x-cs4231.
+
+config SND_OPTI93X
+ tristate "OPTi 82C93x"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for soundcards based on Opti
+ 82C93x chips.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-opti93x.
+
+config SND_MIRO
+ tristate "Miro miroSOUND PCM1pro/PCM12/PCM20radio driver"
+ select SND_OPL4_LIB
+ select SND_WSS_LIB
+ select SND_MPU401_UART
+ select SND_PCM
+ help
+ Say 'Y' or 'M' to include support for Miro miroSOUND PCM1 pro,
+ miroSOUND PCM12 and miroSOUND PCM20 Radio soundcards.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-miro.
+
+config SND_SB8
+ tristate "Sound Blaster 1.0/2.0/Pro (8-bit)"
+ select SND_OPL3_LIB
+ select SND_RAWMIDI
+ select SND_SB8_DSP
+ help
+ Say Y here to include support for Creative Sound Blaster 1.0/
+ 2.0/Pro (8-bit) or 100% compatible soundcards.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-sb8.
+
+config SND_SB16
+ tristate "Sound Blaster 16 (PnP)"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_SB16_DSP
+ help
+ Say Y here to include support for Sound Blaster 16 soundcards
+ (including the Plug and Play version).
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-sb16.
+
+config SND_SBAWE
+ tristate "Sound Blaster AWE (32,64) (PnP)"
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_SB16_DSP
+ select SND_SEQ_DEVICE if SND_SEQUENCER != n
+ help
+ Say Y here to include support for Sound Blaster AWE soundcards
+ (including the Plug and Play version).
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-sbawe.
+
+# select SEQ stuff to min(SND_SEQUENCER,SND_XXX)
+config SND_SBAWE_SEQ
+ def_tristate SND_SEQUENCER && SND_SBAWE
+ select SND_SEQ_MIDI_EMUL
+ select SND_SEQ_VIRMIDI
+ select SND_SYNTH_EMUX
+
+config SND_SB16_CSP
+ bool "Sound Blaster 16/AWE CSP support"
+ depends on (SND_SB16 || SND_SBAWE) && (BROKEN || !PPC)
+ select FW_LOADER
+ help
+ Say Y here to include support for the CSP core. This special
+ coprocessor can do variable tasks like various compression and
+ decompression algorithms.
+
+config SND_SSCAPE
+ tristate "Ensoniq SoundScape driver"
+ select SND_MPU401_UART
+ select SND_WSS_LIB
+ select FW_LOADER
+ help
+ Say Y here to include support for Ensoniq SoundScape
+ and Ensoniq OEM soundcards.
+
+ The PCM audio is supported on SoundScape Classic, Elite, PnP
+ and VIVO cards. The supported OEM cards are SPEA Media FX and
+ Reveal SC-600.
+ The MIDI support is very experimental and requires binary
+ firmware files called "scope.cod" and "sndscape.co?" where the
+ ? is digit 0, 1, 2, 3 or 4. The firmware files can be found
+ in DOS or Windows driver packages. One has to put the firmware
+ files into the /lib/firmware directory.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-sscape.
+
+config SND_WAVEFRONT
+ tristate "Turtle Beach Maui,Tropez,Tropez+ (Wavefront)"
+ select FW_LOADER
+ select SND_OPL3_LIB
+ select SND_MPU401_UART
+ select SND_WSS_LIB
+ help
+ Say Y here to include support for Turtle Beach Maui, Tropez
+ and Tropez+ soundcards based on the Wavefront chip.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-wavefront.
+
+config SND_MSND_PINNACLE
+ tristate "Turtle Beach MultiSound Pinnacle/Fiji driver"
+ depends on X86
+ select FW_LOADER
+ select SND_MPU401_UART
+ select SND_PCM
+ help
+ Say Y to include support for Turtle Beach MultiSound Pinnacle/
+ Fiji soundcards.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-msnd-pinnacle.
+
+config SND_MSND_CLASSIC
+ tristate "Support for Turtle Beach MultiSound Classic, Tahiti, Monterey"
+ depends on X86
+ select FW_LOADER
+ select SND_MPU401_UART
+ select SND_PCM
+ help
+ Say M here if you have a Turtle Beach MultiSound Classic, Tahiti or
+ Monterey (not for the Pinnacle or Fiji).
+
+ See <file:Documentation/sound/cards/multisound.sh> for important information
+ about this driver. Note that it has been discontinued, but the
+ Voyetra Turtle Beach knowledge base entry for it is still available
+ at <http://www.turtlebeach.com/site/kb_ftp/790.asp>.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-msnd-classic.
+
+endif # SND_ISA
+
diff --git a/sound/isa/Makefile b/sound/isa/Makefile
new file mode 100644
index 0000000000..5eaddbf4a7
--- /dev/null
+++ b/sound/isa/Makefile
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-adlib-objs := adlib.o
+snd-als100-objs := als100.o
+snd-azt2320-objs := azt2320.o
+snd-cmi8328-objs := cmi8328.o
+snd-cmi8330-objs := cmi8330.o
+snd-es18xx-objs := es18xx.o
+snd-opl3sa2-objs := opl3sa2.o
+snd-sc6000-objs := sc6000.o
+snd-sscape-objs := sscape.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ADLIB) += snd-adlib.o
+obj-$(CONFIG_SND_ALS100) += snd-als100.o
+obj-$(CONFIG_SND_AZT2320) += snd-azt2320.o
+obj-$(CONFIG_SND_CMI8328) += snd-cmi8328.o
+obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o
+obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o
+obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o
+obj-$(CONFIG_SND_SC6000) += snd-sc6000.o
+obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o
+
+obj-$(CONFIG_SND) += ad1816a/ ad1848/ cs423x/ es1688/ galaxy/ gus/ msnd/ opti9xx/ \
+ sb/ wavefront/ wss/
diff --git a/sound/isa/ad1816a/Makefile b/sound/isa/ad1816a/Makefile
new file mode 100644
index 0000000000..93def7f169
--- /dev/null
+++ b/sound/isa/ad1816a/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ad1816a-objs := ad1816a.o ad1816a_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AD1816A) += snd-ad1816a.o
diff --git a/sound/isa/ad1816a/ad1816a.c b/sound/isa/ad1816a/ad1816a.c
new file mode 100644
index 0000000000..9ac8737731
--- /dev/null
+++ b/sound/isa/ad1816a/ad1816a.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ card-ad1816a.c - driver for ADI SoundPort AD1816A based soundcards.
+ Copyright (C) 2000 by Massimo Piccioni <dafastidio@libero.it>
+
+*/
+
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/ad1816a.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+
+#define PFX "ad1816a: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("AD1816A, AD1815");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 1-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */
+static int clockfreq[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ad1816a based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ad1816a based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ad1816a based soundcard.");
+module_param_array(clockfreq, int, NULL, 0444);
+MODULE_PARM_DESC(clockfreq, "Clock frequency for ad1816a driver (default = 0).");
+
+static const struct pnp_card_device_id snd_ad1816a_pnpids[] = {
+ /* Analog Devices AD1815 */
+ { .id = "ADS7150", .devs = { { .id = "ADS7150" }, { .id = "ADS7151" } } },
+ /* Analog Devices AD1816? */
+ { .id = "ADS7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+ /* Analog Devices AD1816A - added by Kenneth Platz <kxp@atl.hp.com> */
+ { .id = "ADS7181", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+ /* Analog Devices AD1816A - Aztech/Newcom SC-16 3D */
+ { .id = "AZT1022", .devs = { { .id = "AZT1018" }, { .id = "AZT2002" } } },
+ /* Highscreen Sound-Boostar 16 3D - added by Stefan Behnel */
+ { .id = "LWC1061", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+ /* Highscreen Sound-Boostar 16 3D */
+ { .id = "MDK1605", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+ /* Shark Predator ISA - added by Ken Arromdee */
+ { .id = "SMM7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+ /* Analog Devices AD1816A - Terratec AudioSystem EWS64 S */
+ { .id = "TER1112", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+ /* Analog Devices AD1816A - Terratec AudioSystem EWS64 S */
+ { .id = "TER1112", .devs = { { .id = "TER1100" }, { .id = "TER1101" } } },
+ /* Analog Devices AD1816A - Terratec Base 64 */
+ { .id = "TER1411", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+ /* end */
+ { .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_ad1816a_pnpids);
+
+
+#define DRIVER_NAME "snd-card-ad1816a"
+
+
+static int snd_card_ad1816a_pnp(int dev, struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ struct pnp_dev *pdev;
+ int err;
+
+ pdev = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (pdev == NULL)
+ return -EBUSY;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ printk(KERN_ERR PFX "AUDIO PnP configure failure\n");
+ return -EBUSY;
+ }
+
+ port[dev] = pnp_port_start(pdev, 2);
+ fm_port[dev] = pnp_port_start(pdev, 1);
+ dma1[dev] = pnp_dma(pdev, 0);
+ dma2[dev] = pnp_dma(pdev, 1);
+ irq[dev] = pnp_irq(pdev, 0);
+
+ pdev = pnp_request_card_device(card, id->devs[1].id, NULL);
+ if (pdev == NULL) {
+ mpu_port[dev] = -1;
+ snd_printk(KERN_WARNING PFX "MPU401 device busy, skipping.\n");
+ return 0;
+ }
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ printk(KERN_ERR PFX "MPU401 PnP configure failure\n");
+ mpu_port[dev] = -1;
+ } else {
+ mpu_port[dev] = pnp_port_start(pdev, 0);
+ mpu_irq[dev] = pnp_irq(pdev, 0);
+ }
+
+ return 0;
+}
+
+static int snd_card_ad1816a_probe(int dev, struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ int error;
+ struct snd_card *card;
+ struct snd_ad1816a *chip;
+ struct snd_opl3 *opl3;
+
+ error = snd_devm_card_new(&pcard->card->dev,
+ index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_ad1816a), &card);
+ if (error < 0)
+ return error;
+ chip = card->private_data;
+
+ error = snd_card_ad1816a_pnp(dev, pcard, pid);
+ if (error)
+ return error;
+
+ error = snd_ad1816a_create(card, port[dev],
+ irq[dev],
+ dma1[dev],
+ dma2[dev],
+ chip);
+ if (error)
+ return error;
+ if (clockfreq[dev] >= 5000 && clockfreq[dev] <= 100000)
+ chip->clock_freq = clockfreq[dev];
+
+ strcpy(card->driver, "AD1816A");
+ strcpy(card->shortname, "ADI SoundPort AD1816A");
+ sprintf(card->longname, "%s, SS at 0x%lx, irq %d, dma %d&%d",
+ card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]);
+
+ error = snd_ad1816a_pcm(chip, 0);
+ if (error < 0)
+ return error;
+
+ error = snd_ad1816a_mixer(chip);
+ if (error < 0)
+ return error;
+
+ error = snd_ad1816a_timer(chip, 0);
+ if (error < 0)
+ return error;
+
+ if (mpu_port[dev] > 0) {
+ if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+ mpu_port[dev], 0, mpu_irq[dev],
+ NULL) < 0)
+ printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n", mpu_port[dev]);
+ }
+
+ if (fm_port[dev] > 0) {
+ if (snd_opl3_create(card,
+ fm_port[dev], fm_port[dev] + 2,
+ OPL3_HW_AUTO, 0, &opl3) < 0) {
+ printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx.\n", fm_port[dev], fm_port[dev] + 2);
+ } else {
+ error = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ error = snd_card_register(card);
+ if (error < 0)
+ return error;
+ pnp_set_card_drvdata(pcard, card);
+ return 0;
+}
+
+static unsigned int ad1816a_devices;
+
+static int snd_ad1816a_pnp_detect(struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ static int dev;
+ int res;
+
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (!enable[dev])
+ continue;
+ res = snd_card_ad1816a_probe(dev, card, id);
+ if (res < 0)
+ return res;
+ dev++;
+ ad1816a_devices++;
+ return 0;
+ }
+ return -ENODEV;
+}
+
+#ifdef CONFIG_PM
+static int snd_ad1816a_pnp_suspend(struct pnp_card_link *pcard,
+ pm_message_t state)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ snd_ad1816a_suspend(card->private_data);
+ return 0;
+}
+
+static int snd_ad1816a_pnp_resume(struct pnp_card_link *pcard)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+
+ snd_ad1816a_resume(card->private_data);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+static struct pnp_card_driver ad1816a_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = "ad1816a",
+ .id_table = snd_ad1816a_pnpids,
+ .probe = snd_ad1816a_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_ad1816a_pnp_suspend,
+ .resume = snd_ad1816a_pnp_resume,
+#endif
+};
+
+static int __init alsa_card_ad1816a_init(void)
+{
+ int err;
+
+ err = pnp_register_card_driver(&ad1816a_pnpc_driver);
+ if (err)
+ return err;
+
+ if (!ad1816a_devices) {
+ pnp_unregister_card_driver(&ad1816a_pnpc_driver);
+#ifdef MODULE
+ printk(KERN_ERR "no AD1816A based soundcards found.\n");
+#endif /* MODULE */
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void __exit alsa_card_ad1816a_exit(void)
+{
+ pnp_unregister_card_driver(&ad1816a_pnpc_driver);
+}
+
+module_init(alsa_card_ad1816a_init)
+module_exit(alsa_card_ad1816a_exit)
diff --git a/sound/isa/ad1816a/ad1816a_lib.c b/sound/isa/ad1816a/ad1816a_lib.c
new file mode 100644
index 0000000000..132a095dca
--- /dev/null
+++ b/sound/isa/ad1816a/ad1816a_lib.c
@@ -0,0 +1,923 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ ad1816a.c - lowlevel code for Analog Devices AD1816A chip.
+ Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+*/
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/ad1816a.h>
+
+#include <asm/dma.h>
+
+static inline int snd_ad1816a_busy_wait(struct snd_ad1816a *chip)
+{
+ int timeout;
+
+ for (timeout = 1000; timeout-- > 0; udelay(10))
+ if (inb(AD1816A_REG(AD1816A_CHIP_STATUS)) & AD1816A_READY)
+ return 0;
+
+ snd_printk(KERN_WARNING "chip busy.\n");
+ return -EBUSY;
+}
+
+static inline unsigned char snd_ad1816a_in(struct snd_ad1816a *chip, unsigned char reg)
+{
+ snd_ad1816a_busy_wait(chip);
+ return inb(AD1816A_REG(reg));
+}
+
+static inline void snd_ad1816a_out(struct snd_ad1816a *chip, unsigned char reg,
+ unsigned char value)
+{
+ snd_ad1816a_busy_wait(chip);
+ outb(value, AD1816A_REG(reg));
+}
+
+static inline void snd_ad1816a_out_mask(struct snd_ad1816a *chip, unsigned char reg,
+ unsigned char mask, unsigned char value)
+{
+ snd_ad1816a_out(chip, reg,
+ (value & mask) | (snd_ad1816a_in(chip, reg) & ~mask));
+}
+
+static unsigned short snd_ad1816a_read(struct snd_ad1816a *chip, unsigned char reg)
+{
+ snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f);
+ return snd_ad1816a_in(chip, AD1816A_INDIR_DATA_LOW) |
+ (snd_ad1816a_in(chip, AD1816A_INDIR_DATA_HIGH) << 8);
+}
+
+static void snd_ad1816a_write(struct snd_ad1816a *chip, unsigned char reg,
+ unsigned short value)
+{
+ snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f);
+ snd_ad1816a_out(chip, AD1816A_INDIR_DATA_LOW, value & 0xff);
+ snd_ad1816a_out(chip, AD1816A_INDIR_DATA_HIGH, (value >> 8) & 0xff);
+}
+
+static void snd_ad1816a_write_mask(struct snd_ad1816a *chip, unsigned char reg,
+ unsigned short mask, unsigned short value)
+{
+ snd_ad1816a_write(chip, reg,
+ (value & mask) | (snd_ad1816a_read(chip, reg) & ~mask));
+}
+
+
+static unsigned char snd_ad1816a_get_format(struct snd_ad1816a *chip,
+ snd_pcm_format_t format,
+ int channels)
+{
+ unsigned char retval = AD1816A_FMT_LINEAR_8;
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_MU_LAW:
+ retval = AD1816A_FMT_ULAW_8;
+ break;
+ case SNDRV_PCM_FORMAT_A_LAW:
+ retval = AD1816A_FMT_ALAW_8;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ retval = AD1816A_FMT_LINEAR_16_LIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16_BE:
+ retval = AD1816A_FMT_LINEAR_16_BIG;
+ }
+ return (channels > 1) ? (retval | AD1816A_FMT_STEREO) : retval;
+}
+
+static int snd_ad1816a_open(struct snd_ad1816a *chip, unsigned int mode)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ if (chip->mode & mode) {
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return -EAGAIN;
+ }
+
+ switch ((mode &= AD1816A_MODE_OPEN)) {
+ case AD1816A_MODE_PLAYBACK:
+ snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+ AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
+ snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+ AD1816A_PLAYBACK_IRQ_ENABLE, 0xffff);
+ break;
+ case AD1816A_MODE_CAPTURE:
+ snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+ AD1816A_CAPTURE_IRQ_PENDING, 0x00);
+ snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+ AD1816A_CAPTURE_IRQ_ENABLE, 0xffff);
+ break;
+ case AD1816A_MODE_TIMER:
+ snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+ AD1816A_TIMER_IRQ_PENDING, 0x00);
+ snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+ AD1816A_TIMER_IRQ_ENABLE, 0xffff);
+ }
+ chip->mode |= mode;
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return 0;
+}
+
+static void snd_ad1816a_close(struct snd_ad1816a *chip, unsigned int mode)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ switch ((mode &= AD1816A_MODE_OPEN)) {
+ case AD1816A_MODE_PLAYBACK:
+ snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+ AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
+ snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+ AD1816A_PLAYBACK_IRQ_ENABLE, 0x0000);
+ break;
+ case AD1816A_MODE_CAPTURE:
+ snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+ AD1816A_CAPTURE_IRQ_PENDING, 0x00);
+ snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+ AD1816A_CAPTURE_IRQ_ENABLE, 0x0000);
+ break;
+ case AD1816A_MODE_TIMER:
+ snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+ AD1816A_TIMER_IRQ_PENDING, 0x00);
+ snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+ AD1816A_TIMER_IRQ_ENABLE, 0x0000);
+ }
+ chip->mode &= ~mode;
+ if (!(chip->mode & AD1816A_MODE_OPEN))
+ chip->mode = 0;
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+static int snd_ad1816a_trigger(struct snd_ad1816a *chip, unsigned char what,
+ int channel, int cmd, int iscapture)
+{
+ int error = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_STOP:
+ spin_lock(&chip->lock);
+ cmd = (cmd == SNDRV_PCM_TRIGGER_START) ? 0xff: 0x00;
+ /* if (what & AD1816A_PLAYBACK_ENABLE) */
+ /* That is not valid, because playback and capture enable
+ * are the same bit pattern, just to different addresses
+ */
+ if (! iscapture)
+ snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+ AD1816A_PLAYBACK_ENABLE, cmd);
+ else
+ snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+ AD1816A_CAPTURE_ENABLE, cmd);
+ spin_unlock(&chip->lock);
+ break;
+ default:
+ snd_printk(KERN_WARNING "invalid trigger mode 0x%x.\n", what);
+ error = -EINVAL;
+ }
+
+ return error;
+}
+
+static int snd_ad1816a_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+ return snd_ad1816a_trigger(chip, AD1816A_PLAYBACK_ENABLE,
+ SNDRV_PCM_STREAM_PLAYBACK, cmd, 0);
+}
+
+static int snd_ad1816a_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+ return snd_ad1816a_trigger(chip, AD1816A_CAPTURE_ENABLE,
+ SNDRV_PCM_STREAM_CAPTURE, cmd, 1);
+}
+
+static int snd_ad1816a_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+ unsigned long flags;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int size, rate;
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ chip->p_dma_size = size = snd_pcm_lib_buffer_bytes(substream);
+ snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+ AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);
+
+ snd_dma_program(chip->dma1, runtime->dma_addr, size,
+ DMA_MODE_WRITE | DMA_AUTOINIT);
+
+ rate = runtime->rate;
+ if (chip->clock_freq)
+ rate = (rate * 33000) / chip->clock_freq;
+ snd_ad1816a_write(chip, AD1816A_PLAYBACK_SAMPLE_RATE, rate);
+ snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+ AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
+ snd_ad1816a_get_format(chip, runtime->format,
+ runtime->channels));
+
+ snd_ad1816a_write(chip, AD1816A_PLAYBACK_BASE_COUNT,
+ snd_pcm_lib_period_bytes(substream) / 4 - 1);
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return 0;
+}
+
+static int snd_ad1816a_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+ unsigned long flags;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int size, rate;
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ chip->c_dma_size = size = snd_pcm_lib_buffer_bytes(substream);
+ snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+ AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);
+
+ snd_dma_program(chip->dma2, runtime->dma_addr, size,
+ DMA_MODE_READ | DMA_AUTOINIT);
+
+ rate = runtime->rate;
+ if (chip->clock_freq)
+ rate = (rate * 33000) / chip->clock_freq;
+ snd_ad1816a_write(chip, AD1816A_CAPTURE_SAMPLE_RATE, rate);
+ snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+ AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
+ snd_ad1816a_get_format(chip, runtime->format,
+ runtime->channels));
+
+ snd_ad1816a_write(chip, AD1816A_CAPTURE_BASE_COUNT,
+ snd_pcm_lib_period_bytes(substream) / 4 - 1);
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return 0;
+}
+
+
+static snd_pcm_uframes_t snd_ad1816a_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+ if (!(chip->mode & AD1816A_MODE_PLAYBACK))
+ return 0;
+ ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_ad1816a_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+ if (!(chip->mode & AD1816A_MODE_CAPTURE))
+ return 0;
+ ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+
+static irqreturn_t snd_ad1816a_interrupt(int irq, void *dev_id)
+{
+ struct snd_ad1816a *chip = dev_id;
+ unsigned char status;
+
+ spin_lock(&chip->lock);
+ status = snd_ad1816a_in(chip, AD1816A_INTERRUPT_STATUS);
+ spin_unlock(&chip->lock);
+
+ if ((status & AD1816A_PLAYBACK_IRQ_PENDING) && chip->playback_substream)
+ snd_pcm_period_elapsed(chip->playback_substream);
+
+ if ((status & AD1816A_CAPTURE_IRQ_PENDING) && chip->capture_substream)
+ snd_pcm_period_elapsed(chip->capture_substream);
+
+ if ((status & AD1816A_TIMER_IRQ_PENDING) && chip->timer)
+ snd_timer_interrupt(chip->timer, chip->timer->sticks);
+
+ spin_lock(&chip->lock);
+ snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00);
+ spin_unlock(&chip->lock);
+ return IRQ_HANDLED;
+}
+
+
+static const struct snd_pcm_hardware snd_ad1816a_playback = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE),
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 55200,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_ad1816a_capture = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE),
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 55200,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static int snd_ad1816a_timer_close(struct snd_timer *timer)
+{
+ struct snd_ad1816a *chip = snd_timer_chip(timer);
+ snd_ad1816a_close(chip, AD1816A_MODE_TIMER);
+ return 0;
+}
+
+static int snd_ad1816a_timer_open(struct snd_timer *timer)
+{
+ struct snd_ad1816a *chip = snd_timer_chip(timer);
+ snd_ad1816a_open(chip, AD1816A_MODE_TIMER);
+ return 0;
+}
+
+static unsigned long snd_ad1816a_timer_resolution(struct snd_timer *timer)
+{
+ if (snd_BUG_ON(!timer))
+ return 0;
+
+ return 10000;
+}
+
+static int snd_ad1816a_timer_start(struct snd_timer *timer)
+{
+ unsigned short bits;
+ unsigned long flags;
+ struct snd_ad1816a *chip = snd_timer_chip(timer);
+ spin_lock_irqsave(&chip->lock, flags);
+ bits = snd_ad1816a_read(chip, AD1816A_INTERRUPT_ENABLE);
+
+ if (!(bits & AD1816A_TIMER_ENABLE)) {
+ snd_ad1816a_write(chip, AD1816A_TIMER_BASE_COUNT,
+ timer->sticks & 0xffff);
+
+ snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+ AD1816A_TIMER_ENABLE, 0xffff);
+ }
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return 0;
+}
+
+static int snd_ad1816a_timer_stop(struct snd_timer *timer)
+{
+ unsigned long flags;
+ struct snd_ad1816a *chip = snd_timer_chip(timer);
+ spin_lock_irqsave(&chip->lock, flags);
+
+ snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+ AD1816A_TIMER_ENABLE, 0x0000);
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return 0;
+}
+
+static const struct snd_timer_hardware snd_ad1816a_timer_table = {
+ .flags = SNDRV_TIMER_HW_AUTO,
+ .resolution = 10000,
+ .ticks = 65535,
+ .open = snd_ad1816a_timer_open,
+ .close = snd_ad1816a_timer_close,
+ .c_resolution = snd_ad1816a_timer_resolution,
+ .start = snd_ad1816a_timer_start,
+ .stop = snd_ad1816a_timer_stop,
+};
+
+static int snd_ad1816a_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int error;
+
+ error = snd_ad1816a_open(chip, AD1816A_MODE_PLAYBACK);
+ if (error < 0)
+ return error;
+ runtime->hw = snd_ad1816a_playback;
+ snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max);
+ snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max);
+ chip->playback_substream = substream;
+ return 0;
+}
+
+static int snd_ad1816a_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int error;
+
+ error = snd_ad1816a_open(chip, AD1816A_MODE_CAPTURE);
+ if (error < 0)
+ return error;
+ runtime->hw = snd_ad1816a_capture;
+ snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
+ snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max);
+ chip->capture_substream = substream;
+ return 0;
+}
+
+static int snd_ad1816a_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+
+ chip->playback_substream = NULL;
+ snd_ad1816a_close(chip, AD1816A_MODE_PLAYBACK);
+ return 0;
+}
+
+static int snd_ad1816a_capture_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+
+ chip->capture_substream = NULL;
+ snd_ad1816a_close(chip, AD1816A_MODE_CAPTURE);
+ return 0;
+}
+
+
+static void snd_ad1816a_init(struct snd_ad1816a *chip)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00);
+ snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+ AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);
+ snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+ AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);
+ snd_ad1816a_write(chip, AD1816A_INTERRUPT_ENABLE, 0x0000);
+ snd_ad1816a_write_mask(chip, AD1816A_CHIP_CONFIG,
+ AD1816A_CAPTURE_NOT_EQUAL | AD1816A_WSS_ENABLE, 0xffff);
+ snd_ad1816a_write(chip, AD1816A_DSP_CONFIG, 0x0000);
+ snd_ad1816a_write(chip, AD1816A_POWERDOWN_CTRL, 0x0000);
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+#ifdef CONFIG_PM
+void snd_ad1816a_suspend(struct snd_ad1816a *chip)
+{
+ int reg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ for (reg = 0; reg < 48; reg++)
+ chip->image[reg] = snd_ad1816a_read(chip, reg);
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+void snd_ad1816a_resume(struct snd_ad1816a *chip)
+{
+ int reg;
+ unsigned long flags;
+
+ snd_ad1816a_init(chip);
+ spin_lock_irqsave(&chip->lock, flags);
+ for (reg = 0; reg < 48; reg++)
+ snd_ad1816a_write(chip, reg, chip->image[reg]);
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+#endif
+
+static int snd_ad1816a_probe(struct snd_ad1816a *chip)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ switch (chip->version = snd_ad1816a_read(chip, AD1816A_VERSION_ID)) {
+ case 0:
+ chip->hardware = AD1816A_HW_AD1815;
+ break;
+ case 1:
+ chip->hardware = AD1816A_HW_AD18MAX10;
+ break;
+ case 3:
+ chip->hardware = AD1816A_HW_AD1816A;
+ break;
+ default:
+ chip->hardware = AD1816A_HW_AUTO;
+ }
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return 0;
+}
+
+static const char *snd_ad1816a_chip_id(struct snd_ad1816a *chip)
+{
+ switch (chip->hardware) {
+ case AD1816A_HW_AD1816A: return "AD1816A";
+ case AD1816A_HW_AD1815: return "AD1815";
+ case AD1816A_HW_AD18MAX10: return "AD18max10";
+ default:
+ snd_printk(KERN_WARNING "Unknown chip version %d:%d.\n",
+ chip->version, chip->hardware);
+ return "AD1816A - unknown";
+ }
+}
+
+int snd_ad1816a_create(struct snd_card *card,
+ unsigned long port, int irq, int dma1, int dma2,
+ struct snd_ad1816a *chip)
+{
+ int error;
+
+ chip->irq = -1;
+ chip->dma1 = -1;
+ chip->dma2 = -1;
+
+ chip->res_port = devm_request_region(card->dev, port, 16, "AD1816A");
+ if (!chip->res_port) {
+ snd_printk(KERN_ERR "ad1816a: can't grab port 0x%lx\n", port);
+ return -EBUSY;
+ }
+ if (devm_request_irq(card->dev, irq, snd_ad1816a_interrupt, 0,
+ "AD1816A", (void *) chip)) {
+ snd_printk(KERN_ERR "ad1816a: can't grab IRQ %d\n", irq);
+ return -EBUSY;
+ }
+ chip->irq = irq;
+ card->sync_irq = chip->irq;
+ if (snd_devm_request_dma(card->dev, dma1, "AD1816A - 1")) {
+ snd_printk(KERN_ERR "ad1816a: can't grab DMA1 %d\n", dma1);
+ return -EBUSY;
+ }
+ chip->dma1 = dma1;
+ if (snd_devm_request_dma(card->dev, dma2, "AD1816A - 2")) {
+ snd_printk(KERN_ERR "ad1816a: can't grab DMA2 %d\n", dma2);
+ return -EBUSY;
+ }
+ chip->dma2 = dma2;
+
+ chip->card = card;
+ chip->port = port;
+ spin_lock_init(&chip->lock);
+
+ error = snd_ad1816a_probe(chip);
+ if (error)
+ return error;
+
+ snd_ad1816a_init(chip);
+
+ return 0;
+}
+
+static const struct snd_pcm_ops snd_ad1816a_playback_ops = {
+ .open = snd_ad1816a_playback_open,
+ .close = snd_ad1816a_playback_close,
+ .prepare = snd_ad1816a_playback_prepare,
+ .trigger = snd_ad1816a_playback_trigger,
+ .pointer = snd_ad1816a_playback_pointer,
+};
+
+static const struct snd_pcm_ops snd_ad1816a_capture_ops = {
+ .open = snd_ad1816a_capture_open,
+ .close = snd_ad1816a_capture_close,
+ .prepare = snd_ad1816a_capture_prepare,
+ .trigger = snd_ad1816a_capture_trigger,
+ .pointer = snd_ad1816a_capture_pointer,
+};
+
+int snd_ad1816a_pcm(struct snd_ad1816a *chip, int device)
+{
+ int error;
+ struct snd_pcm *pcm;
+
+ error = snd_pcm_new(chip->card, "AD1816A", device, 1, 1, &pcm);
+ if (error)
+ return error;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ad1816a_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ad1816a_capture_ops);
+
+ pcm->private_data = chip;
+ pcm->info_flags = (chip->dma1 == chip->dma2 ) ? SNDRV_PCM_INFO_JOINT_DUPLEX : 0;
+
+ strcpy(pcm->name, snd_ad1816a_chip_id(chip));
+ snd_ad1816a_init(chip);
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, chip->card->dev,
+ 64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
+
+ chip->pcm = pcm;
+ return 0;
+}
+
+int snd_ad1816a_timer(struct snd_ad1816a *chip, int device)
+{
+ struct snd_timer *timer;
+ struct snd_timer_id tid;
+ int error;
+
+ tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+ tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+ tid.card = chip->card->number;
+ tid.device = device;
+ tid.subdevice = 0;
+ error = snd_timer_new(chip->card, "AD1816A", &tid, &timer);
+ if (error < 0)
+ return error;
+ strcpy(timer->name, snd_ad1816a_chip_id(chip));
+ timer->private_data = chip;
+ chip->timer = timer;
+ timer->hw = snd_ad1816a_timer_table;
+ return 0;
+}
+
+/*
+ *
+ */
+
+static int snd_ad1816a_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[8] = {
+ "Line", "Mix", "CD", "Synth", "Video",
+ "Mic", "Phone",
+ };
+
+ return snd_ctl_enum_info(uinfo, 2, 7, texts);
+}
+
+static int snd_ad1816a_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ unsigned short val;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ val = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL);
+ spin_unlock_irqrestore(&chip->lock, flags);
+ ucontrol->value.enumerated.item[0] = (val >> 12) & 7;
+ ucontrol->value.enumerated.item[1] = (val >> 4) & 7;
+ return 0;
+}
+
+static int snd_ad1816a_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ unsigned short val;
+ int change;
+
+ if (ucontrol->value.enumerated.item[0] > 6 ||
+ ucontrol->value.enumerated.item[1] > 6)
+ return -EINVAL;
+ val = (ucontrol->value.enumerated.item[0] << 12) |
+ (ucontrol->value.enumerated.item[1] << 4);
+ spin_lock_irqsave(&chip->lock, flags);
+ change = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL) != val;
+ snd_ad1816a_write(chip, AD1816A_ADC_SOURCE_SEL, val);
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return change;
+}
+
+#define AD1816A_SINGLE_TLV(xname, reg, shift, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .name = xname, .info = snd_ad1816a_info_single, \
+ .get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \
+ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
+ .tlv = { .p = (xtlv) } }
+#define AD1816A_SINGLE(xname, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_single, \
+ .get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \
+ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_ad1816a_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_ad1816a_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ ucontrol->value.integer.value[0] = (snd_ad1816a_read(chip, reg) >> shift) & mask;
+ spin_unlock_irqrestore(&chip->lock, flags);
+ if (invert)
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int snd_ad1816a_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+ int change;
+ unsigned short old_val, val;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+ if (invert)
+ val = mask - val;
+ val <<= shift;
+ spin_lock_irqsave(&chip->lock, flags);
+ old_val = snd_ad1816a_read(chip, reg);
+ val = (old_val & ~(mask << shift)) | val;
+ change = val != old_val;
+ snd_ad1816a_write(chip, reg, val);
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return change;
+}
+
+#define AD1816A_DOUBLE_TLV(xname, reg, shift_left, shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .name = xname, .info = snd_ad1816a_info_double, \
+ .get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \
+ .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24), \
+ .tlv = { .p = (xtlv) } }
+
+#define AD1816A_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_double, \
+ .get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \
+ .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24) }
+
+static int snd_ad1816a_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_ad1816a_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift_left = (kcontrol->private_value >> 8) & 0x0f;
+ int shift_right = (kcontrol->private_value >> 12) & 0x0f;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+ unsigned short val;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ val = snd_ad1816a_read(chip, reg);
+ ucontrol->value.integer.value[0] = (val >> shift_left) & mask;
+ ucontrol->value.integer.value[1] = (val >> shift_right) & mask;
+ spin_unlock_irqrestore(&chip->lock, flags);
+ if (invert) {
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+ }
+ return 0;
+}
+
+static int snd_ad1816a_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift_left = (kcontrol->private_value >> 8) & 0x0f;
+ int shift_right = (kcontrol->private_value >> 12) & 0x0f;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+ int change;
+ unsigned short old_val, val1, val2;
+
+ val1 = ucontrol->value.integer.value[0] & mask;
+ val2 = ucontrol->value.integer.value[1] & mask;
+ if (invert) {
+ val1 = mask - val1;
+ val2 = mask - val2;
+ }
+ val1 <<= shift_left;
+ val2 <<= shift_right;
+ spin_lock_irqsave(&chip->lock, flags);
+ old_val = snd_ad1816a_read(chip, reg);
+ val1 = (old_val & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+ change = val1 != old_val;
+ snd_ad1816a_write(chip, reg, val1);
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+
+static const struct snd_kcontrol_new snd_ad1816a_controls[] = {
+AD1816A_DOUBLE("Master Playback Switch", AD1816A_MASTER_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Master Playback Volume", AD1816A_MASTER_ATT, 8, 0, 31, 1,
+ db_scale_5bit),
+AD1816A_DOUBLE("PCM Playback Switch", AD1816A_VOICE_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("PCM Playback Volume", AD1816A_VOICE_ATT, 8, 0, 63, 1,
+ db_scale_6bit),
+AD1816A_DOUBLE("Line Playback Switch", AD1816A_LINE_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Line Playback Volume", AD1816A_LINE_GAIN_ATT, 8, 0, 31, 1,
+ db_scale_5bit_12db_max),
+AD1816A_DOUBLE("CD Playback Switch", AD1816A_CD_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("CD Playback Volume", AD1816A_CD_GAIN_ATT, 8, 0, 31, 1,
+ db_scale_5bit_12db_max),
+AD1816A_DOUBLE("Synth Playback Switch", AD1816A_SYNTH_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Synth Playback Volume", AD1816A_SYNTH_GAIN_ATT, 8, 0, 31, 1,
+ db_scale_5bit_12db_max),
+AD1816A_DOUBLE("FM Playback Switch", AD1816A_FM_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("FM Playback Volume", AD1816A_FM_ATT, 8, 0, 63, 1,
+ db_scale_6bit),
+AD1816A_SINGLE("Mic Playback Switch", AD1816A_MIC_GAIN_ATT, 15, 1, 1),
+AD1816A_SINGLE_TLV("Mic Playback Volume", AD1816A_MIC_GAIN_ATT, 8, 31, 1,
+ db_scale_5bit_12db_max),
+AD1816A_SINGLE("Mic Boost", AD1816A_MIC_GAIN_ATT, 14, 1, 0),
+AD1816A_DOUBLE("Video Playback Switch", AD1816A_VID_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Video Playback Volume", AD1816A_VID_GAIN_ATT, 8, 0, 31, 1,
+ db_scale_5bit_12db_max),
+AD1816A_SINGLE("Phone Capture Switch", AD1816A_PHONE_IN_GAIN_ATT, 15, 1, 1),
+AD1816A_SINGLE_TLV("Phone Capture Volume", AD1816A_PHONE_IN_GAIN_ATT, 0, 15, 1,
+ db_scale_4bit),
+AD1816A_SINGLE("Phone Playback Switch", AD1816A_PHONE_OUT_ATT, 7, 1, 1),
+AD1816A_SINGLE_TLV("Phone Playback Volume", AD1816A_PHONE_OUT_ATT, 0, 31, 1,
+ db_scale_5bit),
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = snd_ad1816a_info_mux,
+ .get = snd_ad1816a_get_mux,
+ .put = snd_ad1816a_put_mux,
+},
+AD1816A_DOUBLE("Capture Switch", AD1816A_ADC_PGA, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Capture Volume", AD1816A_ADC_PGA, 8, 0, 15, 0,
+ db_scale_rec_gain),
+AD1816A_SINGLE("3D Control - Switch", AD1816A_3D_PHAT_CTRL, 15, 1, 1),
+AD1816A_SINGLE("3D Control - Level", AD1816A_3D_PHAT_CTRL, 0, 15, 0),
+};
+
+int snd_ad1816a_mixer(struct snd_ad1816a *chip)
+{
+ struct snd_card *card;
+ unsigned int idx;
+ int err;
+
+ if (snd_BUG_ON(!chip || !chip->card))
+ return -EINVAL;
+
+ card = chip->card;
+
+ strcpy(card->mixername, snd_ad1816a_chip_id(chip));
+
+ for (idx = 0; idx < ARRAY_SIZE(snd_ad1816a_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_ad1816a_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
diff --git a/sound/isa/ad1848/Makefile b/sound/isa/ad1848/Makefile
new file mode 100644
index 0000000000..4eab89bbc8
--- /dev/null
+++ b/sound/isa/ad1848/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ad1848-objs := ad1848.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AD1848) += snd-ad1848.o
+
diff --git a/sound/isa/ad1848/ad1848.c b/sound/isa/ad1848/ad1848.c
new file mode 100644
index 0000000000..401d8df28d
--- /dev/null
+++ b/sound/isa/ad1848/ad1848.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Generic driver for AD1848/AD1847/CS4248 chips (0.1 Alpha)
+ * Copyright (c) by Tugrul Galatali <galatalt@stuy.edu>,
+ * Jaroslav Kysela <perex@perex.cz>
+ * Based on card-4232.c by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/initval.h>
+
+#define CRD_NAME "Generic AD1848/AD1847/CS4248"
+#define DEV_NAME "ad1848"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Tugrul Galatali <galatalt@stuy.edu>, Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
+static bool thinkpad[SNDRV_CARDS]; /* Thinkpad special case */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
+module_param_array(thinkpad, bool, NULL, 0444);
+MODULE_PARM_DESC(thinkpad, "Enable only for the onboard CS4248 of IBM Thinkpad 360/750/755 series.");
+
+static int snd_ad1848_match(struct device *dev, unsigned int n)
+{
+ if (!enable[n])
+ return 0;
+
+ if (port[n] == SNDRV_AUTO_PORT) {
+ dev_err(dev, "please specify port\n");
+ return 0;
+ }
+ if (irq[n] == SNDRV_AUTO_IRQ) {
+ dev_err(dev, "please specify irq\n");
+ return 0;
+ }
+ if (dma1[n] == SNDRV_AUTO_DMA) {
+ dev_err(dev, "please specify dma1\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int snd_ad1848_probe(struct device *dev, unsigned int n)
+{
+ struct snd_card *card;
+ struct snd_wss *chip;
+ int error;
+
+ error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
+ if (error < 0)
+ return error;
+
+ error = snd_wss_create(card, port[n], -1, irq[n], dma1[n], -1,
+ thinkpad[n] ? WSS_HW_THINKPAD : WSS_HW_DETECT,
+ 0, &chip);
+ if (error < 0)
+ return error;
+
+ card->private_data = chip;
+
+ error = snd_wss_pcm(chip, 0);
+ if (error < 0)
+ return error;
+
+ error = snd_wss_mixer(chip);
+ if (error < 0)
+ return error;
+
+ strscpy(card->driver, "AD1848", sizeof(card->driver));
+ strscpy(card->shortname, chip->pcm->name, sizeof(card->shortname));
+
+ if (!thinkpad[n])
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx, irq %d, dma %d",
+ chip->pcm->name, chip->port, irq[n], dma1[n]);
+ else
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx, irq %d, dma %d [Thinkpad]",
+ chip->pcm->name, chip->port, irq[n], dma1[n]);
+
+ error = snd_card_register(card);
+ if (error < 0)
+ return error;
+
+ dev_set_drvdata(dev, card);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_ad1848_suspend(struct device *dev, unsigned int n, pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_wss *chip = card->private_data;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ chip->suspend(chip);
+ return 0;
+}
+
+static int snd_ad1848_resume(struct device *dev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_wss *chip = card->private_data;
+
+ chip->resume(chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+static struct isa_driver snd_ad1848_driver = {
+ .match = snd_ad1848_match,
+ .probe = snd_ad1848_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_ad1848_suspend,
+ .resume = snd_ad1848_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ }
+};
+
+module_isa_driver(snd_ad1848_driver, SNDRV_CARDS);
diff --git a/sound/isa/adlib.c b/sound/isa/adlib.c
new file mode 100644
index 0000000000..f079ba4ef1
--- /dev/null
+++ b/sound/isa/adlib.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AdLib FM card driver.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/isa.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/opl3.h>
+
+#define CRD_NAME "AdLib FM"
+#define DEV_NAME "adlib"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Rene Herman");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+
+static int snd_adlib_match(struct device *dev, unsigned int n)
+{
+ if (!enable[n])
+ return 0;
+
+ if (port[n] == SNDRV_AUTO_PORT) {
+ dev_err(dev, "please specify port\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int snd_adlib_probe(struct device *dev, unsigned int n)
+{
+ struct snd_card *card;
+ struct snd_opl3 *opl3;
+ int error;
+
+ error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
+ if (error < 0) {
+ dev_err(dev, "could not create card\n");
+ return error;
+ }
+
+ card->private_data = devm_request_region(dev, port[n], 4, CRD_NAME);
+ if (!card->private_data) {
+ dev_err(dev, "could not grab ports\n");
+ return -EBUSY;
+ }
+
+ strcpy(card->driver, DEV_NAME);
+ strcpy(card->shortname, CRD_NAME);
+ sprintf(card->longname, CRD_NAME " at %#lx", port[n]);
+
+ error = snd_opl3_create(card, port[n], port[n] + 2, OPL3_HW_AUTO, 1, &opl3);
+ if (error < 0) {
+ dev_err(dev, "could not create OPL\n");
+ return error;
+ }
+
+ error = snd_opl3_hwdep_new(opl3, 0, 0, NULL);
+ if (error < 0) {
+ dev_err(dev, "could not create FM\n");
+ return error;
+ }
+
+ error = snd_card_register(card);
+ if (error < 0) {
+ dev_err(dev, "could not register card\n");
+ return error;
+ }
+
+ dev_set_drvdata(dev, card);
+ return 0;
+}
+
+static struct isa_driver snd_adlib_driver = {
+ .match = snd_adlib_match,
+ .probe = snd_adlib_probe,
+
+ .driver = {
+ .name = DEV_NAME
+ }
+};
+
+module_isa_driver(snd_adlib_driver, SNDRV_CARDS);
diff --git a/sound/isa/als100.c b/sound/isa/als100.c
new file mode 100644
index 0000000000..d582eff640
--- /dev/null
+++ b/sound/isa/als100.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ card-als100.c - driver for Avance Logic ALS100 based soundcards.
+ Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+ Copyright (C) 1999-2002 by Massimo Piccioni <dafastidio@libero.it>
+
+ Thanks to Pierfrancesco 'qM2' Passerini.
+
+ Generalised for soundcards based on DT-0196 and ALS-007 chips
+ by Jonathan Woithe <jwoithe@just42.net>: June 2002.
+
+*/
+
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/time.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+
+#define PFX "als100: "
+
+MODULE_DESCRIPTION("Avance Logic ALS007/ALS1X0");
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* PnP setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* PnP setup */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Avance Logic based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Avance Logic based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Avance Logic based soundcard.");
+
+MODULE_ALIAS("snd-dt019x");
+
+struct snd_card_als100 {
+ struct pnp_dev *dev;
+ struct pnp_dev *devmpu;
+ struct pnp_dev *devopl;
+ struct snd_sb *chip;
+};
+
+static const struct pnp_card_device_id snd_als100_pnpids[] = {
+ /* DT197A30 */
+ { .id = "RWB1688",
+ .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } },
+ .driver_data = SB_HW_DT019X },
+ /* DT0196 / ALS-007 */
+ { .id = "ALS0007",
+ .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } },
+ .driver_data = SB_HW_DT019X },
+ /* ALS100 - PRO16PNP */
+ { .id = "ALS0001",
+ .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } },
+ .driver_data = SB_HW_ALS100 },
+ /* ALS110 - MF1000 - Digimate 3D Sound */
+ { .id = "ALS0110",
+ .devs = { { "@@@1001" }, { "@X@1001" }, { "@H@1001" } },
+ .driver_data = SB_HW_ALS100 },
+ /* ALS120 */
+ { .id = "ALS0120",
+ .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } },
+ .driver_data = SB_HW_ALS100 },
+ /* ALS200 */
+ { .id = "ALS0200",
+ .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0001" } },
+ .driver_data = SB_HW_ALS100 },
+ /* ALS200 OEM */
+ { .id = "ALS0200",
+ .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0020" } },
+ .driver_data = SB_HW_ALS100 },
+ /* RTL3000 */
+ { .id = "RTL3000",
+ .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } },
+ .driver_data = SB_HW_ALS100 },
+ { .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_als100_pnpids);
+
+static int snd_card_als100_pnp(int dev, struct snd_card_als100 *acard,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ struct pnp_dev *pdev;
+ int err;
+
+ acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (acard->dev == NULL)
+ return -ENODEV;
+
+ acard->devmpu = pnp_request_card_device(card, id->devs[1].id, acard->dev);
+ acard->devopl = pnp_request_card_device(card, id->devs[2].id, acard->dev);
+
+ pdev = acard->dev;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+ return err;
+ }
+ port[dev] = pnp_port_start(pdev, 0);
+ if (id->driver_data == SB_HW_DT019X)
+ dma8[dev] = pnp_dma(pdev, 0);
+ else {
+ dma8[dev] = pnp_dma(pdev, 1);
+ dma16[dev] = pnp_dma(pdev, 0);
+ }
+ irq[dev] = pnp_irq(pdev, 0);
+
+ pdev = acard->devmpu;
+ if (pdev != NULL) {
+ err = pnp_activate_dev(pdev);
+ if (err < 0)
+ goto __mpu_error;
+ mpu_port[dev] = pnp_port_start(pdev, 0);
+ mpu_irq[dev] = pnp_irq(pdev, 0);
+ } else {
+ __mpu_error:
+ if (pdev) {
+ pnp_release_card_device(pdev);
+ snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n");
+ }
+ acard->devmpu = NULL;
+ mpu_port[dev] = -1;
+ }
+
+ pdev = acard->devopl;
+ if (pdev != NULL) {
+ err = pnp_activate_dev(pdev);
+ if (err < 0)
+ goto __fm_error;
+ fm_port[dev] = pnp_port_start(pdev, 0);
+ } else {
+ __fm_error:
+ if (pdev) {
+ pnp_release_card_device(pdev);
+ snd_printk(KERN_ERR PFX "OPL3 pnp configure failure, skipping\n");
+ }
+ acard->devopl = NULL;
+ fm_port[dev] = -1;
+ }
+
+ return 0;
+}
+
+static int snd_card_als100_probe(int dev,
+ struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ int error;
+ struct snd_sb *chip;
+ struct snd_card *card;
+ struct snd_card_als100 *acard;
+ struct snd_opl3 *opl3;
+
+ error = snd_devm_card_new(&pcard->card->dev,
+ index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_card_als100), &card);
+ if (error < 0)
+ return error;
+ acard = card->private_data;
+
+ error = snd_card_als100_pnp(dev, acard, pcard, pid);
+ if (error)
+ return error;
+
+ if (pid->driver_data == SB_HW_DT019X)
+ dma16[dev] = -1;
+
+ error = snd_sbdsp_create(card, port[dev], irq[dev],
+ snd_sb16dsp_interrupt,
+ dma8[dev], dma16[dev],
+ pid->driver_data,
+ &chip);
+ if (error < 0)
+ return error;
+ acard->chip = chip;
+
+ if (pid->driver_data == SB_HW_DT019X) {
+ strcpy(card->driver, "DT-019X");
+ strcpy(card->shortname, "Diamond Tech. DT-019X");
+ snprintf(card->longname, sizeof(card->longname),
+ "Diamond Tech. DT-019X, %s at 0x%lx, irq %d, dma %d",
+ chip->name, chip->port, irq[dev], dma8[dev]);
+ } else {
+ strcpy(card->driver, "ALS100");
+ strcpy(card->shortname, "Avance Logic ALS100");
+ snprintf(card->longname, sizeof(card->longname),
+ "Avance Logic ALS100, %s at 0x%lx, irq %d, dma %d&%d",
+ chip->name, chip->port, irq[dev], dma8[dev],
+ dma16[dev]);
+ }
+
+ error = snd_sb16dsp_pcm(chip, 0);
+ if (error < 0)
+ return error;
+
+ error = snd_sbmixer_new(chip);
+ if (error < 0)
+ return error;
+
+ if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+ int mpu_type = MPU401_HW_ALS100;
+
+ if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+ mpu_irq[dev] = -1;
+
+ if (pid->driver_data == SB_HW_DT019X)
+ mpu_type = MPU401_HW_MPU401;
+
+ if (snd_mpu401_uart_new(card, 0,
+ mpu_type,
+ mpu_port[dev], 0,
+ mpu_irq[dev],
+ NULL) < 0)
+ snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]);
+ }
+
+ if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+ if (snd_opl3_create(card,
+ fm_port[dev], fm_port[dev] + 2,
+ OPL3_HW_AUTO, 0, &opl3) < 0) {
+ snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+ fm_port[dev], fm_port[dev] + 2);
+ } else {
+ error = snd_opl3_timer_new(opl3, 0, 1);
+ if (error < 0)
+ return error;
+ error = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ error = snd_card_register(card);
+ if (error < 0)
+ return error;
+ pnp_set_card_drvdata(pcard, card);
+ return 0;
+}
+
+static unsigned int als100_devices;
+
+static int snd_als100_pnp_detect(struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ static int dev;
+ int res;
+
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (!enable[dev])
+ continue;
+ res = snd_card_als100_probe(dev, card, id);
+ if (res < 0)
+ return res;
+ dev++;
+ als100_devices++;
+ return 0;
+ }
+ return -ENODEV;
+}
+
+#ifdef CONFIG_PM
+static int snd_als100_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+ struct snd_card_als100 *acard = card->private_data;
+ struct snd_sb *chip = acard->chip;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ snd_sbmixer_suspend(chip);
+ return 0;
+}
+
+static int snd_als100_pnp_resume(struct pnp_card_link *pcard)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+ struct snd_card_als100 *acard = card->private_data;
+ struct snd_sb *chip = acard->chip;
+
+ snd_sbdsp_reset(chip);
+ snd_sbmixer_resume(chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+static struct pnp_card_driver als100_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = "als100",
+ .id_table = snd_als100_pnpids,
+ .probe = snd_als100_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_als100_pnp_suspend,
+ .resume = snd_als100_pnp_resume,
+#endif
+};
+
+static int __init alsa_card_als100_init(void)
+{
+ int err;
+
+ err = pnp_register_card_driver(&als100_pnpc_driver);
+ if (err)
+ return err;
+
+ if (!als100_devices) {
+ pnp_unregister_card_driver(&als100_pnpc_driver);
+#ifdef MODULE
+ snd_printk(KERN_ERR "no Avance Logic based soundcards found\n");
+#endif
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void __exit alsa_card_als100_exit(void)
+{
+ pnp_unregister_card_driver(&als100_pnpc_driver);
+}
+
+module_init(alsa_card_als100_init)
+module_exit(alsa_card_als100_exit)
diff --git a/sound/isa/azt2320.c b/sound/isa/azt2320.c
new file mode 100644
index 0000000000..761cd198df
--- /dev/null
+++ b/sound/isa/azt2320.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ card-azt2320.c - driver for Aztech Systems AZT2320 based soundcards.
+ Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+*/
+
+/*
+ This driver should provide support for most Aztech AZT2320 based cards.
+ Several AZT2316 chips are also supported/tested, but autoprobe doesn't
+ work: all module option have to be set.
+
+ No docs available for us at Aztech headquarters !!! Unbelievable ...
+ No other help obtained.
+
+ Thanks to Rainer Wiesner <rainer.wiesner@01019freenet.de> for the WSS
+ activation method (full-duplex audio!).
+*/
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+
+#define PFX "azt2320: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("Aztech Systems AZT2320");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for azt2320 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for azt2320 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable azt2320 based soundcard.");
+
+struct snd_card_azt2320 {
+ int dev_no;
+ struct pnp_dev *dev;
+ struct pnp_dev *devmpu;
+ struct snd_wss *chip;
+};
+
+static const struct pnp_card_device_id snd_azt2320_pnpids[] = {
+ /* PRO16V */
+ { .id = "AZT1008", .devs = { { "AZT1008" }, { "AZT2001" }, } },
+ /* Aztech Sound Galaxy 16 */
+ { .id = "AZT2320", .devs = { { "AZT0001" }, { "AZT0002" }, } },
+ /* Packard Bell Sound III 336 AM/SP */
+ { .id = "AZT3000", .devs = { { "AZT1003" }, { "AZT2001" }, } },
+ /* AT3300 */
+ { .id = "AZT3002", .devs = { { "AZT1004" }, { "AZT2001" }, } },
+ /* --- */
+ { .id = "AZT3005", .devs = { { "AZT1003" }, { "AZT2001" }, } },
+ /* --- */
+ { .id = "AZT3011", .devs = { { "AZT1003" }, { "AZT2001" }, } },
+ { .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_azt2320_pnpids);
+
+#define DRIVER_NAME "snd-card-azt2320"
+
+static int snd_card_azt2320_pnp(int dev, struct snd_card_azt2320 *acard,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ struct pnp_dev *pdev;
+ int err;
+
+ acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (acard->dev == NULL)
+ return -ENODEV;
+
+ acard->devmpu = pnp_request_card_device(card, id->devs[1].id, NULL);
+
+ pdev = acard->dev;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+ return err;
+ }
+ port[dev] = pnp_port_start(pdev, 0);
+ fm_port[dev] = pnp_port_start(pdev, 1);
+ wss_port[dev] = pnp_port_start(pdev, 2);
+ dma1[dev] = pnp_dma(pdev, 0);
+ dma2[dev] = pnp_dma(pdev, 1);
+ irq[dev] = pnp_irq(pdev, 0);
+
+ pdev = acard->devmpu;
+ if (pdev != NULL) {
+ err = pnp_activate_dev(pdev);
+ if (err < 0)
+ goto __mpu_error;
+ mpu_port[dev] = pnp_port_start(pdev, 0);
+ mpu_irq[dev] = pnp_irq(pdev, 0);
+ } else {
+ __mpu_error:
+ if (pdev) {
+ pnp_release_card_device(pdev);
+ snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n");
+ }
+ acard->devmpu = NULL;
+ mpu_port[dev] = -1;
+ }
+
+ return 0;
+}
+
+/* same of snd_sbdsp_command by Jaroslav Kysela */
+static int snd_card_azt2320_command(unsigned long port, unsigned char val)
+{
+ int i;
+ unsigned long limit;
+
+ limit = jiffies + HZ / 10;
+ for (i = 50000; i && time_after(limit, jiffies); i--)
+ if (!(inb(port + 0x0c) & 0x80)) {
+ outb(val, port + 0x0c);
+ return 0;
+ }
+ return -EBUSY;
+}
+
+static int snd_card_azt2320_enable_wss(unsigned long port)
+{
+ int error;
+
+ error = snd_card_azt2320_command(port, 0x09);
+ if (error)
+ return error;
+ error = snd_card_azt2320_command(port, 0x00);
+ if (error)
+ return error;
+
+ mdelay(5);
+ return 0;
+}
+
+static int snd_card_azt2320_probe(int dev,
+ struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ int error;
+ struct snd_card *card;
+ struct snd_card_azt2320 *acard;
+ struct snd_wss *chip;
+ struct snd_opl3 *opl3;
+
+ error = snd_devm_card_new(&pcard->card->dev,
+ index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_card_azt2320), &card);
+ if (error < 0)
+ return error;
+ acard = card->private_data;
+
+ error = snd_card_azt2320_pnp(dev, acard, pcard, pid);
+ if (error)
+ return error;
+
+ error = snd_card_azt2320_enable_wss(port[dev]);
+ if (error)
+ return error;
+
+ error = snd_wss_create(card, wss_port[dev], -1,
+ irq[dev],
+ dma1[dev], dma2[dev],
+ WSS_HW_DETECT, 0, &chip);
+ if (error < 0)
+ return error;
+
+ strcpy(card->driver, "AZT2320");
+ strcpy(card->shortname, "Aztech AZT2320");
+ sprintf(card->longname, "%s, WSS at 0x%lx, irq %i, dma %i&%i",
+ card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]);
+
+ error = snd_wss_pcm(chip, 0);
+ if (error < 0)
+ return error;
+ error = snd_wss_mixer(chip);
+ if (error < 0)
+ return error;
+ error = snd_wss_timer(chip, 0);
+ if (error < 0)
+ return error;
+
+ if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+ if (snd_mpu401_uart_new(card, 0, MPU401_HW_AZT2320,
+ mpu_port[dev], 0,
+ mpu_irq[dev], NULL) < 0)
+ snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]);
+ }
+
+ if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+ if (snd_opl3_create(card,
+ fm_port[dev], fm_port[dev] + 2,
+ OPL3_HW_AUTO, 0, &opl3) < 0) {
+ snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+ fm_port[dev], fm_port[dev] + 2);
+ } else {
+ error = snd_opl3_timer_new(opl3, 1, 2);
+ if (error < 0)
+ return error;
+ error = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ error = snd_card_register(card);
+ if (error < 0)
+ return error;
+ pnp_set_card_drvdata(pcard, card);
+ return 0;
+}
+
+static unsigned int azt2320_devices;
+
+static int snd_azt2320_pnp_detect(struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ static int dev;
+ int res;
+
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (!enable[dev])
+ continue;
+ res = snd_card_azt2320_probe(dev, card, id);
+ if (res < 0)
+ return res;
+ dev++;
+ azt2320_devices++;
+ return 0;
+ }
+ return -ENODEV;
+}
+
+#ifdef CONFIG_PM
+static int snd_azt2320_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+ struct snd_card_azt2320 *acard = card->private_data;
+ struct snd_wss *chip = acard->chip;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ chip->suspend(chip);
+ return 0;
+}
+
+static int snd_azt2320_pnp_resume(struct pnp_card_link *pcard)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+ struct snd_card_azt2320 *acard = card->private_data;
+ struct snd_wss *chip = acard->chip;
+
+ chip->resume(chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+static struct pnp_card_driver azt2320_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = "azt2320",
+ .id_table = snd_azt2320_pnpids,
+ .probe = snd_azt2320_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_azt2320_pnp_suspend,
+ .resume = snd_azt2320_pnp_resume,
+#endif
+};
+
+static int __init alsa_card_azt2320_init(void)
+{
+ int err;
+
+ err = pnp_register_card_driver(&azt2320_pnpc_driver);
+ if (err)
+ return err;
+
+ if (!azt2320_devices) {
+ pnp_unregister_card_driver(&azt2320_pnpc_driver);
+#ifdef MODULE
+ snd_printk(KERN_ERR "no AZT2320 based soundcards found\n");
+#endif
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void __exit alsa_card_azt2320_exit(void)
+{
+ pnp_unregister_card_driver(&azt2320_pnpc_driver);
+}
+
+module_init(alsa_card_azt2320_init)
+module_exit(alsa_card_azt2320_exit)
diff --git a/sound/isa/cmi8328.c b/sound/isa/cmi8328.c
new file mode 100644
index 0000000000..8902cfb830
--- /dev/null
+++ b/sound/isa/cmi8328.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for C-Media CMI8328-based soundcards, such as AudioExcel AV500
+ * Copyright (c) 2012 Ondrej Zary
+ *
+ * AudioExcel AV500 card consists of:
+ * - CMI8328 - main chip (SB Pro emulation, gameport, OPL3, MPU401, CD-ROM)
+ * - CS4231A - WSS codec
+ * - Dream SAM9233+GMS950400+RAM+ROM: Wavetable MIDI, connected to MPU401
+ */
+
+#include <linux/init.h>
+#include <linux/isa.h>
+#include <linux/module.h>
+#include <linux/gameport.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.h>
+#define SNDRV_LEGACY_FIND_FREE_IOPORT
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Ondrej Zary <linux@rainbow-software.org>");
+MODULE_DESCRIPTION("C-Media CMI8328");
+MODULE_LICENSE("GPL");
+
+#if IS_ENABLED(CONFIG_GAMEPORT)
+#define SUPPORT_JOYSTICK 1
+#endif
+
+/* I/O port is configured by jumpers on the card to one of these */
+static const int cmi8328_ports[] = { 0x530, 0xe80, 0xf40, 0x604 };
+#define CMI8328_MAX ARRAY_SIZE(cmi8328_ports)
+
+static int index[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = -1};
+static char *id[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = NULL};
+static long port[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT};
+static int irq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ};
+static int dma1[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA};
+static int dma2[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA};
+static long mpuport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT};
+static int mpuirq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ};
+#ifdef SUPPORT_JOYSTICK
+static bool gameport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = true};
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for CMI8328 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for CMI8328 soundcard.");
+
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for CMI8328 driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for CMI8328 driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 for CMI8328 driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 for CMI8328 driver.");
+
+module_param_hw_array(mpuport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8328 driver.");
+module_param_hw_array(mpuirq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpuirq, "IRQ # for CMI8328 MPU-401 port.");
+#ifdef SUPPORT_JOYSTICK
+module_param_array(gameport, bool, NULL, 0444);
+MODULE_PARM_DESC(gameport, "Enable gameport.");
+#endif
+
+struct snd_cmi8328 {
+ u16 port;
+ u8 cfg[3];
+ u8 wss_cfg;
+ struct snd_card *card;
+ struct snd_wss *wss;
+#ifdef SUPPORT_JOYSTICK
+ struct gameport *gameport;
+#endif
+};
+
+/* CMI8328 configuration registers */
+#define CFG1 0x61
+#define CFG1_SB_DISABLE (1 << 0)
+#define CFG1_GAMEPORT (1 << 1)
+/*
+ * bit 0: SB: 0=enabled, 1=disabled
+ * bit 1: gameport: 0=disabled, 1=enabled
+ * bits 2-4: SB IRQ: 001=3, 010=5, 011=7, 100=9, 101=10, 110=11
+ * bits 5-6: SB DMA: 00=disabled (when SB disabled), 01=DMA0, 10=DMA1, 11=DMA3
+ * bit 7: SB port: 0=0x220, 1=0x240
+ */
+#define CFG2 0x62
+#define CFG2_MPU_ENABLE (1 << 2)
+/*
+ * bits 0-1: CD-ROM mode: 00=disabled, 01=Panasonic, 10=Sony/Mitsumi/Wearnes,
+ 11=IDE
+ * bit 2: MPU401: 0=disabled, 1=enabled
+ * bits 3-4: MPU401 IRQ: 00=3, 01=5, 10=7, 11=9,
+ * bits 5-7: MPU401 port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x332,
+ 101=0x334, 110=0x336
+ */
+#define CFG3 0x63
+/*
+ * bits 0-2: CD-ROM IRQ: 000=disabled, 001=3, 010=5, 011=7, 100=9, 101=10,
+ 110=11
+ * bits 3-4: CD-ROM DMA: 00=disabled, 01=DMA0, 10=DMA1, 11=DMA3
+ * bits 5-7: CD-ROM port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x340,
+ 101=0x350, 110=0x360, 111=0x370
+ */
+
+static u8 snd_cmi8328_cfg_read(u16 port, u8 reg)
+{
+ outb(0x43, port + 3);
+ outb(0x21, port + 3);
+ outb(reg, port + 3);
+ return inb(port);
+}
+
+static void snd_cmi8328_cfg_write(u16 port, u8 reg, u8 val)
+{
+ outb(0x43, port + 3);
+ outb(0x21, port + 3);
+ outb(reg, port + 3);
+ outb(val, port + 3); /* yes, value goes to the same port as index */
+}
+
+#ifdef CONFIG_PM
+static void snd_cmi8328_cfg_save(u16 port, u8 cfg[])
+{
+ cfg[0] = snd_cmi8328_cfg_read(port, CFG1);
+ cfg[1] = snd_cmi8328_cfg_read(port, CFG2);
+ cfg[2] = snd_cmi8328_cfg_read(port, CFG3);
+}
+
+static void snd_cmi8328_cfg_restore(u16 port, u8 cfg[])
+{
+ snd_cmi8328_cfg_write(port, CFG1, cfg[0]);
+ snd_cmi8328_cfg_write(port, CFG2, cfg[1]);
+ snd_cmi8328_cfg_write(port, CFG3, cfg[2]);
+}
+#endif /* CONFIG_PM */
+
+static int snd_cmi8328_mixer(struct snd_wss *chip)
+{
+ struct snd_card *card;
+ struct snd_ctl_elem_id id1, id2;
+ int err;
+
+ card = chip->card;
+
+ memset(&id1, 0, sizeof(id1));
+ memset(&id2, 0, sizeof(id2));
+ id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ /* rename AUX0 switch to CD */
+ strcpy(id1.name, "Aux Playback Switch");
+ strcpy(id2.name, "CD Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "error renaming control\n");
+ return err;
+ }
+ /* rename AUX0 volume to CD */
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "CD Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "error renaming control\n");
+ return err;
+ }
+ /* rename AUX1 switch to Synth */
+ strcpy(id1.name, "Aux Playback Switch");
+ id1.index = 1;
+ strcpy(id2.name, "Synth Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "error renaming control\n");
+ return err;
+ }
+ /* rename AUX1 volume to Synth */
+ strcpy(id1.name, "Aux Playback Volume");
+ id1.index = 1;
+ strcpy(id2.name, "Synth Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "error renaming control\n");
+ return err;
+ }
+
+ return 0;
+}
+
+/* find index of an item in "-1"-ended array */
+static int array_find(const int array[], int item)
+{
+ int i;
+
+ for (i = 0; array[i] != -1; i++)
+ if (array[i] == item)
+ return i;
+
+ return -1;
+}
+/* the same for long */
+static int array_find_l(const long array[], long item)
+{
+ int i;
+
+ for (i = 0; array[i] != -1; i++)
+ if (array[i] == item)
+ return i;
+
+ return -1;
+}
+
+static int snd_cmi8328_probe(struct device *pdev, unsigned int ndev)
+{
+ struct snd_card *card;
+ struct snd_opl3 *opl3;
+ struct snd_cmi8328 *cmi;
+#ifdef SUPPORT_JOYSTICK
+ struct resource *res;
+#endif
+ int err, pos;
+ static const long mpu_ports[] = { 0x330, 0x300, 0x310, 0x320, 0x332, 0x334,
+ 0x336, -1 };
+ static const u8 mpu_port_bits[] = { 3, 0, 1, 2, 4, 5, 6 };
+ static const int mpu_irqs[] = { 9, 7, 5, 3, -1 };
+ static const u8 mpu_irq_bits[] = { 3, 2, 1, 0 };
+ static const int irqs[] = { 9, 10, 11, 7, -1 };
+ static const u8 irq_bits[] = { 2, 3, 4, 1 };
+ static const int dma1s[] = { 3, 1, 0, -1 };
+ static const u8 dma_bits[] = { 3, 2, 1 };
+ static const int dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1}, {0, -1} };
+ u16 port = cmi8328_ports[ndev];
+ u8 val;
+
+ /* 0xff is invalid configuration (but settable - hope it isn't set) */
+ if (snd_cmi8328_cfg_read(port, CFG1) == 0xff)
+ return -ENODEV;
+ /* the SB disable bit must NEVER EVER be cleared or the WSS dies */
+ snd_cmi8328_cfg_write(port, CFG1, CFG1_SB_DISABLE);
+ if (snd_cmi8328_cfg_read(port, CFG1) != CFG1_SB_DISABLE)
+ return -ENODEV;
+ /* disable everything first */
+ snd_cmi8328_cfg_write(port, CFG2, 0); /* disable CDROM and MPU401 */
+ snd_cmi8328_cfg_write(port, CFG3, 0); /* disable CDROM IRQ and DMA */
+
+ if (irq[ndev] == SNDRV_AUTO_IRQ) {
+ irq[ndev] = snd_legacy_find_free_irq(irqs);
+ if (irq[ndev] < 0) {
+ snd_printk(KERN_ERR "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (dma1[ndev] == SNDRV_AUTO_DMA) {
+ dma1[ndev] = snd_legacy_find_free_dma(dma1s);
+ if (dma1[ndev] < 0) {
+ snd_printk(KERN_ERR "unable to find a free DMA1\n");
+ return -EBUSY;
+ }
+ }
+ if (dma2[ndev] == SNDRV_AUTO_DMA) {
+ dma2[ndev] = snd_legacy_find_free_dma(dma2s[dma1[ndev] % 4]);
+ if (dma2[ndev] < 0) {
+ snd_printk(KERN_WARNING "unable to find a free DMA2, full-duplex will not work\n");
+ dma2[ndev] = -1;
+ }
+ }
+ /* configure WSS IRQ... */
+ pos = array_find(irqs, irq[ndev]);
+ if (pos < 0) {
+ snd_printk(KERN_ERR "invalid IRQ %d\n", irq[ndev]);
+ return -EINVAL;
+ }
+ val = irq_bits[pos] << 3;
+ /* ...and DMA... */
+ pos = array_find(dma1s, dma1[ndev]);
+ if (pos < 0) {
+ snd_printk(KERN_ERR "invalid DMA1 %d\n", dma1[ndev]);
+ return -EINVAL;
+ }
+ val |= dma_bits[pos];
+ /* ...and DMA2 */
+ if (dma2[ndev] >= 0 && dma1[ndev] != dma2[ndev]) {
+ pos = array_find(dma2s[dma1[ndev]], dma2[ndev]);
+ if (pos < 0) {
+ snd_printk(KERN_ERR "invalid DMA2 %d\n", dma2[ndev]);
+ return -EINVAL;
+ }
+ val |= 0x04; /* enable separate capture DMA */
+ }
+ outb(val, port);
+
+ err = snd_devm_card_new(pdev, index[ndev], id[ndev], THIS_MODULE,
+ sizeof(struct snd_cmi8328), &card);
+ if (err < 0)
+ return err;
+ cmi = card->private_data;
+ cmi->card = card;
+ cmi->port = port;
+ cmi->wss_cfg = val;
+
+ err = snd_wss_create(card, port + 4, -1, irq[ndev], dma1[ndev],
+ dma2[ndev], WSS_HW_DETECT, 0, &cmi->wss);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_pcm(cmi->wss, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_mixer(cmi->wss);
+ if (err < 0)
+ return err;
+ err = snd_cmi8328_mixer(cmi->wss);
+ if (err < 0)
+ return err;
+
+ if (snd_wss_timer(cmi->wss, 0) < 0)
+ snd_printk(KERN_WARNING "error initializing WSS timer\n");
+
+ if (mpuport[ndev] == SNDRV_AUTO_PORT) {
+ mpuport[ndev] = snd_legacy_find_free_ioport(mpu_ports, 2);
+ if (mpuport[ndev] < 0)
+ snd_printk(KERN_ERR "unable to find a free MPU401 port\n");
+ }
+ if (mpuirq[ndev] == SNDRV_AUTO_IRQ) {
+ mpuirq[ndev] = snd_legacy_find_free_irq(mpu_irqs);
+ if (mpuirq[ndev] < 0)
+ snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n");
+ }
+ /* enable and configure MPU401 */
+ if (mpuport[ndev] > 0 && mpuirq[ndev] > 0) {
+ val = CFG2_MPU_ENABLE;
+ pos = array_find_l(mpu_ports, mpuport[ndev]);
+ if (pos < 0)
+ snd_printk(KERN_WARNING "invalid MPU401 port 0x%lx\n",
+ mpuport[ndev]);
+ else {
+ val |= mpu_port_bits[pos] << 5;
+ pos = array_find(mpu_irqs, mpuirq[ndev]);
+ if (pos < 0)
+ snd_printk(KERN_WARNING "invalid MPU401 IRQ %d\n",
+ mpuirq[ndev]);
+ else {
+ val |= mpu_irq_bits[pos] << 3;
+ snd_cmi8328_cfg_write(port, CFG2, val);
+ if (snd_mpu401_uart_new(card, 0,
+ MPU401_HW_MPU401, mpuport[ndev],
+ 0, mpuirq[ndev], NULL) < 0)
+ snd_printk(KERN_ERR "error initializing MPU401\n");
+ }
+ }
+ }
+ /* OPL3 is hardwired to 0x388 and cannot be disabled */
+ if (snd_opl3_create(card, 0x388, 0x38a, OPL3_HW_AUTO, 0, &opl3) < 0)
+ snd_printk(KERN_ERR "error initializing OPL3\n");
+ else
+ if (snd_opl3_hwdep_new(opl3, 0, 1, NULL) < 0)
+ snd_printk(KERN_WARNING "error initializing OPL3 hwdep\n");
+
+ strcpy(card->driver, "CMI8328");
+ strcpy(card->shortname, "C-Media CMI8328");
+ sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d,%d",
+ card->shortname, cmi->wss->port, irq[ndev], dma1[ndev],
+ (dma2[ndev] >= 0) ? dma2[ndev] : dma1[ndev]);
+
+ dev_set_drvdata(pdev, card);
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+#ifdef SUPPORT_JOYSTICK
+ if (!gameport[ndev])
+ return 0;
+ /* gameport is hardwired to 0x200 */
+ res = devm_request_region(pdev, 0x200, 8, "CMI8328 gameport");
+ if (!res)
+ snd_printk(KERN_WARNING "unable to allocate gameport I/O port\n");
+ else {
+ struct gameport *gp = cmi->gameport = gameport_allocate_port();
+ if (cmi->gameport) {
+ gameport_set_name(gp, "CMI8328 Gameport");
+ gameport_set_phys(gp, "%s/gameport0", dev_name(pdev));
+ gameport_set_dev_parent(gp, pdev);
+ gp->io = 0x200;
+ /* Enable gameport */
+ snd_cmi8328_cfg_write(port, CFG1,
+ CFG1_SB_DISABLE | CFG1_GAMEPORT);
+ gameport_register_port(gp);
+ }
+ }
+#endif
+ return 0;
+}
+
+static void snd_cmi8328_remove(struct device *pdev, unsigned int dev)
+{
+ struct snd_card *card = dev_get_drvdata(pdev);
+ struct snd_cmi8328 *cmi = card->private_data;
+
+#ifdef SUPPORT_JOYSTICK
+ if (cmi->gameport)
+ gameport_unregister_port(cmi->gameport);
+#endif
+ /* disable everything */
+ snd_cmi8328_cfg_write(cmi->port, CFG1, CFG1_SB_DISABLE);
+ snd_cmi8328_cfg_write(cmi->port, CFG2, 0);
+ snd_cmi8328_cfg_write(cmi->port, CFG3, 0);
+}
+
+#ifdef CONFIG_PM
+static int snd_cmi8328_suspend(struct device *pdev, unsigned int n,
+ pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(pdev);
+ struct snd_cmi8328 *cmi;
+
+ if (!card) /* ignore absent devices */
+ return 0;
+ cmi = card->private_data;
+ snd_cmi8328_cfg_save(cmi->port, cmi->cfg);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ cmi->wss->suspend(cmi->wss);
+
+ return 0;
+}
+
+static int snd_cmi8328_resume(struct device *pdev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(pdev);
+ struct snd_cmi8328 *cmi;
+
+ if (!card) /* ignore absent devices */
+ return 0;
+ cmi = card->private_data;
+ snd_cmi8328_cfg_restore(cmi->port, cmi->cfg);
+ outb(cmi->wss_cfg, cmi->port);
+ cmi->wss->resume(cmi->wss);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+ return 0;
+}
+#endif
+
+static struct isa_driver snd_cmi8328_driver = {
+ .probe = snd_cmi8328_probe,
+ .remove = snd_cmi8328_remove,
+#ifdef CONFIG_PM
+ .suspend = snd_cmi8328_suspend,
+ .resume = snd_cmi8328_resume,
+#endif
+ .driver = {
+ .name = "cmi8328"
+ },
+};
+
+module_isa_driver(snd_cmi8328_driver, CMI8328_MAX);
diff --git a/sound/isa/cmi8330.c b/sound/isa/cmi8330.c
new file mode 100644
index 0000000000..f209b16c52
--- /dev/null
+++ b/sound/isa/cmi8330.c
@@ -0,0 +1,752 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for C-Media's CMI8330 and CMI8329 soundcards.
+ * Copyright (c) by George Talusan <gstalusan@uwaterloo.ca>
+ * http://www.undergrad.math.uwaterloo.ca/~gstalusa
+ */
+
+/*
+ * NOTES
+ *
+ * The extended registers contain mixer settings which are largely
+ * untapped for the time being.
+ *
+ * MPU401 and SPDIF are not supported yet. I don't have the hardware
+ * to aid in coding and testing, so I won't bother.
+ *
+ * To quickly load the module,
+ *
+ * modprobe -a snd-cmi8330 sbport=0x220 sbirq=5 sbdma8=1
+ * sbdma16=5 wssport=0x530 wssirq=11 wssdma=0 fmport=0x388
+ *
+ * This card has two mixers and two PCM devices. I've cheesed it such
+ * that recording and playback can be done through the same device.
+ * The driver "magically" routes the capturing to the AD1848 codec,
+ * and playback to the SB16 codec. This allows for full-duplex mode
+ * to some extent.
+ * The utilities in alsa-utils are aware of both devices, so passing
+ * the appropriate parameters to amixer and alsactl will give you
+ * full control over both mixers.
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+/*
+ */
+/* #define ENABLE_SB_MIXER */
+#define PLAYBACK_ON_SB
+
+/*
+ */
+MODULE_AUTHOR("George Talusan <gstalusan@uwaterloo.ca>");
+MODULE_DESCRIPTION("C-Media CMI8330/CMI8329");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long sbport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int sbirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int sbdma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int sbdma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static long wssport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int wssirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int wssdma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static long fmport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long mpuport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int mpuirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for CMI8330/CMI8329 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for CMI8330/CMI8329 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable CMI8330/CMI8329 soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+
+module_param_hw_array(sbport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(sbport, "Port # for CMI8330/CMI8329 SB driver.");
+module_param_hw_array(sbirq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(sbirq, "IRQ # for CMI8330/CMI8329 SB driver.");
+module_param_hw_array(sbdma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(sbdma8, "DMA8 for CMI8330/CMI8329 SB driver.");
+module_param_hw_array(sbdma16, int, dma, NULL, 0444);
+MODULE_PARM_DESC(sbdma16, "DMA16 for CMI8330/CMI8329 SB driver.");
+
+module_param_hw_array(wssport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(wssport, "Port # for CMI8330/CMI8329 WSS driver.");
+module_param_hw_array(wssirq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(wssirq, "IRQ # for CMI8330/CMI8329 WSS driver.");
+module_param_hw_array(wssdma, int, dma, NULL, 0444);
+MODULE_PARM_DESC(wssdma, "DMA for CMI8330/CMI8329 WSS driver.");
+
+module_param_hw_array(fmport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fmport, "FM port # for CMI8330/CMI8329 driver.");
+module_param_hw_array(mpuport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8330/CMI8329 driver.");
+module_param_hw_array(mpuirq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpuirq, "IRQ # for CMI8330/CMI8329 MPU-401 port.");
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+#endif
+
+#define CMI8330_RMUX3D 16
+#define CMI8330_MUTEMUX 17
+#define CMI8330_OUTPUTVOL 18
+#define CMI8330_MASTVOL 19
+#define CMI8330_LINVOL 20
+#define CMI8330_CDINVOL 21
+#define CMI8330_WAVVOL 22
+#define CMI8330_RECMUX 23
+#define CMI8330_WAVGAIN 24
+#define CMI8330_LINGAIN 25
+#define CMI8330_CDINGAIN 26
+
+static const unsigned char snd_cmi8330_image[((CMI8330_CDINGAIN)-16) + 1] =
+{
+ 0x40, /* 16 - recording mux (SB-mixer-enabled) */
+#ifdef ENABLE_SB_MIXER
+ 0x40, /* 17 - mute mux (Mode2) */
+#else
+ 0x0, /* 17 - mute mux */
+#endif
+ 0x0, /* 18 - vol */
+ 0x0, /* 19 - master volume */
+ 0x0, /* 20 - line-in volume */
+ 0x0, /* 21 - cd-in volume */
+ 0x0, /* 22 - wave volume */
+ 0x0, /* 23 - mute/rec mux */
+ 0x0, /* 24 - wave rec gain */
+ 0x0, /* 25 - line-in rec gain */
+ 0x0 /* 26 - cd-in rec gain */
+};
+
+typedef int (*snd_pcm_open_callback_t)(struct snd_pcm_substream *);
+
+enum card_type {
+ CMI8330,
+ CMI8329
+};
+
+struct snd_cmi8330 {
+#ifdef CONFIG_PNP
+ struct pnp_dev *cap;
+ struct pnp_dev *play;
+ struct pnp_dev *mpu;
+#endif
+ struct snd_card *card;
+ struct snd_wss *wss;
+ struct snd_sb *sb;
+
+ struct snd_pcm *pcm;
+ struct snd_cmi8330_stream {
+ struct snd_pcm_ops ops;
+ snd_pcm_open_callback_t open;
+ void *private_data; /* sb or wss */
+ } streams[2];
+
+ enum card_type type;
+};
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_card_device_id snd_cmi8330_pnpids[] = {
+ { .id = "CMI0001", .devs = { { "@X@0001" }, { "@@@0001" }, { "@H@0001" }, { "A@@0001" } } },
+ { .id = "CMI0001", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } } },
+ { .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_cmi8330_pnpids);
+
+#endif
+
+
+static const struct snd_kcontrol_new snd_cmi8330_controls[] = {
+WSS_DOUBLE("Master Playback Volume", 0,
+ CMI8330_MASTVOL, CMI8330_MASTVOL, 4, 0, 15, 0),
+WSS_SINGLE("Loud Playback Switch", 0,
+ CMI8330_MUTEMUX, 6, 1, 1),
+WSS_DOUBLE("PCM Playback Switch", 0,
+ CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE("PCM Playback Volume", 0,
+ CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1),
+WSS_DOUBLE("Line Playback Switch", 0,
+ CMI8330_MUTEMUX, CMI8330_MUTEMUX, 4, 3, 1, 0),
+WSS_DOUBLE("Line Playback Volume", 0,
+ CMI8330_LINVOL, CMI8330_LINVOL, 4, 0, 15, 0),
+WSS_DOUBLE("Line Capture Switch", 0,
+ CMI8330_RMUX3D, CMI8330_RMUX3D, 2, 1, 1, 0),
+WSS_DOUBLE("Line Capture Volume", 0,
+ CMI8330_LINGAIN, CMI8330_LINGAIN, 4, 0, 15, 0),
+WSS_DOUBLE("CD Playback Switch", 0,
+ CMI8330_MUTEMUX, CMI8330_MUTEMUX, 2, 1, 1, 0),
+WSS_DOUBLE("CD Capture Switch", 0,
+ CMI8330_RMUX3D, CMI8330_RMUX3D, 4, 3, 1, 0),
+WSS_DOUBLE("CD Playback Volume", 0,
+ CMI8330_CDINVOL, CMI8330_CDINVOL, 4, 0, 15, 0),
+WSS_DOUBLE("CD Capture Volume", 0,
+ CMI8330_CDINGAIN, CMI8330_CDINGAIN, 4, 0, 15, 0),
+WSS_SINGLE("Mic Playback Switch", 0,
+ CMI8330_MUTEMUX, 0, 1, 0),
+WSS_SINGLE("Mic Playback Volume", 0,
+ CMI8330_OUTPUTVOL, 0, 7, 0),
+WSS_SINGLE("Mic Capture Switch", 0,
+ CMI8330_RMUX3D, 0, 1, 0),
+WSS_SINGLE("Mic Capture Volume", 0,
+ CMI8330_OUTPUTVOL, 5, 7, 0),
+WSS_DOUBLE("Wavetable Playback Switch", 0,
+ CMI8330_RECMUX, CMI8330_RECMUX, 1, 0, 1, 0),
+WSS_DOUBLE("Wavetable Playback Volume", 0,
+ CMI8330_WAVVOL, CMI8330_WAVVOL, 4, 0, 15, 0),
+WSS_DOUBLE("Wavetable Capture Switch", 0,
+ CMI8330_RECMUX, CMI8330_RECMUX, 5, 4, 1, 0),
+WSS_DOUBLE("Wavetable Capture Volume", 0,
+ CMI8330_WAVGAIN, CMI8330_WAVGAIN, 4, 0, 15, 0),
+WSS_SINGLE("3D Control - Switch", 0,
+ CMI8330_RMUX3D, 5, 1, 1),
+WSS_SINGLE("Beep Playback Volume", 0,
+ CMI8330_OUTPUTVOL, 3, 3, 0),
+WSS_DOUBLE("FM Playback Switch", 0,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("FM Playback Volume", 0,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
+WSS_SINGLE(SNDRV_CTL_NAME_IEC958("Input ", CAPTURE, SWITCH), 0,
+ CMI8330_RMUX3D, 7, 1, 1),
+WSS_SINGLE(SNDRV_CTL_NAME_IEC958("Input ", PLAYBACK, SWITCH), 0,
+ CMI8330_MUTEMUX, 7, 1, 1),
+};
+
+#ifdef ENABLE_SB_MIXER
+static const struct sbmix_elem cmi8330_sb_mixers[] = {
+SB_DOUBLE("SB Master Playback Volume", SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31),
+SB_DOUBLE("Tone Control - Bass", SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15),
+SB_DOUBLE("Tone Control - Treble", SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15),
+SB_DOUBLE("SB PCM Playback Volume", SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31),
+SB_DOUBLE("SB Synth Playback Volume", SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31),
+SB_DOUBLE("SB CD Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
+SB_DOUBLE("SB CD Playback Volume", SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31),
+SB_DOUBLE("SB Line Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
+SB_DOUBLE("SB Line Playback Volume", SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31),
+SB_SINGLE("SB Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
+SB_SINGLE("SB Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31),
+SB_SINGLE("SB Beep Volume", SB_DSP4_SPEAKER_DEV, 6, 3),
+SB_DOUBLE("SB Capture Volume", SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3),
+SB_DOUBLE("SB Playback Volume", SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3),
+SB_SINGLE("SB Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1),
+};
+
+static const unsigned char cmi8330_sb_init_values[][2] = {
+ { SB_DSP4_MASTER_DEV + 0, 0 },
+ { SB_DSP4_MASTER_DEV + 1, 0 },
+ { SB_DSP4_PCM_DEV + 0, 0 },
+ { SB_DSP4_PCM_DEV + 1, 0 },
+ { SB_DSP4_SYNTH_DEV + 0, 0 },
+ { SB_DSP4_SYNTH_DEV + 1, 0 },
+ { SB_DSP4_INPUT_LEFT, 0 },
+ { SB_DSP4_INPUT_RIGHT, 0 },
+ { SB_DSP4_OUTPUT_SW, 0 },
+ { SB_DSP4_SPEAKER_DEV, 0 },
+};
+
+
+static int cmi8330_add_sb_mixers(struct snd_sb *chip)
+{
+ int idx, err;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ snd_sbmixer_write(chip, 0x00, 0x00); /* mixer reset */
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+ /* mute and zero volume channels */
+ for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_init_values); idx++) {
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ snd_sbmixer_write(chip, cmi8330_sb_init_values[idx][0],
+ cmi8330_sb_init_values[idx][1]);
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+ }
+
+ for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_mixers); idx++) {
+ err = snd_sbmixer_add_ctl_elem(chip, &cmi8330_sb_mixers[idx]);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+#endif
+
+static int snd_cmi8330_mixer(struct snd_card *card, struct snd_cmi8330 *acard)
+{
+ unsigned int idx;
+ int err;
+
+ strcpy(card->mixername, (acard->type == CMI8329) ? "CMI8329" : "CMI8330/C3D");
+
+ for (idx = 0; idx < ARRAY_SIZE(snd_cmi8330_controls); idx++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_cmi8330_controls[idx],
+ acard->wss));
+ if (err < 0)
+ return err;
+ }
+
+#ifdef ENABLE_SB_MIXER
+ err = cmi8330_add_sb_mixers(acard->sb);
+ if (err < 0)
+ return err;
+#endif
+ return 0;
+}
+
+#ifdef CONFIG_PNP
+static int snd_cmi8330_pnp(int dev, struct snd_cmi8330 *acard,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ struct pnp_dev *pdev;
+ int err;
+
+ /* CMI8329 has a device with ID A@@0001, CMI8330 does not */
+ acard->type = (id->devs[3].id[0]) ? CMI8329 : CMI8330;
+
+ acard->cap = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (acard->cap == NULL)
+ return -EBUSY;
+
+ acard->play = pnp_request_card_device(card, id->devs[1].id, NULL);
+ if (acard->play == NULL)
+ return -EBUSY;
+
+ acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+ if (acard->mpu == NULL)
+ return -EBUSY;
+
+ pdev = acard->cap;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "AD1848 PnP configure failure\n");
+ return -EBUSY;
+ }
+ wssport[dev] = pnp_port_start(pdev, 0);
+ wssdma[dev] = pnp_dma(pdev, 0);
+ wssirq[dev] = pnp_irq(pdev, 0);
+ if (pnp_port_start(pdev, 1))
+ fmport[dev] = pnp_port_start(pdev, 1);
+
+ /* allocate SB16 resources */
+ pdev = acard->play;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "SB16 PnP configure failure\n");
+ return -EBUSY;
+ }
+ sbport[dev] = pnp_port_start(pdev, 0);
+ sbdma8[dev] = pnp_dma(pdev, 0);
+ sbdma16[dev] = pnp_dma(pdev, 1);
+ sbirq[dev] = pnp_irq(pdev, 0);
+ /* On CMI8239, the OPL3 port might be present in SB16 PnP resources */
+ if (fmport[dev] == SNDRV_AUTO_PORT) {
+ if (pnp_port_start(pdev, 1))
+ fmport[dev] = pnp_port_start(pdev, 1);
+ else
+ fmport[dev] = 0x388; /* Or hardwired */
+ }
+
+ /* allocate MPU-401 resources */
+ pdev = acard->mpu;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0)
+ snd_printk(KERN_ERR "MPU-401 PnP configure failure: will be disabled\n");
+ else {
+ mpuport[dev] = pnp_port_start(pdev, 0);
+ mpuirq[dev] = pnp_irq(pdev, 0);
+ }
+ return 0;
+}
+#endif
+
+/*
+ * PCM interface
+ *
+ * since we call the different chip interfaces for playback and capture
+ * directions, we need a trick.
+ *
+ * - copy the ops for each direction into a local record.
+ * - replace the open callback with the new one, which replaces the
+ * substream->private_data with the corresponding chip instance
+ * and calls again the original open callback of the chip.
+ *
+ */
+
+#ifdef PLAYBACK_ON_SB
+#define CMI_SB_STREAM SNDRV_PCM_STREAM_PLAYBACK
+#define CMI_AD_STREAM SNDRV_PCM_STREAM_CAPTURE
+#else
+#define CMI_SB_STREAM SNDRV_PCM_STREAM_CAPTURE
+#define CMI_AD_STREAM SNDRV_PCM_STREAM_PLAYBACK
+#endif
+
+static int snd_cmi8330_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream);
+
+ /* replace the private_data and call the original open callback */
+ substream->private_data = chip->streams[SNDRV_PCM_STREAM_PLAYBACK].private_data;
+ return chip->streams[SNDRV_PCM_STREAM_PLAYBACK].open(substream);
+}
+
+static int snd_cmi8330_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream);
+
+ /* replace the private_data and call the original open callback */
+ substream->private_data = chip->streams[SNDRV_PCM_STREAM_CAPTURE].private_data;
+ return chip->streams[SNDRV_PCM_STREAM_CAPTURE].open(substream);
+}
+
+static int snd_cmi8330_pcm(struct snd_card *card, struct snd_cmi8330 *chip)
+{
+ struct snd_pcm *pcm;
+ const struct snd_pcm_ops *ops;
+ int err;
+ static const snd_pcm_open_callback_t cmi_open_callbacks[2] = {
+ snd_cmi8330_playback_open,
+ snd_cmi8330_capture_open
+ };
+
+ err = snd_pcm_new(card, (chip->type == CMI8329) ? "CMI8329" : "CMI8330", 0, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+ strcpy(pcm->name, (chip->type == CMI8329) ? "CMI8329" : "CMI8330");
+ pcm->private_data = chip;
+
+ /* SB16 */
+ ops = snd_sb16dsp_get_pcm_ops(CMI_SB_STREAM);
+ chip->streams[CMI_SB_STREAM].ops = *ops;
+ chip->streams[CMI_SB_STREAM].open = ops->open;
+ chip->streams[CMI_SB_STREAM].ops.open = cmi_open_callbacks[CMI_SB_STREAM];
+ chip->streams[CMI_SB_STREAM].private_data = chip->sb;
+
+ /* AD1848 */
+ ops = snd_wss_get_pcm_ops(CMI_AD_STREAM);
+ chip->streams[CMI_AD_STREAM].ops = *ops;
+ chip->streams[CMI_AD_STREAM].open = ops->open;
+ chip->streams[CMI_AD_STREAM].ops.open = cmi_open_callbacks[CMI_AD_STREAM];
+ chip->streams[CMI_AD_STREAM].private_data = chip->wss;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &chip->streams[SNDRV_PCM_STREAM_PLAYBACK].ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &chip->streams[SNDRV_PCM_STREAM_CAPTURE].ops);
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, 64*1024, 128*1024);
+ chip->pcm = pcm;
+
+ return 0;
+}
+
+
+#ifdef CONFIG_PM
+static int snd_cmi8330_suspend(struct snd_card *card)
+{
+ struct snd_cmi8330 *acard = card->private_data;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ acard->wss->suspend(acard->wss);
+ snd_sbmixer_suspend(acard->sb);
+ return 0;
+}
+
+static int snd_cmi8330_resume(struct snd_card *card)
+{
+ struct snd_cmi8330 *acard = card->private_data;
+
+ snd_sbdsp_reset(acard->sb);
+ snd_sbmixer_suspend(acard->sb);
+ acard->wss->resume(acard->wss);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+
+/*
+ */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev) isapnp[dev]
+#else
+#define is_isapnp_selected(dev) 0
+#endif
+
+#define PFX "cmi8330: "
+
+static int snd_cmi8330_card_new(struct device *pdev, int dev,
+ struct snd_card **cardp)
+{
+ struct snd_card *card;
+ struct snd_cmi8330 *acard;
+ int err;
+
+ err = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_cmi8330), &card);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "could not get a new card\n");
+ return err;
+ }
+ acard = card->private_data;
+ acard->card = card;
+ *cardp = card;
+ return 0;
+}
+
+static int snd_cmi8330_probe(struct snd_card *card, int dev)
+{
+ struct snd_cmi8330 *acard;
+ int i, err;
+ struct snd_opl3 *opl3;
+
+ acard = card->private_data;
+ err = snd_wss_create(card, wssport[dev] + 4, -1,
+ wssirq[dev],
+ wssdma[dev], -1,
+ WSS_HW_DETECT, 0, &acard->wss);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "AD1848 device busy??\n");
+ return err;
+ }
+ if (acard->wss->hardware != WSS_HW_CMI8330) {
+ snd_printk(KERN_ERR PFX "AD1848 not found during probe\n");
+ return -ENODEV;
+ }
+
+ err = snd_sbdsp_create(card, sbport[dev],
+ sbirq[dev],
+ snd_sb16dsp_interrupt,
+ sbdma8[dev],
+ sbdma16[dev],
+ SB_HW_AUTO, &acard->sb);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "SB16 device busy??\n");
+ return err;
+ }
+ if (acard->sb->hardware != SB_HW_16) {
+ snd_printk(KERN_ERR PFX "SB16 not found during probe\n");
+ return -ENODEV;
+ }
+
+ snd_wss_out(acard->wss, CS4231_MISC_INFO, 0x40); /* switch on MODE2 */
+ for (i = CMI8330_RMUX3D; i <= CMI8330_CDINGAIN; i++)
+ snd_wss_out(acard->wss, i,
+ snd_cmi8330_image[i - CMI8330_RMUX3D]);
+
+ err = snd_cmi8330_mixer(card, acard);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "failed to create mixers\n");
+ return err;
+ }
+
+ err = snd_cmi8330_pcm(card, acard);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "failed to create pcms\n");
+ return err;
+ }
+ if (fmport[dev] != SNDRV_AUTO_PORT) {
+ if (snd_opl3_create(card,
+ fmport[dev], fmport[dev] + 2,
+ OPL3_HW_AUTO, 0, &opl3) < 0) {
+ snd_printk(KERN_ERR PFX
+ "no OPL device at 0x%lx-0x%lx ?\n",
+ fmport[dev], fmport[dev] + 2);
+ } else {
+ err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ if (mpuport[dev] != SNDRV_AUTO_PORT) {
+ if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+ mpuport[dev], 0, mpuirq[dev],
+ NULL) < 0)
+ printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n",
+ mpuport[dev]);
+ }
+
+ strcpy(card->driver, (acard->type == CMI8329) ? "CMI8329" : "CMI8330/C3D");
+ strcpy(card->shortname, (acard->type == CMI8329) ? "C-Media CMI8329" : "C-Media CMI8330/C3D");
+ sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+ card->shortname,
+ acard->wss->port,
+ wssirq[dev],
+ wssdma[dev]);
+
+ return snd_card_register(card);
+}
+
+static int snd_cmi8330_isa_match(struct device *pdev,
+ unsigned int dev)
+{
+ if (!enable[dev] || is_isapnp_selected(dev))
+ return 0;
+ if (wssport[dev] == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR PFX "specify wssport\n");
+ return 0;
+ }
+ if (sbport[dev] == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR PFX "specify sbport\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int snd_cmi8330_isa_probe(struct device *pdev,
+ unsigned int dev)
+{
+ struct snd_card *card;
+ int err;
+
+ err = snd_cmi8330_card_new(pdev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_cmi8330_probe(card, dev);
+ if (err < 0)
+ return err;
+ dev_set_drvdata(pdev, card);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cmi8330_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_cmi8330_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_cmi8330_isa_resume(struct device *dev, unsigned int n)
+{
+ return snd_cmi8330_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#define DEV_NAME "cmi8330"
+
+static struct isa_driver snd_cmi8330_driver = {
+ .match = snd_cmi8330_isa_match,
+ .probe = snd_cmi8330_isa_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_cmi8330_isa_suspend,
+ .resume = snd_cmi8330_isa_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_cmi8330_pnp_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ static int dev;
+ struct snd_card *card;
+ int res;
+
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ res = snd_cmi8330_card_new(&pcard->card->dev, dev, &card);
+ if (res < 0)
+ return res;
+ res = snd_cmi8330_pnp(dev, card->private_data, pcard, pid);
+ if (res < 0) {
+ snd_printk(KERN_ERR PFX "PnP detection failed\n");
+ return res;
+ }
+ res = snd_cmi8330_probe(card, dev);
+ if (res < 0)
+ return res;
+ pnp_set_card_drvdata(pcard, card);
+ dev++;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cmi8330_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ return snd_cmi8330_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_cmi8330_pnp_resume(struct pnp_card_link *pcard)
+{
+ return snd_cmi8330_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver cmi8330_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = "cmi8330",
+ .id_table = snd_cmi8330_pnpids,
+ .probe = snd_cmi8330_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_cmi8330_pnp_suspend,
+ .resume = snd_cmi8330_pnp_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_cmi8330_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&snd_cmi8330_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+
+ err = pnp_register_card_driver(&cmi8330_pnpc_driver);
+ if (!err)
+ pnp_registered = 1;
+
+ if (isa_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit alsa_card_cmi8330_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ pnp_unregister_card_driver(&cmi8330_pnpc_driver);
+
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&snd_cmi8330_driver);
+}
+
+module_init(alsa_card_cmi8330_init)
+module_exit(alsa_card_cmi8330_exit)
diff --git a/sound/isa/cs423x/Makefile b/sound/isa/cs423x/Makefile
new file mode 100644
index 0000000000..91c6b8d644
--- /dev/null
+++ b/sound/isa/cs423x/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-cs4231-objs := cs4231.o
+snd-cs4236-objs := cs4236.o cs4236_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_CS4231) += snd-cs4231.o
+obj-$(CONFIG_SND_CS4236) += snd-cs4236.o
+
+
diff --git a/sound/isa/cs423x/cs4231.c b/sound/isa/cs423x/cs4231.c
new file mode 100644
index 0000000000..c87be4be6d
--- /dev/null
+++ b/sound/isa/cs423x/cs4231.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Generic driver for CS4231 chips
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Originally the CS4232/CS4232A driver, modified for use on CS4231 by
+ * Tugrul Galatali <galatalt@stuy.edu>
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+#define CRD_NAME "Generic CS4231"
+#define DEV_NAME "cs4231"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " driver.");
+
+static int snd_cs4231_match(struct device *dev, unsigned int n)
+{
+ if (!enable[n])
+ return 0;
+
+ if (port[n] == SNDRV_AUTO_PORT) {
+ dev_err(dev, "please specify port\n");
+ return 0;
+ }
+ if (irq[n] == SNDRV_AUTO_IRQ) {
+ dev_err(dev, "please specify irq\n");
+ return 0;
+ }
+ if (dma1[n] == SNDRV_AUTO_DMA) {
+ dev_err(dev, "please specify dma1\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int snd_cs4231_probe(struct device *dev, unsigned int n)
+{
+ struct snd_card *card;
+ struct snd_wss *chip;
+ int error;
+
+ error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
+ if (error < 0)
+ return error;
+
+ error = snd_wss_create(card, port[n], -1, irq[n], dma1[n], dma2[n],
+ WSS_HW_DETECT, 0, &chip);
+ if (error < 0)
+ return error;
+
+ card->private_data = chip;
+
+ error = snd_wss_pcm(chip, 0);
+ if (error < 0)
+ return error;
+
+ strscpy(card->driver, "CS4231", sizeof(card->driver));
+ strscpy(card->shortname, chip->pcm->name, sizeof(card->shortname));
+
+ if (dma2[n] < 0)
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx, irq %d, dma %d",
+ chip->pcm->name, chip->port, irq[n], dma1[n]);
+ else
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx, irq %d, dma %d&%d",
+ chip->pcm->name, chip->port, irq[n], dma1[n], dma2[n]);
+
+ error = snd_wss_mixer(chip);
+ if (error < 0)
+ return error;
+
+ error = snd_wss_timer(chip, 0);
+ if (error < 0)
+ return error;
+
+ if (mpu_port[n] > 0 && mpu_port[n] != SNDRV_AUTO_PORT) {
+ if (mpu_irq[n] == SNDRV_AUTO_IRQ)
+ mpu_irq[n] = -1;
+ if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232,
+ mpu_port[n], 0, mpu_irq[n],
+ NULL) < 0)
+ dev_warn(dev, "MPU401 not detected\n");
+ }
+
+ error = snd_card_register(card);
+ if (error < 0)
+ return error;
+
+ dev_set_drvdata(dev, card);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cs4231_suspend(struct device *dev, unsigned int n, pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_wss *chip = card->private_data;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ chip->suspend(chip);
+ return 0;
+}
+
+static int snd_cs4231_resume(struct device *dev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_wss *chip = card->private_data;
+
+ chip->resume(chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+static struct isa_driver snd_cs4231_driver = {
+ .match = snd_cs4231_match,
+ .probe = snd_cs4231_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_cs4231_suspend,
+ .resume = snd_cs4231_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ }
+};
+
+module_isa_driver(snd_cs4231_driver, SNDRV_CARDS);
diff --git a/sound/isa/cs423x/cs4236.c b/sound/isa/cs423x/cs4236.c
new file mode 100644
index 0000000000..7226cbf2d7
--- /dev/null
+++ b/sound/isa/cs423x/cs4236.c
@@ -0,0 +1,647 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for generic CS4232/CS4235/CS4236/CS4236B/CS4237B/CS4238B/CS4239 chips
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cirrus Logic CS4232-9");
+MODULE_ALIAS("snd_cs4232");
+
+#define IDENT "CS4232+"
+#define DEV_NAME "cs4232+"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long cport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " IDENT " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " IDENT " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " IDENT " soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard.");
+#endif
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " IDENT " driver.");
+module_param_hw_array(cport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(cport, "Control port # for " IDENT " driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " IDENT " driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for " IDENT " driver.");
+module_param_hw_array(sb_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(sb_port, "SB port # for " IDENT " driver (optional).");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " IDENT " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " IDENT " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " IDENT " driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " IDENT " driver.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnpc_registered;
+static int pnp_registered;
+#endif /* CONFIG_PNP */
+
+struct snd_card_cs4236 {
+ struct snd_wss *chip;
+#ifdef CONFIG_PNP
+ struct pnp_dev *wss;
+ struct pnp_dev *ctrl;
+ struct pnp_dev *mpu;
+#endif
+};
+
+#ifdef CONFIG_PNP
+
+/*
+ * PNP BIOS
+ */
+static const struct pnp_device_id snd_cs423x_pnpbiosids[] = {
+ { .id = "CSC0100" },
+ { .id = "CSC0000" },
+ /* Guillemot Turtlebeach something appears to be cs4232 compatible
+ * (untested) */
+ { .id = "GIM0100" },
+ { .id = "" }
+};
+MODULE_DEVICE_TABLE(pnp, snd_cs423x_pnpbiosids);
+
+#define CS423X_ISAPNP_DRIVER "cs4232_isapnp"
+static const struct pnp_card_device_id snd_cs423x_pnpids[] = {
+ /* Philips PCA70PS */
+ { .id = "CSC0d32", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } },
+ /* TerraTec Maestro 32/96 (CS4232) */
+ { .id = "CSC1a32", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* HP Omnibook 5500 onboard */
+ { .id = "CSC4232", .devs = { { "CSC0000" }, { "CSC0002" }, { "CSC0003" } } },
+ /* Unnamed CS4236 card (Made in Taiwan) */
+ { .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Turtle Beach TBS-2000 (CS4232) */
+ { .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSCb006" } } },
+ /* Turtle Beach Tropez Plus (CS4232) */
+ { .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } },
+ /* SIC CrystalWave 32 (CS4232) */
+ { .id = "CSCf032", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Netfinity 3000 on-board soundcard */
+ { .id = "CSCe825", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC010f" } } },
+ /* Intel Marlin Spike Motherboard - CS4235 */
+ { .id = "CSC0225", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Intel Marlin Spike Motherboard (#2) - CS4235 */
+ { .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+ /* Unknown Intel mainboard - CS4235 */
+ { .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" } } },
+ /* Genius Sound Maker 3DJ - CS4237B */
+ { .id = "CSC0437", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Digital PC 5000 Onboard - CS4236B */
+ { .id = "CSC0735", .devs = { { "CSC0000" }, { "CSC0010" } } },
+ /* some unknown CS4236B */
+ { .id = "CSC0b35", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Intel PR440FX Onboard sound */
+ { .id = "CSC0b36", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* CS4235 on mainboard without MPU */
+ { .id = "CSC1425", .devs = { { "CSC0100" }, { "CSC0110" } } },
+ /* Gateway E1000 Onboard CS4236B */
+ { .id = "CSC1335", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* HP 6330 Onboard sound */
+ { .id = "CSC1525", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+ /* Crystal Computer TidalWave128 */
+ { .id = "CSC1e37", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* ACER AW37 - CS4235 */
+ { .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* build-in soundcard in EliteGroup P5TX-LA motherboard - CS4237B */
+ { .id = "CSC4237", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Crystal 3D - CS4237B */
+ { .id = "CSC4336", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Typhoon Soundsystem PnP - CS4236B */
+ { .id = "CSC4536", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Crystal CX4235-XQ3 EP - CS4235 */
+ { .id = "CSC4625", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+ /* Crystal Semiconductors CS4237B */
+ { .id = "CSC4637", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* NewClear 3D - CX4237B-XQ3 */
+ { .id = "CSC4837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Dell Optiplex GX1 - CS4236B */
+ { .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Dell P410 motherboard - CS4236B */
+ { .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" } } },
+ /* Dell Workstation 400 Onboard - CS4236B */
+ { .id = "CSC6836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Turtle Beach Malibu - CS4237B */
+ { .id = "CSC7537", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* CS4235 - onboard */
+ { .id = "CSC8025", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+ /* IBM Aptiva 2137 E24 Onboard - CS4237B */
+ { .id = "CSC8037", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* IBM IntelliStation M Pro motherboard */
+ { .id = "CSCc835", .devs = { { "CSC0000" }, { "CSC0010" } } },
+ /* Guillemot MaxiSound 16 PnP - CS4236B */
+ { .id = "CSC9836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Gallant SC-70P */
+ { .id = "CSC9837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* Techmakers MF-4236PW */
+ { .id = "CSCa736", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* TerraTec AudioSystem EWS64XL - CS4236B */
+ { .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" }, { "CSCa803" } } },
+ /* TerraTec AudioSystem EWS64XL - CS4236B */
+ { .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" } } },
+ /* ACER AW37/Pro - CS4235 */
+ { .id = "CSCd925", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* ACER AW35/Pro - CS4237B */
+ { .id = "CSCd937", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* CS4235 without MPU401 */
+ { .id = "CSCe825", .devs = { { "CSC0100" }, { "CSC0110" } } },
+ /* Unknown SiS530 - CS4235 */
+ { .id = "CSC4825", .devs = { { "CSC0100" }, { "CSC0110" } } },
+ /* IBM IntelliStation M Pro 6898 11U - CS4236B */
+ { .id = "CSCe835", .devs = { { "CSC0000" }, { "CSC0010" } } },
+ /* IBM PC 300PL Onboard - CS4236B */
+ { .id = "CSCe836", .devs = { { "CSC0000" }, { "CSC0010" } } },
+ /* Some noname CS4236 based card */
+ { .id = "CSCe936", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* CS4236B */
+ { .id = "CSCf235", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* CS4236B */
+ { .id = "CSCf238", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+ /* --- */
+ { .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_cs423x_pnpids);
+
+/* WSS initialization */
+static int snd_cs423x_pnp_init_wss(int dev, struct pnp_dev *pdev)
+{
+ if (pnp_activate_dev(pdev) < 0) {
+ printk(KERN_ERR IDENT " WSS PnP configure failed for WSS (out of resources?)\n");
+ return -EBUSY;
+ }
+ port[dev] = pnp_port_start(pdev, 0);
+ if (fm_port[dev] > 0)
+ fm_port[dev] = pnp_port_start(pdev, 1);
+ sb_port[dev] = pnp_port_start(pdev, 2);
+ irq[dev] = pnp_irq(pdev, 0);
+ dma1[dev] = pnp_dma(pdev, 0);
+ dma2[dev] = pnp_dma(pdev, 1) == 4 ? -1 : (int)pnp_dma(pdev, 1);
+ snd_printdd("isapnp WSS: wss port=0x%lx, fm port=0x%lx, sb port=0x%lx\n",
+ port[dev], fm_port[dev], sb_port[dev]);
+ snd_printdd("isapnp WSS: irq=%i, dma1=%i, dma2=%i\n",
+ irq[dev], dma1[dev], dma2[dev]);
+ return 0;
+}
+
+/* CTRL initialization */
+static int snd_cs423x_pnp_init_ctrl(int dev, struct pnp_dev *pdev)
+{
+ if (pnp_activate_dev(pdev) < 0) {
+ printk(KERN_ERR IDENT " CTRL PnP configure failed for WSS (out of resources?)\n");
+ return -EBUSY;
+ }
+ cport[dev] = pnp_port_start(pdev, 0);
+ snd_printdd("isapnp CTRL: control port=0x%lx\n", cport[dev]);
+ return 0;
+}
+
+/* MPU initialization */
+static int snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev)
+{
+ if (pnp_activate_dev(pdev) < 0) {
+ printk(KERN_ERR IDENT " MPU401 PnP configure failed for WSS (out of resources?)\n");
+ mpu_port[dev] = SNDRV_AUTO_PORT;
+ mpu_irq[dev] = SNDRV_AUTO_IRQ;
+ } else {
+ mpu_port[dev] = pnp_port_start(pdev, 0);
+ if (mpu_irq[dev] >= 0 &&
+ pnp_irq_valid(pdev, 0) &&
+ pnp_irq(pdev, 0) != (resource_size_t)-1) {
+ mpu_irq[dev] = pnp_irq(pdev, 0);
+ } else {
+ mpu_irq[dev] = -1; /* disable interrupt */
+ }
+ }
+ snd_printdd("isapnp MPU: port=0x%lx, irq=%i\n", mpu_port[dev], mpu_irq[dev]);
+ return 0;
+}
+
+static int snd_card_cs423x_pnp(int dev, struct snd_card_cs4236 *acard,
+ struct pnp_dev *pdev,
+ struct pnp_dev *cdev)
+{
+ acard->wss = pdev;
+ if (snd_cs423x_pnp_init_wss(dev, acard->wss) < 0)
+ return -EBUSY;
+ if (cdev)
+ cport[dev] = pnp_port_start(cdev, 0);
+ else
+ cport[dev] = -1;
+ return 0;
+}
+
+static int snd_card_cs423x_pnpc(int dev, struct snd_card_cs4236 *acard,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (acard->wss == NULL)
+ return -EBUSY;
+ acard->ctrl = pnp_request_card_device(card, id->devs[1].id, NULL);
+ if (acard->ctrl == NULL)
+ return -EBUSY;
+ if (id->devs[2].id[0]) {
+ acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+ if (acard->mpu == NULL)
+ return -EBUSY;
+ }
+
+ /* WSS initialization */
+ if (snd_cs423x_pnp_init_wss(dev, acard->wss) < 0)
+ return -EBUSY;
+
+ /* CTRL initialization */
+ if (acard->ctrl && cport[dev] > 0) {
+ if (snd_cs423x_pnp_init_ctrl(dev, acard->ctrl) < 0)
+ return -EBUSY;
+ }
+ /* MPU initialization */
+ if (acard->mpu && mpu_port[dev] > 0) {
+ if (snd_cs423x_pnp_init_mpu(dev, acard->mpu) < 0)
+ return -EBUSY;
+ }
+ return 0;
+}
+#endif /* CONFIG_PNP */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev) isapnp[dev]
+#else
+#define is_isapnp_selected(dev) 0
+#endif
+
+static int snd_cs423x_card_new(struct device *pdev, int dev,
+ struct snd_card **cardp)
+{
+ struct snd_card *card;
+ int err;
+
+ err = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_card_cs4236), &card);
+ if (err < 0)
+ return err;
+ *cardp = card;
+ return 0;
+}
+
+static int snd_cs423x_probe(struct snd_card *card, int dev)
+{
+ struct snd_card_cs4236 *acard;
+ struct snd_wss *chip;
+ struct snd_opl3 *opl3;
+ int err;
+
+ acard = card->private_data;
+ if (sb_port[dev] > 0 && sb_port[dev] != SNDRV_AUTO_PORT) {
+ if (!devm_request_region(card->dev, sb_port[dev], 16,
+ IDENT " SB")) {
+ printk(KERN_ERR IDENT ": unable to register SB port at 0x%lx\n", sb_port[dev]);
+ return -EBUSY;
+ }
+ }
+
+ err = snd_cs4236_create(card, port[dev], cport[dev],
+ irq[dev],
+ dma1[dev], dma2[dev],
+ WSS_HW_DETECT3, 0, &chip);
+ if (err < 0)
+ return err;
+
+ acard->chip = chip;
+ if (chip->hardware & WSS_HW_CS4236B_MASK) {
+
+ err = snd_cs4236_pcm(chip, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_cs4236_mixer(chip);
+ if (err < 0)
+ return err;
+ } else {
+ err = snd_wss_pcm(chip, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_mixer(chip);
+ if (err < 0)
+ return err;
+ }
+ strscpy(card->driver, chip->pcm->name, sizeof(card->driver));
+ strscpy(card->shortname, chip->pcm->name, sizeof(card->shortname));
+ if (dma2[dev] < 0)
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx, irq %i, dma %i",
+ chip->pcm->name, chip->port, irq[dev], dma1[dev]);
+ else
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx, irq %i, dma %i&%d",
+ chip->pcm->name, chip->port, irq[dev], dma1[dev],
+ dma2[dev]);
+
+ err = snd_wss_timer(chip, 0);
+ if (err < 0)
+ return err;
+
+ if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+ if (snd_opl3_create(card,
+ fm_port[dev], fm_port[dev] + 2,
+ OPL3_HW_OPL3_CS, 0, &opl3) < 0) {
+ printk(KERN_WARNING IDENT ": OPL3 not detected\n");
+ } else {
+ err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+ if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+ mpu_irq[dev] = -1;
+ if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232,
+ mpu_port[dev], 0,
+ mpu_irq[dev], NULL) < 0)
+ printk(KERN_WARNING IDENT ": MPU401 not detected\n");
+ }
+
+ return snd_card_register(card);
+}
+
+static int snd_cs423x_isa_match(struct device *pdev,
+ unsigned int dev)
+{
+ if (!enable[dev] || is_isapnp_selected(dev))
+ return 0;
+
+ if (port[dev] == SNDRV_AUTO_PORT) {
+ dev_err(pdev, "please specify port\n");
+ return 0;
+ }
+ if (cport[dev] == SNDRV_AUTO_PORT) {
+ dev_err(pdev, "please specify cport\n");
+ return 0;
+ }
+ if (irq[dev] == SNDRV_AUTO_IRQ) {
+ dev_err(pdev, "please specify irq\n");
+ return 0;
+ }
+ if (dma1[dev] == SNDRV_AUTO_DMA) {
+ dev_err(pdev, "please specify dma1\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int snd_cs423x_isa_probe(struct device *pdev,
+ unsigned int dev)
+{
+ struct snd_card *card;
+ int err;
+
+ err = snd_cs423x_card_new(pdev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_cs423x_probe(card, dev);
+ if (err < 0)
+ return err;
+ dev_set_drvdata(pdev, card);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cs423x_suspend(struct snd_card *card)
+{
+ struct snd_card_cs4236 *acard = card->private_data;
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ acard->chip->suspend(acard->chip);
+ return 0;
+}
+
+static int snd_cs423x_resume(struct snd_card *card)
+{
+ struct snd_card_cs4236 *acard = card->private_data;
+ acard->chip->resume(acard->chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+static int snd_cs423x_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_cs423x_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_cs423x_isa_resume(struct device *dev, unsigned int n)
+{
+ return snd_cs423x_resume(dev_get_drvdata(dev));
+}
+#endif
+
+static struct isa_driver cs423x_isa_driver = {
+ .match = snd_cs423x_isa_match,
+ .probe = snd_cs423x_isa_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_cs423x_isa_suspend,
+ .resume = snd_cs423x_isa_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_cs423x_pnpbios_detect(struct pnp_dev *pdev,
+ const struct pnp_device_id *id)
+{
+ static int dev;
+ int err;
+ struct snd_card *card;
+ struct pnp_dev *cdev, *iter;
+ char cid[PNP_ID_LEN];
+
+ if (pnp_device_is_isapnp(pdev))
+ return -ENOENT; /* we have another procedure - card */
+ for (; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ /* prepare second id */
+ strcpy(cid, pdev->id[0].id);
+ cid[5] = '1';
+ cdev = NULL;
+ list_for_each_entry(iter, &(pdev->protocol->devices), protocol_list) {
+ if (!strcmp(iter->id[0].id, cid)) {
+ cdev = iter;
+ break;
+ }
+ }
+ err = snd_cs423x_card_new(&pdev->dev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_card_cs423x_pnp(dev, card->private_data, pdev, cdev);
+ if (err < 0) {
+ printk(KERN_ERR "PnP BIOS detection failed for " IDENT "\n");
+ return err;
+ }
+ err = snd_cs423x_probe(card, dev);
+ if (err < 0)
+ return err;
+ pnp_set_drvdata(pdev, card);
+ dev++;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cs423x_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+ return snd_cs423x_suspend(pnp_get_drvdata(pdev));
+}
+
+static int snd_cs423x_pnp_resume(struct pnp_dev *pdev)
+{
+ return snd_cs423x_resume(pnp_get_drvdata(pdev));
+}
+#endif
+
+static struct pnp_driver cs423x_pnp_driver = {
+ .name = "cs423x-pnpbios",
+ .id_table = snd_cs423x_pnpbiosids,
+ .probe = snd_cs423x_pnpbios_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_cs423x_pnp_suspend,
+ .resume = snd_cs423x_pnp_resume,
+#endif
+};
+
+static int snd_cs423x_pnpc_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ static int dev;
+ struct snd_card *card;
+ int res;
+
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ res = snd_cs423x_card_new(&pcard->card->dev, dev, &card);
+ if (res < 0)
+ return res;
+ res = snd_card_cs423x_pnpc(dev, card->private_data, pcard, pid);
+ if (res < 0) {
+ printk(KERN_ERR "isapnp detection failed and probing for " IDENT
+ " is not supported\n");
+ return res;
+ }
+ res = snd_cs423x_probe(card, dev);
+ if (res < 0)
+ return res;
+ pnp_set_card_drvdata(pcard, card);
+ dev++;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cs423x_pnpc_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ return snd_cs423x_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_cs423x_pnpc_resume(struct pnp_card_link *pcard)
+{
+ return snd_cs423x_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver cs423x_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = CS423X_ISAPNP_DRIVER,
+ .id_table = snd_cs423x_pnpids,
+ .probe = snd_cs423x_pnpc_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_cs423x_pnpc_suspend,
+ .resume = snd_cs423x_pnpc_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_cs423x_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&cs423x_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+ err = pnp_register_driver(&cs423x_pnp_driver);
+ if (!err)
+ pnp_registered = 1;
+ err = pnp_register_card_driver(&cs423x_pnpc_driver);
+ if (!err)
+ pnpc_registered = 1;
+ if (pnp_registered)
+ err = 0;
+ if (isa_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit alsa_card_cs423x_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnpc_registered)
+ pnp_unregister_card_driver(&cs423x_pnpc_driver);
+ if (pnp_registered)
+ pnp_unregister_driver(&cs423x_pnp_driver);
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&cs423x_isa_driver);
+}
+
+module_init(alsa_card_cs423x_init)
+module_exit(alsa_card_cs423x_exit)
diff --git a/sound/isa/cs423x/cs4236_lib.c b/sound/isa/cs423x/cs4236_lib.c
new file mode 100644
index 0000000000..35f25911ad
--- /dev/null
+++ b/sound/isa/cs423x/cs4236_lib.c
@@ -0,0 +1,1074 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for control of CS4235/4236B/4237B/4238B/4239 chips
+ *
+ * Note:
+ * -----
+ *
+ * Bugs:
+ * -----
+ */
+
+/*
+ * Indirect control registers (CS4236B+)
+ *
+ * C0
+ * D8: WSS reset (all chips)
+ *
+ * C1 (all chips except CS4236)
+ * D7-D5: version
+ * D4-D0: chip id
+ * 11101 - CS4235
+ * 01011 - CS4236B
+ * 01000 - CS4237B
+ * 01001 - CS4238B
+ * 11110 - CS4239
+ *
+ * C2
+ * D7-D4: 3D Space (CS4235,CS4237B,CS4238B,CS4239)
+ * D3-D0: 3D Center (CS4237B); 3D Volume (CS4238B)
+ *
+ * C3
+ * D7: 3D Enable (CS4237B)
+ * D6: 3D Mono Enable (CS4237B)
+ * D5: 3D Serial Output (CS4237B,CS4238B)
+ * D4: 3D Enable (CS4235,CS4238B,CS4239)
+ *
+ * C4
+ * D7: consumer serial port enable (CS4237B,CS4238B)
+ * D6: channels status block reset (CS4237B,CS4238B)
+ * D5: user bit in sub-frame of digital audio data (CS4237B,CS4238B)
+ * D4: validity bit in sub-frame of digital audio data (CS4237B,CS4238B)
+ *
+ * C5 lower channel status (digital serial data description) (CS4237B,CS4238B)
+ * D7-D6: first two bits of category code
+ * D5: lock
+ * D4-D3: pre-emphasis (0 = none, 1 = 50/15us)
+ * D2: copy/copyright (0 = copy inhibited)
+ * D1: 0 = digital audio / 1 = non-digital audio
+ *
+ * C6 upper channel status (digital serial data description) (CS4237B,CS4238B)
+ * D7-D6: sample frequency (0 = 44.1kHz)
+ * D5: generation status (0 = no indication, 1 = original/commercially precaptureed data)
+ * D4-D0: category code (upper bits)
+ *
+ * C7 reserved (must write 0)
+ *
+ * C8 wavetable control
+ * D7: volume control interrupt enable (CS4235,CS4239)
+ * D6: hardware volume control format (CS4235,CS4239)
+ * D3: wavetable serial port enable (all chips)
+ * D2: DSP serial port switch (all chips)
+ * D1: disable MCLK (all chips)
+ * D0: force BRESET low (all chips)
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+/*
+ *
+ */
+
+static const unsigned char snd_cs4236_ext_map[18] = {
+ /* CS4236_LEFT_LINE */ 0xff,
+ /* CS4236_RIGHT_LINE */ 0xff,
+ /* CS4236_LEFT_MIC */ 0xdf,
+ /* CS4236_RIGHT_MIC */ 0xdf,
+ /* CS4236_LEFT_MIX_CTRL */ 0xe0 | 0x18,
+ /* CS4236_RIGHT_MIX_CTRL */ 0xe0,
+ /* CS4236_LEFT_FM */ 0xbf,
+ /* CS4236_RIGHT_FM */ 0xbf,
+ /* CS4236_LEFT_DSP */ 0xbf,
+ /* CS4236_RIGHT_DSP */ 0xbf,
+ /* CS4236_RIGHT_LOOPBACK */ 0xbf,
+ /* CS4236_DAC_MUTE */ 0xe0,
+ /* CS4236_ADC_RATE */ 0x01, /* 48kHz */
+ /* CS4236_DAC_RATE */ 0x01, /* 48kHz */
+ /* CS4236_LEFT_MASTER */ 0xbf,
+ /* CS4236_RIGHT_MASTER */ 0xbf,
+ /* CS4236_LEFT_WAVE */ 0xbf,
+ /* CS4236_RIGHT_WAVE */ 0xbf
+};
+
+/*
+ *
+ */
+
+static void snd_cs4236_ctrl_out(struct snd_wss *chip,
+ unsigned char reg, unsigned char val)
+{
+ outb(reg, chip->cport + 3);
+ outb(chip->cimage[reg] = val, chip->cport + 4);
+}
+
+static unsigned char snd_cs4236_ctrl_in(struct snd_wss *chip, unsigned char reg)
+{
+ outb(reg, chip->cport + 3);
+ return inb(chip->cport + 4);
+}
+
+/*
+ * PCM
+ */
+
+#define CLOCKS 8
+
+static const struct snd_ratnum clocks[CLOCKS] = {
+ { .num = 16934400, .den_min = 353, .den_max = 353, .den_step = 1 },
+ { .num = 16934400, .den_min = 529, .den_max = 529, .den_step = 1 },
+ { .num = 16934400, .den_min = 617, .den_max = 617, .den_step = 1 },
+ { .num = 16934400, .den_min = 1058, .den_max = 1058, .den_step = 1 },
+ { .num = 16934400, .den_min = 1764, .den_max = 1764, .den_step = 1 },
+ { .num = 16934400, .den_min = 2117, .den_max = 2117, .den_step = 1 },
+ { .num = 16934400, .den_min = 2558, .den_max = 2558, .den_step = 1 },
+ { .num = 16934400/16, .den_min = 21, .den_max = 192, .den_step = 1 }
+};
+
+static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = {
+ .nrats = CLOCKS,
+ .rats = clocks,
+};
+
+static int snd_cs4236_xrate(struct snd_pcm_runtime *runtime)
+{
+ return snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_clocks);
+}
+
+static unsigned char divisor_to_rate_register(unsigned int divisor)
+{
+ switch (divisor) {
+ case 353: return 1;
+ case 529: return 2;
+ case 617: return 3;
+ case 1058: return 4;
+ case 1764: return 5;
+ case 2117: return 6;
+ case 2558: return 7;
+ default:
+ if (divisor < 21 || divisor > 192) {
+ snd_BUG();
+ return 192;
+ }
+ return divisor;
+ }
+}
+
+static void snd_cs4236_playback_format(struct snd_wss *chip,
+ struct snd_pcm_hw_params *params,
+ unsigned char pdfr)
+{
+ unsigned long flags;
+ unsigned char rate = divisor_to_rate_register(params->rate_den);
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ /* set fast playback format change and clean playback FIFO */
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1] | 0x10);
+ snd_wss_out(chip, CS4231_PLAYBK_FORMAT, pdfr & 0xf0);
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1] & ~0x10);
+ snd_cs4236_ext_out(chip, CS4236_DAC_RATE, rate);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs4236_capture_format(struct snd_wss *chip,
+ struct snd_pcm_hw_params *params,
+ unsigned char cdfr)
+{
+ unsigned long flags;
+ unsigned char rate = divisor_to_rate_register(params->rate_den);
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ /* set fast capture format change and clean capture FIFO */
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1] | 0x20);
+ snd_wss_out(chip, CS4231_REC_FORMAT, cdfr & 0xf0);
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1] & ~0x20);
+ snd_cs4236_ext_out(chip, CS4236_ADC_RATE, rate);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+#ifdef CONFIG_PM
+
+static void snd_cs4236_suspend(struct snd_wss *chip)
+{
+ int reg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ for (reg = 0; reg < 32; reg++)
+ chip->image[reg] = snd_wss_in(chip, reg);
+ for (reg = 0; reg < 18; reg++)
+ chip->eimage[reg] = snd_cs4236_ext_in(chip, CS4236_I23VAL(reg));
+ for (reg = 2; reg < 9; reg++)
+ chip->cimage[reg] = snd_cs4236_ctrl_in(chip, reg);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs4236_resume(struct snd_wss *chip)
+{
+ int reg;
+ unsigned long flags;
+
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ for (reg = 0; reg < 32; reg++) {
+ switch (reg) {
+ case CS4236_EXT_REG:
+ case CS4231_VERSION:
+ case 27: /* why? CS4235 - master left */
+ case 29: /* why? CS4235 - master right */
+ break;
+ default:
+ snd_wss_out(chip, reg, chip->image[reg]);
+ break;
+ }
+ }
+ for (reg = 0; reg < 18; reg++)
+ snd_cs4236_ext_out(chip, CS4236_I23VAL(reg), chip->eimage[reg]);
+ for (reg = 2; reg < 9; reg++) {
+ switch (reg) {
+ case 7:
+ break;
+ default:
+ snd_cs4236_ctrl_out(chip, reg, chip->cimage[reg]);
+ }
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+}
+
+#endif /* CONFIG_PM */
+/*
+ * This function does no fail if the chip is not CS4236B or compatible.
+ * It just an equivalent to the snd_wss_create() then.
+ */
+int snd_cs4236_create(struct snd_card *card,
+ unsigned long port,
+ unsigned long cport,
+ int irq, int dma1, int dma2,
+ unsigned short hardware,
+ unsigned short hwshare,
+ struct snd_wss **rchip)
+{
+ struct snd_wss *chip;
+ unsigned char ver1, ver2;
+ unsigned int reg;
+ int err;
+
+ *rchip = NULL;
+ if (hardware == WSS_HW_DETECT)
+ hardware = WSS_HW_DETECT3;
+
+ err = snd_wss_create(card, port, cport,
+ irq, dma1, dma2, hardware, hwshare, &chip);
+ if (err < 0)
+ return err;
+
+ if ((chip->hardware & WSS_HW_CS4236B_MASK) == 0) {
+ snd_printd("chip is not CS4236+, hardware=0x%x\n",
+ chip->hardware);
+ *rchip = chip;
+ return 0;
+ }
+#if 0
+ {
+ int idx;
+ for (idx = 0; idx < 8; idx++)
+ snd_printk(KERN_DEBUG "CD%i = 0x%x\n",
+ idx, inb(chip->cport + idx));
+ for (idx = 0; idx < 9; idx++)
+ snd_printk(KERN_DEBUG "C%i = 0x%x\n",
+ idx, snd_cs4236_ctrl_in(chip, idx));
+ }
+#endif
+ if (cport < 0x100 || cport == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR "please, specify control port "
+ "for CS4236+ chips\n");
+ return -ENODEV;
+ }
+ ver1 = snd_cs4236_ctrl_in(chip, 1);
+ ver2 = snd_cs4236_ext_in(chip, CS4236_VERSION);
+ snd_printdd("CS4236: [0x%lx] C1 (version) = 0x%x, ext = 0x%x\n",
+ cport, ver1, ver2);
+ if (ver1 != ver2) {
+ snd_printk(KERN_ERR "CS4236+ chip detected, but "
+ "control port 0x%lx is not valid\n", cport);
+ return -ENODEV;
+ }
+ snd_cs4236_ctrl_out(chip, 0, 0x00);
+ snd_cs4236_ctrl_out(chip, 2, 0xff);
+ snd_cs4236_ctrl_out(chip, 3, 0x00);
+ snd_cs4236_ctrl_out(chip, 4, 0x80);
+ reg = ((IEC958_AES1_CON_PCM_CODER & 3) << 6) |
+ IEC958_AES0_CON_EMPHASIS_NONE;
+ snd_cs4236_ctrl_out(chip, 5, reg);
+ snd_cs4236_ctrl_out(chip, 6, IEC958_AES1_CON_PCM_CODER >> 2);
+ snd_cs4236_ctrl_out(chip, 7, 0x00);
+ /*
+ * 0x8c for C8 is valid for Turtle Beach Malibu - the IEC-958
+ * output is working with this setup, other hardware should
+ * have different signal paths and this value should be
+ * selectable in the future
+ */
+ snd_cs4236_ctrl_out(chip, 8, 0x8c);
+ chip->rate_constraint = snd_cs4236_xrate;
+ chip->set_playback_format = snd_cs4236_playback_format;
+ chip->set_capture_format = snd_cs4236_capture_format;
+#ifdef CONFIG_PM
+ chip->suspend = snd_cs4236_suspend;
+ chip->resume = snd_cs4236_resume;
+#endif
+
+ /* initialize extended registers */
+ for (reg = 0; reg < sizeof(snd_cs4236_ext_map); reg++)
+ snd_cs4236_ext_out(chip, CS4236_I23VAL(reg),
+ snd_cs4236_ext_map[reg]);
+
+ /* initialize compatible but more featured registers */
+ snd_wss_out(chip, CS4231_LEFT_INPUT, 0x40);
+ snd_wss_out(chip, CS4231_RIGHT_INPUT, 0x40);
+ snd_wss_out(chip, CS4231_AUX1_LEFT_INPUT, 0xff);
+ snd_wss_out(chip, CS4231_AUX1_RIGHT_INPUT, 0xff);
+ snd_wss_out(chip, CS4231_AUX2_LEFT_INPUT, 0xdf);
+ snd_wss_out(chip, CS4231_AUX2_RIGHT_INPUT, 0xdf);
+ snd_wss_out(chip, CS4231_RIGHT_LINE_IN, 0xff);
+ snd_wss_out(chip, CS4231_LEFT_LINE_IN, 0xff);
+ snd_wss_out(chip, CS4231_RIGHT_LINE_IN, 0xff);
+ switch (chip->hardware) {
+ case WSS_HW_CS4235:
+ case WSS_HW_CS4239:
+ snd_wss_out(chip, CS4235_LEFT_MASTER, 0xff);
+ snd_wss_out(chip, CS4235_RIGHT_MASTER, 0xff);
+ break;
+ }
+
+ *rchip = chip;
+ return 0;
+}
+
+int snd_cs4236_pcm(struct snd_wss *chip, int device)
+{
+ int err;
+
+ err = snd_wss_pcm(chip, device);
+ if (err < 0)
+ return err;
+ chip->pcm->info_flags &= ~SNDRV_PCM_INFO_JOINT_DUPLEX;
+ return 0;
+}
+
+/*
+ * MIXER
+ */
+
+#define CS4236_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_cs4236_info_single, \
+ .get = snd_cs4236_get_single, .put = snd_cs4236_put_single, \
+ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+#define CS4236_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .info = snd_cs4236_info_single, \
+ .get = snd_cs4236_get_single, .put = snd_cs4236_put_single, \
+ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
+ .tlv = { .p = (xtlv) } }
+
+static int snd_cs4236_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_cs4236_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(reg)] >> shift) & mask;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (invert)
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int snd_cs4236_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+ int change;
+ unsigned short val;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+ if (invert)
+ val = mask - val;
+ val <<= shift;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ val = (chip->eimage[CS4236_REG(reg)] & ~(mask << shift)) | val;
+ change = val != chip->eimage[CS4236_REG(reg)];
+ snd_cs4236_ext_out(chip, reg, val);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+#define CS4236_SINGLEC(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_cs4236_info_single, \
+ .get = snd_cs4236_get_singlec, .put = snd_cs4236_put_singlec, \
+ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_cs4236_get_singlec(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (chip->cimage[reg] >> shift) & mask;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (invert)
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int snd_cs4236_put_singlec(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+ int change;
+ unsigned short val;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+ if (invert)
+ val = mask - val;
+ val <<= shift;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ val = (chip->cimage[reg] & ~(mask << shift)) | val;
+ change = val != chip->cimage[reg];
+ snd_cs4236_ctrl_out(chip, reg, val);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+#define CS4236_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_cs4236_info_double, \
+ .get = snd_cs4236_get_double, .put = snd_cs4236_put_double, \
+ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+#define CS4236_DOUBLE_TLV(xname, xindex, left_reg, right_reg, shift_left, \
+ shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .info = snd_cs4236_info_double, \
+ .get = snd_cs4236_get_double, .put = snd_cs4236_put_double, \
+ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | \
+ (shift_right << 19) | (mask << 24) | (invert << 22), \
+ .tlv = { .p = (xtlv) } }
+
+static int snd_cs4236_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_cs4236_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(left_reg)] >> shift_left) & mask;
+ ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (invert) {
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+ }
+ return 0;
+}
+
+static int snd_cs4236_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+ int change;
+ unsigned short val1, val2;
+
+ val1 = ucontrol->value.integer.value[0] & mask;
+ val2 = ucontrol->value.integer.value[1] & mask;
+ if (invert) {
+ val1 = mask - val1;
+ val2 = mask - val2;
+ }
+ val1 <<= shift_left;
+ val2 <<= shift_right;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (left_reg != right_reg) {
+ val1 = (chip->eimage[CS4236_REG(left_reg)] & ~(mask << shift_left)) | val1;
+ val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2;
+ change = val1 != chip->eimage[CS4236_REG(left_reg)] || val2 != chip->eimage[CS4236_REG(right_reg)];
+ snd_cs4236_ext_out(chip, left_reg, val1);
+ snd_cs4236_ext_out(chip, right_reg, val2);
+ } else {
+ val1 = (chip->eimage[CS4236_REG(left_reg)] & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+ change = val1 != chip->eimage[CS4236_REG(left_reg)];
+ snd_cs4236_ext_out(chip, left_reg, val1);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+#define CS4236_DOUBLE1(xname, xindex, left_reg, right_reg, shift_left, \
+ shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_cs4236_info_double, \
+ .get = snd_cs4236_get_double1, .put = snd_cs4236_put_double1, \
+ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+#define CS4236_DOUBLE1_TLV(xname, xindex, left_reg, right_reg, shift_left, \
+ shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .info = snd_cs4236_info_double, \
+ .get = snd_cs4236_get_double1, .put = snd_cs4236_put_double1, \
+ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | \
+ (shift_right << 19) | (mask << 24) | (invert << 22), \
+ .tlv = { .p = (xtlv) } }
+
+static int snd_cs4236_get_double1(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
+ ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (invert) {
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+ }
+ return 0;
+}
+
+static int snd_cs4236_put_double1(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+ int change;
+ unsigned short val1, val2;
+
+ val1 = ucontrol->value.integer.value[0] & mask;
+ val2 = ucontrol->value.integer.value[1] & mask;
+ if (invert) {
+ val1 = mask - val1;
+ val2 = mask - val2;
+ }
+ val1 <<= shift_left;
+ val2 <<= shift_right;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+ val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2;
+ change = val1 != chip->image[left_reg] || val2 != chip->eimage[CS4236_REG(right_reg)];
+ snd_wss_out(chip, left_reg, val1);
+ snd_cs4236_ext_out(chip, right_reg, val2);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+#define CS4236_MASTER_DIGITAL(xname, xindex, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .info = snd_cs4236_info_double, \
+ .get = snd_cs4236_get_master_digital, .put = snd_cs4236_put_master_digital, \
+ .private_value = 71 << 24, \
+ .tlv = { .p = (xtlv) } }
+
+static inline int snd_cs4236_mixer_master_digital_invert_volume(int vol)
+{
+ return (vol < 64) ? 63 - vol : 64 + (71 - vol);
+}
+
+static int snd_cs4236_get_master_digital(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & 0x7f);
+ ucontrol->value.integer.value[1] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & 0x7f);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_cs4236_put_master_digital(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned short val1, val2;
+
+ val1 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[0] & 0x7f);
+ val2 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[1] & 0x7f);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ val1 = (chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & ~0x7f) | val1;
+ val2 = (chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & ~0x7f) | val2;
+ change = val1 != chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] || val2 != chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)];
+ snd_cs4236_ext_out(chip, CS4236_LEFT_MASTER, val1);
+ snd_cs4236_ext_out(chip, CS4236_RIGHT_MASTER, val2);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+#define CS4235_OUTPUT_ACCU(xname, xindex, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .info = snd_cs4236_info_double, \
+ .get = snd_cs4235_get_output_accu, .put = snd_cs4235_put_output_accu, \
+ .private_value = 3 << 24, \
+ .tlv = { .p = (xtlv) } }
+
+static inline int snd_cs4235_mixer_output_accu_get_volume(int vol)
+{
+ switch ((vol >> 5) & 3) {
+ case 0: return 1;
+ case 1: return 3;
+ case 2: return 2;
+ case 3: return 0;
+ }
+ return 3;
+}
+
+static inline int snd_cs4235_mixer_output_accu_set_volume(int vol)
+{
+ switch (vol & 3) {
+ case 0: return 3 << 5;
+ case 1: return 0 << 5;
+ case 2: return 2 << 5;
+ case 3: return 1 << 5;
+ }
+ return 1 << 5;
+}
+
+static int snd_cs4235_get_output_accu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_LEFT_MASTER]);
+ ucontrol->value.integer.value[1] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_RIGHT_MASTER]);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_cs4235_put_output_accu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned short val1, val2;
+
+ val1 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[0]);
+ val2 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[1]);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ val1 = (chip->image[CS4235_LEFT_MASTER] & ~(3 << 5)) | val1;
+ val2 = (chip->image[CS4235_RIGHT_MASTER] & ~(3 << 5)) | val2;
+ change = val1 != chip->image[CS4235_LEFT_MASTER] || val2 != chip->image[CS4235_RIGHT_MASTER];
+ snd_wss_out(chip, CS4235_LEFT_MASTER, val1);
+ snd_wss_out(chip, CS4235_RIGHT_MASTER, val2);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_7bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit_12db_max, -8250, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_22db_max, -2400, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_2bit, -1800, 600, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+
+static const struct snd_kcontrol_new snd_cs4236_controls[] = {
+
+CS4236_DOUBLE("Master Digital Playback Switch", 0,
+ CS4236_LEFT_MASTER, CS4236_RIGHT_MASTER, 7, 7, 1, 1),
+CS4236_DOUBLE("Master Digital Capture Switch", 0,
+ CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1),
+CS4236_MASTER_DIGITAL("Master Digital Volume", 0, db_scale_7bit),
+
+CS4236_DOUBLE_TLV("Capture Boost Volume", 0,
+ CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1,
+ db_scale_2bit),
+
+WSS_DOUBLE("PCM Playback Switch", 0,
+ CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+ CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+ db_scale_6bit),
+
+CS4236_DOUBLE("DSP Playback Switch", 0,
+ CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("DSP Playback Volume", 0,
+ CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 0, 0, 63, 1,
+ db_scale_6bit),
+
+CS4236_DOUBLE("FM Playback Switch", 0,
+ CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("FM Playback Volume", 0,
+ CS4236_LEFT_FM, CS4236_RIGHT_FM, 0, 0, 63, 1,
+ db_scale_6bit),
+
+CS4236_DOUBLE("Wavetable Playback Switch", 0,
+ CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("Wavetable Playback Volume", 0,
+ CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 0, 0, 63, 1,
+ db_scale_6bit_12db_max),
+
+WSS_DOUBLE("Synth Playback Switch", 0,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Synth Volume", 0,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1,
+ db_scale_5bit_12db_max),
+WSS_DOUBLE("Synth Capture Switch", 0,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1),
+WSS_DOUBLE("Synth Capture Bypass", 0,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 5, 5, 1, 1),
+
+CS4236_DOUBLE("Mic Playback Switch", 0,
+ CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1),
+CS4236_DOUBLE("Mic Capture Switch", 0,
+ CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("Mic Volume", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC,
+ 0, 0, 31, 1, db_scale_5bit_22db_max),
+CS4236_DOUBLE("Mic Playback Boost (+20dB)", 0,
+ CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 5, 5, 1, 0),
+
+WSS_DOUBLE("Line Playback Switch", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Line Volume", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+ db_scale_5bit_12db_max),
+WSS_DOUBLE("Line Capture Switch", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1),
+WSS_DOUBLE("Line Capture Bypass", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 5, 5, 1, 1),
+
+WSS_DOUBLE("CD Playback Switch", 0,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("CD Volume", 0,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+ db_scale_5bit_12db_max),
+WSS_DOUBLE("CD Capture Switch", 0,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1),
+
+CS4236_DOUBLE1("Mono Output Playback Switch", 0,
+ CS4231_MONO_CTRL, CS4236_RIGHT_MIX_CTRL, 6, 7, 1, 1),
+CS4236_DOUBLE1("Beep Playback Switch", 0,
+ CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1),
+WSS_SINGLE_TLV("Beep Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1,
+ db_scale_4bit),
+WSS_SINGLE("Beep Bypass Playback Switch", 0, CS4231_MONO_CTRL, 5, 1, 0),
+
+WSS_DOUBLE_TLV("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT,
+ 0, 0, 15, 0, db_scale_rec_gain),
+WSS_DOUBLE("Analog Loopback Capture Switch", 0,
+ CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0),
+
+WSS_SINGLE("Loopback Digital Playback Switch", 0, CS4231_LOOPBACK, 0, 1, 0),
+CS4236_DOUBLE1_TLV("Loopback Digital Playback Volume", 0,
+ CS4231_LOOPBACK, CS4236_RIGHT_LOOPBACK, 2, 0, 63, 1,
+ db_scale_6bit),
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_6db_max, -5600, 200, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_2bit_16db_max, -2400, 800, 0);
+
+static const struct snd_kcontrol_new snd_cs4235_controls[] = {
+
+WSS_DOUBLE("Master Playback Switch", 0,
+ CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Master Playback Volume", 0,
+ CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 0, 0, 31, 1,
+ db_scale_5bit_6db_max),
+
+CS4235_OUTPUT_ACCU("Playback Volume", 0, db_scale_2bit_16db_max),
+
+WSS_DOUBLE("Synth Playback Switch", 1,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE("Synth Capture Switch", 1,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1),
+WSS_DOUBLE_TLV("Synth Volume", 1,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1,
+ db_scale_5bit_12db_max),
+
+CS4236_DOUBLE_TLV("Capture Volume", 0,
+ CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1,
+ db_scale_2bit),
+
+WSS_DOUBLE("PCM Playback Switch", 0,
+ CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE("PCM Capture Switch", 0,
+ CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1),
+WSS_DOUBLE_TLV("PCM Volume", 0,
+ CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+ db_scale_6bit),
+
+CS4236_DOUBLE("DSP Switch", 0, CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1),
+
+CS4236_DOUBLE("FM Switch", 0, CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1),
+
+CS4236_DOUBLE("Wavetable Switch", 0,
+ CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1),
+
+CS4236_DOUBLE("Mic Capture Switch", 0,
+ CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1),
+CS4236_DOUBLE("Mic Playback Switch", 0,
+ CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1),
+CS4236_SINGLE_TLV("Mic Volume", 0, CS4236_LEFT_MIC, 0, 31, 1,
+ db_scale_5bit_22db_max),
+CS4236_SINGLE("Mic Boost (+20dB)", 0, CS4236_LEFT_MIC, 5, 1, 0),
+
+WSS_DOUBLE("Line Playback Switch", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("Line Capture Switch", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1),
+WSS_DOUBLE_TLV("Line Volume", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+ db_scale_5bit_12db_max),
+
+WSS_DOUBLE("CD Playback Switch", 1,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("CD Capture Switch", 1,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1),
+WSS_DOUBLE_TLV("CD Volume", 1,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+ db_scale_5bit_12db_max),
+
+CS4236_DOUBLE1("Beep Playback Switch", 0,
+ CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1),
+WSS_SINGLE("Beep Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1),
+
+WSS_DOUBLE("Analog Loopback Switch", 0,
+ CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0),
+};
+
+#define CS4236_IEC958_ENABLE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_cs4236_info_single, \
+ .get = snd_cs4236_get_iec958_switch, .put = snd_cs4236_put_iec958_switch, \
+ .private_value = 1 << 16 }
+
+static int snd_cs4236_get_iec958_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = chip->image[CS4231_ALT_FEATURE_1] & 0x02 ? 1 : 0;
+#if 0
+ printk(KERN_DEBUG "get valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, "
+ "C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n",
+ snd_wss_in(chip, CS4231_ALT_FEATURE_1),
+ snd_cs4236_ctrl_in(chip, 3),
+ snd_cs4236_ctrl_in(chip, 4),
+ snd_cs4236_ctrl_in(chip, 5),
+ snd_cs4236_ctrl_in(chip, 6),
+ snd_cs4236_ctrl_in(chip, 8));
+#endif
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_cs4236_put_iec958_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned short enable, val;
+
+ enable = ucontrol->value.integer.value[0] & 1;
+
+ mutex_lock(&chip->mce_mutex);
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ val = (chip->image[CS4231_ALT_FEATURE_1] & ~0x0e) | (0<<2) | (enable << 1);
+ change = val != chip->image[CS4231_ALT_FEATURE_1];
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1, val);
+ val = snd_cs4236_ctrl_in(chip, 4) | 0xc0;
+ snd_cs4236_ctrl_out(chip, 4, val);
+ udelay(100);
+ val &= ~0x40;
+ snd_cs4236_ctrl_out(chip, 4, val);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+ mutex_unlock(&chip->mce_mutex);
+
+#if 0
+ printk(KERN_DEBUG "set valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, "
+ "C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n",
+ snd_wss_in(chip, CS4231_ALT_FEATURE_1),
+ snd_cs4236_ctrl_in(chip, 3),
+ snd_cs4236_ctrl_in(chip, 4),
+ snd_cs4236_ctrl_in(chip, 5),
+ snd_cs4236_ctrl_in(chip, 6),
+ snd_cs4236_ctrl_in(chip, 8));
+#endif
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_cs4236_iec958_controls[] = {
+CS4236_IEC958_ENABLE("IEC958 Output Enable", 0),
+CS4236_SINGLEC("IEC958 Output Validity", 0, 4, 4, 1, 0),
+CS4236_SINGLEC("IEC958 Output User", 0, 4, 5, 1, 0),
+CS4236_SINGLEC("IEC958 Output CSBR", 0, 4, 6, 1, 0),
+CS4236_SINGLEC("IEC958 Output Channel Status Low", 0, 5, 1, 127, 0),
+CS4236_SINGLEC("IEC958 Output Channel Status High", 0, 6, 0, 255, 0)
+};
+
+static const struct snd_kcontrol_new snd_cs4236_3d_controls_cs4235[] = {
+CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0),
+CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1)
+};
+
+static const struct snd_kcontrol_new snd_cs4236_3d_controls_cs4237[] = {
+CS4236_SINGLEC("3D Control - Switch", 0, 3, 7, 1, 0),
+CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1),
+CS4236_SINGLEC("3D Control - Center", 0, 2, 0, 15, 1),
+CS4236_SINGLEC("3D Control - Mono", 0, 3, 6, 1, 0),
+CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0)
+};
+
+static const struct snd_kcontrol_new snd_cs4236_3d_controls_cs4238[] = {
+CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0),
+CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1),
+CS4236_SINGLEC("3D Control - Volume", 0, 2, 0, 15, 1),
+CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0)
+};
+
+int snd_cs4236_mixer(struct snd_wss *chip)
+{
+ struct snd_card *card;
+ unsigned int idx, count;
+ int err;
+ const struct snd_kcontrol_new *kcontrol;
+
+ if (snd_BUG_ON(!chip || !chip->card))
+ return -EINVAL;
+ card = chip->card;
+ strcpy(card->mixername, snd_wss_chip_id(chip));
+
+ if (chip->hardware == WSS_HW_CS4235 ||
+ chip->hardware == WSS_HW_CS4239) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_cs4235_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4235_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ } else {
+ for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ }
+ switch (chip->hardware) {
+ case WSS_HW_CS4235:
+ case WSS_HW_CS4239:
+ count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4235);
+ kcontrol = snd_cs4236_3d_controls_cs4235;
+ break;
+ case WSS_HW_CS4237B:
+ count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4237);
+ kcontrol = snd_cs4236_3d_controls_cs4237;
+ break;
+ case WSS_HW_CS4238B:
+ count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4238);
+ kcontrol = snd_cs4236_3d_controls_cs4238;
+ break;
+ default:
+ count = 0;
+ kcontrol = NULL;
+ }
+ for (idx = 0; idx < count; idx++, kcontrol++) {
+ err = snd_ctl_add(card, snd_ctl_new1(kcontrol, chip));
+ if (err < 0)
+ return err;
+ }
+ if (chip->hardware == WSS_HW_CS4237B ||
+ chip->hardware == WSS_HW_CS4238B) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_iec958_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_iec958_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ }
+ return 0;
+}
diff --git a/sound/isa/es1688/Makefile b/sound/isa/es1688/Makefile
new file mode 100644
index 0000000000..c683ac36c5
--- /dev/null
+++ b/sound/isa/es1688/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-es1688-lib-objs := es1688_lib.o
+snd-es1688-objs := es1688.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ES1688) += snd-es1688.o snd-es1688-lib.o
+obj-$(CONFIG_SND_GUSEXTREME) += snd-es1688-lib.o
diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c
new file mode 100644
index 0000000000..97728bf454
--- /dev/null
+++ b/sound/isa/es1688/es1688.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for generic ESS AudioDrive ESx688 soundcards
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/isapnp.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/es1688.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define CRD_NAME "Generic ESS ES1688/ES688 AudioDrive"
+#define DEV_NAME "es1688"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("snd_es968");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#endif
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* Usually 0x388 */
+static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1};
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for ES1688 driver.");
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver.");
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev) isapnp[dev]
+#else
+#define is_isapnp_selected(dev) 0
+#endif
+
+static int snd_es1688_match(struct device *dev, unsigned int n)
+{
+ return enable[n] && !is_isapnp_selected(n);
+}
+
+static int snd_es1688_legacy_create(struct snd_card *card,
+ struct device *dev, unsigned int n)
+{
+ struct snd_es1688 *chip = card->private_data;
+ static const long possible_ports[] = {0x220, 0x240, 0x260};
+ static const int possible_irqs[] = {5, 9, 10, 7, -1};
+ static const int possible_dmas[] = {1, 3, 0, -1};
+
+ int i, error;
+
+ if (irq[n] == SNDRV_AUTO_IRQ) {
+ irq[n] = snd_legacy_find_free_irq(possible_irqs);
+ if (irq[n] < 0) {
+ dev_err(dev, "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (dma8[n] == SNDRV_AUTO_DMA) {
+ dma8[n] = snd_legacy_find_free_dma(possible_dmas);
+ if (dma8[n] < 0) {
+ dev_err(dev, "unable to find a free DMA\n");
+ return -EBUSY;
+ }
+ }
+
+ if (port[n] != SNDRV_AUTO_PORT)
+ return snd_es1688_create(card, chip, port[n], mpu_port[n],
+ irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO);
+
+ i = 0;
+ do {
+ port[n] = possible_ports[i];
+ error = snd_es1688_create(card, chip, port[n], mpu_port[n],
+ irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO);
+ } while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
+
+ return error;
+}
+
+static int snd_es1688_probe(struct snd_card *card, unsigned int n)
+{
+ struct snd_es1688 *chip = card->private_data;
+ struct snd_opl3 *opl3;
+ int error;
+
+ error = snd_es1688_pcm(card, chip, 0);
+ if (error < 0)
+ return error;
+
+ error = snd_es1688_mixer(card, chip);
+ if (error < 0)
+ return error;
+
+ strscpy(card->driver, "ES1688", sizeof(card->driver));
+ strscpy(card->shortname, chip->pcm->name, sizeof(card->shortname));
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx, irq %i, dma %i", chip->pcm->name, chip->port,
+ chip->irq, chip->dma8);
+
+ if (fm_port[n] == SNDRV_AUTO_PORT)
+ fm_port[n] = port[n]; /* share the same port */
+
+ if (fm_port[n] > 0) {
+ if (snd_opl3_create(card, fm_port[n], fm_port[n] + 2,
+ OPL3_HW_OPL3, 0, &opl3) < 0)
+ dev_warn(card->dev,
+ "opl3 not detected at 0x%lx\n", fm_port[n]);
+ else {
+ error = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ if (mpu_irq[n] >= 0 && mpu_irq[n] != SNDRV_AUTO_IRQ &&
+ chip->mpu_port > 0) {
+ error = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688,
+ chip->mpu_port, 0,
+ mpu_irq[n], NULL);
+ if (error < 0)
+ return error;
+ }
+
+ return snd_card_register(card);
+}
+
+static int snd_es1688_isa_probe(struct device *dev, unsigned int n)
+{
+ struct snd_card *card;
+ int error;
+
+ error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE,
+ sizeof(struct snd_es1688), &card);
+ if (error < 0)
+ return error;
+
+ error = snd_es1688_legacy_create(card, dev, n);
+ if (error < 0)
+ return error;
+
+ error = snd_es1688_probe(card, n);
+ if (error < 0)
+ return error;
+
+ dev_set_drvdata(dev, card);
+
+ return 0;
+}
+
+static struct isa_driver snd_es1688_driver = {
+ .match = snd_es1688_match,
+ .probe = snd_es1688_isa_probe,
+#if 0 /* FIXME */
+ .suspend = snd_es1688_suspend,
+ .resume = snd_es1688_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ }
+};
+
+static int snd_es968_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+static int snd_card_es968_pnp(struct snd_card *card, unsigned int n,
+ struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ struct snd_es1688 *chip = card->private_data;
+ struct pnp_dev *pdev;
+ int error;
+
+ pdev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+ if (pdev == NULL)
+ return -ENODEV;
+
+ error = pnp_activate_dev(pdev);
+ if (error < 0) {
+ snd_printk(KERN_ERR "ES968 pnp configure failure\n");
+ return error;
+ }
+ port[n] = pnp_port_start(pdev, 0);
+ dma8[n] = pnp_dma(pdev, 0);
+ irq[n] = pnp_irq(pdev, 0);
+
+ return snd_es1688_create(card, chip, port[n], mpu_port[n], irq[n],
+ mpu_irq[n], dma8[n], ES1688_HW_AUTO);
+}
+
+static int snd_es968_pnp_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ struct snd_card *card;
+ static unsigned int dev;
+ int error;
+
+ if (snd_es968_pnp_is_probed)
+ return -EBUSY;
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev == SNDRV_CARDS)
+ return -ENODEV;
+
+ error = snd_devm_card_new(&pcard->card->dev,
+ index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_es1688), &card);
+ if (error < 0)
+ return error;
+
+ error = snd_card_es968_pnp(card, dev, pcard, pid);
+ if (error < 0)
+ return error;
+ error = snd_es1688_probe(card, dev);
+ if (error < 0)
+ return error;
+ pnp_set_card_drvdata(pcard, card);
+ snd_es968_pnp_is_probed = 1;
+ return 0;
+}
+
+static void snd_es968_pnp_remove(struct pnp_card_link *pcard)
+{
+ snd_es968_pnp_is_probed = 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_es968_pnp_suspend(struct pnp_card_link *pcard,
+ pm_message_t state)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ return 0;
+}
+
+static int snd_es968_pnp_resume(struct pnp_card_link *pcard)
+{
+ struct snd_card *card = pnp_get_card_drvdata(pcard);
+ struct snd_es1688 *chip = card->private_data;
+
+ snd_es1688_reset(chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+static const struct pnp_card_device_id snd_es968_pnpids[] = {
+ { .id = "ESS0968", .devs = { { "@@@0968" }, } },
+ { .id = "ESS0968", .devs = { { "ESS0968" }, } },
+ { .id = "", } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_es968_pnpids);
+
+static struct pnp_card_driver es968_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = DEV_NAME " PnP",
+ .id_table = snd_es968_pnpids,
+ .probe = snd_es968_pnp_detect,
+ .remove = snd_es968_pnp_remove,
+#ifdef CONFIG_PM
+ .suspend = snd_es968_pnp_suspend,
+ .resume = snd_es968_pnp_resume,
+#endif
+};
+#endif
+
+static int __init alsa_card_es1688_init(void)
+{
+#ifdef CONFIG_PNP
+ pnp_register_card_driver(&es968_pnpc_driver);
+ if (snd_es968_pnp_is_probed)
+ return 0;
+ pnp_unregister_card_driver(&es968_pnpc_driver);
+#endif
+ return isa_register_driver(&snd_es1688_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_es1688_exit(void)
+{
+ if (!snd_es968_pnp_is_probed) {
+ isa_unregister_driver(&snd_es1688_driver);
+ return;
+ }
+#ifdef CONFIG_PNP
+ pnp_unregister_card_driver(&es968_pnpc_driver);
+#endif
+}
+
+module_init(alsa_card_es1688_init);
+module_exit(alsa_card_es1688_exit);
diff --git a/sound/isa/es1688/es1688_lib.c b/sound/isa/es1688/es1688_lib.c
new file mode 100644
index 0000000000..8554cb2263
--- /dev/null
+++ b/sound/isa/es1688/es1688_lib.c
@@ -0,0 +1,992 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for control of ESS ES1688/688/488 chip
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/es1688.h>
+#include <sound/initval.h>
+
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ESS ESx688 lowlevel module");
+MODULE_LICENSE("GPL");
+
+static int snd_es1688_dsp_command(struct snd_es1688 *chip, unsigned char val)
+{
+ int i;
+
+ for (i = 10000; i; i--)
+ if ((inb(ES1688P(chip, STATUS)) & 0x80) == 0) {
+ outb(val, ES1688P(chip, COMMAND));
+ return 1;
+ }
+#ifdef CONFIG_SND_DEBUG
+ printk(KERN_DEBUG "snd_es1688_dsp_command: timeout (0x%x)\n", val);
+#endif
+ return 0;
+}
+
+static int snd_es1688_dsp_get_byte(struct snd_es1688 *chip)
+{
+ int i;
+
+ for (i = 1000; i; i--)
+ if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80)
+ return inb(ES1688P(chip, READ));
+ snd_printd("es1688 get byte failed: 0x%lx = 0x%x!!!\n", ES1688P(chip, DATA_AVAIL), inb(ES1688P(chip, DATA_AVAIL)));
+ return -ENODEV;
+}
+
+static int snd_es1688_write(struct snd_es1688 *chip,
+ unsigned char reg, unsigned char data)
+{
+ if (!snd_es1688_dsp_command(chip, reg))
+ return 0;
+ return snd_es1688_dsp_command(chip, data);
+}
+
+static int snd_es1688_read(struct snd_es1688 *chip, unsigned char reg)
+{
+ /* Read a byte from an extended mode register of ES1688 */
+ if (!snd_es1688_dsp_command(chip, 0xc0))
+ return -1;
+ if (!snd_es1688_dsp_command(chip, reg))
+ return -1;
+ return snd_es1688_dsp_get_byte(chip);
+}
+
+void snd_es1688_mixer_write(struct snd_es1688 *chip,
+ unsigned char reg, unsigned char data)
+{
+ outb(reg, ES1688P(chip, MIXER_ADDR));
+ udelay(10);
+ outb(data, ES1688P(chip, MIXER_DATA));
+ udelay(10);
+}
+
+static unsigned char snd_es1688_mixer_read(struct snd_es1688 *chip, unsigned char reg)
+{
+ unsigned char result;
+
+ outb(reg, ES1688P(chip, MIXER_ADDR));
+ udelay(10);
+ result = inb(ES1688P(chip, MIXER_DATA));
+ udelay(10);
+ return result;
+}
+
+int snd_es1688_reset(struct snd_es1688 *chip)
+{
+ int i;
+
+ outb(3, ES1688P(chip, RESET)); /* valid only for ESS chips, SB -> 1 */
+ udelay(10);
+ outb(0, ES1688P(chip, RESET));
+ udelay(30);
+ for (i = 0; i < 1000 && !(inb(ES1688P(chip, DATA_AVAIL)) & 0x80); i++);
+ if (inb(ES1688P(chip, READ)) != 0xaa) {
+ snd_printd("ess_reset at 0x%lx: failed!!!\n", chip->port);
+ return -ENODEV;
+ }
+ snd_es1688_dsp_command(chip, 0xc6); /* enable extended mode */
+ return 0;
+}
+EXPORT_SYMBOL(snd_es1688_reset);
+
+static int snd_es1688_probe(struct snd_es1688 *chip)
+{
+ unsigned long flags;
+ unsigned short major, minor;
+ int i;
+
+ /*
+ * initialization sequence
+ */
+
+ spin_lock_irqsave(&chip->reg_lock, flags); /* Some ESS1688 cards need this */
+ inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
+ inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
+ inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
+ inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */
+ inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
+ inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */
+ inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
+ inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
+ inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */
+ inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
+ inb(ES1688P(chip, ENABLE0)); /* ENABLE0 */
+
+ if (snd_es1688_reset(chip) < 0) {
+ snd_printdd("ESS: [0x%lx] reset failed... 0x%x\n", chip->port, inb(ES1688P(chip, READ)));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return -ENODEV;
+ }
+ snd_es1688_dsp_command(chip, 0xe7); /* return identification */
+
+ for (i = 1000, major = minor = 0; i; i--) {
+ if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80) {
+ if (major == 0) {
+ major = inb(ES1688P(chip, READ));
+ } else {
+ minor = inb(ES1688P(chip, READ));
+ }
+ }
+ }
+
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ snd_printdd("ESS: [0x%lx] found.. major = 0x%x, minor = 0x%x\n", chip->port, major, minor);
+
+ chip->version = (major << 8) | minor;
+ if (!chip->version)
+ return -ENODEV; /* probably SB */
+
+ switch (chip->version & 0xfff0) {
+ case 0x4880:
+ snd_printk(KERN_ERR "[0x%lx] ESS: AudioDrive ES488 detected, "
+ "but driver is in another place\n", chip->port);
+ return -ENODEV;
+ case 0x6880:
+ break;
+ default:
+ snd_printk(KERN_ERR "[0x%lx] ESS: unknown AudioDrive chip "
+ "with version 0x%x (Jazz16 soundcard?)\n",
+ chip->port, chip->version);
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_write(chip, 0xb1, 0x10); /* disable IRQ */
+ snd_es1688_write(chip, 0xb2, 0x00); /* disable DMA */
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ /* enable joystick, but disable OPL3 */
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ snd_es1688_mixer_write(chip, 0x40, 0x01);
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+ return 0;
+}
+
+static int snd_es1688_init(struct snd_es1688 * chip, int enable)
+{
+ static const int irqs[16] = {-1, -1, 0, -1, -1, 1, -1, 2, -1, 0, 3, -1, -1, -1, -1, -1};
+ unsigned long flags;
+ int cfg, irq_bits, dma, dma_bits, tmp, tmp1;
+
+ /* ok.. setup MPU-401 port and joystick and OPL3 */
+ cfg = 0x01; /* enable joystick, but disable OPL3 */
+ if (enable && chip->mpu_port >= 0x300 && chip->mpu_irq > 0 && chip->hardware != ES1688_HW_688) {
+ tmp = (chip->mpu_port & 0x0f0) >> 4;
+ if (tmp <= 3) {
+ switch (chip->mpu_irq) {
+ case 9:
+ tmp1 = 4;
+ break;
+ case 5:
+ tmp1 = 5;
+ break;
+ case 7:
+ tmp1 = 6;
+ break;
+ case 10:
+ tmp1 = 7;
+ break;
+ default:
+ tmp1 = 0;
+ }
+ if (tmp1) {
+ cfg |= (tmp << 3) | (tmp1 << 5);
+ }
+ }
+ }
+#if 0
+ snd_printk(KERN_DEBUG "mpu cfg = 0x%x\n", cfg);
+#endif
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_mixer_write(chip, 0x40, cfg);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ /* --- */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_read(chip, 0xb1);
+ snd_es1688_read(chip, 0xb2);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (enable) {
+ cfg = 0xf0; /* enable only DMA counter interrupt */
+ irq_bits = irqs[chip->irq & 0x0f];
+ if (irq_bits < 0) {
+ snd_printk(KERN_ERR "[0x%lx] ESS: bad IRQ %d "
+ "for ES1688 chip!!\n",
+ chip->port, chip->irq);
+#if 0
+ irq_bits = 0;
+ cfg = 0x10;
+#endif
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_write(chip, 0xb1, cfg | (irq_bits << 2));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ cfg = 0xf0; /* extended mode DMA enable */
+ dma = chip->dma8;
+ if (dma > 3 || dma == 2) {
+ snd_printk(KERN_ERR "[0x%lx] ESS: bad DMA channel %d "
+ "for ES1688 chip!!\n", chip->port, dma);
+#if 0
+ dma_bits = 0;
+ cfg = 0x00; /* disable all DMA */
+#endif
+ return -EINVAL;
+ } else {
+ dma_bits = dma;
+ if (dma != 3)
+ dma_bits++;
+ }
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_write(chip, 0xb2, cfg | (dma_bits << 2));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ } else {
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_write(chip, 0xb1, 0x10); /* disable IRQ */
+ snd_es1688_write(chip, 0xb2, 0x00); /* disable DMA */
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ }
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_read(chip, 0xb1);
+ snd_es1688_read(chip, 0xb2);
+ snd_es1688_reset(chip);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+/*
+
+ */
+
+static const struct snd_ratnum clocks[2] = {
+ {
+ .num = 795444,
+ .den_min = 1,
+ .den_max = 128,
+ .den_step = 1,
+ },
+ {
+ .num = 397722,
+ .den_min = 1,
+ .den_max = 128,
+ .den_step = 1,
+ }
+};
+
+static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = {
+ .nrats = 2,
+ .rats = clocks,
+};
+
+static void snd_es1688_set_rate(struct snd_es1688 *chip, struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int bits, divider;
+
+ if (runtime->rate_num == clocks[0].num)
+ bits = 256 - runtime->rate_den;
+ else
+ bits = 128 - runtime->rate_den;
+ /* set filter register */
+ divider = 256 - 7160000*20/(8*82*runtime->rate);
+ /* write result to hardware */
+ snd_es1688_write(chip, 0xa1, bits);
+ snd_es1688_write(chip, 0xa2, divider);
+}
+
+static int snd_es1688_trigger(struct snd_es1688 *chip, int cmd, unsigned char value)
+{
+ int val;
+
+ if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ value = 0x00;
+ } else if (cmd != SNDRV_PCM_TRIGGER_START) {
+ return -EINVAL;
+ }
+ spin_lock(&chip->reg_lock);
+ chip->trigger_value = value;
+ val = snd_es1688_read(chip, 0xb8);
+ if ((val < 0) || (val & 0x0f) == value) {
+ spin_unlock(&chip->reg_lock);
+ return -EINVAL; /* something is wrong */
+ }
+#if 0
+ printk(KERN_DEBUG "trigger: val = 0x%x, value = 0x%x\n", val, value);
+ printk(KERN_DEBUG "trigger: pointer = 0x%x\n",
+ snd_dma_pointer(chip->dma8, chip->dma_size));
+#endif
+ snd_es1688_write(chip, 0xb8, (val & 0xf0) | value);
+ spin_unlock(&chip->reg_lock);
+ return 0;
+}
+
+static int snd_es1688_playback_prepare(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+ chip->dma_size = size;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_reset(chip);
+ snd_es1688_set_rate(chip, substream);
+ snd_es1688_write(chip, 0xb8, 4); /* auto init DMA mode */
+ snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels));
+ snd_es1688_write(chip, 0xb9, 2); /* demand mode (4 bytes/request) */
+ if (runtime->channels == 1) {
+ if (snd_pcm_format_width(runtime->format) == 8) {
+ /* 8. bit mono */
+ snd_es1688_write(chip, 0xb6, 0x80);
+ snd_es1688_write(chip, 0xb7, 0x51);
+ snd_es1688_write(chip, 0xb7, 0xd0);
+ } else {
+ /* 16. bit mono */
+ snd_es1688_write(chip, 0xb6, 0x00);
+ snd_es1688_write(chip, 0xb7, 0x71);
+ snd_es1688_write(chip, 0xb7, 0xf4);
+ }
+ } else {
+ if (snd_pcm_format_width(runtime->format) == 8) {
+ /* 8. bit stereo */
+ snd_es1688_write(chip, 0xb6, 0x80);
+ snd_es1688_write(chip, 0xb7, 0x51);
+ snd_es1688_write(chip, 0xb7, 0x98);
+ } else {
+ /* 16. bit stereo */
+ snd_es1688_write(chip, 0xb6, 0x00);
+ snd_es1688_write(chip, 0xb7, 0x71);
+ snd_es1688_write(chip, 0xb7, 0xbc);
+ }
+ }
+ snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50);
+ snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50);
+ snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKON);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ /* --- */
+ count = -count;
+ snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_write(chip, 0xa4, (unsigned char) count);
+ snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_es1688_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+ return snd_es1688_trigger(chip, cmd, 0x05);
+}
+
+static int snd_es1688_capture_prepare(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+ chip->dma_size = size;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_reset(chip);
+ snd_es1688_set_rate(chip, substream);
+ snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKOFF);
+ snd_es1688_write(chip, 0xb8, 0x0e); /* auto init DMA mode */
+ snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels));
+ snd_es1688_write(chip, 0xb9, 2); /* demand mode (4 bytes/request) */
+ if (runtime->channels == 1) {
+ if (snd_pcm_format_width(runtime->format) == 8) {
+ /* 8. bit mono */
+ snd_es1688_write(chip, 0xb7, 0x51);
+ snd_es1688_write(chip, 0xb7, 0xd0);
+ } else {
+ /* 16. bit mono */
+ snd_es1688_write(chip, 0xb7, 0x71);
+ snd_es1688_write(chip, 0xb7, 0xf4);
+ }
+ } else {
+ if (snd_pcm_format_width(runtime->format) == 8) {
+ /* 8. bit stereo */
+ snd_es1688_write(chip, 0xb7, 0x51);
+ snd_es1688_write(chip, 0xb7, 0x98);
+ } else {
+ /* 16. bit stereo */
+ snd_es1688_write(chip, 0xb7, 0x71);
+ snd_es1688_write(chip, 0xb7, 0xbc);
+ }
+ }
+ snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50);
+ snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ /* --- */
+ count = -count;
+ snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_es1688_write(chip, 0xa4, (unsigned char) count);
+ snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_es1688_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+ return snd_es1688_trigger(chip, cmd, 0x0f);
+}
+
+static irqreturn_t snd_es1688_interrupt(int irq, void *dev_id)
+{
+ struct snd_es1688 *chip = dev_id;
+
+ if (chip->trigger_value == 0x05) /* ok.. playback is active */
+ snd_pcm_period_elapsed(chip->playback_substream);
+ if (chip->trigger_value == 0x0f) /* ok.. capture is active */
+ snd_pcm_period_elapsed(chip->capture_substream);
+
+ inb(ES1688P(chip, DATA_AVAIL)); /* ack interrupt */
+ return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_es1688_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+
+ if (chip->trigger_value != 0x05)
+ return 0;
+ ptr = snd_dma_pointer(chip->dma8, chip->dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_es1688_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+
+ if (chip->trigger_value != 0x0f)
+ return 0;
+ ptr = snd_dma_pointer(chip->dma8, chip->dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static const struct snd_pcm_hardware snd_es1688_playback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = 65536,
+ .period_bytes_min = 64,
+ .period_bytes_max = 65536,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_es1688_capture =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = 65536,
+ .period_bytes_min = 64,
+ .period_bytes_max = 65536,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/*
+
+ */
+
+static int snd_es1688_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if (chip->capture_substream != NULL)
+ return -EAGAIN;
+ chip->playback_substream = substream;
+ runtime->hw = snd_es1688_playback;
+ snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_clocks);
+ return 0;
+}
+
+static int snd_es1688_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if (chip->playback_substream != NULL)
+ return -EAGAIN;
+ chip->capture_substream = substream;
+ runtime->hw = snd_es1688_capture;
+ snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_clocks);
+ return 0;
+}
+
+static int snd_es1688_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+
+ chip->playback_substream = NULL;
+ return 0;
+}
+
+static int snd_es1688_capture_close(struct snd_pcm_substream *substream)
+{
+ struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+
+ chip->capture_substream = NULL;
+ return 0;
+}
+
+static int snd_es1688_free(struct snd_es1688 *chip)
+{
+ if (chip->hardware != ES1688_HW_UNDEF)
+ snd_es1688_init(chip, 0);
+ release_and_free_resource(chip->res_port);
+ if (chip->irq >= 0)
+ free_irq(chip->irq, (void *) chip);
+ if (chip->dma8 >= 0) {
+ disable_dma(chip->dma8);
+ free_dma(chip->dma8);
+ }
+ return 0;
+}
+
+static int snd_es1688_dev_free(struct snd_device *device)
+{
+ struct snd_es1688 *chip = device->device_data;
+ return snd_es1688_free(chip);
+}
+
+static const char *snd_es1688_chip_id(struct snd_es1688 *chip)
+{
+ static char tmp[16];
+ sprintf(tmp, "ES%s688 rev %i", chip->hardware == ES1688_HW_688 ? "" : "1", chip->version & 0x0f);
+ return tmp;
+}
+
+int snd_es1688_create(struct snd_card *card,
+ struct snd_es1688 *chip,
+ unsigned long port,
+ unsigned long mpu_port,
+ int irq,
+ int mpu_irq,
+ int dma8,
+ unsigned short hardware)
+{
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_es1688_dev_free,
+ };
+
+ int err;
+
+ if (chip == NULL)
+ return -ENOMEM;
+ chip->irq = -1;
+ chip->dma8 = -1;
+ chip->hardware = ES1688_HW_UNDEF;
+
+ chip->res_port = request_region(port + 4, 12, "ES1688");
+ if (chip->res_port == NULL) {
+ snd_printk(KERN_ERR "es1688: can't grab port 0x%lx\n", port + 4);
+ err = -EBUSY;
+ goto exit;
+ }
+
+ err = request_irq(irq, snd_es1688_interrupt, 0, "ES1688", (void *) chip);
+ if (err < 0) {
+ snd_printk(KERN_ERR "es1688: can't grab IRQ %d\n", irq);
+ goto exit;
+ }
+
+ chip->irq = irq;
+ card->sync_irq = chip->irq;
+ err = request_dma(dma8, "ES1688");
+
+ if (err < 0) {
+ snd_printk(KERN_ERR "es1688: can't grab DMA8 %d\n", dma8);
+ goto exit;
+ }
+ chip->dma8 = dma8;
+
+ spin_lock_init(&chip->reg_lock);
+ spin_lock_init(&chip->mixer_lock);
+ chip->port = port;
+ mpu_port &= ~0x000f;
+ if (mpu_port < 0x300 || mpu_port > 0x330)
+ mpu_port = 0;
+ chip->mpu_port = mpu_port;
+ chip->mpu_irq = mpu_irq;
+ chip->hardware = hardware;
+
+ err = snd_es1688_probe(chip);
+ if (err < 0)
+ goto exit;
+
+ err = snd_es1688_init(chip, 1);
+ if (err < 0)
+ goto exit;
+
+ /* Register device */
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+exit:
+ if (err)
+ snd_es1688_free(chip);
+ return err;
+}
+
+static const struct snd_pcm_ops snd_es1688_playback_ops = {
+ .open = snd_es1688_playback_open,
+ .close = snd_es1688_playback_close,
+ .prepare = snd_es1688_playback_prepare,
+ .trigger = snd_es1688_playback_trigger,
+ .pointer = snd_es1688_playback_pointer,
+};
+
+static const struct snd_pcm_ops snd_es1688_capture_ops = {
+ .open = snd_es1688_capture_open,
+ .close = snd_es1688_capture_close,
+ .prepare = snd_es1688_capture_prepare,
+ .trigger = snd_es1688_capture_trigger,
+ .pointer = snd_es1688_capture_pointer,
+};
+
+int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(card, "ESx688", device, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1688_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1688_capture_ops);
+
+ pcm->private_data = chip;
+ pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+ strcpy(pcm->name, snd_es1688_chip_id(chip));
+ chip->pcm = pcm;
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, card->dev,
+ 64*1024, 64*1024);
+ return 0;
+}
+
+/*
+ * MIXER part
+ */
+
+static int snd_es1688_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[8] = {
+ "Mic", "Mic Master", "CD", "AOUT",
+ "Mic1", "Mix", "Line", "Master"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, 8, texts);
+}
+
+static int snd_es1688_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.enumerated.item[0] = snd_es1688_mixer_read(chip, ES1688_REC_DEV) & 7;
+ return 0;
+}
+
+static int snd_es1688_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ unsigned char oval, nval;
+ int change;
+
+ if (ucontrol->value.enumerated.item[0] > 8)
+ return -EINVAL;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ oval = snd_es1688_mixer_read(chip, ES1688_REC_DEV);
+ nval = (ucontrol->value.enumerated.item[0] & 7) | (oval & ~15);
+ change = nval != oval;
+ if (change)
+ snd_es1688_mixer_write(chip, ES1688_REC_DEV, nval);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+#define ES1688_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_es1688_info_single, \
+ .get = snd_es1688_get_single, .put = snd_es1688_put_single, \
+ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_es1688_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_es1688_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (snd_es1688_mixer_read(chip, reg) >> shift) & mask;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (invert)
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int snd_es1688_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+ int change;
+ unsigned char oval, nval;
+
+ nval = (ucontrol->value.integer.value[0] & mask);
+ if (invert)
+ nval = mask - nval;
+ nval <<= shift;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ oval = snd_es1688_mixer_read(chip, reg);
+ nval = (oval & ~(mask << shift)) | nval;
+ change = nval != oval;
+ if (change)
+ snd_es1688_mixer_write(chip, reg, nval);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+#define ES1688_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_es1688_info_double, \
+ .get = snd_es1688_get_double, .put = snd_es1688_put_double, \
+ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_es1688_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_es1688_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+ unsigned char left, right;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (left_reg < 0xa0)
+ left = snd_es1688_mixer_read(chip, left_reg);
+ else
+ left = snd_es1688_read(chip, left_reg);
+ if (left_reg != right_reg) {
+ if (right_reg < 0xa0)
+ right = snd_es1688_mixer_read(chip, right_reg);
+ else
+ right = snd_es1688_read(chip, right_reg);
+ } else
+ right = left;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
+ ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
+ if (invert) {
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+ }
+ return 0;
+}
+
+static int snd_es1688_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+ int change;
+ unsigned char val1, val2, oval1, oval2;
+
+ val1 = ucontrol->value.integer.value[0] & mask;
+ val2 = ucontrol->value.integer.value[1] & mask;
+ if (invert) {
+ val1 = mask - val1;
+ val2 = mask - val2;
+ }
+ val1 <<= shift_left;
+ val2 <<= shift_right;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (left_reg != right_reg) {
+ if (left_reg < 0xa0)
+ oval1 = snd_es1688_mixer_read(chip, left_reg);
+ else
+ oval1 = snd_es1688_read(chip, left_reg);
+ if (right_reg < 0xa0)
+ oval2 = snd_es1688_mixer_read(chip, right_reg);
+ else
+ oval2 = snd_es1688_read(chip, right_reg);
+ val1 = (oval1 & ~(mask << shift_left)) | val1;
+ val2 = (oval2 & ~(mask << shift_right)) | val2;
+ change = val1 != oval1 || val2 != oval2;
+ if (change) {
+ if (left_reg < 0xa0)
+ snd_es1688_mixer_write(chip, left_reg, val1);
+ else
+ snd_es1688_write(chip, left_reg, val1);
+ if (right_reg < 0xa0)
+ snd_es1688_mixer_write(chip, right_reg, val1);
+ else
+ snd_es1688_write(chip, right_reg, val1);
+ }
+ } else {
+ if (left_reg < 0xa0)
+ oval1 = snd_es1688_mixer_read(chip, left_reg);
+ else
+ oval1 = snd_es1688_read(chip, left_reg);
+ val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+ change = val1 != oval1;
+ if (change) {
+ if (left_reg < 0xa0)
+ snd_es1688_mixer_write(chip, left_reg, val1);
+ else
+ snd_es1688_write(chip, left_reg, val1);
+ }
+
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_es1688_controls[] = {
+ES1688_DOUBLE("Master Playback Volume", 0, ES1688_MASTER_DEV, ES1688_MASTER_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("PCM Playback Volume", 0, ES1688_PCM_DEV, ES1688_PCM_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("Line Playback Volume", 0, ES1688_LINE_DEV, ES1688_LINE_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("CD Playback Volume", 0, ES1688_CD_DEV, ES1688_CD_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("FM Playback Volume", 0, ES1688_FM_DEV, ES1688_FM_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("Mic Playback Volume", 0, ES1688_MIC_DEV, ES1688_MIC_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("Aux Playback Volume", 0, ES1688_AUX_DEV, ES1688_AUX_DEV, 4, 0, 15, 0),
+ES1688_SINGLE("Beep Playback Volume", 0, ES1688_SPEAKER_DEV, 0, 7, 0),
+ES1688_DOUBLE("Capture Volume", 0, ES1688_RECLEV_DEV, ES1688_RECLEV_DEV, 4, 0, 15, 0),
+ES1688_SINGLE("Capture Switch", 0, ES1688_REC_DEV, 4, 1, 1),
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = snd_es1688_info_mux,
+ .get = snd_es1688_get_mux,
+ .put = snd_es1688_put_mux,
+},
+};
+
+#define ES1688_INIT_TABLE_SIZE (sizeof(snd_es1688_init_table)/2)
+
+static const unsigned char snd_es1688_init_table[][2] = {
+ { ES1688_MASTER_DEV, 0 },
+ { ES1688_PCM_DEV, 0 },
+ { ES1688_LINE_DEV, 0 },
+ { ES1688_CD_DEV, 0 },
+ { ES1688_FM_DEV, 0 },
+ { ES1688_MIC_DEV, 0 },
+ { ES1688_AUX_DEV, 0 },
+ { ES1688_SPEAKER_DEV, 0 },
+ { ES1688_RECLEV_DEV, 0 },
+ { ES1688_REC_DEV, 0x17 }
+};
+
+int snd_es1688_mixer(struct snd_card *card, struct snd_es1688 *chip)
+{
+ unsigned int idx;
+ int err;
+ unsigned char reg, val;
+
+ if (snd_BUG_ON(!chip || !card))
+ return -EINVAL;
+
+ strcpy(card->mixername, snd_es1688_chip_id(chip));
+
+ for (idx = 0; idx < ARRAY_SIZE(snd_es1688_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_es1688_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ for (idx = 0; idx < ES1688_INIT_TABLE_SIZE; idx++) {
+ reg = snd_es1688_init_table[idx][0];
+ val = snd_es1688_init_table[idx][1];
+ if (reg < 0xa0)
+ snd_es1688_mixer_write(chip, reg, val);
+ else
+ snd_es1688_write(chip, reg, val);
+ }
+ return 0;
+}
+
+EXPORT_SYMBOL(snd_es1688_mixer_write);
+EXPORT_SYMBOL(snd_es1688_create);
+EXPORT_SYMBOL(snd_es1688_pcm);
+EXPORT_SYMBOL(snd_es1688_mixer);
diff --git a/sound/isa/es18xx.c b/sound/isa/es18xx.c
new file mode 100644
index 0000000000..0a32845b10
--- /dev/null
+++ b/sound/isa/es18xx.c
@@ -0,0 +1,2342 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for generic ESS AudioDrive ES18xx soundcards
+ * Copyright (c) by Christian Fischbach <fishbach@pool.informatik.rwth-aachen.de>
+ * Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ */
+/* GENERAL NOTES:
+ *
+ * BUGS:
+ * - There are pops (we can't delay in trigger function, cause midlevel
+ * often need to trigger down and then up very quickly).
+ * Any ideas?
+ * - Support for 16 bit DMA seems to be broken. I've no hardware to tune it.
+ */
+
+/*
+ * ES1868 NOTES:
+ * - The chip has one half duplex pcm (with very limited full duplex support).
+ *
+ * - Duplex stereophonic sound is impossible.
+ * - Record and playback must share the same frequency rate.
+ *
+ * - The driver use dma2 for playback and dma1 for capture.
+ */
+
+/*
+ * ES1869 NOTES:
+ *
+ * - there are a first full duplex pcm and a second playback only pcm
+ * (incompatible with first pcm capture)
+ *
+ * - there is support for the capture volume and ESS Spatializer 3D effect.
+ *
+ * - contrarily to some pages in DS_1869.PDF the rates can be set
+ * independently.
+ *
+ * - Zoom Video is implemented by sharing the FM DAC, thus the user can
+ * have either FM playback or Video playback but not both simultaneously.
+ * The Video Playback Switch mixer control toggles this choice.
+ *
+ * BUGS:
+ *
+ * - There is a major trouble I noted:
+ *
+ * using both channel for playback stereo 16 bit samples at 44100 Hz
+ * the second pcm (Audio1) DMA slows down irregularly and sound is garbled.
+ *
+ * The same happens using Audio1 for captureing.
+ *
+ * The Windows driver does not suffer of this (although it use Audio1
+ * only for captureing). I'm unable to discover why.
+ *
+ */
+
+/*
+ * ES1879 NOTES:
+ * - When Zoom Video is enabled (reg 0x71 bit 6 toggled on) the PCM playback
+ * seems to be effected (speaker_test plays a lower frequency). Can't find
+ * anything in the datasheet to account for this, so a Video Playback Switch
+ * control has been included to allow ZV to be enabled only when necessary.
+ * Then again on at least one test system the 0x71 bit 6 enable bit is not
+ * needed for ZV, so maybe the datasheet is entirely wrong here.
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/isapnp.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define PFX "es18xx: "
+
+struct snd_es18xx {
+ unsigned long port; /* port of ESS chip */
+ unsigned long ctrl_port; /* Control port of ESS chip */
+ int irq; /* IRQ number of ESS chip */
+ int dma1; /* DMA1 */
+ int dma2; /* DMA2 */
+ unsigned short version; /* version of ESS chip */
+ int caps; /* Chip capabilities */
+ unsigned short audio2_vol; /* volume level of audio2 */
+
+ unsigned short active; /* active channel mask */
+ unsigned int dma1_shift;
+ unsigned int dma2_shift;
+
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *playback_a_substream;
+ struct snd_pcm_substream *capture_a_substream;
+ struct snd_pcm_substream *playback_b_substream;
+
+ struct snd_rawmidi *rmidi;
+
+ struct snd_kcontrol *hw_volume;
+ struct snd_kcontrol *hw_switch;
+ struct snd_kcontrol *master_volume;
+ struct snd_kcontrol *master_switch;
+
+ spinlock_t reg_lock;
+ spinlock_t mixer_lock;
+#ifdef CONFIG_PM
+ unsigned char pm_reg;
+#endif
+#ifdef CONFIG_PNP
+ struct pnp_dev *dev;
+ struct pnp_dev *devc;
+#endif
+};
+
+#define AUDIO1_IRQ 0x01
+#define AUDIO2_IRQ 0x02
+#define HWV_IRQ 0x04
+#define MPU_IRQ 0x08
+
+#define ES18XX_PCM2 0x0001 /* Has two useable PCM */
+#define ES18XX_SPATIALIZER 0x0002 /* Has 3D Spatializer */
+#define ES18XX_RECMIX 0x0004 /* Has record mixer */
+#define ES18XX_DUPLEX_MONO 0x0008 /* Has mono duplex only */
+#define ES18XX_DUPLEX_SAME 0x0010 /* Playback and record must share the same rate */
+#define ES18XX_NEW_RATE 0x0020 /* More precise rate setting */
+#define ES18XX_AUXB 0x0040 /* AuxB mixer control */
+#define ES18XX_HWV 0x0080 /* Has separate hardware volume mixer controls*/
+#define ES18XX_MONO 0x0100 /* Mono_in mixer control */
+#define ES18XX_I2S 0x0200 /* I2S mixer control */
+#define ES18XX_MUTEREC 0x0400 /* Record source can be muted */
+#define ES18XX_CONTROL 0x0800 /* Has control ports */
+#define ES18XX_GPO_2BIT 0x1000 /* GPO0,1 controlled by PM port */
+
+/* Power Management */
+#define ES18XX_PM 0x07
+#define ES18XX_PM_GPO0 0x01
+#define ES18XX_PM_GPO1 0x02
+#define ES18XX_PM_PDR 0x04
+#define ES18XX_PM_ANA 0x08
+#define ES18XX_PM_FM 0x020
+#define ES18XX_PM_SUS 0x080
+
+/* Lowlevel */
+
+#define DAC1 0x01
+#define ADC1 0x02
+#define DAC2 0x04
+#define MILLISECOND 10000
+
+static int snd_es18xx_dsp_command(struct snd_es18xx *chip, unsigned char val)
+{
+ int i;
+
+ for(i = MILLISECOND; i; i--)
+ if ((inb(chip->port + 0x0C) & 0x80) == 0) {
+ outb(val, chip->port + 0x0C);
+ return 0;
+ }
+ snd_printk(KERN_ERR "dsp_command: timeout (0x%x)\n", val);
+ return -EINVAL;
+}
+
+static int snd_es18xx_dsp_get_byte(struct snd_es18xx *chip)
+{
+ int i;
+
+ for(i = MILLISECOND/10; i; i--)
+ if (inb(chip->port + 0x0C) & 0x40)
+ return inb(chip->port + 0x0A);
+ snd_printk(KERN_ERR "dsp_get_byte failed: 0x%lx = 0x%x!!!\n",
+ chip->port + 0x0A, inb(chip->port + 0x0A));
+ return -ENODEV;
+}
+
+#undef REG_DEBUG
+
+static int snd_es18xx_write(struct snd_es18xx *chip,
+ unsigned char reg, unsigned char data)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ret = snd_es18xx_dsp_command(chip, reg);
+ if (ret < 0)
+ goto end;
+ ret = snd_es18xx_dsp_command(chip, data);
+ end:
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+#ifdef REG_DEBUG
+ snd_printk(KERN_DEBUG "Reg %02x set to %02x\n", reg, data);
+#endif
+ return ret;
+}
+
+static int snd_es18xx_read(struct snd_es18xx *chip, unsigned char reg)
+{
+ unsigned long flags;
+ int ret, data;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ret = snd_es18xx_dsp_command(chip, 0xC0);
+ if (ret < 0)
+ goto end;
+ ret = snd_es18xx_dsp_command(chip, reg);
+ if (ret < 0)
+ goto end;
+ data = snd_es18xx_dsp_get_byte(chip);
+ ret = data;
+#ifdef REG_DEBUG
+ snd_printk(KERN_DEBUG "Reg %02x now is %02x (%d)\n", reg, data, ret);
+#endif
+ end:
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return ret;
+}
+
+/* Return old value */
+static int snd_es18xx_bits(struct snd_es18xx *chip, unsigned char reg,
+ unsigned char mask, unsigned char val)
+{
+ int ret;
+ unsigned char old, new, oval;
+ unsigned long flags;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ret = snd_es18xx_dsp_command(chip, 0xC0);
+ if (ret < 0)
+ goto end;
+ ret = snd_es18xx_dsp_command(chip, reg);
+ if (ret < 0)
+ goto end;
+ ret = snd_es18xx_dsp_get_byte(chip);
+ if (ret < 0) {
+ goto end;
+ }
+ old = ret;
+ oval = old & mask;
+ if (val != oval) {
+ ret = snd_es18xx_dsp_command(chip, reg);
+ if (ret < 0)
+ goto end;
+ new = (old & ~mask) | (val & mask);
+ ret = snd_es18xx_dsp_command(chip, new);
+ if (ret < 0)
+ goto end;
+#ifdef REG_DEBUG
+ snd_printk(KERN_DEBUG "Reg %02x was %02x, set to %02x (%d)\n",
+ reg, old, new, ret);
+#endif
+ }
+ ret = oval;
+ end:
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return ret;
+}
+
+static inline void snd_es18xx_mixer_write(struct snd_es18xx *chip,
+ unsigned char reg, unsigned char data)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ outb(reg, chip->port + 0x04);
+ outb(data, chip->port + 0x05);
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+ snd_printk(KERN_DEBUG "Mixer reg %02x set to %02x\n", reg, data);
+#endif
+}
+
+static inline int snd_es18xx_mixer_read(struct snd_es18xx *chip, unsigned char reg)
+{
+ unsigned long flags;
+ int data;
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ outb(reg, chip->port + 0x04);
+ data = inb(chip->port + 0x05);
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+ snd_printk(KERN_DEBUG "Mixer reg %02x now is %02x\n", reg, data);
+#endif
+ return data;
+}
+
+/* Return old value */
+static inline int snd_es18xx_mixer_bits(struct snd_es18xx *chip, unsigned char reg,
+ unsigned char mask, unsigned char val)
+{
+ unsigned char old, new, oval;
+ unsigned long flags;
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ outb(reg, chip->port + 0x04);
+ old = inb(chip->port + 0x05);
+ oval = old & mask;
+ if (val != oval) {
+ new = (old & ~mask) | (val & mask);
+ outb(new, chip->port + 0x05);
+#ifdef REG_DEBUG
+ snd_printk(KERN_DEBUG "Mixer reg %02x was %02x, set to %02x\n",
+ reg, old, new);
+#endif
+ }
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+ return oval;
+}
+
+static inline int snd_es18xx_mixer_writable(struct snd_es18xx *chip, unsigned char reg,
+ unsigned char mask)
+{
+ int old, expected, new;
+ unsigned long flags;
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ outb(reg, chip->port + 0x04);
+ old = inb(chip->port + 0x05);
+ expected = old ^ mask;
+ outb(expected, chip->port + 0x05);
+ new = inb(chip->port + 0x05);
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+ snd_printk(KERN_DEBUG "Mixer reg %02x was %02x, set to %02x, now is %02x\n",
+ reg, old, expected, new);
+#endif
+ return expected == new;
+}
+
+
+static int snd_es18xx_reset(struct snd_es18xx *chip)
+{
+ int i;
+ outb(0x03, chip->port + 0x06);
+ inb(chip->port + 0x06);
+ outb(0x00, chip->port + 0x06);
+ for(i = 0; i < MILLISECOND && !(inb(chip->port + 0x0E) & 0x80); i++);
+ if (inb(chip->port + 0x0A) != 0xAA)
+ return -1;
+ return 0;
+}
+
+static int snd_es18xx_reset_fifo(struct snd_es18xx *chip)
+{
+ outb(0x02, chip->port + 0x06);
+ inb(chip->port + 0x06);
+ outb(0x00, chip->port + 0x06);
+ return 0;
+}
+
+static const struct snd_ratnum new_clocks[2] = {
+ {
+ .num = 793800,
+ .den_min = 1,
+ .den_max = 128,
+ .den_step = 1,
+ },
+ {
+ .num = 768000,
+ .den_min = 1,
+ .den_max = 128,
+ .den_step = 1,
+ }
+};
+
+static const struct snd_pcm_hw_constraint_ratnums new_hw_constraints_clocks = {
+ .nrats = 2,
+ .rats = new_clocks,
+};
+
+static const struct snd_ratnum old_clocks[2] = {
+ {
+ .num = 795444,
+ .den_min = 1,
+ .den_max = 128,
+ .den_step = 1,
+ },
+ {
+ .num = 397722,
+ .den_min = 1,
+ .den_max = 128,
+ .den_step = 1,
+ }
+};
+
+static const struct snd_pcm_hw_constraint_ratnums old_hw_constraints_clocks = {
+ .nrats = 2,
+ .rats = old_clocks,
+};
+
+
+static void snd_es18xx_rate_set(struct snd_es18xx *chip,
+ struct snd_pcm_substream *substream,
+ int mode)
+{
+ unsigned int bits, div0;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ if (chip->caps & ES18XX_NEW_RATE) {
+ if (runtime->rate_num == new_clocks[0].num)
+ bits = 128 - runtime->rate_den;
+ else
+ bits = 256 - runtime->rate_den;
+ } else {
+ if (runtime->rate_num == old_clocks[0].num)
+ bits = 256 - runtime->rate_den;
+ else
+ bits = 128 - runtime->rate_den;
+ }
+
+ /* set filter register */
+ div0 = 256 - 7160000*20/(8*82*runtime->rate);
+
+ if ((chip->caps & ES18XX_PCM2) && mode == DAC2) {
+ snd_es18xx_mixer_write(chip, 0x70, bits);
+ /*
+ * Comment from kernel oss driver:
+ * FKS: fascinating: 0x72 doesn't seem to work.
+ */
+ snd_es18xx_write(chip, 0xA2, div0);
+ snd_es18xx_mixer_write(chip, 0x72, div0);
+ } else {
+ snd_es18xx_write(chip, 0xA1, bits);
+ snd_es18xx_write(chip, 0xA2, div0);
+ }
+}
+
+static int snd_es18xx_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+ int shift;
+
+ shift = 0;
+ if (params_channels(hw_params) == 2)
+ shift++;
+ if (snd_pcm_format_width(params_format(hw_params)) == 16)
+ shift++;
+
+ if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+ if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+ (chip->capture_a_substream) &&
+ params_channels(hw_params) != 1) {
+ _snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ return -EBUSY;
+ }
+ chip->dma2_shift = shift;
+ } else {
+ chip->dma1_shift = shift;
+ }
+ return 0;
+}
+
+static int snd_es18xx_playback1_prepare(struct snd_es18xx *chip,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+ snd_es18xx_rate_set(chip, substream, DAC2);
+
+ /* Transfer Count Reload */
+ count = 0x10000 - count;
+ snd_es18xx_mixer_write(chip, 0x74, count & 0xff);
+ snd_es18xx_mixer_write(chip, 0x76, count >> 8);
+
+ /* Set format */
+ snd_es18xx_mixer_bits(chip, 0x7A, 0x07,
+ ((runtime->channels == 1) ? 0x00 : 0x02) |
+ (snd_pcm_format_width(runtime->format) == 16 ? 0x01 : 0x00) |
+ (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x04));
+
+ /* Set DMA controller */
+ snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+ return 0;
+}
+
+static int snd_es18xx_playback1_trigger(struct snd_es18xx *chip,
+ struct snd_pcm_substream *substream,
+ int cmd)
+{
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (chip->active & DAC2)
+ return 0;
+ chip->active |= DAC2;
+ /* Start DMA */
+ if (chip->dma2 >= 4)
+ snd_es18xx_mixer_write(chip, 0x78, 0xb3);
+ else
+ snd_es18xx_mixer_write(chip, 0x78, 0x93);
+#ifdef AVOID_POPS
+ /* Avoid pops */
+ mdelay(100);
+ if (chip->caps & ES18XX_PCM2)
+ /* Restore Audio 2 volume */
+ snd_es18xx_mixer_write(chip, 0x7C, chip->audio2_vol);
+ else
+ /* Enable PCM output */
+ snd_es18xx_dsp_command(chip, 0xD1);
+#endif
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (!(chip->active & DAC2))
+ return 0;
+ chip->active &= ~DAC2;
+ /* Stop DMA */
+ snd_es18xx_mixer_write(chip, 0x78, 0x00);
+#ifdef AVOID_POPS
+ mdelay(25);
+ if (chip->caps & ES18XX_PCM2)
+ /* Set Audio 2 volume to 0 */
+ snd_es18xx_mixer_write(chip, 0x7C, 0);
+ else
+ /* Disable PCM output */
+ snd_es18xx_dsp_command(chip, 0xD3);
+#endif
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int snd_es18xx_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+ int shift;
+
+ shift = 0;
+ if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+ chip->playback_a_substream &&
+ params_channels(hw_params) != 1) {
+ _snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ return -EBUSY;
+ }
+ if (params_channels(hw_params) == 2)
+ shift++;
+ if (snd_pcm_format_width(params_format(hw_params)) == 16)
+ shift++;
+ chip->dma1_shift = shift;
+ return 0;
+}
+
+static int snd_es18xx_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+ snd_es18xx_reset_fifo(chip);
+
+ /* Set stereo/mono */
+ snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01);
+
+ snd_es18xx_rate_set(chip, substream, ADC1);
+
+ /* Transfer Count Reload */
+ count = 0x10000 - count;
+ snd_es18xx_write(chip, 0xA4, count & 0xff);
+ snd_es18xx_write(chip, 0xA5, count >> 8);
+
+#ifdef AVOID_POPS
+ mdelay(100);
+#endif
+
+ /* Set format */
+ snd_es18xx_write(chip, 0xB7,
+ snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71);
+ snd_es18xx_write(chip, 0xB7, 0x90 |
+ ((runtime->channels == 1) ? 0x40 : 0x08) |
+ (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) |
+ (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20));
+
+ /* Set DMA controller */
+ snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+
+ return 0;
+}
+
+static int snd_es18xx_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (chip->active & ADC1)
+ return 0;
+ chip->active |= ADC1;
+ /* Start DMA */
+ snd_es18xx_write(chip, 0xB8, 0x0f);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (!(chip->active & ADC1))
+ return 0;
+ chip->active &= ~ADC1;
+ /* Stop DMA */
+ snd_es18xx_write(chip, 0xB8, 0x00);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int snd_es18xx_playback2_prepare(struct snd_es18xx *chip,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+ snd_es18xx_reset_fifo(chip);
+
+ /* Set stereo/mono */
+ snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01);
+
+ snd_es18xx_rate_set(chip, substream, DAC1);
+
+ /* Transfer Count Reload */
+ count = 0x10000 - count;
+ snd_es18xx_write(chip, 0xA4, count & 0xff);
+ snd_es18xx_write(chip, 0xA5, count >> 8);
+
+ /* Set format */
+ snd_es18xx_write(chip, 0xB6,
+ snd_pcm_format_unsigned(runtime->format) ? 0x80 : 0x00);
+ snd_es18xx_write(chip, 0xB7,
+ snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71);
+ snd_es18xx_write(chip, 0xB7, 0x90 |
+ (runtime->channels == 1 ? 0x40 : 0x08) |
+ (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) |
+ (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20));
+
+ /* Set DMA controller */
+ snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+ return 0;
+}
+
+static int snd_es18xx_playback2_trigger(struct snd_es18xx *chip,
+ struct snd_pcm_substream *substream,
+ int cmd)
+{
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (chip->active & DAC1)
+ return 0;
+ chip->active |= DAC1;
+ /* Start DMA */
+ snd_es18xx_write(chip, 0xB8, 0x05);
+#ifdef AVOID_POPS
+ /* Avoid pops */
+ mdelay(100);
+ /* Enable Audio 1 */
+ snd_es18xx_dsp_command(chip, 0xD1);
+#endif
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (!(chip->active & DAC1))
+ return 0;
+ chip->active &= ~DAC1;
+ /* Stop DMA */
+ snd_es18xx_write(chip, 0xB8, 0x00);
+#ifdef AVOID_POPS
+ /* Avoid pops */
+ mdelay(25);
+ /* Disable Audio 1 */
+ snd_es18xx_dsp_command(chip, 0xD3);
+#endif
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int snd_es18xx_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+ if (substream->number == 0 && (chip->caps & ES18XX_PCM2))
+ return snd_es18xx_playback1_prepare(chip, substream);
+ else
+ return snd_es18xx_playback2_prepare(chip, substream);
+}
+
+static int snd_es18xx_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+ if (substream->number == 0 && (chip->caps & ES18XX_PCM2))
+ return snd_es18xx_playback1_trigger(chip, substream, cmd);
+ else
+ return snd_es18xx_playback2_trigger(chip, substream, cmd);
+}
+
+static irqreturn_t snd_es18xx_interrupt(int irq, void *dev_id)
+{
+ struct snd_card *card = dev_id;
+ struct snd_es18xx *chip = card->private_data;
+ unsigned char status;
+
+ if (chip->caps & ES18XX_CONTROL) {
+ /* Read Interrupt status */
+ status = inb(chip->ctrl_port + 6);
+ } else {
+ /* Read Interrupt status */
+ status = snd_es18xx_mixer_read(chip, 0x7f) >> 4;
+ }
+#if 0
+ else {
+ status = 0;
+ if (inb(chip->port + 0x0C) & 0x01)
+ status |= AUDIO1_IRQ;
+ if (snd_es18xx_mixer_read(chip, 0x7A) & 0x80)
+ status |= AUDIO2_IRQ;
+ if ((chip->caps & ES18XX_HWV) &&
+ snd_es18xx_mixer_read(chip, 0x64) & 0x10)
+ status |= HWV_IRQ;
+ }
+#endif
+
+ /* Audio 1 & Audio 2 */
+ if (status & AUDIO2_IRQ) {
+ if (chip->active & DAC2)
+ snd_pcm_period_elapsed(chip->playback_a_substream);
+ /* ack interrupt */
+ snd_es18xx_mixer_bits(chip, 0x7A, 0x80, 0x00);
+ }
+ if (status & AUDIO1_IRQ) {
+ /* ok.. capture is active */
+ if (chip->active & ADC1)
+ snd_pcm_period_elapsed(chip->capture_a_substream);
+ /* ok.. playback2 is active */
+ else if (chip->active & DAC1)
+ snd_pcm_period_elapsed(chip->playback_b_substream);
+ /* ack interrupt */
+ inb(chip->port + 0x0E);
+ }
+
+ /* MPU */
+ if ((status & MPU_IRQ) && chip->rmidi)
+ snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+
+ /* Hardware volume */
+ if (status & HWV_IRQ) {
+ int split = 0;
+ if (chip->caps & ES18XX_HWV) {
+ split = snd_es18xx_mixer_read(chip, 0x64) & 0x80;
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &chip->hw_switch->id);
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &chip->hw_volume->id);
+ }
+ if (!split) {
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &chip->master_switch->id);
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &chip->master_volume->id);
+ }
+ /* ack interrupt */
+ snd_es18xx_mixer_write(chip, 0x66, 0x00);
+ }
+ return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_es18xx_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ int pos;
+
+ if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+ if (!(chip->active & DAC2))
+ return 0;
+ pos = snd_dma_pointer(chip->dma2, size);
+ return pos >> chip->dma2_shift;
+ } else {
+ if (!(chip->active & DAC1))
+ return 0;
+ pos = snd_dma_pointer(chip->dma1, size);
+ return pos >> chip->dma1_shift;
+ }
+}
+
+static snd_pcm_uframes_t snd_es18xx_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ int pos;
+
+ if (!(chip->active & ADC1))
+ return 0;
+ pos = snd_dma_pointer(chip->dma1, size);
+ return pos >> chip->dma1_shift;
+}
+
+static const struct snd_pcm_hardware snd_es18xx_playback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = 65536,
+ .period_bytes_min = 64,
+ .period_bytes_max = 65536,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_es18xx_capture =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = 65536,
+ .period_bytes_min = 64,
+ .period_bytes_max = 65536,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static int snd_es18xx_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+ if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+ if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+ chip->capture_a_substream &&
+ chip->capture_a_substream->runtime->channels != 1)
+ return -EAGAIN;
+ chip->playback_a_substream = substream;
+ } else if (substream->number <= 1) {
+ if (chip->capture_a_substream)
+ return -EAGAIN;
+ chip->playback_b_substream = substream;
+ } else {
+ snd_BUG();
+ return -EINVAL;
+ }
+ substream->runtime->hw = snd_es18xx_playback;
+ snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks);
+ return 0;
+}
+
+static int snd_es18xx_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+ if (chip->playback_b_substream)
+ return -EAGAIN;
+ if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+ chip->playback_a_substream &&
+ chip->playback_a_substream->runtime->channels != 1)
+ return -EAGAIN;
+ chip->capture_a_substream = substream;
+ substream->runtime->hw = snd_es18xx_capture;
+ snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks);
+ return 0;
+}
+
+static int snd_es18xx_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+ if (substream->number == 0 && (chip->caps & ES18XX_PCM2))
+ chip->playback_a_substream = NULL;
+ else
+ chip->playback_b_substream = NULL;
+
+ return 0;
+}
+
+static int snd_es18xx_capture_close(struct snd_pcm_substream *substream)
+{
+ struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+ chip->capture_a_substream = NULL;
+ return 0;
+}
+
+/*
+ * MIXER part
+ */
+
+/* Record source mux routines:
+ * Depending on the chipset this mux switches between 4, 5, or 8 possible inputs.
+ * bit table for the 4/5 source mux:
+ * reg 1C:
+ * b2 b1 b0 muxSource
+ * x 0 x microphone
+ * 0 1 x CD
+ * 1 1 0 line
+ * 1 1 1 mixer
+ * if it's "mixer" and it's a 5 source mux chipset then reg 7A bit 3 determines
+ * either the play mixer or the capture mixer.
+ *
+ * "map4Source" translates from source number to reg bit pattern
+ * "invMap4Source" translates from reg bit pattern to source number
+ */
+
+static int snd_es18xx_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts5Source[5] = {
+ "Mic", "CD", "Line", "Master", "Mix"
+ };
+ static const char * const texts8Source[8] = {
+ "Mic", "Mic Master", "CD", "AOUT",
+ "Mic1", "Mix", "Line", "Master"
+ };
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+
+ switch (chip->version) {
+ case 0x1868:
+ case 0x1878:
+ return snd_ctl_enum_info(uinfo, 1, 4, texts5Source);
+ case 0x1887:
+ case 0x1888:
+ return snd_ctl_enum_info(uinfo, 1, 5, texts5Source);
+ case 0x1869: /* DS somewhat contradictory for 1869: could be 5 or 8 */
+ case 0x1879:
+ return snd_ctl_enum_info(uinfo, 1, 8, texts8Source);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int snd_es18xx_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ static const unsigned char invMap4Source[8] = {0, 0, 1, 1, 0, 0, 2, 3};
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ int muxSource = snd_es18xx_mixer_read(chip, 0x1c) & 0x07;
+ if (!(chip->version == 0x1869 || chip->version == 0x1879)) {
+ muxSource = invMap4Source[muxSource];
+ if (muxSource==3 &&
+ (chip->version == 0x1887 || chip->version == 0x1888) &&
+ (snd_es18xx_mixer_read(chip, 0x7a) & 0x08)
+ )
+ muxSource = 4;
+ }
+ ucontrol->value.enumerated.item[0] = muxSource;
+ return 0;
+}
+
+static int snd_es18xx_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ static const unsigned char map4Source[4] = {0, 2, 6, 7};
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ unsigned char val = ucontrol->value.enumerated.item[0];
+ unsigned char retVal = 0;
+
+ switch (chip->version) {
+ /* 5 source chips */
+ case 0x1887:
+ case 0x1888:
+ if (val > 4)
+ return -EINVAL;
+ if (val == 4) {
+ retVal = snd_es18xx_mixer_bits(chip, 0x7a, 0x08, 0x08) != 0x08;
+ val = 3;
+ } else
+ retVal = snd_es18xx_mixer_bits(chip, 0x7a, 0x08, 0x00) != 0x00;
+ fallthrough;
+ /* 4 source chips */
+ case 0x1868:
+ case 0x1878:
+ if (val > 3)
+ return -EINVAL;
+ val = map4Source[val];
+ break;
+ /* 8 source chips */
+ case 0x1869:
+ case 0x1879:
+ if (val > 7)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return (snd_es18xx_mixer_bits(chip, 0x1c, 0x07, val) != val) || retVal;
+}
+
+#define snd_es18xx_info_spatializer_enable snd_ctl_boolean_mono_info
+
+static int snd_es18xx_get_spatializer_enable(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ unsigned char val = snd_es18xx_mixer_read(chip, 0x50);
+ ucontrol->value.integer.value[0] = !!(val & 8);
+ return 0;
+}
+
+static int snd_es18xx_put_spatializer_enable(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ unsigned char oval, nval;
+ int change;
+ nval = ucontrol->value.integer.value[0] ? 0x0c : 0x04;
+ oval = snd_es18xx_mixer_read(chip, 0x50) & 0x0c;
+ change = nval != oval;
+ if (change) {
+ snd_es18xx_mixer_write(chip, 0x50, nval & ~0x04);
+ snd_es18xx_mixer_write(chip, 0x50, nval);
+ }
+ return change;
+}
+
+static int snd_es18xx_info_hw_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 63;
+ return 0;
+}
+
+static int snd_es18xx_get_hw_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = snd_es18xx_mixer_read(chip, 0x61) & 0x3f;
+ ucontrol->value.integer.value[1] = snd_es18xx_mixer_read(chip, 0x63) & 0x3f;
+ return 0;
+}
+
+#define snd_es18xx_info_hw_switch snd_ctl_boolean_stereo_info
+
+static int snd_es18xx_get_hw_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = !(snd_es18xx_mixer_read(chip, 0x61) & 0x40);
+ ucontrol->value.integer.value[1] = !(snd_es18xx_mixer_read(chip, 0x63) & 0x40);
+ return 0;
+}
+
+static void snd_es18xx_hwv_free(struct snd_kcontrol *kcontrol)
+{
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ chip->master_volume = NULL;
+ chip->master_switch = NULL;
+ chip->hw_volume = NULL;
+ chip->hw_switch = NULL;
+}
+
+static int snd_es18xx_reg_bits(struct snd_es18xx *chip, unsigned char reg,
+ unsigned char mask, unsigned char val)
+{
+ if (reg < 0xa0)
+ return snd_es18xx_mixer_bits(chip, reg, mask, val);
+ else
+ return snd_es18xx_bits(chip, reg, mask, val);
+}
+
+static int snd_es18xx_reg_read(struct snd_es18xx *chip, unsigned char reg)
+{
+ if (reg < 0xa0)
+ return snd_es18xx_mixer_read(chip, reg);
+ else
+ return snd_es18xx_read(chip, reg);
+}
+
+#define ES18XX_SINGLE(xname, xindex, reg, shift, mask, flags) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_es18xx_info_single, \
+ .get = snd_es18xx_get_single, .put = snd_es18xx_put_single, \
+ .private_value = reg | (shift << 8) | (mask << 16) | (flags << 24) }
+
+#define ES18XX_FL_INVERT (1 << 0)
+#define ES18XX_FL_PMPORT (1 << 1)
+
+static int snd_es18xx_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_es18xx_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & ES18XX_FL_INVERT;
+ int pm_port = (kcontrol->private_value >> 24) & ES18XX_FL_PMPORT;
+ int val;
+
+ if (pm_port)
+ val = inb(chip->port + ES18XX_PM);
+ else
+ val = snd_es18xx_reg_read(chip, reg);
+ ucontrol->value.integer.value[0] = (val >> shift) & mask;
+ if (invert)
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int snd_es18xx_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & ES18XX_FL_INVERT;
+ int pm_port = (kcontrol->private_value >> 24) & ES18XX_FL_PMPORT;
+ unsigned char val;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+ if (invert)
+ val = mask - val;
+ mask <<= shift;
+ val <<= shift;
+ if (pm_port) {
+ unsigned char cur = inb(chip->port + ES18XX_PM);
+
+ if ((cur & mask) == val)
+ return 0;
+ outb((cur & ~mask) | val, chip->port + ES18XX_PM);
+ return 1;
+ }
+
+ return snd_es18xx_reg_bits(chip, reg, mask, val) != val;
+}
+
+#define ES18XX_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_es18xx_info_double, \
+ .get = snd_es18xx_get_double, .put = snd_es18xx_put_double, \
+ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_es18xx_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_es18xx_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+ unsigned char left, right;
+
+ left = snd_es18xx_reg_read(chip, left_reg);
+ if (left_reg != right_reg)
+ right = snd_es18xx_reg_read(chip, right_reg);
+ else
+ right = left;
+ ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
+ ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
+ if (invert) {
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+ }
+ return 0;
+}
+
+static int snd_es18xx_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+ int change;
+ unsigned char val1, val2, mask1, mask2;
+
+ val1 = ucontrol->value.integer.value[0] & mask;
+ val2 = ucontrol->value.integer.value[1] & mask;
+ if (invert) {
+ val1 = mask - val1;
+ val2 = mask - val2;
+ }
+ val1 <<= shift_left;
+ val2 <<= shift_right;
+ mask1 = mask << shift_left;
+ mask2 = mask << shift_right;
+ if (left_reg != right_reg) {
+ change = 0;
+ if (snd_es18xx_reg_bits(chip, left_reg, mask1, val1) != val1)
+ change = 1;
+ if (snd_es18xx_reg_bits(chip, right_reg, mask2, val2) != val2)
+ change = 1;
+ } else {
+ change = (snd_es18xx_reg_bits(chip, left_reg, mask1 | mask2,
+ val1 | val2) != (val1 | val2));
+ }
+ return change;
+}
+
+/* Mixer controls
+ * These arrays contain setup data for mixer controls.
+ *
+ * The controls that are universal to all chipsets are fully initialized
+ * here.
+ */
+static const struct snd_kcontrol_new snd_es18xx_base_controls[] = {
+ES18XX_DOUBLE("Master Playback Volume", 0, 0x60, 0x62, 0, 0, 63, 0),
+ES18XX_DOUBLE("Master Playback Switch", 0, 0x60, 0x62, 6, 6, 1, 1),
+ES18XX_DOUBLE("Line Playback Volume", 0, 0x3e, 0x3e, 4, 0, 15, 0),
+ES18XX_DOUBLE("CD Playback Volume", 0, 0x38, 0x38, 4, 0, 15, 0),
+ES18XX_DOUBLE("FM Playback Volume", 0, 0x36, 0x36, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mic Playback Volume", 0, 0x1a, 0x1a, 4, 0, 15, 0),
+ES18XX_DOUBLE("Aux Playback Volume", 0, 0x3a, 0x3a, 4, 0, 15, 0),
+ES18XX_SINGLE("Record Monitor", 0, 0xa8, 3, 1, 0),
+ES18XX_DOUBLE("Capture Volume", 0, 0xb4, 0xb4, 4, 0, 15, 0),
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = snd_es18xx_info_mux,
+ .get = snd_es18xx_get_mux,
+ .put = snd_es18xx_put_mux,
+}
+};
+
+static const struct snd_kcontrol_new snd_es18xx_recmix_controls[] = {
+ES18XX_DOUBLE("PCM Capture Volume", 0, 0x69, 0x69, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mic Capture Volume", 0, 0x68, 0x68, 4, 0, 15, 0),
+ES18XX_DOUBLE("Line Capture Volume", 0, 0x6e, 0x6e, 4, 0, 15, 0),
+ES18XX_DOUBLE("FM Capture Volume", 0, 0x6b, 0x6b, 4, 0, 15, 0),
+ES18XX_DOUBLE("CD Capture Volume", 0, 0x6a, 0x6a, 4, 0, 15, 0),
+ES18XX_DOUBLE("Aux Capture Volume", 0, 0x6c, 0x6c, 4, 0, 15, 0)
+};
+
+/*
+ * The chipset specific mixer controls
+ */
+static const struct snd_kcontrol_new snd_es18xx_opt_speaker =
+ ES18XX_SINGLE("Beep Playback Volume", 0, 0x3c, 0, 7, 0);
+
+static const struct snd_kcontrol_new snd_es18xx_opt_1869[] = {
+ES18XX_SINGLE("Capture Switch", 0, 0x1c, 4, 1, ES18XX_FL_INVERT),
+ES18XX_SINGLE("Video Playback Switch", 0, 0x7f, 0, 1, 0),
+ES18XX_DOUBLE("Mono Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mono Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0)
+};
+
+static const struct snd_kcontrol_new snd_es18xx_opt_1878 =
+ ES18XX_DOUBLE("Video Playback Volume", 0, 0x68, 0x68, 4, 0, 15, 0);
+
+static const struct snd_kcontrol_new snd_es18xx_opt_1879[] = {
+ES18XX_SINGLE("Video Playback Switch", 0, 0x71, 6, 1, 0),
+ES18XX_DOUBLE("Video Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0),
+ES18XX_DOUBLE("Video Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0)
+};
+
+static const struct snd_kcontrol_new snd_es18xx_pcm1_controls[] = {
+ES18XX_DOUBLE("PCM Playback Volume", 0, 0x14, 0x14, 4, 0, 15, 0),
+};
+
+static const struct snd_kcontrol_new snd_es18xx_pcm2_controls[] = {
+ES18XX_DOUBLE("PCM Playback Volume", 0, 0x7c, 0x7c, 4, 0, 15, 0),
+ES18XX_DOUBLE("PCM Playback Volume", 1, 0x14, 0x14, 4, 0, 15, 0)
+};
+
+static const struct snd_kcontrol_new snd_es18xx_spatializer_controls[] = {
+ES18XX_SINGLE("3D Control - Level", 0, 0x52, 0, 63, 0),
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "3D Control - Switch",
+ .info = snd_es18xx_info_spatializer_enable,
+ .get = snd_es18xx_get_spatializer_enable,
+ .put = snd_es18xx_put_spatializer_enable,
+}
+};
+
+static const struct snd_kcontrol_new snd_es18xx_micpre1_control =
+ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0xa9, 2, 1, 0);
+
+static const struct snd_kcontrol_new snd_es18xx_micpre2_control =
+ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0x7d, 3, 1, 0);
+
+static const struct snd_kcontrol_new snd_es18xx_hw_volume_controls[] = {
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Hardware Master Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = snd_es18xx_info_hw_volume,
+ .get = snd_es18xx_get_hw_volume,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Hardware Master Playback Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = snd_es18xx_info_hw_switch,
+ .get = snd_es18xx_get_hw_switch,
+},
+ES18XX_SINGLE("Hardware Master Volume Split", 0, 0x64, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new snd_es18xx_opt_gpo_2bit[] = {
+ES18XX_SINGLE("GPO0 Switch", 0, ES18XX_PM, 0, 1, ES18XX_FL_PMPORT),
+ES18XX_SINGLE("GPO1 Switch", 0, ES18XX_PM, 1, 1, ES18XX_FL_PMPORT),
+};
+
+static int snd_es18xx_config_read(struct snd_es18xx *chip, unsigned char reg)
+{
+ outb(reg, chip->ctrl_port);
+ return inb(chip->ctrl_port + 1);
+}
+
+static void snd_es18xx_config_write(struct snd_es18xx *chip,
+ unsigned char reg, unsigned char data)
+{
+ /* No need for spinlocks, this function is used only in
+ otherwise protected init code */
+ outb(reg, chip->ctrl_port);
+ outb(data, chip->ctrl_port + 1);
+#ifdef REG_DEBUG
+ snd_printk(KERN_DEBUG "Config reg %02x set to %02x\n", reg, data);
+#endif
+}
+
+static int snd_es18xx_initialize(struct snd_es18xx *chip,
+ unsigned long mpu_port,
+ unsigned long fm_port)
+{
+ int mask = 0;
+
+ /* enable extended mode */
+ snd_es18xx_dsp_command(chip, 0xC6);
+ /* Reset mixer registers */
+ snd_es18xx_mixer_write(chip, 0x00, 0x00);
+
+ /* Audio 1 DMA demand mode (4 bytes/request) */
+ snd_es18xx_write(chip, 0xB9, 2);
+ if (chip->caps & ES18XX_CONTROL) {
+ /* Hardware volume IRQ */
+ snd_es18xx_config_write(chip, 0x27, chip->irq);
+ if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) {
+ /* FM I/O */
+ snd_es18xx_config_write(chip, 0x62, fm_port >> 8);
+ snd_es18xx_config_write(chip, 0x63, fm_port & 0xff);
+ }
+ if (mpu_port > 0 && mpu_port != SNDRV_AUTO_PORT) {
+ /* MPU-401 I/O */
+ snd_es18xx_config_write(chip, 0x64, mpu_port >> 8);
+ snd_es18xx_config_write(chip, 0x65, mpu_port & 0xff);
+ /* MPU-401 IRQ */
+ snd_es18xx_config_write(chip, 0x28, chip->irq);
+ }
+ /* Audio1 IRQ */
+ snd_es18xx_config_write(chip, 0x70, chip->irq);
+ /* Audio2 IRQ */
+ snd_es18xx_config_write(chip, 0x72, chip->irq);
+ /* Audio1 DMA */
+ snd_es18xx_config_write(chip, 0x74, chip->dma1);
+ /* Audio2 DMA */
+ snd_es18xx_config_write(chip, 0x75, chip->dma2);
+
+ /* Enable Audio 1 IRQ */
+ snd_es18xx_write(chip, 0xB1, 0x50);
+ /* Enable Audio 2 IRQ */
+ snd_es18xx_mixer_write(chip, 0x7A, 0x40);
+ /* Enable Audio 1 DMA */
+ snd_es18xx_write(chip, 0xB2, 0x50);
+ /* Enable MPU and hardware volume interrupt */
+ snd_es18xx_mixer_write(chip, 0x64, 0x42);
+ /* Enable ESS wavetable input */
+ snd_es18xx_mixer_bits(chip, 0x48, 0x10, 0x10);
+ }
+ else {
+ int irqmask, dma1mask, dma2mask;
+ switch (chip->irq) {
+ case 2:
+ case 9:
+ irqmask = 0;
+ break;
+ case 5:
+ irqmask = 1;
+ break;
+ case 7:
+ irqmask = 2;
+ break;
+ case 10:
+ irqmask = 3;
+ break;
+ default:
+ snd_printk(KERN_ERR "invalid irq %d\n", chip->irq);
+ return -ENODEV;
+ }
+ switch (chip->dma1) {
+ case 0:
+ dma1mask = 1;
+ break;
+ case 1:
+ dma1mask = 2;
+ break;
+ case 3:
+ dma1mask = 3;
+ break;
+ default:
+ snd_printk(KERN_ERR "invalid dma1 %d\n", chip->dma1);
+ return -ENODEV;
+ }
+ switch (chip->dma2) {
+ case 0:
+ dma2mask = 0;
+ break;
+ case 1:
+ dma2mask = 1;
+ break;
+ case 3:
+ dma2mask = 2;
+ break;
+ case 5:
+ dma2mask = 3;
+ break;
+ default:
+ snd_printk(KERN_ERR "invalid dma2 %d\n", chip->dma2);
+ return -ENODEV;
+ }
+
+ /* Enable and set Audio 1 IRQ */
+ snd_es18xx_write(chip, 0xB1, 0x50 | (irqmask << 2));
+ /* Enable and set Audio 1 DMA */
+ snd_es18xx_write(chip, 0xB2, 0x50 | (dma1mask << 2));
+ /* Set Audio 2 DMA */
+ snd_es18xx_mixer_bits(chip, 0x7d, 0x07, 0x04 | dma2mask);
+ /* Enable Audio 2 IRQ and DMA
+ Set capture mixer input */
+ snd_es18xx_mixer_write(chip, 0x7A, 0x68);
+ /* Enable and set hardware volume interrupt */
+ snd_es18xx_mixer_write(chip, 0x64, 0x06);
+ if (mpu_port > 0 && mpu_port != SNDRV_AUTO_PORT) {
+ /* MPU401 share irq with audio
+ Joystick enabled
+ FM enabled */
+ snd_es18xx_mixer_write(chip, 0x40,
+ 0x43 | (mpu_port & 0xf0) >> 1);
+ }
+ snd_es18xx_mixer_write(chip, 0x7f, ((irqmask + 1) << 1) | 0x01);
+ }
+ if (chip->caps & ES18XX_NEW_RATE) {
+ /* Change behaviour of register A1
+ 4x oversampling
+ 2nd channel DAC asynchronous */
+ snd_es18xx_mixer_write(chip, 0x71, 0x32);
+ }
+ if (!(chip->caps & ES18XX_PCM2)) {
+ /* Enable DMA FIFO */
+ snd_es18xx_write(chip, 0xB7, 0x80);
+ }
+ if (chip->caps & ES18XX_SPATIALIZER) {
+ /* Set spatializer parameters to recommended values */
+ snd_es18xx_mixer_write(chip, 0x54, 0x8f);
+ snd_es18xx_mixer_write(chip, 0x56, 0x95);
+ snd_es18xx_mixer_write(chip, 0x58, 0x94);
+ snd_es18xx_mixer_write(chip, 0x5a, 0x80);
+ }
+ /* Flip the "enable I2S" bits for those chipsets that need it */
+ switch (chip->version) {
+ case 0x1879:
+ //Leaving I2S enabled on the 1879 screws up the PCM playback (rate effected somehow)
+ //so a Switch control has been added to toggle this 0x71 bit on/off:
+ //snd_es18xx_mixer_bits(chip, 0x71, 0x40, 0x40);
+ /* Note: we fall through on purpose here. */
+ case 0x1878:
+ snd_es18xx_config_write(chip, 0x29, snd_es18xx_config_read(chip, 0x29) | 0x40);
+ break;
+ }
+ /* Mute input source */
+ if (chip->caps & ES18XX_MUTEREC)
+ mask = 0x10;
+ if (chip->caps & ES18XX_RECMIX)
+ snd_es18xx_mixer_write(chip, 0x1c, 0x05 | mask);
+ else {
+ snd_es18xx_mixer_write(chip, 0x1c, 0x00 | mask);
+ snd_es18xx_write(chip, 0xb4, 0x00);
+ }
+#ifndef AVOID_POPS
+ /* Enable PCM output */
+ snd_es18xx_dsp_command(chip, 0xD1);
+#endif
+
+ return 0;
+}
+
+static int snd_es18xx_identify(struct snd_card *card, struct snd_es18xx *chip)
+{
+ int hi,lo;
+
+ /* reset */
+ if (snd_es18xx_reset(chip) < 0) {
+ snd_printk(KERN_ERR "reset at 0x%lx failed!!!\n", chip->port);
+ return -ENODEV;
+ }
+
+ snd_es18xx_dsp_command(chip, 0xe7);
+ hi = snd_es18xx_dsp_get_byte(chip);
+ if (hi < 0) {
+ return hi;
+ }
+ lo = snd_es18xx_dsp_get_byte(chip);
+ if ((lo & 0xf0) != 0x80) {
+ return -ENODEV;
+ }
+ if (hi == 0x48) {
+ chip->version = 0x488;
+ return 0;
+ }
+ if (hi != 0x68) {
+ return -ENODEV;
+ }
+ if ((lo & 0x0f) < 8) {
+ chip->version = 0x688;
+ return 0;
+ }
+
+ outb(0x40, chip->port + 0x04);
+ udelay(10);
+ hi = inb(chip->port + 0x05);
+ udelay(10);
+ lo = inb(chip->port + 0x05);
+ if (hi != lo) {
+ chip->version = hi << 8 | lo;
+ chip->ctrl_port = inb(chip->port + 0x05) << 8;
+ udelay(10);
+ chip->ctrl_port += inb(chip->port + 0x05);
+
+ if (!devm_request_region(card->dev, chip->ctrl_port, 8,
+ "ES18xx - CTRL")) {
+ snd_printk(KERN_ERR PFX "unable go grab port 0x%lx\n", chip->ctrl_port);
+ return -EBUSY;
+ }
+
+ return 0;
+ }
+
+ /* If has Hardware volume */
+ if (snd_es18xx_mixer_writable(chip, 0x64, 0x04)) {
+ /* If has Audio2 */
+ if (snd_es18xx_mixer_writable(chip, 0x70, 0x7f)) {
+ /* If has volume count */
+ if (snd_es18xx_mixer_writable(chip, 0x64, 0x20)) {
+ chip->version = 0x1887;
+ } else {
+ chip->version = 0x1888;
+ }
+ } else {
+ chip->version = 0x1788;
+ }
+ }
+ else
+ chip->version = 0x1688;
+ return 0;
+}
+
+static int snd_es18xx_probe(struct snd_card *card,
+ struct snd_es18xx *chip,
+ unsigned long mpu_port,
+ unsigned long fm_port)
+{
+ if (snd_es18xx_identify(card, chip) < 0) {
+ snd_printk(KERN_ERR PFX "[0x%lx] ESS chip not found\n", chip->port);
+ return -ENODEV;
+ }
+
+ switch (chip->version) {
+ case 0x1868:
+ chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_CONTROL | ES18XX_GPO_2BIT;
+ break;
+ case 0x1869:
+ chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_MONO | ES18XX_MUTEREC | ES18XX_CONTROL | ES18XX_HWV | ES18XX_GPO_2BIT;
+ break;
+ case 0x1878:
+ chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_I2S | ES18XX_CONTROL;
+ break;
+ case 0x1879:
+ chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_I2S | ES18XX_CONTROL | ES18XX_HWV;
+ break;
+ case 0x1887:
+ case 0x1888:
+ chip->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_DUPLEX_SAME | ES18XX_GPO_2BIT;
+ break;
+ default:
+ snd_printk(KERN_ERR "[0x%lx] unsupported chip ES%x\n",
+ chip->port, chip->version);
+ return -ENODEV;
+ }
+
+ snd_printd("[0x%lx] ESS%x chip found\n", chip->port, chip->version);
+
+ if (chip->dma1 == chip->dma2)
+ chip->caps &= ~(ES18XX_PCM2 | ES18XX_DUPLEX_SAME);
+
+ return snd_es18xx_initialize(chip, mpu_port, fm_port);
+}
+
+static const struct snd_pcm_ops snd_es18xx_playback_ops = {
+ .open = snd_es18xx_playback_open,
+ .close = snd_es18xx_playback_close,
+ .hw_params = snd_es18xx_playback_hw_params,
+ .prepare = snd_es18xx_playback_prepare,
+ .trigger = snd_es18xx_playback_trigger,
+ .pointer = snd_es18xx_playback_pointer,
+};
+
+static const struct snd_pcm_ops snd_es18xx_capture_ops = {
+ .open = snd_es18xx_capture_open,
+ .close = snd_es18xx_capture_close,
+ .hw_params = snd_es18xx_capture_hw_params,
+ .prepare = snd_es18xx_capture_prepare,
+ .trigger = snd_es18xx_capture_trigger,
+ .pointer = snd_es18xx_capture_pointer,
+};
+
+static int snd_es18xx_pcm(struct snd_card *card, int device)
+{
+ struct snd_es18xx *chip = card->private_data;
+ struct snd_pcm *pcm;
+ char str[16];
+ int err;
+
+ sprintf(str, "ES%x", chip->version);
+ if (chip->caps & ES18XX_PCM2)
+ err = snd_pcm_new(card, str, device, 2, 1, &pcm);
+ else
+ err = snd_pcm_new(card, str, device, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es18xx_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es18xx_capture_ops);
+
+ /* global setup */
+ pcm->private_data = chip;
+ pcm->info_flags = 0;
+ if (chip->caps & ES18XX_DUPLEX_SAME)
+ pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+ if (! (chip->caps & ES18XX_PCM2))
+ pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+ sprintf(pcm->name, "ESS AudioDrive ES%x", chip->version);
+ chip->pcm = pcm;
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, card->dev,
+ 64*1024,
+ chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
+ return 0;
+}
+
+/* Power Management support functions */
+#ifdef CONFIG_PM
+static int snd_es18xx_suspend(struct snd_card *card, pm_message_t state)
+{
+ struct snd_es18xx *chip = card->private_data;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+ /* power down */
+ chip->pm_reg = (unsigned char)snd_es18xx_read(chip, ES18XX_PM);
+ chip->pm_reg |= (ES18XX_PM_FM | ES18XX_PM_SUS);
+ snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg);
+ snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_SUS);
+
+ return 0;
+}
+
+static int snd_es18xx_resume(struct snd_card *card)
+{
+ struct snd_es18xx *chip = card->private_data;
+
+ /* restore PM register, we won't wake till (not 0x07) i/o activity though */
+ snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_FM);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static int snd_es18xx_new_device(struct snd_card *card,
+ unsigned long port,
+ unsigned long mpu_port,
+ unsigned long fm_port,
+ int irq, int dma1, int dma2)
+{
+ struct snd_es18xx *chip = card->private_data;
+
+ spin_lock_init(&chip->reg_lock);
+ spin_lock_init(&chip->mixer_lock);
+ chip->port = port;
+ chip->irq = -1;
+ chip->dma1 = -1;
+ chip->dma2 = -1;
+ chip->audio2_vol = 0x00;
+ chip->active = 0;
+
+ if (!devm_request_region(card->dev, port, 16, "ES18xx")) {
+ snd_printk(KERN_ERR PFX "unable to grap ports 0x%lx-0x%lx\n", port, port + 16 - 1);
+ return -EBUSY;
+ }
+
+ if (devm_request_irq(card->dev, irq, snd_es18xx_interrupt, 0, "ES18xx",
+ (void *) card)) {
+ snd_printk(KERN_ERR PFX "unable to grap IRQ %d\n", irq);
+ return -EBUSY;
+ }
+ chip->irq = irq;
+ card->sync_irq = chip->irq;
+
+ if (snd_devm_request_dma(card->dev, dma1, "ES18xx DMA 1")) {
+ snd_printk(KERN_ERR PFX "unable to grap DMA1 %d\n", dma1);
+ return -EBUSY;
+ }
+ chip->dma1 = dma1;
+
+ if (dma2 != dma1 &&
+ snd_devm_request_dma(card->dev, dma2, "ES18xx DMA 2")) {
+ snd_printk(KERN_ERR PFX "unable to grap DMA2 %d\n", dma2);
+ return -EBUSY;
+ }
+ chip->dma2 = dma2;
+
+ if (snd_es18xx_probe(card, chip, mpu_port, fm_port) < 0)
+ return -ENODEV;
+ return 0;
+}
+
+static int snd_es18xx_mixer(struct snd_card *card)
+{
+ struct snd_es18xx *chip = card->private_data;
+ int err;
+ unsigned int idx;
+
+ strcpy(card->mixername, chip->pcm->name);
+
+ for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_base_controls); idx++) {
+ struct snd_kcontrol *kctl;
+ kctl = snd_ctl_new1(&snd_es18xx_base_controls[idx], chip);
+ if (chip->caps & ES18XX_HWV) {
+ switch (idx) {
+ case 0:
+ chip->master_volume = kctl;
+ kctl->private_free = snd_es18xx_hwv_free;
+ break;
+ case 1:
+ chip->master_switch = kctl;
+ kctl->private_free = snd_es18xx_hwv_free;
+ break;
+ }
+ }
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ return err;
+ }
+ if (chip->caps & ES18XX_PCM2) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm2_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm2_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ } else {
+ for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm1_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm1_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ }
+
+ if (chip->caps & ES18XX_RECMIX) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_recmix_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_recmix_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ }
+ switch (chip->version) {
+ default:
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre1_control, chip));
+ if (err < 0)
+ return err;
+ break;
+ case 0x1869:
+ case 0x1879:
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre2_control, chip));
+ if (err < 0)
+ return err;
+ break;
+ }
+ if (chip->caps & ES18XX_SPATIALIZER) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_spatializer_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_spatializer_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ }
+ if (chip->caps & ES18XX_HWV) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_hw_volume_controls); idx++) {
+ struct snd_kcontrol *kctl;
+ kctl = snd_ctl_new1(&snd_es18xx_hw_volume_controls[idx], chip);
+ if (idx == 0)
+ chip->hw_volume = kctl;
+ else
+ chip->hw_switch = kctl;
+ kctl->private_free = snd_es18xx_hwv_free;
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ return err;
+
+ }
+ }
+ /* finish initializing other chipset specific controls
+ */
+ if (chip->version != 0x1868) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_opt_speaker,
+ chip));
+ if (err < 0)
+ return err;
+ }
+ if (chip->version == 0x1869) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_1869); idx++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_es18xx_opt_1869[idx],
+ chip));
+ if (err < 0)
+ return err;
+ }
+ } else if (chip->version == 0x1878) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_opt_1878,
+ chip));
+ if (err < 0)
+ return err;
+ } else if (chip->version == 0x1879) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_1879); idx++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_es18xx_opt_1879[idx],
+ chip));
+ if (err < 0)
+ return err;
+ }
+ }
+ if (chip->caps & ES18XX_GPO_2BIT) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_gpo_2bit); idx++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_es18xx_opt_gpo_2bit[idx],
+ chip));
+ if (err < 0)
+ return err;
+ }
+ }
+ return 0;
+}
+
+
+/* Card level */
+
+MODULE_AUTHOR("Christian Fischbach <fishbach@pool.informatik.rwth-aachen.de>, Abramo Bagnara <abramo@alsa-project.org>");
+MODULE_DESCRIPTION("ESS ES18xx AudioDrive");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260,0x280 */
+#ifndef CONFIG_PNP
+static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1};
+#else
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#endif
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ES18xx soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ES18xx soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ES18xx soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for ES18xx driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ES18xx driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for ES18xx driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for ES18xx driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA 1 # for ES18xx driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA 2 # for ES18xx driver.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+static int pnpc_registered;
+
+static const struct pnp_device_id snd_audiodrive_pnpbiosids[] = {
+ { .id = "ESS1869" },
+ { .id = "ESS1879" },
+ { .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp, snd_audiodrive_pnpbiosids);
+
+/* PnP main device initialization */
+static int snd_audiodrive_pnp_init_main(int dev, struct pnp_dev *pdev)
+{
+ if (pnp_activate_dev(pdev) < 0) {
+ snd_printk(KERN_ERR PFX "PnP configure failure (out of resources?)\n");
+ return -EBUSY;
+ }
+ /* ok. hack using Vendor-Defined Card-Level registers */
+ /* skip csn and logdev initialization - already done in isapnp_configure */
+ if (pnp_device_is_isapnp(pdev)) {
+ isapnp_cfg_begin(isapnp_card_number(pdev), isapnp_csn_number(pdev));
+ isapnp_write_byte(0x27, pnp_irq(pdev, 0)); /* Hardware Volume IRQ Number */
+ if (mpu_port[dev] != SNDRV_AUTO_PORT)
+ isapnp_write_byte(0x28, pnp_irq(pdev, 0)); /* MPU-401 IRQ Number */
+ isapnp_write_byte(0x72, pnp_irq(pdev, 0)); /* second IRQ */
+ isapnp_cfg_end();
+ }
+ port[dev] = pnp_port_start(pdev, 0);
+ fm_port[dev] = pnp_port_start(pdev, 1);
+ mpu_port[dev] = pnp_port_start(pdev, 2);
+ dma1[dev] = pnp_dma(pdev, 0);
+ dma2[dev] = pnp_dma(pdev, 1);
+ irq[dev] = pnp_irq(pdev, 0);
+ snd_printdd("PnP ES18xx: port=0x%lx, fm port=0x%lx, mpu port=0x%lx\n", port[dev], fm_port[dev], mpu_port[dev]);
+ snd_printdd("PnP ES18xx: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]);
+ return 0;
+}
+
+static int snd_audiodrive_pnp(int dev, struct snd_es18xx *chip,
+ struct pnp_dev *pdev)
+{
+ chip->dev = pdev;
+ if (snd_audiodrive_pnp_init_main(dev, chip->dev) < 0)
+ return -EBUSY;
+ return 0;
+}
+
+static const struct pnp_card_device_id snd_audiodrive_pnpids[] = {
+ /* ESS 1868 (integrated on Compaq dual P-Pro motherboard and Genius 18PnP 3D) */
+ { .id = "ESS1868", .devs = { { "ESS1868" }, { "ESS0000" } } },
+ /* ESS 1868 (integrated on Maxisound Cards) */
+ { .id = "ESS1868", .devs = { { "ESS8601" }, { "ESS8600" } } },
+ /* ESS 1868 (integrated on Maxisound Cards) */
+ { .id = "ESS1868", .devs = { { "ESS8611" }, { "ESS8610" } } },
+ /* ESS ES1869 Plug and Play AudioDrive */
+ { .id = "ESS0003", .devs = { { "ESS1869" }, { "ESS0006" } } },
+ /* ESS 1869 */
+ { .id = "ESS1869", .devs = { { "ESS1869" }, { "ESS0006" } } },
+ /* ESS 1878 */
+ { .id = "ESS1878", .devs = { { "ESS1878" }, { "ESS0004" } } },
+ /* ESS 1879 */
+ { .id = "ESS1879", .devs = { { "ESS1879" }, { "ESS0009" } } },
+ /* --- */
+ { .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_audiodrive_pnpids);
+
+static int snd_audiodrive_pnpc(int dev, struct snd_es18xx *chip,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ chip->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (chip->dev == NULL)
+ return -EBUSY;
+
+ chip->devc = pnp_request_card_device(card, id->devs[1].id, NULL);
+ if (chip->devc == NULL)
+ return -EBUSY;
+
+ /* Control port initialization */
+ if (pnp_activate_dev(chip->devc) < 0) {
+ snd_printk(KERN_ERR PFX "PnP control configure failure (out of resources?)\n");
+ return -EAGAIN;
+ }
+ snd_printdd("pnp: port=0x%llx\n",
+ (unsigned long long)pnp_port_start(chip->devc, 0));
+ if (snd_audiodrive_pnp_init_main(dev, chip->dev) < 0)
+ return -EBUSY;
+
+ return 0;
+}
+#endif /* CONFIG_PNP */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev) isapnp[dev]
+#else
+#define is_isapnp_selected(dev) 0
+#endif
+
+static int snd_es18xx_card_new(struct device *pdev, int dev,
+ struct snd_card **cardp)
+{
+ return snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_es18xx), cardp);
+}
+
+static int snd_audiodrive_probe(struct snd_card *card, int dev)
+{
+ struct snd_es18xx *chip = card->private_data;
+ struct snd_opl3 *opl3;
+ int err;
+
+ err = snd_es18xx_new_device(card,
+ port[dev], mpu_port[dev], fm_port[dev],
+ irq[dev], dma1[dev], dma2[dev]);
+ if (err < 0)
+ return err;
+
+ sprintf(card->driver, "ES%x", chip->version);
+
+ sprintf(card->shortname, "ESS AudioDrive ES%x", chip->version);
+ if (dma1[dev] != dma2[dev])
+ sprintf(card->longname, "%s at 0x%lx, irq %d, dma1 %d, dma2 %d",
+ card->shortname,
+ chip->port,
+ irq[dev], dma1[dev], dma2[dev]);
+ else
+ sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+ card->shortname,
+ chip->port,
+ irq[dev], dma1[dev]);
+
+ err = snd_es18xx_pcm(card, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_es18xx_mixer(card);
+ if (err < 0)
+ return err;
+
+ if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+ if (snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+ OPL3_HW_OPL3, 0, &opl3) < 0) {
+ snd_printk(KERN_WARNING PFX
+ "opl3 not detected at 0x%lx\n",
+ fm_port[dev]);
+ } else {
+ err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+ err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES18XX,
+ mpu_port[dev], MPU401_INFO_IRQ_HOOK,
+ -1, &chip->rmidi);
+ if (err < 0)
+ return err;
+ }
+
+ return snd_card_register(card);
+}
+
+static int snd_es18xx_isa_match(struct device *pdev, unsigned int dev)
+{
+ return enable[dev] && !is_isapnp_selected(dev);
+}
+
+static int snd_es18xx_isa_probe1(int dev, struct device *devptr)
+{
+ struct snd_card *card;
+ int err;
+
+ err = snd_es18xx_card_new(devptr, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_audiodrive_probe(card, dev);
+ if (err < 0)
+ return err;
+ dev_set_drvdata(devptr, card);
+ return 0;
+}
+
+static int snd_es18xx_isa_probe(struct device *pdev, unsigned int dev)
+{
+ int err;
+ static const int possible_irqs[] = {5, 9, 10, 7, 11, 12, -1};
+ static const int possible_dmas[] = {1, 0, 3, 5, -1};
+
+ if (irq[dev] == SNDRV_AUTO_IRQ) {
+ irq[dev] = snd_legacy_find_free_irq(possible_irqs);
+ if (irq[dev] < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (dma1[dev] == SNDRV_AUTO_DMA) {
+ dma1[dev] = snd_legacy_find_free_dma(possible_dmas);
+ if (dma1[dev] < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free DMA1\n");
+ return -EBUSY;
+ }
+ }
+ if (dma2[dev] == SNDRV_AUTO_DMA) {
+ dma2[dev] = snd_legacy_find_free_dma(possible_dmas);
+ if (dma2[dev] < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free DMA2\n");
+ return -EBUSY;
+ }
+ }
+
+ if (port[dev] != SNDRV_AUTO_PORT) {
+ return snd_es18xx_isa_probe1(dev, pdev);
+ } else {
+ static const unsigned long possible_ports[] = {0x220, 0x240, 0x260, 0x280};
+ int i;
+ for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+ port[dev] = possible_ports[i];
+ err = snd_es18xx_isa_probe1(dev, pdev);
+ if (! err)
+ return 0;
+ }
+ return err;
+ }
+}
+
+#ifdef CONFIG_PM
+static int snd_es18xx_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_es18xx_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_es18xx_isa_resume(struct device *dev, unsigned int n)
+{
+ return snd_es18xx_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#define DEV_NAME "es18xx"
+
+static struct isa_driver snd_es18xx_isa_driver = {
+ .match = snd_es18xx_isa_match,
+ .probe = snd_es18xx_isa_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_es18xx_isa_suspend,
+ .resume = snd_es18xx_isa_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_audiodrive_pnp_detect(struct pnp_dev *pdev,
+ const struct pnp_device_id *id)
+{
+ static int dev;
+ int err;
+ struct snd_card *card;
+
+ if (pnp_device_is_isapnp(pdev))
+ return -ENOENT; /* we have another procedure - card */
+ for (; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ err = snd_es18xx_card_new(&pdev->dev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_audiodrive_pnp(dev, card->private_data, pdev);
+ if (err < 0)
+ return err;
+ err = snd_audiodrive_probe(card, dev);
+ if (err < 0)
+ return err;
+ pnp_set_drvdata(pdev, card);
+ dev++;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_audiodrive_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+ return snd_es18xx_suspend(pnp_get_drvdata(pdev), state);
+}
+static int snd_audiodrive_pnp_resume(struct pnp_dev *pdev)
+{
+ return snd_es18xx_resume(pnp_get_drvdata(pdev));
+}
+#endif
+
+static struct pnp_driver es18xx_pnp_driver = {
+ .name = "es18xx-pnpbios",
+ .id_table = snd_audiodrive_pnpbiosids,
+ .probe = snd_audiodrive_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_audiodrive_pnp_suspend,
+ .resume = snd_audiodrive_pnp_resume,
+#endif
+};
+
+static int snd_audiodrive_pnpc_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ static int dev;
+ struct snd_card *card;
+ int res;
+
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ res = snd_es18xx_card_new(&pcard->card->dev, dev, &card);
+ if (res < 0)
+ return res;
+
+ res = snd_audiodrive_pnpc(dev, card->private_data, pcard, pid);
+ if (res < 0)
+ return res;
+ res = snd_audiodrive_probe(card, dev);
+ if (res < 0)
+ return res;
+
+ pnp_set_card_drvdata(pcard, card);
+ dev++;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_audiodrive_pnpc_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ return snd_es18xx_suspend(pnp_get_card_drvdata(pcard), state);
+}
+
+static int snd_audiodrive_pnpc_resume(struct pnp_card_link *pcard)
+{
+ return snd_es18xx_resume(pnp_get_card_drvdata(pcard));
+}
+
+#endif
+
+static struct pnp_card_driver es18xx_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = "es18xx",
+ .id_table = snd_audiodrive_pnpids,
+ .probe = snd_audiodrive_pnpc_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_audiodrive_pnpc_suspend,
+ .resume = snd_audiodrive_pnpc_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_es18xx_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&snd_es18xx_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+
+ err = pnp_register_driver(&es18xx_pnp_driver);
+ if (!err)
+ pnp_registered = 1;
+
+ err = pnp_register_card_driver(&es18xx_pnpc_driver);
+ if (!err)
+ pnpc_registered = 1;
+
+ if (isa_registered || pnp_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit alsa_card_es18xx_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnpc_registered)
+ pnp_unregister_card_driver(&es18xx_pnpc_driver);
+ if (pnp_registered)
+ pnp_unregister_driver(&es18xx_pnp_driver);
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&snd_es18xx_isa_driver);
+}
+
+module_init(alsa_card_es18xx_init)
+module_exit(alsa_card_es18xx_exit)
diff --git a/sound/isa/galaxy/Makefile b/sound/isa/galaxy/Makefile
new file mode 100644
index 0000000000..ff861f2380
--- /dev/null
+++ b/sound/isa/galaxy/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-azt1605-objs := azt1605.o
+snd-azt2316-objs := azt2316.o
+
+obj-$(CONFIG_SND_AZT1605) += snd-azt1605.o
+obj-$(CONFIG_SND_AZT2316) += snd-azt2316.o
diff --git a/sound/isa/galaxy/azt1605.c b/sound/isa/galaxy/azt1605.c
new file mode 100644
index 0000000000..545fb13fbe
--- /dev/null
+++ b/sound/isa/galaxy/azt1605.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Aztech AZT1605 Driver
+ * Copyright (C) 2007,2010 Rene Herman
+ */
+
+#define AZT1605
+
+#define CRD_NAME "Aztech AZT1605"
+#define DRV_NAME "AZT1605"
+#define DEV_NAME "azt1605"
+
+#define GALAXY_DSP_MAJOR 2
+#define GALAXY_DSP_MINOR 1
+
+#define GALAXY_CONFIG_SIZE 3
+
+/*
+ * 24-bit config register
+ */
+
+#define GALAXY_CONFIG_SBA_220 (0 << 0)
+#define GALAXY_CONFIG_SBA_240 (1 << 0)
+#define GALAXY_CONFIG_SBA_260 (2 << 0)
+#define GALAXY_CONFIG_SBA_280 (3 << 0)
+#define GALAXY_CONFIG_SBA_MASK GALAXY_CONFIG_SBA_280
+
+#define GALAXY_CONFIG_MPUA_300 (0 << 2)
+#define GALAXY_CONFIG_MPUA_330 (1 << 2)
+
+#define GALAXY_CONFIG_MPU_ENABLE (1 << 3)
+
+#define GALAXY_CONFIG_GAME_ENABLE (1 << 4)
+
+#define GALAXY_CONFIG_CD_PANASONIC (1 << 5)
+#define GALAXY_CONFIG_CD_MITSUMI (1 << 6)
+#define GALAXY_CONFIG_CD_MASK (\
+ GALAXY_CONFIG_CD_PANASONIC | GALAXY_CONFIG_CD_MITSUMI)
+
+#define GALAXY_CONFIG_UNUSED (1 << 7)
+#define GALAXY_CONFIG_UNUSED_MASK GALAXY_CONFIG_UNUSED
+
+#define GALAXY_CONFIG_SBIRQ_2 (1 << 8)
+#define GALAXY_CONFIG_SBIRQ_3 (1 << 9)
+#define GALAXY_CONFIG_SBIRQ_5 (1 << 10)
+#define GALAXY_CONFIG_SBIRQ_7 (1 << 11)
+
+#define GALAXY_CONFIG_MPUIRQ_2 (1 << 12)
+#define GALAXY_CONFIG_MPUIRQ_3 (1 << 13)
+#define GALAXY_CONFIG_MPUIRQ_5 (1 << 14)
+#define GALAXY_CONFIG_MPUIRQ_7 (1 << 15)
+
+#define GALAXY_CONFIG_WSSA_530 (0 << 16)
+#define GALAXY_CONFIG_WSSA_604 (1 << 16)
+#define GALAXY_CONFIG_WSSA_E80 (2 << 16)
+#define GALAXY_CONFIG_WSSA_F40 (3 << 16)
+
+#define GALAXY_CONFIG_WSS_ENABLE (1 << 18)
+
+#define GALAXY_CONFIG_CDIRQ_11 (1 << 19)
+#define GALAXY_CONFIG_CDIRQ_12 (1 << 20)
+#define GALAXY_CONFIG_CDIRQ_15 (1 << 21)
+#define GALAXY_CONFIG_CDIRQ_MASK (\
+ GALAXY_CONFIG_CDIRQ_11 | GALAXY_CONFIG_CDIRQ_12 |\
+ GALAXY_CONFIG_CDIRQ_15)
+
+#define GALAXY_CONFIG_CDDMA_DISABLE (0 << 22)
+#define GALAXY_CONFIG_CDDMA_0 (1 << 22)
+#define GALAXY_CONFIG_CDDMA_1 (2 << 22)
+#define GALAXY_CONFIG_CDDMA_3 (3 << 22)
+#define GALAXY_CONFIG_CDDMA_MASK GALAXY_CONFIG_CDDMA_3
+
+#define GALAXY_CONFIG_MASK (\
+ GALAXY_CONFIG_SBA_MASK | GALAXY_CONFIG_CD_MASK |\
+ GALAXY_CONFIG_UNUSED_MASK | GALAXY_CONFIG_CDIRQ_MASK |\
+ GALAXY_CONFIG_CDDMA_MASK)
+
+#include "galaxy.c"
diff --git a/sound/isa/galaxy/azt2316.c b/sound/isa/galaxy/azt2316.c
new file mode 100644
index 0000000000..76251e845f
--- /dev/null
+++ b/sound/isa/galaxy/azt2316.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Aztech AZT2316 Driver
+ * Copyright (C) 2007,2010 Rene Herman
+ */
+
+#define AZT2316
+
+#define CRD_NAME "Aztech AZT2316"
+#define DRV_NAME "AZT2316"
+#define DEV_NAME "azt2316"
+
+#define GALAXY_DSP_MAJOR 3
+#define GALAXY_DSP_MINOR 1
+
+#define GALAXY_CONFIG_SIZE 4
+
+/*
+ * 32-bit config register
+ */
+
+#define GALAXY_CONFIG_SBA_220 (0 << 0)
+#define GALAXY_CONFIG_SBA_240 (1 << 0)
+#define GALAXY_CONFIG_SBA_260 (2 << 0)
+#define GALAXY_CONFIG_SBA_280 (3 << 0)
+#define GALAXY_CONFIG_SBA_MASK GALAXY_CONFIG_SBA_280
+
+#define GALAXY_CONFIG_SBIRQ_2 (1 << 2)
+#define GALAXY_CONFIG_SBIRQ_5 (1 << 3)
+#define GALAXY_CONFIG_SBIRQ_7 (1 << 4)
+#define GALAXY_CONFIG_SBIRQ_10 (1 << 5)
+
+#define GALAXY_CONFIG_SBDMA_DISABLE (0 << 6)
+#define GALAXY_CONFIG_SBDMA_0 (1 << 6)
+#define GALAXY_CONFIG_SBDMA_1 (2 << 6)
+#define GALAXY_CONFIG_SBDMA_3 (3 << 6)
+
+#define GALAXY_CONFIG_WSSA_530 (0 << 8)
+#define GALAXY_CONFIG_WSSA_604 (1 << 8)
+#define GALAXY_CONFIG_WSSA_E80 (2 << 8)
+#define GALAXY_CONFIG_WSSA_F40 (3 << 8)
+
+#define GALAXY_CONFIG_WSS_ENABLE (1 << 10)
+
+#define GALAXY_CONFIG_GAME_ENABLE (1 << 11)
+
+#define GALAXY_CONFIG_MPUA_300 (0 << 12)
+#define GALAXY_CONFIG_MPUA_330 (1 << 12)
+
+#define GALAXY_CONFIG_MPU_ENABLE (1 << 13)
+
+#define GALAXY_CONFIG_CDA_310 (0 << 14)
+#define GALAXY_CONFIG_CDA_320 (1 << 14)
+#define GALAXY_CONFIG_CDA_340 (2 << 14)
+#define GALAXY_CONFIG_CDA_350 (3 << 14)
+#define GALAXY_CONFIG_CDA_MASK GALAXY_CONFIG_CDA_350
+
+#define GALAXY_CONFIG_CD_DISABLE (0 << 16)
+#define GALAXY_CONFIG_CD_PANASONIC (1 << 16)
+#define GALAXY_CONFIG_CD_SONY (2 << 16)
+#define GALAXY_CONFIG_CD_MITSUMI (3 << 16)
+#define GALAXY_CONFIG_CD_AZTECH (4 << 16)
+#define GALAXY_CONFIG_CD_UNUSED_5 (5 << 16)
+#define GALAXY_CONFIG_CD_UNUSED_6 (6 << 16)
+#define GALAXY_CONFIG_CD_UNUSED_7 (7 << 16)
+#define GALAXY_CONFIG_CD_MASK GALAXY_CONFIG_CD_UNUSED_7
+
+#define GALAXY_CONFIG_CDDMA8_DISABLE (0 << 20)
+#define GALAXY_CONFIG_CDDMA8_0 (1 << 20)
+#define GALAXY_CONFIG_CDDMA8_1 (2 << 20)
+#define GALAXY_CONFIG_CDDMA8_3 (3 << 20)
+#define GALAXY_CONFIG_CDDMA8_MASK GALAXY_CONFIG_CDDMA8_3
+
+#define GALAXY_CONFIG_CDDMA16_DISABLE (0 << 22)
+#define GALAXY_CONFIG_CDDMA16_5 (1 << 22)
+#define GALAXY_CONFIG_CDDMA16_6 (2 << 22)
+#define GALAXY_CONFIG_CDDMA16_7 (3 << 22)
+#define GALAXY_CONFIG_CDDMA16_MASK GALAXY_CONFIG_CDDMA16_7
+
+#define GALAXY_CONFIG_MPUIRQ_2 (1 << 24)
+#define GALAXY_CONFIG_MPUIRQ_5 (1 << 25)
+#define GALAXY_CONFIG_MPUIRQ_7 (1 << 26)
+#define GALAXY_CONFIG_MPUIRQ_10 (1 << 27)
+
+#define GALAXY_CONFIG_CDIRQ_5 (1 << 28)
+#define GALAXY_CONFIG_CDIRQ_11 (1 << 29)
+#define GALAXY_CONFIG_CDIRQ_12 (1 << 30)
+#define GALAXY_CONFIG_CDIRQ_15 (1 << 31)
+#define GALAXY_CONFIG_CDIRQ_MASK (\
+ GALAXY_CONFIG_CDIRQ_5 | GALAXY_CONFIG_CDIRQ_11 |\
+ GALAXY_CONFIG_CDIRQ_12 | GALAXY_CONFIG_CDIRQ_15)
+
+#define GALAXY_CONFIG_MASK (\
+ GALAXY_CONFIG_SBA_MASK | GALAXY_CONFIG_CDA_MASK |\
+ GALAXY_CONFIG_CD_MASK | GALAXY_CONFIG_CDDMA16_MASK |\
+ GALAXY_CONFIG_CDDMA8_MASK | GALAXY_CONFIG_CDIRQ_MASK)
+
+#include "galaxy.c"
diff --git a/sound/isa/galaxy/galaxy.c b/sound/isa/galaxy/galaxy.c
new file mode 100644
index 0000000000..3164eb8510
--- /dev/null
+++ b/sound/isa/galaxy/galaxy.c
@@ -0,0 +1,615 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Aztech AZT1605/AZT2316 Driver
+ * Copyright (C) 2007,2010 Rene Herman
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <asm/processor.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Rene Herman");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(wss_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS port # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "Playback DMA # for " CRD_NAME " driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "Capture DMA # for " CRD_NAME " driver.");
+
+/*
+ * Generic SB DSP support routines
+ */
+
+#define DSP_PORT_RESET 0x6
+#define DSP_PORT_READ 0xa
+#define DSP_PORT_COMMAND 0xc
+#define DSP_PORT_STATUS 0xc
+#define DSP_PORT_DATA_AVAIL 0xe
+
+#define DSP_SIGNATURE 0xaa
+
+#define DSP_COMMAND_GET_VERSION 0xe1
+
+static int dsp_get_byte(void __iomem *port, u8 *val)
+{
+ int loops = 1000;
+
+ while (!(ioread8(port + DSP_PORT_DATA_AVAIL) & 0x80)) {
+ if (!loops--)
+ return -EIO;
+ cpu_relax();
+ }
+ *val = ioread8(port + DSP_PORT_READ);
+ return 0;
+}
+
+static int dsp_reset(void __iomem *port)
+{
+ u8 val;
+
+ iowrite8(1, port + DSP_PORT_RESET);
+ udelay(10);
+ iowrite8(0, port + DSP_PORT_RESET);
+
+ if (dsp_get_byte(port, &val) < 0 || val != DSP_SIGNATURE)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int dsp_command(void __iomem *port, u8 cmd)
+{
+ int loops = 1000;
+
+ while (ioread8(port + DSP_PORT_STATUS) & 0x80) {
+ if (!loops--)
+ return -EIO;
+ cpu_relax();
+ }
+ iowrite8(cmd, port + DSP_PORT_COMMAND);
+ return 0;
+}
+
+static int dsp_get_version(void __iomem *port, u8 *major, u8 *minor)
+{
+ int err;
+
+ err = dsp_command(port, DSP_COMMAND_GET_VERSION);
+ if (err < 0)
+ return err;
+
+ err = dsp_get_byte(port, major);
+ if (err < 0)
+ return err;
+
+ err = dsp_get_byte(port, minor);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+/*
+ * Generic WSS support routines
+ */
+
+#define WSS_CONFIG_DMA_0 (1 << 0)
+#define WSS_CONFIG_DMA_1 (2 << 0)
+#define WSS_CONFIG_DMA_3 (3 << 0)
+#define WSS_CONFIG_DUPLEX (1 << 2)
+#define WSS_CONFIG_IRQ_7 (1 << 3)
+#define WSS_CONFIG_IRQ_9 (2 << 3)
+#define WSS_CONFIG_IRQ_10 (3 << 3)
+#define WSS_CONFIG_IRQ_11 (4 << 3)
+
+#define WSS_PORT_CONFIG 0
+#define WSS_PORT_SIGNATURE 3
+
+#define WSS_SIGNATURE 4
+
+static int wss_detect(void __iomem *wss_port)
+{
+ if ((ioread8(wss_port + WSS_PORT_SIGNATURE) & 0x3f) != WSS_SIGNATURE)
+ return -ENODEV;
+
+ return 0;
+}
+
+static void wss_set_config(void __iomem *wss_port, u8 wss_config)
+{
+ iowrite8(wss_config, wss_port + WSS_PORT_CONFIG);
+}
+
+/*
+ * Aztech Sound Galaxy specifics
+ */
+
+#define GALAXY_PORT_CONFIG 1024
+#define CONFIG_PORT_SET 4
+
+#define DSP_COMMAND_GALAXY_8 8
+#define GALAXY_COMMAND_GET_TYPE 5
+
+#define DSP_COMMAND_GALAXY_9 9
+#define GALAXY_COMMAND_WSSMODE 0
+#define GALAXY_COMMAND_SB8MODE 1
+
+#define GALAXY_MODE_WSS GALAXY_COMMAND_WSSMODE
+#define GALAXY_MODE_SB8 GALAXY_COMMAND_SB8MODE
+
+struct snd_galaxy {
+ void __iomem *port;
+ void __iomem *config_port;
+ void __iomem *wss_port;
+ u32 config;
+ struct resource *res_port;
+ struct resource *res_config_port;
+ struct resource *res_wss_port;
+};
+
+static u32 config[SNDRV_CARDS];
+static u8 wss_config[SNDRV_CARDS];
+
+static int snd_galaxy_match(struct device *dev, unsigned int n)
+{
+ if (!enable[n])
+ return 0;
+
+ switch (port[n]) {
+ case SNDRV_AUTO_PORT:
+ dev_err(dev, "please specify port\n");
+ return 0;
+ case 0x220:
+ config[n] |= GALAXY_CONFIG_SBA_220;
+ break;
+ case 0x240:
+ config[n] |= GALAXY_CONFIG_SBA_240;
+ break;
+ case 0x260:
+ config[n] |= GALAXY_CONFIG_SBA_260;
+ break;
+ case 0x280:
+ config[n] |= GALAXY_CONFIG_SBA_280;
+ break;
+ default:
+ dev_err(dev, "invalid port %#lx\n", port[n]);
+ return 0;
+ }
+
+ switch (wss_port[n]) {
+ case SNDRV_AUTO_PORT:
+ dev_err(dev, "please specify wss_port\n");
+ return 0;
+ case 0x530:
+ config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_530;
+ break;
+ case 0x604:
+ config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_604;
+ break;
+ case 0xe80:
+ config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_E80;
+ break;
+ case 0xf40:
+ config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_F40;
+ break;
+ default:
+ dev_err(dev, "invalid WSS port %#lx\n", wss_port[n]);
+ return 0;
+ }
+
+ switch (irq[n]) {
+ case SNDRV_AUTO_IRQ:
+ dev_err(dev, "please specify irq\n");
+ return 0;
+ case 7:
+ wss_config[n] |= WSS_CONFIG_IRQ_7;
+ break;
+ case 2:
+ irq[n] = 9;
+ fallthrough;
+ case 9:
+ wss_config[n] |= WSS_CONFIG_IRQ_9;
+ break;
+ case 10:
+ wss_config[n] |= WSS_CONFIG_IRQ_10;
+ break;
+ case 11:
+ wss_config[n] |= WSS_CONFIG_IRQ_11;
+ break;
+ default:
+ dev_err(dev, "invalid IRQ %d\n", irq[n]);
+ return 0;
+ }
+
+ switch (dma1[n]) {
+ case SNDRV_AUTO_DMA:
+ dev_err(dev, "please specify dma1\n");
+ return 0;
+ case 0:
+ wss_config[n] |= WSS_CONFIG_DMA_0;
+ break;
+ case 1:
+ wss_config[n] |= WSS_CONFIG_DMA_1;
+ break;
+ case 3:
+ wss_config[n] |= WSS_CONFIG_DMA_3;
+ break;
+ default:
+ dev_err(dev, "invalid playback DMA %d\n", dma1[n]);
+ return 0;
+ }
+
+ if (dma2[n] == SNDRV_AUTO_DMA || dma2[n] == dma1[n]) {
+ dma2[n] = -1;
+ goto mpu;
+ }
+
+ wss_config[n] |= WSS_CONFIG_DUPLEX;
+ switch (dma2[n]) {
+ case 0:
+ break;
+ case 1:
+ if (dma1[n] == 0)
+ break;
+ fallthrough;
+ default:
+ dev_err(dev, "invalid capture DMA %d\n", dma2[n]);
+ return 0;
+ }
+
+mpu:
+ switch (mpu_port[n]) {
+ case SNDRV_AUTO_PORT:
+ dev_warn(dev, "mpu_port not specified; not using MPU-401\n");
+ mpu_port[n] = -1;
+ goto fm;
+ case 0x300:
+ config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_300;
+ break;
+ case 0x330:
+ config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_330;
+ break;
+ default:
+ dev_err(dev, "invalid MPU port %#lx\n", mpu_port[n]);
+ return 0;
+ }
+
+ switch (mpu_irq[n]) {
+ case SNDRV_AUTO_IRQ:
+ dev_warn(dev, "mpu_irq not specified: using polling mode\n");
+ mpu_irq[n] = -1;
+ break;
+ case 2:
+ mpu_irq[n] = 9;
+ fallthrough;
+ case 9:
+ config[n] |= GALAXY_CONFIG_MPUIRQ_2;
+ break;
+#ifdef AZT1605
+ case 3:
+ config[n] |= GALAXY_CONFIG_MPUIRQ_3;
+ break;
+#endif
+ case 5:
+ config[n] |= GALAXY_CONFIG_MPUIRQ_5;
+ break;
+ case 7:
+ config[n] |= GALAXY_CONFIG_MPUIRQ_7;
+ break;
+#ifdef AZT2316
+ case 10:
+ config[n] |= GALAXY_CONFIG_MPUIRQ_10;
+ break;
+#endif
+ default:
+ dev_err(dev, "invalid MPU IRQ %d\n", mpu_irq[n]);
+ return 0;
+ }
+
+ if (mpu_irq[n] == irq[n]) {
+ dev_err(dev, "cannot share IRQ between WSS and MPU-401\n");
+ return 0;
+ }
+
+fm:
+ switch (fm_port[n]) {
+ case SNDRV_AUTO_PORT:
+ dev_warn(dev, "fm_port not specified: not using OPL3\n");
+ fm_port[n] = -1;
+ break;
+ case 0x388:
+ break;
+ default:
+ dev_err(dev, "illegal FM port %#lx\n", fm_port[n]);
+ return 0;
+ }
+
+ config[n] |= GALAXY_CONFIG_GAME_ENABLE;
+ return 1;
+}
+
+static int galaxy_init(struct snd_galaxy *galaxy, u8 *type)
+{
+ u8 major;
+ u8 minor;
+ int err;
+
+ err = dsp_reset(galaxy->port);
+ if (err < 0)
+ return err;
+
+ err = dsp_get_version(galaxy->port, &major, &minor);
+ if (err < 0)
+ return err;
+
+ if (major != GALAXY_DSP_MAJOR || minor != GALAXY_DSP_MINOR)
+ return -ENODEV;
+
+ err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_8);
+ if (err < 0)
+ return err;
+
+ err = dsp_command(galaxy->port, GALAXY_COMMAND_GET_TYPE);
+ if (err < 0)
+ return err;
+
+ err = dsp_get_byte(galaxy->port, type);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int galaxy_set_mode(struct snd_galaxy *galaxy, u8 mode)
+{
+ int err;
+
+ err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_9);
+ if (err < 0)
+ return err;
+
+ err = dsp_command(galaxy->port, mode);
+ if (err < 0)
+ return err;
+
+#ifdef AZT1605
+ /*
+ * Needed for MPU IRQ on AZT1605, but AZT2316 loses WSS again
+ */
+ err = dsp_reset(galaxy->port);
+ if (err < 0)
+ return err;
+#endif
+
+ return 0;
+}
+
+static void galaxy_set_config(struct snd_galaxy *galaxy, u32 config)
+{
+ u8 tmp = ioread8(galaxy->config_port + CONFIG_PORT_SET);
+ int i;
+
+ iowrite8(tmp | 0x80, galaxy->config_port + CONFIG_PORT_SET);
+ for (i = 0; i < GALAXY_CONFIG_SIZE; i++) {
+ iowrite8(config, galaxy->config_port + i);
+ config >>= 8;
+ }
+ iowrite8(tmp & 0x7f, galaxy->config_port + CONFIG_PORT_SET);
+ msleep(10);
+}
+
+static void galaxy_config(struct snd_galaxy *galaxy, u32 config)
+{
+ int i;
+
+ for (i = GALAXY_CONFIG_SIZE; i; i--) {
+ u8 tmp = ioread8(galaxy->config_port + i - 1);
+ galaxy->config = (galaxy->config << 8) | tmp;
+ }
+ config |= galaxy->config & GALAXY_CONFIG_MASK;
+ galaxy_set_config(galaxy, config);
+}
+
+static int galaxy_wss_config(struct snd_galaxy *galaxy, u8 wss_config)
+{
+ int err;
+
+ err = wss_detect(galaxy->wss_port);
+ if (err < 0)
+ return err;
+
+ wss_set_config(galaxy->wss_port, wss_config);
+
+ err = galaxy_set_mode(galaxy, GALAXY_MODE_WSS);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static void snd_galaxy_free(struct snd_card *card)
+{
+ struct snd_galaxy *galaxy = card->private_data;
+
+ if (galaxy->wss_port)
+ wss_set_config(galaxy->wss_port, 0);
+ if (galaxy->config_port)
+ galaxy_set_config(galaxy, galaxy->config);
+}
+
+static int __snd_galaxy_probe(struct device *dev, unsigned int n)
+{
+ struct snd_galaxy *galaxy;
+ struct snd_wss *chip;
+ struct snd_card *card;
+ u8 type;
+ int err;
+
+ err = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE,
+ sizeof(*galaxy), &card);
+ if (err < 0)
+ return err;
+
+ card->private_free = snd_galaxy_free;
+ galaxy = card->private_data;
+
+ galaxy->res_port = devm_request_region(dev, port[n], 16, DRV_NAME);
+ if (!galaxy->res_port) {
+ dev_err(dev, "could not grab ports %#lx-%#lx\n", port[n],
+ port[n] + 15);
+ return -EBUSY;
+ }
+ galaxy->port = devm_ioport_map(dev, port[n], 16);
+ if (!galaxy->port)
+ return -ENOMEM;
+
+ err = galaxy_init(galaxy, &type);
+ if (err < 0) {
+ dev_err(dev, "did not find a Sound Galaxy at %#lx\n", port[n]);
+ return err;
+ }
+ dev_info(dev, "Sound Galaxy (type %d) found at %#lx\n", type, port[n]);
+
+ galaxy->res_config_port =
+ devm_request_region(dev, port[n] + GALAXY_PORT_CONFIG, 16,
+ DRV_NAME);
+ if (!galaxy->res_config_port) {
+ dev_err(dev, "could not grab ports %#lx-%#lx\n",
+ port[n] + GALAXY_PORT_CONFIG,
+ port[n] + GALAXY_PORT_CONFIG + 15);
+ return -EBUSY;
+ }
+ galaxy->config_port =
+ devm_ioport_map(dev, port[n] + GALAXY_PORT_CONFIG, 16);
+ if (!galaxy->config_port)
+ return -ENOMEM;
+ galaxy_config(galaxy, config[n]);
+
+ galaxy->res_wss_port = devm_request_region(dev, wss_port[n], 4, DRV_NAME);
+ if (!galaxy->res_wss_port) {
+ dev_err(dev, "could not grab ports %#lx-%#lx\n", wss_port[n],
+ wss_port[n] + 3);
+ return -EBUSY;
+ }
+ galaxy->wss_port = devm_ioport_map(dev, wss_port[n], 4);
+ if (!galaxy->wss_port)
+ return -ENOMEM;
+
+ err = galaxy_wss_config(galaxy, wss_config[n]);
+ if (err < 0) {
+ dev_err(dev, "could not configure WSS\n");
+ return err;
+ }
+
+ strcpy(card->driver, DRV_NAME);
+ strcpy(card->shortname, DRV_NAME);
+ sprintf(card->longname, "%s at %#lx/%#lx, irq %d, dma %d/%d",
+ card->shortname, port[n], wss_port[n], irq[n], dma1[n],
+ dma2[n]);
+
+ err = snd_wss_create(card, wss_port[n] + 4, -1, irq[n], dma1[n],
+ dma2[n], WSS_HW_DETECT, 0, &chip);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_pcm(chip, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_mixer(chip);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_timer(chip, 0);
+ if (err < 0)
+ return err;
+
+ if (mpu_port[n] >= 0) {
+ err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+ mpu_port[n], 0, mpu_irq[n], NULL);
+ if (err < 0)
+ return err;
+ }
+
+ if (fm_port[n] >= 0) {
+ struct snd_opl3 *opl3;
+
+ err = snd_opl3_create(card, fm_port[n], fm_port[n] + 2,
+ OPL3_HW_AUTO, 0, &opl3);
+ if (err < 0) {
+ dev_err(dev, "no OPL device at %#lx\n", fm_port[n]);
+ return err;
+ }
+ err = snd_opl3_timer_new(opl3, 1, 2);
+ if (err < 0)
+ return err;
+
+ err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+
+ dev_set_drvdata(dev, card);
+ return 0;
+}
+
+static int snd_galaxy_probe(struct device *dev, unsigned int n)
+{
+ return snd_card_free_on_error(dev, __snd_galaxy_probe(dev, n));
+}
+
+static struct isa_driver snd_galaxy_driver = {
+ .match = snd_galaxy_match,
+ .probe = snd_galaxy_probe,
+
+ .driver = {
+ .name = DEV_NAME
+ }
+};
+
+module_isa_driver(snd_galaxy_driver, SNDRV_CARDS);
diff --git a/sound/isa/gus/Makefile b/sound/isa/gus/Makefile
new file mode 100644
index 0000000000..c6f32ffd34
--- /dev/null
+++ b/sound/isa/gus/Makefile
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-gus-lib-objs := gus_main.o \
+ gus_io.o gus_irq.o gus_timer.o \
+ gus_mem.o gus_mem_proc.o gus_dram.o gus_dma.o gus_volume.o \
+ gus_pcm.o gus_mixer.o \
+ gus_uart.o \
+ gus_reset.o
+
+snd-gusclassic-objs := gusclassic.o
+snd-gusextreme-objs := gusextreme.o
+snd-gusmax-objs := gusmax.o
+snd-interwave-objs := interwave.o
+snd-interwave-stb-objs := interwave-stb.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_GUSCLASSIC) += snd-gusclassic.o snd-gus-lib.o
+obj-$(CONFIG_SND_GUSMAX) += snd-gusmax.o snd-gus-lib.o
+obj-$(CONFIG_SND_GUSEXTREME) += snd-gusextreme.o snd-gus-lib.o
+obj-$(CONFIG_SND_INTERWAVE) += snd-interwave.o snd-gus-lib.o
+obj-$(CONFIG_SND_INTERWAVE_STB) += snd-interwave-stb.o snd-gus-lib.o
diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c
new file mode 100644
index 0000000000..6d664dd8dd
--- /dev/null
+++ b/sound/isa/gus/gus_dma.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Routines for GF1 DMA control
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <asm/dma.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+static void snd_gf1_dma_ack(struct snd_gus_card * gus)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, 0x00);
+ snd_gf1_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_dma_program(struct snd_gus_card * gus,
+ unsigned int addr,
+ unsigned long buf_addr,
+ unsigned int count,
+ unsigned int cmd)
+{
+ unsigned long flags;
+ unsigned int address;
+ unsigned char dma_cmd;
+ unsigned int address_high;
+
+ snd_printdd("dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n",
+ addr, buf_addr, count);
+
+ if (gus->gf1.dma1 > 3) {
+ if (gus->gf1.enh_mode) {
+ address = addr >> 1;
+ } else {
+ if (addr & 0x1f) {
+ snd_printd("snd_gf1_dma_transfer: unaligned address (0x%x)?\n", addr);
+ return;
+ }
+ address = (addr & 0x000c0000) | ((addr & 0x0003ffff) >> 1);
+ }
+ } else {
+ address = addr;
+ }
+
+ dma_cmd = SNDRV_GF1_DMA_ENABLE | (unsigned short) cmd;
+#if 0
+ dma_cmd |= 0x08;
+#endif
+ if (dma_cmd & SNDRV_GF1_DMA_16BIT) {
+ count++;
+ count &= ~1; /* align */
+ }
+ if (gus->gf1.dma1 > 3) {
+ dma_cmd |= SNDRV_GF1_DMA_WIDTH16;
+ count++;
+ count &= ~1; /* align */
+ }
+ snd_gf1_dma_ack(gus);
+ snd_dma_program(gus->gf1.dma1, buf_addr, count, dma_cmd & SNDRV_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE);
+#if 0
+ snd_printk(KERN_DEBUG "address = 0x%x, count = 0x%x, dma_cmd = 0x%x\n",
+ address << 1, count, dma_cmd);
+#endif
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ if (gus->gf1.enh_mode) {
+ address_high = ((address >> 16) & 0x000000f0) | (address & 0x0000000f);
+ snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4));
+ snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH, (unsigned char) address_high);
+ } else
+ snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4));
+ snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, dma_cmd);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static struct snd_gf1_dma_block *snd_gf1_dma_next_block(struct snd_gus_card * gus)
+{
+ struct snd_gf1_dma_block *block;
+
+ /* PCM block have bigger priority than synthesizer one */
+ if (gus->gf1.dma_data_pcm) {
+ block = gus->gf1.dma_data_pcm;
+ if (gus->gf1.dma_data_pcm_last == block) {
+ gus->gf1.dma_data_pcm =
+ gus->gf1.dma_data_pcm_last = NULL;
+ } else {
+ gus->gf1.dma_data_pcm = block->next;
+ }
+ } else if (gus->gf1.dma_data_synth) {
+ block = gus->gf1.dma_data_synth;
+ if (gus->gf1.dma_data_synth_last == block) {
+ gus->gf1.dma_data_synth =
+ gus->gf1.dma_data_synth_last = NULL;
+ } else {
+ gus->gf1.dma_data_synth = block->next;
+ }
+ } else {
+ block = NULL;
+ }
+ if (block) {
+ gus->gf1.dma_ack = block->ack;
+ gus->gf1.dma_private_data = block->private_data;
+ }
+ return block;
+}
+
+
+static void snd_gf1_dma_interrupt(struct snd_gus_card * gus)
+{
+ struct snd_gf1_dma_block *block;
+
+ snd_gf1_dma_ack(gus);
+ if (gus->gf1.dma_ack)
+ gus->gf1.dma_ack(gus, gus->gf1.dma_private_data);
+ spin_lock(&gus->dma_lock);
+ if (gus->gf1.dma_data_pcm == NULL &&
+ gus->gf1.dma_data_synth == NULL) {
+ gus->gf1.dma_ack = NULL;
+ gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER;
+ spin_unlock(&gus->dma_lock);
+ return;
+ }
+ block = snd_gf1_dma_next_block(gus);
+ spin_unlock(&gus->dma_lock);
+ if (!block)
+ return;
+ snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd);
+ kfree(block);
+#if 0
+ snd_printd(KERN_DEBUG "program dma (IRQ) - "
+ "addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n",
+ block->addr, block->buf_addr, block->count, block->cmd);
+#endif
+}
+
+int snd_gf1_dma_init(struct snd_gus_card * gus)
+{
+ mutex_lock(&gus->dma_mutex);
+ gus->gf1.dma_shared++;
+ if (gus->gf1.dma_shared > 1) {
+ mutex_unlock(&gus->dma_mutex);
+ return 0;
+ }
+ gus->gf1.interrupt_handler_dma_write = snd_gf1_dma_interrupt;
+ gus->gf1.dma_data_pcm =
+ gus->gf1.dma_data_pcm_last =
+ gus->gf1.dma_data_synth =
+ gus->gf1.dma_data_synth_last = NULL;
+ mutex_unlock(&gus->dma_mutex);
+ return 0;
+}
+
+int snd_gf1_dma_done(struct snd_gus_card * gus)
+{
+ struct snd_gf1_dma_block *block;
+
+ mutex_lock(&gus->dma_mutex);
+ gus->gf1.dma_shared--;
+ if (!gus->gf1.dma_shared) {
+ snd_dma_disable(gus->gf1.dma1);
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_WRITE);
+ snd_gf1_dma_ack(gus);
+ while ((block = gus->gf1.dma_data_pcm)) {
+ gus->gf1.dma_data_pcm = block->next;
+ kfree(block);
+ }
+ while ((block = gus->gf1.dma_data_synth)) {
+ gus->gf1.dma_data_synth = block->next;
+ kfree(block);
+ }
+ gus->gf1.dma_data_pcm_last =
+ gus->gf1.dma_data_synth_last = NULL;
+ }
+ mutex_unlock(&gus->dma_mutex);
+ return 0;
+}
+
+int snd_gf1_dma_transfer_block(struct snd_gus_card * gus,
+ struct snd_gf1_dma_block * __block,
+ int atomic,
+ int synth)
+{
+ unsigned long flags;
+ struct snd_gf1_dma_block *block;
+
+ block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL);
+ if (!block)
+ return -ENOMEM;
+
+ *block = *__block;
+ block->next = NULL;
+
+ snd_printdd("addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n",
+ block->addr, (long) block->buffer, block->count,
+ block->cmd);
+
+ snd_printdd("gus->gf1.dma_data_pcm_last = 0x%lx\n",
+ (long)gus->gf1.dma_data_pcm_last);
+ snd_printdd("gus->gf1.dma_data_pcm = 0x%lx\n",
+ (long)gus->gf1.dma_data_pcm);
+
+ spin_lock_irqsave(&gus->dma_lock, flags);
+ if (synth) {
+ if (gus->gf1.dma_data_synth_last) {
+ gus->gf1.dma_data_synth_last->next = block;
+ gus->gf1.dma_data_synth_last = block;
+ } else {
+ gus->gf1.dma_data_synth =
+ gus->gf1.dma_data_synth_last = block;
+ }
+ } else {
+ if (gus->gf1.dma_data_pcm_last) {
+ gus->gf1.dma_data_pcm_last->next = block;
+ gus->gf1.dma_data_pcm_last = block;
+ } else {
+ gus->gf1.dma_data_pcm =
+ gus->gf1.dma_data_pcm_last = block;
+ }
+ }
+ if (!(gus->gf1.dma_flags & SNDRV_GF1_DMA_TRIGGER)) {
+ gus->gf1.dma_flags |= SNDRV_GF1_DMA_TRIGGER;
+ block = snd_gf1_dma_next_block(gus);
+ spin_unlock_irqrestore(&gus->dma_lock, flags);
+ if (block == NULL)
+ return 0;
+ snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd);
+ kfree(block);
+ return 0;
+ }
+ spin_unlock_irqrestore(&gus->dma_lock, flags);
+ return 0;
+}
diff --git a/sound/isa/gus/gus_dram.c b/sound/isa/gus/gus_dram.c
new file mode 100644
index 0000000000..5cebc0119d
--- /dev/null
+++ b/sound/isa/gus/gus_dram.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * DRAM access routines
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+
+static int snd_gus_dram_poke(struct snd_gus_card *gus, char __user *_buffer,
+ unsigned int address, unsigned int size)
+{
+ unsigned long flags;
+ unsigned int size1, size2;
+ char buffer[256], *pbuffer;
+
+ while (size > 0) {
+ size1 = size > sizeof(buffer) ? sizeof(buffer) : size;
+ if (copy_from_user(buffer, _buffer, size1))
+ return -EFAULT;
+ if (gus->interwave) {
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+ snd_gf1_dram_addr(gus, address);
+ outsb(GUSP(gus, DRAM), buffer, size1);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ address += size1;
+ } else {
+ pbuffer = buffer;
+ size2 = size1;
+ while (size2--)
+ snd_gf1_poke(gus, address++, *pbuffer++);
+ }
+ size -= size1;
+ _buffer += size1;
+ }
+ return 0;
+}
+
+
+int snd_gus_dram_write(struct snd_gus_card *gus, char __user *buffer,
+ unsigned int address, unsigned int size)
+{
+ return snd_gus_dram_poke(gus, buffer, address, size);
+}
+
+static int snd_gus_dram_peek(struct snd_gus_card *gus, char __user *_buffer,
+ unsigned int address, unsigned int size,
+ int rom)
+{
+ unsigned long flags;
+ unsigned int size1, size2;
+ char buffer[256], *pbuffer;
+
+ while (size > 0) {
+ size1 = size > sizeof(buffer) ? sizeof(buffer) : size;
+ if (gus->interwave) {
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, rom ? 0x03 : 0x01);
+ snd_gf1_dram_addr(gus, address);
+ insb(GUSP(gus, DRAM), buffer, size1);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ address += size1;
+ } else {
+ pbuffer = buffer;
+ size2 = size1;
+ while (size2--)
+ *pbuffer++ = snd_gf1_peek(gus, address++);
+ }
+ if (copy_to_user(_buffer, buffer, size1))
+ return -EFAULT;
+ size -= size1;
+ _buffer += size1;
+ }
+ return 0;
+}
+
+int snd_gus_dram_read(struct snd_gus_card *gus, char __user *buffer,
+ unsigned int address, unsigned int size,
+ int rom)
+{
+ return snd_gus_dram_peek(gus, buffer, address, size, rom);
+}
diff --git a/sound/isa/gus/gus_io.c b/sound/isa/gus/gus_io.c
new file mode 100644
index 0000000000..fb7b5e2636
--- /dev/null
+++ b/sound/isa/gus/gus_io.c
@@ -0,0 +1,525 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * I/O routines for GF1/InterWave synthesizer chips
+ */
+
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+void snd_gf1_delay(struct snd_gus_card * gus)
+{
+ int i;
+
+ for (i = 0; i < 6; i++) {
+ mb();
+ inb(GUSP(gus, DRAM));
+ }
+}
+
+/*
+ * =======================================================================
+ */
+
+/*
+ * ok.. stop of control registers (wave & ramp) need some special things..
+ * big UltraClick (tm) elimination...
+ */
+
+static inline void __snd_gf1_ctrl_stop(struct snd_gus_card * gus, unsigned char reg)
+{
+ unsigned char value;
+
+ outb(reg | 0x80, gus->gf1.reg_regsel);
+ mb();
+ value = inb(gus->gf1.reg_data8);
+ mb();
+ outb(reg, gus->gf1.reg_regsel);
+ mb();
+ outb((value | 0x03) & ~(0x80 | 0x20), gus->gf1.reg_data8);
+ mb();
+}
+
+static inline void __snd_gf1_write8(struct snd_gus_card * gus,
+ unsigned char reg,
+ unsigned char data)
+{
+ outb(reg, gus->gf1.reg_regsel);
+ mb();
+ outb(data, gus->gf1.reg_data8);
+ mb();
+}
+
+static inline unsigned char __snd_gf1_look8(struct snd_gus_card * gus,
+ unsigned char reg)
+{
+ outb(reg, gus->gf1.reg_regsel);
+ mb();
+ return inb(gus->gf1.reg_data8);
+}
+
+static inline void __snd_gf1_write16(struct snd_gus_card * gus,
+ unsigned char reg, unsigned int data)
+{
+ outb(reg, gus->gf1.reg_regsel);
+ mb();
+ outw((unsigned short) data, gus->gf1.reg_data16);
+ mb();
+}
+
+static inline unsigned short __snd_gf1_look16(struct snd_gus_card * gus,
+ unsigned char reg)
+{
+ outb(reg, gus->gf1.reg_regsel);
+ mb();
+ return inw(gus->gf1.reg_data16);
+}
+
+static inline void __snd_gf1_adlib_write(struct snd_gus_card * gus,
+ unsigned char reg, unsigned char data)
+{
+ outb(reg, gus->gf1.reg_timerctrl);
+ inb(gus->gf1.reg_timerctrl);
+ inb(gus->gf1.reg_timerctrl);
+ outb(data, gus->gf1.reg_timerdata);
+ inb(gus->gf1.reg_timerctrl);
+ inb(gus->gf1.reg_timerctrl);
+}
+
+static inline void __snd_gf1_write_addr(struct snd_gus_card * gus, unsigned char reg,
+ unsigned int addr, int w_16bit)
+{
+ if (gus->gf1.enh_mode) {
+ if (w_16bit)
+ addr = ((addr >> 1) & ~0x0000000f) | (addr & 0x0000000f);
+ __snd_gf1_write8(gus, SNDRV_GF1_VB_UPPER_ADDRESS, (unsigned char) ((addr >> 26) & 0x03));
+ } else if (w_16bit)
+ addr = (addr & 0x00c0000f) | ((addr & 0x003ffff0) >> 1);
+ __snd_gf1_write16(gus, reg, (unsigned short) (addr >> 11));
+ __snd_gf1_write16(gus, reg + 1, (unsigned short) (addr << 5));
+}
+
+static inline unsigned int __snd_gf1_read_addr(struct snd_gus_card * gus,
+ unsigned char reg, short w_16bit)
+{
+ unsigned int res;
+
+ res = ((unsigned int) __snd_gf1_look16(gus, reg | 0x80) << 11) & 0xfff800;
+ res |= ((unsigned int) __snd_gf1_look16(gus, (reg + 1) | 0x80) >> 5) & 0x0007ff;
+ if (gus->gf1.enh_mode) {
+ res |= (unsigned int) __snd_gf1_look8(gus, SNDRV_GF1_VB_UPPER_ADDRESS | 0x80) << 26;
+ if (w_16bit)
+ res = ((res << 1) & 0xffffffe0) | (res & 0x0000000f);
+ } else if (w_16bit)
+ res = ((res & 0x001ffff0) << 1) | (res & 0x00c0000f);
+ return res;
+}
+
+
+/*
+ * =======================================================================
+ */
+
+void snd_gf1_ctrl_stop(struct snd_gus_card * gus, unsigned char reg)
+{
+ __snd_gf1_ctrl_stop(gus, reg);
+}
+
+void snd_gf1_write8(struct snd_gus_card * gus,
+ unsigned char reg,
+ unsigned char data)
+{
+ __snd_gf1_write8(gus, reg, data);
+}
+
+unsigned char snd_gf1_look8(struct snd_gus_card * gus, unsigned char reg)
+{
+ return __snd_gf1_look8(gus, reg);
+}
+
+void snd_gf1_write16(struct snd_gus_card * gus,
+ unsigned char reg,
+ unsigned int data)
+{
+ __snd_gf1_write16(gus, reg, data);
+}
+
+unsigned short snd_gf1_look16(struct snd_gus_card * gus, unsigned char reg)
+{
+ return __snd_gf1_look16(gus, reg);
+}
+
+void snd_gf1_adlib_write(struct snd_gus_card * gus,
+ unsigned char reg,
+ unsigned char data)
+{
+ __snd_gf1_adlib_write(gus, reg, data);
+}
+
+void snd_gf1_write_addr(struct snd_gus_card * gus, unsigned char reg,
+ unsigned int addr, short w_16bit)
+{
+ __snd_gf1_write_addr(gus, reg, addr, w_16bit);
+}
+
+unsigned int snd_gf1_read_addr(struct snd_gus_card * gus,
+ unsigned char reg,
+ short w_16bit)
+{
+ return __snd_gf1_read_addr(gus, reg, w_16bit);
+}
+
+/*
+
+ */
+
+void snd_gf1_i_ctrl_stop(struct snd_gus_card * gus, unsigned char reg)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ __snd_gf1_ctrl_stop(gus, reg);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+void snd_gf1_i_write8(struct snd_gus_card * gus,
+ unsigned char reg,
+ unsigned char data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ __snd_gf1_write8(gus, reg, data);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned char snd_gf1_i_look8(struct snd_gus_card * gus, unsigned char reg)
+{
+ unsigned long flags;
+ unsigned char res;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ res = __snd_gf1_look8(gus, reg);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return res;
+}
+
+void snd_gf1_i_write16(struct snd_gus_card * gus,
+ unsigned char reg,
+ unsigned int data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ __snd_gf1_write16(gus, reg, data);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned short snd_gf1_i_look16(struct snd_gus_card * gus, unsigned char reg)
+{
+ unsigned long flags;
+ unsigned short res;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ res = __snd_gf1_look16(gus, reg);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return res;
+}
+
+#if 0
+
+void snd_gf1_i_adlib_write(struct snd_gus_card * gus,
+ unsigned char reg,
+ unsigned char data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ __snd_gf1_adlib_write(gus, reg, data);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+void snd_gf1_i_write_addr(struct snd_gus_card * gus, unsigned char reg,
+ unsigned int addr, short w_16bit)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ __snd_gf1_write_addr(gus, reg, addr, w_16bit);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+#endif /* 0 */
+
+#ifdef CONFIG_SND_DEBUG
+static unsigned int snd_gf1_i_read_addr(struct snd_gus_card * gus,
+ unsigned char reg, short w_16bit)
+{
+ unsigned int res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ res = __snd_gf1_read_addr(gus, reg, w_16bit);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return res;
+}
+#endif
+
+/*
+
+ */
+
+void snd_gf1_dram_addr(struct snd_gus_card * gus, unsigned int addr)
+{
+ outb(0x43, gus->gf1.reg_regsel);
+ mb();
+ outw((unsigned short) addr, gus->gf1.reg_data16);
+ mb();
+ outb(0x44, gus->gf1.reg_regsel);
+ mb();
+ outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+ mb();
+}
+
+void snd_gf1_poke(struct snd_gus_card * gus, unsigned int addr, unsigned char data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+ mb();
+ outw((unsigned short) addr, gus->gf1.reg_data16);
+ mb();
+ outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+ mb();
+ outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+ mb();
+ outb(data, gus->gf1.reg_dram);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned char snd_gf1_peek(struct snd_gus_card * gus, unsigned int addr)
+{
+ unsigned long flags;
+ unsigned char res;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+ mb();
+ outw((unsigned short) addr, gus->gf1.reg_data16);
+ mb();
+ outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+ mb();
+ outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+ mb();
+ res = inb(gus->gf1.reg_dram);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return res;
+}
+
+#if 0
+
+void snd_gf1_pokew(struct snd_gus_card * gus, unsigned int addr, unsigned short data)
+{
+ unsigned long flags;
+
+#ifdef CONFIG_SND_DEBUG
+ if (!gus->interwave)
+ snd_printk(KERN_DEBUG "snd_gf1_pokew - GF1!!!\n");
+#endif
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+ mb();
+ outw((unsigned short) addr, gus->gf1.reg_data16);
+ mb();
+ outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+ mb();
+ outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+ mb();
+ outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel);
+ mb();
+ outw(data, gus->gf1.reg_data16);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned short snd_gf1_peekw(struct snd_gus_card * gus, unsigned int addr)
+{
+ unsigned long flags;
+ unsigned short res;
+
+#ifdef CONFIG_SND_DEBUG
+ if (!gus->interwave)
+ snd_printk(KERN_DEBUG "snd_gf1_peekw - GF1!!!\n");
+#endif
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+ mb();
+ outw((unsigned short) addr, gus->gf1.reg_data16);
+ mb();
+ outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+ mb();
+ outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+ mb();
+ outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel);
+ mb();
+ res = inw(gus->gf1.reg_data16);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return res;
+}
+
+void snd_gf1_dram_setmem(struct snd_gus_card * gus, unsigned int addr,
+ unsigned short value, unsigned int count)
+{
+ unsigned long port;
+ unsigned long flags;
+
+#ifdef CONFIG_SND_DEBUG
+ if (!gus->interwave)
+ snd_printk(KERN_DEBUG "snd_gf1_dram_setmem - GF1!!!\n");
+#endif
+ addr &= ~1;
+ count >>= 1;
+ port = GUSP(gus, GF1DATALOW);
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+ mb();
+ outw((unsigned short) addr, gus->gf1.reg_data16);
+ mb();
+ outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+ mb();
+ outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+ mb();
+ outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel);
+ while (count--)
+ outw(value, port);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+#endif /* 0 */
+
+void snd_gf1_select_active_voices(struct snd_gus_card * gus)
+{
+ unsigned short voices;
+
+ static const unsigned short voices_tbl[32 - 14 + 1] =
+ {
+ 44100, 41160, 38587, 36317, 34300, 32494, 30870, 29400, 28063, 26843,
+ 25725, 24696, 23746, 22866, 22050, 21289, 20580, 19916, 19293
+ };
+
+ voices = gus->gf1.active_voices;
+ if (voices > 32)
+ voices = 32;
+ if (voices < 14)
+ voices = 14;
+ if (gus->gf1.enh_mode)
+ voices = 32;
+ gus->gf1.active_voices = voices;
+ gus->gf1.playback_freq =
+ gus->gf1.enh_mode ? 44100 : voices_tbl[voices - 14];
+ if (!gus->gf1.enh_mode) {
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_ACTIVE_VOICES, 0xc0 | (voices - 1));
+ udelay(100);
+ }
+}
+
+#ifdef CONFIG_SND_DEBUG
+
+void snd_gf1_print_voice_registers(struct snd_gus_card * gus)
+{
+ unsigned char mode;
+ int voice, ctrl;
+
+ voice = gus->gf1.active_voice;
+ printk(KERN_INFO " -%i- GF1 voice ctrl, ramp ctrl = 0x%x, 0x%x\n", voice, ctrl = snd_gf1_i_read8(gus, 0), snd_gf1_i_read8(gus, 0x0d));
+ printk(KERN_INFO " -%i- GF1 frequency = 0x%x\n", voice, snd_gf1_i_read16(gus, 1));
+ printk(KERN_INFO " -%i- GF1 loop start, end = 0x%x (0x%x), 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 2, ctrl & 4), snd_gf1_i_read_addr(gus, 2, (ctrl & 4) ^ 4), snd_gf1_i_read_addr(gus, 4, ctrl & 4), snd_gf1_i_read_addr(gus, 4, (ctrl & 4) ^ 4));
+ printk(KERN_INFO " -%i- GF1 ramp start, end, rate = 0x%x, 0x%x, 0x%x\n", voice, snd_gf1_i_read8(gus, 7), snd_gf1_i_read8(gus, 8), snd_gf1_i_read8(gus, 6));
+ printk(KERN_INFO" -%i- GF1 volume = 0x%x\n", voice, snd_gf1_i_read16(gus, 9));
+ printk(KERN_INFO " -%i- GF1 position = 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 0x0a, ctrl & 4), snd_gf1_i_read_addr(gus, 0x0a, (ctrl & 4) ^ 4));
+ if (gus->interwave && snd_gf1_i_read8(gus, 0x19) & 0x01) { /* enhanced mode */
+ mode = snd_gf1_i_read8(gus, 0x15);
+ printk(KERN_INFO " -%i- GFA1 mode = 0x%x\n", voice, mode);
+ if (mode & 0x01) { /* Effect processor */
+ printk(KERN_INFO " -%i- GFA1 effect address = 0x%x\n", voice, snd_gf1_i_read_addr(gus, 0x11, ctrl & 4));
+ printk(KERN_INFO " -%i- GFA1 effect volume = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x16));
+ printk(KERN_INFO " -%i- GFA1 effect volume final = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x1d));
+ printk(KERN_INFO " -%i- GFA1 effect accumulator = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x14));
+ }
+ if (mode & 0x20) {
+ printk(KERN_INFO " -%i- GFA1 left offset = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x13), snd_gf1_i_read16(gus, 0x13) >> 4);
+ printk(KERN_INFO " -%i- GFA1 left offset final = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1c), snd_gf1_i_read16(gus, 0x1c) >> 4);
+ printk(KERN_INFO " -%i- GFA1 right offset = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x0c), snd_gf1_i_read16(gus, 0x0c) >> 4);
+ printk(KERN_INFO " -%i- GFA1 right offset final = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1b), snd_gf1_i_read16(gus, 0x1b) >> 4);
+ } else
+ printk(KERN_INFO " -%i- GF1 pan = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c));
+ } else
+ printk(KERN_INFO " -%i- GF1 pan = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c));
+}
+
+#if 0
+
+void snd_gf1_print_global_registers(struct snd_gus_card * gus)
+{
+ unsigned char global_mode = 0x00;
+
+ printk(KERN_INFO " -G- GF1 active voices = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ACTIVE_VOICES));
+ if (gus->interwave) {
+ global_mode = snd_gf1_i_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE);
+ printk(KERN_INFO " -G- GF1 global mode = 0x%x\n", global_mode);
+ }
+ if (global_mode & 0x02) /* LFO enabled? */
+ printk(KERN_INFO " -G- GF1 LFO base = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_LFO_BASE));
+ printk(KERN_INFO " -G- GF1 voices IRQ read = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VOICES_IRQ_READ));
+ printk(KERN_INFO " -G- GF1 DRAM DMA control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL));
+ printk(KERN_INFO " -G- GF1 DRAM DMA high/low = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW));
+ printk(KERN_INFO " -G- GF1 DRAM IO high/low = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_IO_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_IO_LOW));
+ if (!gus->interwave)
+ printk(KERN_INFO " -G- GF1 record DMA control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL));
+ printk(KERN_INFO " -G- GF1 DRAM IO 16 = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_DRAM_IO16));
+ if (gus->gf1.enh_mode) {
+ printk(KERN_INFO " -G- GFA1 memory config = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG));
+ printk(KERN_INFO " -G- GFA1 memory control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MEMORY_CONTROL));
+ printk(KERN_INFO " -G- GFA1 FIFO record base = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR));
+ printk(KERN_INFO " -G- GFA1 FIFO playback base = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR));
+ printk(KERN_INFO " -G- GFA1 interleave control = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_INTERLEAVE));
+ }
+}
+
+void snd_gf1_print_setup_registers(struct snd_gus_card * gus)
+{
+ printk(KERN_INFO " -S- mix control = 0x%x\n", inb(GUSP(gus, MIXCNTRLREG)));
+ printk(KERN_INFO " -S- IRQ status = 0x%x\n", inb(GUSP(gus, IRQSTAT)));
+ printk(KERN_INFO " -S- timer control = 0x%x\n", inb(GUSP(gus, TIMERCNTRL)));
+ printk(KERN_INFO " -S- timer data = 0x%x\n", inb(GUSP(gus, TIMERDATA)));
+ printk(KERN_INFO " -S- status read = 0x%x\n", inb(GUSP(gus, REGCNTRLS)));
+ printk(KERN_INFO " -S- Sound Blaster control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL));
+ printk(KERN_INFO " -S- AdLib timer 1/2 = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1), snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2));
+ printk(KERN_INFO " -S- reset = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET));
+ if (gus->interwave) {
+ printk(KERN_INFO " -S- compatibility = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_COMPATIBILITY));
+ printk(KERN_INFO " -S- decode control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DECODE_CONTROL));
+ printk(KERN_INFO " -S- version number = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER));
+ printk(KERN_INFO " -S- MPU-401 emul. control A/B = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A), snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B));
+ printk(KERN_INFO " -S- emulation IRQ = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_EMULATION_IRQ));
+ }
+}
+
+void snd_gf1_peek_print_block(struct snd_gus_card * gus, unsigned int addr, int count, int w_16bit)
+{
+ if (!w_16bit) {
+ while (count-- > 0)
+ printk(count > 0 ? "%02x:" : "%02x", snd_gf1_peek(gus, addr++));
+ } else {
+ while (count-- > 0) {
+ printk(count > 0 ? "%04x:" : "%04x", snd_gf1_peek(gus, addr) | (snd_gf1_peek(gus, addr + 1) << 8));
+ addr += 2;
+ }
+ }
+}
+
+#endif /* 0 */
+
+#endif
diff --git a/sound/isa/gus/gus_irq.c b/sound/isa/gus/gus_irq.c
new file mode 100644
index 0000000000..226b8438aa
--- /dev/null
+++ b/sound/isa/gus/gus_irq.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Routine for IRQ handling from GF1/InterWave chip
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/gus.h>
+
+#ifdef CONFIG_SND_DEBUG
+#define STAT_ADD(x) ((x)++)
+#else
+#define STAT_ADD(x) while (0) { ; }
+#endif
+
+irqreturn_t snd_gus_interrupt(int irq, void *dev_id)
+{
+ struct snd_gus_card * gus = dev_id;
+ unsigned char status;
+ int loop = 100;
+ int handled = 0;
+
+__again:
+ status = inb(gus->gf1.reg_irqstat);
+ if (status == 0)
+ return IRQ_RETVAL(handled);
+ handled = 1;
+ /* snd_printk(KERN_DEBUG "IRQ: status = 0x%x\n", status); */
+ if (status & 0x02) {
+ STAT_ADD(gus->gf1.interrupt_stat_midi_in);
+ if (gus->gf1.interrupt_handler_midi_in)
+ gus->gf1.interrupt_handler_midi_in(gus);
+ }
+ if (status & 0x01) {
+ STAT_ADD(gus->gf1.interrupt_stat_midi_out);
+ if (gus->gf1.interrupt_handler_midi_out)
+ gus->gf1.interrupt_handler_midi_out(gus);
+ }
+ if (status & (0x20 | 0x40)) {
+ unsigned int already, _current_;
+ unsigned char voice_status, voice;
+ struct snd_gus_voice *pvoice;
+
+ already = 0;
+ while (((voice_status = snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ)) & 0xc0) != 0xc0) {
+ voice = voice_status & 0x1f;
+ _current_ = 1 << voice;
+ if (already & _current_)
+ continue; /* multi request */
+ already |= _current_; /* mark request */
+#if 0
+ printk(KERN_DEBUG "voice = %i, voice_status = 0x%x, "
+ "voice_verify = %i\n",
+ voice, voice_status, inb(GUSP(gus, GF1PAGE)));
+#endif
+ pvoice = &gus->gf1.voices[voice];
+ if (pvoice->use) {
+ if (!(voice_status & 0x80)) { /* voice position IRQ */
+ STAT_ADD(pvoice->interrupt_stat_wave);
+ pvoice->handler_wave(gus, pvoice);
+ }
+ if (!(voice_status & 0x40)) { /* volume ramp IRQ */
+ STAT_ADD(pvoice->interrupt_stat_volume);
+ pvoice->handler_volume(gus, pvoice);
+ }
+ } else {
+ STAT_ADD(gus->gf1.interrupt_stat_voice_lost);
+ snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+ snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+ }
+ }
+ }
+ if (status & 0x04) {
+ STAT_ADD(gus->gf1.interrupt_stat_timer1);
+ if (gus->gf1.interrupt_handler_timer1)
+ gus->gf1.interrupt_handler_timer1(gus);
+ }
+ if (status & 0x08) {
+ STAT_ADD(gus->gf1.interrupt_stat_timer2);
+ if (gus->gf1.interrupt_handler_timer2)
+ gus->gf1.interrupt_handler_timer2(gus);
+ }
+ if (status & 0x80) {
+ if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL) & 0x40) {
+ STAT_ADD(gus->gf1.interrupt_stat_dma_write);
+ if (gus->gf1.interrupt_handler_dma_write)
+ gus->gf1.interrupt_handler_dma_write(gus);
+ }
+ if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL) & 0x40) {
+ STAT_ADD(gus->gf1.interrupt_stat_dma_read);
+ if (gus->gf1.interrupt_handler_dma_read)
+ gus->gf1.interrupt_handler_dma_read(gus);
+ }
+ }
+ if (--loop > 0)
+ goto __again;
+ return IRQ_NONE;
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gus_irq_info_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_gus_card *gus;
+ struct snd_gus_voice *pvoice;
+ int idx;
+
+ gus = entry->private_data;
+ snd_iprintf(buffer, "midi out = %u\n", gus->gf1.interrupt_stat_midi_out);
+ snd_iprintf(buffer, "midi in = %u\n", gus->gf1.interrupt_stat_midi_in);
+ snd_iprintf(buffer, "timer1 = %u\n", gus->gf1.interrupt_stat_timer1);
+ snd_iprintf(buffer, "timer2 = %u\n", gus->gf1.interrupt_stat_timer2);
+ snd_iprintf(buffer, "dma write = %u\n", gus->gf1.interrupt_stat_dma_write);
+ snd_iprintf(buffer, "dma read = %u\n", gus->gf1.interrupt_stat_dma_read);
+ snd_iprintf(buffer, "voice lost = %u\n", gus->gf1.interrupt_stat_voice_lost);
+ for (idx = 0; idx < 32; idx++) {
+ pvoice = &gus->gf1.voices[idx];
+ snd_iprintf(buffer, "voice %i: wave = %u, volume = %u\n",
+ idx,
+ pvoice->interrupt_stat_wave,
+ pvoice->interrupt_stat_volume);
+ }
+}
+
+void snd_gus_irq_profile_init(struct snd_gus_card *gus)
+{
+ snd_card_ro_proc_new(gus->card, "gusirq", gus, snd_gus_irq_info_read);
+}
+
+#endif
diff --git a/sound/isa/gus/gus_main.c b/sound/isa/gus/gus_main.c
new file mode 100644
index 0000000000..3b46490271
--- /dev/null
+++ b/sound/isa/gus/gus_main.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Routines for Gravis UltraSound soundcards
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/control.h>
+
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for Gravis UltraSound soundcards");
+MODULE_LICENSE("GPL");
+
+static int snd_gus_init_dma_irq(struct snd_gus_card * gus, int latches);
+
+int snd_gus_use_inc(struct snd_gus_card * gus)
+{
+ if (!try_module_get(gus->card->module))
+ return 0;
+ return 1;
+}
+
+void snd_gus_use_dec(struct snd_gus_card * gus)
+{
+ module_put(gus->card->module);
+}
+
+static int snd_gus_joystick_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 31;
+ return 0;
+}
+
+static int snd_gus_joystick_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = gus->joystick_dac & 31;
+ return 0;
+}
+
+static int snd_gus_joystick_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned char nval;
+
+ nval = ucontrol->value.integer.value[0] & 31;
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ change = gus->joystick_dac != nval;
+ gus->joystick_dac = nval;
+ snd_gf1_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_gus_joystick_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "Joystick Speed",
+ .info = snd_gus_joystick_info,
+ .get = snd_gus_joystick_get,
+ .put = snd_gus_joystick_put
+};
+
+static void snd_gus_init_control(struct snd_gus_card *gus)
+{
+ if (!gus->ace_flag)
+ snd_ctl_add(gus->card, snd_ctl_new1(&snd_gus_joystick_control, gus));
+}
+
+/*
+ *
+ */
+
+static int snd_gus_free(struct snd_gus_card *gus)
+{
+ if (gus->gf1.res_port2 == NULL)
+ goto __hw_end;
+ snd_gf1_stop(gus);
+ snd_gus_init_dma_irq(gus, 0);
+ __hw_end:
+ release_and_free_resource(gus->gf1.res_port1);
+ release_and_free_resource(gus->gf1.res_port2);
+ if (gus->gf1.irq >= 0)
+ free_irq(gus->gf1.irq, (void *) gus);
+ if (gus->gf1.dma1 >= 0) {
+ disable_dma(gus->gf1.dma1);
+ free_dma(gus->gf1.dma1);
+ }
+ if (!gus->equal_dma && gus->gf1.dma2 >= 0) {
+ disable_dma(gus->gf1.dma2);
+ free_dma(gus->gf1.dma2);
+ }
+ kfree(gus);
+ return 0;
+}
+
+static int snd_gus_dev_free(struct snd_device *device)
+{
+ struct snd_gus_card *gus = device->device_data;
+ return snd_gus_free(gus);
+}
+
+int snd_gus_create(struct snd_card *card,
+ unsigned long port,
+ int irq, int dma1, int dma2,
+ int timer_dev,
+ int voices,
+ int pcm_channels,
+ int effect,
+ struct snd_gus_card **rgus)
+{
+ struct snd_gus_card *gus;
+ int err;
+ static const struct snd_device_ops ops = {
+ .dev_free = snd_gus_dev_free,
+ };
+
+ *rgus = NULL;
+ gus = kzalloc(sizeof(*gus), GFP_KERNEL);
+ if (gus == NULL)
+ return -ENOMEM;
+ spin_lock_init(&gus->reg_lock);
+ spin_lock_init(&gus->voice_alloc);
+ spin_lock_init(&gus->active_voice_lock);
+ spin_lock_init(&gus->event_lock);
+ spin_lock_init(&gus->dma_lock);
+ spin_lock_init(&gus->pcm_volume_level_lock);
+ spin_lock_init(&gus->uart_cmd_lock);
+ mutex_init(&gus->dma_mutex);
+ gus->gf1.irq = -1;
+ gus->gf1.dma1 = -1;
+ gus->gf1.dma2 = -1;
+ gus->card = card;
+ gus->gf1.port = port;
+ /* fill register variables for speedup */
+ gus->gf1.reg_page = GUSP(gus, GF1PAGE);
+ gus->gf1.reg_regsel = GUSP(gus, GF1REGSEL);
+ gus->gf1.reg_data8 = GUSP(gus, GF1DATAHIGH);
+ gus->gf1.reg_data16 = GUSP(gus, GF1DATALOW);
+ gus->gf1.reg_irqstat = GUSP(gus, IRQSTAT);
+ gus->gf1.reg_dram = GUSP(gus, DRAM);
+ gus->gf1.reg_timerctrl = GUSP(gus, TIMERCNTRL);
+ gus->gf1.reg_timerdata = GUSP(gus, TIMERDATA);
+ /* allocate resources */
+ gus->gf1.res_port1 = request_region(port, 16, "GUS GF1 (Adlib/SB)");
+ if (!gus->gf1.res_port1) {
+ snd_printk(KERN_ERR "gus: can't grab SB port 0x%lx\n", port);
+ snd_gus_free(gus);
+ return -EBUSY;
+ }
+ gus->gf1.res_port2 = request_region(port + 0x100, 12, "GUS GF1 (Synth)");
+ if (!gus->gf1.res_port2) {
+ snd_printk(KERN_ERR "gus: can't grab synth port 0x%lx\n", port + 0x100);
+ snd_gus_free(gus);
+ return -EBUSY;
+ }
+ if (irq >= 0 && request_irq(irq, snd_gus_interrupt, 0, "GUS GF1", (void *) gus)) {
+ snd_printk(KERN_ERR "gus: can't grab irq %d\n", irq);
+ snd_gus_free(gus);
+ return -EBUSY;
+ }
+ gus->gf1.irq = irq;
+ card->sync_irq = irq;
+ if (request_dma(dma1, "GUS - 1")) {
+ snd_printk(KERN_ERR "gus: can't grab DMA1 %d\n", dma1);
+ snd_gus_free(gus);
+ return -EBUSY;
+ }
+ gus->gf1.dma1 = dma1;
+ if (dma2 >= 0 && dma1 != dma2) {
+ if (request_dma(dma2, "GUS - 2")) {
+ snd_printk(KERN_ERR "gus: can't grab DMA2 %d\n", dma2);
+ snd_gus_free(gus);
+ return -EBUSY;
+ }
+ gus->gf1.dma2 = dma2;
+ } else {
+ gus->gf1.dma2 = gus->gf1.dma1;
+ gus->equal_dma = 1;
+ }
+ gus->timer_dev = timer_dev;
+ if (voices < 14)
+ voices = 14;
+ if (voices > 32)
+ voices = 32;
+ if (pcm_channels < 0)
+ pcm_channels = 0;
+ if (pcm_channels > 8)
+ pcm_channels = 8;
+ pcm_channels++;
+ pcm_channels &= ~1;
+ gus->gf1.effect = effect ? 1 : 0;
+ gus->gf1.active_voices = voices;
+ gus->gf1.pcm_channels = pcm_channels;
+ gus->gf1.volume_ramp = 25;
+ gus->gf1.smooth_pan = 1;
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, gus, &ops);
+ if (err < 0) {
+ snd_gus_free(gus);
+ return err;
+ }
+ *rgus = gus;
+ return 0;
+}
+
+/*
+ * Memory detection routine for plain GF1 soundcards
+ */
+
+static int snd_gus_detect_memory(struct snd_gus_card * gus)
+{
+ int l, idx, local;
+ unsigned char d;
+
+ snd_gf1_poke(gus, 0L, 0xaa);
+ snd_gf1_poke(gus, 1L, 0x55);
+ if (snd_gf1_peek(gus, 0L) != 0xaa || snd_gf1_peek(gus, 1L) != 0x55) {
+ snd_printk(KERN_ERR "plain GF1 card at 0x%lx without onboard DRAM?\n", gus->gf1.port);
+ return -ENOMEM;
+ }
+ for (idx = 1, d = 0xab; idx < 4; idx++, d++) {
+ local = idx << 18;
+ snd_gf1_poke(gus, local, d);
+ snd_gf1_poke(gus, local + 1, d + 1);
+ if (snd_gf1_peek(gus, local) != d ||
+ snd_gf1_peek(gus, local + 1) != d + 1 ||
+ snd_gf1_peek(gus, 0L) != 0xaa)
+ break;
+ }
+#if 1
+ gus->gf1.memory = idx << 18;
+#else
+ gus->gf1.memory = 256 * 1024;
+#endif
+ for (l = 0, local = gus->gf1.memory; l < 4; l++, local -= 256 * 1024) {
+ gus->gf1.mem_alloc.banks_8[l].address =
+ gus->gf1.mem_alloc.banks_8[l].size = 0;
+ gus->gf1.mem_alloc.banks_16[l].address = l << 18;
+ gus->gf1.mem_alloc.banks_16[l].size = local > 0 ? 256 * 1024 : 0;
+ }
+ gus->gf1.mem_alloc.banks_8[0].size = gus->gf1.memory;
+ return 0; /* some memory were detected */
+}
+
+static int snd_gus_init_dma_irq(struct snd_gus_card * gus, int latches)
+{
+ struct snd_card *card;
+ unsigned long flags;
+ int irq, dma1, dma2;
+ static const unsigned char irqs[16] =
+ {0, 0, 1, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7};
+ static const unsigned char dmas[8] =
+ {6, 1, 0, 2, 0, 3, 4, 5};
+
+ if (snd_BUG_ON(!gus))
+ return -EINVAL;
+ card = gus->card;
+ if (snd_BUG_ON(!card))
+ return -EINVAL;
+
+ gus->mix_cntrl_reg &= 0xf8;
+ gus->mix_cntrl_reg |= 0x01; /* disable MIC, LINE IN, enable LINE OUT */
+ if (gus->codec_flag || gus->ess_flag) {
+ gus->mix_cntrl_reg &= ~1; /* enable LINE IN */
+ gus->mix_cntrl_reg |= 4; /* enable MIC */
+ }
+ dma1 = gus->gf1.dma1;
+ dma1 = abs(dma1);
+ dma1 = dmas[dma1 & 7];
+ dma2 = gus->gf1.dma2;
+ dma2 = abs(dma2);
+ dma2 = dmas[dma2 & 7];
+ dma1 |= gus->equal_dma ? 0x40 : (dma2 << 3);
+
+ if ((dma1 & 7) == 0 || (dma2 & 7) == 0) {
+ snd_printk(KERN_ERR "Error! DMA isn't defined.\n");
+ return -EINVAL;
+ }
+ irq = gus->gf1.irq;
+ irq = abs(irq);
+ irq = irqs[irq & 0x0f];
+ if (irq == 0) {
+ snd_printk(KERN_ERR "Error! IRQ isn't defined.\n");
+ return -EINVAL;
+ }
+ irq |= 0x40;
+#if 0
+ card->mixer.mix_ctrl_reg |= 0x10;
+#endif
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(5, GUSP(gus, REGCNTRLS));
+ outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+ outb(0x00, GUSP(gus, IRQDMACNTRLREG));
+ outb(0, GUSP(gus, REGCNTRLS));
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+ udelay(100);
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+ outb(dma1, GUSP(gus, IRQDMACNTRLREG));
+ if (latches) {
+ outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+ outb(irq, GUSP(gus, IRQDMACNTRLREG));
+ }
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+ udelay(100);
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+ outb(dma1, GUSP(gus, IRQDMACNTRLREG));
+ if (latches) {
+ outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+ outb(irq, GUSP(gus, IRQDMACNTRLREG));
+ }
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+ snd_gf1_delay(gus);
+
+ if (latches)
+ gus->mix_cntrl_reg |= 0x08; /* enable latches */
+ else
+ gus->mix_cntrl_reg &= ~0x08; /* disable latches */
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+ outb(0, GUSP(gus, GF1PAGE));
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+ return 0;
+}
+
+static int snd_gus_check_version(struct snd_gus_card * gus)
+{
+ unsigned long flags;
+ unsigned char val, rev;
+ struct snd_card *card;
+
+ card = gus->card;
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(0x20, GUSP(gus, REGCNTRLS));
+ val = inb(GUSP(gus, REGCNTRLS));
+ rev = inb(GUSP(gus, BOARDVERSION));
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ snd_printdd("GF1 [0x%lx] init - val = 0x%x, rev = 0x%x\n", gus->gf1.port, val, rev);
+ strcpy(card->driver, "GUS");
+ strcpy(card->longname, "Gravis UltraSound Classic (2.4)");
+ if ((val != 255 && (val & 0x06)) || (rev >= 5 && rev != 255)) {
+ if (rev >= 5 && rev <= 9) {
+ gus->ics_flag = 1;
+ if (rev == 5)
+ gus->ics_flipped = 1;
+ card->longname[27] = '3';
+ card->longname[29] = rev == 5 ? '5' : '7';
+ }
+ if (rev >= 10 && rev != 255) {
+ if (rev >= 10 && rev <= 11) {
+ strcpy(card->driver, "GUS MAX");
+ strcpy(card->longname, "Gravis UltraSound MAX");
+ gus->max_flag = 1;
+ } else if (rev == 0x30) {
+ strcpy(card->driver, "GUS ACE");
+ strcpy(card->longname, "Gravis UltraSound Ace");
+ gus->ace_flag = 1;
+ } else if (rev == 0x50) {
+ strcpy(card->driver, "GUS Extreme");
+ strcpy(card->longname, "Gravis UltraSound Extreme");
+ gus->ess_flag = 1;
+ } else {
+ snd_printk(KERN_ERR "unknown GF1 revision number at 0x%lx - 0x%x (0x%x)\n", gus->gf1.port, rev, val);
+ snd_printk(KERN_ERR " please - report to <perex@perex.cz>\n");
+ }
+ }
+ }
+ strscpy(card->shortname, card->longname, sizeof(card->shortname));
+ gus->uart_enable = 1; /* standard GUSes doesn't have midi uart trouble */
+ snd_gus_init_control(gus);
+ return 0;
+}
+
+int snd_gus_initialize(struct snd_gus_card *gus)
+{
+ int err;
+
+ if (!gus->interwave) {
+ err = snd_gus_check_version(gus);
+ if (err < 0) {
+ snd_printk(KERN_ERR "version check failed\n");
+ return err;
+ }
+ err = snd_gus_detect_memory(gus);
+ if (err < 0)
+ return err;
+ }
+ err = snd_gus_init_dma_irq(gus, 1);
+ if (err < 0)
+ return err;
+ snd_gf1_start(gus);
+ gus->initialized = 1;
+ return 0;
+}
+
+ /* gus_io.c */
+EXPORT_SYMBOL(snd_gf1_delay);
+EXPORT_SYMBOL(snd_gf1_write8);
+EXPORT_SYMBOL(snd_gf1_look8);
+EXPORT_SYMBOL(snd_gf1_write16);
+EXPORT_SYMBOL(snd_gf1_look16);
+EXPORT_SYMBOL(snd_gf1_i_write8);
+EXPORT_SYMBOL(snd_gf1_i_look8);
+EXPORT_SYMBOL(snd_gf1_i_look16);
+EXPORT_SYMBOL(snd_gf1_dram_addr);
+EXPORT_SYMBOL(snd_gf1_write_addr);
+EXPORT_SYMBOL(snd_gf1_poke);
+EXPORT_SYMBOL(snd_gf1_peek);
+ /* gus_reset.c */
+EXPORT_SYMBOL(snd_gf1_alloc_voice);
+EXPORT_SYMBOL(snd_gf1_free_voice);
+EXPORT_SYMBOL(snd_gf1_ctrl_stop);
+EXPORT_SYMBOL(snd_gf1_stop_voice);
+ /* gus_mixer.c */
+EXPORT_SYMBOL(snd_gf1_new_mixer);
+ /* gus_pcm.c */
+EXPORT_SYMBOL(snd_gf1_pcm_new);
+ /* gus.c */
+EXPORT_SYMBOL(snd_gus_use_inc);
+EXPORT_SYMBOL(snd_gus_use_dec);
+EXPORT_SYMBOL(snd_gus_create);
+EXPORT_SYMBOL(snd_gus_initialize);
+ /* gus_irq.c */
+EXPORT_SYMBOL(snd_gus_interrupt);
+ /* gus_uart.c */
+EXPORT_SYMBOL(snd_gf1_rawmidi_new);
+ /* gus_dram.c */
+EXPORT_SYMBOL(snd_gus_dram_write);
+EXPORT_SYMBOL(snd_gus_dram_read);
+ /* gus_volume.c */
+EXPORT_SYMBOL(snd_gf1_lvol_to_gvol_raw);
+EXPORT_SYMBOL(snd_gf1_translate_freq);
+ /* gus_mem.c */
+EXPORT_SYMBOL(snd_gf1_mem_alloc);
+EXPORT_SYMBOL(snd_gf1_mem_xfree);
+EXPORT_SYMBOL(snd_gf1_mem_free);
+EXPORT_SYMBOL(snd_gf1_mem_lock);
diff --git a/sound/isa/gus/gus_mem.c b/sound/isa/gus/gus_mem.c
new file mode 100644
index 0000000000..3e56c01c45
--- /dev/null
+++ b/sound/isa/gus/gus_mem.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * GUS's memory allocation routines / bottom layer
+ */
+
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gf1_mem_info_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer);
+#endif
+
+void snd_gf1_mem_lock(struct snd_gf1_mem * alloc, int xup)
+{
+ if (!xup) {
+ mutex_lock(&alloc->memory_mutex);
+ } else {
+ mutex_unlock(&alloc->memory_mutex);
+ }
+}
+
+static struct snd_gf1_mem_block *
+snd_gf1_mem_xalloc(struct snd_gf1_mem *alloc, struct snd_gf1_mem_block *block,
+ const char *name)
+{
+ struct snd_gf1_mem_block *pblock, *nblock;
+
+ nblock = kmalloc(sizeof(struct snd_gf1_mem_block), GFP_KERNEL);
+ if (nblock == NULL)
+ return NULL;
+ *nblock = *block;
+ nblock->name = kstrdup(name, GFP_KERNEL);
+ if (!nblock->name) {
+ kfree(nblock);
+ return NULL;
+ }
+
+ pblock = alloc->first;
+ while (pblock) {
+ if (pblock->ptr > nblock->ptr) {
+ nblock->prev = pblock->prev;
+ nblock->next = pblock;
+ pblock->prev = nblock;
+ if (pblock == alloc->first)
+ alloc->first = nblock;
+ else
+ nblock->prev->next = nblock;
+ mutex_unlock(&alloc->memory_mutex);
+ return nblock;
+ }
+ pblock = pblock->next;
+ }
+ nblock->next = NULL;
+ if (alloc->last == NULL) {
+ nblock->prev = NULL;
+ alloc->first = alloc->last = nblock;
+ } else {
+ nblock->prev = alloc->last;
+ alloc->last->next = nblock;
+ alloc->last = nblock;
+ }
+ return nblock;
+}
+
+int snd_gf1_mem_xfree(struct snd_gf1_mem * alloc, struct snd_gf1_mem_block * block)
+{
+ if (block->share) { /* ok.. shared block */
+ block->share--;
+ mutex_unlock(&alloc->memory_mutex);
+ return 0;
+ }
+ if (alloc->first == block) {
+ alloc->first = block->next;
+ if (block->next)
+ block->next->prev = NULL;
+ } else {
+ block->prev->next = block->next;
+ if (block->next)
+ block->next->prev = block->prev;
+ }
+ if (alloc->last == block) {
+ alloc->last = block->prev;
+ if (block->prev)
+ block->prev->next = NULL;
+ } else {
+ block->next->prev = block->prev;
+ if (block->prev)
+ block->prev->next = block->next;
+ }
+ kfree(block->name);
+ kfree(block);
+ return 0;
+}
+
+static struct snd_gf1_mem_block *snd_gf1_mem_look(struct snd_gf1_mem * alloc,
+ unsigned int address)
+{
+ struct snd_gf1_mem_block *block;
+
+ for (block = alloc->first; block; block = block->next) {
+ if (block->ptr == address) {
+ return block;
+ }
+ }
+ return NULL;
+}
+
+static struct snd_gf1_mem_block *snd_gf1_mem_share(struct snd_gf1_mem * alloc,
+ unsigned int *share_id)
+{
+ struct snd_gf1_mem_block *block;
+
+ if (!share_id[0] && !share_id[1] &&
+ !share_id[2] && !share_id[3])
+ return NULL;
+ for (block = alloc->first; block; block = block->next)
+ if (!memcmp(share_id, block->share_id,
+ sizeof(block->share_id)))
+ return block;
+ return NULL;
+}
+
+static int snd_gf1_mem_find(struct snd_gf1_mem * alloc,
+ struct snd_gf1_mem_block * block,
+ unsigned int size, int w_16, int align)
+{
+ struct snd_gf1_bank_info *info = w_16 ? alloc->banks_16 : alloc->banks_8;
+ unsigned int idx, boundary;
+ int size1;
+ struct snd_gf1_mem_block *pblock;
+ unsigned int ptr1, ptr2;
+
+ if (w_16 && align < 2)
+ align = 2;
+ block->flags = w_16 ? SNDRV_GF1_MEM_BLOCK_16BIT : 0;
+ block->owner = SNDRV_GF1_MEM_OWNER_DRIVER;
+ block->share = 0;
+ block->share_id[0] = block->share_id[1] =
+ block->share_id[2] = block->share_id[3] = 0;
+ block->name = NULL;
+ block->prev = block->next = NULL;
+ for (pblock = alloc->first, idx = 0; pblock; pblock = pblock->next) {
+ while (pblock->ptr >= (boundary = info[idx].address + info[idx].size))
+ idx++;
+ while (pblock->ptr + pblock->size >= (boundary = info[idx].address + info[idx].size))
+ idx++;
+ ptr2 = boundary;
+ if (pblock->next) {
+ if (pblock->ptr + pblock->size == pblock->next->ptr)
+ continue;
+ if (pblock->next->ptr < boundary)
+ ptr2 = pblock->next->ptr;
+ }
+ ptr1 = ALIGN(pblock->ptr + pblock->size, align);
+ if (ptr1 >= ptr2)
+ continue;
+ size1 = ptr2 - ptr1;
+ if ((int)size <= size1) {
+ block->ptr = ptr1;
+ block->size = size;
+ return 0;
+ }
+ }
+ while (++idx < 4) {
+ if (size <= info[idx].size) {
+ /* I assume that bank address is already aligned.. */
+ block->ptr = info[idx].address;
+ block->size = size;
+ return 0;
+ }
+ }
+ return -ENOMEM;
+}
+
+struct snd_gf1_mem_block *snd_gf1_mem_alloc(struct snd_gf1_mem * alloc, int owner,
+ char *name, int size, int w_16, int align,
+ unsigned int *share_id)
+{
+ struct snd_gf1_mem_block block, *nblock;
+
+ snd_gf1_mem_lock(alloc, 0);
+ if (share_id != NULL) {
+ nblock = snd_gf1_mem_share(alloc, share_id);
+ if (nblock != NULL) {
+ if (size != (int)nblock->size) {
+ /* TODO: remove in the future */
+ snd_printk(KERN_ERR "snd_gf1_mem_alloc - share: sizes differ\n");
+ goto __std;
+ }
+ nblock->share++;
+ snd_gf1_mem_lock(alloc, 1);
+ return NULL;
+ }
+ }
+ __std:
+ if (snd_gf1_mem_find(alloc, &block, size, w_16, align) < 0) {
+ snd_gf1_mem_lock(alloc, 1);
+ return NULL;
+ }
+ if (share_id != NULL)
+ memcpy(&block.share_id, share_id, sizeof(block.share_id));
+ block.owner = owner;
+ nblock = snd_gf1_mem_xalloc(alloc, &block, name);
+ snd_gf1_mem_lock(alloc, 1);
+ return nblock;
+}
+
+int snd_gf1_mem_free(struct snd_gf1_mem * alloc, unsigned int address)
+{
+ int result;
+ struct snd_gf1_mem_block *block;
+
+ snd_gf1_mem_lock(alloc, 0);
+ block = snd_gf1_mem_look(alloc, address);
+ if (block) {
+ result = snd_gf1_mem_xfree(alloc, block);
+ snd_gf1_mem_lock(alloc, 1);
+ return result;
+ }
+ snd_gf1_mem_lock(alloc, 1);
+ return -EINVAL;
+}
+
+int snd_gf1_mem_init(struct snd_gus_card * gus)
+{
+ struct snd_gf1_mem *alloc;
+ struct snd_gf1_mem_block block;
+
+ alloc = &gus->gf1.mem_alloc;
+ mutex_init(&alloc->memory_mutex);
+ alloc->first = alloc->last = NULL;
+ if (!gus->gf1.memory)
+ return 0;
+
+ memset(&block, 0, sizeof(block));
+ block.owner = SNDRV_GF1_MEM_OWNER_DRIVER;
+ if (gus->gf1.enh_mode) {
+ block.ptr = 0;
+ block.size = 1024;
+ if (!snd_gf1_mem_xalloc(alloc, &block, "InterWave LFOs"))
+ return -ENOMEM;
+ }
+ block.ptr = gus->gf1.default_voice_address;
+ block.size = 4;
+ if (!snd_gf1_mem_xalloc(alloc, &block, "Voice default (NULL's)"))
+ return -ENOMEM;
+#ifdef CONFIG_SND_DEBUG
+ snd_card_ro_proc_new(gus->card, "gusmem", gus, snd_gf1_mem_info_read);
+#endif
+ return 0;
+}
+
+int snd_gf1_mem_done(struct snd_gus_card * gus)
+{
+ struct snd_gf1_mem *alloc;
+ struct snd_gf1_mem_block *block, *nblock;
+
+ alloc = &gus->gf1.mem_alloc;
+ block = alloc->first;
+ while (block) {
+ nblock = block->next;
+ snd_gf1_mem_xfree(alloc, block);
+ block = nblock;
+ }
+ return 0;
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gf1_mem_info_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_gus_card *gus;
+ struct snd_gf1_mem *alloc;
+ struct snd_gf1_mem_block *block;
+ unsigned int total, used;
+ int i;
+
+ gus = entry->private_data;
+ alloc = &gus->gf1.mem_alloc;
+ mutex_lock(&alloc->memory_mutex);
+ snd_iprintf(buffer, "8-bit banks : \n ");
+ for (i = 0; i < 4; i++)
+ snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_8[i].address, alloc->banks_8[i].size >> 10, i + 1 < 4 ? "," : "");
+ snd_iprintf(buffer, "\n"
+ "16-bit banks : \n ");
+ for (i = total = 0; i < 4; i++) {
+ snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_16[i].address, alloc->banks_16[i].size >> 10, i + 1 < 4 ? "," : "");
+ total += alloc->banks_16[i].size;
+ }
+ snd_iprintf(buffer, "\n");
+ used = 0;
+ for (block = alloc->first, i = 0; block; block = block->next, i++) {
+ used += block->size;
+ snd_iprintf(buffer, "Block %i onboard 0x%x size %i (0x%x):\n", i, block->ptr, block->size, block->size);
+ if (block->share ||
+ block->share_id[0] || block->share_id[1] ||
+ block->share_id[2] || block->share_id[3])
+ snd_iprintf(buffer, " Share : %i [id0 0x%x] [id1 0x%x] [id2 0x%x] [id3 0x%x]\n",
+ block->share,
+ block->share_id[0], block->share_id[1],
+ block->share_id[2], block->share_id[3]);
+ snd_iprintf(buffer, " Flags :%s\n",
+ block->flags & SNDRV_GF1_MEM_BLOCK_16BIT ? " 16-bit" : "");
+ snd_iprintf(buffer, " Owner : ");
+ switch (block->owner) {
+ case SNDRV_GF1_MEM_OWNER_DRIVER:
+ snd_iprintf(buffer, "driver - %s\n", block->name);
+ break;
+ case SNDRV_GF1_MEM_OWNER_WAVE_SIMPLE:
+ snd_iprintf(buffer, "SIMPLE wave\n");
+ break;
+ case SNDRV_GF1_MEM_OWNER_WAVE_GF1:
+ snd_iprintf(buffer, "GF1 wave\n");
+ break;
+ case SNDRV_GF1_MEM_OWNER_WAVE_IWFFFF:
+ snd_iprintf(buffer, "IWFFFF wave\n");
+ break;
+ default:
+ snd_iprintf(buffer, "unknown\n");
+ }
+ }
+ snd_iprintf(buffer, " Total: memory = %i, used = %i, free = %i\n",
+ total, used, total - used);
+ mutex_unlock(&alloc->memory_mutex);
+#if 0
+ ultra_iprintf(buffer, " Verify: free = %i, max 8-bit block = %i, max 16-bit block = %i\n",
+ ultra_memory_free_size(card, &card->gf1.mem_alloc),
+ ultra_memory_free_block(card, &card->gf1.mem_alloc, 0),
+ ultra_memory_free_block(card, &card->gf1.mem_alloc, 1));
+#endif
+}
+#endif
diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c
new file mode 100644
index 0000000000..b5e1d16495
--- /dev/null
+++ b/sound/isa/gus/gus_mem_proc.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * GUS's memory access via proc filesystem
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+struct gus_proc_private {
+ int rom; /* data are in ROM */
+ unsigned int address;
+ unsigned int size;
+ struct snd_gus_card * gus;
+};
+
+static ssize_t snd_gf1_mem_proc_dump(struct snd_info_entry *entry,
+ void *file_private_data,
+ struct file *file, char __user *buf,
+ size_t count, loff_t pos)
+{
+ struct gus_proc_private *priv = entry->private_data;
+ struct snd_gus_card *gus = priv->gus;
+ int err;
+
+ err = snd_gus_dram_read(gus, buf, pos, count, priv->rom);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+static void snd_gf1_mem_proc_free(struct snd_info_entry *entry)
+{
+ struct gus_proc_private *priv = entry->private_data;
+ kfree(priv);
+}
+
+static const struct snd_info_entry_ops snd_gf1_mem_proc_ops = {
+ .read = snd_gf1_mem_proc_dump,
+};
+
+int snd_gf1_mem_proc_init(struct snd_gus_card * gus)
+{
+ int idx;
+ char name[16];
+ struct gus_proc_private *priv;
+ struct snd_info_entry *entry;
+
+ for (idx = 0; idx < 4; idx++) {
+ if (gus->gf1.mem_alloc.banks_8[idx].size > 0) {
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (priv == NULL)
+ return -ENOMEM;
+ priv->gus = gus;
+ sprintf(name, "gus-ram-%i", idx);
+ if (! snd_card_proc_new(gus->card, name, &entry)) {
+ entry->content = SNDRV_INFO_CONTENT_DATA;
+ entry->private_data = priv;
+ entry->private_free = snd_gf1_mem_proc_free;
+ entry->c.ops = &snd_gf1_mem_proc_ops;
+ priv->address = gus->gf1.mem_alloc.banks_8[idx].address;
+ priv->size = entry->size = gus->gf1.mem_alloc.banks_8[idx].size;
+ }
+ }
+ }
+ for (idx = 0; idx < 4; idx++) {
+ if (gus->gf1.rom_present & (1 << idx)) {
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (priv == NULL)
+ return -ENOMEM;
+ priv->rom = 1;
+ priv->gus = gus;
+ sprintf(name, "gus-rom-%i", idx);
+ if (! snd_card_proc_new(gus->card, name, &entry)) {
+ entry->content = SNDRV_INFO_CONTENT_DATA;
+ entry->private_data = priv;
+ entry->private_free = snd_gf1_mem_proc_free;
+ entry->c.ops = &snd_gf1_mem_proc_ops;
+ priv->address = idx * 4096 * 1024;
+ priv->size = entry->size = gus->gf1.rom_memory;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/sound/isa/gus/gus_mixer.c b/sound/isa/gus/gus_mixer.c
new file mode 100644
index 0000000000..03f9cfcbf6
--- /dev/null
+++ b/sound/isa/gus/gus_mixer.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for control of ICS 2101 chip and "mixer" in GF1 chip
+ */
+
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/gus.h>
+
+/*
+ *
+ */
+
+#define GF1_SINGLE(xname, xindex, shift, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_gf1_info_single, \
+ .get = snd_gf1_get_single, .put = snd_gf1_put_single, \
+ .private_value = shift | (invert << 8) }
+
+#define snd_gf1_info_single snd_ctl_boolean_mono_info
+
+static int snd_gf1_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+ int shift = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value >> 8) & 1;
+
+ ucontrol->value.integer.value[0] = (gus->mix_cntrl_reg >> shift) & 1;
+ if (invert)
+ ucontrol->value.integer.value[0] ^= 1;
+ return 0;
+}
+
+static int snd_gf1_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int shift = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value >> 8) & 1;
+ int change;
+ unsigned char oval, nval;
+
+ nval = ucontrol->value.integer.value[0] & 1;
+ if (invert)
+ nval ^= 1;
+ nval <<= shift;
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ oval = gus->mix_cntrl_reg;
+ nval = (oval & ~(1 << shift)) | nval;
+ change = nval != oval;
+ outb(gus->mix_cntrl_reg = nval, GUSP(gus, MIXCNTRLREG));
+ outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE));
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return change;
+}
+
+#define ICS_DOUBLE(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_ics_info_double, \
+ .get = snd_ics_get_double, .put = snd_ics_put_double, \
+ .private_value = addr }
+
+static int snd_ics_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 127;
+ return 0;
+}
+
+static int snd_ics_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int addr = kcontrol->private_value & 0xff;
+ unsigned char left, right;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ left = gus->gf1.ics_regs[addr][0];
+ right = gus->gf1.ics_regs[addr][1];
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ ucontrol->value.integer.value[0] = left & 127;
+ ucontrol->value.integer.value[1] = right & 127;
+ return 0;
+}
+
+static int snd_ics_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int addr = kcontrol->private_value & 0xff;
+ int change;
+ unsigned char val1, val2, oval1, oval2;
+
+ val1 = ucontrol->value.integer.value[0] & 127;
+ val2 = ucontrol->value.integer.value[1] & 127;
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ oval1 = gus->gf1.ics_regs[addr][0];
+ oval2 = gus->gf1.ics_regs[addr][1];
+ change = val1 != oval1 || val2 != oval2;
+ gus->gf1.ics_regs[addr][0] = val1;
+ gus->gf1.ics_regs[addr][1] = val2;
+ if (gus->ics_flag && gus->ics_flipped &&
+ (addr == SNDRV_ICS_GF1_DEV || addr == SNDRV_ICS_MASTER_DEV))
+ swap(val1, val2);
+ addr <<= 3;
+ outb(addr | 0, GUSP(gus, MIXCNTRLPORT));
+ outb(1, GUSP(gus, MIXDATAPORT));
+ outb(addr | 2, GUSP(gus, MIXCNTRLPORT));
+ outb((unsigned char) val1, GUSP(gus, MIXDATAPORT));
+ outb(addr | 1, GUSP(gus, MIXCNTRLPORT));
+ outb(2, GUSP(gus, MIXDATAPORT));
+ outb(addr | 3, GUSP(gus, MIXCNTRLPORT));
+ outb((unsigned char) val2, GUSP(gus, MIXDATAPORT));
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_gf1_controls[] = {
+GF1_SINGLE("Master Playback Switch", 0, 1, 1),
+GF1_SINGLE("Line Switch", 0, 0, 1),
+GF1_SINGLE("Mic Switch", 0, 2, 0)
+};
+
+static const struct snd_kcontrol_new snd_ics_controls[] = {
+GF1_SINGLE("Master Playback Switch", 0, 1, 1),
+ICS_DOUBLE("Master Playback Volume", 0, SNDRV_ICS_MASTER_DEV),
+ICS_DOUBLE("Synth Playback Volume", 0, SNDRV_ICS_GF1_DEV),
+GF1_SINGLE("Line Switch", 0, 0, 1),
+ICS_DOUBLE("Line Playback Volume", 0, SNDRV_ICS_LINE_DEV),
+GF1_SINGLE("Mic Switch", 0, 2, 0),
+ICS_DOUBLE("Mic Playback Volume", 0, SNDRV_ICS_MIC_DEV),
+ICS_DOUBLE("CD Playback Volume", 0, SNDRV_ICS_CD_DEV)
+};
+
+int snd_gf1_new_mixer(struct snd_gus_card * gus)
+{
+ struct snd_card *card;
+ unsigned int idx, max;
+ int err;
+
+ if (snd_BUG_ON(!gus))
+ return -EINVAL;
+ card = gus->card;
+ if (snd_BUG_ON(!card))
+ return -EINVAL;
+
+ if (gus->ics_flag)
+ snd_component_add(card, "ICS2101");
+ if (card->mixername[0] == '\0') {
+ strcpy(card->mixername, gus->ics_flag ? "GF1,ICS2101" : "GF1");
+ } else {
+ if (gus->ics_flag)
+ strcat(card->mixername, ",ICS2101");
+ strcat(card->mixername, ",GF1");
+ }
+
+ if (!gus->ics_flag) {
+ max = gus->ess_flag ? 1 : ARRAY_SIZE(snd_gf1_controls);
+ for (idx = 0; idx < max; idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_gf1_controls[idx], gus));
+ if (err < 0)
+ return err;
+ }
+ } else {
+ for (idx = 0; idx < ARRAY_SIZE(snd_ics_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_ics_controls[idx], gus));
+ if (err < 0)
+ return err;
+ }
+ }
+ return 0;
+}
diff --git a/sound/isa/gus/gus_pcm.c b/sound/isa/gus/gus_pcm.c
new file mode 100644
index 0000000000..850544725d
--- /dev/null
+++ b/sound/isa/gus/gus_pcm.c
@@ -0,0 +1,885 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for control of GF1 chip (PCM things)
+ *
+ * InterWave chips supports interleaved DMA, but this feature isn't used in
+ * this code.
+ *
+ * This code emulates autoinit DMA transfer for playback, recording by GF1
+ * chip doesn't support autoinit DMA.
+ */
+
+#include <asm/dma.h>
+#include <linux/slab.h>
+#include <linux/sched/signal.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/gus.h>
+#include <sound/pcm_params.h>
+#include "gus_tables.h"
+
+/* maximum rate */
+
+#define SNDRV_GF1_PCM_RATE 48000
+
+#define SNDRV_GF1_PCM_PFLG_NONE 0
+#define SNDRV_GF1_PCM_PFLG_ACTIVE (1<<0)
+#define SNDRV_GF1_PCM_PFLG_NEUTRAL (2<<0)
+
+struct gus_pcm_private {
+ struct snd_gus_card * gus;
+ struct snd_pcm_substream *substream;
+ spinlock_t lock;
+ unsigned int voices;
+ struct snd_gus_voice *pvoices[2];
+ unsigned int memory;
+ unsigned short flags;
+ unsigned char voice_ctrl, ramp_ctrl;
+ unsigned int bpos;
+ unsigned int blocks;
+ unsigned int block_size;
+ unsigned int dma_size;
+ wait_queue_head_t sleep;
+ atomic_t dma_count;
+ int final_volume;
+};
+
+static void snd_gf1_pcm_block_change_ack(struct snd_gus_card * gus, void *private_data)
+{
+ struct gus_pcm_private *pcmp = private_data;
+
+ if (pcmp) {
+ atomic_dec(&pcmp->dma_count);
+ wake_up(&pcmp->sleep);
+ }
+}
+
+static int snd_gf1_pcm_block_change(struct snd_pcm_substream *substream,
+ unsigned int offset,
+ unsigned int addr,
+ unsigned int count)
+{
+ struct snd_gf1_dma_block block;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+
+ count += offset & 31;
+ offset &= ~31;
+ /*
+ snd_printk(KERN_DEBUG "block change - offset = 0x%x, count = 0x%x\n",
+ offset, count);
+ */
+ memset(&block, 0, sizeof(block));
+ block.cmd = SNDRV_GF1_DMA_IRQ;
+ if (snd_pcm_format_unsigned(runtime->format))
+ block.cmd |= SNDRV_GF1_DMA_UNSIGNED;
+ if (snd_pcm_format_width(runtime->format) == 16)
+ block.cmd |= SNDRV_GF1_DMA_16BIT;
+ block.addr = addr & ~31;
+ block.buffer = runtime->dma_area + offset;
+ block.buf_addr = runtime->dma_addr + offset;
+ block.count = count;
+ block.private_data = pcmp;
+ block.ack = snd_gf1_pcm_block_change_ack;
+ if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 0, 0))
+ atomic_inc(&pcmp->dma_count);
+ return 0;
+}
+
+static void snd_gf1_pcm_trigger_up(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+ struct snd_gus_card * gus = pcmp->gus;
+ unsigned long flags;
+ unsigned char voice_ctrl, ramp_ctrl;
+ unsigned short rate;
+ unsigned int curr, begin, end;
+ unsigned short vol;
+ unsigned char pan;
+ unsigned int voice;
+
+ spin_lock_irqsave(&pcmp->lock, flags);
+ if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) {
+ spin_unlock_irqrestore(&pcmp->lock, flags);
+ return;
+ }
+ pcmp->flags |= SNDRV_GF1_PCM_PFLG_ACTIVE;
+ pcmp->final_volume = 0;
+ spin_unlock_irqrestore(&pcmp->lock, flags);
+ rate = snd_gf1_translate_freq(gus, runtime->rate << 4);
+ /* enable WAVE IRQ */
+ voice_ctrl = snd_pcm_format_width(runtime->format) == 16 ? 0x24 : 0x20;
+ /* enable RAMP IRQ + rollover */
+ ramp_ctrl = 0x24;
+ if (pcmp->blocks == 1) {
+ voice_ctrl |= 0x08; /* loop enable */
+ ramp_ctrl &= ~0x04; /* disable rollover */
+ }
+ for (voice = 0; voice < pcmp->voices; voice++) {
+ begin = pcmp->memory + voice * (pcmp->dma_size / runtime->channels);
+ curr = begin + (pcmp->bpos * pcmp->block_size) / runtime->channels;
+ end = curr + (pcmp->block_size / runtime->channels);
+ end -= snd_pcm_format_width(runtime->format) == 16 ? 2 : 1;
+ /*
+ snd_printk(KERN_DEBUG "init: curr=0x%x, begin=0x%x, end=0x%x, "
+ "ctrl=0x%x, ramp=0x%x, rate=0x%x\n",
+ curr, begin, end, voice_ctrl, ramp_ctrl, rate);
+ */
+ pan = runtime->channels == 2 ? (!voice ? 1 : 14) : 8;
+ vol = !voice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, pan);
+ snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, rate);
+ snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, begin << 4, voice_ctrl & 4);
+ snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4);
+ snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4);
+ snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME << 4);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0x2f);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, vol >> 8);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+ if (!gus->gf1.enh_mode) {
+ snd_gf1_delay(gus);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+ }
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ }
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ for (voice = 0; voice < pcmp->voices; voice++) {
+ snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
+ if (gus->gf1.enh_mode)
+ snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, 0x00); /* deactivate voice */
+ snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+ voice_ctrl &= ~0x20;
+ }
+ voice_ctrl |= 0x20;
+ if (!gus->gf1.enh_mode) {
+ snd_gf1_delay(gus);
+ for (voice = 0; voice < pcmp->voices; voice++) {
+ snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+ voice_ctrl &= ~0x20; /* disable IRQ for next voice */
+ }
+ }
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_pcm_interrupt_wave(struct snd_gus_card * gus,
+ struct snd_gus_voice *pvoice)
+{
+ struct gus_pcm_private * pcmp;
+ struct snd_pcm_runtime *runtime;
+ unsigned char voice_ctrl, ramp_ctrl;
+ unsigned int idx;
+ unsigned int end, step;
+
+ if (!pvoice->private_data) {
+ snd_printd("snd_gf1_pcm: unknown wave irq?\n");
+ snd_gf1_smart_stop_voice(gus, pvoice->number);
+ return;
+ }
+ pcmp = pvoice->private_data;
+ if (pcmp == NULL) {
+ snd_printd("snd_gf1_pcm: unknown wave irq?\n");
+ snd_gf1_smart_stop_voice(gus, pvoice->number);
+ return;
+ }
+ gus = pcmp->gus;
+ runtime = pcmp->substream->runtime;
+
+ spin_lock(&gus->reg_lock);
+ snd_gf1_select_voice(gus, pvoice->number);
+ voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & ~0x8b;
+ ramp_ctrl = (snd_gf1_read8(gus, SNDRV_GF1_VB_VOLUME_CONTROL) & ~0xa4) | 0x03;
+#if 0
+ snd_gf1_select_voice(gus, pvoice->number);
+ printk(KERN_DEBUG "position = 0x%x\n",
+ (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4));
+ snd_gf1_select_voice(gus, pcmp->pvoices[1]->number);
+ printk(KERN_DEBUG "position = 0x%x\n",
+ (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4));
+ snd_gf1_select_voice(gus, pvoice->number);
+#endif
+ pcmp->bpos++;
+ pcmp->bpos %= pcmp->blocks;
+ if (pcmp->bpos + 1 >= pcmp->blocks) { /* last block? */
+ voice_ctrl |= 0x08; /* enable loop */
+ } else {
+ ramp_ctrl |= 0x04; /* enable rollover */
+ }
+ end = pcmp->memory + (((pcmp->bpos + 1) * pcmp->block_size) / runtime->channels);
+ end -= voice_ctrl & 4 ? 2 : 1;
+ step = pcmp->dma_size / runtime->channels;
+ voice_ctrl |= 0x20;
+ if (!pcmp->final_volume) {
+ ramp_ctrl |= 0x20;
+ ramp_ctrl &= ~0x03;
+ }
+ for (idx = 0; idx < pcmp->voices; idx++, end += step) {
+ snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number);
+ snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+ voice_ctrl &= ~0x20;
+ }
+ if (!gus->gf1.enh_mode) {
+ snd_gf1_delay(gus);
+ voice_ctrl |= 0x20;
+ for (idx = 0; idx < pcmp->voices; idx++) {
+ snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+ voice_ctrl &= ~0x20;
+ }
+ }
+ spin_unlock(&gus->reg_lock);
+
+ snd_pcm_period_elapsed(pcmp->substream);
+#if 0
+ if ((runtime->flags & SNDRV_PCM_FLG_MMAP) &&
+ *runtime->state == SNDRV_PCM_STATE_RUNNING) {
+ end = pcmp->bpos * pcmp->block_size;
+ if (runtime->channels > 1) {
+ snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + (end / 2), pcmp->block_size / 2);
+ snd_gf1_pcm_block_change(pcmp->substream, end + (pcmp->block_size / 2), pcmp->memory + (pcmp->dma_size / 2) + (end / 2), pcmp->block_size / 2);
+ } else {
+ snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + end, pcmp->block_size);
+ }
+ }
+#endif
+}
+
+static void snd_gf1_pcm_interrupt_volume(struct snd_gus_card * gus,
+ struct snd_gus_voice * pvoice)
+{
+ unsigned short vol;
+ int cvoice;
+ struct gus_pcm_private *pcmp = pvoice->private_data;
+
+ /* stop ramp, but leave rollover bit untouched */
+ spin_lock(&gus->reg_lock);
+ snd_gf1_select_voice(gus, pvoice->number);
+ snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+ spin_unlock(&gus->reg_lock);
+ if (pcmp == NULL)
+ return;
+ /* are we active? */
+ if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE))
+ return;
+ /* load real volume - better precision */
+ cvoice = pcmp->pvoices[0] == pvoice ? 0 : 1;
+ if (pcmp->substream == NULL)
+ return;
+ vol = !cvoice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
+ spin_lock(&gus->reg_lock);
+ snd_gf1_select_voice(gus, pvoice->number);
+ snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol);
+ pcmp->final_volume = 1;
+ spin_unlock(&gus->reg_lock);
+}
+
+static void snd_gf1_pcm_volume_change(struct snd_gus_card * gus)
+{
+}
+
+static int snd_gf1_pcm_poke_block(struct snd_gus_card *gus, unsigned char *buf,
+ unsigned int pos, unsigned int count,
+ int w16, int invert)
+{
+ unsigned int len;
+ unsigned long flags;
+
+ /*
+ printk(KERN_DEBUG
+ "poke block; buf = 0x%x, pos = %i, count = %i, port = 0x%x\n",
+ (int)buf, pos, count, gus->gf1.port);
+ */
+ while (count > 0) {
+ len = count;
+ if (len > 512) /* limit, to allow IRQ */
+ len = 512;
+ count -= len;
+ if (gus->interwave) {
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01 | (invert ? 0x08 : 0x00));
+ snd_gf1_dram_addr(gus, pos);
+ if (w16) {
+ outb(SNDRV_GF1_GW_DRAM_IO16, GUSP(gus, GF1REGSEL));
+ outsw(GUSP(gus, GF1DATALOW), buf, len >> 1);
+ } else {
+ outsb(GUSP(gus, DRAM), buf, len);
+ }
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ buf += 512;
+ pos += 512;
+ } else {
+ invert = invert ? 0x80 : 0x00;
+ if (w16) {
+ len >>= 1;
+ while (len--) {
+ snd_gf1_poke(gus, pos++, *buf++);
+ snd_gf1_poke(gus, pos++, *buf++ ^ invert);
+ }
+ } else {
+ while (len--)
+ snd_gf1_poke(gus, pos++, *buf++ ^ invert);
+ }
+ }
+ if (count > 0 && !in_interrupt()) {
+ schedule_timeout_interruptible(1);
+ if (signal_pending(current))
+ return -EAGAIN;
+ }
+ }
+ return 0;
+}
+
+static int get_bpos(struct gus_pcm_private *pcmp, int voice, unsigned int pos,
+ unsigned int len)
+{
+ unsigned int bpos = pos + (voice * (pcmp->dma_size / 2));
+ if (snd_BUG_ON(bpos > pcmp->dma_size))
+ return -EIO;
+ if (snd_BUG_ON(bpos + len > pcmp->dma_size))
+ return -EIO;
+ return bpos;
+}
+
+static int playback_copy_ack(struct snd_pcm_substream *substream,
+ unsigned int bpos, unsigned int len)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+ struct snd_gus_card *gus = pcmp->gus;
+ int w16, invert;
+
+ if (len > 32)
+ return snd_gf1_pcm_block_change(substream, bpos,
+ pcmp->memory + bpos, len);
+
+ w16 = (snd_pcm_format_width(runtime->format) == 16);
+ invert = snd_pcm_format_unsigned(runtime->format);
+ return snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos,
+ pcmp->memory + bpos, len, w16, invert);
+}
+
+static int snd_gf1_pcm_playback_copy(struct snd_pcm_substream *substream,
+ int voice, unsigned long pos,
+ struct iov_iter *src, unsigned long count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+ unsigned int len = count;
+ int bpos;
+
+ bpos = get_bpos(pcmp, voice, pos, len);
+ if (bpos < 0)
+ return pos;
+ if (copy_from_iter(runtime->dma_area + bpos, len, src) != len)
+ return -EFAULT;
+ return playback_copy_ack(substream, bpos, len);
+}
+
+static int snd_gf1_pcm_playback_silence(struct snd_pcm_substream *substream,
+ int voice, unsigned long pos,
+ unsigned long count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+ unsigned int len = count;
+ int bpos;
+
+ bpos = get_bpos(pcmp, voice, pos, len);
+ if (bpos < 0)
+ return pos;
+ snd_pcm_format_set_silence(runtime->format, runtime->dma_area + bpos,
+ bytes_to_samples(runtime, count));
+ return playback_copy_ack(substream, bpos, len);
+}
+
+static int snd_gf1_pcm_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+
+ if (runtime->buffer_changed) {
+ struct snd_gf1_mem_block *block;
+ if (pcmp->memory > 0) {
+ snd_gf1_mem_free(&gus->gf1.mem_alloc, pcmp->memory);
+ pcmp->memory = 0;
+ }
+ block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
+ SNDRV_GF1_MEM_OWNER_DRIVER,
+ "GF1 PCM",
+ runtime->dma_bytes, 1, 32,
+ NULL);
+ if (!block)
+ return -ENOMEM;
+ pcmp->memory = block->ptr;
+ }
+ pcmp->voices = params_channels(hw_params);
+ if (pcmp->pvoices[0] == NULL) {
+ pcmp->pvoices[0] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0);
+ if (!pcmp->pvoices[0])
+ return -ENOMEM;
+ pcmp->pvoices[0]->handler_wave = snd_gf1_pcm_interrupt_wave;
+ pcmp->pvoices[0]->handler_volume = snd_gf1_pcm_interrupt_volume;
+ pcmp->pvoices[0]->volume_change = snd_gf1_pcm_volume_change;
+ pcmp->pvoices[0]->private_data = pcmp;
+ }
+ if (pcmp->voices > 1 && pcmp->pvoices[1] == NULL) {
+ pcmp->pvoices[1] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0);
+ if (!pcmp->pvoices[1])
+ return -ENOMEM;
+ pcmp->pvoices[1]->handler_wave = snd_gf1_pcm_interrupt_wave;
+ pcmp->pvoices[1]->handler_volume = snd_gf1_pcm_interrupt_volume;
+ pcmp->pvoices[1]->volume_change = snd_gf1_pcm_volume_change;
+ pcmp->pvoices[1]->private_data = pcmp;
+ } else if (pcmp->voices == 1) {
+ if (pcmp->pvoices[1]) {
+ snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]);
+ pcmp->pvoices[1] = NULL;
+ }
+ }
+ return 0;
+}
+
+static int snd_gf1_pcm_playback_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+
+ if (pcmp->pvoices[0]) {
+ snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[0]);
+ pcmp->pvoices[0] = NULL;
+ }
+ if (pcmp->pvoices[1]) {
+ snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]);
+ pcmp->pvoices[1] = NULL;
+ }
+ if (pcmp->memory > 0) {
+ snd_gf1_mem_free(&pcmp->gus->gf1.mem_alloc, pcmp->memory);
+ pcmp->memory = 0;
+ }
+ return 0;
+}
+
+static int snd_gf1_pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+
+ pcmp->bpos = 0;
+ pcmp->dma_size = snd_pcm_lib_buffer_bytes(substream);
+ pcmp->block_size = snd_pcm_lib_period_bytes(substream);
+ pcmp->blocks = pcmp->dma_size / pcmp->block_size;
+ return 0;
+}
+
+static int snd_gf1_pcm_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+ int voice;
+
+ if (cmd == SNDRV_PCM_TRIGGER_START) {
+ snd_gf1_pcm_trigger_up(substream);
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ spin_lock(&pcmp->lock);
+ pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE;
+ spin_unlock(&pcmp->lock);
+ voice = pcmp->pvoices[0]->number;
+ snd_gf1_stop_voices(gus, voice, voice);
+ if (pcmp->pvoices[1]) {
+ voice = pcmp->pvoices[1]->number;
+ snd_gf1_stop_voices(gus, voice, voice);
+ }
+ } else {
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_gf1_pcm_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+ unsigned int pos;
+ unsigned char voice_ctrl;
+
+ pos = 0;
+ spin_lock(&gus->reg_lock);
+ if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) {
+ snd_gf1_select_voice(gus, pcmp->pvoices[0]->number);
+ voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+ pos = (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4) - pcmp->memory;
+ if (substream->runtime->channels > 1)
+ pos <<= 1;
+ pos = bytes_to_frames(runtime, pos);
+ }
+ spin_unlock(&gus->reg_lock);
+ return pos;
+}
+
+static const struct snd_ratnum clock = {
+ .num = 9878400/16,
+ .den_min = 2,
+ .den_max = 257,
+ .den_step = 1,
+};
+
+static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = {
+ .nrats = 1,
+ .rats = &clock,
+};
+
+static int snd_gf1_pcm_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+
+ gus->c_dma_size = params_buffer_bytes(hw_params);
+ gus->c_period_size = params_period_bytes(hw_params);
+ gus->c_pos = 0;
+ gus->gf1.pcm_rcntrl_reg = 0x21; /* IRQ at end, enable & start */
+ if (params_channels(hw_params) > 1)
+ gus->gf1.pcm_rcntrl_reg |= 2;
+ if (gus->gf1.dma2 > 3)
+ gus->gf1.pcm_rcntrl_reg |= 4;
+ if (snd_pcm_format_unsigned(params_format(hw_params)))
+ gus->gf1.pcm_rcntrl_reg |= 0x80;
+ return 0;
+}
+
+static int snd_gf1_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RECORD_RATE, runtime->rate_den - 2);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); /* disable sampling */
+ snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); /* Sampling Control Register */
+ snd_dma_program(gus->gf1.dma2, runtime->dma_addr, gus->c_period_size, DMA_MODE_READ);
+ return 0;
+}
+
+static int snd_gf1_pcm_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+ int val;
+
+ if (cmd == SNDRV_PCM_TRIGGER_START) {
+ val = gus->gf1.pcm_rcntrl_reg;
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ val = 0;
+ } else {
+ return -EINVAL;
+ }
+
+ spin_lock(&gus->reg_lock);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, val);
+ snd_gf1_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL);
+ spin_unlock(&gus->reg_lock);
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_gf1_pcm_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+ int pos = snd_dma_pointer(gus->gf1.dma2, gus->c_period_size);
+ pos = bytes_to_frames(substream->runtime, (gus->c_pos + pos) % gus->c_dma_size);
+ return pos;
+}
+
+static void snd_gf1_pcm_interrupt_dma_read(struct snd_gus_card * gus)
+{
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); /* disable sampling */
+ snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); /* Sampling Control Register */
+ if (gus->pcm_cap_substream != NULL) {
+ snd_gf1_pcm_capture_prepare(gus->pcm_cap_substream);
+ snd_gf1_pcm_capture_trigger(gus->pcm_cap_substream, SNDRV_PCM_TRIGGER_START);
+ gus->c_pos += gus->c_period_size;
+ snd_pcm_period_elapsed(gus->pcm_cap_substream);
+ }
+}
+
+static const struct snd_pcm_hardware snd_gf1_pcm_playback =
+{
+ .info = SNDRV_PCM_INFO_NONINTERLEAVED,
+ .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 5510,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_gf1_pcm_capture =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+ .rate_min = 5510,
+ .rate_max = 44100,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static void snd_gf1_pcm_playback_free(struct snd_pcm_runtime *runtime)
+{
+ kfree(runtime->private_data);
+}
+
+static int snd_gf1_pcm_playback_open(struct snd_pcm_substream *substream)
+{
+ struct gus_pcm_private *pcmp;
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ pcmp = kzalloc(sizeof(*pcmp), GFP_KERNEL);
+ if (pcmp == NULL)
+ return -ENOMEM;
+ pcmp->gus = gus;
+ spin_lock_init(&pcmp->lock);
+ init_waitqueue_head(&pcmp->sleep);
+ atomic_set(&pcmp->dma_count, 0);
+
+ runtime->private_data = pcmp;
+ runtime->private_free = snd_gf1_pcm_playback_free;
+
+#if 0
+ printk(KERN_DEBUG "playback.buffer = 0x%lx, gf1.pcm_buffer = 0x%lx\n",
+ (long) pcm->playback.buffer, (long) gus->gf1.pcm_buffer);
+#endif
+ err = snd_gf1_dma_init(gus);
+ if (err < 0)
+ return err;
+ pcmp->flags = SNDRV_GF1_PCM_PFLG_NONE;
+ pcmp->substream = substream;
+ runtime->hw = snd_gf1_pcm_playback;
+ snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.buffer_bytes_max);
+ snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.period_bytes_max);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64);
+ return 0;
+}
+
+static int snd_gf1_pcm_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct gus_pcm_private *pcmp = runtime->private_data;
+
+ if (!wait_event_timeout(pcmp->sleep, (atomic_read(&pcmp->dma_count) <= 0), 2*HZ))
+ snd_printk(KERN_ERR "gf1 pcm - serious DMA problem\n");
+
+ snd_gf1_dma_done(gus);
+ return 0;
+}
+
+static int snd_gf1_pcm_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+
+ gus->gf1.interrupt_handler_dma_read = snd_gf1_pcm_interrupt_dma_read;
+ gus->pcm_cap_substream = substream;
+ substream->runtime->hw = snd_gf1_pcm_capture;
+ snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.buffer_bytes_max);
+ snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.period_bytes_max);
+ snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_clocks);
+ return 0;
+}
+
+static int snd_gf1_pcm_capture_close(struct snd_pcm_substream *substream)
+{
+ struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+
+ gus->pcm_cap_substream = NULL;
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_READ);
+ return 0;
+}
+
+static int snd_gf1_pcm_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 127;
+ return 0;
+}
+
+static int snd_gf1_pcm_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->pcm_volume_level_lock, flags);
+ ucontrol->value.integer.value[0] = gus->gf1.pcm_volume_level_left1;
+ ucontrol->value.integer.value[1] = gus->gf1.pcm_volume_level_right1;
+ spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags);
+ return 0;
+}
+
+static int snd_gf1_pcm_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned int idx;
+ unsigned short val1, val2, vol;
+ struct gus_pcm_private *pcmp;
+ struct snd_gus_voice *pvoice;
+
+ val1 = ucontrol->value.integer.value[0] & 127;
+ val2 = ucontrol->value.integer.value[1] & 127;
+ spin_lock_irqsave(&gus->pcm_volume_level_lock, flags);
+ change = val1 != gus->gf1.pcm_volume_level_left1 ||
+ val2 != gus->gf1.pcm_volume_level_right1;
+ gus->gf1.pcm_volume_level_left1 = val1;
+ gus->gf1.pcm_volume_level_right1 = val2;
+ gus->gf1.pcm_volume_level_left = snd_gf1_lvol_to_gvol_raw(val1 << 9) << 4;
+ gus->gf1.pcm_volume_level_right = snd_gf1_lvol_to_gvol_raw(val2 << 9) << 4;
+ spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags);
+ /* are we active? */
+ spin_lock_irqsave(&gus->voice_alloc, flags);
+ for (idx = 0; idx < 32; idx++) {
+ pvoice = &gus->gf1.voices[idx];
+ if (!pvoice->pcm)
+ continue;
+ pcmp = pvoice->private_data;
+ if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE))
+ continue;
+ /* load real volume - better precision */
+ spin_lock(&gus->reg_lock);
+ snd_gf1_select_voice(gus, pvoice->number);
+ snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+ vol = pvoice == pcmp->pvoices[0] ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
+ snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol);
+ pcmp->final_volume = 1;
+ spin_unlock(&gus->reg_lock);
+ }
+ spin_unlock_irqrestore(&gus->voice_alloc, flags);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_gf1_pcm_volume_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .info = snd_gf1_pcm_volume_info,
+ .get = snd_gf1_pcm_volume_get,
+ .put = snd_gf1_pcm_volume_put
+};
+
+static const struct snd_kcontrol_new snd_gf1_pcm_volume_control1 =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "GPCM Playback Volume",
+ .info = snd_gf1_pcm_volume_info,
+ .get = snd_gf1_pcm_volume_get,
+ .put = snd_gf1_pcm_volume_put
+};
+
+static const struct snd_pcm_ops snd_gf1_pcm_playback_ops = {
+ .open = snd_gf1_pcm_playback_open,
+ .close = snd_gf1_pcm_playback_close,
+ .hw_params = snd_gf1_pcm_playback_hw_params,
+ .hw_free = snd_gf1_pcm_playback_hw_free,
+ .prepare = snd_gf1_pcm_playback_prepare,
+ .trigger = snd_gf1_pcm_playback_trigger,
+ .pointer = snd_gf1_pcm_playback_pointer,
+ .copy = snd_gf1_pcm_playback_copy,
+ .fill_silence = snd_gf1_pcm_playback_silence,
+};
+
+static const struct snd_pcm_ops snd_gf1_pcm_capture_ops = {
+ .open = snd_gf1_pcm_capture_open,
+ .close = snd_gf1_pcm_capture_close,
+ .hw_params = snd_gf1_pcm_capture_hw_params,
+ .prepare = snd_gf1_pcm_capture_prepare,
+ .trigger = snd_gf1_pcm_capture_trigger,
+ .pointer = snd_gf1_pcm_capture_pointer,
+};
+
+int snd_gf1_pcm_new(struct snd_gus_card *gus, int pcm_dev, int control_index)
+{
+ struct snd_card *card;
+ struct snd_kcontrol *kctl;
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *substream;
+ int capture, err;
+
+ card = gus->card;
+ capture = !gus->interwave && !gus->ess_flag && !gus->ace_flag ? 1 : 0;
+ err = snd_pcm_new(card,
+ gus->interwave ? "AMD InterWave" : "GF1",
+ pcm_dev,
+ gus->gf1.pcm_channels / 2,
+ capture,
+ &pcm);
+ if (err < 0)
+ return err;
+ pcm->private_data = gus;
+ /* playback setup */
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_gf1_pcm_playback_ops);
+
+ for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+ snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV,
+ card->dev,
+ 64*1024, gus->gf1.dma1 > 3 ? 128*1024 : 64*1024);
+
+ pcm->info_flags = 0;
+ pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+ if (capture) {
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_gf1_pcm_capture_ops);
+ if (gus->gf1.dma2 == gus->gf1.dma1)
+ pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+ snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+ SNDRV_DMA_TYPE_DEV, card->dev,
+ 64*1024, gus->gf1.dma2 > 3 ? 128*1024 : 64*1024);
+ }
+ strcpy(pcm->name, pcm->id);
+ if (gus->interwave) {
+ sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A');
+ }
+ strcat(pcm->name, " (synth)");
+ gus->pcm = pcm;
+
+ if (gus->codec_flag)
+ kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control1, gus);
+ else
+ kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control, gus);
+ kctl->id.index = control_index;
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c
new file mode 100644
index 0000000000..9a1ab5872c
--- /dev/null
+++ b/sound/isa/gus/gus_reset.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+extern int snd_gf1_synth_init(struct snd_gus_card * gus);
+extern void snd_gf1_synth_done(struct snd_gus_card * gus);
+
+/*
+ * ok.. default interrupt handlers...
+ */
+
+static void snd_gf1_default_interrupt_handler_midi_out(struct snd_gus_card * gus)
+{
+ snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x20);
+}
+
+static void snd_gf1_default_interrupt_handler_midi_in(struct snd_gus_card * gus)
+{
+ snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x80);
+}
+
+static void snd_gf1_default_interrupt_handler_timer1(struct snd_gus_card * gus)
+{
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~4);
+}
+
+static void snd_gf1_default_interrupt_handler_timer2(struct snd_gus_card * gus)
+{
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~8);
+}
+
+static void snd_gf1_default_interrupt_handler_wave_and_volume(struct snd_gus_card * gus, struct snd_gus_voice * voice)
+{
+ snd_gf1_i_ctrl_stop(gus, 0x00);
+ snd_gf1_i_ctrl_stop(gus, 0x0d);
+}
+
+static void snd_gf1_default_interrupt_handler_dma_write(struct snd_gus_card * gus)
+{
+ snd_gf1_i_write8(gus, 0x41, 0x00);
+}
+
+static void snd_gf1_default_interrupt_handler_dma_read(struct snd_gus_card * gus)
+{
+ snd_gf1_i_write8(gus, 0x49, 0x00);
+}
+
+void snd_gf1_set_default_handlers(struct snd_gus_card * gus, unsigned int what)
+{
+ if (what & SNDRV_GF1_HANDLER_MIDI_OUT)
+ gus->gf1.interrupt_handler_midi_out = snd_gf1_default_interrupt_handler_midi_out;
+ if (what & SNDRV_GF1_HANDLER_MIDI_IN)
+ gus->gf1.interrupt_handler_midi_in = snd_gf1_default_interrupt_handler_midi_in;
+ if (what & SNDRV_GF1_HANDLER_TIMER1)
+ gus->gf1.interrupt_handler_timer1 = snd_gf1_default_interrupt_handler_timer1;
+ if (what & SNDRV_GF1_HANDLER_TIMER2)
+ gus->gf1.interrupt_handler_timer2 = snd_gf1_default_interrupt_handler_timer2;
+ if (what & SNDRV_GF1_HANDLER_VOICE) {
+ struct snd_gus_voice *voice;
+
+ voice = &gus->gf1.voices[what & 0xffff];
+ voice->handler_wave =
+ voice->handler_volume = snd_gf1_default_interrupt_handler_wave_and_volume;
+ voice->handler_effect = NULL;
+ voice->volume_change = NULL;
+ }
+ if (what & SNDRV_GF1_HANDLER_DMA_WRITE)
+ gus->gf1.interrupt_handler_dma_write = snd_gf1_default_interrupt_handler_dma_write;
+ if (what & SNDRV_GF1_HANDLER_DMA_READ)
+ gus->gf1.interrupt_handler_dma_read = snd_gf1_default_interrupt_handler_dma_read;
+}
+
+/*
+
+ */
+
+static void snd_gf1_clear_regs(struct snd_gus_card * gus)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ inb(GUSP(gus, IRQSTAT));
+ snd_gf1_write8(gus, 0x41, 0); /* DRAM DMA Control Register */
+ snd_gf1_write8(gus, 0x45, 0); /* Timer Control */
+ snd_gf1_write8(gus, 0x49, 0); /* Sampling Control Register */
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_look_regs(struct snd_gus_card * gus)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_look8(gus, 0x41); /* DRAM DMA Control Register */
+ snd_gf1_look8(gus, 0x49); /* Sampling Control Register */
+ inb(GUSP(gus, IRQSTAT));
+ snd_gf1_read8(gus, 0x0f); /* IRQ Source Register */
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+/*
+ * put selected GF1 voices to initial stage...
+ */
+
+void snd_gf1_smart_stop_voice(struct snd_gus_card * gus, unsigned short voice)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_select_voice(gus, voice);
+#if 0
+ printk(KERN_DEBUG " -%i- smart stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME));
+#endif
+ snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+ snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+void snd_gf1_stop_voice(struct snd_gus_card * gus, unsigned short voice)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_select_voice(gus, voice);
+#if 0
+ printk(KERN_DEBUG " -%i- stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME));
+#endif
+ snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+ snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+ if (gus->gf1.enh_mode)
+ snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+#if 0
+ snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_VIBRATO);
+ snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_TREMOLO);
+#endif
+}
+
+static void snd_gf1_clear_voices(struct snd_gus_card * gus, unsigned short v_min,
+ unsigned short v_max)
+{
+ unsigned long flags;
+ unsigned int daddr;
+ unsigned short i, w_16;
+
+ daddr = gus->gf1.default_voice_address << 4;
+ for (i = v_min; i <= v_max; i++) {
+#if 0
+ if (gus->gf1.syn_voices)
+ gus->gf1.syn_voices[i].flags = ~VFLG_DYNAMIC;
+#endif
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_select_voice(gus, i);
+ snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); /* Voice Control Register = voice stop */
+ snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); /* Volume Ramp Control Register = ramp off */
+ if (gus->gf1.enh_mode)
+ snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, gus->gf1.memory ? 0x02 : 0x82); /* Deactivate voice */
+ w_16 = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & 0x04;
+ snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, 0x400);
+ snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, daddr, w_16);
+ snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, daddr, w_16);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, 0);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, 0);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0);
+ snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, 0);
+ snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, daddr, w_16);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, 7);
+ if (gus->gf1.enh_mode) {
+ snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0);
+ snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME, 0);
+ snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME_FINAL, 0);
+ }
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+#if 0
+ snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_VIBRATO);
+ snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_TREMOLO);
+#endif
+ }
+}
+
+void snd_gf1_stop_voices(struct snd_gus_card * gus, unsigned short v_min, unsigned short v_max)
+{
+ unsigned long flags;
+ short i, ramp_ok;
+ unsigned short ramp_end;
+
+ if (!in_interrupt()) { /* this can't be done in interrupt */
+ for (i = v_min, ramp_ok = 0; i <= v_max; i++) {
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_select_voice(gus, i);
+ ramp_end = snd_gf1_read16(gus, 9) >> 8;
+ if (ramp_end > SNDRV_GF1_MIN_OFFSET) {
+ ramp_ok++;
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 20); /* ramp rate */
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET); /* ramp start */
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, ramp_end); /* ramp end */
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40); /* ramp down */
+ if (gus->gf1.enh_mode) {
+ snd_gf1_delay(gus);
+ snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40);
+ }
+ }
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ }
+ msleep_interruptible(50);
+ }
+ snd_gf1_clear_voices(gus, v_min, v_max);
+}
+
+static void snd_gf1_alloc_voice_use(struct snd_gus_card * gus,
+ struct snd_gus_voice * pvoice,
+ int type, int client, int port)
+{
+ pvoice->use = 1;
+ switch (type) {
+ case SNDRV_GF1_VOICE_TYPE_PCM:
+ gus->gf1.pcm_alloc_voices++;
+ pvoice->pcm = 1;
+ break;
+ case SNDRV_GF1_VOICE_TYPE_SYNTH:
+ pvoice->synth = 1;
+ pvoice->client = client;
+ pvoice->port = port;
+ break;
+ case SNDRV_GF1_VOICE_TYPE_MIDI:
+ pvoice->midi = 1;
+ pvoice->client = client;
+ pvoice->port = port;
+ break;
+ }
+}
+
+struct snd_gus_voice *snd_gf1_alloc_voice(struct snd_gus_card * gus, int type, int client, int port)
+{
+ struct snd_gus_voice *pvoice;
+ unsigned long flags;
+ int idx;
+
+ spin_lock_irqsave(&gus->voice_alloc, flags);
+ if (type == SNDRV_GF1_VOICE_TYPE_PCM) {
+ if (gus->gf1.pcm_alloc_voices >= gus->gf1.pcm_channels) {
+ spin_unlock_irqrestore(&gus->voice_alloc, flags);
+ return NULL;
+ }
+ }
+ for (idx = 0; idx < 32; idx++) {
+ pvoice = &gus->gf1.voices[idx];
+ if (!pvoice->use) {
+ snd_gf1_alloc_voice_use(gus, pvoice, type, client, port);
+ spin_unlock_irqrestore(&gus->voice_alloc, flags);
+ return pvoice;
+ }
+ }
+ for (idx = 0; idx < 32; idx++) {
+ pvoice = &gus->gf1.voices[idx];
+ if (pvoice->midi && !pvoice->client) {
+ snd_gf1_clear_voices(gus, pvoice->number, pvoice->number);
+ snd_gf1_alloc_voice_use(gus, pvoice, type, client, port);
+ spin_unlock_irqrestore(&gus->voice_alloc, flags);
+ return pvoice;
+ }
+ }
+ spin_unlock_irqrestore(&gus->voice_alloc, flags);
+ return NULL;
+}
+
+void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice)
+{
+ unsigned long flags;
+ void (*private_free)(struct snd_gus_voice *voice);
+
+ if (voice == NULL || !voice->use)
+ return;
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | voice->number);
+ snd_gf1_clear_voices(gus, voice->number, voice->number);
+ spin_lock_irqsave(&gus->voice_alloc, flags);
+ private_free = voice->private_free;
+ voice->private_free = NULL;
+ voice->private_data = NULL;
+ if (voice->pcm)
+ gus->gf1.pcm_alloc_voices--;
+ voice->use = voice->pcm = 0;
+ voice->sample_ops = NULL;
+ spin_unlock_irqrestore(&gus->voice_alloc, flags);
+ if (private_free)
+ private_free(voice);
+}
+
+/*
+ * call this function only by start of driver
+ */
+
+int snd_gf1_start(struct snd_gus_card * gus)
+{
+ unsigned long flags;
+ unsigned int i;
+
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */
+ udelay(160);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* disable IRQ & DAC */
+ udelay(160);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
+
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL);
+ for (i = 0; i < 32; i++) {
+ gus->gf1.voices[i].number = i;
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i);
+ }
+
+ snd_gf1_uart_cmd(gus, 0x03); /* huh.. this cleanup took me some time... */
+
+ if (gus->gf1.enh_mode) { /* enhanced mode !!!! */
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+ }
+ snd_gf1_clear_regs(gus);
+ snd_gf1_select_active_voices(gus);
+ snd_gf1_delay(gus);
+ gus->gf1.default_voice_address = gus->gf1.memory > 0 ? 0 : 512 - 8;
+ /* initialize LFOs & clear LFOs memory */
+ if (gus->gf1.enh_mode && gus->gf1.memory) {
+ gus->gf1.hw_lfo = 1;
+ gus->gf1.default_voice_address += 1024;
+ } else {
+ gus->gf1.sw_lfo = 1;
+ }
+#if 0
+ snd_gf1_lfo_init(gus);
+#endif
+ if (gus->gf1.memory > 0)
+ for (i = 0; i < 4; i++)
+ snd_gf1_poke(gus, gus->gf1.default_voice_address + i, 0);
+ snd_gf1_clear_regs(gus);
+ snd_gf1_clear_voices(gus, 0, 31);
+ snd_gf1_look_regs(gus);
+ udelay(160);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7); /* Reset Register = IRQ enable, DAC enable */
+ udelay(160);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7); /* Reset Register = IRQ enable, DAC enable */
+ if (gus->gf1.enh_mode) { /* enhanced mode !!!! */
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+ }
+ while ((snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ) & 0xc0) != 0xc0);
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE));
+ outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+ snd_gf1_timers_init(gus);
+ snd_gf1_look_regs(gus);
+ snd_gf1_mem_init(gus);
+ snd_gf1_mem_proc_init(gus);
+#ifdef CONFIG_SND_DEBUG
+ snd_gus_irq_profile_init(gus);
+#endif
+
+#if 0
+ if (gus->pnp_flag) {
+ if (gus->chip.playback_fifo_size > 0)
+ snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR, gus->chip.playback_fifo_block->ptr >> 8);
+ if (gus->chip.record_fifo_size > 0)
+ snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR, gus->chip.record_fifo_block->ptr >> 8);
+ snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_SIZE, gus->chip.interwave_fifo_reg);
+ }
+#endif
+
+ return 0;
+}
+
+/*
+ * call this function only by shutdown of driver
+ */
+
+int snd_gf1_stop(struct snd_gus_card * gus)
+{
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0); /* stop all timers */
+ snd_gf1_stop_voices(gus, 0, 31); /* stop all voices */
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* disable IRQ & DAC */
+ snd_gf1_timers_done(gus);
+ snd_gf1_mem_done(gus);
+#if 0
+ snd_gf1_lfo_done(gus);
+#endif
+ return 0;
+}
diff --git a/sound/isa/gus/gus_tables.h b/sound/isa/gus/gus_tables.h
new file mode 100644
index 0000000000..ea0f007fa2
--- /dev/null
+++ b/sound/isa/gus/gus_tables.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#define SNDRV_GF1_SCALE_TABLE_SIZE 128
+#define SNDRV_GF1_ATTEN_TABLE_SIZE 128
+
+#ifdef __GUS_TABLES_ALLOC__
+
+#if 0
+
+unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE] =
+{
+ 8372, 8870, 9397, 9956, 10548, 11175,
+ 11840, 12544, 13290, 14080, 14917, 15804,
+ 16744, 17740, 18795, 19912, 21096, 22351,
+ 23680, 25088, 26580, 28160, 29834, 31609,
+ 33488, 35479, 37589, 39824, 42192, 44701,
+ 47359, 50175, 53159, 56320, 59669, 63217,
+ 66976, 70959, 75178, 79649, 84385, 89402,
+ 94719, 100351, 106318, 112640, 119338, 126434,
+ 133952, 141918, 150356, 159297, 168769, 178805,
+ 189437, 200702, 212636, 225280, 238676, 252868,
+ 267905, 283835, 300713, 318594, 337539, 357610,
+ 378874, 401403, 425272, 450560, 477352, 505737,
+ 535809, 567670, 601425, 637188, 675077, 715219,
+ 757749, 802807, 850544, 901120, 954703, 1011473,
+ 1071618, 1135340, 1202851, 1274376, 1350154, 1430439,
+ 1515497, 1605613, 1701088, 1802240, 1909407, 2022946,
+ 2143237, 2270680, 2405702, 2548752, 2700309, 2860878,
+ 3030994, 3211227, 3402176, 3604480, 3818814, 4045892,
+ 4286473, 4541360, 4811404, 5097505, 5400618, 5721755,
+ 6061989, 6422453, 6804352, 7208960, 7637627, 8091784,
+ 8572947, 9082720, 9622807, 10195009, 10801236, 11443511,
+ 12123977, 12844906
+};
+
+#endif /* 0 */
+
+unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE] = {
+ 4095 /* 0 */,1789 /* 1 */,1533 /* 2 */,1383 /* 3 */,1277 /* 4 */,
+ 1195 /* 5 */,1127 /* 6 */,1070 /* 7 */,1021 /* 8 */,978 /* 9 */,
+ 939 /* 10 */,903 /* 11 */,871 /* 12 */,842 /* 13 */,814 /* 14 */,
+ 789 /* 15 */,765 /* 16 */,743 /* 17 */,722 /* 18 */,702 /* 19 */,
+ 683 /* 20 */,665 /* 21 */,647 /* 22 */,631 /* 23 */,615 /* 24 */,
+ 600 /* 25 */,586 /* 26 */,572 /* 27 */,558 /* 28 */,545 /* 29 */,
+ 533 /* 30 */,521 /* 31 */,509 /* 32 */,498 /* 33 */,487 /* 34 */,
+ 476 /* 35 */,466 /* 36 */,455 /* 37 */,446 /* 38 */,436 /* 39 */,
+ 427 /* 40 */,418 /* 41 */,409 /* 42 */,400 /* 43 */,391 /* 44 */,
+ 383 /* 45 */,375 /* 46 */,367 /* 47 */,359 /* 48 */,352 /* 49 */,
+ 344 /* 50 */,337 /* 51 */,330 /* 52 */,323 /* 53 */,316 /* 54 */,
+ 309 /* 55 */,302 /* 56 */,296 /* 57 */,289 /* 58 */,283 /* 59 */,
+ 277 /* 60 */,271 /* 61 */,265 /* 62 */,259 /* 63 */,253 /* 64 */,
+ 247 /* 65 */,242 /* 66 */,236 /* 67 */,231 /* 68 */,225 /* 69 */,
+ 220 /* 70 */,215 /* 71 */,210 /* 72 */,205 /* 73 */,199 /* 74 */,
+ 195 /* 75 */,190 /* 76 */,185 /* 77 */,180 /* 78 */,175 /* 79 */,
+ 171 /* 80 */,166 /* 81 */,162 /* 82 */,157 /* 83 */,153 /* 84 */,
+ 148 /* 85 */,144 /* 86 */,140 /* 87 */,135 /* 88 */,131 /* 89 */,
+ 127 /* 90 */,123 /* 91 */,119 /* 92 */,115 /* 93 */,111 /* 94 */,
+ 107 /* 95 */,103 /* 96 */,100 /* 97 */,96 /* 98 */,92 /* 99 */,
+ 88 /* 100 */,85 /* 101 */,81 /* 102 */,77 /* 103 */,74 /* 104 */,
+ 70 /* 105 */,67 /* 106 */,63 /* 107 */,60 /* 108 */,56 /* 109 */,
+ 53 /* 110 */,50 /* 111 */,46 /* 112 */,43 /* 113 */,40 /* 114 */,
+ 37 /* 115 */,33 /* 116 */,30 /* 117 */,27 /* 118 */,24 /* 119 */,
+ 21 /* 120 */,18 /* 121 */,15 /* 122 */,12 /* 123 */,9 /* 124 */,
+ 6 /* 125 */,3 /* 126 */,0 /* 127 */,
+};
+
+#else
+
+extern unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE];
+extern unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE];
+
+#endif
diff --git a/sound/isa/gus/gus_timer.c b/sound/isa/gus/gus_timer.c
new file mode 100644
index 0000000000..047ddbc619
--- /dev/null
+++ b/sound/isa/gus/gus_timer.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Routines for Gravis UltraSound soundcards - Timers
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ * GUS have similar timers as AdLib (OPL2/OPL3 chips).
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+/*
+ * Timer 1 - 80us
+ */
+
+static int snd_gf1_timer1_start(struct snd_timer * timer)
+{
+ unsigned long flags;
+ unsigned char tmp;
+ unsigned int ticks;
+ struct snd_gus_card *gus;
+
+ gus = snd_timer_chip(timer);
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ ticks = timer->sticks;
+ tmp = (gus->gf1.timer_enabled |= 4);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1, 256 - ticks); /* timer 1 count */
+ snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* enable timer 1 IRQ */
+ snd_gf1_adlib_write(gus, 0x04, tmp >> 2); /* timer 2 start */
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return 0;
+}
+
+static int snd_gf1_timer1_stop(struct snd_timer * timer)
+{
+ unsigned long flags;
+ unsigned char tmp;
+ struct snd_gus_card *gus;
+
+ gus = snd_timer_chip(timer);
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ tmp = (gus->gf1.timer_enabled &= ~4);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* disable timer #1 */
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return 0;
+}
+
+/*
+ * Timer 2 - 320us
+ */
+
+static int snd_gf1_timer2_start(struct snd_timer * timer)
+{
+ unsigned long flags;
+ unsigned char tmp;
+ unsigned int ticks;
+ struct snd_gus_card *gus;
+
+ gus = snd_timer_chip(timer);
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ ticks = timer->sticks;
+ tmp = (gus->gf1.timer_enabled |= 8);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2, 256 - ticks); /* timer 2 count */
+ snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* enable timer 2 IRQ */
+ snd_gf1_adlib_write(gus, 0x04, tmp >> 2); /* timer 2 start */
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return 0;
+}
+
+static int snd_gf1_timer2_stop(struct snd_timer * timer)
+{
+ unsigned long flags;
+ unsigned char tmp;
+ struct snd_gus_card *gus;
+
+ gus = snd_timer_chip(timer);
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ tmp = (gus->gf1.timer_enabled &= ~8);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* disable timer #1 */
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ return 0;
+}
+
+/*
+
+ */
+
+static void snd_gf1_interrupt_timer1(struct snd_gus_card * gus)
+{
+ struct snd_timer *timer = gus->gf1.timer1;
+
+ if (timer == NULL)
+ return;
+ snd_timer_interrupt(timer, timer->sticks);
+}
+
+static void snd_gf1_interrupt_timer2(struct snd_gus_card * gus)
+{
+ struct snd_timer *timer = gus->gf1.timer2;
+
+ if (timer == NULL)
+ return;
+ snd_timer_interrupt(timer, timer->sticks);
+}
+
+/*
+
+ */
+
+static const struct snd_timer_hardware snd_gf1_timer1 =
+{
+ .flags = SNDRV_TIMER_HW_STOP,
+ .resolution = 80000,
+ .ticks = 256,
+ .start = snd_gf1_timer1_start,
+ .stop = snd_gf1_timer1_stop,
+};
+
+static const struct snd_timer_hardware snd_gf1_timer2 =
+{
+ .flags = SNDRV_TIMER_HW_STOP,
+ .resolution = 320000,
+ .ticks = 256,
+ .start = snd_gf1_timer2_start,
+ .stop = snd_gf1_timer2_stop,
+};
+
+static void snd_gf1_timer1_free(struct snd_timer *timer)
+{
+ struct snd_gus_card *gus = timer->private_data;
+ gus->gf1.timer1 = NULL;
+}
+
+static void snd_gf1_timer2_free(struct snd_timer *timer)
+{
+ struct snd_gus_card *gus = timer->private_data;
+ gus->gf1.timer2 = NULL;
+}
+
+void snd_gf1_timers_init(struct snd_gus_card * gus)
+{
+ struct snd_timer *timer;
+ struct snd_timer_id tid;
+
+ if (gus->gf1.timer1 != NULL || gus->gf1.timer2 != NULL)
+ return;
+
+ gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1;
+ gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2;
+
+ tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+ tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+ tid.card = gus->card->number;
+ tid.device = gus->timer_dev;
+ tid.subdevice = 0;
+
+ if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) {
+ strcpy(timer->name, "GF1 timer #1");
+ timer->private_data = gus;
+ timer->private_free = snd_gf1_timer1_free;
+ timer->hw = snd_gf1_timer1;
+ }
+ gus->gf1.timer1 = timer;
+
+ tid.device++;
+
+ if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) {
+ strcpy(timer->name, "GF1 timer #2");
+ timer->private_data = gus;
+ timer->private_free = snd_gf1_timer2_free;
+ timer->hw = snd_gf1_timer2;
+ }
+ gus->gf1.timer2 = timer;
+}
+
+void snd_gf1_timers_done(struct snd_gus_card * gus)
+{
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_TIMER1 | SNDRV_GF1_HANDLER_TIMER2);
+ if (gus->gf1.timer1) {
+ snd_device_free(gus->card, gus->gf1.timer1);
+ gus->gf1.timer1 = NULL;
+ }
+ if (gus->gf1.timer2) {
+ snd_device_free(gus->card, gus->gf1.timer2);
+ gus->gf1.timer2 = NULL;
+ }
+}
diff --git a/sound/isa/gus/gus_uart.c b/sound/isa/gus/gus_uart.c
new file mode 100644
index 0000000000..3975848160
--- /dev/null
+++ b/sound/isa/gus/gus_uart.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for the GF1 MIDI interface - like UART 6850
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+static void snd_gf1_interrupt_midi_in(struct snd_gus_card * gus)
+{
+ int count;
+ unsigned char stat, byte;
+ __always_unused unsigned char data;
+ unsigned long flags;
+
+ count = 10;
+ while (count) {
+ spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+ stat = snd_gf1_uart_stat(gus);
+ if (!(stat & 0x01)) { /* data in Rx FIFO? */
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+ count--;
+ continue;
+ }
+ count = 100; /* arm counter to new value */
+ data = snd_gf1_uart_get(gus);
+ if (!(gus->gf1.uart_cmd & 0x80)) {
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+ continue;
+ }
+ if (stat & 0x10) { /* framing error */
+ gus->gf1.uart_framing++;
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+ continue;
+ }
+ byte = snd_gf1_uart_get(gus);
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+ snd_rawmidi_receive(gus->midi_substream_input, &byte, 1);
+ if (stat & 0x20) {
+ gus->gf1.uart_overrun++;
+ }
+ }
+}
+
+static void snd_gf1_interrupt_midi_out(struct snd_gus_card * gus)
+{
+ char byte;
+ unsigned long flags;
+
+ /* try unlock output */
+ if (snd_gf1_uart_stat(gus) & 0x01)
+ snd_gf1_interrupt_midi_in(gus);
+
+ spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+ if (snd_gf1_uart_stat(gus) & 0x02) { /* Tx FIFO free? */
+ if (snd_rawmidi_transmit(gus->midi_substream_output, &byte, 1) != 1) { /* no other bytes or error */
+ snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); /* disable Tx interrupt */
+ } else {
+ snd_gf1_uart_put(gus, byte);
+ }
+ }
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+}
+
+static void snd_gf1_uart_reset(struct snd_gus_card * gus, int close)
+{
+ snd_gf1_uart_cmd(gus, 0x03); /* reset */
+ if (!close && gus->uart_enable) {
+ udelay(160);
+ snd_gf1_uart_cmd(gus, 0x00); /* normal operations */
+ }
+}
+
+static int snd_gf1_uart_output_open(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ struct snd_gus_card *gus;
+
+ gus = substream->rmidi->private_data;
+ spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+ if (!(gus->gf1.uart_cmd & 0x80)) { /* input active? */
+ snd_gf1_uart_reset(gus, 0);
+ }
+ gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out;
+ gus->midi_substream_output = substream;
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+#if 0
+ snd_printk(KERN_DEBUG "write init - cmd = 0x%x, stat = 0x%x\n", gus->gf1.uart_cmd, snd_gf1_uart_stat(gus));
+#endif
+ return 0;
+}
+
+static int snd_gf1_uart_input_open(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ struct snd_gus_card *gus;
+ int i;
+
+ gus = substream->rmidi->private_data;
+ spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+ if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) {
+ snd_gf1_uart_reset(gus, 0);
+ }
+ gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in;
+ gus->midi_substream_input = substream;
+ if (gus->uart_enable) {
+ for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++)
+ snd_gf1_uart_get(gus); /* clean Rx */
+ if (i >= 1000)
+ snd_printk(KERN_ERR "gus midi uart init read - cleanup error\n");
+ }
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+#if 0
+ snd_printk(KERN_DEBUG
+ "read init - enable = %i, cmd = 0x%x, stat = 0x%x\n",
+ gus->uart_enable, gus->gf1.uart_cmd, snd_gf1_uart_stat(gus));
+ snd_printk(KERN_DEBUG
+ "[0x%x] reg (ctrl/status) = 0x%x, reg (data) = 0x%x "
+ "(page = 0x%x)\n",
+ gus->gf1.port + 0x100, inb(gus->gf1.port + 0x100),
+ inb(gus->gf1.port + 0x101), inb(gus->gf1.port + 0x102));
+#endif
+ return 0;
+}
+
+static int snd_gf1_uart_output_close(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ struct snd_gus_card *gus;
+
+ gus = substream->rmidi->private_data;
+ spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+ if (gus->gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in)
+ snd_gf1_uart_reset(gus, 1);
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_OUT);
+ gus->midi_substream_output = NULL;
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+ return 0;
+}
+
+static int snd_gf1_uart_input_close(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ struct snd_gus_card *gus;
+
+ gus = substream->rmidi->private_data;
+ spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+ if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out)
+ snd_gf1_uart_reset(gus, 1);
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_IN);
+ gus->midi_substream_input = NULL;
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+ return 0;
+}
+
+static void snd_gf1_uart_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ struct snd_gus_card *gus;
+ unsigned long flags;
+
+ gus = substream->rmidi->private_data;
+
+ spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+ if (up) {
+ if ((gus->gf1.uart_cmd & 0x80) == 0)
+ snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x80); /* enable Rx interrupts */
+ } else {
+ if (gus->gf1.uart_cmd & 0x80)
+ snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x80); /* disable Rx interrupts */
+ }
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+}
+
+static void snd_gf1_uart_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ struct snd_gus_card *gus;
+ char byte;
+ int timeout;
+
+ gus = substream->rmidi->private_data;
+
+ spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+ if (up) {
+ if ((gus->gf1.uart_cmd & 0x20) == 0) {
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+ /* wait for empty Rx - Tx is probably unlocked */
+ timeout = 10000;
+ while (timeout-- > 0 && snd_gf1_uart_stat(gus) & 0x01);
+ /* Tx FIFO free? */
+ spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+ if (gus->gf1.uart_cmd & 0x20) {
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+ return;
+ }
+ if (snd_gf1_uart_stat(gus) & 0x02) {
+ if (snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+ return;
+ }
+ snd_gf1_uart_put(gus, byte);
+ }
+ snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x20); /* enable Tx interrupt */
+ }
+ } else {
+ if (gus->gf1.uart_cmd & 0x20)
+ snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20);
+ }
+ spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+}
+
+static const struct snd_rawmidi_ops snd_gf1_uart_output =
+{
+ .open = snd_gf1_uart_output_open,
+ .close = snd_gf1_uart_output_close,
+ .trigger = snd_gf1_uart_output_trigger,
+};
+
+static const struct snd_rawmidi_ops snd_gf1_uart_input =
+{
+ .open = snd_gf1_uart_input_open,
+ .close = snd_gf1_uart_input_close,
+ .trigger = snd_gf1_uart_input_trigger,
+};
+
+int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device)
+{
+ struct snd_rawmidi *rmidi;
+ int err;
+
+ err = snd_rawmidi_new(gus->card, "GF1", device, 1, 1, &rmidi);
+ if (err < 0)
+ return err;
+ strcpy(rmidi->name, gus->interwave ? "AMD InterWave" : "GF1");
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_gf1_uart_output);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_gf1_uart_input);
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->private_data = gus;
+ gus->midi_uart = rmidi;
+ return err;
+}
diff --git a/sound/isa/gus/gus_volume.c b/sound/isa/gus/gus_volume.c
new file mode 100644
index 0000000000..ed72196a36
--- /dev/null
+++ b/sound/isa/gus/gus_volume.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/time.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#define __GUS_TABLES_ALLOC__
+#include "gus_tables.h"
+
+EXPORT_SYMBOL(snd_gf1_atten_table); /* for snd-gus-synth module */
+
+unsigned short snd_gf1_lvol_to_gvol_raw(unsigned int vol)
+{
+ unsigned short e, m, tmp;
+
+ if (vol > 65535)
+ vol = 65535;
+ tmp = vol;
+ e = 7;
+ if (tmp < 128) {
+ while (e > 0 && tmp < (1 << e))
+ e--;
+ } else {
+ while (tmp > 255) {
+ tmp >>= 1;
+ e++;
+ }
+ }
+ m = vol - (1 << e);
+ if (m > 0) {
+ if (e > 8)
+ m >>= e - 8;
+ else if (e < 8)
+ m <<= 8 - e;
+ m &= 255;
+ }
+ return (e << 8) | m;
+}
+
+#if 0
+
+unsigned int snd_gf1_gvol_to_lvol_raw(unsigned short gf1_vol)
+{
+ unsigned int rvol;
+ unsigned short e, m;
+
+ if (!gf1_vol)
+ return 0;
+ e = gf1_vol >> 8;
+ m = (unsigned char) gf1_vol;
+ rvol = 1 << e;
+ if (e > 8)
+ return rvol | (m << (e - 8));
+ return rvol | (m >> (8 - e));
+}
+
+unsigned int snd_gf1_calc_ramp_rate(struct snd_gus_card * gus,
+ unsigned short start,
+ unsigned short end,
+ unsigned int us)
+{
+ static const unsigned char vol_rates[19] =
+ {
+ 23, 24, 26, 28, 29, 31, 32, 34,
+ 36, 37, 39, 40, 42, 44, 45, 47,
+ 49, 50, 52
+ };
+ unsigned short range, increment, value, i;
+
+ start >>= 4;
+ end >>= 4;
+ if (start < end)
+ us /= end - start;
+ else
+ us /= start - end;
+ range = 4;
+ value = gus->gf1.enh_mode ?
+ vol_rates[0] :
+ vol_rates[gus->gf1.active_voices - 14];
+ for (i = 0; i < 3; i++) {
+ if (us < value) {
+ range = i;
+ break;
+ } else
+ value <<= 3;
+ }
+ if (range == 4) {
+ range = 3;
+ increment = 1;
+ } else
+ increment = (value + (value >> 1)) / us;
+ return (range << 6) | (increment & 0x3f);
+}
+
+#endif /* 0 */
+
+unsigned short snd_gf1_translate_freq(struct snd_gus_card * gus, unsigned int freq16)
+{
+ freq16 >>= 3;
+ if (freq16 < 50)
+ freq16 = 50;
+ if (freq16 & 0xf8000000) {
+ freq16 = ~0xf8000000;
+ snd_printk(KERN_ERR "snd_gf1_translate_freq: overflow - freq = 0x%x\n", freq16);
+ }
+ return ((freq16 << 9) + (gus->gf1.playback_freq >> 1)) / gus->gf1.playback_freq;
+}
+
+#if 0
+
+short snd_gf1_compute_vibrato(short cents, unsigned short fc_register)
+{
+ static const short vibrato_table[] =
+ {
+ 0, 0, 32, 592, 61, 1175, 93, 1808,
+ 124, 2433, 152, 3007, 182, 3632, 213, 4290,
+ 241, 4834, 255, 5200
+ };
+
+ long depth;
+ const short *vi1, *vi2;
+ short pcents, v1;
+
+ pcents = cents < 0 ? -cents : cents;
+ for (vi1 = vibrato_table, vi2 = vi1 + 2; pcents > *vi2; vi1 = vi2, vi2 += 2);
+ v1 = *(vi1 + 1);
+ /* The FC table above is a list of pairs. The first number in the pair */
+ /* is the cents index from 0-255 cents, and the second number in the */
+ /* pair is the FC adjustment needed to change the pitch by the indexed */
+ /* number of cents. The table was created for an FC of 32768. */
+ /* The following expression does a linear interpolation against the */
+ /* approximated log curve in the table above, and then scales the number */
+ /* by the FC before the LFO. This calculation also adjusts the output */
+ /* value to produce the appropriate depth for the hardware. The depth */
+ /* is 2 * desired FC + 1. */
+ depth = (((int) (*(vi2 + 1) - *vi1) * (pcents - *vi1) / (*vi2 - *vi1)) + v1) * fc_register >> 14;
+ if (depth)
+ depth++;
+ if (depth > 255)
+ depth = 255;
+ return cents < 0 ? -(short) depth : (short) depth;
+}
+
+unsigned short snd_gf1_compute_pitchbend(unsigned short pitchbend, unsigned short sens)
+{
+ static const long log_table[] = {1024, 1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825, 1933};
+ int wheel, sensitivity;
+ unsigned int mantissa, f1, f2;
+ unsigned short semitones, f1_index, f2_index, f1_power, f2_power;
+ char bend_down = 0;
+ int bend;
+
+ if (!sens)
+ return 1024;
+ wheel = (int) pitchbend - 8192;
+ sensitivity = ((int) sens * wheel) / 128;
+ if (sensitivity < 0) {
+ bend_down = 1;
+ sensitivity = -sensitivity;
+ }
+ semitones = (unsigned int) (sensitivity >> 13);
+ mantissa = sensitivity % 8192;
+ f1_index = semitones % 12;
+ f2_index = (semitones + 1) % 12;
+ f1_power = semitones / 12;
+ f2_power = (semitones + 1) / 12;
+ f1 = log_table[f1_index] << f1_power;
+ f2 = log_table[f2_index] << f2_power;
+ bend = (int) ((((f2 - f1) * mantissa) >> 13) + f1);
+ if (bend_down)
+ bend = 1048576L / bend;
+ return bend;
+}
+
+unsigned short snd_gf1_compute_freq(unsigned int freq,
+ unsigned int rate,
+ unsigned short mix_rate)
+{
+ unsigned int fc;
+ int scale = 0;
+
+ while (freq >= 4194304L) {
+ scale++;
+ freq >>= 1;
+ }
+ fc = (freq << 10) / rate;
+ if (fc > 97391L) {
+ fc = 97391;
+ snd_printk(KERN_ERR "patch: (1) fc frequency overflow - %u\n", fc);
+ }
+ fc = (fc * 44100UL) / mix_rate;
+ while (scale--)
+ fc <<= 1;
+ if (fc > 65535L) {
+ fc = 65535;
+ snd_printk(KERN_ERR "patch: (2) fc frequency overflow - %u\n", fc);
+ }
+ return (unsigned short) fc;
+}
+
+#endif /* 0 */
diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c
new file mode 100644
index 0000000000..09cc53ceea
--- /dev/null
+++ b/sound/isa/gus/gusclassic.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Gravis UltraSound Classic soundcard
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define CRD_NAME "Gravis UltraSound Classic"
+#define DEV_NAME "gusclassic"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x230,0x240,0x250,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+ /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for " CRD_NAME " driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver.");
+
+static int snd_gusclassic_match(struct device *dev, unsigned int n)
+{
+ return enable[n];
+}
+
+static int snd_gusclassic_create(struct snd_card *card,
+ struct device *dev, unsigned int n,
+ struct snd_gus_card **rgus)
+{
+ static const long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260};
+ static const int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, 4, -1};
+ static const int possible_dmas[] = {5, 6, 7, 1, 3, -1};
+
+ int i, error;
+
+ if (irq[n] == SNDRV_AUTO_IRQ) {
+ irq[n] = snd_legacy_find_free_irq(possible_irqs);
+ if (irq[n] < 0) {
+ dev_err(dev, "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (dma1[n] == SNDRV_AUTO_DMA) {
+ dma1[n] = snd_legacy_find_free_dma(possible_dmas);
+ if (dma1[n] < 0) {
+ dev_err(dev, "unable to find a free DMA1\n");
+ return -EBUSY;
+ }
+ }
+ if (dma2[n] == SNDRV_AUTO_DMA) {
+ dma2[n] = snd_legacy_find_free_dma(possible_dmas);
+ if (dma2[n] < 0) {
+ dev_err(dev, "unable to find a free DMA2\n");
+ return -EBUSY;
+ }
+ }
+
+ if (port[n] != SNDRV_AUTO_PORT)
+ return snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n],
+ 0, channels[n], pcm_channels[n], 0, rgus);
+
+ i = 0;
+ do {
+ port[n] = possible_ports[i];
+ error = snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n],
+ 0, channels[n], pcm_channels[n], 0, rgus);
+ } while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
+
+ return error;
+}
+
+static int snd_gusclassic_detect(struct snd_gus_card *gus)
+{
+ unsigned char d;
+
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */
+ d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET);
+ if ((d & 0x07) != 0) {
+ snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+ return -ENODEV;
+ }
+ udelay(160);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */
+ udelay(160);
+ d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET);
+ if ((d & 0x07) != 1) {
+ snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static int snd_gusclassic_probe(struct device *dev, unsigned int n)
+{
+ struct snd_card *card;
+ struct snd_gus_card *gus;
+ int error;
+
+ error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
+ if (error < 0)
+ return error;
+
+ if (pcm_channels[n] < 2)
+ pcm_channels[n] = 2;
+
+ error = snd_gusclassic_create(card, dev, n, &gus);
+ if (error < 0)
+ return error;
+
+ error = snd_gusclassic_detect(gus);
+ if (error < 0)
+ return error;
+
+ gus->joystick_dac = joystick_dac[n];
+
+ error = snd_gus_initialize(gus);
+ if (error < 0)
+ return error;
+
+ error = -ENODEV;
+ if (gus->max_flag || gus->ess_flag) {
+ dev_err(dev, "GUS Classic or ACE soundcard was "
+ "not detected at 0x%lx\n", gus->gf1.port);
+ return error;
+ }
+
+ error = snd_gf1_new_mixer(gus);
+ if (error < 0)
+ return error;
+
+ error = snd_gf1_pcm_new(gus, 0, 0);
+ if (error < 0)
+ return error;
+
+ if (!gus->ace_flag) {
+ error = snd_gf1_rawmidi_new(gus, 0);
+ if (error < 0)
+ return error;
+ }
+
+ sprintf(card->longname + strlen(card->longname),
+ " at 0x%lx, irq %d, dma %d",
+ gus->gf1.port, gus->gf1.irq, gus->gf1.dma1);
+
+ if (gus->gf1.dma2 >= 0)
+ sprintf(card->longname + strlen(card->longname),
+ "&%d", gus->gf1.dma2);
+
+ error = snd_card_register(card);
+ if (error < 0)
+ return error;
+
+ dev_set_drvdata(dev, card);
+ return 0;
+}
+
+static struct isa_driver snd_gusclassic_driver = {
+ .match = snd_gusclassic_match,
+ .probe = snd_gusclassic_probe,
+#if 0 /* FIXME */
+ .suspend = snd_gusclassic_suspend,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ }
+};
+
+module_isa_driver(snd_gusclassic_driver, SNDRV_CARDS);
diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c
new file mode 100644
index 0000000000..63d9f2d75d
--- /dev/null
+++ b/sound/isa/gus/gusextreme.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Gravis UltraSound Extreme soundcards
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/es1688.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define CRD_NAME "Gravis UltraSound Extreme"
+#define DEV_NAME "gusextreme"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */
+static long gf1_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x210,0x220,0x230,0x240,0x250,0x260,0x270 */
+static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x300,0x310,0x320 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */
+static int gf1_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+ /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(gf1_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(gf1_port, "GF1 port # for " CRD_NAME " driver (optional).");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(gf1_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(gf1_irq, "GF1 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "GF1 DMA # for " CRD_NAME " driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for " CRD_NAME " driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver.");
+
+static int snd_gusextreme_match(struct device *dev, unsigned int n)
+{
+ return enable[n];
+}
+
+static int snd_gusextreme_es1688_create(struct snd_card *card,
+ struct snd_es1688 *chip,
+ struct device *dev, unsigned int n)
+{
+ static const long possible_ports[] = {0x220, 0x240, 0x260};
+ static const int possible_irqs[] = {5, 9, 10, 7, -1};
+ static const int possible_dmas[] = {1, 3, 0, -1};
+
+ int i, error;
+
+ if (irq[n] == SNDRV_AUTO_IRQ) {
+ irq[n] = snd_legacy_find_free_irq(possible_irqs);
+ if (irq[n] < 0) {
+ dev_err(dev, "unable to find a free IRQ for ES1688\n");
+ return -EBUSY;
+ }
+ }
+ if (dma8[n] == SNDRV_AUTO_DMA) {
+ dma8[n] = snd_legacy_find_free_dma(possible_dmas);
+ if (dma8[n] < 0) {
+ dev_err(dev, "unable to find a free DMA for ES1688\n");
+ return -EBUSY;
+ }
+ }
+
+ if (port[n] != SNDRV_AUTO_PORT)
+ return snd_es1688_create(card, chip, port[n], mpu_port[n],
+ irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688);
+
+ i = 0;
+ do {
+ port[n] = possible_ports[i];
+ error = snd_es1688_create(card, chip, port[n], mpu_port[n],
+ irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688);
+ } while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
+
+ return error;
+}
+
+static int snd_gusextreme_gus_card_create(struct snd_card *card,
+ struct device *dev, unsigned int n,
+ struct snd_gus_card **rgus)
+{
+ static const int possible_irqs[] = {11, 12, 15, 9, 5, 7, 3, -1};
+ static const int possible_dmas[] = {5, 6, 7, 3, 1, -1};
+
+ if (gf1_irq[n] == SNDRV_AUTO_IRQ) {
+ gf1_irq[n] = snd_legacy_find_free_irq(possible_irqs);
+ if (gf1_irq[n] < 0) {
+ dev_err(dev, "unable to find a free IRQ for GF1\n");
+ return -EBUSY;
+ }
+ }
+ if (dma1[n] == SNDRV_AUTO_DMA) {
+ dma1[n] = snd_legacy_find_free_dma(possible_dmas);
+ if (dma1[n] < 0) {
+ dev_err(dev, "unable to find a free DMA for GF1\n");
+ return -EBUSY;
+ }
+ }
+ return snd_gus_create(card, gf1_port[n], gf1_irq[n], dma1[n], -1,
+ 0, channels[n], pcm_channels[n], 0, rgus);
+}
+
+static int snd_gusextreme_detect(struct snd_gus_card *gus,
+ struct snd_es1688 *es1688)
+{
+ unsigned long flags;
+ unsigned char d;
+
+ /*
+ * This is main stuff - enable access to GF1 chip...
+ * I'm not sure, if this will work for card which have
+ * ES1688 chip in another place than 0x220.
+ *
+ * I used reverse-engineering in DOSEMU. [--jk]
+ *
+ * ULTRINIT.EXE:
+ * 0x230 = 0,2,3
+ * 0x240 = 2,0,1
+ * 0x250 = 2,0,3
+ * 0x260 = 2,2,1
+ */
+
+ spin_lock_irqsave(&es1688->mixer_lock, flags);
+ snd_es1688_mixer_write(es1688, 0x40, 0x0b); /* don't change!!! */
+ spin_unlock_irqrestore(&es1688->mixer_lock, flags);
+
+ spin_lock_irqsave(&es1688->reg_lock, flags);
+ outb(gus->gf1.port & 0x040 ? 2 : 0, ES1688P(es1688, INIT1));
+ outb(0, 0x201);
+ outb(gus->gf1.port & 0x020 ? 2 : 0, ES1688P(es1688, INIT1));
+ outb(0, 0x201);
+ outb(gus->gf1.port & 0x010 ? 3 : 1, ES1688P(es1688, INIT1));
+ spin_unlock_irqrestore(&es1688->reg_lock, flags);
+
+ udelay(100);
+
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */
+ d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET);
+ if ((d & 0x07) != 0) {
+ snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+ return -EIO;
+ }
+ udelay(160);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */
+ udelay(160);
+ d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET);
+ if ((d & 0x07) != 1) {
+ snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int snd_gusextreme_mixer(struct snd_card *card)
+{
+ struct snd_ctl_elem_id id1, id2;
+ int error;
+
+ memset(&id1, 0, sizeof(id1));
+ memset(&id2, 0, sizeof(id2));
+ id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+
+ /* reassign AUX to SYNTHESIZER */
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "Synth Playback Volume");
+ error = snd_ctl_rename_id(card, &id1, &id2);
+ if (error < 0)
+ return error;
+
+ /* reassign Master Playback Switch to Synth Playback Switch */
+ strcpy(id1.name, "Master Playback Switch");
+ strcpy(id2.name, "Synth Playback Switch");
+ error = snd_ctl_rename_id(card, &id1, &id2);
+ if (error < 0)
+ return error;
+
+ return 0;
+}
+
+static int snd_gusextreme_probe(struct device *dev, unsigned int n)
+{
+ struct snd_card *card;
+ struct snd_gus_card *gus;
+ struct snd_es1688 *es1688;
+ struct snd_opl3 *opl3;
+ int error;
+
+ error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE,
+ sizeof(struct snd_es1688), &card);
+ if (error < 0)
+ return error;
+
+ es1688 = card->private_data;
+
+ if (mpu_port[n] == SNDRV_AUTO_PORT)
+ mpu_port[n] = 0;
+
+ if (mpu_irq[n] > 15)
+ mpu_irq[n] = -1;
+
+ error = snd_gusextreme_es1688_create(card, es1688, dev, n);
+ if (error < 0)
+ return error;
+
+ if (gf1_port[n] < 0)
+ gf1_port[n] = es1688->port + 0x20;
+
+ error = snd_gusextreme_gus_card_create(card, dev, n, &gus);
+ if (error < 0)
+ return error;
+
+ error = snd_gusextreme_detect(gus, es1688);
+ if (error < 0)
+ return error;
+
+ gus->joystick_dac = joystick_dac[n];
+
+ error = snd_gus_initialize(gus);
+ if (error < 0)
+ return error;
+
+ error = -ENODEV;
+ if (!gus->ess_flag) {
+ dev_err(dev, "GUS Extreme soundcard was not "
+ "detected at 0x%lx\n", gus->gf1.port);
+ return error;
+ }
+ gus->codec_flag = 1;
+
+ error = snd_es1688_pcm(card, es1688, 0);
+ if (error < 0)
+ return error;
+
+ error = snd_es1688_mixer(card, es1688);
+ if (error < 0)
+ return error;
+
+ snd_component_add(card, "ES1688");
+
+ if (pcm_channels[n] > 0) {
+ error = snd_gf1_pcm_new(gus, 1, 1);
+ if (error < 0)
+ return error;
+ }
+
+ error = snd_gf1_new_mixer(gus);
+ if (error < 0)
+ return error;
+
+ error = snd_gusextreme_mixer(card);
+ if (error < 0)
+ return error;
+
+ if (snd_opl3_create(card, es1688->port, es1688->port + 2,
+ OPL3_HW_OPL3, 0, &opl3) < 0)
+ dev_warn(dev, "opl3 not detected at 0x%lx\n", es1688->port);
+ else {
+ error = snd_opl3_hwdep_new(opl3, 0, 2, NULL);
+ if (error < 0)
+ return error;
+ }
+
+ if (es1688->mpu_port >= 0x300) {
+ error = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688,
+ es1688->mpu_port, 0, mpu_irq[n], NULL);
+ if (error < 0)
+ return error;
+ }
+
+ sprintf(card->longname, "Gravis UltraSound Extreme at 0x%lx, "
+ "irq %i&%i, dma %i&%i", es1688->port,
+ gus->gf1.irq, es1688->irq, gus->gf1.dma1, es1688->dma8);
+
+ error = snd_card_register(card);
+ if (error < 0)
+ return error;
+
+ dev_set_drvdata(dev, card);
+ return 0;
+}
+
+static struct isa_driver snd_gusextreme_driver = {
+ .match = snd_gusextreme_match,
+ .probe = snd_gusextreme_probe,
+#if 0 /* FIXME */
+ .suspend = snd_gusextreme_suspend,
+ .resume = snd_gusextreme_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ }
+};
+
+module_isa_driver(snd_gusextreme_driver, SNDRV_CARDS);
diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c
new file mode 100644
index 0000000000..6834c05600
--- /dev/null
+++ b/sound/isa/gus/gusmax.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Gravis UltraSound MAX soundcard
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/wss.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Gravis UltraSound MAX");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x230,0x240,0x250,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+ /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for GUS MAX soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for GUS MAX soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable GUS MAX soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for GUS MAX driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for GUS MAX driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for GUS MAX driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for GUS MAX driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS MAX driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "Used GF1 channels for GUS MAX driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS MAX driver.");
+
+struct snd_gusmax {
+ int irq;
+ struct snd_card *card;
+ struct snd_gus_card *gus;
+ struct snd_wss *wss;
+ unsigned short gus_status_reg;
+ unsigned short pcm_status_reg;
+};
+
+#define PFX "gusmax: "
+
+static int snd_gusmax_detect(struct snd_gus_card *gus)
+{
+ unsigned char d;
+
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */
+ d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET);
+ if ((d & 0x07) != 0) {
+ snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+ return -ENODEV;
+ }
+ udelay(160);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */
+ udelay(160);
+ d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET);
+ if ((d & 0x07) != 1) {
+ snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static irqreturn_t snd_gusmax_interrupt(int irq, void *dev_id)
+{
+ struct snd_gusmax *maxcard = dev_id;
+ int loop, max = 5;
+ int handled = 0;
+
+ do {
+ loop = 0;
+ if (inb(maxcard->gus_status_reg)) {
+ handled = 1;
+ snd_gus_interrupt(irq, maxcard->gus);
+ loop++;
+ }
+ if (inb(maxcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */
+ handled = 1;
+ snd_wss_interrupt(irq, maxcard->wss);
+ loop++;
+ }
+ } while (loop && --max > 0);
+ return IRQ_RETVAL(handled);
+}
+
+static void snd_gusmax_init(int dev, struct snd_card *card,
+ struct snd_gus_card *gus)
+{
+ gus->equal_irq = 1;
+ gus->codec_flag = 1;
+ gus->joystick_dac = joystick_dac[dev];
+ /* init control register */
+ gus->max_cntrl_val = (gus->gf1.port >> 4) & 0x0f;
+ if (gus->gf1.dma1 > 3)
+ gus->max_cntrl_val |= 0x10;
+ if (gus->gf1.dma2 > 3)
+ gus->max_cntrl_val |= 0x20;
+ gus->max_cntrl_val |= 0x40;
+ outb(gus->max_cntrl_val, GUSP(gus, MAXCNTRLPORT));
+}
+
+static int snd_gusmax_mixer(struct snd_wss *chip)
+{
+ struct snd_card *card = chip->card;
+ struct snd_ctl_elem_id id1, id2;
+ int err;
+
+ memset(&id1, 0, sizeof(id1));
+ memset(&id2, 0, sizeof(id2));
+ id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ /* reassign AUXA to SYNTHESIZER */
+ strcpy(id1.name, "Aux Playback Switch");
+ strcpy(id2.name, "Synth Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "Synth Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ /* reassign AUXB to CD */
+ strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+ strcpy(id2.name, "CD Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "CD Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+#if 0
+ /* reassign Mono Input to MIC */
+ if (snd_mixer_group_rename(mixer,
+ SNDRV_MIXER_IN_MONO, 0,
+ SNDRV_MIXER_IN_MIC, 0) < 0)
+ goto __error;
+ if (snd_mixer_elem_rename(mixer,
+ SNDRV_MIXER_IN_MONO, 0, SNDRV_MIXER_ETYPE_INPUT,
+ SNDRV_MIXER_IN_MIC, 0) < 0)
+ goto __error;
+ if (snd_mixer_elem_rename(mixer,
+ "Mono Capture Volume", 0, SNDRV_MIXER_ETYPE_VOLUME1,
+ "Mic Capture Volume", 0) < 0)
+ goto __error;
+ if (snd_mixer_elem_rename(mixer,
+ "Mono Capture Switch", 0, SNDRV_MIXER_ETYPE_SWITCH1,
+ "Mic Capture Switch", 0) < 0)
+ goto __error;
+#endif
+ return 0;
+}
+
+static int snd_gusmax_match(struct device *pdev, unsigned int dev)
+{
+ return enable[dev];
+}
+
+static int snd_gusmax_probe(struct device *pdev, unsigned int dev)
+{
+ static const int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1};
+ static const int possible_dmas[] = {5, 6, 7, 1, 3, -1};
+ int xirq, xdma1, xdma2, err;
+ struct snd_card *card;
+ struct snd_gus_card *gus = NULL;
+ struct snd_wss *wss;
+ struct snd_gusmax *maxcard;
+
+ err = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_gusmax), &card);
+ if (err < 0)
+ return err;
+ maxcard = card->private_data;
+ maxcard->card = card;
+ maxcard->irq = -1;
+
+ xirq = irq[dev];
+ if (xirq == SNDRV_AUTO_IRQ) {
+ xirq = snd_legacy_find_free_irq(possible_irqs);
+ if (xirq < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ xdma1 = dma1[dev];
+ if (xdma1 == SNDRV_AUTO_DMA) {
+ xdma1 = snd_legacy_find_free_dma(possible_dmas);
+ if (xdma1 < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free DMA1\n");
+ return -EBUSY;
+ }
+ }
+ xdma2 = dma2[dev];
+ if (xdma2 == SNDRV_AUTO_DMA) {
+ xdma2 = snd_legacy_find_free_dma(possible_dmas);
+ if (xdma2 < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free DMA2\n");
+ return -EBUSY;
+ }
+ }
+
+ if (port[dev] != SNDRV_AUTO_PORT) {
+ err = snd_gus_create(card,
+ port[dev],
+ -xirq, xdma1, xdma2,
+ 0, channels[dev],
+ pcm_channels[dev],
+ 0, &gus);
+ } else {
+ static const unsigned long possible_ports[] = {
+ 0x220, 0x230, 0x240, 0x250, 0x260
+ };
+ int i;
+ for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+ err = snd_gus_create(card,
+ possible_ports[i],
+ -xirq, xdma1, xdma2,
+ 0, channels[dev],
+ pcm_channels[dev],
+ 0, &gus);
+ if (err >= 0) {
+ port[dev] = possible_ports[i];
+ break;
+ }
+ }
+ }
+ if (err < 0)
+ return err;
+
+ err = snd_gusmax_detect(gus);
+ if (err < 0)
+ return err;
+
+ maxcard->gus_status_reg = gus->gf1.reg_irqstat;
+ maxcard->pcm_status_reg = gus->gf1.port + 0x10c + 2;
+ snd_gusmax_init(dev, card, gus);
+ err = snd_gus_initialize(gus);
+ if (err < 0)
+ return err;
+
+ if (!gus->max_flag) {
+ snd_printk(KERN_ERR PFX "GUS MAX soundcard was not detected at 0x%lx\n", gus->gf1.port);
+ return -ENODEV;
+ }
+
+ if (devm_request_irq(card->dev, xirq, snd_gusmax_interrupt, 0,
+ "GUS MAX", (void *)maxcard)) {
+ snd_printk(KERN_ERR PFX "unable to grab IRQ %d\n", xirq);
+ return -EBUSY;
+ }
+ maxcard->irq = xirq;
+ card->sync_irq = maxcard->irq;
+
+ err = snd_wss_create(card,
+ gus->gf1.port + 0x10c, -1, xirq,
+ xdma2 < 0 ? xdma1 : xdma2, xdma1,
+ WSS_HW_DETECT,
+ WSS_HWSHARE_IRQ |
+ WSS_HWSHARE_DMA1 |
+ WSS_HWSHARE_DMA2,
+ &wss);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_pcm(wss, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_mixer(wss);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_timer(wss, 2);
+ if (err < 0)
+ return err;
+
+ if (pcm_channels[dev] > 0) {
+ err = snd_gf1_pcm_new(gus, 1, 1);
+ if (err < 0)
+ return err;
+ }
+ err = snd_gusmax_mixer(wss);
+ if (err < 0)
+ return err;
+
+ err = snd_gf1_rawmidi_new(gus, 0);
+ if (err < 0)
+ return err;
+
+ sprintf(card->longname + strlen(card->longname), " at 0x%lx, irq %i, dma %i", gus->gf1.port, xirq, xdma1);
+ if (xdma2 >= 0)
+ sprintf(card->longname + strlen(card->longname), "&%i", xdma2);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+
+ maxcard->gus = gus;
+ maxcard->wss = wss;
+
+ dev_set_drvdata(pdev, card);
+ return 0;
+}
+
+#define DEV_NAME "gusmax"
+
+static struct isa_driver snd_gusmax_driver = {
+ .match = snd_gusmax_match,
+ .probe = snd_gusmax_probe,
+ /* FIXME: suspend/resume */
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+module_isa_driver(snd_gusmax_driver, SNDRV_CARDS);
diff --git a/sound/isa/gus/interwave-stb.c b/sound/isa/gus/interwave-stb.c
new file mode 100644
index 0000000000..dbe4f48a98
--- /dev/null
+++ b/sound/isa/gus/interwave-stb.c
@@ -0,0 +1,2 @@
+#define SNDRV_STB
+#include "interwave.c"
diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c
new file mode 100644
index 0000000000..a04a9d3253
--- /dev/null
+++ b/sound/isa/gus/interwave.c
@@ -0,0 +1,912 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for AMD InterWave soundcard
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ * 1999/07/22 Erik Inge Bolso <knan@mo.himolde.no>
+ * * mixer group handlers
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/wss.h>
+#ifdef SNDRV_STB
+#include <sound/tea6330t.h>
+#endif
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+#ifndef SNDRV_STB
+MODULE_DESCRIPTION("AMD InterWave");
+#else
+MODULE_DESCRIPTION("AMD InterWave STB with TEA6330T");
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x210,0x220,0x230,0x240,0x250,0x260 */
+#ifdef SNDRV_STB
+static long port_tc[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x350,0x360,0x370,0x380 */
+#endif
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+ /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int midi[SNDRV_CARDS];
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+static int effect[SNDRV_CARDS];
+
+#ifdef SNDRV_STB
+#define PFX "interwave-stb: "
+#define INTERWAVE_DRIVER "snd_interwave_stb"
+#define INTERWAVE_PNP_DRIVER "interwave-stb"
+#else
+#define PFX "interwave: "
+#define INTERWAVE_DRIVER "snd_interwave"
+#define INTERWAVE_PNP_DRIVER "interwave"
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for InterWave soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for InterWave soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable InterWave soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard.");
+#endif
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for InterWave driver.");
+#ifdef SNDRV_STB
+module_param_hw_array(port_tc, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port_tc, "Tone control (TEA6330T - i2c bus) port # for InterWave driver.");
+#endif
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for InterWave driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for InterWave driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for InterWave driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for InterWave driver.");
+module_param_array(midi, int, NULL, 0444);
+MODULE_PARM_DESC(midi, "MIDI UART enable for InterWave driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for InterWave driver.");
+module_param_array(effect, int, NULL, 0444);
+MODULE_PARM_DESC(effect, "Effects enable for InterWave driver.");
+
+struct snd_interwave {
+ int irq;
+ struct snd_card *card;
+ struct snd_gus_card *gus;
+ struct snd_wss *wss;
+#ifdef SNDRV_STB
+ struct resource *i2c_res;
+#endif
+ unsigned short gus_status_reg;
+ unsigned short pcm_status_reg;
+#ifdef CONFIG_PNP
+ struct pnp_dev *dev;
+#ifdef SNDRV_STB
+ struct pnp_dev *devtc;
+#endif
+#endif
+};
+
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+
+static const struct pnp_card_device_id snd_interwave_pnpids[] = {
+#ifndef SNDRV_STB
+ /* Gravis UltraSound Plug & Play */
+ { .id = "GRV0001", .devs = { { .id = "GRV0000" } } },
+ /* STB SoundRage32 */
+ { .id = "STB011a", .devs = { { .id = "STB0010" } } },
+ /* MED3210 */
+ { .id = "DXP3201", .devs = { { .id = "DXP0010" } } },
+ /* Dynasonic Pro */
+ /* This device also have CDC1117:DynaSonix Pro Audio Effects Processor */
+ { .id = "CDC1111", .devs = { { .id = "CDC1112" } } },
+ /* Panasonic PCA761AW Audio Card */
+ { .id = "ADV55ff", .devs = { { .id = "ADV0010" } } },
+ /* InterWave STB without TEA6330T */
+ { .id = "ADV550a", .devs = { { .id = "ADV0010" } } },
+#else
+ /* InterWave STB with TEA6330T */
+ { .id = "ADV550a", .devs = { { .id = "ADV0010" }, { .id = "ADV0015" } } },
+#endif
+ { .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_interwave_pnpids);
+
+#endif /* CONFIG_PNP */
+
+
+#ifdef SNDRV_STB
+static void snd_interwave_i2c_setlines(struct snd_i2c_bus *bus, int ctrl, int data)
+{
+ unsigned long port = bus->private_value;
+
+#if 0
+ printk(KERN_DEBUG "i2c_setlines - 0x%lx <- %i,%i\n", port, ctrl, data);
+#endif
+ outb((data << 1) | ctrl, port);
+ udelay(10);
+}
+
+static int snd_interwave_i2c_getclockline(struct snd_i2c_bus *bus)
+{
+ unsigned long port = bus->private_value;
+ unsigned char res;
+
+ res = inb(port) & 1;
+#if 0
+ printk(KERN_DEBUG "i2c_getclockline - 0x%lx -> %i\n", port, res);
+#endif
+ return res;
+}
+
+static int snd_interwave_i2c_getdataline(struct snd_i2c_bus *bus, int ack)
+{
+ unsigned long port = bus->private_value;
+ unsigned char res;
+
+ if (ack)
+ udelay(10);
+ res = (inb(port) & 2) >> 1;
+#if 0
+ printk(KERN_DEBUG "i2c_getdataline - 0x%lx -> %i\n", port, res);
+#endif
+ return res;
+}
+
+static struct snd_i2c_bit_ops snd_interwave_i2c_bit_ops = {
+ .setlines = snd_interwave_i2c_setlines,
+ .getclock = snd_interwave_i2c_getclockline,
+ .getdata = snd_interwave_i2c_getdataline,
+};
+
+static int snd_interwave_detect_stb(struct snd_interwave *iwcard,
+ struct snd_gus_card *gus, int dev,
+ struct snd_i2c_bus **rbus)
+{
+ unsigned long port;
+ struct snd_i2c_bus *bus;
+ struct snd_card *card = iwcard->card;
+ char name[32];
+ int err;
+
+ *rbus = NULL;
+ port = port_tc[dev];
+ if (port == SNDRV_AUTO_PORT) {
+ port = 0x350;
+ if (gus->gf1.port == 0x250) {
+ port = 0x360;
+ }
+ while (port <= 0x380) {
+ iwcard->i2c_res = devm_request_region(card->dev, port, 1,
+ "InterWave (I2C bus)");
+ if (iwcard->i2c_res)
+ break;
+ port += 0x10;
+ }
+ } else {
+ iwcard->i2c_res = devm_request_region(card->dev, port, 1,
+ "InterWave (I2C bus)");
+ }
+ if (iwcard->i2c_res == NULL) {
+ snd_printk(KERN_ERR "interwave: can't grab i2c bus port\n");
+ return -ENODEV;
+ }
+
+ sprintf(name, "InterWave-%i", card->number);
+ err = snd_i2c_bus_create(card, name, NULL, &bus);
+ if (err < 0)
+ return err;
+ bus->private_value = port;
+ bus->hw_ops.bit = &snd_interwave_i2c_bit_ops;
+ err = snd_tea6330t_detect(bus, 0);
+ if (err < 0)
+ return err;
+ *rbus = bus;
+ return 0;
+}
+#endif
+
+static int snd_interwave_detect(struct snd_interwave *iwcard,
+ struct snd_gus_card *gus,
+ int dev
+#ifdef SNDRV_STB
+ , struct snd_i2c_bus **rbus
+#endif
+ )
+{
+ unsigned long flags;
+ unsigned char rev1, rev2;
+ int d;
+
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */
+ d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET);
+ if ((d & 0x07) != 0) {
+ snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+ return -ENODEV;
+ }
+ udelay(160);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */
+ udelay(160);
+ d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET);
+ if ((d & 0x07) != 1) {
+ snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+ return -ENODEV;
+ }
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ rev1 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, ~rev1);
+ rev2 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, rev1);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ snd_printdd("[0x%lx] InterWave check - rev1=0x%x, rev2=0x%x\n", gus->gf1.port, rev1, rev2);
+ if ((rev1 & 0xf0) == (rev2 & 0xf0) &&
+ (rev1 & 0x0f) != (rev2 & 0x0f)) {
+ snd_printdd("[0x%lx] InterWave check - passed\n", gus->gf1.port);
+ gus->interwave = 1;
+ strcpy(gus->card->shortname, "AMD InterWave");
+ gus->revision = rev1 >> 4;
+#ifndef SNDRV_STB
+ return 0; /* ok.. We have an InterWave board */
+#else
+ return snd_interwave_detect_stb(iwcard, gus, dev, rbus);
+#endif
+ }
+ snd_printdd("[0x%lx] InterWave check - failed\n", gus->gf1.port);
+ return -ENODEV;
+}
+
+static irqreturn_t snd_interwave_interrupt(int irq, void *dev_id)
+{
+ struct snd_interwave *iwcard = dev_id;
+ int loop, max = 5;
+ int handled = 0;
+
+ do {
+ loop = 0;
+ if (inb(iwcard->gus_status_reg)) {
+ handled = 1;
+ snd_gus_interrupt(irq, iwcard->gus);
+ loop++;
+ }
+ if (inb(iwcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */
+ handled = 1;
+ snd_wss_interrupt(irq, iwcard->wss);
+ loop++;
+ }
+ } while (loop && --max > 0);
+ return IRQ_RETVAL(handled);
+}
+
+static void snd_interwave_reset(struct snd_gus_card *gus)
+{
+ snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x00);
+ udelay(160);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x01);
+ udelay(160);
+}
+
+static void snd_interwave_bank_sizes(struct snd_gus_card *gus, int *sizes)
+{
+ unsigned int idx;
+ unsigned int local;
+ unsigned char d;
+
+ for (idx = 0; idx < 4; idx++) {
+ sizes[idx] = 0;
+ d = 0x55;
+ for (local = idx << 22;
+ local < (idx << 22) + 0x400000;
+ local += 0x40000, d++) {
+ snd_gf1_poke(gus, local, d);
+ snd_gf1_poke(gus, local + 1, d + 1);
+#if 0
+ printk(KERN_DEBUG "d = 0x%x, local = 0x%x, "
+ "local + 1 = 0x%x, idx << 22 = 0x%x\n",
+ d,
+ snd_gf1_peek(gus, local),
+ snd_gf1_peek(gus, local + 1),
+ snd_gf1_peek(gus, idx << 22));
+#endif
+ if (snd_gf1_peek(gus, local) != d ||
+ snd_gf1_peek(gus, local + 1) != d + 1 ||
+ snd_gf1_peek(gus, idx << 22) != 0x55)
+ break;
+ sizes[idx]++;
+ }
+ }
+#if 0
+ printk(KERN_DEBUG "sizes: %i %i %i %i\n",
+ sizes[0], sizes[1], sizes[2], sizes[3]);
+#endif
+}
+
+struct rom_hdr {
+ /* 000 */ unsigned char iwave[8];
+ /* 008 */ unsigned char rom_hdr_revision;
+ /* 009 */ unsigned char series_number;
+ /* 010 */ unsigned char series_name[16];
+ /* 026 */ unsigned char date[10];
+ /* 036 */ unsigned short vendor_revision_major;
+ /* 038 */ unsigned short vendor_revision_minor;
+ /* 040 */ unsigned int rom_size;
+ /* 044 */ unsigned char copyright[128];
+ /* 172 */ unsigned char vendor_name[64];
+ /* 236 */ unsigned char rom_description[128];
+ /* 364 */ unsigned char pad[147];
+ /* 511 */ unsigned char csum;
+};
+
+static void snd_interwave_detect_memory(struct snd_gus_card *gus)
+{
+ static const unsigned int lmc[13] =
+ {
+ 0x00000001, 0x00000101, 0x01010101, 0x00000401,
+ 0x04040401, 0x00040101, 0x04040101, 0x00000004,
+ 0x00000404, 0x04040404, 0x00000010, 0x00001010,
+ 0x10101010
+ };
+
+ int bank_pos, pages;
+ unsigned int i, lmct;
+ int psizes[4];
+ unsigned char iwave[8];
+ unsigned char csum;
+
+ snd_interwave_reset(gus);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); /* enhanced mode */
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); /* DRAM I/O cycles selected */
+ snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff10) | 0x004c);
+ /* ok.. simple test of memory size */
+ pages = 0;
+ snd_gf1_poke(gus, 0, 0x55);
+ snd_gf1_poke(gus, 1, 0xaa);
+#if 1
+ if (snd_gf1_peek(gus, 0) == 0x55 && snd_gf1_peek(gus, 1) == 0xaa)
+#else
+ if (0) /* ok.. for testing of 0k RAM */
+#endif
+ {
+ snd_interwave_bank_sizes(gus, psizes);
+ lmct = (psizes[3] << 24) | (psizes[2] << 16) |
+ (psizes[1] << 8) | psizes[0];
+#if 0
+ printk(KERN_DEBUG "lmct = 0x%08x\n", lmct);
+#endif
+ for (i = 0; i < ARRAY_SIZE(lmc); i++)
+ if (lmct == lmc[i]) {
+#if 0
+ printk(KERN_DEBUG "found !!! %i\n", i);
+#endif
+ snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | i);
+ snd_interwave_bank_sizes(gus, psizes);
+ break;
+ }
+ if (i >= ARRAY_SIZE(lmc) && !gus->gf1.enh_mode)
+ snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | 2);
+ for (i = 0; i < 4; i++) {
+ gus->gf1.mem_alloc.banks_8[i].address =
+ gus->gf1.mem_alloc.banks_16[i].address = i << 22;
+ gus->gf1.mem_alloc.banks_8[i].size =
+ gus->gf1.mem_alloc.banks_16[i].size = psizes[i] << 18;
+ pages += psizes[i];
+ }
+ }
+ pages <<= 18;
+ gus->gf1.memory = pages;
+
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x03); /* select ROM */
+ snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff1f) | (4 << 5));
+ gus->gf1.rom_banks = 0;
+ gus->gf1.rom_memory = 0;
+ for (bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L) {
+ for (i = 0; i < 8; ++i)
+ iwave[i] = snd_gf1_peek(gus, bank_pos + i);
+ if (strncmp(iwave, "INTRWAVE", 8))
+ continue; /* first check */
+ csum = 0;
+ for (i = 0; i < sizeof(struct rom_hdr); i++)
+ csum += snd_gf1_peek(gus, bank_pos + i);
+ if (csum != 0)
+ continue; /* not valid rom */
+ gus->gf1.rom_banks++;
+ gus->gf1.rom_present |= 1 << (bank_pos >> 22);
+ gus->gf1.rom_memory = snd_gf1_peek(gus, bank_pos + 40) |
+ (snd_gf1_peek(gus, bank_pos + 41) << 8) |
+ (snd_gf1_peek(gus, bank_pos + 42) << 16) |
+ (snd_gf1_peek(gus, bank_pos + 43) << 24);
+ }
+#if 0
+ if (gus->gf1.rom_memory > 0) {
+ if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8)
+ gus->card->type = SNDRV_CARD_TYPE_IW_DYNASONIC;
+ }
+#endif
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x00); /* select RAM */
+
+ if (!gus->gf1.enh_mode)
+ snd_interwave_reset(gus);
+}
+
+static void snd_interwave_init(int dev, struct snd_gus_card *gus)
+{
+ unsigned long flags;
+
+ /* ok.. some InterWave specific initialization */
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0x00);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+ gus->equal_irq = 1;
+ gus->codec_flag = 1;
+ gus->interwave = 1;
+ gus->max_flag = 1;
+ gus->joystick_dac = joystick_dac[dev];
+
+}
+
+static const struct snd_kcontrol_new snd_interwave_controls[] = {
+WSS_DOUBLE("Master Playback Switch", 0,
+ CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE("Master Playback Volume", 0,
+ CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 0, 0, 31, 1),
+WSS_DOUBLE("Mic Playback Switch", 0,
+ CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("Mic Playback Volume", 0,
+ CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 0, 0, 31, 1)
+};
+
+static int snd_interwave_mixer(struct snd_wss *chip)
+{
+ struct snd_card *card = chip->card;
+ struct snd_ctl_elem_id id1, id2;
+ unsigned int idx;
+ int err;
+
+ memset(&id1, 0, sizeof(id1));
+ memset(&id2, 0, sizeof(id2));
+ id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+#if 0
+ /* remove mono microphone controls */
+ strcpy(id1.name, "Mic Playback Switch");
+ err = snd_ctl_remove_id(card, &id1);
+ if (err < 0)
+ return err;
+ strcpy(id1.name, "Mic Playback Volume");
+ err = snd_ctl_remove_id(card, &id1);
+ if (err < 0)
+ return err;
+#endif
+ /* add new master and mic controls */
+ for (idx = 0; idx < ARRAY_SIZE(snd_interwave_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_interwave_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ snd_wss_out(chip, CS4231_LINE_LEFT_OUTPUT, 0x9f);
+ snd_wss_out(chip, CS4231_LINE_RIGHT_OUTPUT, 0x9f);
+ snd_wss_out(chip, CS4231_LEFT_MIC_INPUT, 0x9f);
+ snd_wss_out(chip, CS4231_RIGHT_MIC_INPUT, 0x9f);
+ /* reassign AUXA to SYNTHESIZER */
+ strcpy(id1.name, "Aux Playback Switch");
+ strcpy(id2.name, "Synth Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "Synth Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ /* reassign AUXB to CD */
+ strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+ strcpy(id2.name, "CD Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "CD Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+#ifdef CONFIG_PNP
+
+static int snd_interwave_pnp(int dev, struct snd_interwave *iwcard,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ struct pnp_dev *pdev;
+ int err;
+
+ iwcard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (iwcard->dev == NULL)
+ return -EBUSY;
+
+#ifdef SNDRV_STB
+ iwcard->devtc = pnp_request_card_device(card, id->devs[1].id, NULL);
+ if (iwcard->devtc == NULL)
+ return -EBUSY;
+#endif
+ /* Synth & Codec initialization */
+ pdev = iwcard->dev;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "InterWave PnP configure failure (out of resources?)\n");
+ return err;
+ }
+ if (pnp_port_start(pdev, 0) + 0x100 != pnp_port_start(pdev, 1) ||
+ pnp_port_start(pdev, 0) + 0x10c != pnp_port_start(pdev, 2)) {
+ snd_printk(KERN_ERR "PnP configure failure (wrong ports)\n");
+ return -ENOENT;
+ }
+ port[dev] = pnp_port_start(pdev, 0);
+ dma1[dev] = pnp_dma(pdev, 0);
+ if (dma2[dev] >= 0)
+ dma2[dev] = pnp_dma(pdev, 1);
+ irq[dev] = pnp_irq(pdev, 0);
+ snd_printdd("isapnp IW: sb port=0x%llx, gf1 port=0x%llx, codec port=0x%llx\n",
+ (unsigned long long)pnp_port_start(pdev, 0),
+ (unsigned long long)pnp_port_start(pdev, 1),
+ (unsigned long long)pnp_port_start(pdev, 2));
+ snd_printdd("isapnp IW: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]);
+#ifdef SNDRV_STB
+ /* Tone Control initialization */
+ pdev = iwcard->devtc;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "InterWave ToneControl PnP configure failure (out of resources?)\n");
+ return err;
+ }
+ port_tc[dev] = pnp_port_start(pdev, 0);
+ snd_printdd("isapnp IW: tone control port=0x%lx\n", port_tc[dev]);
+#endif
+ return 0;
+}
+#endif /* CONFIG_PNP */
+
+static int snd_interwave_card_new(struct device *pdev, int dev,
+ struct snd_card **cardp)
+{
+ struct snd_card *card;
+ struct snd_interwave *iwcard;
+ int err;
+
+ err = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_interwave), &card);
+ if (err < 0)
+ return err;
+ iwcard = card->private_data;
+ iwcard->card = card;
+ iwcard->irq = -1;
+ *cardp = card;
+ return 0;
+}
+
+static int snd_interwave_probe_gus(struct snd_card *card, int dev,
+ struct snd_gus_card **gusp)
+{
+ return snd_gus_create(card, port[dev], -irq[dev], dma1[dev], dma2[dev],
+ 0, 32, pcm_channels[dev], effect[dev], gusp);
+}
+
+static int snd_interwave_probe(struct snd_card *card, int dev,
+ struct snd_gus_card *gus)
+{
+ int xirq, xdma1, xdma2;
+ struct snd_interwave *iwcard = card->private_data;
+ struct snd_wss *wss;
+#ifdef SNDRV_STB
+ struct snd_i2c_bus *i2c_bus;
+#endif
+ char *str;
+ int err;
+
+ xirq = irq[dev];
+ xdma1 = dma1[dev];
+ xdma2 = dma2[dev];
+
+ err = snd_interwave_detect(iwcard, gus, dev
+#ifdef SNDRV_STB
+ , &i2c_bus
+#endif
+ );
+ if (err < 0)
+ return err;
+
+ iwcard->gus_status_reg = gus->gf1.reg_irqstat;
+ iwcard->pcm_status_reg = gus->gf1.port + 0x10c + 2;
+
+ snd_interwave_init(dev, gus);
+ snd_interwave_detect_memory(gus);
+ err = snd_gus_initialize(gus);
+ if (err < 0)
+ return err;
+
+ if (devm_request_irq(card->dev, xirq, snd_interwave_interrupt, 0,
+ "InterWave", iwcard)) {
+ snd_printk(KERN_ERR PFX "unable to grab IRQ %d\n", xirq);
+ return -EBUSY;
+ }
+ iwcard->irq = xirq;
+ card->sync_irq = iwcard->irq;
+
+ err = snd_wss_create(card,
+ gus->gf1.port + 0x10c, -1, xirq,
+ xdma2 < 0 ? xdma1 : xdma2, xdma1,
+ WSS_HW_INTERWAVE,
+ WSS_HWSHARE_IRQ |
+ WSS_HWSHARE_DMA1 |
+ WSS_HWSHARE_DMA2,
+ &wss);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_pcm(wss, 0);
+ if (err < 0)
+ return err;
+
+ sprintf(wss->pcm->name + strlen(wss->pcm->name), " rev %c",
+ gus->revision + 'A');
+ strcat(wss->pcm->name, " (codec)");
+
+ err = snd_wss_timer(wss, 2);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_mixer(wss);
+ if (err < 0)
+ return err;
+
+ if (pcm_channels[dev] > 0) {
+ err = snd_gf1_pcm_new(gus, 1, 1);
+ if (err < 0)
+ return err;
+ }
+ err = snd_interwave_mixer(wss);
+ if (err < 0)
+ return err;
+
+#ifdef SNDRV_STB
+ {
+ struct snd_ctl_elem_id id1, id2;
+ memset(&id1, 0, sizeof(id1));
+ memset(&id2, 0, sizeof(id2));
+ id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(id1.name, "Master Playback Switch");
+ strcpy(id2.name, id1.name);
+ id2.index = 1;
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ strcpy(id1.name, "Master Playback Volume");
+ strcpy(id2.name, id1.name);
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1);
+ if (err < 0)
+ return err;
+ }
+#endif
+
+ gus->uart_enable = midi[dev];
+ err = snd_gf1_rawmidi_new(gus, 0);
+ if (err < 0)
+ return err;
+
+#ifndef SNDRV_STB
+ str = "AMD InterWave";
+ if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8)
+ str = "Dynasonic 3-D";
+#else
+ str = "InterWave STB";
+#endif
+ strcpy(card->driver, str);
+ strcpy(card->shortname, str);
+ sprintf(card->longname, "%s at 0x%lx, irq %i, dma %d",
+ str,
+ gus->gf1.port,
+ xirq,
+ xdma1);
+ if (xdma2 >= 0)
+ sprintf(card->longname + strlen(card->longname), "&%d", xdma2);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+
+ iwcard->wss = wss;
+ iwcard->gus = gus;
+ return 0;
+}
+
+static int snd_interwave_isa_match(struct device *pdev,
+ unsigned int dev)
+{
+ if (!enable[dev])
+ return 0;
+#ifdef CONFIG_PNP
+ if (isapnp[dev])
+ return 0;
+#endif
+ return 1;
+}
+
+static int snd_interwave_isa_probe(struct device *pdev,
+ unsigned int dev)
+{
+ struct snd_card *card;
+ struct snd_gus_card *gus;
+ int err;
+ static const int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1};
+ static const int possible_dmas[] = {0, 1, 3, 5, 6, 7, -1};
+
+ if (irq[dev] == SNDRV_AUTO_IRQ) {
+ irq[dev] = snd_legacy_find_free_irq(possible_irqs);
+ if (irq[dev] < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (dma1[dev] == SNDRV_AUTO_DMA) {
+ dma1[dev] = snd_legacy_find_free_dma(possible_dmas);
+ if (dma1[dev] < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free DMA1\n");
+ return -EBUSY;
+ }
+ }
+ if (dma2[dev] == SNDRV_AUTO_DMA) {
+ dma2[dev] = snd_legacy_find_free_dma(possible_dmas);
+ if (dma2[dev] < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free DMA2\n");
+ return -EBUSY;
+ }
+ }
+
+ err = snd_interwave_card_new(pdev, dev, &card);
+ if (err < 0)
+ return err;
+
+ if (port[dev] != SNDRV_AUTO_PORT)
+ err = snd_interwave_probe_gus(card, dev, &gus);
+ else {
+ static const long possible_ports[] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260};
+ int i;
+ for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+ port[dev] = possible_ports[i];
+ err = snd_interwave_probe_gus(card, dev, &gus);
+ if (! err)
+ return 0;
+ }
+ }
+ if (err < 0)
+ return err;
+
+ err = snd_interwave_probe(card, dev, gus);
+ if (err < 0)
+ return err;
+
+ dev_set_drvdata(pdev, card);
+ return 0;
+}
+
+static struct isa_driver snd_interwave_driver = {
+ .match = snd_interwave_isa_match,
+ .probe = snd_interwave_isa_probe,
+ /* FIXME: suspend,resume */
+ .driver = {
+ .name = INTERWAVE_DRIVER
+ },
+};
+
+#ifdef CONFIG_PNP
+static int snd_interwave_pnp_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ static int dev;
+ struct snd_card *card;
+ struct snd_gus_card *gus;
+ int res;
+
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ res = snd_interwave_card_new(&pcard->card->dev, dev, &card);
+ if (res < 0)
+ return res;
+
+ res = snd_interwave_pnp(dev, card->private_data, pcard, pid);
+ if (res < 0)
+ return res;
+ res = snd_interwave_probe_gus(card, dev, &gus);
+ if (res < 0)
+ return res;
+ res = snd_interwave_probe(card, dev, gus);
+ if (res < 0)
+ return res;
+ pnp_set_card_drvdata(pcard, card);
+ dev++;
+ return 0;
+}
+
+static struct pnp_card_driver interwave_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = INTERWAVE_PNP_DRIVER,
+ .id_table = snd_interwave_pnpids,
+ .probe = snd_interwave_pnp_detect,
+ /* FIXME: suspend,resume */
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_interwave_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&snd_interwave_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+
+ err = pnp_register_card_driver(&interwave_pnpc_driver);
+ if (!err)
+ pnp_registered = 1;
+
+ if (isa_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit alsa_card_interwave_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ pnp_unregister_card_driver(&interwave_pnpc_driver);
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&snd_interwave_driver);
+}
+
+module_init(alsa_card_interwave_init)
+module_exit(alsa_card_interwave_exit)
diff --git a/sound/isa/msnd/Makefile b/sound/isa/msnd/Makefile
new file mode 100644
index 0000000000..ec231a7b1d
--- /dev/null
+++ b/sound/isa/msnd/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+
+snd-msnd-lib-objs := msnd.o msnd_midi.o msnd_pinnacle_mixer.o
+snd-msnd-pinnacle-objs := msnd_pinnacle.o
+snd-msnd-classic-objs := msnd_classic.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_MSND_PINNACLE) += snd-msnd-pinnacle.o snd-msnd-lib.o
+obj-$(CONFIG_SND_MSND_CLASSIC) += snd-msnd-classic.o snd-msnd-lib.o
+
diff --git a/sound/isa/msnd/msnd.c b/sound/isa/msnd/msnd.c
new file mode 100644
index 0000000000..c3fd1eb301
--- /dev/null
+++ b/sound/isa/msnd/msnd.c
@@ -0,0 +1,694 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ *
+ * 2002/06/30 Karsten Wiese:
+ * removed kernel-version dependencies.
+ * ripped from linux kernel 2.4.18 (OSS Implementation) by me.
+ * In the OSS Version, this file is compiled to a separate MODULE,
+ * that is used by the pinnacle and the classic driver.
+ * since there is no classic driver for alsa yet (i dont have a classic
+ * & writing one blindfold is difficult) this file's object is statically
+ * linked into the pinnacle-driver-module for now. look for the string
+ * "uncomment this to make this a module again"
+ * to do guess what.
+ *
+ * the following is a copy of the 2.4.18 OSS FREE file-heading comment:
+ *
+ * msnd.c - Driver Base
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/sched/signal.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "msnd.h"
+
+#define LOGNAME "msnd"
+
+
+void snd_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);
+}
+EXPORT_SYMBOL(snd_msnd_init_queue);
+
+static int snd_msnd_wait_TXDE(struct snd_msnd *dev)
+{
+ unsigned int io = dev->io;
+ int timeout = 1000;
+
+ while (timeout-- > 0)
+ if (inb(io + HP_ISR) & HPISR_TXDE)
+ return 0;
+
+ return -EIO;
+}
+
+static int snd_msnd_wait_HC0(struct snd_msnd *dev)
+{
+ unsigned int io = dev->io;
+ int timeout = 1000;
+
+ while (timeout-- > 0)
+ if (!(inb(io + HP_CVR) & HPCVR_HC))
+ return 0;
+
+ return -EIO;
+}
+
+int snd_msnd_send_dsp_cmd(struct snd_msnd *dev, u8 cmd)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (snd_msnd_wait_HC0(dev) == 0) {
+ outb(cmd, dev->io + HP_CVR);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ snd_printd(KERN_ERR LOGNAME ": Send DSP command timeout\n");
+
+ return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_send_dsp_cmd);
+
+int snd_msnd_send_word(struct snd_msnd *dev, unsigned char high,
+ unsigned char mid, unsigned char low)
+{
+ unsigned int io = dev->io;
+
+ if (snd_msnd_wait_TXDE(dev) == 0) {
+ outb(high, io + HP_TXH);
+ outb(mid, io + HP_TXM);
+ outb(low, io + HP_TXL);
+ return 0;
+ }
+
+ snd_printd(KERN_ERR LOGNAME ": Send host word timeout\n");
+
+ return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_send_word);
+
+int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len)
+{
+ int i;
+
+ if (len % 3 != 0) {
+ snd_printk(KERN_ERR LOGNAME
+ ": Upload host data not multiple of 3!\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < len; i += 3)
+ if (snd_msnd_send_word(dev, bin[i], bin[i + 1], bin[i + 2]))
+ return -EIO;
+
+ inb(dev->io + HP_RXL);
+ inb(dev->io + HP_CVR);
+
+ return 0;
+}
+EXPORT_SYMBOL(snd_msnd_upload_host);
+
+int snd_msnd_enable_irq(struct snd_msnd *dev)
+{
+ unsigned long flags;
+
+ if (dev->irq_ref++)
+ return 0;
+
+ snd_printdd(LOGNAME ": Enabling IRQ\n");
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (snd_msnd_wait_TXDE(dev) == 0) {
+ outb(inb(dev->io + HP_ICR) | HPICR_TREQ, dev->io + HP_ICR);
+ if (dev->type == msndClassic)
+ outb(dev->irqid, dev->io + HP_IRQM);
+
+ outb(inb(dev->io + HP_ICR) & ~HPICR_TREQ, dev->io + HP_ICR);
+ outb(inb(dev->io + HP_ICR) | HPICR_RREQ, dev->io + HP_ICR);
+ enable_irq(dev->irq);
+ snd_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);
+
+ snd_printd(KERN_ERR LOGNAME ": Enable IRQ failed\n");
+
+ return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_enable_irq);
+
+int snd_msnd_disable_irq(struct snd_msnd *dev)
+{
+ unsigned long flags;
+
+ if (--dev->irq_ref > 0)
+ return 0;
+
+ if (dev->irq_ref < 0)
+ snd_printd(KERN_WARNING LOGNAME ": IRQ ref count is %d\n",
+ dev->irq_ref);
+
+ snd_printdd(LOGNAME ": Disabling IRQ\n");
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (snd_msnd_wait_TXDE(dev) == 0) {
+ outb(inb(dev->io + HP_ICR) & ~HPICR_RREQ, dev->io + HP_ICR);
+ if (dev->type == msndClassic)
+ 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);
+
+ snd_printd(KERN_ERR LOGNAME ": Disable IRQ failed\n");
+
+ return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_disable_irq);
+
+static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size)
+{
+ long tmp = (size * HZ * chip->play_sample_size) / 8;
+ return tmp / (chip->play_sample_rate * chip->play_channels);
+}
+
+static void snd_msnd_dsp_write_flush(struct snd_msnd *chip)
+{
+ if (!(chip->mode & FMODE_WRITE) || !test_bit(F_WRITING, &chip->flags))
+ return;
+ set_bit(F_WRITEFLUSH, &chip->flags);
+/* interruptible_sleep_on_timeout(
+ &chip->writeflush,
+ get_play_delay_jiffies(&chip, chip->DAPF.len));*/
+ clear_bit(F_WRITEFLUSH, &chip->flags);
+ if (!signal_pending(current))
+ schedule_timeout_interruptible(
+ get_play_delay_jiffies(chip, chip->play_period_bytes));
+ clear_bit(F_WRITING, &chip->flags);
+}
+
+void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file)
+{
+ if ((file ? file->f_mode : chip->mode) & FMODE_READ) {
+ clear_bit(F_READING, &chip->flags);
+ snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
+ snd_msnd_disable_irq(chip);
+ if (file) {
+ snd_printd(KERN_INFO LOGNAME
+ ": Stopping read for %p\n", file);
+ chip->mode &= ~FMODE_READ;
+ }
+ clear_bit(F_AUDIO_READ_INUSE, &chip->flags);
+ }
+ if ((file ? file->f_mode : chip->mode) & FMODE_WRITE) {
+ if (test_bit(F_WRITING, &chip->flags)) {
+ snd_msnd_dsp_write_flush(chip);
+ snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
+ }
+ snd_msnd_disable_irq(chip);
+ if (file) {
+ snd_printd(KERN_INFO
+ LOGNAME ": Stopping write for %p\n", file);
+ chip->mode &= ~FMODE_WRITE;
+ }
+ clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+ }
+}
+EXPORT_SYMBOL(snd_msnd_dsp_halt);
+
+
+int snd_msnd_DARQ(struct snd_msnd *chip, int bank)
+{
+ int /*size, n,*/ timeout = 3;
+ u16 wTmp;
+ /* void *DAQD; */
+
+ /* Increment the tail and check for queue wrap */
+ wTmp = readw(chip->DARQ + JQS_wTail) + PCTODSP_OFFSET(DAQDS__size);
+ if (wTmp > readw(chip->DARQ + JQS_wSize))
+ wTmp = 0;
+ while (wTmp == readw(chip->DARQ + JQS_wHead) && timeout--)
+ udelay(1);
+
+ if (chip->capturePeriods == 2) {
+ void __iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF +
+ bank * DAQDS__size + DAQDS_wStart;
+ unsigned short offset = 0x3000 + chip->capturePeriodBytes;
+
+ if (readw(pDAQ) != PCTODSP_BASED(0x3000))
+ offset = 0x3000;
+ writew(PCTODSP_BASED(offset), pDAQ);
+ }
+
+ writew(wTmp, chip->DARQ + JQS_wTail);
+
+#if 0
+ /* Get our digital audio queue struct */
+ DAQD = bank * DAQDS__size + chip->mappedbase + 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) */
+ outb(HPBLKSEL_1, chip->io + HP_BLKS);
+ n = msnd_fifo_write(&chip->DARF,
+ (char *)(chip->base + bank * DAR_BUFF_SIZE),
+ size, 0);
+ if (n <= 0) {
+ outb(HPBLKSEL_0, chip->io + HP_BLKS);
+ return n;
+ }
+ outb(HPBLKSEL_0, chip->io + HP_BLKS);
+#endif
+
+ return 1;
+}
+EXPORT_SYMBOL(snd_msnd_DARQ);
+
+int snd_msnd_DAPQ(struct snd_msnd *chip, int start)
+{
+ u16 DAPQ_tail;
+ int protect = start, nbanks = 0;
+ void __iomem *DAQD;
+ static int play_banks_submitted;
+ /* unsigned long flags;
+ spin_lock_irqsave(&chip->lock, flags); not necessary */
+
+ DAPQ_tail = readw(chip->DAPQ + JQS_wTail);
+ while (DAPQ_tail != readw(chip->DAPQ + JQS_wHead) || start) {
+ int bank_num = DAPQ_tail / PCTODSP_OFFSET(DAQDS__size);
+
+ if (start) {
+ start = 0;
+ play_banks_submitted = 0;
+ }
+
+ /* Get our digital audio queue struct */
+ DAQD = bank_num * DAQDS__size + chip->mappedbase +
+ DAPQ_DATA_BUFF;
+
+ /* Write size of this bank */
+ writew(chip->play_period_bytes, DAQD + DAQDS_wSize);
+ if (play_banks_submitted < 3)
+ ++play_banks_submitted;
+ else if (chip->playPeriods == 2) {
+ unsigned short offset = chip->play_period_bytes;
+
+ if (readw(DAQD + DAQDS_wStart) != PCTODSP_BASED(0x0))
+ offset = 0;
+
+ writew(PCTODSP_BASED(offset), DAQD + DAQDS_wStart);
+ }
+ ++nbanks;
+
+ /* Then advance the tail */
+ /*
+ if (protect)
+ snd_printd(KERN_INFO "B %X %lX\n",
+ bank_num, xtime.tv_usec);
+ */
+
+ DAPQ_tail = (++bank_num % 3) * PCTODSP_OFFSET(DAQDS__size);
+ writew(DAPQ_tail, chip->DAPQ + JQS_wTail);
+ /* Tell the DSP to play the bank */
+ snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_START);
+ if (protect)
+ if (2 == bank_num)
+ break;
+ }
+ /*
+ if (protect)
+ snd_printd(KERN_INFO "%lX\n", xtime.tv_usec);
+ */
+ /* spin_unlock_irqrestore(&chip->lock, flags); not necessary */
+ return nbanks;
+}
+EXPORT_SYMBOL(snd_msnd_DAPQ);
+
+static void snd_msnd_play_reset_queue(struct snd_msnd *chip,
+ unsigned int pcm_periods,
+ unsigned int pcm_count)
+{
+ int n;
+ void __iomem *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF;
+
+ chip->last_playbank = -1;
+ chip->playLimit = pcm_count * (pcm_periods - 1);
+ chip->playPeriods = pcm_periods;
+ writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wHead);
+ writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wTail);
+
+ chip->play_period_bytes = pcm_count;
+
+ for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) {
+ writew(PCTODSP_BASED((u32)(pcm_count * n)),
+ pDAQ + DAQDS_wStart);
+ writew(0, pDAQ + DAQDS_wSize);
+ writew(1, pDAQ + DAQDS_wFormat);
+ writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize);
+ writew(chip->play_channels, pDAQ + DAQDS_wChannels);
+ writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate);
+ writew(HIMT_PLAY_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg);
+ writew(n, pDAQ + DAQDS_wFlags);
+ }
+}
+
+static void snd_msnd_capture_reset_queue(struct snd_msnd *chip,
+ unsigned int pcm_periods,
+ unsigned int pcm_count)
+{
+ int n;
+ void __iomem *pDAQ;
+ /* unsigned long flags; */
+
+ /* snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE); */
+
+ chip->last_recbank = 2;
+ chip->captureLimit = pcm_count * (pcm_periods - 1);
+ chip->capturePeriods = pcm_periods;
+ writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DARQ + JQS_wHead);
+ writew(PCTODSP_OFFSET(chip->last_recbank * DAQDS__size),
+ chip->DARQ + JQS_wTail);
+
+#if 0 /* Critical section: bank 1 access. this is how the OSS driver does it:*/
+ spin_lock_irqsave(&chip->lock, flags);
+ outb(HPBLKSEL_1, chip->io + HP_BLKS);
+ memset_io(chip->mappedbase, 0, DAR_BUFF_SIZE * 3);
+ outb(HPBLKSEL_0, chip->io + HP_BLKS);
+ spin_unlock_irqrestore(&chip->lock, flags);
+#endif
+
+ chip->capturePeriodBytes = pcm_count;
+ snd_printdd("snd_msnd_capture_reset_queue() %i\n", pcm_count);
+
+ pDAQ = chip->mappedbase + DARQ_DATA_BUFF;
+
+ for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) {
+ u32 tmp = pcm_count * n;
+
+ writew(PCTODSP_BASED(tmp + 0x3000), pDAQ + DAQDS_wStart);
+ writew(pcm_count, pDAQ + DAQDS_wSize);
+ writew(1, pDAQ + DAQDS_wFormat);
+ writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize);
+ writew(chip->capture_channels, pDAQ + DAQDS_wChannels);
+ writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate);
+ writew(HIMT_RECORD_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg);
+ writew(n, pDAQ + DAQDS_wFlags);
+ }
+}
+
+static const struct snd_pcm_hardware snd_msnd_playback = {
+ .info = SNDRV_PCM_INFO_MMAP_IOMEM |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH,
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = 0x3000,
+ .period_bytes_min = 0x40,
+ .period_bytes_max = 0x1800,
+ .periods_min = 2,
+ .periods_max = 3,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_msnd_capture = {
+ .info = SNDRV_PCM_INFO_MMAP_IOMEM |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH,
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = 0x3000,
+ .period_bytes_min = 0x40,
+ .period_bytes_max = 0x1800,
+ .periods_min = 2,
+ .periods_max = 3,
+ .fifo_size = 0,
+};
+
+
+static int snd_msnd_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+ set_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+ clear_bit(F_WRITING, &chip->flags);
+ snd_msnd_enable_irq(chip);
+
+ runtime->dma_area = (__force void *)chip->mappedbase;
+ runtime->dma_addr = chip->base;
+ runtime->dma_bytes = 0x3000;
+
+ chip->playback_substream = substream;
+ runtime->hw = snd_msnd_playback;
+ return 0;
+}
+
+static int snd_msnd_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+ snd_msnd_disable_irq(chip);
+ clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+ return 0;
+}
+
+
+static int snd_msnd_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ int i;
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+ void __iomem *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF;
+
+ chip->play_sample_size = snd_pcm_format_width(params_format(params));
+ chip->play_channels = params_channels(params);
+ chip->play_sample_rate = params_rate(params);
+
+ for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) {
+ writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize);
+ writew(chip->play_channels, pDAQ + DAQDS_wChannels);
+ writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate);
+ }
+ /* dont do this here:
+ * snd_msnd_calibrate_adc(chip->play_sample_rate);
+ */
+
+ return 0;
+}
+
+static int snd_msnd_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+ unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int pcm_count = snd_pcm_lib_period_bytes(substream);
+ unsigned int pcm_periods = pcm_size / pcm_count;
+
+ snd_msnd_play_reset_queue(chip, pcm_periods, pcm_count);
+ chip->playDMAPos = 0;
+ return 0;
+}
+
+static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+ int result = 0;
+
+ if (cmd == SNDRV_PCM_TRIGGER_START) {
+ snd_printdd("snd_msnd_playback_trigger(START)\n");
+ chip->banksPlayed = 0;
+ set_bit(F_WRITING, &chip->flags);
+ snd_msnd_DAPQ(chip, 1);
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ snd_printdd("snd_msnd_playback_trigger(STop)\n");
+ /* interrupt diagnostic, comment this out later */
+ clear_bit(F_WRITING, &chip->flags);
+ snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
+ } else {
+ snd_printd(KERN_ERR "snd_msnd_playback_trigger(?????)\n");
+ result = -EINVAL;
+ }
+
+ snd_printdd("snd_msnd_playback_trigger() ENDE\n");
+ return result;
+}
+
+static snd_pcm_uframes_t
+snd_msnd_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+ return bytes_to_frames(substream->runtime, chip->playDMAPos);
+}
+
+
+static const struct snd_pcm_ops snd_msnd_playback_ops = {
+ .open = snd_msnd_playback_open,
+ .close = snd_msnd_playback_close,
+ .hw_params = snd_msnd_playback_hw_params,
+ .prepare = snd_msnd_playback_prepare,
+ .trigger = snd_msnd_playback_trigger,
+ .pointer = snd_msnd_playback_pointer,
+ .mmap = snd_pcm_lib_mmap_iomem,
+};
+
+static int snd_msnd_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+ set_bit(F_AUDIO_READ_INUSE, &chip->flags);
+ snd_msnd_enable_irq(chip);
+ runtime->dma_area = (__force void *)chip->mappedbase + 0x3000;
+ runtime->dma_addr = chip->base + 0x3000;
+ runtime->dma_bytes = 0x3000;
+ memset(runtime->dma_area, 0, runtime->dma_bytes);
+ chip->capture_substream = substream;
+ runtime->hw = snd_msnd_capture;
+ return 0;
+}
+
+static int snd_msnd_capture_close(struct snd_pcm_substream *substream)
+{
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+ snd_msnd_disable_irq(chip);
+ clear_bit(F_AUDIO_READ_INUSE, &chip->flags);
+ return 0;
+}
+
+static int snd_msnd_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+ unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int pcm_count = snd_pcm_lib_period_bytes(substream);
+ unsigned int pcm_periods = pcm_size / pcm_count;
+
+ snd_msnd_capture_reset_queue(chip, pcm_periods, pcm_count);
+ chip->captureDMAPos = 0;
+ return 0;
+}
+
+static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+ if (cmd == SNDRV_PCM_TRIGGER_START) {
+ chip->last_recbank = -1;
+ set_bit(F_READING, &chip->flags);
+ if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0)
+ return 0;
+
+ clear_bit(F_READING, &chip->flags);
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ clear_bit(F_READING, &chip->flags);
+ snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+
+static snd_pcm_uframes_t
+snd_msnd_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+ return bytes_to_frames(runtime, chip->captureDMAPos);
+}
+
+
+static int snd_msnd_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ int i;
+ struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+ void __iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF;
+
+ chip->capture_sample_size = snd_pcm_format_width(params_format(params));
+ chip->capture_channels = params_channels(params);
+ chip->capture_sample_rate = params_rate(params);
+
+ for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) {
+ writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize);
+ writew(chip->capture_channels, pDAQ + DAQDS_wChannels);
+ writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate);
+ }
+ return 0;
+}
+
+
+static const struct snd_pcm_ops snd_msnd_capture_ops = {
+ .open = snd_msnd_capture_open,
+ .close = snd_msnd_capture_close,
+ .hw_params = snd_msnd_capture_hw_params,
+ .prepare = snd_msnd_capture_prepare,
+ .trigger = snd_msnd_capture_trigger,
+ .pointer = snd_msnd_capture_pointer,
+ .mmap = snd_pcm_lib_mmap_iomem,
+};
+
+
+int snd_msnd_pcm(struct snd_card *card, int device)
+{
+ struct snd_msnd *chip = card->private_data;
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(card, "MSNDPINNACLE", device, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_msnd_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_msnd_capture_ops);
+
+ pcm->private_data = chip;
+ strcpy(pcm->name, "Hurricane");
+
+ return 0;
+}
+EXPORT_SYMBOL(snd_msnd_pcm);
+
+MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/isa/msnd/msnd.h b/sound/isa/msnd/msnd.h
new file mode 100644
index 0000000000..533d71cee9
--- /dev/null
+++ b/sound/isa/msnd/msnd.h
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*********************************************************************
+ *
+ * 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.
+ *
+ ********************************************************************/
+#ifndef __MSND_H
+#define __MSND_H
+
+#define DEFSAMPLERATE 44100
+#define DEFSAMPLESIZE SNDRV_PCM_FORMAT_S16
+#define DEFCHANNELS 1
+
+#define SRAM_BANK_SIZE 0x8000
+#define SRAM_CNTL_START 0x7F00
+#define SMA_STRUCT_START 0x7F40
+
+#define DSP_BASE_ADDR 0x4000
+#define DSP_BANK_BASE 0x4000
+
+#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 DAP_BUFF_SIZE 0x2400
+
+#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 DAPQ_DATA_BUFF 0x6C00
+#define DARQ_DATA_BUFF 0x6C30
+#define MODQ_DATA_BUFF 0x6C60
+#define MIDQ_DATA_BUFF 0x7060
+
+#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 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 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
+
+/* Pinnacle only HDEXAR defs */
+#define HDEXAR_SET_ANA_IN 0
+#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 HIWORD(l) ((u16)((((u32)(l)) >> 16) & 0xFFFF))
+#define LOWORD(l) ((u16)(u32)(l))
+#define HIBYTE(w) ((u8)(((u16)(w) >> 8) & 0xFF))
+#define LOBYTE(w) ((u8)(w))
+#define MAKELONG(low, hi) ((long)(((u16)(low))|(((u32)((u16)(hi)))<<16)))
+#define MAKEWORD(low, hi) ((u16)(((u8)(low))|(((u16)((u8)(hi)))<<8)))
+
+#define PCTODSP_OFFSET(w) (u16)((w)/2)
+#define PCTODSP_BASED(w) (u16)(((w)/2) + DSP_BASE_ADDR)
+#define DSPTOPC_BASED(w) (((w) - DSP_BASE_ADDR) * 2)
+
+#ifdef SLOWIO
+# undef outb
+# undef inb
+# define outb outb_p
+# define inb inb_p
+#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
+
+#include <sound/pcm.h>
+
+struct snd_msnd {
+ void __iomem *mappedbase;
+ int play_period_bytes;
+ int playLimit;
+ int playPeriods;
+ int playDMAPos;
+ int banksPlayed;
+ int captureDMAPos;
+ int capturePeriodBytes;
+ int captureLimit;
+ int capturePeriods;
+ struct snd_card *card;
+ void *msndmidi_mpu;
+ struct snd_rawmidi *rmidi;
+
+ /* Hardware resources */
+ long io;
+ int memid, irqid;
+ int irq, irq_ref;
+ unsigned long base;
+
+ /* Motorola 56k DSP SMA */
+ void __iomem *SMA;
+ void __iomem *DAPQ;
+ void __iomem *DARQ;
+ void __iomem *MODQ;
+ void __iomem *MIDQ;
+ void __iomem *DSPQ;
+ int dspq_data_buff, dspq_buff_size;
+
+ /* State variables */
+ enum { msndClassic, msndPinnacle } type;
+ fmode_t mode;
+ unsigned long flags;
+#define F_RESETTING 0
+#define F_HAVEDIGITAL 1
+#define F_AUDIO_WRITE_INUSE 2
+#define F_WRITING 3
+#define F_WRITEBLOCK 4
+#define F_WRITEFLUSH 5
+#define F_AUDIO_READ_INUSE 6
+#define F_READING 7
+#define F_READBLOCK 8
+#define F_EXT_MIDI_INUSE 9
+#define F_HDR_MIDI_INUSE 10
+#define F_DISABLE_WRITE_NDELAY 11
+ spinlock_t lock;
+ spinlock_t mixer_lock;
+ int nresets;
+ unsigned recsrc;
+#define LEVEL_ENTRIES 32
+ int left_levels[LEVEL_ENTRIES];
+ int right_levels[LEVEL_ENTRIES];
+ int calibrate_signal;
+ int play_sample_size, play_sample_rate, play_channels;
+ int play_ndelay;
+ int capture_sample_size, capture_sample_rate, capture_channels;
+ int capture_ndelay;
+ u8 bCurrentMidiPatch;
+
+ int last_playbank, last_recbank;
+ struct snd_pcm_substream *playback_substream;
+ struct snd_pcm_substream *capture_substream;
+
+};
+
+void snd_msnd_init_queue(void __iomem *base, int start, int size);
+
+int snd_msnd_send_dsp_cmd(struct snd_msnd *chip, u8 cmd);
+int snd_msnd_send_word(struct snd_msnd *chip,
+ unsigned char high,
+ unsigned char mid,
+ unsigned char low);
+int snd_msnd_upload_host(struct snd_msnd *chip,
+ const u8 *bin, int len);
+int snd_msnd_enable_irq(struct snd_msnd *chip);
+int snd_msnd_disable_irq(struct snd_msnd *chip);
+void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file);
+int snd_msnd_DAPQ(struct snd_msnd *chip, int start);
+int snd_msnd_DARQ(struct snd_msnd *chip, int start);
+int snd_msnd_pcm(struct snd_card *card, int device);
+
+int snd_msndmidi_new(struct snd_card *card, int device);
+void snd_msndmidi_input_read(void *mpu);
+
+void snd_msndmix_setup(struct snd_msnd *chip);
+int snd_msndmix_new(struct snd_card *card);
+int snd_msndmix_force_recsrc(struct snd_msnd *chip, int recsrc);
+#endif /* __MSND_H */
diff --git a/sound/isa/msnd/msnd_classic.c b/sound/isa/msnd/msnd_classic.c
new file mode 100644
index 0000000000..3b23a096fa
--- /dev/null
+++ b/sound/isa/msnd/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/isa/msnd/msnd_classic.h b/sound/isa/msnd/msnd_classic.h
new file mode 100644
index 0000000000..74d2b9af46
--- /dev/null
+++ b/sound/isa/msnd/msnd_classic.h
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*********************************************************************
+ *
+ * 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.
+ *
+ ********************************************************************/
+#ifndef __MSND_CLASSIC_H
+#define __MSND_CLASSIC_H
+
+#define DSP_NUMIO 0x10
+
+#define HP_MEMM 0x08
+
+#define HP_BITM 0x0E
+#define HP_WAIT 0x0D
+#define HP_DSPR 0x0A
+#define HP_PROR 0x0B
+#define HP_BLKS 0x0C
+
+#define HPPRORESET_OFF 0
+#define HPPRORESET_ON 1
+
+#define HPDSPRESET_OFF 0
+#define HPDSPRESET_ON 1
+
+#define HPBLKSEL_0 0
+#define HPBLKSEL_1 1
+
+#define HPWAITSTATE_0 0
+#define HPWAITSTATE_1 1
+
+#define HPBITMODE_16 0
+#define HPBITMODE_8 1
+
+#define HIDSP_INT_PLAY_UNDER 0x00
+#define HIDSP_INT_RECORD_OVER 0x01
+#define HIDSP_INPUT_CLIPPING 0x02
+#define HIDSP_MIDI_IN_OVER 0x10
+#define HIDSP_MIDI_OVERRUN_ERR 0x13
+
+#define TIME_PRO_RESET_DONE 0x028A
+#define TIME_PRO_SYSEX 0x0040
+#define TIME_PRO_RESET 0x0032
+
+#define DAR_BUFF_SIZE 0x2000
+
+#define MIDQ_BUFF_SIZE 0x200
+#define DSPQ_BUFF_SIZE 0x40
+
+#define DSPQ_DATA_BUFF 0x7260
+
+#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
+
+#define INITCODEFILE "turtlebeach/msndinit.bin"
+#define PERMCODEFILE "turtlebeach/msndperm.bin"
+#define LONGNAME "MultiSound (Classic/Monterey/Tahiti)"
+
+#endif /* __MSND_CLASSIC_H */
diff --git a/sound/isa/msnd/msnd_midi.c b/sound/isa/msnd/msnd_midi.c
new file mode 100644
index 0000000000..7c61caaf99
--- /dev/null
+++ b/sound/isa/msnd/msnd_midi.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Copyright (c) 2009 by Krzysztof Helt
+ * Routines for control of MPU-401 in UART mode
+ *
+ * MPU-401 supports UART mode which is not capable generate transmit
+ * interrupts thus output is done via polling. Also, if irq < 0, then
+ * input is done also via polling. Do not expect good performance.
+ */
+
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+
+#include "msnd.h"
+
+#define MSNDMIDI_MODE_BIT_INPUT 0
+#define MSNDMIDI_MODE_BIT_OUTPUT 1
+#define MSNDMIDI_MODE_BIT_INPUT_TRIGGER 2
+#define MSNDMIDI_MODE_BIT_OUTPUT_TRIGGER 3
+
+struct snd_msndmidi {
+ struct snd_msnd *dev;
+
+ unsigned long mode; /* MSNDMIDI_MODE_XXXX */
+
+ struct snd_rawmidi_substream *substream_input;
+
+ spinlock_t input_lock;
+};
+
+/*
+ * input/output open/close - protected by open_mutex in rawmidi.c
+ */
+static int snd_msndmidi_input_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_msndmidi *mpu;
+
+ snd_printdd("snd_msndmidi_input_open()\n");
+
+ mpu = substream->rmidi->private_data;
+
+ mpu->substream_input = substream;
+
+ snd_msnd_enable_irq(mpu->dev);
+
+ snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_START);
+ set_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode);
+ return 0;
+}
+
+static int snd_msndmidi_input_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_msndmidi *mpu;
+
+ mpu = substream->rmidi->private_data;
+ snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_STOP);
+ clear_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode);
+ mpu->substream_input = NULL;
+ snd_msnd_disable_irq(mpu->dev);
+ return 0;
+}
+
+static void snd_msndmidi_input_drop(struct snd_msndmidi *mpu)
+{
+ u16 tail;
+
+ tail = readw(mpu->dev->MIDQ + JQS_wTail);
+ writew(tail, mpu->dev->MIDQ + JQS_wHead);
+}
+
+/*
+ * trigger input
+ */
+static void snd_msndmidi_input_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ unsigned long flags;
+ struct snd_msndmidi *mpu;
+
+ snd_printdd("snd_msndmidi_input_trigger(, %i)\n", up);
+
+ mpu = substream->rmidi->private_data;
+ spin_lock_irqsave(&mpu->input_lock, flags);
+ if (up) {
+ if (!test_and_set_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER,
+ &mpu->mode))
+ snd_msndmidi_input_drop(mpu);
+ } else {
+ clear_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, &mpu->mode);
+ }
+ spin_unlock_irqrestore(&mpu->input_lock, flags);
+ if (up)
+ snd_msndmidi_input_read(mpu);
+}
+
+void snd_msndmidi_input_read(void *mpuv)
+{
+ unsigned long flags;
+ struct snd_msndmidi *mpu = mpuv;
+ void __iomem *pwMIDQData = mpu->dev->mappedbase + MIDQ_DATA_BUFF;
+ u16 head, tail, size;
+
+ spin_lock_irqsave(&mpu->input_lock, flags);
+ head = readw(mpu->dev->MIDQ + JQS_wHead);
+ tail = readw(mpu->dev->MIDQ + JQS_wTail);
+ size = readw(mpu->dev->MIDQ + JQS_wSize);
+ if (head > size || tail > size)
+ goto out;
+ while (head != tail) {
+ unsigned char val = readw(pwMIDQData + 2 * head);
+
+ if (test_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, &mpu->mode))
+ snd_rawmidi_receive(mpu->substream_input, &val, 1);
+ if (++head > size)
+ head = 0;
+ writew(head, mpu->dev->MIDQ + JQS_wHead);
+ }
+ out:
+ spin_unlock_irqrestore(&mpu->input_lock, flags);
+}
+EXPORT_SYMBOL(snd_msndmidi_input_read);
+
+static const struct snd_rawmidi_ops snd_msndmidi_input = {
+ .open = snd_msndmidi_input_open,
+ .close = snd_msndmidi_input_close,
+ .trigger = snd_msndmidi_input_trigger,
+};
+
+static void snd_msndmidi_free(struct snd_rawmidi *rmidi)
+{
+ struct snd_msndmidi *mpu = rmidi->private_data;
+ kfree(mpu);
+}
+
+int snd_msndmidi_new(struct snd_card *card, int device)
+{
+ struct snd_msnd *chip = card->private_data;
+ struct snd_msndmidi *mpu;
+ struct snd_rawmidi *rmidi;
+ int err;
+
+ err = snd_rawmidi_new(card, "MSND-MIDI", device, 1, 1, &rmidi);
+ if (err < 0)
+ return err;
+ mpu = kzalloc(sizeof(*mpu), GFP_KERNEL);
+ if (mpu == NULL) {
+ snd_device_free(card, rmidi);
+ return -ENOMEM;
+ }
+ mpu->dev = chip;
+ chip->msndmidi_mpu = mpu;
+ rmidi->private_data = mpu;
+ rmidi->private_free = snd_msndmidi_free;
+ spin_lock_init(&mpu->input_lock);
+ strcpy(rmidi->name, "MSND MIDI");
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &snd_msndmidi_input);
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+ return 0;
+}
diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c
new file mode 100644
index 0000000000..4433a92f08
--- /dev/null
+++ b/sound/isa/msnd/msnd_pinnacle.c
@@ -0,0 +1,1166 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ *
+ * Linux multisound pinnacle/fiji driver for ALSA.
+ *
+ * 2002/06/30 Karsten Wiese:
+ * for now this is only used to build a pinnacle / fiji driver.
+ * the OSS parent of this code is designed to also support
+ * the multisound classic via the file msnd_classic.c.
+ * to make it easier for some brave heart to implemt classic
+ * support in alsa, i left all the MSND_CLASSIC tokens in this file.
+ * but for now this untested & undone.
+ *
+ * ripped from linux kernel 2.4.18 by Karsten Wiese.
+ *
+ * the following is a copy of the 2.4.18 OSS FREE file-heading comment:
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ * 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
+ *
+ * 12-3-2000 Modified IO port validation Steve Sycamore
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/firmware.h>
+#include <linux/isa.h>
+#include <linux/isapnp.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/asound.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+
+#ifdef MSND_CLASSIC
+# ifndef __alpha__
+# define SLOWIO
+# endif
+#endif
+#include "msnd.h"
+#ifdef MSND_CLASSIC
+# include "msnd_classic.h"
+# define LOGNAME "msnd_classic"
+# define DEV_NAME "msnd-classic"
+#else
+# include "msnd_pinnacle.h"
+# define LOGNAME "snd_msnd_pinnacle"
+# define DEV_NAME "msnd-pinnacle"
+#endif
+
+static void set_default_audio_parameters(struct snd_msnd *chip)
+{
+ chip->play_sample_size = snd_pcm_format_width(DEFSAMPLESIZE);
+ chip->play_sample_rate = DEFSAMPLERATE;
+ chip->play_channels = DEFCHANNELS;
+ chip->capture_sample_size = snd_pcm_format_width(DEFSAMPLESIZE);
+ chip->capture_sample_rate = DEFSAMPLERATE;
+ chip->capture_channels = DEFCHANNELS;
+}
+
+static void snd_msnd_eval_dsp_msg(struct snd_msnd *chip, u16 wMessage)
+{
+ switch (HIBYTE(wMessage)) {
+ case HIMT_PLAY_DONE: {
+ if (chip->banksPlayed < 3)
+ snd_printdd("%08X: HIMT_PLAY_DONE: %i\n",
+ (unsigned)jiffies, LOBYTE(wMessage));
+
+ if (chip->last_playbank == LOBYTE(wMessage)) {
+ snd_printdd("chip.last_playbank == LOBYTE(wMessage)\n");
+ break;
+ }
+ chip->banksPlayed++;
+
+ if (test_bit(F_WRITING, &chip->flags))
+ snd_msnd_DAPQ(chip, 0);
+
+ chip->last_playbank = LOBYTE(wMessage);
+ chip->playDMAPos += chip->play_period_bytes;
+ if (chip->playDMAPos > chip->playLimit)
+ chip->playDMAPos = 0;
+ snd_pcm_period_elapsed(chip->playback_substream);
+
+ break;
+ }
+ case HIMT_RECORD_DONE:
+ if (chip->last_recbank == LOBYTE(wMessage))
+ break;
+ chip->last_recbank = LOBYTE(wMessage);
+ chip->captureDMAPos += chip->capturePeriodBytes;
+ if (chip->captureDMAPos > (chip->captureLimit))
+ chip->captureDMAPos = 0;
+
+ if (test_bit(F_READING, &chip->flags))
+ snd_msnd_DARQ(chip, chip->last_recbank);
+
+ snd_pcm_period_elapsed(chip->capture_substream);
+ break;
+
+ case HIMT_DSP:
+ switch (LOBYTE(wMessage)) {
+#ifndef MSND_CLASSIC
+ case HIDSP_PLAY_UNDER:
+#endif
+ case HIDSP_INT_PLAY_UNDER:
+ snd_printd(KERN_WARNING LOGNAME ": Play underflow %i\n",
+ chip->banksPlayed);
+ if (chip->banksPlayed > 2)
+ clear_bit(F_WRITING, &chip->flags);
+ break;
+
+ case HIDSP_INT_RECORD_OVER:
+ snd_printd(KERN_WARNING LOGNAME ": Record overflow\n");
+ clear_bit(F_READING, &chip->flags);
+ break;
+
+ default:
+ snd_printd(KERN_WARNING LOGNAME
+ ": DSP message %d 0x%02x\n",
+ LOBYTE(wMessage), LOBYTE(wMessage));
+ break;
+ }
+ break;
+
+ case HIMT_MIDI_IN_UCHAR:
+ if (chip->msndmidi_mpu)
+ snd_msndmidi_input_read(chip->msndmidi_mpu);
+ break;
+
+ default:
+ snd_printd(KERN_WARNING LOGNAME ": HIMT message %d 0x%02x\n",
+ HIBYTE(wMessage), HIBYTE(wMessage));
+ break;
+ }
+}
+
+static irqreturn_t snd_msnd_interrupt(int irq, void *dev_id)
+{
+ struct snd_msnd *chip = dev_id;
+ void __iomem *pwDSPQData = chip->mappedbase + DSPQ_DATA_BUFF;
+ u16 head, tail, size;
+
+ /* Send ack to DSP */
+ /* inb(chip->io + HP_RXL); */
+
+ /* Evaluate queued DSP messages */
+ head = readw(chip->DSPQ + JQS_wHead);
+ tail = readw(chip->DSPQ + JQS_wTail);
+ size = readw(chip->DSPQ + JQS_wSize);
+ if (head > size || tail > size)
+ goto out;
+ while (head != tail) {
+ snd_msnd_eval_dsp_msg(chip, readw(pwDSPQData + 2 * head));
+ if (++head > size)
+ head = 0;
+ writew(head, chip->DSPQ + JQS_wHead);
+ }
+ out:
+ /* Send ack to DSP */
+ inb(chip->io + HP_RXL);
+ return IRQ_HANDLED;
+}
+
+
+static int snd_msnd_reset_dsp(long io, unsigned char *info)
+{
+ int timeout = 100;
+
+ outb(HPDSPRESET_ON, io + HP_DSPR);
+ msleep(1);
+#ifndef MSND_CLASSIC
+ if (info)
+ *info = inb(io + HP_INFO);
+#endif
+ outb(HPDSPRESET_OFF, io + HP_DSPR);
+ msleep(1);
+ while (timeout-- > 0) {
+ if (inb(io + HP_CVR) == HP_CVR_DEF)
+ return 0;
+ msleep(1);
+ }
+ snd_printk(KERN_ERR LOGNAME ": Cannot reset DSP\n");
+
+ return -EIO;
+}
+
+static int snd_msnd_probe(struct snd_card *card)
+{
+ struct snd_msnd *chip = card->private_data;
+ unsigned char info;
+#ifndef MSND_CLASSIC
+ char *xv, *rev = NULL;
+ char *pin = "TB Pinnacle", *fiji = "TB Fiji";
+ char *pinfiji = "TB Pinnacle/Fiji";
+#endif
+
+ if (!request_region(chip->io, DSP_NUMIO, "probing")) {
+ snd_printk(KERN_ERR LOGNAME ": I/O port conflict\n");
+ return -ENODEV;
+ }
+
+ if (snd_msnd_reset_dsp(chip->io, &info) < 0) {
+ release_region(chip->io, DSP_NUMIO);
+ return -ENODEV;
+ }
+
+#ifdef MSND_CLASSIC
+ strcpy(card->shortname, "Classic/Tahiti/Monterey");
+ strcpy(card->longname, "Turtle Beach Multisound");
+ printk(KERN_INFO LOGNAME ": %s, "
+ "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n",
+ card->shortname,
+ chip->io, chip->io + DSP_NUMIO - 1,
+ chip->irq,
+ chip->base, chip->base + 0x7fff);
+#else
+ switch (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 (info & 0x7) {
+ case 0x0:
+ rev = "I";
+ strcpy(card->shortname, pin);
+ break;
+ case 0x1:
+ rev = "F";
+ strcpy(card->shortname, pin);
+ break;
+ case 0x2:
+ rev = "G";
+ strcpy(card->shortname, pin);
+ break;
+ case 0x3:
+ rev = "H";
+ strcpy(card->shortname, pin);
+ break;
+ case 0x4:
+ rev = "E";
+ strcpy(card->shortname, fiji);
+ break;
+ case 0x5:
+ rev = "C";
+ strcpy(card->shortname, fiji);
+ break;
+ case 0x6:
+ rev = "D";
+ strcpy(card->shortname, fiji);
+ break;
+ case 0x7:
+ rev = "A-B (Fiji) or A-E (Pinnacle)";
+ strcpy(card->shortname, pinfiji);
+ break;
+ }
+ strcpy(card->longname, "Turtle Beach Multisound Pinnacle");
+ printk(KERN_INFO LOGNAME ": %s revision %s, Xilinx version %s, "
+ "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n",
+ card->shortname,
+ rev, xv,
+ chip->io, chip->io + DSP_NUMIO - 1,
+ chip->irq,
+ chip->base, chip->base + 0x7fff);
+#endif
+
+ release_region(chip->io, DSP_NUMIO);
+ return 0;
+}
+
+static int snd_msnd_init_sma(struct snd_msnd *chip)
+{
+ static int initted;
+ u16 mastVolLeft, mastVolRight;
+ unsigned long flags;
+
+#ifdef MSND_CLASSIC
+ outb(chip->memid, chip->io + HP_MEMM);
+#endif
+ outb(HPBLKSEL_0, chip->io + HP_BLKS);
+ /* Motorola 56k shared memory base */
+ chip->SMA = chip->mappedbase + SMA_STRUCT_START;
+
+ if (initted) {
+ mastVolLeft = readw(chip->SMA + SMA_wCurrMastVolLeft);
+ mastVolRight = readw(chip->SMA + SMA_wCurrMastVolRight);
+ } else
+ mastVolLeft = mastVolRight = 0;
+ memset_io(chip->mappedbase, 0, 0x8000);
+
+ /* Critical section: bank 1 access */
+ spin_lock_irqsave(&chip->lock, flags);
+ outb(HPBLKSEL_1, chip->io + HP_BLKS);
+ memset_io(chip->mappedbase, 0, 0x8000);
+ outb(HPBLKSEL_0, chip->io + HP_BLKS);
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+ /* Digital audio play queue */
+ chip->DAPQ = chip->mappedbase + DAPQ_OFFSET;
+ snd_msnd_init_queue(chip->DAPQ, DAPQ_DATA_BUFF, DAPQ_BUFF_SIZE);
+
+ /* Digital audio record queue */
+ chip->DARQ = chip->mappedbase + DARQ_OFFSET;
+ snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE);
+
+ /* MIDI out queue */
+ chip->MODQ = chip->mappedbase + MODQ_OFFSET;
+ snd_msnd_init_queue(chip->MODQ, MODQ_DATA_BUFF, MODQ_BUFF_SIZE);
+
+ /* MIDI in queue */
+ chip->MIDQ = chip->mappedbase + MIDQ_OFFSET;
+ snd_msnd_init_queue(chip->MIDQ, MIDQ_DATA_BUFF, MIDQ_BUFF_SIZE);
+
+ /* DSP -> host message queue */
+ chip->DSPQ = chip->mappedbase + DSPQ_OFFSET;
+ snd_msnd_init_queue(chip->DSPQ, DSPQ_DATA_BUFF, DSPQ_BUFF_SIZE);
+
+ /* Setup some DSP values */
+#ifndef MSND_CLASSIC
+ writew(1, chip->SMA + SMA_wCurrPlayFormat);
+ writew(chip->play_sample_size, chip->SMA + SMA_wCurrPlaySampleSize);
+ writew(chip->play_channels, chip->SMA + SMA_wCurrPlayChannels);
+ writew(chip->play_sample_rate, chip->SMA + SMA_wCurrPlaySampleRate);
+#endif
+ writew(chip->play_sample_rate, chip->SMA + SMA_wCalFreqAtoD);
+ writew(mastVolLeft, chip->SMA + SMA_wCurrMastVolLeft);
+ writew(mastVolRight, chip->SMA + SMA_wCurrMastVolRight);
+#ifndef MSND_CLASSIC
+ writel(0x00010000, chip->SMA + SMA_dwCurrPlayPitch);
+ writel(0x00000001, chip->SMA + SMA_dwCurrPlayRate);
+#endif
+ writew(0x303, chip->SMA + SMA_wCurrInputTagBits);
+
+ initted = 1;
+
+ return 0;
+}
+
+
+static int upload_dsp_code(struct snd_card *card)
+{
+ struct snd_msnd *chip = card->private_data;
+ const struct firmware *init_fw = NULL, *perm_fw = NULL;
+ int err;
+
+ outb(HPBLKSEL_0, chip->io + HP_BLKS);
+
+ err = request_firmware(&init_fw, INITCODEFILE, card->dev);
+ if (err < 0) {
+ printk(KERN_ERR LOGNAME ": Error loading " INITCODEFILE);
+ goto cleanup1;
+ }
+ err = request_firmware(&perm_fw, PERMCODEFILE, card->dev);
+ if (err < 0) {
+ printk(KERN_ERR LOGNAME ": Error loading " PERMCODEFILE);
+ goto cleanup;
+ }
+
+ memcpy_toio(chip->mappedbase, perm_fw->data, perm_fw->size);
+ if (snd_msnd_upload_host(chip, init_fw->data, init_fw->size) < 0) {
+ printk(KERN_WARNING LOGNAME ": Error uploading to DSP\n");
+ err = -ENODEV;
+ goto cleanup;
+ }
+ printk(KERN_INFO LOGNAME ": DSP firmware uploaded\n");
+ err = 0;
+
+cleanup:
+ release_firmware(perm_fw);
+cleanup1:
+ release_firmware(init_fw);
+ return err;
+}
+
+#ifdef MSND_CLASSIC
+static void reset_proteus(struct snd_msnd *chip)
+{
+ outb(HPPRORESET_ON, chip->io + HP_PROR);
+ msleep(TIME_PRO_RESET);
+ outb(HPPRORESET_OFF, chip->io + HP_PROR);
+ msleep(TIME_PRO_RESET_DONE);
+}
+#endif
+
+static int snd_msnd_initialize(struct snd_card *card)
+{
+ struct snd_msnd *chip = card->private_data;
+ int err, timeout;
+
+#ifdef MSND_CLASSIC
+ outb(HPWAITSTATE_0, chip->io + HP_WAIT);
+ outb(HPBITMODE_16, chip->io + HP_BITM);
+
+ reset_proteus(chip);
+#endif
+ err = snd_msnd_init_sma(chip);
+ if (err < 0) {
+ printk(KERN_WARNING LOGNAME ": Cannot initialize SMA\n");
+ return err;
+ }
+
+ err = snd_msnd_reset_dsp(chip->io, NULL);
+ if (err < 0)
+ return err;
+
+ err = upload_dsp_code(card);
+ if (err < 0) {
+ printk(KERN_WARNING LOGNAME ": Cannot upload DSP code\n");
+ return err;
+ }
+
+ timeout = 200;
+
+ while (readw(chip->mappedbase)) {
+ msleep(1);
+ if (!timeout--) {
+ snd_printd(KERN_ERR LOGNAME ": DSP reset timeout\n");
+ return -EIO;
+ }
+ }
+
+ snd_msndmix_setup(chip);
+ return 0;
+}
+
+static int snd_msnd_dsp_full_reset(struct snd_card *card)
+{
+ struct snd_msnd *chip = card->private_data;
+ int rv;
+
+ if (test_bit(F_RESETTING, &chip->flags) || ++chip->nresets > 10)
+ return 0;
+
+ set_bit(F_RESETTING, &chip->flags);
+ snd_msnd_dsp_halt(chip, NULL); /* Unconditionally halt */
+
+ rv = snd_msnd_initialize(card);
+ if (rv)
+ printk(KERN_WARNING LOGNAME ": DSP reset failed\n");
+ snd_msndmix_force_recsrc(chip, 0);
+ clear_bit(F_RESETTING, &chip->flags);
+ return rv;
+}
+
+
+static int snd_msnd_send_dsp_cmd_chk(struct snd_msnd *chip, u8 cmd)
+{
+ if (snd_msnd_send_dsp_cmd(chip, cmd) == 0)
+ return 0;
+ snd_msnd_dsp_full_reset(chip->card);
+ return snd_msnd_send_dsp_cmd(chip, cmd);
+}
+
+static int snd_msnd_calibrate_adc(struct snd_msnd *chip, u16 srate)
+{
+ snd_printdd("snd_msnd_calibrate_adc(%i)\n", srate);
+ writew(srate, chip->SMA + SMA_wCalFreqAtoD);
+ if (chip->calibrate_signal == 0)
+ writew(readw(chip->SMA + SMA_wCurrHostStatusFlags)
+ | 0x0001, chip->SMA + SMA_wCurrHostStatusFlags);
+ else
+ writew(readw(chip->SMA + SMA_wCurrHostStatusFlags)
+ & ~0x0001, chip->SMA + SMA_wCurrHostStatusFlags);
+ if (snd_msnd_send_word(chip, 0, 0, HDEXAR_CAL_A_TO_D) == 0 &&
+ snd_msnd_send_dsp_cmd_chk(chip, HDEX_AUX_REQ) == 0) {
+ schedule_timeout_interruptible(msecs_to_jiffies(333));
+ return 0;
+ }
+ printk(KERN_WARNING LOGNAME ": ADC calibration failed\n");
+ return -EIO;
+}
+
+/*
+ * ALSA callback function, called when attempting to open the MIDI device.
+ */
+static int snd_msnd_mpu401_open(struct snd_mpu401 *mpu)
+{
+ snd_msnd_enable_irq(mpu->private_data);
+ snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_START);
+ return 0;
+}
+
+static void snd_msnd_mpu401_close(struct snd_mpu401 *mpu)
+{
+ snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_STOP);
+ snd_msnd_disable_irq(mpu->private_data);
+}
+
+static long mpu_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+
+static int snd_msnd_attach(struct snd_card *card)
+{
+ struct snd_msnd *chip = card->private_data;
+ int err;
+
+ err = devm_request_irq(card->dev, chip->irq, snd_msnd_interrupt, 0,
+ card->shortname, chip);
+ if (err < 0) {
+ printk(KERN_ERR LOGNAME ": Couldn't grab IRQ %d\n", chip->irq);
+ return err;
+ }
+ card->sync_irq = chip->irq;
+ if (!devm_request_region(card->dev, chip->io, DSP_NUMIO,
+ card->shortname))
+ return -EBUSY;
+
+ if (!devm_request_mem_region(card->dev, chip->base, BUFFSIZE,
+ card->shortname)) {
+ printk(KERN_ERR LOGNAME
+ ": unable to grab memory region 0x%lx-0x%lx\n",
+ chip->base, chip->base + BUFFSIZE - 1);
+ return -EBUSY;
+ }
+ chip->mappedbase = devm_ioremap(card->dev, chip->base, 0x8000);
+ if (!chip->mappedbase) {
+ printk(KERN_ERR LOGNAME
+ ": unable to map memory region 0x%lx-0x%lx\n",
+ chip->base, chip->base + BUFFSIZE - 1);
+ return -EIO;
+ }
+
+ err = snd_msnd_dsp_full_reset(card);
+ if (err < 0)
+ return err;
+
+ err = snd_msnd_pcm(card, 0);
+ if (err < 0) {
+ printk(KERN_ERR LOGNAME ": error creating new PCM device\n");
+ return err;
+ }
+
+ err = snd_msndmix_new(card);
+ if (err < 0) {
+ printk(KERN_ERR LOGNAME ": error creating new Mixer device\n");
+ return err;
+ }
+
+
+ if (mpu_io[0] != SNDRV_AUTO_PORT) {
+ struct snd_mpu401 *mpu;
+
+ err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+ mpu_io[0],
+ MPU401_MODE_INPUT |
+ MPU401_MODE_OUTPUT,
+ mpu_irq[0],
+ &chip->rmidi);
+ if (err < 0) {
+ printk(KERN_ERR LOGNAME
+ ": error creating new Midi device\n");
+ return err;
+ }
+ mpu = chip->rmidi->private_data;
+
+ mpu->open_input = snd_msnd_mpu401_open;
+ mpu->close_input = snd_msnd_mpu401_close;
+ mpu->private_data = chip;
+ }
+
+ disable_irq(chip->irq);
+ snd_msnd_calibrate_adc(chip, chip->play_sample_rate);
+ snd_msndmix_force_recsrc(chip, 0);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+
+#ifndef MSND_CLASSIC
+
+/* Pinnacle/Fiji Logical Device Configuration */
+
+static int snd_msnd_write_cfg(int cfg, int reg, int value)
+{
+ outb(reg, cfg);
+ outb(value, cfg + 1);
+ if (value != inb(cfg + 1)) {
+ printk(KERN_ERR LOGNAME ": snd_msnd_write_cfg: I/O error\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static int snd_msnd_write_cfg_io0(int cfg, int num, u16 io)
+{
+ if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (snd_msnd_write_cfg(cfg, IREG_IO0_BASEHI, HIBYTE(io)))
+ return -EIO;
+ if (snd_msnd_write_cfg(cfg, IREG_IO0_BASELO, LOBYTE(io)))
+ return -EIO;
+ return 0;
+}
+
+static int snd_msnd_write_cfg_io1(int cfg, int num, u16 io)
+{
+ if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (snd_msnd_write_cfg(cfg, IREG_IO1_BASEHI, HIBYTE(io)))
+ return -EIO;
+ if (snd_msnd_write_cfg(cfg, IREG_IO1_BASELO, LOBYTE(io)))
+ return -EIO;
+ return 0;
+}
+
+static int snd_msnd_write_cfg_irq(int cfg, int num, u16 irq)
+{
+ if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (snd_msnd_write_cfg(cfg, IREG_IRQ_NUMBER, LOBYTE(irq)))
+ return -EIO;
+ if (snd_msnd_write_cfg(cfg, IREG_IRQ_TYPE, IRQTYPE_EDGE))
+ return -EIO;
+ return 0;
+}
+
+static int snd_msnd_write_cfg_mem(int cfg, int num, int mem)
+{
+ u16 wmem;
+
+ mem >>= 8;
+ wmem = (u16)(mem & 0xfff);
+ if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (snd_msnd_write_cfg(cfg, IREG_MEMBASEHI, HIBYTE(wmem)))
+ return -EIO;
+ if (snd_msnd_write_cfg(cfg, IREG_MEMBASELO, LOBYTE(wmem)))
+ return -EIO;
+ if (wmem && snd_msnd_write_cfg(cfg, IREG_MEMCONTROL,
+ MEMTYPE_HIADDR | MEMTYPE_16BIT))
+ return -EIO;
+ return 0;
+}
+
+static int snd_msnd_activate_logical(int cfg, int num)
+{
+ if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (snd_msnd_write_cfg(cfg, IREG_ACTIVATE, LD_ACTIVATE))
+ return -EIO;
+ return 0;
+}
+
+static int snd_msnd_write_cfg_logical(int cfg, int num, u16 io0,
+ u16 io1, u16 irq, int mem)
+{
+ if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (snd_msnd_write_cfg_io0(cfg, num, io0))
+ return -EIO;
+ if (snd_msnd_write_cfg_io1(cfg, num, io1))
+ return -EIO;
+ if (snd_msnd_write_cfg_irq(cfg, num, irq))
+ return -EIO;
+ if (snd_msnd_write_cfg_mem(cfg, num, mem))
+ return -EIO;
+ if (snd_msnd_activate_logical(cfg, num))
+ return -EIO;
+ return 0;
+}
+
+static int snd_msnd_pinnacle_cfg_reset(int cfg)
+{
+ int i;
+
+ /* Reset devices if told to */
+ printk(KERN_INFO LOGNAME ": Resetting all devices\n");
+ for (i = 0; i < 4; ++i)
+ if (snd_msnd_write_cfg_logical(cfg, i, 0, 0, 0, 0))
+ return -EIO;
+
+ return 0;
+}
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for msnd_pinnacle soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for msnd_pinnacle soundcard.");
+
+static long io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static long mem[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+
+#ifndef MSND_CLASSIC
+static long cfg[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+
+/* Extra Peripheral Configuration (Default: Disable) */
+static long ide_io0[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long ide_io1[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int ide_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+
+static long joystick_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+/* If we have the digital daugherboard... */
+static int digital[SNDRV_CARDS];
+
+/* Extra Peripheral Configuration */
+static int reset[SNDRV_CARDS];
+#endif
+
+static int write_ndelay[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = 1 };
+
+static int calibrate_signal;
+
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard.");
+#define has_isapnp(x) isapnp[x]
+#else
+#define has_isapnp(x) 0
+#endif
+
+MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>");
+MODULE_DESCRIPTION("Turtle Beach " LONGNAME " Linux Driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(INITCODEFILE);
+MODULE_FIRMWARE(PERMCODEFILE);
+
+module_param_hw_array(io, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(io, "IO port #");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+module_param_hw_array(mem, long, iomem, NULL, 0444);
+module_param_array(write_ndelay, int, NULL, 0444);
+module_param(calibrate_signal, int, 0444);
+#ifndef MSND_CLASSIC
+module_param_array(digital, int, NULL, 0444);
+module_param_hw_array(cfg, long, ioport, NULL, 0444);
+module_param_array(reset, int, NULL, 0444);
+module_param_hw_array(mpu_io, long, ioport, NULL, 0444);
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+module_param_hw_array(ide_io0, long, ioport, NULL, 0444);
+module_param_hw_array(ide_io1, long, ioport, NULL, 0444);
+module_param_hw_array(ide_irq, int, irq, NULL, 0444);
+module_param_hw_array(joystick_io, long, ioport, NULL, 0444);
+#endif
+
+
+static int snd_msnd_isa_match(struct device *pdev, unsigned int i)
+{
+ if (io[i] == SNDRV_AUTO_PORT)
+ return 0;
+
+ if (irq[i] == SNDRV_AUTO_PORT || mem[i] == SNDRV_AUTO_PORT) {
+ printk(KERN_WARNING LOGNAME ": io, irq and mem must be set\n");
+ return 0;
+ }
+
+#ifdef MSND_CLASSIC
+ if (!(io[i] == 0x290 ||
+ io[i] == 0x260 ||
+ io[i] == 0x250 ||
+ io[i] == 0x240 ||
+ io[i] == 0x230 ||
+ io[i] == 0x220 ||
+ io[i] == 0x210 ||
+ io[i] == 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 0;
+ }
+#else
+ if (io[i] < 0x100 || io[i] > 0x3e0 || (io[i] % 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 0;
+ }
+#endif /* MSND_CLASSIC */
+
+ if (!(irq[i] == 5 ||
+ irq[i] == 7 ||
+ irq[i] == 9 ||
+ irq[i] == 10 ||
+ irq[i] == 11 ||
+ irq[i] == 12)) {
+ printk(KERN_ERR LOGNAME
+ ": \"irq\" - must be set to 5, 7, 9, 10, 11 or 12\n");
+ return 0;
+ }
+
+ if (!(mem[i] == 0xb0000 ||
+ mem[i] == 0xc8000 ||
+ mem[i] == 0xd0000 ||
+ mem[i] == 0xd8000 ||
+ mem[i] == 0xe0000 ||
+ mem[i] == 0xe8000)) {
+ printk(KERN_ERR LOGNAME ": \"mem\" - must be set to "
+ "0xb0000, 0xc8000, 0xd0000, 0xd8000, 0xe0000 or "
+ "0xe8000\n");
+ return 0;
+ }
+
+#ifndef MSND_CLASSIC
+ if (cfg[i] == SNDRV_AUTO_PORT) {
+ printk(KERN_INFO LOGNAME ": Assuming PnP mode\n");
+ } else if (cfg[i] != 0x250 && cfg[i] != 0x260 && cfg[i] != 0x270) {
+ printk(KERN_INFO LOGNAME
+ ": Config port must be 0x250, 0x260 or 0x270 "
+ "(or unspecified for PnP mode)\n");
+ return 0;
+ }
+#endif /* MSND_CLASSIC */
+
+ return 1;
+}
+
+static int snd_msnd_isa_probe(struct device *pdev, unsigned int idx)
+{
+ int err;
+ struct snd_card *card;
+ struct snd_msnd *chip;
+
+ if (has_isapnp(idx)
+#ifndef MSND_CLASSIC
+ || cfg[idx] == SNDRV_AUTO_PORT
+#endif
+ ) {
+ printk(KERN_INFO LOGNAME ": Assuming PnP mode\n");
+ return -ENODEV;
+ }
+
+ err = snd_devm_card_new(pdev, index[idx], id[idx], THIS_MODULE,
+ sizeof(struct snd_msnd), &card);
+ if (err < 0)
+ return err;
+
+ chip = card->private_data;
+ chip->card = card;
+
+#ifdef MSND_CLASSIC
+ switch (irq[idx]) {
+ case 5:
+ chip->irqid = HPIRQ_5; break;
+ case 7:
+ chip->irqid = HPIRQ_7; break;
+ case 9:
+ chip->irqid = HPIRQ_9; break;
+ case 10:
+ chip->irqid = HPIRQ_10; break;
+ case 11:
+ chip->irqid = HPIRQ_11; break;
+ case 12:
+ chip->irqid = HPIRQ_12; break;
+ }
+
+ switch (mem[idx]) {
+ case 0xb0000:
+ chip->memid = HPMEM_B000; break;
+ case 0xc8000:
+ chip->memid = HPMEM_C800; break;
+ case 0xd0000:
+ chip->memid = HPMEM_D000; break;
+ case 0xd8000:
+ chip->memid = HPMEM_D800; break;
+ case 0xe0000:
+ chip->memid = HPMEM_E000; break;
+ case 0xe8000:
+ chip->memid = HPMEM_E800; break;
+ }
+#else
+ printk(KERN_INFO LOGNAME ": Non-PnP mode: configuring at port 0x%lx\n",
+ cfg[idx]);
+
+ if (!devm_request_region(card->dev, cfg[idx], 2,
+ "Pinnacle/Fiji Config")) {
+ printk(KERN_ERR LOGNAME ": Config port 0x%lx conflict\n",
+ cfg[idx]);
+ return -EIO;
+ }
+ if (reset[idx])
+ if (snd_msnd_pinnacle_cfg_reset(cfg[idx]))
+ return -EIO;
+
+ /* DSP */
+ err = snd_msnd_write_cfg_logical(cfg[idx], 0,
+ io[idx], 0,
+ irq[idx], mem[idx]);
+
+ if (err)
+ return err;
+
+ /* The following are Pinnacle specific */
+
+ /* MPU */
+ if (mpu_io[idx] != SNDRV_AUTO_PORT
+ && mpu_irq[idx] != SNDRV_AUTO_IRQ) {
+ printk(KERN_INFO LOGNAME
+ ": Configuring MPU to I/O 0x%lx IRQ %d\n",
+ mpu_io[idx], mpu_irq[idx]);
+ err = snd_msnd_write_cfg_logical(cfg[idx], 1,
+ mpu_io[idx], 0,
+ mpu_irq[idx], 0);
+
+ if (err)
+ return err;
+ }
+
+ /* IDE */
+ if (ide_io0[idx] != SNDRV_AUTO_PORT
+ && ide_io1[idx] != SNDRV_AUTO_PORT
+ && ide_irq[idx] != SNDRV_AUTO_IRQ) {
+ printk(KERN_INFO LOGNAME
+ ": Configuring IDE to I/O 0x%lx, 0x%lx IRQ %d\n",
+ ide_io0[idx], ide_io1[idx], ide_irq[idx]);
+ err = snd_msnd_write_cfg_logical(cfg[idx], 2,
+ ide_io0[idx], ide_io1[idx],
+ ide_irq[idx], 0);
+
+ if (err)
+ return err;
+ }
+
+ /* Joystick */
+ if (joystick_io[idx] != SNDRV_AUTO_PORT) {
+ printk(KERN_INFO LOGNAME
+ ": Configuring joystick to I/O 0x%lx\n",
+ joystick_io[idx]);
+ err = snd_msnd_write_cfg_logical(cfg[idx], 3,
+ joystick_io[idx], 0,
+ 0, 0);
+
+ if (err)
+ return err;
+ }
+
+#endif /* MSND_CLASSIC */
+
+ set_default_audio_parameters(chip);
+#ifdef MSND_CLASSIC
+ chip->type = msndClassic;
+#else
+ chip->type = msndPinnacle;
+#endif
+ chip->io = io[idx];
+ chip->irq = irq[idx];
+ chip->base = mem[idx];
+
+ chip->calibrate_signal = calibrate_signal ? 1 : 0;
+ chip->recsrc = 0;
+ chip->dspq_data_buff = DSPQ_DATA_BUFF;
+ chip->dspq_buff_size = DSPQ_BUFF_SIZE;
+ if (write_ndelay[idx])
+ clear_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+ else
+ set_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+#ifndef MSND_CLASSIC
+ if (digital[idx])
+ set_bit(F_HAVEDIGITAL, &chip->flags);
+#endif
+ spin_lock_init(&chip->lock);
+ err = snd_msnd_probe(card);
+ if (err < 0) {
+ printk(KERN_ERR LOGNAME ": Probe failed\n");
+ return err;
+ }
+
+ err = snd_msnd_attach(card);
+ if (err < 0) {
+ printk(KERN_ERR LOGNAME ": Attach failed\n");
+ return err;
+ }
+ dev_set_drvdata(pdev, card);
+
+ return 0;
+}
+
+static struct isa_driver snd_msnd_driver = {
+ .match = snd_msnd_isa_match,
+ .probe = snd_msnd_isa_probe,
+ /* FIXME: suspend, resume */
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+#ifdef CONFIG_PNP
+static int snd_msnd_pnp_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ static int idx;
+ struct pnp_dev *pnp_dev;
+ struct pnp_dev *mpu_dev;
+ struct snd_card *card;
+ struct snd_msnd *chip;
+ int ret;
+
+ for ( ; idx < SNDRV_CARDS; idx++) {
+ if (has_isapnp(idx))
+ break;
+ }
+ if (idx >= SNDRV_CARDS)
+ return -ENODEV;
+
+ /*
+ * Check that we still have room for another sound card ...
+ */
+ pnp_dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+ if (!pnp_dev)
+ return -ENODEV;
+
+ mpu_dev = pnp_request_card_device(pcard, pid->devs[1].id, NULL);
+ if (!mpu_dev)
+ return -ENODEV;
+
+ if (!pnp_is_active(pnp_dev) && pnp_activate_dev(pnp_dev) < 0) {
+ printk(KERN_INFO "msnd_pinnacle: device is inactive\n");
+ return -EBUSY;
+ }
+
+ if (!pnp_is_active(mpu_dev) && pnp_activate_dev(mpu_dev) < 0) {
+ printk(KERN_INFO "msnd_pinnacle: MPU device is inactive\n");
+ return -EBUSY;
+ }
+
+ /*
+ * Create a new ALSA sound card entry, in anticipation
+ * of detecting our hardware ...
+ */
+ ret = snd_devm_card_new(&pcard->card->dev,
+ index[idx], id[idx], THIS_MODULE,
+ sizeof(struct snd_msnd), &card);
+ if (ret < 0)
+ return ret;
+
+ chip = card->private_data;
+ chip->card = card;
+
+ /*
+ * Read the correct parameters off the ISA PnP bus ...
+ */
+ io[idx] = pnp_port_start(pnp_dev, 0);
+ irq[idx] = pnp_irq(pnp_dev, 0);
+ mem[idx] = pnp_mem_start(pnp_dev, 0);
+ mpu_io[idx] = pnp_port_start(mpu_dev, 0);
+ mpu_irq[idx] = pnp_irq(mpu_dev, 0);
+
+ set_default_audio_parameters(chip);
+#ifdef MSND_CLASSIC
+ chip->type = msndClassic;
+#else
+ chip->type = msndPinnacle;
+#endif
+ chip->io = io[idx];
+ chip->irq = irq[idx];
+ chip->base = mem[idx];
+
+ chip->calibrate_signal = calibrate_signal ? 1 : 0;
+ chip->recsrc = 0;
+ chip->dspq_data_buff = DSPQ_DATA_BUFF;
+ chip->dspq_buff_size = DSPQ_BUFF_SIZE;
+ if (write_ndelay[idx])
+ clear_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+ else
+ set_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+#ifndef MSND_CLASSIC
+ if (digital[idx])
+ set_bit(F_HAVEDIGITAL, &chip->flags);
+#endif
+ spin_lock_init(&chip->lock);
+ ret = snd_msnd_probe(card);
+ if (ret < 0) {
+ printk(KERN_ERR LOGNAME ": Probe failed\n");
+ return ret;
+ }
+
+ ret = snd_msnd_attach(card);
+ if (ret < 0) {
+ printk(KERN_ERR LOGNAME ": Attach failed\n");
+ return ret;
+ }
+
+ pnp_set_card_drvdata(pcard, card);
+ ++idx;
+ return 0;
+}
+
+static int isa_registered;
+static int pnp_registered;
+
+static const struct pnp_card_device_id msnd_pnpids[] = {
+ /* Pinnacle PnP */
+ { .id = "BVJ0440", .devs = { { "TBS0000" }, { "TBS0001" } } },
+ { .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, msnd_pnpids);
+
+static struct pnp_card_driver msnd_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
+ .name = "msnd_pinnacle",
+ .id_table = msnd_pnpids,
+ .probe = snd_msnd_pnp_detect,
+};
+#endif /* CONFIG_PNP */
+
+static int __init snd_msnd_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&snd_msnd_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+
+ err = pnp_register_card_driver(&msnd_pnpc_driver);
+ if (!err)
+ pnp_registered = 1;
+
+ if (isa_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit snd_msnd_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ pnp_unregister_card_driver(&msnd_pnpc_driver);
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&snd_msnd_driver);
+}
+
+module_init(snd_msnd_init);
+module_exit(snd_msnd_exit);
+
diff --git a/sound/isa/msnd/msnd_pinnacle.h b/sound/isa/msnd/msnd_pinnacle.h
new file mode 100644
index 0000000000..c928d8492e
--- /dev/null
+++ b/sound/isa/msnd/msnd_pinnacle.h
@@ -0,0 +1,168 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*********************************************************************
+ *
+ * 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.
+ *
+ ********************************************************************/
+#ifndef __MSND_PINNACLE_H
+#define __MSND_PINNACLE_H
+
+#define DSP_NUMIO 0x08
+
+#define IREG_LOGDEVICE 0x07
+#define IREG_ACTIVATE 0x30
+#define LD_ACTIVATE 0x01
+#define LD_DISACTIVATE 0x00
+#define IREG_EECONTROL 0x3F
+#define IREG_MEMBASEHI 0x40
+#define IREG_MEMBASELO 0x41
+#define IREG_MEMCONTROL 0x42
+#define IREG_MEMRANGEHI 0x43
+#define IREG_MEMRANGELO 0x44
+#define MEMTYPE_8BIT 0x00
+#define MEMTYPE_16BIT 0x02
+#define MEMTYPE_RANGE 0x00
+#define MEMTYPE_HIADDR 0x01
+#define IREG_IO0_BASEHI 0x60
+#define IREG_IO0_BASELO 0x61
+#define IREG_IO1_BASEHI 0x62
+#define IREG_IO1_BASELO 0x63
+#define IREG_IRQ_NUMBER 0x70
+#define IREG_IRQ_TYPE 0x71
+#define IRQTYPE_HIGH 0x02
+#define IRQTYPE_LOW 0x00
+#define IRQTYPE_LEVEL 0x01
+#define IRQTYPE_EDGE 0x00
+
+#define HP_DSPR 0x04
+#define HP_BLKS 0x04
+
+#define HPDSPRESET_OFF 2
+#define HPDSPRESET_ON 0
+
+#define HPBLKSEL_0 2
+#define HPBLKSEL_1 3
+
+#define HIMT_DAT_OFF 0x03
+
+#define HIDSP_PLAY_UNDER 0x00
+#define HIDSP_INT_PLAY_UNDER 0x01
+#define HIDSP_SSI_TX_UNDER 0x02
+#define HIDSP_RECQ_OVERFLOW 0x08
+#define HIDSP_INT_RECORD_OVER 0x09
+#define HIDSP_SSI_RX_OVERFLOW 0x0a
+
+#define HIDSP_MIDI_IN_OVER 0x10
+
+#define HIDSP_MIDI_FRAME_ERR 0x11
+#define HIDSP_MIDI_PARITY_ERR 0x12
+#define HIDSP_MIDI_OVERRUN_ERR 0x13
+
+#define HIDSP_INPUT_CLIPPING 0x20
+#define HIDSP_MIX_CLIPPING 0x30
+#define HIDSP_DAT_IN_OFF 0x21
+
+#define TIME_PRO_RESET_DONE 0x028A
+#define TIME_PRO_SYSEX 0x001E
+#define TIME_PRO_RESET 0x0032
+
+#define DAR_BUFF_SIZE 0x1000
+
+#define MIDQ_BUFF_SIZE 0x800
+#define DSPQ_BUFF_SIZE 0x5A0
+
+#define DSPQ_DATA_BUFF 0x7860
+
+#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
+
+#define INITCODEFILE "turtlebeach/pndspini.bin"
+#define PERMCODEFILE "turtlebeach/pndsperm.bin"
+#define LONGNAME "MultiSound (Pinnacle/Fiji)"
+
+#endif /* __MSND_PINNACLE_H */
diff --git a/sound/isa/msnd/msnd_pinnacle_mixer.c b/sound/isa/msnd/msnd_pinnacle_mixer.c
new file mode 100644
index 0000000000..63633bd41e
--- /dev/null
+++ b/sound/isa/msnd/msnd_pinnacle_mixer.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/***************************************************************************
+ msnd_pinnacle_mixer.c - description
+ -------------------
+ begin : Fre Jun 7 2002
+ copyright : (C) 2002 by karsten wiese
+ email : annabellesgarden@yahoo.de
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * *
+ ***************************************************************************/
+
+#include <linux/io.h>
+#include <linux/export.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include "msnd.h"
+#include "msnd_pinnacle.h"
+
+
+#define MSND_MIXER_VOLUME 0
+#define MSND_MIXER_PCM 1
+#define MSND_MIXER_AUX 2 /* Input source 1 (aux1) */
+#define MSND_MIXER_IMIX 3 /* Recording monitor */
+#define MSND_MIXER_SYNTH 4
+#define MSND_MIXER_SPEAKER 5
+#define MSND_MIXER_LINE 6
+#define MSND_MIXER_MIC 7
+#define MSND_MIXER_RECLEV 11 /* Recording level */
+#define MSND_MIXER_IGAIN 12 /* Input gain */
+#define MSND_MIXER_OGAIN 13 /* Output gain */
+#define MSND_MIXER_DIGITAL 17 /* Digital (input) 1 */
+
+/* Device mask bits */
+
+#define MSND_MASK_VOLUME (1 << MSND_MIXER_VOLUME)
+#define MSND_MASK_SYNTH (1 << MSND_MIXER_SYNTH)
+#define MSND_MASK_PCM (1 << MSND_MIXER_PCM)
+#define MSND_MASK_SPEAKER (1 << MSND_MIXER_SPEAKER)
+#define MSND_MASK_LINE (1 << MSND_MIXER_LINE)
+#define MSND_MASK_MIC (1 << MSND_MIXER_MIC)
+#define MSND_MASK_IMIX (1 << MSND_MIXER_IMIX)
+#define MSND_MASK_RECLEV (1 << MSND_MIXER_RECLEV)
+#define MSND_MASK_IGAIN (1 << MSND_MIXER_IGAIN)
+#define MSND_MASK_OGAIN (1 << MSND_MIXER_OGAIN)
+#define MSND_MASK_AUX (1 << MSND_MIXER_AUX)
+#define MSND_MASK_DIGITAL (1 << MSND_MIXER_DIGITAL)
+
+static int snd_msndmix_info_mux(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[3] = {
+ "Analog", "MASS", "SPDIF",
+ };
+ struct snd_msnd *chip = snd_kcontrol_chip(kcontrol);
+ unsigned items = test_bit(F_HAVEDIGITAL, &chip->flags) ? 3 : 2;
+
+ return snd_ctl_enum_info(uinfo, 1, items, texts);
+}
+
+static int snd_msndmix_get_mux(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_msnd *chip = snd_kcontrol_chip(kcontrol);
+ /* MSND_MASK_IMIX is the default */
+ ucontrol->value.enumerated.item[0] = 0;
+
+ if (chip->recsrc & MSND_MASK_SYNTH) {
+ ucontrol->value.enumerated.item[0] = 1;
+ } else if ((chip->recsrc & MSND_MASK_DIGITAL) &&
+ test_bit(F_HAVEDIGITAL, &chip->flags)) {
+ ucontrol->value.enumerated.item[0] = 2;
+ }
+
+
+ return 0;
+}
+
+static int snd_msndmix_set_mux(struct snd_msnd *chip, int val)
+{
+ unsigned newrecsrc;
+ int change;
+ unsigned char msndbyte;
+
+ switch (val) {
+ case 0:
+ newrecsrc = MSND_MASK_IMIX;
+ msndbyte = HDEXAR_SET_ANA_IN;
+ break;
+ case 1:
+ newrecsrc = MSND_MASK_SYNTH;
+ msndbyte = HDEXAR_SET_SYNTH_IN;
+ break;
+ case 2:
+ newrecsrc = MSND_MASK_DIGITAL;
+ msndbyte = HDEXAR_SET_DAT_IN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ change = newrecsrc != chip->recsrc;
+ if (change) {
+ change = 0;
+ if (!snd_msnd_send_word(chip, 0, 0, msndbyte))
+ if (!snd_msnd_send_dsp_cmd(chip, HDEX_AUX_REQ)) {
+ chip->recsrc = newrecsrc;
+ change = 1;
+ }
+ }
+ return change;
+}
+
+static int snd_msndmix_put_mux(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol);
+ return snd_msndmix_set_mux(msnd, ucontrol->value.enumerated.item[0]);
+}
+
+
+static int snd_msndmix_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 100;
+ return 0;
+}
+
+static int snd_msndmix_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol);
+ int addr = kcontrol->private_value;
+ unsigned long flags;
+
+ spin_lock_irqsave(&msnd->mixer_lock, flags);
+ ucontrol->value.integer.value[0] = msnd->left_levels[addr] * 100;
+ ucontrol->value.integer.value[0] /= 0xFFFF;
+ ucontrol->value.integer.value[1] = msnd->right_levels[addr] * 100;
+ ucontrol->value.integer.value[1] /= 0xFFFF;
+ spin_unlock_irqrestore(&msnd->mixer_lock, flags);
+ return 0;
+}
+
+#define update_volm(a, b) \
+ do { \
+ 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); \
+ } while (0);
+
+#define update_potm(d, s, ar) \
+ do { \
+ 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 (snd_msnd_send_word(dev, 0, 0, ar) == 0) \
+ snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ); \
+ } while (0);
+
+#define update_pot(d, s, ar) \
+ do { \
+ writeb(dev->left_levels[d] >> 8, \
+ dev->SMA + SMA_##s##Left); \
+ writeb(dev->right_levels[d] >> 8, \
+ dev->SMA + SMA_##s##Right); \
+ if (snd_msnd_send_word(dev, 0, 0, ar) == 0) \
+ snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ); \
+ } while (0);
+
+
+static int snd_msndmix_set(struct snd_msnd *dev, int d, int left, int right)
+{
+ int bLeft, bRight;
+ int wLeft, wRight;
+ int updatemaster = 0;
+
+ if (d >= LEVEL_ENTRIES)
+ 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 MSND_MIXER_LINE: /* line pot control */
+ /* scaled by IMIX in digital mix */
+ writeb(bLeft, dev->SMA + SMA_bInPotPosLeft);
+ writeb(bRight, dev->SMA + SMA_bInPotPosRight);
+ if (snd_msnd_send_word(dev, 0, 0, HDEXAR_IN_SET_POTS) == 0)
+ snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);
+ break;
+ case MSND_MIXER_MIC: /* mic pot control */
+ if (dev->type == msndClassic)
+ return -EINVAL;
+ /* scaled by IMIX in digital mix */
+ writeb(bLeft, dev->SMA + SMA_bMicPotPosLeft);
+ writeb(bRight, dev->SMA + SMA_bMicPotPosRight);
+ if (snd_msnd_send_word(dev, 0, 0, HDEXAR_MIC_SET_POTS) == 0)
+ snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);
+ break;
+ case MSND_MIXER_VOLUME: /* master volume */
+ writew(wLeft, dev->SMA + SMA_wCurrMastVolLeft);
+ writew(wRight, dev->SMA + SMA_wCurrMastVolRight);
+ fallthrough;
+ case MSND_MIXER_AUX: /* aux pot control */
+ /* scaled by master volume */
+
+ /* digital controls */
+ case MSND_MIXER_SYNTH: /* synth vol (dsp mix) */
+ case MSND_MIXER_PCM: /* pcm vol (dsp mix) */
+ case MSND_MIXER_IMIX: /* input monitor (dsp mix) */
+ /* scaled by master volume */
+ updatemaster = 1;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (updatemaster) {
+ /* update master volume scaled controls */
+ update_volm(MSND_MIXER_PCM, wCurrPlayVol);
+ update_volm(MSND_MIXER_IMIX, wCurrInVol);
+ if (dev->type == msndPinnacle)
+ update_volm(MSND_MIXER_SYNTH, wCurrMHdrVol);
+ update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+ }
+
+ return 0;
+}
+
+static int snd_msndmix_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol);
+ int change, addr = kcontrol->private_value;
+ int left, right;
+ unsigned long flags;
+
+ left = ucontrol->value.integer.value[0] % 101;
+ right = ucontrol->value.integer.value[1] % 101;
+ spin_lock_irqsave(&msnd->mixer_lock, flags);
+ change = msnd->left_levels[addr] != left
+ || msnd->right_levels[addr] != right;
+ snd_msndmix_set(msnd, addr, left, right);
+ spin_unlock_irqrestore(&msnd->mixer_lock, flags);
+ return change;
+}
+
+
+#define DUMMY_VOLUME(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_msndmix_volume_info, \
+ .get = snd_msndmix_volume_get, .put = snd_msndmix_volume_put, \
+ .private_value = addr }
+
+
+static const struct snd_kcontrol_new snd_msnd_controls[] = {
+DUMMY_VOLUME("Master Volume", 0, MSND_MIXER_VOLUME),
+DUMMY_VOLUME("PCM Volume", 0, MSND_MIXER_PCM),
+DUMMY_VOLUME("Aux Volume", 0, MSND_MIXER_AUX),
+DUMMY_VOLUME("Line Volume", 0, MSND_MIXER_LINE),
+DUMMY_VOLUME("Mic Volume", 0, MSND_MIXER_MIC),
+DUMMY_VOLUME("Monitor", 0, MSND_MIXER_IMIX),
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = snd_msndmix_info_mux,
+ .get = snd_msndmix_get_mux,
+ .put = snd_msndmix_put_mux,
+}
+};
+
+
+int snd_msndmix_new(struct snd_card *card)
+{
+ struct snd_msnd *chip = card->private_data;
+ unsigned int idx;
+ int err;
+
+ if (snd_BUG_ON(!chip))
+ return -EINVAL;
+ spin_lock_init(&chip->mixer_lock);
+ strcpy(card->mixername, "MSND Pinnacle Mixer");
+
+ for (idx = 0; idx < ARRAY_SIZE(snd_msnd_controls); idx++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(snd_msnd_controls + idx, chip));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(snd_msndmix_new);
+
+void snd_msndmix_setup(struct snd_msnd *dev)
+{
+ update_pot(MSND_MIXER_LINE, bInPotPos, HDEXAR_IN_SET_POTS);
+ update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+ update_volm(MSND_MIXER_PCM, wCurrPlayVol);
+ update_volm(MSND_MIXER_IMIX, wCurrInVol);
+ if (dev->type == msndPinnacle) {
+ update_pot(MSND_MIXER_MIC, bMicPotPos, HDEXAR_MIC_SET_POTS);
+ update_volm(MSND_MIXER_SYNTH, wCurrMHdrVol);
+ }
+}
+EXPORT_SYMBOL(snd_msndmix_setup);
+
+int snd_msndmix_force_recsrc(struct snd_msnd *dev, int recsrc)
+{
+ dev->recsrc = -1;
+ return snd_msndmix_set_mux(dev, recsrc);
+}
+EXPORT_SYMBOL(snd_msndmix_force_recsrc);
diff --git a/sound/isa/opl3sa2.c b/sound/isa/opl3sa2.c
new file mode 100644
index 0000000000..bad1490a66
--- /dev/null
+++ b/sound/isa/opl3sa2.c
@@ -0,0 +1,928 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Yamaha OPL3-SA[2,3] soundcards
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Yamaha OPL3SA2+");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0xf86,0x370,0x100 */
+static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x530,0xe80,0xf40,0x604 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x388 */
+static long midi_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x330,0x300 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 0,1,3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */
+static int opl3sa3_ymode[SNDRV_CARDS]; /* 0,1,2,3 */ /*SL Added*/
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for OPL3-SA soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for OPL3-SA soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable OPL3-SA soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for OPL3-SA driver.");
+module_param_hw_array(sb_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(sb_port, "SB port # for OPL3-SA driver.");
+module_param_hw_array(wss_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS port # for OPL3-SA driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for OPL3-SA driver.");
+module_param_hw_array(midi_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(midi_port, "MIDI port # for OPL3-SA driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for OPL3-SA driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for OPL3-SA driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for OPL3-SA driver.");
+module_param_array(opl3sa3_ymode, int, NULL, 0444);
+MODULE_PARM_DESC(opl3sa3_ymode, "Speaker size selection for 3D Enhancement mode: Desktop/Large Notebook/Small Notebook/HiFi.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+static int pnpc_registered;
+#endif
+
+/* control ports */
+#define OPL3SA2_PM_CTRL 0x01
+#define OPL3SA2_SYS_CTRL 0x02
+#define OPL3SA2_IRQ_CONFIG 0x03
+#define OPL3SA2_IRQ_STATUS 0x04
+#define OPL3SA2_DMA_CONFIG 0x06
+#define OPL3SA2_MASTER_LEFT 0x07
+#define OPL3SA2_MASTER_RIGHT 0x08
+#define OPL3SA2_MIC 0x09
+#define OPL3SA2_MISC 0x0A
+
+/* opl3sa3 only */
+#define OPL3SA3_DGTL_DOWN 0x12
+#define OPL3SA3_ANLG_DOWN 0x13
+#define OPL3SA3_WIDE 0x14
+#define OPL3SA3_BASS 0x15
+#define OPL3SA3_TREBLE 0x16
+
+/* power management bits */
+#define OPL3SA2_PM_ADOWN 0x20
+#define OPL3SA2_PM_PSV 0x04
+#define OPL3SA2_PM_PDN 0x02
+#define OPL3SA2_PM_PDX 0x01
+
+#define OPL3SA2_PM_D0 0x00
+#define OPL3SA2_PM_D3 (OPL3SA2_PM_ADOWN|OPL3SA2_PM_PSV|OPL3SA2_PM_PDN|OPL3SA2_PM_PDX)
+
+struct snd_opl3sa2 {
+ int version; /* 2 or 3 */
+ unsigned long port; /* control port */
+ struct resource *res_port; /* control port resource */
+ int irq;
+ int single_dma;
+ spinlock_t reg_lock;
+ struct snd_hwdep *synth;
+ struct snd_rawmidi *rmidi;
+ struct snd_wss *wss;
+ unsigned char ctlregs[0x20];
+ int ymode; /* SL added */
+ struct snd_kcontrol *master_switch;
+ struct snd_kcontrol *master_volume;
+};
+
+#define PFX "opl3sa2: "
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_device_id snd_opl3sa2_pnpbiosids[] = {
+ { .id = "YMH0021" },
+ { .id = "NMX2210" }, /* Gateway Solo 2500 */
+ { .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp, snd_opl3sa2_pnpbiosids);
+
+static const struct pnp_card_device_id snd_opl3sa2_pnpids[] = {
+ /* Yamaha YMF719E-S (Genius Sound Maker 3DX) */
+ { .id = "YMH0020", .devs = { { "YMH0021" } } },
+ /* Yamaha OPL3-SA3 (integrated on Intel's Pentium II AL440LX motherboard) */
+ { .id = "YMH0030", .devs = { { "YMH0021" } } },
+ /* Yamaha OPL3-SA2 */
+ { .id = "YMH0800", .devs = { { "YMH0021" } } },
+ /* Yamaha OPL3-SA2 */
+ { .id = "YMH0801", .devs = { { "YMH0021" } } },
+ /* NeoMagic MagicWave 3DX */
+ { .id = "NMX2200", .devs = { { "YMH2210" } } },
+ /* NeoMagic MagicWave 3D */
+ { .id = "NMX2200", .devs = { { "NMX2210" } } },
+ /* --- */
+ { .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_opl3sa2_pnpids);
+
+#endif /* CONFIG_PNP */
+
+
+/* read control port (w/o spinlock) */
+static unsigned char __snd_opl3sa2_read(struct snd_opl3sa2 *chip, unsigned char reg)
+{
+ unsigned char result;
+#if 0
+ outb(0x1d, port); /* password */
+ printk(KERN_DEBUG "read [0x%lx] = 0x%x\n", port, inb(port));
+#endif
+ outb(reg, chip->port); /* register */
+ result = inb(chip->port + 1);
+#if 0
+ printk(KERN_DEBUG "read [0x%lx] = 0x%x [0x%x]\n",
+ port, result, inb(port));
+#endif
+ return result;
+}
+
+/* read control port (with spinlock) */
+static unsigned char snd_opl3sa2_read(struct snd_opl3sa2 *chip, unsigned char reg)
+{
+ unsigned long flags;
+ unsigned char result;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ result = __snd_opl3sa2_read(chip, reg);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return result;
+}
+
+/* write control port (w/o spinlock) */
+static void __snd_opl3sa2_write(struct snd_opl3sa2 *chip, unsigned char reg, unsigned char value)
+{
+#if 0
+ outb(0x1d, port); /* password */
+#endif
+ outb(reg, chip->port); /* register */
+ outb(value, chip->port + 1);
+ chip->ctlregs[reg] = value;
+}
+
+/* write control port (with spinlock) */
+static void snd_opl3sa2_write(struct snd_opl3sa2 *chip, unsigned char reg, unsigned char value)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ __snd_opl3sa2_write(chip, reg, value);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static int snd_opl3sa2_detect(struct snd_card *card)
+{
+ struct snd_opl3sa2 *chip = card->private_data;
+ unsigned long port;
+ unsigned char tmp, tmp1;
+ char str[2];
+
+ port = chip->port;
+ chip->res_port = devm_request_region(card->dev, port, 2,
+ "OPL3-SA control");
+ if (!chip->res_port) {
+ snd_printk(KERN_ERR PFX "can't grab port 0x%lx\n", port);
+ return -EBUSY;
+ }
+ /*
+ snd_printk(KERN_DEBUG "REG 0A = 0x%x\n",
+ snd_opl3sa2_read(chip, 0x0a));
+ */
+ chip->version = 0;
+ tmp = snd_opl3sa2_read(chip, OPL3SA2_MISC);
+ if (tmp == 0xff) {
+ snd_printd("OPL3-SA [0x%lx] detect = 0x%x\n", port, tmp);
+ return -ENODEV;
+ }
+ switch (tmp & 0x07) {
+ case 0x01:
+ chip->version = 2; /* YMF711 */
+ break;
+ default:
+ chip->version = 3;
+ /* 0x02 - standard */
+ /* 0x03 - YM715B */
+ /* 0x04 - YM719 - OPL-SA4? */
+ /* 0x05 - OPL3-SA3 - Libretto 100 */
+ /* 0x07 - unknown - Neomagic MagicWave 3D */
+ break;
+ }
+ str[0] = chip->version + '0';
+ str[1] = 0;
+ strcat(card->shortname, str);
+ snd_opl3sa2_write(chip, OPL3SA2_MISC, tmp ^ 7);
+ tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MISC);
+ if (tmp1 != tmp) {
+ snd_printd("OPL3-SA [0x%lx] detect (1) = 0x%x (0x%x)\n", port, tmp, tmp1);
+ return -ENODEV;
+ }
+ /* try if the MIC register is accessible */
+ tmp = snd_opl3sa2_read(chip, OPL3SA2_MIC);
+ snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x8a);
+ tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MIC);
+ if ((tmp1 & 0x9f) != 0x8a) {
+ snd_printd("OPL3-SA [0x%lx] detect (2) = 0x%x (0x%x)\n", port, tmp, tmp1);
+ return -ENODEV;
+ }
+ snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x9f);
+ /* initialization */
+ /* Power Management - full on */
+ snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0);
+ if (chip->version > 2) {
+ /* ymode is bits 4&5 (of 0 to 7) on all but opl3sa2 versions */
+ snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, (chip->ymode << 4));
+ } else {
+ /* default for opl3sa2 versions */
+ snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, 0x00);
+ }
+ snd_opl3sa2_write(chip, OPL3SA2_IRQ_CONFIG, 0x0d); /* Interrupt Channel Configuration - IRQ A = OPL3 + MPU + WSS */
+ if (chip->single_dma) {
+ snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x03); /* DMA Configuration - DMA A = WSS-R + WSS-P */
+ } else {
+ snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x21); /* DMA Configuration - DMA B = WSS-R, DMA A = WSS-P */
+ }
+ snd_opl3sa2_write(chip, OPL3SA2_MISC, 0x80 | (tmp & 7)); /* Miscellaneous - default */
+ if (chip->version > 2) {
+ snd_opl3sa2_write(chip, OPL3SA3_DGTL_DOWN, 0x00); /* Digital Block Partial Power Down - default */
+ snd_opl3sa2_write(chip, OPL3SA3_ANLG_DOWN, 0x00); /* Analog Block Partial Power Down - default */
+ }
+ return 0;
+}
+
+static irqreturn_t snd_opl3sa2_interrupt(int irq, void *dev_id)
+{
+ unsigned short status;
+ struct snd_card *card = dev_id;
+ struct snd_opl3sa2 *chip;
+ int handled = 0;
+
+ if (card == NULL)
+ return IRQ_NONE;
+
+ chip = card->private_data;
+ status = snd_opl3sa2_read(chip, OPL3SA2_IRQ_STATUS);
+
+ if (status & 0x20) {
+ handled = 1;
+ snd_opl3_interrupt(chip->synth);
+ }
+
+ if ((status & 0x10) && chip->rmidi != NULL) {
+ handled = 1;
+ snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+ }
+
+ if (status & 0x07) { /* TI,CI,PI */
+ handled = 1;
+ snd_wss_interrupt(irq, chip->wss);
+ }
+
+ if (status & 0x40) { /* hardware volume change */
+ handled = 1;
+ /* reading from Master Lch register at 0x07 clears this bit */
+ snd_opl3sa2_read(chip, OPL3SA2_MASTER_RIGHT);
+ snd_opl3sa2_read(chip, OPL3SA2_MASTER_LEFT);
+ if (chip->master_switch && chip->master_volume) {
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &chip->master_switch->id);
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &chip->master_volume->id);
+ }
+ }
+ return IRQ_RETVAL(handled);
+}
+
+#define OPL3SA2_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_wss_info_single, \
+ .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \
+ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+#define OPL3SA2_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .name = xname, .index = xindex, \
+ .info = snd_wss_info_single, \
+ .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \
+ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
+ .tlv = { .p = (xtlv) } }
+
+static int snd_opl3sa2_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (chip->ctlregs[reg] >> shift) & mask;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (invert)
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int snd_opl3sa2_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+ int change;
+ unsigned short val, oval;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+ if (invert)
+ val = mask - val;
+ val <<= shift;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ oval = chip->ctlregs[reg];
+ val = (oval & ~(mask << shift)) | val;
+ change = val != oval;
+ __snd_opl3sa2_write(chip, reg, val);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+#define OPL3SA2_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+ .info = snd_wss_info_double, \
+ .get = snd_opl3sa2_get_double, .put = snd_opl3sa2_put_double, \
+ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+#define OPL3SA2_DOUBLE_TLV(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .name = xname, .index = xindex, \
+ .info = snd_wss_info_double, \
+ .get = snd_opl3sa2_get_double, .put = snd_opl3sa2_put_double, \
+ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22), \
+ .tlv = { .p = (xtlv) } }
+
+static int snd_opl3sa2_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (chip->ctlregs[left_reg] >> shift_left) & mask;
+ ucontrol->value.integer.value[1] = (chip->ctlregs[right_reg] >> shift_right) & mask;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (invert) {
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+ }
+ return 0;
+}
+
+static int snd_opl3sa2_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+ int change;
+ unsigned short val1, val2, oval1, oval2;
+
+ val1 = ucontrol->value.integer.value[0] & mask;
+ val2 = ucontrol->value.integer.value[1] & mask;
+ if (invert) {
+ val1 = mask - val1;
+ val2 = mask - val2;
+ }
+ val1 <<= shift_left;
+ val2 <<= shift_right;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (left_reg != right_reg) {
+ oval1 = chip->ctlregs[left_reg];
+ oval2 = chip->ctlregs[right_reg];
+ val1 = (oval1 & ~(mask << shift_left)) | val1;
+ val2 = (oval2 & ~(mask << shift_right)) | val2;
+ change = val1 != oval1 || val2 != oval2;
+ __snd_opl3sa2_write(chip, left_reg, val1);
+ __snd_opl3sa2_write(chip, right_reg, val2);
+ } else {
+ oval1 = chip->ctlregs[left_reg];
+ val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+ change = val1 != oval1;
+ __snd_opl3sa2_write(chip, left_reg, val1);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_master, -3000, 200, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+
+static const struct snd_kcontrol_new snd_opl3sa2_controls[] = {
+OPL3SA2_DOUBLE("Master Playback Switch", 0, 0x07, 0x08, 7, 7, 1, 1),
+OPL3SA2_DOUBLE_TLV("Master Playback Volume", 0, 0x07, 0x08, 0, 0, 15, 1,
+ db_scale_master),
+OPL3SA2_SINGLE("Mic Playback Switch", 0, 0x09, 7, 1, 1),
+OPL3SA2_SINGLE_TLV("Mic Playback Volume", 0, 0x09, 0, 31, 1,
+ db_scale_5bit_12db_max),
+OPL3SA2_SINGLE("ZV Port Switch", 0, 0x02, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new snd_opl3sa2_tone_controls[] = {
+OPL3SA2_DOUBLE("3D Control - Wide", 0, 0x14, 0x14, 4, 0, 7, 0),
+OPL3SA2_DOUBLE("Tone Control - Bass", 0, 0x15, 0x15, 4, 0, 7, 0),
+OPL3SA2_DOUBLE("Tone Control - Treble", 0, 0x16, 0x16, 4, 0, 7, 0)
+};
+
+static void snd_opl3sa2_master_free(struct snd_kcontrol *kcontrol)
+{
+ struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+ chip->master_switch = NULL;
+ chip->master_volume = NULL;
+}
+
+static int snd_opl3sa2_mixer(struct snd_card *card)
+{
+ struct snd_opl3sa2 *chip = card->private_data;
+ struct snd_ctl_elem_id id1, id2;
+ struct snd_kcontrol *kctl;
+ unsigned int idx;
+ int err;
+
+ memset(&id1, 0, sizeof(id1));
+ memset(&id2, 0, sizeof(id2));
+ id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ /* reassign AUX0 to CD */
+ strcpy(id1.name, "Aux Playback Switch");
+ strcpy(id2.name, "CD Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+ return err;
+ }
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "CD Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+ return err;
+ }
+ /* reassign AUX1 to FM */
+ strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+ strcpy(id2.name, "FM Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+ return err;
+ }
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "FM Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+ return err;
+ }
+ /* add OPL3SA2 controls */
+ for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_controls); idx++) {
+ kctl = snd_ctl_new1(&snd_opl3sa2_controls[idx], chip);
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ return err;
+ switch (idx) {
+ case 0: chip->master_switch = kctl; kctl->private_free = snd_opl3sa2_master_free; break;
+ case 1: chip->master_volume = kctl; kctl->private_free = snd_opl3sa2_master_free; break;
+ }
+ }
+ if (chip->version > 2) {
+ for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_tone_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_opl3sa2_tone_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ }
+ return 0;
+}
+
+/* Power Management support functions */
+#ifdef CONFIG_PM
+static int snd_opl3sa2_suspend(struct snd_card *card, pm_message_t state)
+{
+ if (card) {
+ struct snd_opl3sa2 *chip = card->private_data;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ chip->wss->suspend(chip->wss);
+ /* power down */
+ snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D3);
+ }
+
+ return 0;
+}
+
+static int snd_opl3sa2_resume(struct snd_card *card)
+{
+ struct snd_opl3sa2 *chip;
+ int i;
+
+ if (!card)
+ return 0;
+
+ chip = card->private_data;
+ /* power up */
+ snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0);
+
+ /* restore registers */
+ for (i = 2; i <= 0x0a; i++) {
+ if (i != OPL3SA2_IRQ_STATUS)
+ snd_opl3sa2_write(chip, i, chip->ctlregs[i]);
+ }
+ if (chip->version > 2) {
+ for (i = 0x12; i <= 0x16; i++)
+ snd_opl3sa2_write(chip, i, chip->ctlregs[i]);
+ }
+ /* restore wss */
+ chip->wss->resume(chip->wss);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PNP
+static int snd_opl3sa2_pnp(int dev, struct snd_opl3sa2 *chip,
+ struct pnp_dev *pdev)
+{
+ if (pnp_activate_dev(pdev) < 0) {
+ snd_printk(KERN_ERR "PnP configure failure (out of resources?)\n");
+ return -EBUSY;
+ }
+ sb_port[dev] = pnp_port_start(pdev, 0);
+ wss_port[dev] = pnp_port_start(pdev, 1);
+ fm_port[dev] = pnp_port_start(pdev, 2);
+ midi_port[dev] = pnp_port_start(pdev, 3);
+ port[dev] = pnp_port_start(pdev, 4);
+ dma1[dev] = pnp_dma(pdev, 0);
+ dma2[dev] = pnp_dma(pdev, 1);
+ irq[dev] = pnp_irq(pdev, 0);
+ snd_printdd("%sPnP OPL3-SA: sb port=0x%lx, wss port=0x%lx, fm port=0x%lx, midi port=0x%lx\n",
+ pnp_device_is_pnpbios(pdev) ? "BIOS" : "ISA", sb_port[dev], wss_port[dev], fm_port[dev], midi_port[dev]);
+ snd_printdd("%sPnP OPL3-SA: control port=0x%lx, dma1=%i, dma2=%i, irq=%i\n",
+ pnp_device_is_pnpbios(pdev) ? "BIOS" : "ISA", port[dev], dma1[dev], dma2[dev], irq[dev]);
+ return 0;
+}
+#endif /* CONFIG_PNP */
+
+static int snd_opl3sa2_card_new(struct device *pdev, int dev,
+ struct snd_card **cardp)
+{
+ struct snd_card *card;
+ struct snd_opl3sa2 *chip;
+ int err;
+
+ err = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_opl3sa2), &card);
+ if (err < 0)
+ return err;
+ strcpy(card->driver, "OPL3SA2");
+ strcpy(card->shortname, "Yamaha OPL3-SA");
+ chip = card->private_data;
+ spin_lock_init(&chip->reg_lock);
+ chip->irq = -1;
+ *cardp = card;
+ return 0;
+}
+
+static int snd_opl3sa2_probe(struct snd_card *card, int dev)
+{
+ int xirq, xdma1, xdma2;
+ struct snd_opl3sa2 *chip;
+ struct snd_wss *wss;
+ struct snd_opl3 *opl3;
+ int err;
+
+ /* initialise this card from supplied (or default) parameter*/
+ chip = card->private_data;
+ chip->ymode = opl3sa3_ymode[dev] & 0x03 ;
+ chip->port = port[dev];
+ xirq = irq[dev];
+ xdma1 = dma1[dev];
+ xdma2 = dma2[dev];
+ if (xdma2 < 0)
+ chip->single_dma = 1;
+ err = snd_opl3sa2_detect(card);
+ if (err < 0)
+ return err;
+ err = devm_request_irq(card->dev, xirq, snd_opl3sa2_interrupt, 0,
+ "OPL3-SA2", card);
+ if (err) {
+ snd_printk(KERN_ERR PFX "can't grab IRQ %d\n", xirq);
+ return -ENODEV;
+ }
+ chip->irq = xirq;
+ card->sync_irq = chip->irq;
+ err = snd_wss_create(card,
+ wss_port[dev] + 4, -1,
+ xirq, xdma1, xdma2,
+ WSS_HW_OPL3SA2, WSS_HWSHARE_IRQ, &wss);
+ if (err < 0) {
+ snd_printd("Oops, WSS not detected at 0x%lx\n", wss_port[dev] + 4);
+ return err;
+ }
+ chip->wss = wss;
+ err = snd_wss_pcm(wss, 0);
+ if (err < 0)
+ return err;
+ err = snd_wss_mixer(wss);
+ if (err < 0)
+ return err;
+ err = snd_opl3sa2_mixer(card);
+ if (err < 0)
+ return err;
+ err = snd_wss_timer(wss, 0);
+ if (err < 0)
+ return err;
+ if (fm_port[dev] >= 0x340 && fm_port[dev] < 0x400) {
+ err = snd_opl3_create(card, fm_port[dev],
+ fm_port[dev] + 2,
+ OPL3_HW_OPL3, 0, &opl3);
+ if (err < 0)
+ return err;
+ err = snd_opl3_timer_new(opl3, 1, 2);
+ if (err < 0)
+ return err;
+ err = snd_opl3_hwdep_new(opl3, 0, 1, &chip->synth);
+ if (err < 0)
+ return err;
+ }
+ if (midi_port[dev] >= 0x300 && midi_port[dev] < 0x340) {
+ err = snd_mpu401_uart_new(card, 0, MPU401_HW_OPL3SA2,
+ midi_port[dev],
+ MPU401_INFO_IRQ_HOOK, -1,
+ &chip->rmidi);
+ if (err < 0)
+ return err;
+ }
+ sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+ card->shortname, chip->port, xirq, xdma1);
+ if (xdma2 >= 0)
+ sprintf(card->longname + strlen(card->longname), "&%d", xdma2);
+
+ return snd_card_register(card);
+}
+
+#ifdef CONFIG_PNP
+static int snd_opl3sa2_pnp_detect(struct pnp_dev *pdev,
+ const struct pnp_device_id *id)
+{
+ static int dev;
+ int err;
+ struct snd_card *card;
+
+ if (pnp_device_is_isapnp(pdev))
+ return -ENOENT; /* we have another procedure - card */
+ for (; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ err = snd_opl3sa2_card_new(&pdev->dev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_opl3sa2_pnp(dev, card->private_data, pdev);
+ if (err < 0)
+ return err;
+ err = snd_opl3sa2_probe(card, dev);
+ if (err < 0)
+ return err;
+ pnp_set_drvdata(pdev, card);
+ dev++;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_opl3sa2_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+ return snd_opl3sa2_suspend(pnp_get_drvdata(pdev), state);
+}
+static int snd_opl3sa2_pnp_resume(struct pnp_dev *pdev)
+{
+ return snd_opl3sa2_resume(pnp_get_drvdata(pdev));
+}
+#endif
+
+static struct pnp_driver opl3sa2_pnp_driver = {
+ .name = "snd-opl3sa2-pnpbios",
+ .id_table = snd_opl3sa2_pnpbiosids,
+ .probe = snd_opl3sa2_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_opl3sa2_pnp_suspend,
+ .resume = snd_opl3sa2_pnp_resume,
+#endif
+};
+
+static int snd_opl3sa2_pnp_cdetect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *id)
+{
+ static int dev;
+ struct pnp_dev *pdev;
+ int err;
+ struct snd_card *card;
+
+ pdev = pnp_request_card_device(pcard, id->devs[0].id, NULL);
+ if (pdev == NULL) {
+ snd_printk(KERN_ERR PFX "can't get pnp device from id '%s'\n",
+ id->devs[0].id);
+ return -EBUSY;
+ }
+ for (; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ err = snd_opl3sa2_card_new(&pdev->dev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_opl3sa2_pnp(dev, card->private_data, pdev);
+ if (err < 0)
+ return err;
+ err = snd_opl3sa2_probe(card, dev);
+ if (err < 0)
+ return err;
+ pnp_set_card_drvdata(pcard, card);
+ dev++;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_opl3sa2_pnp_csuspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ return snd_opl3sa2_suspend(pnp_get_card_drvdata(pcard), state);
+}
+static int snd_opl3sa2_pnp_cresume(struct pnp_card_link *pcard)
+{
+ return snd_opl3sa2_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver opl3sa2_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = "snd-opl3sa2-cpnp",
+ .id_table = snd_opl3sa2_pnpids,
+ .probe = snd_opl3sa2_pnp_cdetect,
+#ifdef CONFIG_PM
+ .suspend = snd_opl3sa2_pnp_csuspend,
+ .resume = snd_opl3sa2_pnp_cresume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int snd_opl3sa2_isa_match(struct device *pdev,
+ unsigned int dev)
+{
+ if (!enable[dev])
+ return 0;
+#ifdef CONFIG_PNP
+ if (isapnp[dev])
+ return 0;
+#endif
+ if (port[dev] == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR PFX "specify port\n");
+ return 0;
+ }
+ if (wss_port[dev] == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR PFX "specify wss_port\n");
+ return 0;
+ }
+ if (fm_port[dev] == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR PFX "specify fm_port\n");
+ return 0;
+ }
+ if (midi_port[dev] == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR PFX "specify midi_port\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int snd_opl3sa2_isa_probe(struct device *pdev,
+ unsigned int dev)
+{
+ struct snd_card *card;
+ int err;
+
+ err = snd_opl3sa2_card_new(pdev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_opl3sa2_probe(card, dev);
+ if (err < 0)
+ return err;
+ dev_set_drvdata(pdev, card);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_opl3sa2_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_opl3sa2_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_opl3sa2_isa_resume(struct device *dev, unsigned int n)
+{
+ return snd_opl3sa2_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#define DEV_NAME "opl3sa2"
+
+static struct isa_driver snd_opl3sa2_isa_driver = {
+ .match = snd_opl3sa2_isa_match,
+ .probe = snd_opl3sa2_isa_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_opl3sa2_isa_suspend,
+ .resume = snd_opl3sa2_isa_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+static int __init alsa_card_opl3sa2_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&snd_opl3sa2_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+
+ err = pnp_register_driver(&opl3sa2_pnp_driver);
+ if (!err)
+ pnp_registered = 1;
+
+ err = pnp_register_card_driver(&opl3sa2_pnpc_driver);
+ if (!err)
+ pnpc_registered = 1;
+
+ if (isa_registered || pnp_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit alsa_card_opl3sa2_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnpc_registered)
+ pnp_unregister_card_driver(&opl3sa2_pnpc_driver);
+ if (pnp_registered)
+ pnp_unregister_driver(&opl3sa2_pnp_driver);
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&snd_opl3sa2_isa_driver);
+}
+
+module_init(alsa_card_opl3sa2_init)
+module_exit(alsa_card_opl3sa2_exit)
diff --git a/sound/isa/opti9xx/Makefile b/sound/isa/opti9xx/Makefile
new file mode 100644
index 0000000000..a9dcdeb502
--- /dev/null
+++ b/sound/isa/opti9xx/Makefile
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-opti92x-ad1848-objs := opti92x-ad1848.o
+snd-opti92x-cs4231-objs := opti92x-cs4231.o
+snd-opti93x-objs := opti93x.o
+snd-miro-objs := miro.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_OPTI92X_AD1848) += snd-opti92x-ad1848.o
+obj-$(CONFIG_SND_OPTI92X_CS4231) += snd-opti92x-cs4231.o
+obj-$(CONFIG_SND_OPTI93X) += snd-opti93x.o
+obj-$(CONFIG_SND_MIRO) += snd-miro.o
diff --git a/sound/isa/opti9xx/miro.c b/sound/isa/opti9xx/miro.c
new file mode 100644
index 0000000000..59792f2fad
--- /dev/null
+++ b/sound/isa/opti9xx/miro.c
@@ -0,0 +1,1624 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA soundcard driver for Miro miroSOUND PCM1 pro
+ * miroSOUND PCM12
+ * miroSOUND PCM20 Radio
+ *
+ * Copyright (C) 2004-2005 Martin Langer <martin-langer@gmx.de>
+ *
+ * Based on OSS ACI and ALSA OPTi9xx drivers
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl4.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#define SNDRV_LEGACY_FIND_FREE_IOPORT
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+#include <sound/aci.h>
+
+MODULE_AUTHOR("Martin Langer <martin-langer@gmx.de>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Miro miroSOUND PCM1 pro, PCM12, PCM20 Radio");
+
+static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */
+static long port = SNDRV_DEFAULT_PORT1; /* 0x530,0xe80,0xf40,0x604 */
+static long mpu_port = SNDRV_DEFAULT_PORT1; /* 0x300,0x310,0x320,0x330 */
+static long fm_port = SNDRV_DEFAULT_PORT1; /* 0x388 */
+static int irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10,11 */
+static int mpu_irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10 */
+static int dma1 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */
+static int dma2 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */
+static int wss;
+static int ide;
+#ifdef CONFIG_PNP
+static bool isapnp = 1; /* Enable ISA PnP detection */
+#endif
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for miro soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for miro soundcard.");
+module_param_hw(port, long, ioport, 0444);
+MODULE_PARM_DESC(port, "WSS port # for miro driver.");
+module_param_hw(mpu_port, long, ioport, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for miro driver.");
+module_param_hw(fm_port, long, ioport, 0444);
+MODULE_PARM_DESC(fm_port, "FM Port # for miro driver.");
+module_param_hw(irq, int, irq, 0444);
+MODULE_PARM_DESC(irq, "WSS irq # for miro driver.");
+module_param_hw(mpu_irq, int, irq, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for miro driver.");
+module_param_hw(dma1, int, dma, 0444);
+MODULE_PARM_DESC(dma1, "1st dma # for miro driver.");
+module_param_hw(dma2, int, dma, 0444);
+MODULE_PARM_DESC(dma2, "2nd dma # for miro driver.");
+module_param(wss, int, 0444);
+MODULE_PARM_DESC(wss, "wss mode");
+module_param(ide, int, 0444);
+MODULE_PARM_DESC(ide, "enable ide port");
+#ifdef CONFIG_PNP
+module_param(isapnp, bool, 0444);
+MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard.");
+#endif
+
+#define OPTi9XX_HW_DETECT 0
+#define OPTi9XX_HW_82C928 1
+#define OPTi9XX_HW_82C929 2
+#define OPTi9XX_HW_82C924 3
+#define OPTi9XX_HW_82C925 4
+#define OPTi9XX_HW_82C930 5
+#define OPTi9XX_HW_82C931 6
+#define OPTi9XX_HW_82C933 7
+#define OPTi9XX_HW_LAST OPTi9XX_HW_82C933
+
+#define OPTi9XX_MC_REG(n) n
+
+struct snd_miro {
+ unsigned short hardware;
+ unsigned char password;
+ char name[7];
+
+ struct resource *res_mc_base;
+ struct resource *res_aci_port;
+
+ unsigned long mc_base;
+ unsigned long mc_base_size;
+ unsigned long pwd_reg;
+
+ spinlock_t lock;
+ struct snd_pcm *pcm;
+
+ long wss_base;
+ int irq;
+ int dma1;
+ int dma2;
+
+ long mpu_port;
+ int mpu_irq;
+
+ struct snd_miro_aci *aci;
+};
+
+static struct snd_miro_aci aci_device;
+
+static const char * const snd_opti9xx_names[] = {
+ "unknown",
+ "82C928", "82C929",
+ "82C924", "82C925",
+ "82C930", "82C931", "82C933"
+};
+
+static int snd_miro_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_card_device_id snd_miro_pnpids[] = {
+ /* PCM20 and PCM12 in PnP mode */
+ { .id = "MIR0924",
+ .devs = { { "MIR0000" }, { "MIR0002" }, { "MIR0005" } }, },
+ { .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_miro_pnpids);
+
+#endif /* CONFIG_PNP */
+
+/*
+ * ACI control
+ */
+
+static int aci_busy_wait(struct snd_miro_aci *aci)
+{
+ long timeout;
+ unsigned char byte;
+
+ for (timeout = 1; timeout <= ACI_MINTIME + 30; timeout++) {
+ byte = inb(aci->aci_port + ACI_REG_BUSY);
+ if ((byte & 1) == 0) {
+ if (timeout >= ACI_MINTIME)
+ snd_printd("aci ready in round %ld.\n",
+ timeout-ACI_MINTIME);
+ return byte;
+ }
+ if (timeout >= ACI_MINTIME) {
+ long out=10*HZ;
+ switch (timeout-ACI_MINTIME) {
+ case 0 ... 9:
+ out /= 10;
+ fallthrough;
+ case 10 ... 19:
+ out /= 10;
+ fallthrough;
+ case 20 ... 30:
+ out /= 10;
+ fallthrough;
+ default:
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(out);
+ break;
+ }
+ }
+ }
+ snd_printk(KERN_ERR "aci_busy_wait() time out\n");
+ return -EBUSY;
+}
+
+static inline int aci_write(struct snd_miro_aci *aci, unsigned char byte)
+{
+ if (aci_busy_wait(aci) >= 0) {
+ outb(byte, aci->aci_port + ACI_REG_COMMAND);
+ return 0;
+ } else {
+ snd_printk(KERN_ERR "aci busy, aci_write(0x%x) stopped.\n", byte);
+ return -EBUSY;
+ }
+}
+
+static inline int aci_read(struct snd_miro_aci *aci)
+{
+ unsigned char byte;
+
+ if (aci_busy_wait(aci) >= 0) {
+ byte = inb(aci->aci_port + ACI_REG_STATUS);
+ return byte;
+ } else {
+ snd_printk(KERN_ERR "aci busy, aci_read() stopped.\n");
+ return -EBUSY;
+ }
+}
+
+int snd_aci_cmd(struct snd_miro_aci *aci, int write1, int write2, int write3)
+{
+ int write[] = {write1, write2, write3};
+ int value, i;
+
+ if (mutex_lock_interruptible(&aci->aci_mutex))
+ return -EINTR;
+
+ for (i=0; i<3; i++) {
+ if (write[i]< 0 || write[i] > 255)
+ break;
+ else {
+ value = aci_write(aci, write[i]);
+ if (value < 0)
+ goto out;
+ }
+ }
+
+ value = aci_read(aci);
+
+out: mutex_unlock(&aci->aci_mutex);
+ return value;
+}
+EXPORT_SYMBOL(snd_aci_cmd);
+
+static int aci_getvalue(struct snd_miro_aci *aci, unsigned char index)
+{
+ return snd_aci_cmd(aci, ACI_STATUS, index, -1);
+}
+
+static int aci_setvalue(struct snd_miro_aci *aci, unsigned char index,
+ int value)
+{
+ return snd_aci_cmd(aci, index, value, -1);
+}
+
+struct snd_miro_aci *snd_aci_get_aci(void)
+{
+ if (aci_device.aci_port == 0)
+ return NULL;
+ return &aci_device;
+}
+EXPORT_SYMBOL(snd_aci_get_aci);
+
+/*
+ * MIXER part
+ */
+
+#define snd_miro_info_capture snd_ctl_boolean_mono_info
+
+static int snd_miro_get_capture(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+ int value;
+
+ value = aci_getvalue(miro->aci, ACI_S_GENERAL);
+ if (value < 0) {
+ snd_printk(KERN_ERR "snd_miro_get_capture() failed: %d\n",
+ value);
+ return value;
+ }
+
+ ucontrol->value.integer.value[0] = value & 0x20;
+
+ return 0;
+}
+
+static int snd_miro_put_capture(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+ int change, value, error;
+
+ value = !(ucontrol->value.integer.value[0]);
+
+ error = aci_setvalue(miro->aci, ACI_SET_SOLOMODE, value);
+ if (error < 0) {
+ snd_printk(KERN_ERR "snd_miro_put_capture() failed: %d\n",
+ error);
+ return error;
+ }
+
+ change = (value != miro->aci->aci_solomode);
+ miro->aci->aci_solomode = value;
+
+ return change;
+}
+
+static int snd_miro_info_preamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 3;
+
+ return 0;
+}
+
+static int snd_miro_get_preamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+ int value;
+
+ if (miro->aci->aci_version <= 176) {
+
+ /*
+ OSS says it's not readable with versions < 176.
+ But it doesn't work on my card,
+ which is a PCM12 with aci_version = 176.
+ */
+
+ ucontrol->value.integer.value[0] = miro->aci->aci_preamp;
+ return 0;
+ }
+
+ value = aci_getvalue(miro->aci, ACI_GET_PREAMP);
+ if (value < 0) {
+ snd_printk(KERN_ERR "snd_miro_get_preamp() failed: %d\n",
+ value);
+ return value;
+ }
+
+ ucontrol->value.integer.value[0] = value;
+
+ return 0;
+}
+
+static int snd_miro_put_preamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+ int error, value, change;
+
+ value = ucontrol->value.integer.value[0];
+
+ error = aci_setvalue(miro->aci, ACI_SET_PREAMP, value);
+ if (error < 0) {
+ snd_printk(KERN_ERR "snd_miro_put_preamp() failed: %d\n",
+ error);
+ return error;
+ }
+
+ change = (value != miro->aci->aci_preamp);
+ miro->aci->aci_preamp = value;
+
+ return change;
+}
+
+#define snd_miro_info_amp snd_ctl_boolean_mono_info
+
+static int snd_miro_get_amp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = miro->aci->aci_amp;
+
+ return 0;
+}
+
+static int snd_miro_put_amp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+ int error, value, change;
+
+ value = ucontrol->value.integer.value[0];
+
+ error = aci_setvalue(miro->aci, ACI_SET_POWERAMP, value);
+ if (error < 0) {
+ snd_printk(KERN_ERR "snd_miro_put_amp() to %d failed: %d\n", value, error);
+ return error;
+ }
+
+ change = (value != miro->aci->aci_amp);
+ miro->aci->aci_amp = value;
+
+ return change;
+}
+
+#define MIRO_DOUBLE(ctl_name, ctl_index, get_right_reg, set_right_reg) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = ctl_name, \
+ .index = ctl_index, \
+ .info = snd_miro_info_double, \
+ .get = snd_miro_get_double, \
+ .put = snd_miro_put_double, \
+ .private_value = get_right_reg | (set_right_reg << 8) \
+}
+
+static int snd_miro_info_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int reg = kcontrol->private_value & 0xff;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+
+ if ((reg >= ACI_GET_EQ1) && (reg <= ACI_GET_EQ7)) {
+
+ /* equalizer elements */
+
+ uinfo->value.integer.min = - 0x7f;
+ uinfo->value.integer.max = 0x7f;
+ } else {
+
+ /* non-equalizer elements */
+
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0x20;
+ }
+
+ return 0;
+}
+
+static int snd_miro_get_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+ int left_val, right_val;
+
+ int right_reg = kcontrol->private_value & 0xff;
+ int left_reg = right_reg + 1;
+
+ right_val = aci_getvalue(miro->aci, right_reg);
+ if (right_val < 0) {
+ snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", right_reg, right_val);
+ return right_val;
+ }
+
+ left_val = aci_getvalue(miro->aci, left_reg);
+ if (left_val < 0) {
+ snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", left_reg, left_val);
+ return left_val;
+ }
+
+ if ((right_reg >= ACI_GET_EQ1) && (right_reg <= ACI_GET_EQ7)) {
+
+ /* equalizer elements */
+
+ if (left_val < 0x80) {
+ uinfo->value.integer.value[0] = left_val;
+ } else {
+ uinfo->value.integer.value[0] = 0x80 - left_val;
+ }
+
+ if (right_val < 0x80) {
+ uinfo->value.integer.value[1] = right_val;
+ } else {
+ uinfo->value.integer.value[1] = 0x80 - right_val;
+ }
+
+ } else {
+
+ /* non-equalizer elements */
+
+ uinfo->value.integer.value[0] = 0x20 - left_val;
+ uinfo->value.integer.value[1] = 0x20 - right_val;
+ }
+
+ return 0;
+}
+
+static int snd_miro_put_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+ struct snd_miro_aci *aci = miro->aci;
+ int left, right, left_old, right_old;
+ int setreg_left, setreg_right, getreg_left, getreg_right;
+ int change, error;
+
+ left = ucontrol->value.integer.value[0];
+ right = ucontrol->value.integer.value[1];
+
+ setreg_right = (kcontrol->private_value >> 8) & 0xff;
+ setreg_left = setreg_right + 8;
+ if (setreg_right == ACI_SET_MASTER)
+ setreg_left -= 7;
+
+ getreg_right = kcontrol->private_value & 0xff;
+ getreg_left = getreg_right + 1;
+
+ left_old = aci_getvalue(aci, getreg_left);
+ if (left_old < 0) {
+ snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_left, left_old);
+ return left_old;
+ }
+
+ right_old = aci_getvalue(aci, getreg_right);
+ if (right_old < 0) {
+ snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_right, right_old);
+ return right_old;
+ }
+
+ if ((getreg_right >= ACI_GET_EQ1) && (getreg_right <= ACI_GET_EQ7)) {
+
+ /* equalizer elements */
+
+ if (left < -0x7f || left > 0x7f ||
+ right < -0x7f || right > 0x7f)
+ return -EINVAL;
+
+ if (left_old > 0x80)
+ left_old = 0x80 - left_old;
+ if (right_old > 0x80)
+ right_old = 0x80 - right_old;
+
+ if (left >= 0) {
+ error = aci_setvalue(aci, setreg_left, left);
+ if (error < 0) {
+ snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+ left, error);
+ return error;
+ }
+ } else {
+ error = aci_setvalue(aci, setreg_left, 0x80 - left);
+ if (error < 0) {
+ snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+ 0x80 - left, error);
+ return error;
+ }
+ }
+
+ if (right >= 0) {
+ error = aci_setvalue(aci, setreg_right, right);
+ if (error < 0) {
+ snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+ right, error);
+ return error;
+ }
+ } else {
+ error = aci_setvalue(aci, setreg_right, 0x80 - right);
+ if (error < 0) {
+ snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+ 0x80 - right, error);
+ return error;
+ }
+ }
+
+ } else {
+
+ /* non-equalizer elements */
+
+ if (left < 0 || left > 0x20 ||
+ right < 0 || right > 0x20)
+ return -EINVAL;
+
+ left_old = 0x20 - left_old;
+ right_old = 0x20 - right_old;
+
+ error = aci_setvalue(aci, setreg_left, 0x20 - left);
+ if (error < 0) {
+ snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+ 0x20 - left, error);
+ return error;
+ }
+ error = aci_setvalue(aci, setreg_right, 0x20 - right);
+ if (error < 0) {
+ snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+ 0x20 - right, error);
+ return error;
+ }
+ }
+
+ change = (left != left_old) || (right != right_old);
+
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_miro_controls[] = {
+MIRO_DOUBLE("Master Playback Volume", 0, ACI_GET_MASTER, ACI_SET_MASTER),
+MIRO_DOUBLE("Mic Playback Volume", 1, ACI_GET_MIC, ACI_SET_MIC),
+MIRO_DOUBLE("Line Playback Volume", 1, ACI_GET_LINE, ACI_SET_LINE),
+MIRO_DOUBLE("CD Playback Volume", 0, ACI_GET_CD, ACI_SET_CD),
+MIRO_DOUBLE("Synth Playback Volume", 0, ACI_GET_SYNTH, ACI_SET_SYNTH),
+MIRO_DOUBLE("PCM Playback Volume", 1, ACI_GET_PCM, ACI_SET_PCM),
+MIRO_DOUBLE("Aux Playback Volume", 2, ACI_GET_LINE2, ACI_SET_LINE2),
+};
+
+/* Equalizer with seven bands (only PCM20)
+ from -12dB up to +12dB on each band */
+static const struct snd_kcontrol_new snd_miro_eq_controls[] = {
+MIRO_DOUBLE("Tone Control - 28 Hz", 0, ACI_GET_EQ1, ACI_SET_EQ1),
+MIRO_DOUBLE("Tone Control - 160 Hz", 0, ACI_GET_EQ2, ACI_SET_EQ2),
+MIRO_DOUBLE("Tone Control - 400 Hz", 0, ACI_GET_EQ3, ACI_SET_EQ3),
+MIRO_DOUBLE("Tone Control - 1 kHz", 0, ACI_GET_EQ4, ACI_SET_EQ4),
+MIRO_DOUBLE("Tone Control - 2.5 kHz", 0, ACI_GET_EQ5, ACI_SET_EQ5),
+MIRO_DOUBLE("Tone Control - 6.3 kHz", 0, ACI_GET_EQ6, ACI_SET_EQ6),
+MIRO_DOUBLE("Tone Control - 16 kHz", 0, ACI_GET_EQ7, ACI_SET_EQ7),
+};
+
+static const struct snd_kcontrol_new snd_miro_radio_control[] = {
+MIRO_DOUBLE("Radio Playback Volume", 0, ACI_GET_LINE1, ACI_SET_LINE1),
+};
+
+static const struct snd_kcontrol_new snd_miro_line_control[] = {
+MIRO_DOUBLE("Line Playback Volume", 2, ACI_GET_LINE1, ACI_SET_LINE1),
+};
+
+static const struct snd_kcontrol_new snd_miro_preamp_control[] = {
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic Boost",
+ .index = 1,
+ .info = snd_miro_info_preamp,
+ .get = snd_miro_get_preamp,
+ .put = snd_miro_put_preamp,
+}};
+
+static const struct snd_kcontrol_new snd_miro_amp_control[] = {
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line Boost",
+ .index = 0,
+ .info = snd_miro_info_amp,
+ .get = snd_miro_get_amp,
+ .put = snd_miro_put_amp,
+}};
+
+static const struct snd_kcontrol_new snd_miro_capture_control[] = {
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Switch",
+ .index = 0,
+ .info = snd_miro_info_capture,
+ .get = snd_miro_get_capture,
+ .put = snd_miro_put_capture,
+}};
+
+static const unsigned char aci_init_values[][2] = {
+ { ACI_SET_MUTE, 0x00 },
+ { ACI_SET_POWERAMP, 0x00 },
+ { ACI_SET_PREAMP, 0x00 },
+ { ACI_SET_SOLOMODE, 0x00 },
+ { ACI_SET_MIC + 0, 0x20 },
+ { ACI_SET_MIC + 8, 0x20 },
+ { ACI_SET_LINE + 0, 0x20 },
+ { ACI_SET_LINE + 8, 0x20 },
+ { ACI_SET_CD + 0, 0x20 },
+ { ACI_SET_CD + 8, 0x20 },
+ { ACI_SET_PCM + 0, 0x20 },
+ { ACI_SET_PCM + 8, 0x20 },
+ { ACI_SET_LINE1 + 0, 0x20 },
+ { ACI_SET_LINE1 + 8, 0x20 },
+ { ACI_SET_LINE2 + 0, 0x20 },
+ { ACI_SET_LINE2 + 8, 0x20 },
+ { ACI_SET_SYNTH + 0, 0x20 },
+ { ACI_SET_SYNTH + 8, 0x20 },
+ { ACI_SET_MASTER + 0, 0x20 },
+ { ACI_SET_MASTER + 1, 0x20 },
+};
+
+static int snd_set_aci_init_values(struct snd_miro *miro)
+{
+ int idx, error;
+ struct snd_miro_aci *aci = miro->aci;
+
+ /* enable WSS on PCM1 */
+
+ if ((aci->aci_product == 'A') && wss) {
+ error = aci_setvalue(aci, ACI_SET_WSS, wss);
+ if (error < 0) {
+ snd_printk(KERN_ERR "enabling WSS mode failed\n");
+ return error;
+ }
+ }
+
+ /* enable IDE port */
+
+ if (ide) {
+ error = aci_setvalue(aci, ACI_SET_IDE, ide);
+ if (error < 0) {
+ snd_printk(KERN_ERR "enabling IDE port failed\n");
+ return error;
+ }
+ }
+
+ /* set common aci values */
+
+ for (idx = 0; idx < ARRAY_SIZE(aci_init_values); idx++) {
+ error = aci_setvalue(aci, aci_init_values[idx][0],
+ aci_init_values[idx][1]);
+ if (error < 0) {
+ snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+ aci_init_values[idx][0], error);
+ return error;
+ }
+ }
+ aci->aci_amp = 0;
+ aci->aci_preamp = 0;
+ aci->aci_solomode = 1;
+
+ return 0;
+}
+
+static int snd_miro_mixer(struct snd_card *card,
+ struct snd_miro *miro)
+{
+ unsigned int idx;
+ int err;
+
+ if (snd_BUG_ON(!miro || !card))
+ return -EINVAL;
+
+ switch (miro->hardware) {
+ case OPTi9XX_HW_82C924:
+ strcpy(card->mixername, "ACI & OPTi924");
+ break;
+ case OPTi9XX_HW_82C929:
+ strcpy(card->mixername, "ACI & OPTi929");
+ break;
+ default:
+ snd_BUG();
+ break;
+ }
+
+ for (idx = 0; idx < ARRAY_SIZE(snd_miro_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_controls[idx], miro));
+ if (err < 0)
+ return err;
+ }
+
+ if ((miro->aci->aci_product == 'A') ||
+ (miro->aci->aci_product == 'B')) {
+ /* PCM1/PCM12 with power-amp and Line 2 */
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_line_control[0], miro));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_amp_control[0], miro));
+ if (err < 0)
+ return err;
+ }
+
+ if ((miro->aci->aci_product == 'B') ||
+ (miro->aci->aci_product == 'C')) {
+ /* PCM12/PCM20 with mic-preamp */
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_preamp_control[0], miro));
+ if (err < 0)
+ return err;
+ if (miro->aci->aci_version >= 176) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_capture_control[0], miro));
+ if (err < 0)
+ return err;
+ }
+ }
+
+ if (miro->aci->aci_product == 'C') {
+ /* PCM20 with radio and 7 band equalizer */
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_radio_control[0], miro));
+ if (err < 0)
+ return err;
+ for (idx = 0; idx < ARRAY_SIZE(snd_miro_eq_controls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_eq_controls[idx], miro));
+ if (err < 0)
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int snd_miro_init(struct snd_miro *chip,
+ unsigned short hardware)
+{
+ static const int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2};
+
+ chip->hardware = hardware;
+ strcpy(chip->name, snd_opti9xx_names[hardware]);
+
+ chip->mc_base_size = opti9xx_mc_size[hardware];
+
+ spin_lock_init(&chip->lock);
+
+ chip->wss_base = -1;
+ chip->irq = -1;
+ chip->dma1 = -1;
+ chip->dma2 = -1;
+ chip->mpu_port = -1;
+ chip->mpu_irq = -1;
+
+ chip->pwd_reg = 3;
+
+#ifdef CONFIG_PNP
+ if (isapnp && chip->mc_base)
+ /* PnP resource gives the least 10 bits */
+ chip->mc_base |= 0xc00;
+ else
+#endif
+ chip->mc_base = 0xf8c;
+
+ switch (hardware) {
+ case OPTi9XX_HW_82C929:
+ chip->password = 0xe3;
+ break;
+
+ case OPTi9XX_HW_82C924:
+ chip->password = 0xe5;
+ break;
+
+ default:
+ snd_printk(KERN_ERR "sorry, no support for %d\n", hardware);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static unsigned char snd_miro_read(struct snd_miro *chip,
+ unsigned char reg)
+{
+ unsigned long flags;
+ unsigned char retval = 0xff;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+ switch (chip->hardware) {
+ case OPTi9XX_HW_82C924:
+ if (reg > 7) {
+ outb(reg, chip->mc_base + 8);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+ retval = inb(chip->mc_base + 9);
+ break;
+ }
+ fallthrough;
+
+ case OPTi9XX_HW_82C929:
+ retval = inb(chip->mc_base + reg);
+ break;
+
+ default:
+ snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware);
+ }
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return retval;
+}
+
+static void snd_miro_write(struct snd_miro *chip, unsigned char reg,
+ unsigned char value)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+ switch (chip->hardware) {
+ case OPTi9XX_HW_82C924:
+ if (reg > 7) {
+ outb(reg, chip->mc_base + 8);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+ outb(value, chip->mc_base + 9);
+ break;
+ }
+ fallthrough;
+
+ case OPTi9XX_HW_82C929:
+ outb(value, chip->mc_base + reg);
+ break;
+
+ default:
+ snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware);
+ }
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static inline void snd_miro_write_mask(struct snd_miro *chip,
+ unsigned char reg, unsigned char value, unsigned char mask)
+{
+ unsigned char oldval = snd_miro_read(chip, reg);
+
+ snd_miro_write(chip, reg, (oldval & ~mask) | (value & mask));
+}
+
+/*
+ * Proc Interface
+ */
+
+static void snd_miro_proc_read(struct snd_info_entry * entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_miro *miro = (struct snd_miro *) entry->private_data;
+ struct snd_miro_aci *aci = miro->aci;
+ char* model = "unknown";
+
+ /* miroSOUND PCM1 pro, early PCM12 */
+
+ if ((miro->hardware == OPTi9XX_HW_82C929) &&
+ (aci->aci_vendor == 'm') &&
+ (aci->aci_product == 'A')) {
+ switch (aci->aci_version) {
+ case 3:
+ model = "miroSOUND PCM1 pro";
+ break;
+ default:
+ model = "miroSOUND PCM1 pro / (early) PCM12";
+ break;
+ }
+ }
+
+ /* miroSOUND PCM12, PCM12 (Rev. E), PCM12 pnp */
+
+ if ((miro->hardware == OPTi9XX_HW_82C924) &&
+ (aci->aci_vendor == 'm') &&
+ (aci->aci_product == 'B')) {
+ switch (aci->aci_version) {
+ case 4:
+ model = "miroSOUND PCM12";
+ break;
+ case 176:
+ model = "miroSOUND PCM12 (Rev. E)";
+ break;
+ default:
+ model = "miroSOUND PCM12 / PCM12 pnp";
+ break;
+ }
+ }
+
+ /* miroSOUND PCM20 radio */
+
+ if ((miro->hardware == OPTi9XX_HW_82C924) &&
+ (aci->aci_vendor == 'm') &&
+ (aci->aci_product == 'C')) {
+ switch (aci->aci_version) {
+ case 7:
+ model = "miroSOUND PCM20 radio (Rev. E)";
+ break;
+ default:
+ model = "miroSOUND PCM20 radio";
+ break;
+ }
+ }
+
+ snd_iprintf(buffer, "\nGeneral information:\n");
+ snd_iprintf(buffer, " model : %s\n", model);
+ snd_iprintf(buffer, " opti : %s\n", miro->name);
+ snd_iprintf(buffer, " codec : %s\n", miro->pcm->name);
+ snd_iprintf(buffer, " port : 0x%lx\n", miro->wss_base);
+ snd_iprintf(buffer, " irq : %d\n", miro->irq);
+ snd_iprintf(buffer, " dma : %d,%d\n\n", miro->dma1, miro->dma2);
+
+ snd_iprintf(buffer, "MPU-401:\n");
+ snd_iprintf(buffer, " port : 0x%lx\n", miro->mpu_port);
+ snd_iprintf(buffer, " irq : %d\n\n", miro->mpu_irq);
+
+ snd_iprintf(buffer, "ACI information:\n");
+ snd_iprintf(buffer, " vendor : ");
+ switch (aci->aci_vendor) {
+ case 'm':
+ snd_iprintf(buffer, "Miro\n");
+ break;
+ default:
+ snd_iprintf(buffer, "unknown (0x%x)\n", aci->aci_vendor);
+ break;
+ }
+
+ snd_iprintf(buffer, " product : ");
+ switch (aci->aci_product) {
+ case 'A':
+ snd_iprintf(buffer, "miroSOUND PCM1 pro / (early) PCM12\n");
+ break;
+ case 'B':
+ snd_iprintf(buffer, "miroSOUND PCM12\n");
+ break;
+ case 'C':
+ snd_iprintf(buffer, "miroSOUND PCM20 radio\n");
+ break;
+ default:
+ snd_iprintf(buffer, "unknown (0x%x)\n", aci->aci_product);
+ break;
+ }
+
+ snd_iprintf(buffer, " firmware: %d (0x%x)\n",
+ aci->aci_version, aci->aci_version);
+ snd_iprintf(buffer, " port : 0x%lx-0x%lx\n",
+ aci->aci_port, aci->aci_port+2);
+ snd_iprintf(buffer, " wss : 0x%x\n", wss);
+ snd_iprintf(buffer, " ide : 0x%x\n", ide);
+ snd_iprintf(buffer, " solomode: 0x%x\n", aci->aci_solomode);
+ snd_iprintf(buffer, " amp : 0x%x\n", aci->aci_amp);
+ snd_iprintf(buffer, " preamp : 0x%x\n", aci->aci_preamp);
+}
+
+static void snd_miro_proc_init(struct snd_card *card,
+ struct snd_miro *miro)
+{
+ snd_card_ro_proc_new(card, "miro", miro, snd_miro_proc_read);
+}
+
+/*
+ * Init
+ */
+
+static int snd_miro_configure(struct snd_miro *chip)
+{
+ unsigned char wss_base_bits;
+ unsigned char irq_bits;
+ unsigned char dma_bits;
+ unsigned char mpu_port_bits = 0;
+ unsigned char mpu_irq_bits;
+ unsigned long flags;
+
+ snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+ snd_miro_write_mask(chip, OPTi9XX_MC_REG(2), 0x20, 0x20); /* OPL4 */
+ snd_miro_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+
+ switch (chip->hardware) {
+ case OPTi9XX_HW_82C924:
+ snd_miro_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02);
+ snd_miro_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff);
+ break;
+ case OPTi9XX_HW_82C929:
+ /* untested init commands for OPTi929 */
+ snd_miro_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c);
+ break;
+ default:
+ snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware);
+ return -EINVAL;
+ }
+
+ /* PnP resource says it decodes only 10 bits of address */
+ switch (chip->wss_base & 0x3ff) {
+ case 0x130:
+ chip->wss_base = 0x530;
+ wss_base_bits = 0x00;
+ break;
+ case 0x204:
+ chip->wss_base = 0x604;
+ wss_base_bits = 0x03;
+ break;
+ case 0x280:
+ chip->wss_base = 0xe80;
+ wss_base_bits = 0x01;
+ break;
+ case 0x340:
+ chip->wss_base = 0xf40;
+ wss_base_bits = 0x02;
+ break;
+ default:
+ snd_printk(KERN_ERR "WSS port 0x%lx not valid\n", chip->wss_base);
+ goto __skip_base;
+ }
+ snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30);
+
+__skip_base:
+ switch (chip->irq) {
+ case 5:
+ irq_bits = 0x05;
+ break;
+ case 7:
+ irq_bits = 0x01;
+ break;
+ case 9:
+ irq_bits = 0x02;
+ break;
+ case 10:
+ irq_bits = 0x03;
+ break;
+ case 11:
+ irq_bits = 0x04;
+ break;
+ default:
+ snd_printk(KERN_ERR "WSS irq # %d not valid\n", chip->irq);
+ goto __skip_resources;
+ }
+
+ switch (chip->dma1) {
+ case 0:
+ dma_bits = 0x01;
+ break;
+ case 1:
+ dma_bits = 0x02;
+ break;
+ case 3:
+ dma_bits = 0x03;
+ break;
+ default:
+ snd_printk(KERN_ERR "WSS dma1 # %d not valid\n", chip->dma1);
+ goto __skip_resources;
+ }
+
+ if (chip->dma1 == chip->dma2) {
+ snd_printk(KERN_ERR "don't want to share dmas\n");
+ return -EBUSY;
+ }
+
+ switch (chip->dma2) {
+ case 0:
+ case 1:
+ break;
+ default:
+ snd_printk(KERN_ERR "WSS dma2 # %d not valid\n", chip->dma2);
+ goto __skip_resources;
+ }
+ dma_bits |= 0x04;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ outb(irq_bits << 3 | dma_bits, chip->wss_base);
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+__skip_resources:
+ if (chip->hardware > OPTi9XX_HW_82C928) {
+ switch (chip->mpu_port) {
+ case 0:
+ case -1:
+ break;
+ case 0x300:
+ mpu_port_bits = 0x03;
+ break;
+ case 0x310:
+ mpu_port_bits = 0x02;
+ break;
+ case 0x320:
+ mpu_port_bits = 0x01;
+ break;
+ case 0x330:
+ mpu_port_bits = 0x00;
+ break;
+ default:
+ snd_printk(KERN_ERR "MPU-401 port 0x%lx not valid\n",
+ chip->mpu_port);
+ goto __skip_mpu;
+ }
+
+ switch (chip->mpu_irq) {
+ case 5:
+ mpu_irq_bits = 0x02;
+ break;
+ case 7:
+ mpu_irq_bits = 0x03;
+ break;
+ case 9:
+ mpu_irq_bits = 0x00;
+ break;
+ case 10:
+ mpu_irq_bits = 0x01;
+ break;
+ default:
+ snd_printk(KERN_ERR "MPU-401 irq # %d not valid\n",
+ chip->mpu_irq);
+ goto __skip_mpu;
+ }
+
+ snd_miro_write_mask(chip, OPTi9XX_MC_REG(6),
+ (chip->mpu_port <= 0) ? 0x00 :
+ 0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3,
+ 0xf8);
+ }
+__skip_mpu:
+
+ return 0;
+}
+
+static int snd_miro_opti_check(struct snd_card *card, struct snd_miro *chip)
+{
+ unsigned char value;
+
+ chip->res_mc_base =
+ devm_request_region(card->dev, chip->mc_base,
+ chip->mc_base_size, "OPTi9xx MC");
+ if (chip->res_mc_base == NULL)
+ return -ENOMEM;
+
+ value = snd_miro_read(chip, OPTi9XX_MC_REG(1));
+ if (value != 0xff && value != inb(chip->mc_base + OPTi9XX_MC_REG(1)))
+ if (value == snd_miro_read(chip, OPTi9XX_MC_REG(1)))
+ return 0;
+
+ devm_release_resource(card->dev, chip->res_mc_base);
+ chip->res_mc_base = NULL;
+
+ return -ENODEV;
+}
+
+static int snd_card_miro_detect(struct snd_card *card,
+ struct snd_miro *chip)
+{
+ int i, err;
+
+ for (i = OPTi9XX_HW_82C929; i <= OPTi9XX_HW_82C924; i++) {
+
+ err = snd_miro_init(chip, i);
+ if (err < 0)
+ return err;
+
+ err = snd_miro_opti_check(card, chip);
+ if (err == 0)
+ return 1;
+ }
+
+ return -ENODEV;
+}
+
+static int snd_card_miro_aci_detect(struct snd_card *card,
+ struct snd_miro *miro)
+{
+ unsigned char regval;
+ int i;
+ struct snd_miro_aci *aci = &aci_device;
+
+ miro->aci = aci;
+
+ mutex_init(&aci->aci_mutex);
+
+ /* get ACI port from OPTi9xx MC 4 */
+
+ regval=inb(miro->mc_base + 4);
+ aci->aci_port = (regval & 0x10) ? 0x344 : 0x354;
+
+ miro->res_aci_port =
+ devm_request_region(card->dev, aci->aci_port, 3, "miro aci");
+ if (miro->res_aci_port == NULL) {
+ snd_printk(KERN_ERR "aci i/o area 0x%lx-0x%lx already used.\n",
+ aci->aci_port, aci->aci_port+2);
+ return -ENOMEM;
+ }
+
+ /* force ACI into a known state */
+ for (i = 0; i < 3; i++)
+ if (snd_aci_cmd(aci, ACI_ERROR_OP, -1, -1) < 0) {
+ snd_printk(KERN_ERR "can't force aci into known state.\n");
+ return -ENXIO;
+ }
+
+ aci->aci_vendor = snd_aci_cmd(aci, ACI_READ_IDCODE, -1, -1);
+ aci->aci_product = snd_aci_cmd(aci, ACI_READ_IDCODE, -1, -1);
+ if (aci->aci_vendor < 0 || aci->aci_product < 0) {
+ snd_printk(KERN_ERR "can't read aci id on 0x%lx.\n",
+ aci->aci_port);
+ return -ENXIO;
+ }
+
+ aci->aci_version = snd_aci_cmd(aci, ACI_READ_VERSION, -1, -1);
+ if (aci->aci_version < 0) {
+ snd_printk(KERN_ERR "can't read aci version on 0x%lx.\n",
+ aci->aci_port);
+ return -ENXIO;
+ }
+
+ if (snd_aci_cmd(aci, ACI_INIT, -1, -1) < 0 ||
+ snd_aci_cmd(aci, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0 ||
+ snd_aci_cmd(aci, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0) {
+ snd_printk(KERN_ERR "can't initialize aci.\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int snd_miro_probe(struct snd_card *card)
+{
+ int error;
+ struct snd_miro *miro = card->private_data;
+ struct snd_wss *codec;
+ struct snd_rawmidi *rmidi;
+
+ if (!miro->res_mc_base) {
+ miro->res_mc_base = devm_request_region(card->dev,
+ miro->mc_base,
+ miro->mc_base_size,
+ "miro (OPTi9xx MC)");
+ if (miro->res_mc_base == NULL) {
+ snd_printk(KERN_ERR "request for OPTI9xx MC failed\n");
+ return -ENOMEM;
+ }
+ }
+
+ error = snd_card_miro_aci_detect(card, miro);
+ if (error < 0) {
+ snd_printk(KERN_ERR "unable to detect aci chip\n");
+ return -ENODEV;
+ }
+
+ miro->wss_base = port;
+ miro->mpu_port = mpu_port;
+ miro->irq = irq;
+ miro->mpu_irq = mpu_irq;
+ miro->dma1 = dma1;
+ miro->dma2 = dma2;
+
+ /* init proc interface */
+ snd_miro_proc_init(card, miro);
+
+ error = snd_miro_configure(miro);
+ if (error)
+ return error;
+
+ error = snd_wss_create(card, miro->wss_base + 4, -1,
+ miro->irq, miro->dma1, miro->dma2,
+ WSS_HW_DETECT, 0, &codec);
+ if (error < 0)
+ return error;
+
+ error = snd_wss_pcm(codec, 0);
+ if (error < 0)
+ return error;
+
+ error = snd_wss_mixer(codec);
+ if (error < 0)
+ return error;
+
+ error = snd_wss_timer(codec, 0);
+ if (error < 0)
+ return error;
+
+ miro->pcm = codec->pcm;
+
+ error = snd_miro_mixer(card, miro);
+ if (error < 0)
+ return error;
+
+ if (miro->aci->aci_vendor == 'm') {
+ /* It looks like a miro sound card. */
+ switch (miro->aci->aci_product) {
+ case 'A':
+ sprintf(card->shortname,
+ "miroSOUND PCM1 pro / PCM12");
+ break;
+ case 'B':
+ sprintf(card->shortname,
+ "miroSOUND PCM12");
+ break;
+ case 'C':
+ sprintf(card->shortname,
+ "miroSOUND PCM20 radio");
+ break;
+ default:
+ sprintf(card->shortname,
+ "unknown miro");
+ snd_printk(KERN_INFO "unknown miro aci id\n");
+ break;
+ }
+ } else {
+ snd_printk(KERN_INFO "found unsupported aci card\n");
+ sprintf(card->shortname, "unknown Cardinal Technologies");
+ }
+
+ strcpy(card->driver, "miro");
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s: OPTi%s, %s at 0x%lx, irq %d, dma %d&%d",
+ card->shortname, miro->name, codec->pcm->name,
+ miro->wss_base + 4, miro->irq, miro->dma1, miro->dma2);
+
+ if (mpu_port <= 0 || mpu_port == SNDRV_AUTO_PORT)
+ rmidi = NULL;
+ else {
+ error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+ mpu_port, 0, miro->mpu_irq, &rmidi);
+ if (error < 0)
+ snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n",
+ mpu_port);
+ }
+
+ if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) {
+ struct snd_opl3 *opl3 = NULL;
+ struct snd_opl4 *opl4;
+
+ if (snd_opl4_create(card, fm_port, fm_port - 8,
+ 2, &opl3, &opl4) < 0)
+ snd_printk(KERN_WARNING "no OPL4 device at 0x%lx\n",
+ fm_port);
+ }
+
+ error = snd_set_aci_init_values(miro);
+ if (error < 0)
+ return error;
+
+ return snd_card_register(card);
+}
+
+static int snd_miro_isa_match(struct device *devptr, unsigned int n)
+{
+#ifdef CONFIG_PNP
+ if (snd_miro_pnp_is_probed)
+ return 0;
+ if (isapnp)
+ return 0;
+#endif
+ return 1;
+}
+
+static int snd_miro_isa_probe(struct device *devptr, unsigned int n)
+{
+ static const long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1};
+ static const long possible_mpu_ports[] = {0x330, 0x300, 0x310, 0x320, -1};
+ static const int possible_irqs[] = {11, 9, 10, 7, -1};
+ static const int possible_mpu_irqs[] = {10, 5, 9, 7, -1};
+ static const int possible_dma1s[] = {3, 1, 0, -1};
+ static const int possible_dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1},
+ {0, -1} };
+
+ int error;
+ struct snd_miro *miro;
+ struct snd_card *card;
+
+ error = snd_devm_card_new(devptr, index, id, THIS_MODULE,
+ sizeof(struct snd_miro), &card);
+ if (error < 0)
+ return error;
+
+ miro = card->private_data;
+
+ error = snd_card_miro_detect(card, miro);
+ if (error < 0) {
+ snd_printk(KERN_ERR "unable to detect OPTi9xx chip\n");
+ return -ENODEV;
+ }
+
+ if (port == SNDRV_AUTO_PORT) {
+ port = snd_legacy_find_free_ioport(possible_ports, 4);
+ if (port < 0) {
+ snd_printk(KERN_ERR "unable to find a free WSS port\n");
+ return -EBUSY;
+ }
+ }
+
+ if (mpu_port == SNDRV_AUTO_PORT) {
+ mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2);
+ if (mpu_port < 0) {
+ snd_printk(KERN_ERR
+ "unable to find a free MPU401 port\n");
+ return -EBUSY;
+ }
+ }
+
+ if (irq == SNDRV_AUTO_IRQ) {
+ irq = snd_legacy_find_free_irq(possible_irqs);
+ if (irq < 0) {
+ snd_printk(KERN_ERR "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (mpu_irq == SNDRV_AUTO_IRQ) {
+ mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs);
+ if (mpu_irq < 0) {
+ snd_printk(KERN_ERR
+ "unable to find a free MPU401 IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (dma1 == SNDRV_AUTO_DMA) {
+ dma1 = snd_legacy_find_free_dma(possible_dma1s);
+ if (dma1 < 0) {
+ snd_printk(KERN_ERR "unable to find a free DMA1\n");
+ return -EBUSY;
+ }
+ }
+ if (dma2 == SNDRV_AUTO_DMA) {
+ dma2 = snd_legacy_find_free_dma(possible_dma2s[dma1 % 4]);
+ if (dma2 < 0) {
+ snd_printk(KERN_ERR "unable to find a free DMA2\n");
+ return -EBUSY;
+ }
+ }
+
+ error = snd_miro_probe(card);
+ if (error < 0)
+ return error;
+
+ dev_set_drvdata(devptr, card);
+ return 0;
+}
+
+#define DEV_NAME "miro"
+
+static struct isa_driver snd_miro_driver = {
+ .match = snd_miro_isa_match,
+ .probe = snd_miro_isa_probe,
+ /* FIXME: suspend/resume */
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+#ifdef CONFIG_PNP
+
+static int snd_card_miro_pnp(struct snd_miro *chip,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *pid)
+{
+ struct pnp_dev *pdev;
+ int err;
+ struct pnp_dev *devmpu;
+ struct pnp_dev *devmc;
+
+ pdev = pnp_request_card_device(card, pid->devs[0].id, NULL);
+ if (pdev == NULL)
+ return -EBUSY;
+
+ devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL);
+ if (devmpu == NULL)
+ return -EBUSY;
+
+ devmc = pnp_request_card_device(card, pid->devs[2].id, NULL);
+ if (devmc == NULL)
+ return -EBUSY;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err);
+ return err;
+ }
+
+ err = pnp_activate_dev(devmc);
+ if (err < 0) {
+ snd_printk(KERN_ERR "MC pnp configure failure: %d\n",
+ err);
+ return err;
+ }
+
+ port = pnp_port_start(pdev, 1);
+ fm_port = pnp_port_start(pdev, 2) + 8;
+
+ /*
+ * The MC(0) is never accessed and the miroSOUND PCM20 card does not
+ * include it in the PnP resource range. OPTI93x include it.
+ */
+ chip->mc_base = pnp_port_start(devmc, 0) - 1;
+ chip->mc_base_size = pnp_port_len(devmc, 0) + 1;
+
+ irq = pnp_irq(pdev, 0);
+ dma1 = pnp_dma(pdev, 0);
+ dma2 = pnp_dma(pdev, 1);
+
+ if (mpu_port > 0) {
+ err = pnp_activate_dev(devmpu);
+ if (err < 0) {
+ snd_printk(KERN_ERR "MPU401 pnp configure failure\n");
+ mpu_port = -1;
+ return err;
+ }
+ mpu_port = pnp_port_start(devmpu, 0);
+ mpu_irq = pnp_irq(devmpu, 0);
+ }
+ return 0;
+}
+
+static int snd_miro_pnp_probe(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ struct snd_card *card;
+ int err;
+ struct snd_miro *miro;
+
+ if (snd_miro_pnp_is_probed)
+ return -EBUSY;
+ if (!isapnp)
+ return -ENODEV;
+ err = snd_devm_card_new(&pcard->card->dev, index, id, THIS_MODULE,
+ sizeof(struct snd_miro), &card);
+ if (err < 0)
+ return err;
+
+ miro = card->private_data;
+
+ err = snd_card_miro_pnp(miro, pcard, pid);
+ if (err)
+ return err;
+
+ /* only miroSOUND PCM20 and PCM12 == OPTi924 */
+ err = snd_miro_init(miro, OPTi9XX_HW_82C924);
+ if (err)
+ return err;
+
+ err = snd_miro_opti_check(card, miro);
+ if (err) {
+ snd_printk(KERN_ERR "OPTI chip not found\n");
+ return err;
+ }
+
+ err = snd_miro_probe(card);
+ if (err < 0)
+ return err;
+ pnp_set_card_drvdata(pcard, card);
+ snd_miro_pnp_is_probed = 1;
+ return 0;
+}
+
+static void snd_miro_pnp_remove(struct pnp_card_link *pcard)
+{
+ snd_miro_pnp_is_probed = 0;
+}
+
+static struct pnp_card_driver miro_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = "miro",
+ .id_table = snd_miro_pnpids,
+ .probe = snd_miro_pnp_probe,
+ .remove = snd_miro_pnp_remove,
+};
+#endif
+
+static int __init alsa_card_miro_init(void)
+{
+#ifdef CONFIG_PNP
+ pnp_register_card_driver(&miro_pnpc_driver);
+ if (snd_miro_pnp_is_probed)
+ return 0;
+ pnp_unregister_card_driver(&miro_pnpc_driver);
+#endif
+ return isa_register_driver(&snd_miro_driver, 1);
+}
+
+static void __exit alsa_card_miro_exit(void)
+{
+ if (!snd_miro_pnp_is_probed) {
+ isa_unregister_driver(&snd_miro_driver);
+ return;
+ }
+#ifdef CONFIG_PNP
+ pnp_unregister_card_driver(&miro_pnpc_driver);
+#endif
+}
+
+module_init(alsa_card_miro_init)
+module_exit(alsa_card_miro_exit)
diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c
new file mode 100644
index 0000000000..c33f67dd51
--- /dev/null
+++ b/sound/isa/opti9xx/opti92x-ad1848.c
@@ -0,0 +1,1171 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ card-opti92x-ad1848.c - driver for OPTi 82c92x based soundcards.
+ Copyright (C) 1998-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+ Part of this code was developed at the Italian Ministry of Air Defence,
+ Sixth Division (oh, che pace ...), Rome.
+
+ Thanks to Maria Grazia Pollarini, Salvatore Vassallo.
+
+*/
+
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#ifndef OPTi93X
+#include <sound/opl4.h>
+#endif
+#define SNDRV_LEGACY_FIND_FREE_IOPORT
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_LICENSE("GPL");
+#ifdef OPTi93X
+MODULE_DESCRIPTION("OPTi93X");
+#else /* OPTi93X */
+#ifdef CS4231
+MODULE_DESCRIPTION("OPTi92X - CS4231");
+#else /* CS4231 */
+MODULE_DESCRIPTION("OPTi92X - AD1848");
+#endif /* CS4231 */
+#endif /* OPTi93X */
+
+static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */
+//static bool enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp = true; /* Enable ISA PnP detection */
+#endif
+static long port = SNDRV_DEFAULT_PORT1; /* 0x530,0xe80,0xf40,0x604 */
+static long mpu_port = SNDRV_DEFAULT_PORT1; /* 0x300,0x310,0x320,0x330 */
+static long fm_port = SNDRV_DEFAULT_PORT1; /* 0x388 */
+static int irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10,11 */
+static int mpu_irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10 */
+static int dma1 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */
+#if defined(CS4231) || defined(OPTi93X)
+static int dma2 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */
+#endif /* CS4231 || OPTi93X */
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for opti9xx based soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for opti9xx based soundcard.");
+//module_param(enable, bool, 0444);
+//MODULE_PARM_DESC(enable, "Enable opti9xx soundcard.");
+#ifdef CONFIG_PNP
+module_param(isapnp, bool, 0444);
+MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard.");
+#endif
+module_param_hw(port, long, ioport, 0444);
+MODULE_PARM_DESC(port, "WSS port # for opti9xx driver.");
+module_param_hw(mpu_port, long, ioport, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for opti9xx driver.");
+module_param_hw(fm_port, long, ioport, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for opti9xx driver.");
+module_param_hw(irq, int, irq, 0444);
+MODULE_PARM_DESC(irq, "WSS irq # for opti9xx driver.");
+module_param_hw(mpu_irq, int, irq, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for opti9xx driver.");
+module_param_hw(dma1, int, dma, 0444);
+MODULE_PARM_DESC(dma1, "1st dma # for opti9xx driver.");
+#if defined(CS4231) || defined(OPTi93X)
+module_param_hw(dma2, int, dma, 0444);
+MODULE_PARM_DESC(dma2, "2nd dma # for opti9xx driver.");
+#endif /* CS4231 || OPTi93X */
+
+#define OPTi9XX_HW_82C928 1
+#define OPTi9XX_HW_82C929 2
+#define OPTi9XX_HW_82C924 3
+#define OPTi9XX_HW_82C925 4
+#define OPTi9XX_HW_82C930 5
+#define OPTi9XX_HW_82C931 6
+#define OPTi9XX_HW_82C933 7
+#define OPTi9XX_HW_LAST OPTi9XX_HW_82C933
+
+#define OPTi9XX_MC_REG(n) n
+
+#ifdef OPTi93X
+
+#define OPTi93X_STATUS 0x02
+#define OPTi93X_PORT(chip, r) ((chip)->port + OPTi93X_##r)
+
+#define OPTi93X_IRQ_PLAYBACK 0x04
+#define OPTi93X_IRQ_CAPTURE 0x08
+
+#endif /* OPTi93X */
+
+struct snd_opti9xx {
+ unsigned short hardware;
+ unsigned char password;
+ char name[7];
+
+ unsigned long mc_base;
+ struct resource *res_mc_base;
+ unsigned long mc_base_size;
+#ifdef OPTi93X
+ unsigned long mc_indir_index;
+ struct resource *res_mc_indir;
+#endif /* OPTi93X */
+ struct snd_wss *codec;
+ unsigned long pwd_reg;
+
+ spinlock_t lock;
+
+ long wss_base;
+ int irq;
+};
+
+static int snd_opti9xx_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_card_device_id snd_opti9xx_pnpids[] = {
+#ifndef OPTi93X
+ /* OPTi 82C924 */
+ { .id = "OPT0924",
+ .devs = { { "OPT0000" }, { "OPT0002" }, { "OPT0005" } },
+ .driver_data = 0x0924 },
+ /* OPTi 82C925 */
+ { .id = "OPT0925",
+ .devs = { { "OPT9250" }, { "OPT0002" }, { "OPT0005" } },
+ .driver_data = 0x0925 },
+#else
+ /* OPTi 82C931/3 */
+ { .id = "OPT0931", .devs = { { "OPT9310" }, { "OPT0002" } },
+ .driver_data = 0x0931 },
+#endif /* OPTi93X */
+ { .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_opti9xx_pnpids);
+
+#endif /* CONFIG_PNP */
+
+#define DEV_NAME KBUILD_MODNAME
+
+static const char * const snd_opti9xx_names[] = {
+ "unknown",
+ "82C928", "82C929",
+ "82C924", "82C925",
+ "82C930", "82C931", "82C933"
+};
+
+static int snd_opti9xx_init(struct snd_opti9xx *chip,
+ unsigned short hardware)
+{
+ static const int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2};
+
+ chip->hardware = hardware;
+ strcpy(chip->name, snd_opti9xx_names[hardware]);
+
+ spin_lock_init(&chip->lock);
+
+ chip->irq = -1;
+
+#ifndef OPTi93X
+#ifdef CONFIG_PNP
+ if (isapnp && chip->mc_base)
+ /* PnP resource gives the least 10 bits */
+ chip->mc_base |= 0xc00;
+ else
+#endif /* CONFIG_PNP */
+ {
+ chip->mc_base = 0xf8c;
+ chip->mc_base_size = opti9xx_mc_size[hardware];
+ }
+#else
+ chip->mc_base_size = opti9xx_mc_size[hardware];
+#endif
+
+ switch (hardware) {
+#ifndef OPTi93X
+ case OPTi9XX_HW_82C928:
+ case OPTi9XX_HW_82C929:
+ chip->password = (hardware == OPTi9XX_HW_82C928) ? 0xe2 : 0xe3;
+ chip->pwd_reg = 3;
+ break;
+
+ case OPTi9XX_HW_82C924:
+ case OPTi9XX_HW_82C925:
+ chip->password = 0xe5;
+ chip->pwd_reg = 3;
+ break;
+#else /* OPTi93X */
+
+ case OPTi9XX_HW_82C930:
+ case OPTi9XX_HW_82C931:
+ case OPTi9XX_HW_82C933:
+ chip->mc_base = (hardware == OPTi9XX_HW_82C930) ? 0xf8f : 0xf8d;
+ if (!chip->mc_indir_index)
+ chip->mc_indir_index = 0xe0e;
+ chip->password = 0xe4;
+ chip->pwd_reg = 0;
+ break;
+#endif /* OPTi93X */
+
+ default:
+ snd_printk(KERN_ERR "chip %d not supported\n", hardware);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static unsigned char snd_opti9xx_read(struct snd_opti9xx *chip,
+ unsigned char reg)
+{
+ unsigned long flags;
+ unsigned char retval = 0xff;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+ switch (chip->hardware) {
+#ifndef OPTi93X
+ case OPTi9XX_HW_82C924:
+ case OPTi9XX_HW_82C925:
+ if (reg > 7) {
+ outb(reg, chip->mc_base + 8);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+ retval = inb(chip->mc_base + 9);
+ break;
+ }
+ fallthrough;
+
+ case OPTi9XX_HW_82C928:
+ case OPTi9XX_HW_82C929:
+ retval = inb(chip->mc_base + reg);
+ break;
+#else /* OPTi93X */
+
+ case OPTi9XX_HW_82C930:
+ case OPTi9XX_HW_82C931:
+ case OPTi9XX_HW_82C933:
+ outb(reg, chip->mc_indir_index);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+ retval = inb(chip->mc_indir_index + 1);
+ break;
+#endif /* OPTi93X */
+
+ default:
+ snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware);
+ }
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return retval;
+}
+
+static void snd_opti9xx_write(struct snd_opti9xx *chip, unsigned char reg,
+ unsigned char value)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+ switch (chip->hardware) {
+#ifndef OPTi93X
+ case OPTi9XX_HW_82C924:
+ case OPTi9XX_HW_82C925:
+ if (reg > 7) {
+ outb(reg, chip->mc_base + 8);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+ outb(value, chip->mc_base + 9);
+ break;
+ }
+ fallthrough;
+
+ case OPTi9XX_HW_82C928:
+ case OPTi9XX_HW_82C929:
+ outb(value, chip->mc_base + reg);
+ break;
+#else /* OPTi93X */
+
+ case OPTi9XX_HW_82C930:
+ case OPTi9XX_HW_82C931:
+ case OPTi9XX_HW_82C933:
+ outb(reg, chip->mc_indir_index);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+ outb(value, chip->mc_indir_index + 1);
+ break;
+#endif /* OPTi93X */
+
+ default:
+ snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware);
+ }
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+static inline void snd_opti9xx_write_mask(struct snd_opti9xx *chip,
+ unsigned char reg, unsigned char value, unsigned char mask)
+{
+ unsigned char oldval = snd_opti9xx_read(chip, reg);
+
+ snd_opti9xx_write(chip, reg, (oldval & ~mask) | (value & mask));
+}
+
+static int snd_opti9xx_configure(struct snd_opti9xx *chip,
+ long port,
+ int irq, int dma1, int dma2,
+ long mpu_port, int mpu_irq)
+{
+ unsigned char wss_base_bits;
+ unsigned char irq_bits;
+ unsigned char dma_bits;
+ unsigned char mpu_port_bits = 0;
+ unsigned char mpu_irq_bits;
+
+ switch (chip->hardware) {
+#ifndef OPTi93X
+ case OPTi9XX_HW_82C924:
+ /* opti 929 mode (?), OPL3 clock output, audio enable */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0xf0, 0xfc);
+ /* enable wave audio */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02);
+ fallthrough;
+
+ case OPTi9XX_HW_82C925:
+ /* enable WSS mode */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+ /* OPL3 FM synthesis */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20);
+ /* disable Sound Blaster IRQ and DMA */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff);
+#ifdef CS4231
+ /* cs4231/4248 fix enabled */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+#else
+ /* cs4231/4248 fix disabled */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02);
+#endif /* CS4231 */
+ break;
+
+ case OPTi9XX_HW_82C928:
+ case OPTi9XX_HW_82C929:
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20);
+ /*
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xa2, 0xae);
+ */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c);
+#ifdef CS4231
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+#else
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02);
+#endif /* CS4231 */
+ break;
+
+#else /* OPTi93X */
+ case OPTi9XX_HW_82C931:
+ /* disable 3D sound (set GPIO1 as output, low) */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(20), 0x04, 0x0c);
+ fallthrough;
+
+ case OPTi9XX_HW_82C933:
+ /*
+ * The BTC 1817DW has QS1000 wavetable which is connected
+ * to the serial digital input of the OPTI931.
+ */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff);
+ /*
+ * This bit sets OPTI931 to automaticaly select FM
+ * or digital input signal.
+ */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01);
+ fallthrough;
+
+ case OPTi9XX_HW_82C930:
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x03);
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0x00, 0xff);
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x10 |
+ (chip->hardware == OPTi9XX_HW_82C930 ? 0x00 : 0x04),
+ 0x34);
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x20, 0xbf);
+ break;
+#endif /* OPTi93X */
+
+ default:
+ snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware);
+ return -EINVAL;
+ }
+
+ /* PnP resource says it decodes only 10 bits of address */
+ switch (port & 0x3ff) {
+ case 0x130:
+ chip->wss_base = 0x530;
+ wss_base_bits = 0x00;
+ break;
+ case 0x204:
+ chip->wss_base = 0x604;
+ wss_base_bits = 0x03;
+ break;
+ case 0x280:
+ chip->wss_base = 0xe80;
+ wss_base_bits = 0x01;
+ break;
+ case 0x340:
+ chip->wss_base = 0xf40;
+ wss_base_bits = 0x02;
+ break;
+ default:
+ snd_printk(KERN_WARNING "WSS port 0x%lx not valid\n", port);
+ goto __skip_base;
+ }
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30);
+
+__skip_base:
+ switch (irq) {
+//#ifdef OPTi93X
+ case 5:
+ irq_bits = 0x05;
+ break;
+//#endif /* OPTi93X */
+ case 7:
+ irq_bits = 0x01;
+ break;
+ case 9:
+ irq_bits = 0x02;
+ break;
+ case 10:
+ irq_bits = 0x03;
+ break;
+ case 11:
+ irq_bits = 0x04;
+ break;
+ default:
+ snd_printk(KERN_WARNING "WSS irq # %d not valid\n", irq);
+ goto __skip_resources;
+ }
+
+ switch (dma1) {
+ case 0:
+ dma_bits = 0x01;
+ break;
+ case 1:
+ dma_bits = 0x02;
+ break;
+ case 3:
+ dma_bits = 0x03;
+ break;
+ default:
+ snd_printk(KERN_WARNING "WSS dma1 # %d not valid\n", dma1);
+ goto __skip_resources;
+ }
+
+#if defined(CS4231) || defined(OPTi93X)
+ if (dma1 == dma2) {
+ snd_printk(KERN_ERR "don't want to share dmas\n");
+ return -EBUSY;
+ }
+
+ switch (dma2) {
+ case 0:
+ case 1:
+ break;
+ default:
+ snd_printk(KERN_WARNING "WSS dma2 # %d not valid\n", dma2);
+ goto __skip_resources;
+ }
+ dma_bits |= 0x04;
+#endif /* CS4231 || OPTi93X */
+
+#ifndef OPTi93X
+ outb(irq_bits << 3 | dma_bits, chip->wss_base);
+#else /* OPTi93X */
+ snd_opti9xx_write(chip, OPTi9XX_MC_REG(3), (irq_bits << 3 | dma_bits));
+#endif /* OPTi93X */
+
+__skip_resources:
+ if (chip->hardware > OPTi9XX_HW_82C928) {
+ switch (mpu_port) {
+ case 0:
+ case -1:
+ break;
+ case 0x300:
+ mpu_port_bits = 0x03;
+ break;
+ case 0x310:
+ mpu_port_bits = 0x02;
+ break;
+ case 0x320:
+ mpu_port_bits = 0x01;
+ break;
+ case 0x330:
+ mpu_port_bits = 0x00;
+ break;
+ default:
+ snd_printk(KERN_WARNING
+ "MPU-401 port 0x%lx not valid\n", mpu_port);
+ goto __skip_mpu;
+ }
+
+ switch (mpu_irq) {
+ case 5:
+ mpu_irq_bits = 0x02;
+ break;
+ case 7:
+ mpu_irq_bits = 0x03;
+ break;
+ case 9:
+ mpu_irq_bits = 0x00;
+ break;
+ case 10:
+ mpu_irq_bits = 0x01;
+ break;
+ default:
+ snd_printk(KERN_WARNING "MPU-401 irq # %d not valid\n",
+ mpu_irq);
+ goto __skip_mpu;
+ }
+
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6),
+ (mpu_port <= 0) ? 0x00 :
+ 0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3,
+ 0xf8);
+ }
+__skip_mpu:
+
+ return 0;
+}
+
+#ifdef OPTi93X
+
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_3db_step, -9300, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit_12db_max, -3300, 300, 0);
+
+static const struct snd_kcontrol_new snd_opti93x_controls[] = {
+WSS_DOUBLE("Master Playback Switch", 0,
+ OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Master Playback Volume", 0,
+ OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 1, 1, 31, 1,
+ db_scale_5bit_3db_step),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+ CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 31, 1,
+ db_scale_5bit),
+WSS_DOUBLE_TLV("FM Playback Volume", 0,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 1, 1, 15, 1,
+ db_scale_4bit_12db_max),
+WSS_DOUBLE("Line Playback Switch", 0,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Line Playback Volume", 0,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 15, 1,
+ db_scale_4bit_12db_max),
+WSS_DOUBLE("Mic Playback Switch", 0,
+ OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Mic Playback Volume", 0,
+ OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 1, 1, 15, 1,
+ db_scale_4bit_12db_max),
+WSS_DOUBLE_TLV("CD Playback Volume", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 1, 1, 15, 1,
+ db_scale_4bit_12db_max),
+WSS_DOUBLE("Aux Playback Switch", 0,
+ OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 0,
+ OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 1, 1, 15, 1,
+ db_scale_4bit_12db_max),
+};
+
+static int snd_opti93x_mixer(struct snd_wss *chip)
+{
+ struct snd_card *card;
+ unsigned int idx;
+ struct snd_ctl_elem_id id1, id2;
+ int err;
+
+ if (snd_BUG_ON(!chip || !chip->pcm))
+ return -EINVAL;
+
+ card = chip->card;
+
+ strcpy(card->mixername, chip->pcm->name);
+
+ memset(&id1, 0, sizeof(id1));
+ memset(&id2, 0, sizeof(id2));
+ id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ /* reassign AUX0 switch to CD */
+ strcpy(id1.name, "Aux Playback Switch");
+ strcpy(id2.name, "CD Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Cannot rename opti93x control\n");
+ return err;
+ }
+ /* reassign AUX1 switch to FM */
+ strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+ strcpy(id2.name, "FM Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Cannot rename opti93x control\n");
+ return err;
+ }
+ /* remove AUX1 volume */
+ strcpy(id1.name, "Aux Playback Volume"); id1.index = 1;
+ snd_ctl_remove_id(card, &id1);
+
+ /* Replace WSS volume controls with OPTi93x volume controls */
+ id1.index = 0;
+ for (idx = 0; idx < ARRAY_SIZE(snd_opti93x_controls); idx++) {
+ strcpy(id1.name, snd_opti93x_controls[idx].name);
+ snd_ctl_remove_id(card, &id1);
+
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_opti93x_controls[idx], chip));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static irqreturn_t snd_opti93x_interrupt(int irq, void *dev_id)
+{
+ struct snd_opti9xx *chip = dev_id;
+ struct snd_wss *codec = chip->codec;
+ unsigned char status;
+
+ if (!codec)
+ return IRQ_HANDLED;
+
+ status = snd_opti9xx_read(chip, OPTi9XX_MC_REG(11));
+ if ((status & OPTi93X_IRQ_PLAYBACK) && codec->playback_substream)
+ snd_pcm_period_elapsed(codec->playback_substream);
+ if ((status & OPTi93X_IRQ_CAPTURE) && codec->capture_substream) {
+ snd_wss_overrange(codec);
+ snd_pcm_period_elapsed(codec->capture_substream);
+ }
+ outb(0x00, OPTi93X_PORT(codec, STATUS));
+ return IRQ_HANDLED;
+}
+
+#endif /* OPTi93X */
+
+static int snd_opti9xx_read_check(struct snd_card *card,
+ struct snd_opti9xx *chip)
+{
+ unsigned char value;
+#ifdef OPTi93X
+ unsigned long flags;
+#endif
+
+ chip->res_mc_base =
+ devm_request_region(card->dev, chip->mc_base,
+ chip->mc_base_size, "OPTi9xx MC");
+ if (!chip->res_mc_base)
+ return -EBUSY;
+#ifndef OPTi93X
+ value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(1));
+ if (value != 0xff && value != inb(chip->mc_base + OPTi9XX_MC_REG(1)))
+ if (value == snd_opti9xx_read(chip, OPTi9XX_MC_REG(1)))
+ return 0;
+#else /* OPTi93X */
+ chip->res_mc_indir =
+ devm_request_region(card->dev, chip->mc_indir_index, 2,
+ "OPTi93x MC");
+ if (!chip->res_mc_indir)
+ return -EBUSY;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ outb(chip->password, chip->mc_base + chip->pwd_reg);
+ outb(((chip->mc_indir_index & 0x1f0) >> 4), chip->mc_base);
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+ value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(7));
+ snd_opti9xx_write(chip, OPTi9XX_MC_REG(7), 0xff - value);
+ if (snd_opti9xx_read(chip, OPTi9XX_MC_REG(7)) == 0xff - value)
+ return 0;
+
+ devm_release_resource(card->dev, chip->res_mc_indir);
+ chip->res_mc_indir = NULL;
+#endif /* OPTi93X */
+ devm_release_resource(card->dev, chip->res_mc_base);
+ chip->res_mc_base = NULL;
+
+ return -ENODEV;
+}
+
+static int snd_card_opti9xx_detect(struct snd_card *card,
+ struct snd_opti9xx *chip)
+{
+ int i, err;
+
+#ifndef OPTi93X
+ for (i = OPTi9XX_HW_82C928; i < OPTi9XX_HW_82C930; i++) {
+#else
+ for (i = OPTi9XX_HW_82C931; i >= OPTi9XX_HW_82C930; i--) {
+#endif
+ err = snd_opti9xx_init(chip, i);
+ if (err < 0)
+ return err;
+
+ err = snd_opti9xx_read_check(card, chip);
+ if (err == 0)
+ return 1;
+#ifdef OPTi93X
+ chip->mc_indir_index = 0;
+#endif
+ }
+ return -ENODEV;
+}
+
+#ifdef CONFIG_PNP
+static int snd_card_opti9xx_pnp(struct snd_opti9xx *chip,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *pid)
+{
+ struct pnp_dev *pdev;
+ int err;
+ struct pnp_dev *devmpu;
+#ifndef OPTi93X
+ struct pnp_dev *devmc;
+#endif
+
+ pdev = pnp_request_card_device(card, pid->devs[0].id, NULL);
+ if (pdev == NULL)
+ return -EBUSY;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err);
+ return err;
+ }
+
+#ifdef OPTi93X
+ port = pnp_port_start(pdev, 0) - 4;
+ fm_port = pnp_port_start(pdev, 1) + 8;
+ /* adjust mc_indir_index - some cards report it at 0xe?d,
+ other at 0xe?c but it really is always at 0xe?e */
+ chip->mc_indir_index = (pnp_port_start(pdev, 3) & ~0xf) | 0xe;
+#else
+ devmc = pnp_request_card_device(card, pid->devs[2].id, NULL);
+ if (devmc == NULL)
+ return -EBUSY;
+
+ err = pnp_activate_dev(devmc);
+ if (err < 0) {
+ snd_printk(KERN_ERR "MC pnp configure failure: %d\n", err);
+ return err;
+ }
+
+ port = pnp_port_start(pdev, 1);
+ fm_port = pnp_port_start(pdev, 2) + 8;
+ /*
+ * The MC(0) is never accessed and card does not
+ * include it in the PnP resource range. OPTI93x include it.
+ */
+ chip->mc_base = pnp_port_start(devmc, 0) - 1;
+ chip->mc_base_size = pnp_port_len(devmc, 0) + 1;
+#endif /* OPTi93X */
+ irq = pnp_irq(pdev, 0);
+ dma1 = pnp_dma(pdev, 0);
+#if defined(CS4231) || defined(OPTi93X)
+ dma2 = pnp_dma(pdev, 1);
+#endif /* CS4231 || OPTi93X */
+
+ devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL);
+
+ if (devmpu && mpu_port > 0) {
+ err = pnp_activate_dev(devmpu);
+ if (err < 0) {
+ snd_printk(KERN_ERR "MPU401 pnp configure failure\n");
+ mpu_port = -1;
+ } else {
+ mpu_port = pnp_port_start(devmpu, 0);
+ mpu_irq = pnp_irq(devmpu, 0);
+ }
+ }
+ return pid->driver_data;
+}
+#endif /* CONFIG_PNP */
+
+static int snd_opti9xx_probe(struct snd_card *card)
+{
+ static const long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1};
+ int error;
+ int xdma2;
+ struct snd_opti9xx *chip = card->private_data;
+ struct snd_wss *codec;
+ struct snd_rawmidi *rmidi;
+ struct snd_hwdep *synth;
+
+#if defined(CS4231) || defined(OPTi93X)
+ xdma2 = dma2;
+#else
+ xdma2 = -1;
+#endif
+
+ if (port == SNDRV_AUTO_PORT) {
+ port = snd_legacy_find_free_ioport(possible_ports, 4);
+ if (port < 0) {
+ snd_printk(KERN_ERR "unable to find a free WSS port\n");
+ return -EBUSY;
+ }
+ }
+ error = snd_opti9xx_configure(chip, port, irq, dma1, xdma2,
+ mpu_port, mpu_irq);
+ if (error)
+ return error;
+
+ error = snd_wss_create(card, chip->wss_base + 4, -1, irq, dma1, xdma2,
+#ifdef OPTi93X
+ WSS_HW_OPTI93X, WSS_HWSHARE_IRQ,
+#else
+ WSS_HW_DETECT, 0,
+#endif
+ &codec);
+ if (error < 0)
+ return error;
+ chip->codec = codec;
+ error = snd_wss_pcm(codec, 0);
+ if (error < 0)
+ return error;
+ error = snd_wss_mixer(codec);
+ if (error < 0)
+ return error;
+#ifdef OPTi93X
+ error = snd_opti93x_mixer(codec);
+ if (error < 0)
+ return error;
+#endif
+#ifdef CS4231
+ error = snd_wss_timer(codec, 0);
+ if (error < 0)
+ return error;
+#endif
+#ifdef OPTi93X
+ error = devm_request_irq(card->dev, irq, snd_opti93x_interrupt,
+ 0, DEV_NAME" - WSS", chip);
+ if (error < 0) {
+ snd_printk(KERN_ERR "opti9xx: can't grab IRQ %d\n", irq);
+ return error;
+ }
+#endif
+ chip->irq = irq;
+ card->sync_irq = chip->irq;
+ strcpy(card->driver, chip->name);
+ sprintf(card->shortname, "OPTi %s", card->driver);
+#if defined(CS4231) || defined(OPTi93X)
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s, %s at 0x%lx, irq %d, dma %d&%d",
+ card->shortname, codec->pcm->name,
+ chip->wss_base + 4, irq, dma1, xdma2);
+#else
+ scnprintf(card->longname, sizeof(card->longname),
+ "%s, %s at 0x%lx, irq %d, dma %d",
+ card->shortname, codec->pcm->name, chip->wss_base + 4, irq,
+ dma1);
+#endif /* CS4231 || OPTi93X */
+
+ if (mpu_port <= 0 || mpu_port == SNDRV_AUTO_PORT)
+ rmidi = NULL;
+ else {
+ error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+ mpu_port, 0, mpu_irq, &rmidi);
+ if (error)
+ snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n",
+ mpu_port);
+ }
+
+ if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) {
+ struct snd_opl3 *opl3 = NULL;
+#ifndef OPTi93X
+ if (chip->hardware == OPTi9XX_HW_82C928 ||
+ chip->hardware == OPTi9XX_HW_82C929 ||
+ chip->hardware == OPTi9XX_HW_82C924) {
+ struct snd_opl4 *opl4;
+ /* assume we have an OPL4 */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2),
+ 0x20, 0x20);
+ if (snd_opl4_create(card, fm_port, fm_port - 8,
+ 2, &opl3, &opl4) < 0) {
+ /* no luck, use OPL3 instead */
+ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2),
+ 0x00, 0x20);
+ }
+ }
+#endif /* !OPTi93X */
+ if (!opl3 && snd_opl3_create(card, fm_port, fm_port + 2,
+ OPL3_HW_AUTO, 0, &opl3) < 0) {
+ snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n",
+ fm_port, fm_port + 4 - 1);
+ }
+ if (opl3) {
+ error = snd_opl3_hwdep_new(opl3, 0, 1, &synth);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ return snd_card_register(card);
+}
+
+static int snd_opti9xx_card_new(struct device *pdev, struct snd_card **cardp)
+{
+ struct snd_card *card;
+ int err;
+
+ err = snd_devm_card_new(pdev, index, id, THIS_MODULE,
+ sizeof(struct snd_opti9xx), &card);
+ if (err < 0)
+ return err;
+ *cardp = card;
+ return 0;
+}
+
+static int snd_opti9xx_isa_match(struct device *devptr,
+ unsigned int dev)
+{
+#ifdef CONFIG_PNP
+ if (snd_opti9xx_pnp_is_probed)
+ return 0;
+ if (isapnp)
+ return 0;
+#endif
+ return 1;
+}
+
+static int snd_opti9xx_isa_probe(struct device *devptr,
+ unsigned int dev)
+{
+ struct snd_card *card;
+ int error;
+ static const long possible_mpu_ports[] = {0x300, 0x310, 0x320, 0x330, -1};
+#ifdef OPTi93X
+ static const int possible_irqs[] = {5, 9, 10, 11, 7, -1};
+#else
+ static const int possible_irqs[] = {9, 10, 11, 7, -1};
+#endif /* OPTi93X */
+ static const int possible_mpu_irqs[] = {5, 9, 10, 7, -1};
+ static const int possible_dma1s[] = {3, 1, 0, -1};
+#if defined(CS4231) || defined(OPTi93X)
+ static const int possible_dma2s[][2] = {{1,-1}, {0,-1}, {-1,-1}, {0,-1}};
+#endif /* CS4231 || OPTi93X */
+
+ if (mpu_port == SNDRV_AUTO_PORT) {
+ mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2);
+ if (mpu_port < 0) {
+ snd_printk(KERN_ERR "unable to find a free MPU401 port\n");
+ return -EBUSY;
+ }
+ }
+ if (irq == SNDRV_AUTO_IRQ) {
+ irq = snd_legacy_find_free_irq(possible_irqs);
+ if (irq < 0) {
+ snd_printk(KERN_ERR "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (mpu_irq == SNDRV_AUTO_IRQ) {
+ mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs);
+ if (mpu_irq < 0) {
+ snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (dma1 == SNDRV_AUTO_DMA) {
+ dma1 = snd_legacy_find_free_dma(possible_dma1s);
+ if (dma1 < 0) {
+ snd_printk(KERN_ERR "unable to find a free DMA1\n");
+ return -EBUSY;
+ }
+ }
+#if defined(CS4231) || defined(OPTi93X)
+ if (dma2 == SNDRV_AUTO_DMA) {
+ dma2 = snd_legacy_find_free_dma(possible_dma2s[dma1 % 4]);
+ if (dma2 < 0) {
+ snd_printk(KERN_ERR "unable to find a free DMA2\n");
+ return -EBUSY;
+ }
+ }
+#endif
+
+ error = snd_opti9xx_card_new(devptr, &card);
+ if (error < 0)
+ return error;
+
+ error = snd_card_opti9xx_detect(card, card->private_data);
+ if (error < 0)
+ return error;
+ error = snd_opti9xx_probe(card);
+ if (error < 0)
+ return error;
+ dev_set_drvdata(devptr, card);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_opti9xx_suspend(struct snd_card *card)
+{
+ struct snd_opti9xx *chip = card->private_data;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ chip->codec->suspend(chip->codec);
+ return 0;
+}
+
+static int snd_opti9xx_resume(struct snd_card *card)
+{
+ struct snd_opti9xx *chip = card->private_data;
+ int error, xdma2;
+#if defined(CS4231) || defined(OPTi93X)
+ xdma2 = dma2;
+#else
+ xdma2 = -1;
+#endif
+
+ error = snd_opti9xx_configure(chip, port, irq, dma1, xdma2,
+ mpu_port, mpu_irq);
+ if (error)
+ return error;
+ chip->codec->resume(chip->codec);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+static int snd_opti9xx_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_opti9xx_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_opti9xx_isa_resume(struct device *dev, unsigned int n)
+{
+ return snd_opti9xx_resume(dev_get_drvdata(dev));
+}
+#endif
+
+static struct isa_driver snd_opti9xx_driver = {
+ .match = snd_opti9xx_isa_match,
+ .probe = snd_opti9xx_isa_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_opti9xx_isa_suspend,
+ .resume = snd_opti9xx_isa_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+#ifdef CONFIG_PNP
+static int snd_opti9xx_pnp_probe(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ struct snd_card *card;
+ int error, hw;
+ struct snd_opti9xx *chip;
+
+ if (snd_opti9xx_pnp_is_probed)
+ return -EBUSY;
+ if (! isapnp)
+ return -ENODEV;
+ error = snd_opti9xx_card_new(&pcard->card->dev, &card);
+ if (error < 0)
+ return error;
+ chip = card->private_data;
+
+ hw = snd_card_opti9xx_pnp(chip, pcard, pid);
+ switch (hw) {
+ case 0x0924:
+ hw = OPTi9XX_HW_82C924;
+ break;
+ case 0x0925:
+ hw = OPTi9XX_HW_82C925;
+ break;
+ case 0x0931:
+ hw = OPTi9XX_HW_82C931;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ error = snd_opti9xx_init(chip, hw);
+ if (error)
+ return error;
+ error = snd_opti9xx_read_check(card, chip);
+ if (error) {
+ snd_printk(KERN_ERR "OPTI chip not found\n");
+ return error;
+ }
+ error = snd_opti9xx_probe(card);
+ if (error < 0)
+ return error;
+ pnp_set_card_drvdata(pcard, card);
+ snd_opti9xx_pnp_is_probed = 1;
+ return 0;
+}
+
+static void snd_opti9xx_pnp_remove(struct pnp_card_link *pcard)
+{
+ snd_opti9xx_pnp_is_probed = 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_opti9xx_pnp_suspend(struct pnp_card_link *pcard,
+ pm_message_t state)
+{
+ return snd_opti9xx_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_opti9xx_pnp_resume(struct pnp_card_link *pcard)
+{
+ return snd_opti9xx_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver opti9xx_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = DEV_NAME,
+ .id_table = snd_opti9xx_pnpids,
+ .probe = snd_opti9xx_pnp_probe,
+ .remove = snd_opti9xx_pnp_remove,
+#ifdef CONFIG_PM
+ .suspend = snd_opti9xx_pnp_suspend,
+ .resume = snd_opti9xx_pnp_resume,
+#endif
+};
+#endif
+
+#ifdef OPTi93X
+#define CHIP_NAME "82C93x"
+#else
+#define CHIP_NAME "82C92x"
+#endif
+
+static int __init alsa_card_opti9xx_init(void)
+{
+#ifdef CONFIG_PNP
+ pnp_register_card_driver(&opti9xx_pnpc_driver);
+ if (snd_opti9xx_pnp_is_probed)
+ return 0;
+ pnp_unregister_card_driver(&opti9xx_pnpc_driver);
+#endif
+ return isa_register_driver(&snd_opti9xx_driver, 1);
+}
+
+static void __exit alsa_card_opti9xx_exit(void)
+{
+ if (!snd_opti9xx_pnp_is_probed) {
+ isa_unregister_driver(&snd_opti9xx_driver);
+ return;
+ }
+#ifdef CONFIG_PNP
+ pnp_unregister_card_driver(&opti9xx_pnpc_driver);
+#endif
+}
+
+module_init(alsa_card_opti9xx_init)
+module_exit(alsa_card_opti9xx_exit)
diff --git a/sound/isa/opti9xx/opti92x-cs4231.c b/sound/isa/opti9xx/opti92x-cs4231.c
new file mode 100644
index 0000000000..b17ab19f6d
--- /dev/null
+++ b/sound/isa/opti9xx/opti92x-cs4231.c
@@ -0,0 +1,2 @@
+#define CS4231
+#include "opti92x-ad1848.c"
diff --git a/sound/isa/opti9xx/opti93x.c b/sound/isa/opti9xx/opti93x.c
new file mode 100644
index 0000000000..bad9da521b
--- /dev/null
+++ b/sound/isa/opti9xx/opti93x.c
@@ -0,0 +1,3 @@
+#define OPTi93X
+#include "opti92x-ad1848.c"
+
diff --git a/sound/isa/sb/Makefile b/sound/isa/sb/Makefile
new file mode 100644
index 0000000000..f174a5b3c8
--- /dev/null
+++ b/sound/isa/sb/Makefile
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-sb-common-objs := sb_common.o sb_mixer.o
+snd-sb8-dsp-objs := sb8_main.o sb8_midi.o
+snd-sb16-dsp-objs := sb16_main.o
+snd-sb16-csp-objs := sb16_csp.o
+snd-sb8-objs := sb8.o
+snd-sb16-objs := sb16.o
+snd-sbawe-objs := sbawe.o emu8000.o
+snd-emu8000-synth-objs := emu8000_synth.o emu8000_callback.o emu8000_patch.o emu8000_pcm.o
+snd-jazz16-objs := jazz16.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_SB_COMMON) += snd-sb-common.o
+obj-$(CONFIG_SND_SB16_DSP) += snd-sb16-dsp.o
+obj-$(CONFIG_SND_SB8_DSP) += snd-sb8-dsp.o
+obj-$(CONFIG_SND_SB8) += snd-sb8.o
+obj-$(CONFIG_SND_SB16) += snd-sb16.o
+obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o
+obj-$(CONFIG_SND_JAZZ16) += snd-jazz16.o
+ifeq ($(CONFIG_SND_SB16_CSP),y)
+ obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o
+ obj-$(CONFIG_SND_SBAWE) += snd-sb16-csp.o
+endif
+obj-$(CONFIG_SND_SBAWE_SEQ) += snd-emu8000-synth.o
diff --git a/sound/isa/sb/emu8000.c b/sound/isa/sb/emu8000.c
new file mode 100644
index 0000000000..a6405772d5
--- /dev/null
+++ b/sound/isa/sb/emu8000.c
@@ -0,0 +1,1127 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk>
+ * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ * Routines for control of EMU8000 chip
+ */
+
+#include <linux/wait.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/export.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/emu8000.h>
+#include <sound/emu8000_reg.h>
+#include <linux/uaccess.h>
+#include <linux/init.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+/*
+ * emu8000 register controls
+ */
+
+/*
+ * The following routines read and write registers on the emu8000. They
+ * should always be called via the EMU8000*READ/WRITE macros and never
+ * directly. The macros handle the port number and command word.
+ */
+/* Write a word */
+void snd_emu8000_poke(struct snd_emu8000 *emu, unsigned int port, unsigned int reg, unsigned int val)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ if (reg != emu->last_reg) {
+ outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+ emu->last_reg = reg;
+ }
+ outw((unsigned short)val, port); /* Send data */
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+/* Read a word */
+unsigned short snd_emu8000_peek(struct snd_emu8000 *emu, unsigned int port, unsigned int reg)
+{
+ unsigned short res;
+ unsigned long flags;
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ if (reg != emu->last_reg) {
+ outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+ emu->last_reg = reg;
+ }
+ res = inw(port); /* Read data */
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return res;
+}
+
+/* Write a double word */
+void snd_emu8000_poke_dw(struct snd_emu8000 *emu, unsigned int port, unsigned int reg, unsigned int val)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ if (reg != emu->last_reg) {
+ outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+ emu->last_reg = reg;
+ }
+ outw((unsigned short)val, port); /* Send low word of data */
+ outw((unsigned short)(val>>16), port+2); /* Send high word of data */
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+/* Read a double word */
+unsigned int snd_emu8000_peek_dw(struct snd_emu8000 *emu, unsigned int port, unsigned int reg)
+{
+ unsigned short low;
+ unsigned int res;
+ unsigned long flags;
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ if (reg != emu->last_reg) {
+ outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+ emu->last_reg = reg;
+ }
+ low = inw(port); /* Read low word of data */
+ res = low + (inw(port+2) << 16);
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ return res;
+}
+
+/*
+ * Set up / close a channel to be used for DMA.
+ */
+/*exported*/ void
+snd_emu8000_dma_chan(struct snd_emu8000 *emu, int ch, int mode)
+{
+ unsigned right_bit = (mode & EMU8000_RAM_RIGHT) ? 0x01000000 : 0;
+ mode &= EMU8000_RAM_MODE_MASK;
+ if (mode == EMU8000_RAM_CLOSE) {
+ EMU8000_CCCA_WRITE(emu, ch, 0);
+ EMU8000_DCYSUSV_WRITE(emu, ch, 0x807F);
+ return;
+ }
+ EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
+ EMU8000_VTFT_WRITE(emu, ch, 0);
+ EMU8000_CVCF_WRITE(emu, ch, 0);
+ EMU8000_PTRX_WRITE(emu, ch, 0x40000000);
+ EMU8000_CPF_WRITE(emu, ch, 0x40000000);
+ EMU8000_PSST_WRITE(emu, ch, 0);
+ EMU8000_CSL_WRITE(emu, ch, 0);
+ if (mode == EMU8000_RAM_WRITE) /* DMA write */
+ EMU8000_CCCA_WRITE(emu, ch, 0x06000000 | right_bit);
+ else /* DMA read */
+ EMU8000_CCCA_WRITE(emu, ch, 0x04000000 | right_bit);
+}
+
+/*
+ */
+static void
+snd_emu8000_read_wait(struct snd_emu8000 *emu)
+{
+ while ((EMU8000_SMALR_READ(emu) & 0x80000000) != 0) {
+ schedule_timeout_interruptible(1);
+ if (signal_pending(current))
+ break;
+ }
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(struct snd_emu8000 *emu)
+{
+ while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+ schedule_timeout_interruptible(1);
+ if (signal_pending(current))
+ break;
+ }
+}
+
+/*
+ * detect a card at the given port
+ */
+static int
+snd_emu8000_detect(struct snd_emu8000 *emu)
+{
+ /* Initialise */
+ EMU8000_HWCF1_WRITE(emu, 0x0059);
+ EMU8000_HWCF2_WRITE(emu, 0x0020);
+ EMU8000_HWCF3_WRITE(emu, 0x0000);
+ /* Check for a recognisable emu8000 */
+ /*
+ if ((EMU8000_U1_READ(emu) & 0x000f) != 0x000c)
+ return -ENODEV;
+ */
+ if ((EMU8000_HWCF1_READ(emu) & 0x007e) != 0x0058)
+ return -ENODEV;
+ if ((EMU8000_HWCF2_READ(emu) & 0x0003) != 0x0003)
+ return -ENODEV;
+
+ snd_printdd("EMU8000 [0x%lx]: Synth chip found\n",
+ emu->port1);
+ return 0;
+}
+
+
+/*
+ * intiailize audio channels
+ */
+static void
+init_audio(struct snd_emu8000 *emu)
+{
+ int ch;
+
+ /* turn off envelope engines */
+ for (ch = 0; ch < EMU8000_CHANNELS; ch++)
+ EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
+
+ /* reset all other parameters to zero */
+ for (ch = 0; ch < EMU8000_CHANNELS; ch++) {
+ EMU8000_ENVVOL_WRITE(emu, ch, 0);
+ EMU8000_ENVVAL_WRITE(emu, ch, 0);
+ EMU8000_DCYSUS_WRITE(emu, ch, 0);
+ EMU8000_ATKHLDV_WRITE(emu, ch, 0);
+ EMU8000_LFO1VAL_WRITE(emu, ch, 0);
+ EMU8000_ATKHLD_WRITE(emu, ch, 0);
+ EMU8000_LFO2VAL_WRITE(emu, ch, 0);
+ EMU8000_IP_WRITE(emu, ch, 0);
+ EMU8000_IFATN_WRITE(emu, ch, 0);
+ EMU8000_PEFE_WRITE(emu, ch, 0);
+ EMU8000_FMMOD_WRITE(emu, ch, 0);
+ EMU8000_TREMFRQ_WRITE(emu, ch, 0);
+ EMU8000_FM2FRQ2_WRITE(emu, ch, 0);
+ EMU8000_PTRX_WRITE(emu, ch, 0);
+ EMU8000_VTFT_WRITE(emu, ch, 0);
+ EMU8000_PSST_WRITE(emu, ch, 0);
+ EMU8000_CSL_WRITE(emu, ch, 0);
+ EMU8000_CCCA_WRITE(emu, ch, 0);
+ }
+
+ for (ch = 0; ch < EMU8000_CHANNELS; ch++) {
+ EMU8000_CPF_WRITE(emu, ch, 0);
+ EMU8000_CVCF_WRITE(emu, ch, 0);
+ }
+}
+
+
+/*
+ * initialize DMA address
+ */
+static void
+init_dma(struct snd_emu8000 *emu)
+{
+ EMU8000_SMALR_WRITE(emu, 0);
+ EMU8000_SMARR_WRITE(emu, 0);
+ EMU8000_SMALW_WRITE(emu, 0);
+ EMU8000_SMARW_WRITE(emu, 0);
+}
+
+/*
+ * initialization arrays; from ADIP
+ */
+static const 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 const 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 const 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 const 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 an initialization array
+ * Taken from the oss driver, not obvious from the doc how this
+ * is meant to work
+ */
+static void
+send_array(struct snd_emu8000 *emu, const unsigned short *data, int size)
+{
+ int i;
+ const unsigned short *p;
+
+ p = data;
+ for (i = 0; i < size; i++, p++)
+ EMU8000_INIT1_WRITE(emu, i, *p);
+ for (i = 0; i < size; i++, p++)
+ EMU8000_INIT2_WRITE(emu, i, *p);
+ for (i = 0; i < size; i++, p++)
+ EMU8000_INIT3_WRITE(emu, i, *p);
+ for (i = 0; i < size; i++, p++)
+ EMU8000_INIT4_WRITE(emu, i, *p);
+}
+
+
+/*
+ * Send initialization arrays to start up, this just follows the
+ * initialisation sequence in the adip.
+ */
+static void
+init_arrays(struct snd_emu8000 *emu)
+{
+ send_array(emu, init1, ARRAY_SIZE(init1)/4);
+
+ msleep((1024 * 1000) / 44100); /* wait for 1024 clocks */
+ send_array(emu, init2, ARRAY_SIZE(init2)/4);
+ send_array(emu, init3, ARRAY_SIZE(init3)/4);
+
+ EMU8000_HWCF4_WRITE(emu, 0);
+ EMU8000_HWCF5_WRITE(emu, 0x83);
+ EMU8000_HWCF6_WRITE(emu, 0x8000);
+
+ send_array(emu, init4, ARRAY_SIZE(init4)/4);
+}
+
+
+#define UNIQUE_ID1 0xa5b9
+#define UNIQUE_ID2 0x9d53
+
+/*
+ * Size the onboard memory.
+ * This is written so as not to need arbitrary delays after the write. It
+ * seems that the only way to do this is to use the one channel and keep
+ * reallocating between read and write.
+ */
+static void
+size_dram(struct snd_emu8000 *emu)
+{
+ int i, size;
+
+ if (emu->dram_checked)
+ return;
+
+ size = 0;
+
+ /* write out a magic number */
+ snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);
+ snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_READ);
+ EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET);
+ EMU8000_SMLD_WRITE(emu, UNIQUE_ID1);
+ snd_emu8000_init_fm(emu); /* This must really be here and not 2 lines back even */
+ snd_emu8000_write_wait(emu);
+
+ /*
+ * Detect first 512 KiB. If a write succeeds at the beginning of a
+ * 512 KiB page we assume that the whole page is there.
+ */
+ EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET);
+ EMU8000_SMLD_READ(emu); /* discard stale data */
+ if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1)
+ goto skip_detect; /* No RAM */
+ snd_emu8000_read_wait(emu);
+
+ for (size = 512 * 1024; size < EMU8000_MAX_DRAM; size += 512 * 1024) {
+
+ /* Write a unique data on the test address.
+ * if the address is out of range, the data is written on
+ * 0x200000(=EMU8000_DRAM_OFFSET). Then the id word is
+ * changed by this data.
+ */
+ /*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);*/
+ EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
+ EMU8000_SMLD_WRITE(emu, UNIQUE_ID2);
+ snd_emu8000_write_wait(emu);
+
+ /*
+ * read the data on the just written DRAM address
+ * if not the same then we have reached the end of ram.
+ */
+ /*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_READ);*/
+ EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
+ /*snd_emu8000_read_wait(emu);*/
+ EMU8000_SMLD_READ(emu); /* discard stale data */
+ if (EMU8000_SMLD_READ(emu) != UNIQUE_ID2)
+ break; /* no memory at this address */
+ snd_emu8000_read_wait(emu);
+
+ /*
+ * If it is the same it could be that the address just
+ * wraps back to the beginning; so check to see if the
+ * initial value has been overwritten.
+ */
+ EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET);
+ EMU8000_SMLD_READ(emu); /* discard stale data */
+ if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1)
+ break; /* we must have wrapped around */
+ snd_emu8000_read_wait(emu);
+
+ /* Otherwise, it's valid memory. */
+ }
+
+skip_detect:
+ /* wait until FULL bit in SMAxW register is false */
+ for (i = 0; i < 10000; i++) {
+ if ((EMU8000_SMALW_READ(emu) & 0x80000000) == 0)
+ break;
+ schedule_timeout_interruptible(1);
+ if (signal_pending(current))
+ break;
+ }
+ snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_CLOSE);
+ snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_CLOSE);
+
+ pr_info("EMU8000 [0x%lx]: %d KiB on-board DRAM detected\n",
+ emu->port1, size/1024);
+
+ emu->mem_size = size;
+ emu->dram_checked = 1;
+}
+
+
+/*
+ * Initiailise the FM section. You have to do this to use sample RAM
+ * and therefore lose 2 voices.
+ */
+/*exported*/ void
+snd_emu8000_init_fm(struct snd_emu8000 *emu)
+{
+ unsigned long flags;
+
+ /* 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 */
+ EMU8000_DCYSUSV_WRITE(emu, 30, 0x80);
+ EMU8000_PSST_WRITE(emu, 30, 0xFFFFFFE0); /* full left */
+ EMU8000_CSL_WRITE(emu, 30, 0x00FFFFE8 | (emu->fm_chorus_depth << 24));
+ EMU8000_PTRX_WRITE(emu, 30, (emu->fm_reverb_depth << 8));
+ EMU8000_CPF_WRITE(emu, 30, 0);
+ EMU8000_CCCA_WRITE(emu, 30, 0x00FFFFE3);
+
+ /* 32: FM right channel, 0xfffff0-0xfffff8 */
+ EMU8000_DCYSUSV_WRITE(emu, 31, 0x80);
+ EMU8000_PSST_WRITE(emu, 31, 0x00FFFFF0); /* full right */
+ EMU8000_CSL_WRITE(emu, 31, 0x00FFFFF8 | (emu->fm_chorus_depth << 24));
+ EMU8000_PTRX_WRITE(emu, 31, (emu->fm_reverb_depth << 8));
+ EMU8000_CPF_WRITE(emu, 31, 0x8000);
+ EMU8000_CCCA_WRITE(emu, 31, 0x00FFFFF3);
+
+ snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0);
+
+ spin_lock_irqsave(&emu->reg_lock, flags);
+ while (!(inw(EMU8000_PTR(emu)) & 0x1000))
+ ;
+ while ((inw(EMU8000_PTR(emu)) & 0x1000))
+ ;
+ spin_unlock_irqrestore(&emu->reg_lock, flags);
+ snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0x4828);
+ /* this is really odd part.. */
+ outb(0x3C, EMU8000_PTR(emu));
+ outb(0, EMU8000_DATA1(emu));
+
+ /* skew volume & cutoff */
+ EMU8000_VTFT_WRITE(emu, 30, 0x8000FFFF);
+ EMU8000_VTFT_WRITE(emu, 31, 0x8000FFFF);
+}
+
+
+/*
+ * The main initialization routine.
+ */
+static void
+snd_emu8000_init_hw(struct snd_emu8000 *emu)
+{
+ int i;
+
+ emu->last_reg = 0xffff; /* reset the last register index */
+
+ /* initialize hardware configuration */
+ EMU8000_HWCF1_WRITE(emu, 0x0059);
+ EMU8000_HWCF2_WRITE(emu, 0x0020);
+
+ /* disable audio; this seems to reduce a clicking noise a bit.. */
+ EMU8000_HWCF3_WRITE(emu, 0);
+
+ /* initialize audio channels */
+ init_audio(emu);
+
+ /* initialize DMA */
+ init_dma(emu);
+
+ /* initialize init arrays */
+ init_arrays(emu);
+
+ /*
+ * Initialize the FM section of the AWE32, this is needed
+ * for DRAM refresh as well
+ */
+ snd_emu8000_init_fm(emu);
+
+ /* terminate all voices */
+ for (i = 0; i < EMU8000_DRAM_VOICES; i++)
+ EMU8000_DCYSUSV_WRITE(emu, 0, 0x807F);
+
+ /* check DRAM memory size */
+ size_dram(emu);
+
+ /* enable audio */
+ EMU8000_HWCF3_WRITE(emu, 0x4);
+
+ /* set equzlier, chorus and reverb modes */
+ snd_emu8000_update_equalizer(emu);
+ snd_emu8000_update_chorus_mode(emu);
+ snd_emu8000_update_reverb_mode(emu);
+}
+
+
+/*----------------------------------------------------------------
+ * Bass/Treble Equalizer
+ *----------------------------------------------------------------*/
+
+static const 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, 0xC384, 0x0002}, /* +10 */
+ {0xC26A, 0xC36A, 0x0002}, /* +12 dB */
+};
+
+static const 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]
+ */
+/*exported*/ void
+snd_emu8000_update_equalizer(struct snd_emu8000 *emu)
+{
+ unsigned short w;
+ int bass = emu->bass_level;
+ int treble = emu->treble_level;
+
+ if (bass < 0 || bass > 11 || treble < 0 || treble > 11)
+ return;
+ EMU8000_INIT4_WRITE(emu, 0x01, bass_parm[bass][0]);
+ EMU8000_INIT4_WRITE(emu, 0x11, bass_parm[bass][1]);
+ EMU8000_INIT3_WRITE(emu, 0x11, treble_parm[treble][0]);
+ EMU8000_INIT3_WRITE(emu, 0x13, treble_parm[treble][1]);
+ EMU8000_INIT3_WRITE(emu, 0x1b, treble_parm[treble][2]);
+ EMU8000_INIT4_WRITE(emu, 0x07, treble_parm[treble][3]);
+ EMU8000_INIT4_WRITE(emu, 0x0b, treble_parm[treble][4]);
+ EMU8000_INIT4_WRITE(emu, 0x0d, treble_parm[treble][5]);
+ EMU8000_INIT4_WRITE(emu, 0x17, treble_parm[treble][6]);
+ EMU8000_INIT4_WRITE(emu, 0x19, treble_parm[treble][7]);
+ w = bass_parm[bass][2] + treble_parm[treble][8];
+ EMU8000_INIT4_WRITE(emu, 0x15, (unsigned short)(w + 0x0262));
+ EMU8000_INIT4_WRITE(emu, 0x1d, (unsigned short)(w + 0x8362));
+}
+
+
+/*----------------------------------------------------------------
+ * Chorus mode control
+ *----------------------------------------------------------------*/
+
+/*
+ * chorus mode parameters
+ */
+#define SNDRV_EMU8000_CHORUS_1 0
+#define SNDRV_EMU8000_CHORUS_2 1
+#define SNDRV_EMU8000_CHORUS_3 2
+#define SNDRV_EMU8000_CHORUS_4 3
+#define SNDRV_EMU8000_CHORUS_FEEDBACK 4
+#define SNDRV_EMU8000_CHORUS_FLANGER 5
+#define SNDRV_EMU8000_CHORUS_SHORTDELAY 6
+#define SNDRV_EMU8000_CHORUS_SHORTDELAY2 7
+#define SNDRV_EMU8000_CHORUS_PREDEFINED 8
+/* user can define chorus modes up to 32 */
+#define SNDRV_EMU8000_CHORUS_NUMBERS 32
+
+struct soundfont_chorus_fx {
+ unsigned short feedback; /* feedback level (0xE600-0xE6FF) */
+ unsigned short delay_offset; /* delay (0-0x0DA3) [1/44100 sec] */
+ unsigned short lfo_depth; /* LFO depth (0xBC00-0xBCFF) */
+ unsigned int delay; /* right delay (0-0xFFFFFFFF) [1/256/44100 sec] */
+ unsigned int lfo_freq; /* LFO freq LFO freq (0-0xFFFFFFFF) */
+};
+
+/* 5 parameters for each chorus mode; 3 x 16bit, 2 x 32bit */
+static char chorus_defined[SNDRV_EMU8000_CHORUS_NUMBERS];
+static struct soundfont_chorus_fx chorus_parm[SNDRV_EMU8000_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, 0x0006E000, 0x00000083}, /* short delay */
+ {0xE6C0, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay + feedback */
+};
+
+/*exported*/ int
+snd_emu8000_load_chorus_fx(struct snd_emu8000 *emu, int mode, const void __user *buf, long len)
+{
+ struct soundfont_chorus_fx rec;
+ if (mode < SNDRV_EMU8000_CHORUS_PREDEFINED || mode >= SNDRV_EMU8000_CHORUS_NUMBERS) {
+ snd_printk(KERN_WARNING "invalid chorus mode %d for uploading\n", mode);
+ return -EINVAL;
+ }
+ if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
+ return -EFAULT;
+ chorus_parm[mode] = rec;
+ chorus_defined[mode] = 1;
+ return 0;
+}
+
+/*exported*/ void
+snd_emu8000_update_chorus_mode(struct snd_emu8000 *emu)
+{
+ int effect = emu->chorus_mode;
+ if (effect < 0 || effect >= SNDRV_EMU8000_CHORUS_NUMBERS ||
+ (effect >= SNDRV_EMU8000_CHORUS_PREDEFINED && !chorus_defined[effect]))
+ return;
+ EMU8000_INIT3_WRITE(emu, 0x09, chorus_parm[effect].feedback);
+ EMU8000_INIT3_WRITE(emu, 0x0c, chorus_parm[effect].delay_offset);
+ EMU8000_INIT4_WRITE(emu, 0x03, chorus_parm[effect].lfo_depth);
+ EMU8000_HWCF4_WRITE(emu, chorus_parm[effect].delay);
+ EMU8000_HWCF5_WRITE(emu, chorus_parm[effect].lfo_freq);
+ EMU8000_HWCF6_WRITE(emu, 0x8000);
+ EMU8000_HWCF7_WRITE(emu, 0x0000);
+}
+
+/*----------------------------------------------------------------
+ * Reverb mode control
+ *----------------------------------------------------------------*/
+
+/*
+ * reverb mode parameters
+ */
+#define SNDRV_EMU8000_REVERB_ROOM1 0
+#define SNDRV_EMU8000_REVERB_ROOM2 1
+#define SNDRV_EMU8000_REVERB_ROOM3 2
+#define SNDRV_EMU8000_REVERB_HALL1 3
+#define SNDRV_EMU8000_REVERB_HALL2 4
+#define SNDRV_EMU8000_REVERB_PLATE 5
+#define SNDRV_EMU8000_REVERB_DELAY 6
+#define SNDRV_EMU8000_REVERB_PANNINGDELAY 7
+#define SNDRV_EMU8000_REVERB_PREDEFINED 8
+/* user can define reverb modes up to 32 */
+#define SNDRV_EMU8000_REVERB_NUMBERS 32
+
+struct soundfont_reverb_fx {
+ unsigned short parms[28];
+};
+
+/* 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[SNDRV_EMU8000_CHORUS_NUMBERS];
+static struct soundfont_reverb_fx reverb_parm[SNDRV_EMU8000_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,
+}},
+};
+
+enum { DATA1, DATA2 };
+#define AWE_INIT1(c) EMU8000_CMD(2,c), DATA1
+#define AWE_INIT2(c) EMU8000_CMD(2,c), DATA2
+#define AWE_INIT3(c) EMU8000_CMD(3,c), DATA1
+#define AWE_INIT4(c) EMU8000_CMD(3,c), DATA2
+
+static struct reverb_cmd_pair {
+ 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)},
+};
+
+/*exported*/ int
+snd_emu8000_load_reverb_fx(struct snd_emu8000 *emu, int mode, const void __user *buf, long len)
+{
+ struct soundfont_reverb_fx rec;
+
+ if (mode < SNDRV_EMU8000_REVERB_PREDEFINED || mode >= SNDRV_EMU8000_REVERB_NUMBERS) {
+ snd_printk(KERN_WARNING "invalid reverb mode %d for uploading\n", mode);
+ return -EINVAL;
+ }
+ if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
+ return -EFAULT;
+ reverb_parm[mode] = rec;
+ reverb_defined[mode] = 1;
+ return 0;
+}
+
+/*exported*/ void
+snd_emu8000_update_reverb_mode(struct snd_emu8000 *emu)
+{
+ int effect = emu->reverb_mode;
+ int i;
+
+ if (effect < 0 || effect >= SNDRV_EMU8000_REVERB_NUMBERS ||
+ (effect >= SNDRV_EMU8000_REVERB_PREDEFINED && !reverb_defined[effect]))
+ return;
+ for (i = 0; i < 28; i++) {
+ int port;
+ if (reverb_cmds[i].port == DATA1)
+ port = EMU8000_DATA1(emu);
+ else
+ port = EMU8000_DATA2(emu);
+ snd_emu8000_poke(emu, port, reverb_cmds[i].cmd, reverb_parm[effect].parms[i]);
+ }
+}
+
+
+/*----------------------------------------------------------------
+ * mixer interface
+ *----------------------------------------------------------------*/
+
+/*
+ * bass/treble
+ */
+static int mixer_bass_treble_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 11;
+ return 0;
+}
+
+static int mixer_bass_treble_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->treble_level : emu->bass_level;
+ return 0;
+}
+
+static int mixer_bass_treble_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned short val1;
+
+ val1 = ucontrol->value.integer.value[0] % 12;
+ spin_lock_irqsave(&emu->control_lock, flags);
+ if (kcontrol->private_value) {
+ change = val1 != emu->treble_level;
+ emu->treble_level = val1;
+ } else {
+ change = val1 != emu->bass_level;
+ emu->bass_level = val1;
+ }
+ spin_unlock_irqrestore(&emu->control_lock, flags);
+ snd_emu8000_update_equalizer(emu);
+ return change;
+}
+
+static const struct snd_kcontrol_new mixer_bass_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Synth Tone Control - Bass",
+ .info = mixer_bass_treble_info,
+ .get = mixer_bass_treble_get,
+ .put = mixer_bass_treble_put,
+ .private_value = 0,
+};
+
+static const struct snd_kcontrol_new mixer_treble_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Synth Tone Control - Treble",
+ .info = mixer_bass_treble_info,
+ .get = mixer_bass_treble_get,
+ .put = mixer_bass_treble_put,
+ .private_value = 1,
+};
+
+/*
+ * chorus/reverb mode
+ */
+static int mixer_chorus_reverb_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = kcontrol->private_value ? (SNDRV_EMU8000_CHORUS_NUMBERS-1) : (SNDRV_EMU8000_REVERB_NUMBERS-1);
+ return 0;
+}
+
+static int mixer_chorus_reverb_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->chorus_mode : emu->reverb_mode;
+ return 0;
+}
+
+static int mixer_chorus_reverb_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned short val1;
+
+ spin_lock_irqsave(&emu->control_lock, flags);
+ if (kcontrol->private_value) {
+ val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_CHORUS_NUMBERS;
+ change = val1 != emu->chorus_mode;
+ emu->chorus_mode = val1;
+ } else {
+ val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_REVERB_NUMBERS;
+ change = val1 != emu->reverb_mode;
+ emu->reverb_mode = val1;
+ }
+ spin_unlock_irqrestore(&emu->control_lock, flags);
+ if (change) {
+ if (kcontrol->private_value)
+ snd_emu8000_update_chorus_mode(emu);
+ else
+ snd_emu8000_update_reverb_mode(emu);
+ }
+ return change;
+}
+
+static const struct snd_kcontrol_new mixer_chorus_mode_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Chorus Mode",
+ .info = mixer_chorus_reverb_info,
+ .get = mixer_chorus_reverb_get,
+ .put = mixer_chorus_reverb_put,
+ .private_value = 1,
+};
+
+static const struct snd_kcontrol_new mixer_reverb_mode_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Reverb Mode",
+ .info = mixer_chorus_reverb_info,
+ .get = mixer_chorus_reverb_get,
+ .put = mixer_chorus_reverb_put,
+ .private_value = 0,
+};
+
+/*
+ * FM OPL3 chorus/reverb depth
+ */
+static int mixer_fm_depth_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int mixer_fm_depth_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->fm_chorus_depth : emu->fm_reverb_depth;
+ return 0;
+}
+
+static int mixer_fm_depth_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned short val1;
+
+ val1 = ucontrol->value.integer.value[0] % 256;
+ spin_lock_irqsave(&emu->control_lock, flags);
+ if (kcontrol->private_value) {
+ change = val1 != emu->fm_chorus_depth;
+ emu->fm_chorus_depth = val1;
+ } else {
+ change = val1 != emu->fm_reverb_depth;
+ emu->fm_reverb_depth = val1;
+ }
+ spin_unlock_irqrestore(&emu->control_lock, flags);
+ if (change)
+ snd_emu8000_init_fm(emu);
+ return change;
+}
+
+static const struct snd_kcontrol_new mixer_fm_chorus_depth_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "FM Chorus Depth",
+ .info = mixer_fm_depth_info,
+ .get = mixer_fm_depth_get,
+ .put = mixer_fm_depth_put,
+ .private_value = 1,
+};
+
+static const struct snd_kcontrol_new mixer_fm_reverb_depth_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "FM Reverb Depth",
+ .info = mixer_fm_depth_info,
+ .get = mixer_fm_depth_get,
+ .put = mixer_fm_depth_put,
+ .private_value = 0,
+};
+
+
+static const struct snd_kcontrol_new *mixer_defs[EMU8000_NUM_CONTROLS] = {
+ &mixer_bass_control,
+ &mixer_treble_control,
+ &mixer_chorus_mode_control,
+ &mixer_reverb_mode_control,
+ &mixer_fm_chorus_depth_control,
+ &mixer_fm_reverb_depth_control,
+};
+
+/*
+ * create and attach mixer elements for WaveTable treble/bass controls
+ */
+static int
+snd_emu8000_create_mixer(struct snd_card *card, struct snd_emu8000 *emu)
+{
+ struct snd_kcontrol *kctl;
+ int i, err = 0;
+
+ if (snd_BUG_ON(!emu || !card))
+ return -EINVAL;
+
+ spin_lock_init(&emu->control_lock);
+
+ memset(emu->controls, 0, sizeof(emu->controls));
+ for (i = 0; i < EMU8000_NUM_CONTROLS; i++) {
+ kctl = snd_ctl_new1(mixer_defs[i], emu);
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ goto __error;
+ emu->controls[i] = kctl;
+ }
+ return 0;
+
+__error:
+ for (i = 0; i < EMU8000_NUM_CONTROLS; i++) {
+ if (emu->controls[i])
+ snd_ctl_remove(card, emu->controls[i]);
+ }
+ return err;
+}
+
+/*
+ * initialize and register emu8000 synth device.
+ */
+int
+snd_emu8000_new(struct snd_card *card, int index, long port, int seq_ports,
+ struct snd_seq_device **awe_ret)
+{
+ struct snd_seq_device *awe;
+ struct snd_emu8000 *hw;
+ int err;
+
+ if (awe_ret)
+ *awe_ret = NULL;
+
+ if (seq_ports <= 0)
+ return 0;
+
+ hw = devm_kzalloc(card->dev, sizeof(*hw), GFP_KERNEL);
+ if (hw == NULL)
+ return -ENOMEM;
+ spin_lock_init(&hw->reg_lock);
+ hw->index = index;
+ hw->port1 = port;
+ hw->port2 = port + 0x400;
+ hw->port3 = port + 0x800;
+ if (!devm_request_region(card->dev, hw->port1, 4, "Emu8000-1") ||
+ !devm_request_region(card->dev, hw->port2, 4, "Emu8000-2") ||
+ !devm_request_region(card->dev, hw->port3, 4, "Emu8000-3")) {
+ snd_printk(KERN_ERR "sbawe: can't grab ports 0x%lx, 0x%lx, 0x%lx\n", hw->port1, hw->port2, hw->port3);
+ return -EBUSY;
+ }
+ hw->mem_size = 0;
+ hw->card = card;
+ hw->seq_ports = seq_ports;
+ hw->bass_level = 5;
+ hw->treble_level = 9;
+ hw->chorus_mode = 2;
+ hw->reverb_mode = 4;
+ hw->fm_chorus_depth = 0;
+ hw->fm_reverb_depth = 0;
+
+ if (snd_emu8000_detect(hw) < 0)
+ return -ENODEV;
+
+ snd_emu8000_init_hw(hw);
+ err = snd_emu8000_create_mixer(card, hw);
+ if (err < 0)
+ return err;
+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
+ if (snd_seq_device_new(card, index, SNDRV_SEQ_DEV_ID_EMU8000,
+ sizeof(struct snd_emu8000*), &awe) >= 0) {
+ strcpy(awe->name, "EMU-8000");
+ *(struct snd_emu8000 **)SNDRV_SEQ_DEVICE_ARGPTR(awe) = hw;
+ }
+#else
+ awe = NULL;
+#endif
+ if (awe_ret)
+ *awe_ret = awe;
+
+ return 0;
+}
+
+
+/*
+ * exported stuff
+ */
+
+EXPORT_SYMBOL(snd_emu8000_poke);
+EXPORT_SYMBOL(snd_emu8000_peek);
+EXPORT_SYMBOL(snd_emu8000_poke_dw);
+EXPORT_SYMBOL(snd_emu8000_peek_dw);
+EXPORT_SYMBOL(snd_emu8000_dma_chan);
+EXPORT_SYMBOL(snd_emu8000_init_fm);
+EXPORT_SYMBOL(snd_emu8000_load_chorus_fx);
+EXPORT_SYMBOL(snd_emu8000_load_reverb_fx);
+EXPORT_SYMBOL(snd_emu8000_update_chorus_mode);
+EXPORT_SYMBOL(snd_emu8000_update_reverb_mode);
+EXPORT_SYMBOL(snd_emu8000_update_equalizer);
diff --git a/sound/isa/sb/emu8000_callback.c b/sound/isa/sb/emu8000_callback.c
new file mode 100644
index 0000000000..7609a5b640
--- /dev/null
+++ b/sound/isa/sb/emu8000_callback.c
@@ -0,0 +1,534 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * synth callback routines for the emu8000 (AWE32/64)
+ *
+ * Copyright (C) 1999 Steve Ratcliffe
+ * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include "emu8000_local.h"
+#include <linux/export.h>
+#include <sound/asoundef.h>
+
+/*
+ * prototypes
+ */
+static struct snd_emux_voice *get_voice(struct snd_emux *emu,
+ struct snd_emux_port *port);
+static int start_voice(struct snd_emux_voice *vp);
+static void trigger_voice(struct snd_emux_voice *vp);
+static void release_voice(struct snd_emux_voice *vp);
+static void update_voice(struct snd_emux_voice *vp, int update);
+static void reset_voice(struct snd_emux *emu, int ch);
+static void terminate_voice(struct snd_emux_voice *vp);
+static void sysex(struct snd_emux *emu, char *buf, int len, int parsed,
+ struct snd_midi_channel_set *chset);
+#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
+static int oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2);
+#endif
+static int load_fx(struct snd_emux *emu, int type, int mode,
+ const void __user *buf, long len);
+
+static void set_pitch(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_pan(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_fmmod(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_tremfreq(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_filterQ(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void snd_emu8000_tweak_voice(struct snd_emu8000 *emu, int ch);
+
+/*
+ * Ensure a value is between two points
+ * macro evaluates its args more than once, so changed to upper-case.
+ */
+#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
+#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)
+
+
+/*
+ * set up operators
+ */
+static const struct snd_emux_operators emu8000_ops = {
+ .owner = THIS_MODULE,
+ .get_voice = get_voice,
+ .prepare = start_voice,
+ .trigger = trigger_voice,
+ .release = release_voice,
+ .update = update_voice,
+ .terminate = terminate_voice,
+ .reset = reset_voice,
+ .sample_new = snd_emu8000_sample_new,
+ .sample_free = snd_emu8000_sample_free,
+ .sample_reset = snd_emu8000_sample_reset,
+ .load_fx = load_fx,
+ .sysex = sysex,
+#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
+ .oss_ioctl = oss_ioctl,
+#endif
+};
+
+void
+snd_emu8000_ops_setup(struct snd_emu8000 *hw)
+{
+ hw->emu->ops = emu8000_ops;
+}
+
+
+
+/*
+ * Terminate a voice
+ */
+static void
+release_voice(struct snd_emux_voice *vp)
+{
+ int dcysusv;
+ struct snd_emu8000 *hw;
+
+ hw = vp->hw;
+ dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
+ EMU8000_DCYSUS_WRITE(hw, vp->ch, dcysusv);
+ dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease;
+ EMU8000_DCYSUSV_WRITE(hw, vp->ch, dcysusv);
+}
+
+
+/*
+ */
+static void
+terminate_voice(struct snd_emux_voice *vp)
+{
+ struct snd_emu8000 *hw;
+
+ hw = vp->hw;
+ EMU8000_DCYSUSV_WRITE(hw, vp->ch, 0x807F);
+}
+
+
+/*
+ */
+static void
+update_voice(struct snd_emux_voice *vp, int update)
+{
+ struct snd_emu8000 *hw;
+
+ hw = vp->hw;
+ if (update & SNDRV_EMUX_UPDATE_VOLUME)
+ set_volume(hw, vp);
+ if (update & SNDRV_EMUX_UPDATE_PITCH)
+ set_pitch(hw, vp);
+ if ((update & SNDRV_EMUX_UPDATE_PAN) &&
+ vp->port->ctrls[EMUX_MD_REALTIME_PAN])
+ set_pan(hw, vp);
+ if (update & SNDRV_EMUX_UPDATE_FMMOD)
+ set_fmmod(hw, vp);
+ if (update & SNDRV_EMUX_UPDATE_TREMFREQ)
+ set_tremfreq(hw, vp);
+ if (update & SNDRV_EMUX_UPDATE_FM2FRQ2)
+ set_fm2frq2(hw, vp);
+ if (update & SNDRV_EMUX_UPDATE_Q)
+ set_filterQ(hw, vp);
+}
+
+
+/*
+ * Find a channel (voice) within the EMU that is not in use or at least
+ * less in use than other channels. Always returns a valid pointer
+ * no matter what. If there is a real shortage of voices then one
+ * will be cut. Such is life.
+ *
+ * The channel index (vp->ch) must be initialized in this routine.
+ * In Emu8k, it is identical with the array index.
+ */
+static struct snd_emux_voice *
+get_voice(struct snd_emux *emu, struct snd_emux_port *port)
+{
+ int i;
+ struct snd_emux_voice *vp;
+ struct snd_emu8000 *hw;
+
+ /* what we are looking for, in order of preference */
+ enum {
+ OFF=0, RELEASED, PLAYING, END
+ };
+
+ /* Keeps track of what we are finding */
+ struct best {
+ unsigned int time;
+ int voice;
+ } best[END];
+ struct best *bp;
+
+ hw = emu->hw;
+
+ for (i = 0; i < END; i++) {
+ best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */
+ best[i].voice = -1;
+ }
+
+ /*
+ * Go through them all and get a best one to use.
+ */
+ for (i = 0; i < emu->max_voices; i++) {
+ int state, val;
+
+ vp = &emu->voices[i];
+ state = vp->state;
+
+ if (state == SNDRV_EMUX_ST_OFF)
+ bp = best + OFF;
+ else if (state == SNDRV_EMUX_ST_RELEASED ||
+ state == SNDRV_EMUX_ST_PENDING) {
+ bp = best + RELEASED;
+ val = (EMU8000_CVCF_READ(hw, vp->ch) >> 16) & 0xffff;
+ if (! val)
+ bp = best + OFF;
+ }
+ else if (state & SNDRV_EMUX_ST_ON)
+ bp = best + PLAYING;
+ else
+ continue;
+
+ /* check if sample is finished playing (non-looping only) */
+ if (state != SNDRV_EMUX_ST_OFF &&
+ (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) {
+ val = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
+ if (val >= vp->reg.loopstart)
+ bp = best + OFF;
+ }
+
+ if (vp->time < bp->time) {
+ bp->time = vp->time;
+ bp->voice = i;
+ }
+ }
+
+ for (i = 0; i < END; i++) {
+ if (best[i].voice >= 0) {
+ vp = &emu->voices[best[i].voice];
+ vp->ch = best[i].voice;
+ return vp;
+ }
+ }
+
+ /* not found */
+ return NULL;
+}
+
+/*
+ */
+static int
+start_voice(struct snd_emux_voice *vp)
+{
+ unsigned int temp;
+ int ch;
+ int addr;
+ struct snd_midi_channel *chan;
+ struct snd_emu8000 *hw;
+
+ hw = vp->hw;
+ ch = vp->ch;
+ chan = vp->chan;
+
+ /* channel to be silent and idle */
+ EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
+ EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
+ EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
+ EMU8000_PTRX_WRITE(hw, ch, 0);
+ EMU8000_CPF_WRITE(hw, ch, 0);
+
+ /* set pitch offset */
+ set_pitch(hw, vp);
+
+ /* set envelope parameters */
+ EMU8000_ENVVAL_WRITE(hw, ch, vp->reg.parm.moddelay);
+ EMU8000_ATKHLD_WRITE(hw, ch, vp->reg.parm.modatkhld);
+ EMU8000_DCYSUS_WRITE(hw, ch, vp->reg.parm.moddcysus);
+ EMU8000_ENVVOL_WRITE(hw, ch, vp->reg.parm.voldelay);
+ EMU8000_ATKHLDV_WRITE(hw, ch, vp->reg.parm.volatkhld);
+ /* decay/sustain parameter for volume envelope is used
+ for triggerg the voice */
+
+ /* cutoff and volume */
+ set_volume(hw, vp);
+
+ /* modulation envelope heights */
+ EMU8000_PEFE_WRITE(hw, ch, vp->reg.parm.pefe);
+
+ /* lfo1/2 delay */
+ EMU8000_LFO1VAL_WRITE(hw, ch, vp->reg.parm.lfo1delay);
+ EMU8000_LFO2VAL_WRITE(hw, ch, vp->reg.parm.lfo2delay);
+
+ /* lfo1 pitch & cutoff shift */
+ set_fmmod(hw, vp);
+ /* lfo1 volume & freq */
+ set_tremfreq(hw, vp);
+ /* lfo2 pitch & freq */
+ set_fm2frq2(hw, vp);
+ /* pan & loop start */
+ set_pan(hw, vp);
+
+ /* chorus & loop end (chorus 8bit, MSB) */
+ addr = vp->reg.loopend - 1;
+ temp = vp->reg.parm.chorus;
+ temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10;
+ LIMITMAX(temp, 255);
+ temp = (temp <<24) | (unsigned int)addr;
+ EMU8000_CSL_WRITE(hw, ch, temp);
+
+ /* Q & current address (Q 4bit value, MSB) */
+ addr = vp->reg.start - 1;
+ temp = vp->reg.parm.filterQ;
+ temp = (temp<<28) | (unsigned int)addr;
+ EMU8000_CCCA_WRITE(hw, ch, temp);
+
+ /* clear unknown registers */
+ EMU8000_00A0_WRITE(hw, ch, 0);
+ EMU8000_0080_WRITE(hw, ch, 0);
+
+ /* reset volume */
+ temp = vp->vtarget << 16;
+ EMU8000_VTFT_WRITE(hw, ch, temp | vp->ftarget);
+ EMU8000_CVCF_WRITE(hw, ch, temp | 0xff00);
+
+ return 0;
+}
+
+/*
+ * Start envelope
+ */
+static void
+trigger_voice(struct snd_emux_voice *vp)
+{
+ int ch = vp->ch;
+ unsigned int temp;
+ struct snd_emu8000 *hw;
+
+ hw = vp->hw;
+
+ /* set reverb and pitch target */
+ temp = vp->reg.parm.reverb;
+ temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10;
+ LIMITMAX(temp, 255);
+ temp = (temp << 8) | (vp->ptarget << 16) | vp->aaux;
+ EMU8000_PTRX_WRITE(hw, ch, temp);
+ EMU8000_CPF_WRITE(hw, ch, vp->ptarget << 16);
+ EMU8000_DCYSUSV_WRITE(hw, ch, vp->reg.parm.voldcysus);
+}
+
+/*
+ * reset voice parameters
+ */
+static void
+reset_voice(struct snd_emux *emu, int ch)
+{
+ struct snd_emu8000 *hw;
+
+ hw = emu->hw;
+ EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
+ snd_emu8000_tweak_voice(hw, ch);
+}
+
+/*
+ * Set the pitch of a possibly playing note.
+ */
+static void
+set_pitch(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+ EMU8000_IP_WRITE(hw, vp->ch, vp->apitch);
+}
+
+/*
+ * Set the volume of a possibly already playing note
+ */
+static void
+set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+ int ifatn;
+
+ ifatn = (unsigned char)vp->acutoff;
+ ifatn = (ifatn << 8);
+ ifatn |= (unsigned char)vp->avol;
+ EMU8000_IFATN_WRITE(hw, vp->ch, ifatn);
+}
+
+/*
+ * Set pan and loop start address.
+ */
+static void
+set_pan(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+ unsigned int temp;
+
+ temp = ((unsigned int)vp->apan<<24) | ((unsigned int)vp->reg.loopstart - 1);
+ EMU8000_PSST_WRITE(hw, vp->ch, temp);
+}
+
+#define MOD_SENSE 18
+
+static void
+set_fmmod(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+ unsigned short fmmod;
+ short pitch;
+ unsigned char cutoff;
+ int modulation;
+
+ pitch = (char)(vp->reg.parm.fmmod>>8);
+ cutoff = (vp->reg.parm.fmmod & 0xff);
+ modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+ pitch += (MOD_SENSE * modulation) / 1200;
+ LIMITVALUE(pitch, -128, 127);
+ fmmod = ((unsigned char)pitch<<8) | cutoff;
+ EMU8000_FMMOD_WRITE(hw, vp->ch, fmmod);
+}
+
+/* set tremolo (lfo1) volume & frequency */
+static void
+set_tremfreq(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+ EMU8000_TREMFRQ_WRITE(hw, vp->ch, vp->reg.parm.tremfrq);
+}
+
+/* set lfo2 pitch & frequency */
+static void
+set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+ unsigned short fm2frq2;
+ short pitch;
+ unsigned char freq;
+ int modulation;
+
+ pitch = (char)(vp->reg.parm.fm2frq2>>8);
+ freq = vp->reg.parm.fm2frq2 & 0xff;
+ modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+ pitch += (MOD_SENSE * modulation) / 1200;
+ LIMITVALUE(pitch, -128, 127);
+ fm2frq2 = ((unsigned char)pitch<<8) | freq;
+ EMU8000_FM2FRQ2_WRITE(hw, vp->ch, fm2frq2);
+}
+
+/* set filterQ */
+static void
+set_filterQ(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+ unsigned int addr;
+ addr = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
+ addr |= (vp->reg.parm.filterQ << 28);
+ EMU8000_CCCA_WRITE(hw, vp->ch, addr);
+}
+
+/*
+ * set the envelope & LFO parameters to the default values
+ */
+static void
+snd_emu8000_tweak_voice(struct snd_emu8000 *emu, int i)
+{
+ /* set all mod/vol envelope shape to minimum */
+ EMU8000_ENVVOL_WRITE(emu, i, 0x8000);
+ EMU8000_ENVVAL_WRITE(emu, i, 0x8000);
+ EMU8000_DCYSUS_WRITE(emu, i, 0x7F7F);
+ EMU8000_ATKHLDV_WRITE(emu, i, 0x7F7F);
+ EMU8000_ATKHLD_WRITE(emu, i, 0x7F7F);
+ EMU8000_PEFE_WRITE(emu, i, 0); /* mod envelope height to zero */
+ EMU8000_LFO1VAL_WRITE(emu, i, 0x8000); /* no delay for LFO1 */
+ EMU8000_LFO2VAL_WRITE(emu, i, 0x8000);
+ EMU8000_IP_WRITE(emu, i, 0xE000); /* no pitch shift */
+ EMU8000_IFATN_WRITE(emu, i, 0xFF00); /* volume to minimum */
+ EMU8000_FMMOD_WRITE(emu, i, 0);
+ EMU8000_TREMFRQ_WRITE(emu, i, 0);
+ EMU8000_FM2FRQ2_WRITE(emu, i, 0);
+}
+
+/*
+ * sysex callback
+ */
+static void
+sysex(struct snd_emux *emu, char *buf, int len, int parsed, struct snd_midi_channel_set *chset)
+{
+ struct snd_emu8000 *hw;
+
+ hw = emu->hw;
+
+ switch (parsed) {
+ case SNDRV_MIDI_SYSEX_GS_CHORUS_MODE:
+ hw->chorus_mode = chset->gs_chorus_mode;
+ snd_emu8000_update_chorus_mode(hw);
+ break;
+
+ case SNDRV_MIDI_SYSEX_GS_REVERB_MODE:
+ hw->reverb_mode = chset->gs_reverb_mode;
+ snd_emu8000_update_reverb_mode(hw);
+ break;
+ }
+}
+
+
+#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
+/*
+ * OSS ioctl callback
+ */
+static int
+oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2)
+{
+ struct snd_emu8000 *hw;
+
+ hw = emu->hw;
+
+ switch (cmd) {
+ case _EMUX_OSS_REVERB_MODE:
+ hw->reverb_mode = p1;
+ snd_emu8000_update_reverb_mode(hw);
+ break;
+
+ case _EMUX_OSS_CHORUS_MODE:
+ hw->chorus_mode = p1;
+ snd_emu8000_update_chorus_mode(hw);
+ break;
+
+ case _EMUX_OSS_INITIALIZE_CHIP:
+ /* snd_emu8000_init(hw); */ /*ignored*/
+ break;
+
+ case _EMUX_OSS_EQUALIZER:
+ hw->bass_level = p1;
+ hw->treble_level = p2;
+ snd_emu8000_update_equalizer(hw);
+ break;
+ }
+ return 0;
+}
+#endif
+
+
+/*
+ * additional patch keys
+ */
+
+#define SNDRV_EMU8000_LOAD_CHORUS_FX 0x10 /* optarg=mode */
+#define SNDRV_EMU8000_LOAD_REVERB_FX 0x11 /* optarg=mode */
+
+
+/*
+ * callback routine
+ */
+
+static int
+load_fx(struct snd_emux *emu, int type, int mode, const void __user *buf, long len)
+{
+ struct snd_emu8000 *hw;
+ hw = emu->hw;
+
+ /* skip header */
+ buf += 16;
+ len -= 16;
+
+ switch (type) {
+ case SNDRV_EMU8000_LOAD_CHORUS_FX:
+ return snd_emu8000_load_chorus_fx(hw, mode, buf, len);
+ case SNDRV_EMU8000_LOAD_REVERB_FX:
+ return snd_emu8000_load_reverb_fx(hw, mode, buf, len);
+ }
+ return -EINVAL;
+}
+
diff --git a/sound/isa/sb/emu8000_local.h b/sound/isa/sb/emu8000_local.h
new file mode 100644
index 0000000000..f90526ab1a
--- /dev/null
+++ b/sound/isa/sb/emu8000_local.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __EMU8000_LOCAL_H
+#define __EMU8000_LOCAL_H
+/*
+ * Local defininitons for the emu8000 (AWE32/64)
+ *
+ * Copyright (C) 1999 Steve Ratcliffe
+ * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/emu8000.h>
+#include <sound/emu8000_reg.h>
+
+/* emu8000_patch.c */
+int snd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
+ struct snd_util_memhdr *hdr,
+ const void __user *data, long count);
+int snd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
+ struct snd_util_memhdr *hdr);
+void snd_emu8000_sample_reset(struct snd_emux *rec);
+
+/* emu8000_callback.c */
+void snd_emu8000_ops_setup(struct snd_emu8000 *emu);
+
+/* emu8000_pcm.c */
+int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index);
+
+#endif /* __EMU8000_LOCAL_H */
diff --git a/sound/isa/sb/emu8000_patch.c b/sound/isa/sb/emu8000_patch.c
new file mode 100644
index 0000000000..8c1e7f2bfc
--- /dev/null
+++ b/sound/isa/sb/emu8000_patch.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Patch routines for the emu8000 (AWE32/64)
+ *
+ * Copyright (C) 1999 Steve Ratcliffe
+ * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include "emu8000_local.h"
+
+#include <linux/sched/signal.h>
+#include <linux/uaccess.h>
+#include <linux/moduleparam.h>
+
+static int emu8000_reset_addr;
+module_param(emu8000_reset_addr, int, 0444);
+MODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)");
+
+
+/*
+ * Open up channels.
+ */
+static int
+snd_emu8000_open_dma(struct snd_emu8000 *emu, int write)
+{
+ int i;
+
+ /* reserve all 30 voices for loading */
+ for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
+ snd_emux_lock_voice(emu->emu, i);
+ snd_emu8000_dma_chan(emu, i, write);
+ }
+
+ /* assign voice 31 and 32 to ROM */
+ EMU8000_VTFT_WRITE(emu, 30, 0);
+ EMU8000_PSST_WRITE(emu, 30, 0x1d8);
+ EMU8000_CSL_WRITE(emu, 30, 0x1e0);
+ EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
+ EMU8000_VTFT_WRITE(emu, 31, 0);
+ EMU8000_PSST_WRITE(emu, 31, 0x1d8);
+ EMU8000_CSL_WRITE(emu, 31, 0x1e0);
+ EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
+
+ return 0;
+}
+
+/*
+ * Close all dram channels.
+ */
+static void
+snd_emu8000_close_dma(struct snd_emu8000 *emu)
+{
+ int i;
+
+ for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
+ snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
+ snd_emux_unlock_voice(emu->emu, i);
+ }
+}
+
+/*
+ */
+
+#define BLANK_LOOP_START 4
+#define BLANK_LOOP_END 8
+#define BLANK_LOOP_SIZE 12
+#define BLANK_HEAD_SIZE 48
+
+/*
+ * Read a word from userland, taking care of conversions from
+ * 8bit samples etc.
+ */
+static unsigned short
+read_word(const void __user *buf, int offset, int mode)
+{
+ unsigned short c;
+ if (mode & SNDRV_SFNT_SAMPLE_8BITS) {
+ unsigned char cc;
+ get_user(cc, (unsigned char __user *)buf + offset);
+ c = cc << 8; /* convert 8bit -> 16bit */
+ } else {
+#ifdef SNDRV_LITTLE_ENDIAN
+ get_user(c, (unsigned short __user *)buf + offset);
+#else
+ unsigned short cc;
+ get_user(cc, (unsigned short __user *)buf + offset);
+ c = swab16(cc);
+#endif
+ }
+ if (mode & SNDRV_SFNT_SAMPLE_UNSIGNED)
+ c ^= 0x8000; /* unsigned -> signed */
+ return c;
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(struct snd_emu8000 *emu)
+{
+ while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+ schedule_timeout_interruptible(1);
+ if (signal_pending(current))
+ break;
+ }
+}
+
+/*
+ * write sample word data
+ *
+ * You should not have to keep resetting the address each time
+ * as the chip is supposed to step on the next address automatically.
+ * It mostly does, but during writes of some samples at random it
+ * completely loses words (every one in 16 roughly but with no
+ * obvious pattern).
+ *
+ * This is therefore much slower than need be, but is at least
+ * working.
+ */
+static inline void
+write_word(struct snd_emu8000 *emu, int *offset, unsigned short data)
+{
+ if (emu8000_reset_addr) {
+ if (emu8000_reset_addr > 1)
+ snd_emu8000_write_wait(emu);
+ EMU8000_SMALW_WRITE(emu, *offset);
+ }
+ EMU8000_SMLD_WRITE(emu, data);
+ *offset += 1;
+}
+
+/*
+ * Write the sample to EMU800 memory. This routine is invoked out of
+ * the generic soundfont routines as a callback.
+ */
+int
+snd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
+ struct snd_util_memhdr *hdr,
+ const void __user *data, long count)
+{
+ int i;
+ int rc;
+ int offset;
+ int truesize;
+ int dram_offset, dram_start;
+ struct snd_emu8000 *emu;
+
+ emu = rec->hw;
+ if (snd_BUG_ON(!sp))
+ return -EINVAL;
+
+ if (sp->v.size == 0)
+ return 0;
+
+ /* be sure loop points start < end */
+ if (sp->v.loopstart > sp->v.loopend)
+ swap(sp->v.loopstart, sp->v.loopend);
+
+ /* compute true data size to be loaded */
+ truesize = sp->v.size;
+ if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
+ truesize += sp->v.loopend - sp->v.loopstart;
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
+ truesize += BLANK_LOOP_SIZE;
+
+ sp->block = snd_util_mem_alloc(hdr, truesize * 2);
+ if (sp->block == NULL) {
+ /*snd_printd("EMU8000: out of memory\n");*/
+ /* not ENOMEM (for compatibility) */
+ return -ENOSPC;
+ }
+
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) {
+ if (!access_ok(data, sp->v.size))
+ return -EFAULT;
+ } else {
+ if (!access_ok(data, sp->v.size * 2))
+ return -EFAULT;
+ }
+
+ /* recalculate address offset */
+ sp->v.end -= sp->v.start;
+ sp->v.loopstart -= sp->v.start;
+ sp->v.loopend -= sp->v.start;
+ sp->v.start = 0;
+
+ /* dram position (in word) -- mem_offset is byte */
+ dram_offset = EMU8000_DRAM_OFFSET + (sp->block->offset >> 1);
+ dram_start = dram_offset;
+
+ /* set the total size (store onto obsolete checksum value) */
+ sp->v.truesize = truesize * 2; /* in bytes */
+
+ snd_emux_terminate_all(emu->emu);
+ rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE);
+ if (rc)
+ return rc;
+
+ /* Set the address to start writing at */
+ snd_emu8000_write_wait(emu);
+ EMU8000_SMALW_WRITE(emu, dram_offset);
+
+ /*snd_emu8000_init_fm(emu);*/
+
+#if 0
+ /* first block - write 48 samples for silence */
+ if (! sp->block->offset) {
+ for (i = 0; i < BLANK_HEAD_SIZE; i++) {
+ write_word(emu, &dram_offset, 0);
+ }
+ }
+#endif
+
+ offset = 0;
+ for (i = 0; i < sp->v.size; i++) {
+ unsigned short s;
+
+ s = read_word(data, offset, sp->v.mode_flags);
+ offset++;
+ write_word(emu, &dram_offset, s);
+
+ /* we may take too long time in this loop.
+ * so give controls back to kernel if needed.
+ */
+ cond_resched();
+
+ if (i == sp->v.loopend &&
+ (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)))
+ {
+ int looplen = sp->v.loopend - sp->v.loopstart;
+ int k;
+
+ /* copy reverse loop */
+ for (k = 1; k <= looplen; k++) {
+ s = read_word(data, offset - k, sp->v.mode_flags);
+ write_word(emu, &dram_offset, s);
+ }
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
+ sp->v.loopend += looplen;
+ } else {
+ sp->v.loopstart += looplen;
+ sp->v.loopend += looplen;
+ }
+ sp->v.end += looplen;
+ }
+ }
+
+ /* if no blank loop is attached in the sample, add it */
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
+ for (i = 0; i < BLANK_LOOP_SIZE; i++) {
+ write_word(emu, &dram_offset, 0);
+ }
+ if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
+ sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
+ sp->v.loopend = sp->v.end + BLANK_LOOP_END;
+ }
+ }
+
+ /* add dram offset */
+ sp->v.start += dram_start;
+ sp->v.end += dram_start;
+ sp->v.loopstart += dram_start;
+ sp->v.loopend += dram_start;
+
+ snd_emu8000_close_dma(emu);
+ snd_emu8000_init_fm(emu);
+
+ return 0;
+}
+
+/*
+ * free a sample block
+ */
+int
+snd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
+ struct snd_util_memhdr *hdr)
+{
+ if (sp->block) {
+ snd_util_mem_free(hdr, sp->block);
+ sp->block = NULL;
+ }
+ return 0;
+}
+
+
+/*
+ * sample_reset callback - terminate voices
+ */
+void
+snd_emu8000_sample_reset(struct snd_emux *rec)
+{
+ snd_emux_terminate_all(rec);
+}
diff --git a/sound/isa/sb/emu8000_pcm.c b/sound/isa/sb/emu8000_pcm.c
new file mode 100644
index 0000000000..9234d4fe8a
--- /dev/null
+++ b/sound/isa/sb/emu8000_pcm.c
@@ -0,0 +1,655 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * pcm emulation on emu8000 wavetable
+ *
+ * Copyright (C) 2002 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include "emu8000_local.h"
+
+#include <linux/sched/signal.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+/*
+ * define the following if you want to use this pcm with non-interleaved mode
+ */
+/* #define USE_NONINTERLEAVE */
+
+/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set
+ * mmap_emulation flag to 1 in your .asoundrc, such like
+ *
+ * pcm.emu8k {
+ * type plug
+ * slave.pcm {
+ * type hw
+ * card 0
+ * device 1
+ * mmap_emulation 1
+ * }
+ * }
+ *
+ * besides, for the time being, the non-interleaved mode doesn't work well on
+ * alsa-lib...
+ */
+
+
+struct snd_emu8k_pcm {
+ struct snd_emu8000 *emu;
+ struct snd_pcm_substream *substream;
+
+ unsigned int allocated_bytes;
+ struct snd_util_memblk *block;
+ unsigned int offset;
+ unsigned int buf_size;
+ unsigned int period_size;
+ unsigned int loop_start[2];
+ unsigned int pitch;
+ int panning[2];
+ int last_ptr;
+ int period_pos;
+ int voices;
+ unsigned int dram_opened: 1;
+ unsigned int running: 1;
+ unsigned int timer_running: 1;
+ struct timer_list timer;
+ spinlock_t timer_lock;
+};
+
+#define LOOP_BLANK_SIZE 8
+
+
+/*
+ * open up channels for the simultaneous data transfer and playback
+ */
+static int
+emu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels)
+{
+ int i;
+
+ /* reserve up to 2 voices for playback */
+ snd_emux_lock_voice(emu->emu, 0);
+ if (channels > 1)
+ snd_emux_lock_voice(emu->emu, 1);
+
+ /* reserve 28 voices for loading */
+ for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) {
+ unsigned int mode = EMU8000_RAM_WRITE;
+ snd_emux_lock_voice(emu->emu, i);
+#ifndef USE_NONINTERLEAVE
+ if (channels > 1 && (i & 1) != 0)
+ mode |= EMU8000_RAM_RIGHT;
+#endif
+ snd_emu8000_dma_chan(emu, i, mode);
+ }
+
+ /* assign voice 31 and 32 to ROM */
+ EMU8000_VTFT_WRITE(emu, 30, 0);
+ EMU8000_PSST_WRITE(emu, 30, 0x1d8);
+ EMU8000_CSL_WRITE(emu, 30, 0x1e0);
+ EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
+ EMU8000_VTFT_WRITE(emu, 31, 0);
+ EMU8000_PSST_WRITE(emu, 31, 0x1d8);
+ EMU8000_CSL_WRITE(emu, 31, 0x1e0);
+ EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
+
+ return 0;
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule)
+{
+ while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+ if (can_schedule) {
+ schedule_timeout_interruptible(1);
+ if (signal_pending(current))
+ break;
+ }
+ }
+}
+
+/*
+ * close all channels
+ */
+static void
+emu8k_close_dram(struct snd_emu8000 *emu)
+{
+ int i;
+
+ for (i = 0; i < 2; i++)
+ snd_emux_unlock_voice(emu->emu, i);
+ for (; i < EMU8000_DRAM_VOICES; i++) {
+ snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
+ snd_emux_unlock_voice(emu->emu, i);
+ }
+}
+
+/*
+ * convert Hz to AWE32 rate offset (see emux/soundfont.c)
+ */
+
+#define OFFSET_SAMPLERATE 1011119 /* base = 44100 */
+#define SAMPLERATE_RATIO 4096
+
+static int calc_rate_offset(int hz)
+{
+ return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO);
+}
+
+
+/*
+ */
+
+static const struct snd_pcm_hardware emu8k_pcm_hw = {
+#ifdef USE_NONINTERLEAVE
+ .info = SNDRV_PCM_INFO_NONINTERLEAVED,
+#else
+ .info = SNDRV_PCM_INFO_INTERLEAVED,
+#endif
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 1024,
+ .period_bytes_max = (128*1024),
+ .periods_min = 2,
+ .periods_max = 1024,
+ .fifo_size = 0,
+
+};
+
+/*
+ * get the current position at the given channel from CCCA register
+ */
+static inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch)
+{
+ int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff;
+ val -= rec->loop_start[ch] - 1;
+ return val;
+}
+
+
+/*
+ * timer interrupt handler
+ * check the current position and update the period if necessary.
+ */
+static void emu8k_pcm_timer_func(struct timer_list *t)
+{
+ struct snd_emu8k_pcm *rec = from_timer(rec, t, timer);
+ int ptr, delta;
+
+ spin_lock(&rec->timer_lock);
+ /* update the current pointer */
+ ptr = emu8k_get_curpos(rec, 0);
+ if (ptr < rec->last_ptr)
+ delta = ptr + rec->buf_size - rec->last_ptr;
+ else
+ delta = ptr - rec->last_ptr;
+ rec->period_pos += delta;
+ rec->last_ptr = ptr;
+
+ /* reprogram timer */
+ mod_timer(&rec->timer, jiffies + 1);
+
+ /* update period */
+ if (rec->period_pos >= (int)rec->period_size) {
+ rec->period_pos %= rec->period_size;
+ spin_unlock(&rec->timer_lock);
+ snd_pcm_period_elapsed(rec->substream);
+ return;
+ }
+ spin_unlock(&rec->timer_lock);
+}
+
+
+/*
+ * open pcm
+ * creating an instance here
+ */
+static int emu8k_pcm_open(struct snd_pcm_substream *subs)
+{
+ struct snd_emu8000 *emu = snd_pcm_substream_chip(subs);
+ struct snd_emu8k_pcm *rec;
+ struct snd_pcm_runtime *runtime = subs->runtime;
+
+ rec = kzalloc(sizeof(*rec), GFP_KERNEL);
+ if (! rec)
+ return -ENOMEM;
+
+ rec->emu = emu;
+ rec->substream = subs;
+ runtime->private_data = rec;
+
+ spin_lock_init(&rec->timer_lock);
+ timer_setup(&rec->timer, emu8k_pcm_timer_func, 0);
+
+ runtime->hw = emu8k_pcm_hw;
+ runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3;
+ runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2;
+
+ /* use timer to update periods.. (specified in msec) */
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+ DIV_ROUND_UP(1000000, HZ), UINT_MAX);
+
+ return 0;
+}
+
+static int emu8k_pcm_close(struct snd_pcm_substream *subs)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+ kfree(rec);
+ subs->runtime->private_data = NULL;
+ return 0;
+}
+
+/*
+ * calculate pitch target
+ */
+static int calc_pitch_target(int pitch)
+{
+ int 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;
+ return ptarget;
+}
+
+/*
+ * set up the voice
+ */
+static void setup_voice(struct snd_emu8k_pcm *rec, int ch)
+{
+ struct snd_emu8000 *hw = rec->emu;
+ unsigned int temp;
+
+ /* channel to be silent and idle */
+ EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
+ EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
+ EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
+ EMU8000_PTRX_WRITE(hw, ch, 0);
+ EMU8000_CPF_WRITE(hw, ch, 0);
+
+ /* pitch offset */
+ EMU8000_IP_WRITE(hw, ch, rec->pitch);
+ /* set envelope parameters */
+ EMU8000_ENVVAL_WRITE(hw, ch, 0x8000);
+ EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f);
+ EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f);
+ EMU8000_ENVVOL_WRITE(hw, ch, 0x8000);
+ EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f);
+ /* decay/sustain parameter for volume envelope is used
+ for triggerg the voice */
+ /* modulation envelope heights */
+ EMU8000_PEFE_WRITE(hw, ch, 0x0);
+ /* lfo1/2 delay */
+ EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000);
+ EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000);
+ /* lfo1 pitch & cutoff shift */
+ EMU8000_FMMOD_WRITE(hw, ch, 0);
+ /* lfo1 volume & freq */
+ EMU8000_TREMFRQ_WRITE(hw, ch, 0);
+ /* lfo2 pitch & freq */
+ EMU8000_FM2FRQ2_WRITE(hw, ch, 0);
+ /* pan & loop start */
+ temp = rec->panning[ch];
+ temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1);
+ EMU8000_PSST_WRITE(hw, ch, temp);
+ /* chorus & loop end (chorus 8bit, MSB) */
+ temp = 0; // chorus
+ temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1);
+ EMU8000_CSL_WRITE(hw, ch, temp);
+ /* Q & current address (Q 4bit value, MSB) */
+ temp = 0; // filterQ
+ temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1);
+ EMU8000_CCCA_WRITE(hw, ch, temp);
+ /* clear unknown registers */
+ EMU8000_00A0_WRITE(hw, ch, 0);
+ EMU8000_0080_WRITE(hw, ch, 0);
+}
+
+/*
+ * trigger the voice
+ */
+static void start_voice(struct snd_emu8k_pcm *rec, int ch)
+{
+ unsigned long flags;
+ struct snd_emu8000 *hw = rec->emu;
+ unsigned int temp, aux;
+ int pt = calc_pitch_target(rec->pitch);
+
+ /* cutoff and volume */
+ EMU8000_IFATN_WRITE(hw, ch, 0xff00);
+ EMU8000_VTFT_WRITE(hw, ch, 0xffff);
+ EMU8000_CVCF_WRITE(hw, ch, 0xffff);
+ /* trigger envelope */
+ EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f);
+ /* set reverb and pitch target */
+ temp = 0; // reverb
+ if (rec->panning[ch] == 0)
+ aux = 0xff;
+ else
+ aux = (-rec->panning[ch]) & 0xff;
+ temp = (temp << 8) | (pt << 16) | aux;
+ EMU8000_PTRX_WRITE(hw, ch, temp);
+ EMU8000_CPF_WRITE(hw, ch, pt << 16);
+
+ /* start timer */
+ spin_lock_irqsave(&rec->timer_lock, flags);
+ if (! rec->timer_running) {
+ mod_timer(&rec->timer, jiffies + 1);
+ rec->timer_running = 1;
+ }
+ spin_unlock_irqrestore(&rec->timer_lock, flags);
+}
+
+/*
+ * stop the voice immediately
+ */
+static void stop_voice(struct snd_emu8k_pcm *rec, int ch)
+{
+ unsigned long flags;
+ struct snd_emu8000 *hw = rec->emu;
+
+ EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
+
+ /* stop timer */
+ spin_lock_irqsave(&rec->timer_lock, flags);
+ if (rec->timer_running) {
+ del_timer(&rec->timer);
+ rec->timer_running = 0;
+ }
+ spin_unlock_irqrestore(&rec->timer_lock, flags);
+}
+
+static int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+ int ch;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ for (ch = 0; ch < rec->voices; ch++)
+ start_voice(rec, ch);
+ rec->running = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ rec->running = 0;
+ for (ch = 0; ch < rec->voices; ch++)
+ stop_voice(rec, ch);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+
+/*
+ * copy / silence ops
+ */
+
+/*
+ * this macro should be inserted in the copy/silence loops
+ * to reduce the latency. without this, the system will hang up
+ * during the whole loop.
+ */
+#define CHECK_SCHEDULER() \
+do { \
+ cond_resched();\
+ if (signal_pending(current))\
+ return -EAGAIN;\
+} while (0)
+
+#define GET_VAL(sval, iter) \
+ do { \
+ if (!iter) \
+ sval = 0; \
+ else if (copy_from_iter(&sval, 2, iter) != 2) \
+ return -EFAULT; \
+ } while (0)
+
+#ifdef USE_NONINTERLEAVE
+
+#define LOOP_WRITE(rec, offset, iter, count) \
+ do { \
+ struct snd_emu8000 *emu = (rec)->emu; \
+ snd_emu8000_write_wait(emu, 1); \
+ EMU8000_SMALW_WRITE(emu, offset); \
+ while (count > 0) { \
+ unsigned short sval; \
+ CHECK_SCHEDULER(); \
+ GET_VAL(sval, iter); \
+ EMU8000_SMLD_WRITE(emu, sval); \
+ count--; \
+ } \
+ } while (0)
+
+/* copy one channel block */
+static int emu8k_pcm_copy(struct snd_pcm_substream *subs,
+ int voice, unsigned long pos,
+ struct iov_iter *src, unsigned long count)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+ /* convert to word unit */
+ pos = (pos << 1) + rec->loop_start[voice];
+ count <<= 1;
+ LOOP_WRITE(rec, pos, src, count);
+ return 0;
+}
+
+/* make a channel block silence */
+static int emu8k_pcm_silence(struct snd_pcm_substream *subs,
+ int voice, unsigned long pos, unsigned long count)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+ /* convert to word unit */
+ pos = (pos << 1) + rec->loop_start[voice];
+ count <<= 1;
+ LOOP_WRITE(rec, pos, NULL, count);
+ return 0;
+}
+
+#else /* interleave */
+
+#define LOOP_WRITE(rec, pos, iter, count) \
+ do { \
+ struct snd_emu8000 *emu = rec->emu; \
+ snd_emu8000_write_wait(emu, 1); \
+ EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]); \
+ if (rec->voices > 1) \
+ EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]); \
+ while (count > 0) { \
+ unsigned short sval; \
+ CHECK_SCHEDULER(); \
+ GET_VAL(sval, iter); \
+ EMU8000_SMLD_WRITE(emu, sval); \
+ if (rec->voices > 1) { \
+ CHECK_SCHEDULER(); \
+ GET_VAL(sval, iter); \
+ EMU8000_SMRD_WRITE(emu, sval); \
+ } \
+ count--; \
+ } \
+ } while (0)
+
+
+/*
+ * copy the interleaved data can be done easily by using
+ * DMA "left" and "right" channels on emu8k engine.
+ */
+static int emu8k_pcm_copy(struct snd_pcm_substream *subs,
+ int voice, unsigned long pos,
+ struct iov_iter *src, unsigned long count)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+ /* convert to frames */
+ pos = bytes_to_frames(subs->runtime, pos);
+ count = bytes_to_frames(subs->runtime, count);
+ LOOP_WRITE(rec, pos, src, count);
+ return 0;
+}
+
+static int emu8k_pcm_silence(struct snd_pcm_substream *subs,
+ int voice, unsigned long pos, unsigned long count)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+ /* convert to frames */
+ pos = bytes_to_frames(subs->runtime, pos);
+ count = bytes_to_frames(subs->runtime, count);
+ LOOP_WRITE(rec, pos, NULL, count);
+ return 0;
+}
+#endif
+
+
+/*
+ * allocate a memory block
+ */
+static int emu8k_pcm_hw_params(struct snd_pcm_substream *subs,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+ if (rec->block) {
+ /* reallocation - release the old block */
+ snd_util_mem_free(rec->emu->memhdr, rec->block);
+ rec->block = NULL;
+ }
+
+ rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4;
+ rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes);
+ if (! rec->block)
+ return -ENOMEM;
+ rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */
+ /* at least dma_bytes must be set for non-interleaved mode */
+ subs->dma_buffer.bytes = params_buffer_bytes(hw_params);
+
+ return 0;
+}
+
+/*
+ * free the memory block
+ */
+static int emu8k_pcm_hw_free(struct snd_pcm_substream *subs)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+ if (rec->block) {
+ int ch;
+ for (ch = 0; ch < rec->voices; ch++)
+ stop_voice(rec, ch); // to be sure
+ if (rec->dram_opened)
+ emu8k_close_dram(rec->emu);
+ snd_util_mem_free(rec->emu->memhdr, rec->block);
+ rec->block = NULL;
+ }
+ return 0;
+}
+
+/*
+ */
+static int emu8k_pcm_prepare(struct snd_pcm_substream *subs)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+ rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate);
+ rec->last_ptr = 0;
+ rec->period_pos = 0;
+
+ rec->buf_size = subs->runtime->buffer_size;
+ rec->period_size = subs->runtime->period_size;
+ rec->voices = subs->runtime->channels;
+ rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE;
+ if (rec->voices > 1)
+ rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE;
+ if (rec->voices > 1) {
+ rec->panning[0] = 0xff;
+ rec->panning[1] = 0x00;
+ } else
+ rec->panning[0] = 0x80;
+
+ if (! rec->dram_opened) {
+ int err, i, ch;
+
+ snd_emux_terminate_all(rec->emu->emu);
+ err = emu8k_open_dram_for_pcm(rec->emu, rec->voices);
+ if (err)
+ return err;
+ rec->dram_opened = 1;
+
+ /* clear loop blanks */
+ snd_emu8000_write_wait(rec->emu, 0);
+ EMU8000_SMALW_WRITE(rec->emu, rec->offset);
+ for (i = 0; i < LOOP_BLANK_SIZE; i++)
+ EMU8000_SMLD_WRITE(rec->emu, 0);
+ for (ch = 0; ch < rec->voices; ch++) {
+ EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size);
+ for (i = 0; i < LOOP_BLANK_SIZE; i++)
+ EMU8000_SMLD_WRITE(rec->emu, 0);
+ }
+ }
+
+ setup_voice(rec, 0);
+ if (rec->voices > 1)
+ setup_voice(rec, 1);
+ return 0;
+}
+
+static snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs)
+{
+ struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+ if (rec->running)
+ return emu8k_get_curpos(rec, 0);
+ return 0;
+}
+
+
+static const struct snd_pcm_ops emu8k_pcm_ops = {
+ .open = emu8k_pcm_open,
+ .close = emu8k_pcm_close,
+ .hw_params = emu8k_pcm_hw_params,
+ .hw_free = emu8k_pcm_hw_free,
+ .prepare = emu8k_pcm_prepare,
+ .trigger = emu8k_pcm_trigger,
+ .pointer = emu8k_pcm_pointer,
+ .copy = emu8k_pcm_copy,
+ .fill_silence = emu8k_pcm_silence,
+};
+
+
+static void snd_emu8000_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_emu8000 *emu = pcm->private_data;
+ emu->pcm = NULL;
+}
+
+int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm);
+ if (err < 0)
+ return err;
+ pcm->private_data = emu;
+ pcm->private_free = snd_emu8000_pcm_free;
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops);
+ emu->pcm = pcm;
+
+ snd_device_register(card, pcm);
+
+ return 0;
+}
diff --git a/sound/isa/sb/emu8000_synth.c b/sound/isa/sb/emu8000_synth.c
new file mode 100644
index 0000000000..0edfb68752
--- /dev/null
+++ b/sound/isa/sb/emu8000_synth.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk>
+ * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ * Emu8000 synth plug-in routine
+ */
+
+#include "emu8000_local.h"
+#include <linux/init.h>
+#include <linux/module.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai, Steve Ratcliffe");
+MODULE_DESCRIPTION("Emu8000 synth plug-in routine");
+MODULE_LICENSE("GPL");
+
+/*----------------------------------------------------------------*/
+
+/*
+ * create a new hardware dependent device for Emu8000
+ */
+static int snd_emu8000_probe(struct device *_dev)
+{
+ struct snd_seq_device *dev = to_seq_dev(_dev);
+ struct snd_emu8000 *hw;
+ struct snd_emux *emu;
+
+ hw = *(struct snd_emu8000**)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+ if (hw == NULL)
+ return -EINVAL;
+
+ if (hw->emu)
+ return -EBUSY; /* already exists..? */
+
+ if (snd_emux_new(&emu) < 0)
+ return -ENOMEM;
+
+ hw->emu = emu;
+ snd_emu8000_ops_setup(hw);
+
+ emu->hw = hw;
+ emu->max_voices = EMU8000_DRAM_VOICES;
+ emu->num_ports = hw->seq_ports;
+
+ if (hw->memhdr) {
+ snd_printk(KERN_ERR "memhdr is already initialized!?\n");
+ snd_util_memhdr_free(hw->memhdr);
+ }
+ hw->memhdr = snd_util_memhdr_new(hw->mem_size);
+ if (hw->memhdr == NULL) {
+ snd_emux_free(emu);
+ hw->emu = NULL;
+ return -ENOMEM;
+ }
+
+ emu->memhdr = hw->memhdr;
+ emu->midi_ports = hw->seq_ports < 2 ? hw->seq_ports : 2; /* number of virmidi ports */
+ emu->midi_devidx = 1;
+ emu->linear_panning = 1;
+ emu->hwdep_idx = 2; /* FIXED */
+
+ if (snd_emux_register(emu, dev->card, hw->index, "Emu8000") < 0) {
+ snd_emux_free(emu);
+ snd_util_memhdr_free(hw->memhdr);
+ hw->emu = NULL;
+ hw->memhdr = NULL;
+ return -ENOMEM;
+ }
+
+ if (hw->mem_size > 0)
+ snd_emu8000_pcm_new(dev->card, hw, 1);
+
+ dev->driver_data = hw;
+
+ return 0;
+}
+
+
+/*
+ * free all resources
+ */
+static int snd_emu8000_remove(struct device *_dev)
+{
+ struct snd_seq_device *dev = to_seq_dev(_dev);
+ struct snd_emu8000 *hw;
+
+ if (dev->driver_data == NULL)
+ return 0; /* no synth was allocated actually */
+
+ hw = dev->driver_data;
+ if (hw->pcm)
+ snd_device_free(dev->card, hw->pcm);
+ snd_emux_free(hw->emu);
+ snd_util_memhdr_free(hw->memhdr);
+ hw->emu = NULL;
+ hw->memhdr = NULL;
+ return 0;
+}
+
+/*
+ * INIT part
+ */
+
+static struct snd_seq_driver emu8000_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .probe = snd_emu8000_probe,
+ .remove = snd_emu8000_remove,
+ },
+ .id = SNDRV_SEQ_DEV_ID_EMU8000,
+ .argsize = sizeof(struct snd_emu8000 *),
+};
+
+module_snd_seq_driver(emu8000_driver);
diff --git a/sound/isa/sb/jazz16.c b/sound/isa/sb/jazz16.c
new file mode 100644
index 0000000000..64936c9171
--- /dev/null
+++ b/sound/isa/sb/jazz16.c
@@ -0,0 +1,370 @@
+
+/*
+ * jazz16.c - driver for Media Vision Jazz16 based soundcards.
+ * Copyright (C) 2009 Krzysztof Helt <krzysztof.h1@wp.pl>
+ * Based on patches posted by Rask Ingemann Lambertsen and Rene Herman.
+ * Based on OSS Sound Blaster driver.
+ *
+ * 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <asm/dma.h>
+#include <linux/isa.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define PFX "jazz16: "
+
+MODULE_DESCRIPTION("Media Vision Jazz16");
+MODULE_AUTHOR("Krzysztof Helt <krzysztof.h1@wp.pl>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static unsigned long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static unsigned long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Media Vision Jazz16 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Media Vision Jazz16 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Media Vision Jazz16 based soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for jazz16 driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for jazz16 driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for jazz16 driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for jazz16 driver.");
+module_param_hw_array(dma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma8, "DMA8 # for jazz16 driver.");
+module_param_hw_array(dma16, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma16, "DMA16 # for jazz16 driver.");
+
+#define SB_JAZZ16_WAKEUP 0xaf
+#define SB_JAZZ16_SET_PORTS 0x50
+#define SB_DSP_GET_JAZZ_BRD_REV 0xfa
+#define SB_JAZZ16_SET_DMAINTR 0xfb
+#define SB_DSP_GET_JAZZ_MODEL 0xfe
+
+struct snd_card_jazz16 {
+ struct snd_sb *chip;
+};
+
+static irqreturn_t jazz16_interrupt(int irq, void *chip)
+{
+ return snd_sb8dsp_interrupt(chip);
+}
+
+static int jazz16_configure_ports(unsigned long port,
+ unsigned long mpu_port, int idx)
+{
+ unsigned char val;
+
+ if (!request_region(0x201, 1, "jazz16 config")) {
+ snd_printk(KERN_ERR "config port region is already in use.\n");
+ return -EBUSY;
+ }
+ outb(SB_JAZZ16_WAKEUP - idx, 0x201);
+ udelay(100);
+ outb(SB_JAZZ16_SET_PORTS + idx, 0x201);
+ udelay(100);
+ val = port & 0x70;
+ val |= (mpu_port & 0x30) >> 4;
+ outb(val, 0x201);
+
+ release_region(0x201, 1);
+ return 0;
+}
+
+static int jazz16_detect_board(unsigned long port,
+ unsigned long mpu_port)
+{
+ int err;
+ int val;
+ struct snd_sb chip;
+
+ if (!request_region(port, 0x10, "jazz16")) {
+ snd_printk(KERN_ERR "I/O port region is already in use.\n");
+ return -EBUSY;
+ }
+ /* just to call snd_sbdsp_command/reset/get_byte() */
+ chip.port = port;
+
+ err = snd_sbdsp_reset(&chip);
+ if (err < 0)
+ for (val = 0; val < 4; val++) {
+ err = jazz16_configure_ports(port, mpu_port, val);
+ if (err < 0)
+ break;
+
+ err = snd_sbdsp_reset(&chip);
+ if (!err)
+ break;
+ }
+ if (err < 0) {
+ err = -ENODEV;
+ goto err_unmap;
+ }
+ if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_BRD_REV)) {
+ err = -EBUSY;
+ goto err_unmap;
+ }
+ val = snd_sbdsp_get_byte(&chip);
+ if (val >= 0x30)
+ snd_sbdsp_get_byte(&chip);
+
+ if ((val & 0xf0) != 0x10) {
+ err = -ENODEV;
+ goto err_unmap;
+ }
+ if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_MODEL)) {
+ err = -EBUSY;
+ goto err_unmap;
+ }
+ snd_sbdsp_get_byte(&chip);
+ err = snd_sbdsp_get_byte(&chip);
+ snd_printd("Media Vision Jazz16 board detected: rev 0x%x, model 0x%x\n",
+ val, err);
+
+ err = 0;
+
+err_unmap:
+ release_region(port, 0x10);
+ return err;
+}
+
+static int jazz16_configure_board(struct snd_sb *chip, int mpu_irq)
+{
+ static const unsigned char jazz_irq_bits[] = { 0, 0, 2, 3, 0, 1, 0, 4,
+ 0, 2, 5, 0, 0, 0, 0, 6 };
+ static const unsigned char jazz_dma_bits[] = { 0, 1, 0, 2, 0, 3, 0, 4 };
+
+ if (jazz_dma_bits[chip->dma8] == 0 ||
+ jazz_dma_bits[chip->dma16] == 0 ||
+ jazz_irq_bits[chip->irq] == 0)
+ return -EINVAL;
+
+ if (!snd_sbdsp_command(chip, SB_JAZZ16_SET_DMAINTR))
+ return -EBUSY;
+
+ if (!snd_sbdsp_command(chip,
+ jazz_dma_bits[chip->dma8] |
+ (jazz_dma_bits[chip->dma16] << 4)))
+ return -EBUSY;
+
+ if (!snd_sbdsp_command(chip,
+ jazz_irq_bits[chip->irq] |
+ (jazz_irq_bits[mpu_irq] << 4)))
+ return -EBUSY;
+
+ return 0;
+}
+
+static int snd_jazz16_match(struct device *devptr, unsigned int dev)
+{
+ if (!enable[dev])
+ return 0;
+ if (port[dev] == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR "please specify port\n");
+ return 0;
+ } else if (port[dev] == 0x200 || (port[dev] & ~0x270)) {
+ snd_printk(KERN_ERR "incorrect port specified\n");
+ return 0;
+ }
+ if (dma8[dev] != SNDRV_AUTO_DMA &&
+ dma8[dev] != 1 && dma8[dev] != 3) {
+ snd_printk(KERN_ERR "dma8 must be 1 or 3\n");
+ return 0;
+ }
+ if (dma16[dev] != SNDRV_AUTO_DMA &&
+ dma16[dev] != 5 && dma16[dev] != 7) {
+ snd_printk(KERN_ERR "dma16 must be 5 or 7\n");
+ return 0;
+ }
+ if (mpu_port[dev] != SNDRV_AUTO_PORT &&
+ (mpu_port[dev] & ~0x030) != 0x300) {
+ snd_printk(KERN_ERR "incorrect mpu_port specified\n");
+ return 0;
+ }
+ if (mpu_irq[dev] != SNDRV_AUTO_DMA &&
+ mpu_irq[dev] != 2 && mpu_irq[dev] != 3 &&
+ mpu_irq[dev] != 5 && mpu_irq[dev] != 7) {
+ snd_printk(KERN_ERR "mpu_irq must be 2, 3, 5 or 7\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int snd_jazz16_probe(struct device *devptr, unsigned int dev)
+{
+ struct snd_card *card;
+ struct snd_card_jazz16 *jazz16;
+ struct snd_sb *chip;
+ struct snd_opl3 *opl3;
+ static const int possible_irqs[] = {2, 3, 5, 7, 9, 10, 15, -1};
+ static const int possible_dmas8[] = {1, 3, -1};
+ static const int possible_dmas16[] = {5, 7, -1};
+ int err, xirq, xdma8, xdma16, xmpu_port, xmpu_irq;
+
+ err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_card_jazz16), &card);
+ if (err < 0)
+ return err;
+
+ jazz16 = card->private_data;
+
+ xirq = irq[dev];
+ if (xirq == SNDRV_AUTO_IRQ) {
+ xirq = snd_legacy_find_free_irq(possible_irqs);
+ if (xirq < 0) {
+ snd_printk(KERN_ERR "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ xdma8 = dma8[dev];
+ if (xdma8 == SNDRV_AUTO_DMA) {
+ xdma8 = snd_legacy_find_free_dma(possible_dmas8);
+ if (xdma8 < 0) {
+ snd_printk(KERN_ERR "unable to find a free DMA8\n");
+ return -EBUSY;
+ }
+ }
+ xdma16 = dma16[dev];
+ if (xdma16 == SNDRV_AUTO_DMA) {
+ xdma16 = snd_legacy_find_free_dma(possible_dmas16);
+ if (xdma16 < 0) {
+ snd_printk(KERN_ERR "unable to find a free DMA16\n");
+ return -EBUSY;
+ }
+ }
+
+ xmpu_port = mpu_port[dev];
+ if (xmpu_port == SNDRV_AUTO_PORT)
+ xmpu_port = 0;
+ err = jazz16_detect_board(port[dev], xmpu_port);
+ if (err < 0) {
+ printk(KERN_ERR "Media Vision Jazz16 board not detected\n");
+ return err;
+ }
+ err = snd_sbdsp_create(card, port[dev], irq[dev],
+ jazz16_interrupt,
+ dma8[dev], dma16[dev],
+ SB_HW_JAZZ16,
+ &chip);
+ if (err < 0)
+ return err;
+
+ xmpu_irq = mpu_irq[dev];
+ if (xmpu_irq == SNDRV_AUTO_IRQ || mpu_port[dev] == SNDRV_AUTO_PORT)
+ xmpu_irq = 0;
+ err = jazz16_configure_board(chip, xmpu_irq);
+ if (err < 0) {
+ printk(KERN_ERR "Media Vision Jazz16 configuration failed\n");
+ return err;
+ }
+
+ jazz16->chip = chip;
+
+ strcpy(card->driver, "jazz16");
+ strcpy(card->shortname, "Media Vision Jazz16");
+ sprintf(card->longname,
+ "Media Vision Jazz16 at 0x%lx, irq %d, dma8 %d, dma16 %d",
+ port[dev], xirq, xdma8, xdma16);
+
+ err = snd_sb8dsp_pcm(chip, 0);
+ if (err < 0)
+ return err;
+ err = snd_sbmixer_new(chip);
+ if (err < 0)
+ return err;
+
+ err = snd_opl3_create(card, chip->port, chip->port + 2,
+ OPL3_HW_AUTO, 1, &opl3);
+ if (err < 0)
+ snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n",
+ chip->port, chip->port + 2);
+ else {
+ err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (err < 0)
+ return err;
+ }
+ if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+ if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+ mpu_irq[dev] = -1;
+
+ if (snd_mpu401_uart_new(card, 0,
+ MPU401_HW_MPU401,
+ mpu_port[dev], 0,
+ mpu_irq[dev],
+ NULL) < 0)
+ snd_printk(KERN_ERR "no MPU-401 device at 0x%lx\n",
+ mpu_port[dev]);
+ }
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+
+ dev_set_drvdata(devptr, card);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_jazz16_suspend(struct device *pdev, unsigned int n,
+ pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(pdev);
+ struct snd_card_jazz16 *acard = card->private_data;
+ struct snd_sb *chip = acard->chip;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ snd_sbmixer_suspend(chip);
+ return 0;
+}
+
+static int snd_jazz16_resume(struct device *pdev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(pdev);
+ struct snd_card_jazz16 *acard = card->private_data;
+ struct snd_sb *chip = acard->chip;
+
+ snd_sbdsp_reset(chip);
+ snd_sbmixer_resume(chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+static struct isa_driver snd_jazz16_driver = {
+ .match = snd_jazz16_match,
+ .probe = snd_jazz16_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_jazz16_suspend,
+ .resume = snd_jazz16_resume,
+#endif
+ .driver = {
+ .name = "jazz16"
+ },
+};
+
+module_isa_driver(snd_jazz16_driver, SNDRV_CARDS);
diff --git a/sound/isa/sb/sb16.c b/sound/isa/sb/sb16.c
new file mode 100644
index 0000000000..e89b095aa2
--- /dev/null
+++ b/sound/isa/sb/sb16.c
@@ -0,0 +1,657 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for SoundBlaster 16/AWE32/AWE64 soundcards
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/pnp.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/sb16_csp.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/emu8000.h>
+#include <sound/seq_device.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#ifdef SNDRV_SBAWE
+#define PFX "sbawe: "
+#else
+#define PFX "sb16: "
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+#ifndef SNDRV_SBAWE
+MODULE_DESCRIPTION("Sound Blaster 16");
+#else
+MODULE_DESCRIPTION("Sound Blaster AWE");
+#endif
+
+#if 0
+#define SNDRV_DEBUG_IRQ
+#endif
+
+#if defined(SNDRV_SBAWE) && IS_ENABLED(CONFIG_SND_SEQUENCER)
+#define SNDRV_SBAWE_EMU8000
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260,0x280 */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x330,0x300 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#ifdef SNDRV_SBAWE_EMU8000
+static long awe_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#endif
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 5,6,7 */
+static int mic_agc[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#ifdef CONFIG_SND_SB16_CSP
+static int csp[SNDRV_CARDS];
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for SoundBlaster 16 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for SoundBlaster 16 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable SoundBlaster 16 soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB16 driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for SB16 driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for SB16 PnP driver.");
+#ifdef SNDRV_SBAWE_EMU8000
+module_param_hw_array(awe_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(awe_port, "AWE port # for SB16 PnP driver.");
+#endif
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB16 driver.");
+module_param_hw_array(dma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for SB16 driver.");
+module_param_hw_array(dma16, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma16, "16-bit DMA # for SB16 driver.");
+module_param_array(mic_agc, int, NULL, 0444);
+MODULE_PARM_DESC(mic_agc, "Mic Auto-Gain-Control switch.");
+#ifdef CONFIG_SND_SB16_CSP
+module_param_array(csp, int, NULL, 0444);
+MODULE_PARM_DESC(csp, "ASP/CSP chip support.");
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+module_param_array(seq_ports, int, NULL, 0444);
+MODULE_PARM_DESC(seq_ports, "Number of sequencer ports for WaveTable synth.");
+#endif
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+#endif
+
+struct snd_card_sb16 {
+ struct resource *fm_res; /* used to block FM i/o region for legacy cards */
+ struct snd_sb *chip;
+#ifdef CONFIG_PNP
+ int dev_no;
+ struct pnp_dev *dev;
+#ifdef SNDRV_SBAWE_EMU8000
+ struct pnp_dev *devwt;
+#endif
+#endif
+};
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_card_device_id snd_sb16_pnpids[] = {
+#ifndef SNDRV_SBAWE
+ /* Sound Blaster 16 PnP */
+ { .id = "CTL0024", .devs = { { "CTL0031" } } },
+ /* Sound Blaster 16 PnP */
+ { .id = "CTL0025", .devs = { { "CTL0031" } } },
+ /* Sound Blaster 16 PnP */
+ { .id = "CTL0026", .devs = { { "CTL0031" } } },
+ /* Sound Blaster 16 PnP */
+ { .id = "CTL0027", .devs = { { "CTL0031" } } },
+ /* Sound Blaster 16 PnP */
+ { .id = "CTL0028", .devs = { { "CTL0031" } } },
+ /* Sound Blaster 16 PnP */
+ { .id = "CTL0029", .devs = { { "CTL0031" } } },
+ /* Sound Blaster 16 PnP */
+ { .id = "CTL002a", .devs = { { "CTL0031" } } },
+ /* Sound Blaster 16 PnP */
+ /* Note: This card has also a CTL0051:StereoEnhance device!!! */
+ { .id = "CTL002b", .devs = { { "CTL0031" } } },
+ /* Sound Blaster 16 PnP */
+ { .id = "CTL002c", .devs = { { "CTL0031" } } },
+ /* Sound Blaster Vibra16S */
+ { .id = "CTL0051", .devs = { { "CTL0001" } } },
+ /* Sound Blaster Vibra16C */
+ { .id = "CTL0070", .devs = { { "CTL0001" } } },
+ /* Sound Blaster Vibra16CL - added by ctm@ardi.com */
+ { .id = "CTL0080", .devs = { { "CTL0041" } } },
+ /* Sound Blaster 16 'value' PnP. It says model ct4130 on the pcb, */
+ /* but ct4131 on a sticker on the board.. */
+ { .id = "CTL0086", .devs = { { "CTL0041" } } },
+ /* Sound Blaster Vibra16X */
+ { .id = "CTL00f0", .devs = { { "CTL0043" } } },
+ /* Sound Blaster 16 (Virtual PC 2004) */
+ { .id = "tBA03b0", .devs = { {.id="PNPb003" } } },
+#else /* SNDRV_SBAWE defined */
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL0035", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL0039", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL0042", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL0043", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ /* Note: This card has also a CTL0051:StereoEnhance device!!! */
+ { .id = "CTL0044", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ /* Note: This card has also a CTL0051:StereoEnhance device!!! */
+ { .id = "CTL0045", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL0046", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL0047", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL0048", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL0054", .devs = { { "CTL0031" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL009a", .devs = { { "CTL0041" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 32 PnP */
+ { .id = "CTL009c", .devs = { { "CTL0041" }, { "CTL0021" } } },
+ /* Sound Blaster 32 PnP */
+ { .id = "CTL009f", .devs = { { "CTL0041" }, { "CTL0021" } } },
+ /* Sound Blaster AWE 64 PnP */
+ { .id = "CTL009d", .devs = { { "CTL0042" }, { "CTL0022" } } },
+ /* Sound Blaster AWE 64 PnP Gold */
+ { .id = "CTL009e", .devs = { { "CTL0044" }, { "CTL0023" } } },
+ /* Sound Blaster AWE 64 PnP Gold */
+ { .id = "CTL00b2", .devs = { { "CTL0044" }, { "CTL0023" } } },
+ /* Sound Blaster AWE 64 PnP */
+ { .id = "CTL00c1", .devs = { { "CTL0042" }, { "CTL0022" } } },
+ /* Sound Blaster AWE 64 PnP */
+ { .id = "CTL00c3", .devs = { { "CTL0045" }, { "CTL0022" } } },
+ /* Sound Blaster AWE 64 PnP */
+ { .id = "CTL00c5", .devs = { { "CTL0045" }, { "CTL0022" } } },
+ /* Sound Blaster AWE 64 PnP */
+ { .id = "CTL00c7", .devs = { { "CTL0045" }, { "CTL0022" } } },
+ /* Sound Blaster AWE 64 PnP */
+ { .id = "CTL00e4", .devs = { { "CTL0045" }, { "CTL0022" } } },
+ /* Sound Blaster AWE 64 PnP */
+ { .id = "CTL00e9", .devs = { { "CTL0045" }, { "CTL0022" } } },
+ /* Sound Blaster 16 PnP (AWE) */
+ { .id = "CTL00ed", .devs = { { "CTL0041" }, { "CTL0070" } } },
+ /* Generic entries */
+ { .id = "CTLXXXX" , .devs = { { "CTL0031" }, { "CTL0021" } } },
+ { .id = "CTLXXXX" , .devs = { { "CTL0041" }, { "CTL0021" } } },
+ { .id = "CTLXXXX" , .devs = { { "CTL0042" }, { "CTL0022" } } },
+ { .id = "CTLXXXX" , .devs = { { "CTL0044" }, { "CTL0023" } } },
+ { .id = "CTLXXXX" , .devs = { { "CTL0045" }, { "CTL0022" } } },
+#endif /* SNDRV_SBAWE */
+ { .id = "", }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_sb16_pnpids);
+
+#endif /* CONFIG_PNP */
+
+#ifdef SNDRV_SBAWE_EMU8000
+#define DRIVER_NAME "snd-card-sbawe"
+#else
+#define DRIVER_NAME "snd-card-sb16"
+#endif
+
+#ifdef CONFIG_PNP
+
+static int snd_card_sb16_pnp(int dev, struct snd_card_sb16 *acard,
+ struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ struct pnp_dev *pdev;
+ int err;
+
+ acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (acard->dev == NULL)
+ return -ENODEV;
+
+#ifdef SNDRV_SBAWE_EMU8000
+ acard->devwt = pnp_request_card_device(card, id->devs[1].id, acard->dev);
+#endif
+ /* Audio initialization */
+ pdev = acard->dev;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+ return err;
+ }
+ port[dev] = pnp_port_start(pdev, 0);
+ mpu_port[dev] = pnp_port_start(pdev, 1);
+ fm_port[dev] = pnp_port_start(pdev, 2);
+ dma8[dev] = pnp_dma(pdev, 0);
+ dma16[dev] = pnp_dma(pdev, 1);
+ irq[dev] = pnp_irq(pdev, 0);
+ snd_printdd("pnp SB16: port=0x%lx, mpu port=0x%lx, fm port=0x%lx\n",
+ port[dev], mpu_port[dev], fm_port[dev]);
+ snd_printdd("pnp SB16: dma1=%i, dma2=%i, irq=%i\n",
+ dma8[dev], dma16[dev], irq[dev]);
+#ifdef SNDRV_SBAWE_EMU8000
+ /* WaveTable initialization */
+ pdev = acard->devwt;
+ if (pdev != NULL) {
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ goto __wt_error;
+ }
+ awe_port[dev] = pnp_port_start(pdev, 0);
+ snd_printdd("pnp SB16: wavetable port=0x%llx\n",
+ (unsigned long long)pnp_port_start(pdev, 0));
+ } else {
+__wt_error:
+ if (pdev) {
+ pnp_release_card_device(pdev);
+ snd_printk(KERN_ERR PFX "WaveTable pnp configure failure\n");
+ }
+ acard->devwt = NULL;
+ awe_port[dev] = -1;
+ }
+#endif
+ return 0;
+}
+
+#endif /* CONFIG_PNP */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev) isapnp[dev]
+#else
+#define is_isapnp_selected(dev) 0
+#endif
+
+static int snd_sb16_card_new(struct device *devptr, int dev,
+ struct snd_card **cardp)
+{
+ struct snd_card *card;
+ int err;
+
+ err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_card_sb16), &card);
+ if (err < 0)
+ return err;
+ *cardp = card;
+ return 0;
+}
+
+static int snd_sb16_probe(struct snd_card *card, int dev)
+{
+ int xirq, xdma8, xdma16;
+ struct snd_sb *chip;
+ struct snd_card_sb16 *acard = card->private_data;
+ struct snd_opl3 *opl3;
+ struct snd_hwdep *synth = NULL;
+#ifdef CONFIG_SND_SB16_CSP
+ struct snd_hwdep *xcsp = NULL;
+#endif
+ unsigned long flags;
+ int err;
+
+ xirq = irq[dev];
+ xdma8 = dma8[dev];
+ xdma16 = dma16[dev];
+
+ err = snd_sbdsp_create(card, port[dev], xirq, snd_sb16dsp_interrupt,
+ xdma8, xdma16, SB_HW_AUTO, &chip);
+ if (err < 0)
+ return err;
+
+ acard->chip = chip;
+ if (chip->hardware != SB_HW_16) {
+ snd_printk(KERN_ERR PFX "SB 16 chip was not detected at 0x%lx\n", port[dev]);
+ return -ENODEV;
+ }
+ chip->mpu_port = mpu_port[dev];
+ if (!is_isapnp_selected(dev)) {
+ err = snd_sb16dsp_configure(chip);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_sb16dsp_pcm(chip, 0);
+ if (err < 0)
+ return err;
+
+ strcpy(card->driver,
+#ifdef SNDRV_SBAWE_EMU8000
+ awe_port[dev] > 0 ? "SB AWE" :
+#endif
+ "SB16");
+ strcpy(card->shortname, chip->name);
+ sprintf(card->longname, "%s at 0x%lx, irq %i, dma ",
+ chip->name,
+ chip->port,
+ xirq);
+ if (xdma8 >= 0)
+ sprintf(card->longname + strlen(card->longname), "%d", xdma8);
+ if (xdma16 >= 0)
+ sprintf(card->longname + strlen(card->longname), "%s%d",
+ xdma8 >= 0 ? "&" : "", xdma16);
+
+ if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) {
+ err = snd_mpu401_uart_new(card, 0, MPU401_HW_SB,
+ chip->mpu_port,
+ MPU401_INFO_IRQ_HOOK, -1,
+ &chip->rmidi);
+ if (err < 0)
+ return err;
+ chip->rmidi_callback = snd_mpu401_uart_interrupt;
+ }
+
+#ifdef SNDRV_SBAWE_EMU8000
+ if (awe_port[dev] == SNDRV_AUTO_PORT)
+ awe_port[dev] = 0; /* disable */
+#endif
+
+ if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+ if (snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+ OPL3_HW_OPL3,
+ acard->fm_res != NULL || fm_port[dev] == port[dev],
+ &opl3) < 0) {
+ snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+ fm_port[dev], fm_port[dev] + 2);
+ } else {
+#ifdef SNDRV_SBAWE_EMU8000
+ int seqdev = awe_port[dev] > 0 ? 2 : 1;
+#else
+ int seqdev = 1;
+#endif
+ err = snd_opl3_hwdep_new(opl3, 0, seqdev, &synth);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ err = snd_sbmixer_new(chip);
+ if (err < 0)
+ return err;
+
+#ifdef CONFIG_SND_SB16_CSP
+ /* CSP chip on SB16ASP/AWE32 */
+ if ((chip->hardware == SB_HW_16) && csp[dev]) {
+ snd_sb_csp_new(chip, synth != NULL ? 1 : 0, &xcsp);
+ if (xcsp) {
+ chip->csp = xcsp->private_data;
+ chip->hardware = SB_HW_16CSP;
+ } else {
+ snd_printk(KERN_INFO PFX "warning - CSP chip not detected on soundcard #%i\n", dev + 1);
+ }
+ }
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+ if (awe_port[dev] > 0) {
+ err = snd_emu8000_new(card, 1, awe_port[dev],
+ seq_ports[dev], NULL);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "fatal error - EMU-8000 synthesizer not detected at 0x%lx\n", awe_port[dev]);
+
+ return err;
+ }
+ }
+#endif
+
+ /* setup Mic AGC */
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ snd_sbmixer_write(chip, SB_DSP4_MIC_AGC,
+ (snd_sbmixer_read(chip, SB_DSP4_MIC_AGC) & 0x01) |
+ (mic_agc[dev] ? 0x00 : 0x01));
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_sb16_suspend(struct snd_card *card, pm_message_t state)
+{
+ struct snd_card_sb16 *acard = card->private_data;
+ struct snd_sb *chip = acard->chip;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ snd_sbmixer_suspend(chip);
+ return 0;
+}
+
+static int snd_sb16_resume(struct snd_card *card)
+{
+ struct snd_card_sb16 *acard = card->private_data;
+ struct snd_sb *chip = acard->chip;
+
+ snd_sbdsp_reset(chip);
+ snd_sbmixer_resume(chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+static int snd_sb16_isa_probe1(int dev, struct device *pdev)
+{
+ struct snd_card_sb16 *acard;
+ struct snd_card *card;
+ int err;
+
+ err = snd_sb16_card_new(pdev, dev, &card);
+ if (err < 0)
+ return err;
+
+ acard = card->private_data;
+ /* non-PnP FM port address is hardwired with base port address */
+ fm_port[dev] = port[dev];
+ /* block the 0x388 port to avoid PnP conflicts */
+ acard->fm_res = devm_request_region(card->dev, 0x388, 4,
+ "SoundBlaster FM");
+#ifdef SNDRV_SBAWE_EMU8000
+ /* non-PnP AWE port address is hardwired with base port address */
+ awe_port[dev] = port[dev] + 0x400;
+#endif
+
+ err = snd_sb16_probe(card, dev);
+ if (err < 0)
+ return err;
+ dev_set_drvdata(pdev, card);
+ return 0;
+}
+
+
+static int snd_sb16_isa_match(struct device *pdev, unsigned int dev)
+{
+ return enable[dev] && !is_isapnp_selected(dev);
+}
+
+static int snd_sb16_isa_probe(struct device *pdev, unsigned int dev)
+{
+ int err;
+ static const int possible_irqs[] = {5, 9, 10, 7, -1};
+ static const int possible_dmas8[] = {1, 3, 0, -1};
+ static const int possible_dmas16[] = {5, 6, 7, -1};
+
+ if (irq[dev] == SNDRV_AUTO_IRQ) {
+ irq[dev] = snd_legacy_find_free_irq(possible_irqs);
+ if (irq[dev] < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+ if (dma8[dev] == SNDRV_AUTO_DMA) {
+ dma8[dev] = snd_legacy_find_free_dma(possible_dmas8);
+ if (dma8[dev] < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free 8-bit DMA\n");
+ return -EBUSY;
+ }
+ }
+ if (dma16[dev] == SNDRV_AUTO_DMA) {
+ dma16[dev] = snd_legacy_find_free_dma(possible_dmas16);
+ if (dma16[dev] < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free 16-bit DMA\n");
+ return -EBUSY;
+ }
+ }
+
+ if (port[dev] != SNDRV_AUTO_PORT)
+ return snd_sb16_isa_probe1(dev, pdev);
+ else {
+ static const int possible_ports[] = {0x220, 0x240, 0x260, 0x280};
+ int i;
+ for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+ port[dev] = possible_ports[i];
+ err = snd_sb16_isa_probe1(dev, pdev);
+ if (! err)
+ return 0;
+ }
+ return err;
+ }
+}
+
+#ifdef CONFIG_PM
+static int snd_sb16_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_sb16_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_sb16_isa_resume(struct device *dev, unsigned int n)
+{
+ return snd_sb16_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#ifdef SNDRV_SBAWE
+#define DEV_NAME "sbawe"
+#else
+#define DEV_NAME "sb16"
+#endif
+
+static struct isa_driver snd_sb16_isa_driver = {
+ .match = snd_sb16_isa_match,
+ .probe = snd_sb16_isa_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_sb16_isa_suspend,
+ .resume = snd_sb16_isa_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_sb16_pnp_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ static int dev;
+ struct snd_card *card;
+ int res;
+
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (!enable[dev] || !isapnp[dev])
+ continue;
+ res = snd_sb16_card_new(&pcard->card->dev, dev, &card);
+ if (res < 0)
+ return res;
+ res = snd_card_sb16_pnp(dev, card->private_data, pcard, pid);
+ if (res < 0)
+ return res;
+ res = snd_sb16_probe(card, dev);
+ if (res < 0)
+ return res;
+ pnp_set_card_drvdata(pcard, card);
+ dev++;
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+#ifdef CONFIG_PM
+static int snd_sb16_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ return snd_sb16_suspend(pnp_get_card_drvdata(pcard), state);
+}
+static int snd_sb16_pnp_resume(struct pnp_card_link *pcard)
+{
+ return snd_sb16_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver sb16_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+#ifdef SNDRV_SBAWE
+ .name = "sbawe",
+#else
+ .name = "sb16",
+#endif
+ .id_table = snd_sb16_pnpids,
+ .probe = snd_sb16_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_sb16_pnp_suspend,
+ .resume = snd_sb16_pnp_resume,
+#endif
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_sb16_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&snd_sb16_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+
+ err = pnp_register_card_driver(&sb16_pnpc_driver);
+ if (!err)
+ pnp_registered = 1;
+
+ if (isa_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit alsa_card_sb16_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ pnp_unregister_card_driver(&sb16_pnpc_driver);
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&snd_sb16_isa_driver);
+}
+
+module_init(alsa_card_sb16_init)
+module_exit(alsa_card_sb16_exit)
diff --git a/sound/isa/sb/sb16_csp.c b/sound/isa/sb/sb16_csp.c
new file mode 100644
index 0000000000..8d83570197
--- /dev/null
+++ b/sound/isa/sb/sb16_csp.c
@@ -0,0 +1,1187 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 1999 by Uros Bizjak <uros@kss-loka.si>
+ * Takashi Iwai <tiwai@suse.de>
+ *
+ * SB16ASP/AWE32 CSP control
+ *
+ * CSP microcode loader:
+ * alsa-tools/sb16_csp/
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/sb16_csp.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("ALSA driver for SB16 Creative Signal Processor");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("sb16/mulaw_main.csp");
+MODULE_FIRMWARE("sb16/alaw_main.csp");
+MODULE_FIRMWARE("sb16/ima_adpcm_init.csp");
+MODULE_FIRMWARE("sb16/ima_adpcm_playback.csp");
+MODULE_FIRMWARE("sb16/ima_adpcm_capture.csp");
+
+#ifdef SNDRV_LITTLE_ENDIAN
+#define CSP_HDR_VALUE(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
+#else
+#define CSP_HDR_VALUE(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
+#endif
+
+#define RIFF_HEADER CSP_HDR_VALUE('R', 'I', 'F', 'F')
+#define CSP__HEADER CSP_HDR_VALUE('C', 'S', 'P', ' ')
+#define LIST_HEADER CSP_HDR_VALUE('L', 'I', 'S', 'T')
+#define FUNC_HEADER CSP_HDR_VALUE('f', 'u', 'n', 'c')
+#define CODE_HEADER CSP_HDR_VALUE('c', 'o', 'd', 'e')
+#define INIT_HEADER CSP_HDR_VALUE('i', 'n', 'i', 't')
+#define MAIN_HEADER CSP_HDR_VALUE('m', 'a', 'i', 'n')
+
+/*
+ * RIFF data format
+ */
+struct riff_header {
+ __le32 name;
+ __le32 len;
+};
+
+struct desc_header {
+ struct riff_header info;
+ __le16 func_nr;
+ __le16 VOC_type;
+ __le16 flags_play_rec;
+ __le16 flags_16bit_8bit;
+ __le16 flags_stereo_mono;
+ __le16 flags_rates;
+};
+
+/*
+ * prototypes
+ */
+static void snd_sb_csp_free(struct snd_hwdep *hw);
+static int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file);
+static int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg);
+static int snd_sb_csp_release(struct snd_hwdep * hw, struct file *file);
+
+static int csp_detect(struct snd_sb *chip, int *version);
+static int set_codec_parameter(struct snd_sb *chip, unsigned char par, unsigned char val);
+static int set_register(struct snd_sb *chip, unsigned char reg, unsigned char val);
+static int read_register(struct snd_sb *chip, unsigned char reg);
+static int set_mode_register(struct snd_sb *chip, unsigned char mode);
+static int get_version(struct snd_sb *chip);
+
+static int snd_sb_csp_riff_load(struct snd_sb_csp * p,
+ struct snd_sb_csp_microcode __user * code);
+static int snd_sb_csp_unload(struct snd_sb_csp * p);
+static int snd_sb_csp_load_user(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags);
+static int snd_sb_csp_autoload(struct snd_sb_csp * p, snd_pcm_format_t pcm_sfmt, int play_rec_mode);
+static int snd_sb_csp_check_version(struct snd_sb_csp * p);
+
+static int snd_sb_csp_use(struct snd_sb_csp * p);
+static int snd_sb_csp_unuse(struct snd_sb_csp * p);
+static int snd_sb_csp_start(struct snd_sb_csp * p, int sample_width, int channels);
+static int snd_sb_csp_stop(struct snd_sb_csp * p);
+static int snd_sb_csp_pause(struct snd_sb_csp * p);
+static int snd_sb_csp_restart(struct snd_sb_csp * p);
+
+static int snd_sb_qsound_build(struct snd_sb_csp * p);
+static void snd_sb_qsound_destroy(struct snd_sb_csp * p);
+static int snd_sb_csp_qsound_transfer(struct snd_sb_csp * p);
+
+static int init_proc_entry(struct snd_sb_csp * p, int device);
+static void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
+
+/*
+ * Detect CSP chip and create a new instance
+ */
+int snd_sb_csp_new(struct snd_sb *chip, int device, struct snd_hwdep ** rhwdep)
+{
+ struct snd_sb_csp *p;
+ int version;
+ int err;
+ struct snd_hwdep *hw;
+
+ if (rhwdep)
+ *rhwdep = NULL;
+
+ if (csp_detect(chip, &version))
+ return -ENODEV;
+
+ err = snd_hwdep_new(chip->card, "SB16-CSP", device, &hw);
+ if (err < 0)
+ return err;
+
+ p = kzalloc(sizeof(*p), GFP_KERNEL);
+ if (!p) {
+ snd_device_free(chip->card, hw);
+ return -ENOMEM;
+ }
+ p->chip = chip;
+ p->version = version;
+
+ /* CSP operators */
+ p->ops.csp_use = snd_sb_csp_use;
+ p->ops.csp_unuse = snd_sb_csp_unuse;
+ p->ops.csp_autoload = snd_sb_csp_autoload;
+ p->ops.csp_start = snd_sb_csp_start;
+ p->ops.csp_stop = snd_sb_csp_stop;
+ p->ops.csp_qsound_transfer = snd_sb_csp_qsound_transfer;
+
+ mutex_init(&p->access_mutex);
+ sprintf(hw->name, "CSP v%d.%d", (version >> 4), (version & 0x0f));
+ hw->iface = SNDRV_HWDEP_IFACE_SB16CSP;
+ hw->private_data = p;
+ hw->private_free = snd_sb_csp_free;
+
+ /* operators - only write/ioctl */
+ hw->ops.open = snd_sb_csp_open;
+ hw->ops.ioctl = snd_sb_csp_ioctl;
+ hw->ops.release = snd_sb_csp_release;
+
+ /* create a proc entry */
+ init_proc_entry(p, device);
+ if (rhwdep)
+ *rhwdep = hw;
+ return 0;
+}
+
+/*
+ * free_private for hwdep instance
+ */
+static void snd_sb_csp_free(struct snd_hwdep *hwdep)
+{
+ int i;
+ struct snd_sb_csp *p = hwdep->private_data;
+ if (p) {
+ if (p->running & SNDRV_SB_CSP_ST_RUNNING)
+ snd_sb_csp_stop(p);
+ for (i = 0; i < ARRAY_SIZE(p->csp_programs); ++i)
+ release_firmware(p->csp_programs[i]);
+ kfree(p);
+ }
+}
+
+/* ------------------------------ */
+
+/*
+ * open the device exclusively
+ */
+static int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file)
+{
+ struct snd_sb_csp *p = hw->private_data;
+ return (snd_sb_csp_use(p));
+}
+
+/*
+ * ioctl for hwdep device:
+ */
+static int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct snd_sb_csp *p = hw->private_data;
+ struct snd_sb_csp_info info;
+ struct snd_sb_csp_start start_info;
+ int err;
+
+ if (snd_BUG_ON(!p))
+ return -EINVAL;
+
+ if (snd_sb_csp_check_version(p))
+ return -ENODEV;
+
+ switch (cmd) {
+ /* get information */
+ case SNDRV_SB_CSP_IOCTL_INFO:
+ memset(&info, 0, sizeof(info));
+ *info.codec_name = *p->codec_name;
+ info.func_nr = p->func_nr;
+ info.acc_format = p->acc_format;
+ info.acc_channels = p->acc_channels;
+ info.acc_width = p->acc_width;
+ info.acc_rates = p->acc_rates;
+ info.csp_mode = p->mode;
+ info.run_channels = p->run_channels;
+ info.run_width = p->run_width;
+ info.version = p->version;
+ info.state = p->running;
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+ err = -EFAULT;
+ else
+ err = 0;
+ break;
+
+ /* load CSP microcode */
+ case SNDRV_SB_CSP_IOCTL_LOAD_CODE:
+ err = (p->running & SNDRV_SB_CSP_ST_RUNNING ?
+ -EBUSY : snd_sb_csp_riff_load(p, (struct snd_sb_csp_microcode __user *) arg));
+ break;
+ case SNDRV_SB_CSP_IOCTL_UNLOAD_CODE:
+ err = (p->running & SNDRV_SB_CSP_ST_RUNNING ?
+ -EBUSY : snd_sb_csp_unload(p));
+ break;
+
+ /* change CSP running state */
+ case SNDRV_SB_CSP_IOCTL_START:
+ if (copy_from_user(&start_info, (void __user *) arg, sizeof(start_info)))
+ err = -EFAULT;
+ else
+ err = snd_sb_csp_start(p, start_info.sample_width, start_info.channels);
+ break;
+ case SNDRV_SB_CSP_IOCTL_STOP:
+ err = snd_sb_csp_stop(p);
+ break;
+ case SNDRV_SB_CSP_IOCTL_PAUSE:
+ err = snd_sb_csp_pause(p);
+ break;
+ case SNDRV_SB_CSP_IOCTL_RESTART:
+ err = snd_sb_csp_restart(p);
+ break;
+ default:
+ err = -ENOTTY;
+ break;
+ }
+
+ return err;
+}
+
+/*
+ * close the device
+ */
+static int snd_sb_csp_release(struct snd_hwdep * hw, struct file *file)
+{
+ struct snd_sb_csp *p = hw->private_data;
+ return (snd_sb_csp_unuse(p));
+}
+
+/* ------------------------------ */
+
+/*
+ * acquire device
+ */
+static int snd_sb_csp_use(struct snd_sb_csp * p)
+{
+ mutex_lock(&p->access_mutex);
+ if (p->used) {
+ mutex_unlock(&p->access_mutex);
+ return -EAGAIN;
+ }
+ p->used++;
+ mutex_unlock(&p->access_mutex);
+
+ return 0;
+
+}
+
+/*
+ * release device
+ */
+static int snd_sb_csp_unuse(struct snd_sb_csp * p)
+{
+ mutex_lock(&p->access_mutex);
+ p->used--;
+ mutex_unlock(&p->access_mutex);
+
+ return 0;
+}
+
+/*
+ * load microcode via ioctl:
+ * code is user-space pointer
+ */
+static int snd_sb_csp_riff_load(struct snd_sb_csp * p,
+ struct snd_sb_csp_microcode __user * mcode)
+{
+ struct snd_sb_csp_mc_header info;
+
+ unsigned char __user *data_ptr;
+ unsigned char __user *data_end;
+ unsigned short func_nr = 0;
+
+ struct riff_header file_h, item_h, code_h;
+ __le32 item_type;
+ struct desc_header funcdesc_h;
+
+ unsigned long flags;
+ int err;
+
+ if (copy_from_user(&info, mcode, sizeof(info)))
+ return -EFAULT;
+ data_ptr = mcode->data;
+
+ if (copy_from_user(&file_h, data_ptr, sizeof(file_h)))
+ return -EFAULT;
+ if ((le32_to_cpu(file_h.name) != RIFF_HEADER) ||
+ (le32_to_cpu(file_h.len) >= SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE - sizeof(file_h))) {
+ snd_printd("%s: Invalid RIFF header\n", __func__);
+ return -EINVAL;
+ }
+ data_ptr += sizeof(file_h);
+ data_end = data_ptr + le32_to_cpu(file_h.len);
+
+ if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+ return -EFAULT;
+ if (le32_to_cpu(item_type) != CSP__HEADER) {
+ snd_printd("%s: Invalid RIFF file type\n", __func__);
+ return -EINVAL;
+ }
+ data_ptr += sizeof (item_type);
+
+ for (; data_ptr < data_end; data_ptr += le32_to_cpu(item_h.len)) {
+ if (copy_from_user(&item_h, data_ptr, sizeof(item_h)))
+ return -EFAULT;
+ data_ptr += sizeof(item_h);
+ if (le32_to_cpu(item_h.name) != LIST_HEADER)
+ continue;
+
+ if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+ return -EFAULT;
+ switch (le32_to_cpu(item_type)) {
+ case FUNC_HEADER:
+ if (copy_from_user(&funcdesc_h, data_ptr + sizeof(item_type), sizeof(funcdesc_h)))
+ return -EFAULT;
+ func_nr = le16_to_cpu(funcdesc_h.func_nr);
+ break;
+ case CODE_HEADER:
+ if (func_nr != info.func_req)
+ break; /* not required function, try next */
+ data_ptr += sizeof(item_type);
+
+ /* destroy QSound mixer element */
+ if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+ snd_sb_qsound_destroy(p);
+ }
+ /* Clear all flags */
+ p->running = 0;
+ p->mode = 0;
+
+ /* load microcode blocks */
+ for (;;) {
+ if (data_ptr >= data_end)
+ return -EINVAL;
+ if (copy_from_user(&code_h, data_ptr, sizeof(code_h)))
+ return -EFAULT;
+
+ /* init microcode blocks */
+ if (le32_to_cpu(code_h.name) != INIT_HEADER)
+ break;
+ data_ptr += sizeof(code_h);
+ err = snd_sb_csp_load_user(p, data_ptr, le32_to_cpu(code_h.len),
+ SNDRV_SB_CSP_LOAD_INITBLOCK);
+ if (err)
+ return err;
+ data_ptr += le32_to_cpu(code_h.len);
+ }
+ /* main microcode block */
+ if (copy_from_user(&code_h, data_ptr, sizeof(code_h)))
+ return -EFAULT;
+
+ if (le32_to_cpu(code_h.name) != MAIN_HEADER) {
+ snd_printd("%s: Missing 'main' microcode\n", __func__);
+ return -EINVAL;
+ }
+ data_ptr += sizeof(code_h);
+ err = snd_sb_csp_load_user(p, data_ptr,
+ le32_to_cpu(code_h.len), 0);
+ if (err)
+ return err;
+
+ /* fill in codec header */
+ strscpy(p->codec_name, info.codec_name, sizeof(p->codec_name));
+ p->func_nr = func_nr;
+ p->mode = le16_to_cpu(funcdesc_h.flags_play_rec);
+ switch (le16_to_cpu(funcdesc_h.VOC_type)) {
+ case 0x0001: /* QSound decoder */
+ if (le16_to_cpu(funcdesc_h.flags_play_rec) == SNDRV_SB_CSP_MODE_DSP_WRITE) {
+ if (snd_sb_qsound_build(p) == 0)
+ /* set QSound flag and clear all other mode flags */
+ p->mode = SNDRV_SB_CSP_MODE_QSOUND;
+ }
+ p->acc_format = 0;
+ break;
+ case 0x0006: /* A Law codec */
+ p->acc_format = SNDRV_PCM_FMTBIT_A_LAW;
+ break;
+ case 0x0007: /* Mu Law codec */
+ p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW;
+ break;
+ case 0x0011: /* what Creative thinks is IMA ADPCM codec */
+ case 0x0200: /* Creative ADPCM codec */
+ p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM;
+ break;
+ case 201: /* Text 2 Speech decoder */
+ /* TODO: Text2Speech handling routines */
+ p->acc_format = 0;
+ break;
+ case 0x0202: /* Fast Speech 8 codec */
+ case 0x0203: /* Fast Speech 10 codec */
+ p->acc_format = SNDRV_PCM_FMTBIT_SPECIAL;
+ break;
+ default: /* other codecs are unsupported */
+ p->acc_format = p->acc_width = p->acc_rates = 0;
+ p->mode = 0;
+ snd_printd("%s: Unsupported CSP codec type: 0x%04x\n",
+ __func__,
+ le16_to_cpu(funcdesc_h.VOC_type));
+ return -EINVAL;
+ }
+ p->acc_channels = le16_to_cpu(funcdesc_h.flags_stereo_mono);
+ p->acc_width = le16_to_cpu(funcdesc_h.flags_16bit_8bit);
+ p->acc_rates = le16_to_cpu(funcdesc_h.flags_rates);
+
+ /* Decouple CSP from IRQ and DMAREQ lines */
+ spin_lock_irqsave(&p->chip->reg_lock, flags);
+ set_mode_register(p->chip, 0xfc);
+ set_mode_register(p->chip, 0x00);
+ spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+
+ /* finished loading successfully */
+ p->running = SNDRV_SB_CSP_ST_LOADED; /* set LOADED flag */
+ return 0;
+ }
+ }
+ snd_printd("%s: Function #%d not found\n", __func__, info.func_req);
+ return -EINVAL;
+}
+
+/*
+ * unload CSP microcode
+ */
+static int snd_sb_csp_unload(struct snd_sb_csp * p)
+{
+ if (p->running & SNDRV_SB_CSP_ST_RUNNING)
+ return -EBUSY;
+ if (!(p->running & SNDRV_SB_CSP_ST_LOADED))
+ return -ENXIO;
+
+ /* clear supported formats */
+ p->acc_format = 0;
+ p->acc_channels = p->acc_width = p->acc_rates = 0;
+ /* destroy QSound mixer element */
+ if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+ snd_sb_qsound_destroy(p);
+ }
+ /* clear all flags */
+ p->running = 0;
+ p->mode = 0;
+ return 0;
+}
+
+/*
+ * send command sequence to DSP
+ */
+static inline int command_seq(struct snd_sb *chip, const unsigned char *seq, int size)
+{
+ int i;
+ for (i = 0; i < size; i++) {
+ if (!snd_sbdsp_command(chip, seq[i]))
+ return -EIO;
+ }
+ return 0;
+}
+
+/*
+ * set CSP codec parameter
+ */
+static int set_codec_parameter(struct snd_sb *chip, unsigned char par, unsigned char val)
+{
+ unsigned char dsp_cmd[3];
+
+ dsp_cmd[0] = 0x05; /* CSP set codec parameter */
+ dsp_cmd[1] = val; /* Parameter value */
+ dsp_cmd[2] = par; /* Parameter */
+ command_seq(chip, dsp_cmd, 3);
+ snd_sbdsp_command(chip, 0x03); /* DSP read? */
+ if (snd_sbdsp_get_byte(chip) != par)
+ return -EIO;
+ return 0;
+}
+
+/*
+ * set CSP register
+ */
+static int set_register(struct snd_sb *chip, unsigned char reg, unsigned char val)
+{
+ unsigned char dsp_cmd[3];
+
+ dsp_cmd[0] = 0x0e; /* CSP set register */
+ dsp_cmd[1] = reg; /* CSP Register */
+ dsp_cmd[2] = val; /* value */
+ return command_seq(chip, dsp_cmd, 3);
+}
+
+/*
+ * read CSP register
+ * return < 0 -> error
+ */
+static int read_register(struct snd_sb *chip, unsigned char reg)
+{
+ unsigned char dsp_cmd[2];
+
+ dsp_cmd[0] = 0x0f; /* CSP read register */
+ dsp_cmd[1] = reg; /* CSP Register */
+ command_seq(chip, dsp_cmd, 2);
+ return snd_sbdsp_get_byte(chip); /* Read DSP value */
+}
+
+/*
+ * set CSP mode register
+ */
+static int set_mode_register(struct snd_sb *chip, unsigned char mode)
+{
+ unsigned char dsp_cmd[2];
+
+ dsp_cmd[0] = 0x04; /* CSP set mode register */
+ dsp_cmd[1] = mode; /* mode */
+ return command_seq(chip, dsp_cmd, 2);
+}
+
+/*
+ * Detect CSP
+ * return 0 if CSP exists.
+ */
+static int csp_detect(struct snd_sb *chip, int *version)
+{
+ unsigned char csp_test1, csp_test2;
+ unsigned long flags;
+ int result = -ENODEV;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+
+ set_codec_parameter(chip, 0x00, 0x00);
+ set_mode_register(chip, 0xfc); /* 0xfc = ?? */
+
+ csp_test1 = read_register(chip, 0x83);
+ set_register(chip, 0x83, ~csp_test1);
+ csp_test2 = read_register(chip, 0x83);
+ if (csp_test2 != (csp_test1 ^ 0xff))
+ goto __fail;
+
+ set_register(chip, 0x83, csp_test1);
+ csp_test2 = read_register(chip, 0x83);
+ if (csp_test2 != csp_test1)
+ goto __fail;
+
+ set_mode_register(chip, 0x00); /* 0x00 = ? */
+
+ *version = get_version(chip);
+ snd_sbdsp_reset(chip); /* reset DSP after getversion! */
+ if (*version >= 0x10 && *version <= 0x1f)
+ result = 0; /* valid version id */
+
+ __fail:
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return result;
+}
+
+/*
+ * get CSP version number
+ */
+static int get_version(struct snd_sb *chip)
+{
+ unsigned char dsp_cmd[2];
+
+ dsp_cmd[0] = 0x08; /* SB_DSP_!something! */
+ dsp_cmd[1] = 0x03; /* get chip version id? */
+ command_seq(chip, dsp_cmd, 2);
+
+ return (snd_sbdsp_get_byte(chip));
+}
+
+/*
+ * check if the CSP version is valid
+ */
+static int snd_sb_csp_check_version(struct snd_sb_csp * p)
+{
+ if (p->version < 0x10 || p->version > 0x1f) {
+ snd_printd("%s: Invalid CSP version: 0x%x\n", __func__, p->version);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * download microcode to CSP (microcode should have one "main" block).
+ */
+static int snd_sb_csp_load(struct snd_sb_csp * p, const unsigned char *buf, int size, int load_flags)
+{
+ int status, i;
+ int err;
+ int result = -EIO;
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->chip->reg_lock, flags);
+ snd_sbdsp_command(p->chip, 0x01); /* CSP download command */
+ if (snd_sbdsp_get_byte(p->chip)) {
+ snd_printd("%s: Download command failed\n", __func__);
+ goto __fail;
+ }
+ /* Send CSP low byte (size - 1) */
+ snd_sbdsp_command(p->chip, (unsigned char)(size - 1));
+ /* Send high byte */
+ snd_sbdsp_command(p->chip, (unsigned char)((size - 1) >> 8));
+ /* send microcode sequence */
+ /* load from kernel space */
+ while (size--) {
+ if (!snd_sbdsp_command(p->chip, *buf++))
+ goto __fail;
+ }
+ if (snd_sbdsp_get_byte(p->chip))
+ goto __fail;
+
+ if (load_flags & SNDRV_SB_CSP_LOAD_INITBLOCK) {
+ i = 0;
+ /* some codecs (FastSpeech) take some time to initialize */
+ while (1) {
+ snd_sbdsp_command(p->chip, 0x03);
+ status = snd_sbdsp_get_byte(p->chip);
+ if (status == 0x55 || ++i >= 10)
+ break;
+ udelay (10);
+ }
+ if (status != 0x55) {
+ snd_printd("%s: Microcode initialization failed\n", __func__);
+ goto __fail;
+ }
+ } else {
+ /*
+ * Read mixer register SB_DSP4_DMASETUP after loading 'main' code.
+ * Start CSP chip if no 16bit DMA channel is set - some kind
+ * of autorun or perhaps a bugfix?
+ */
+ spin_lock(&p->chip->mixer_lock);
+ status = snd_sbmixer_read(p->chip, SB_DSP4_DMASETUP);
+ spin_unlock(&p->chip->mixer_lock);
+ if (!(status & (SB_DMASETUP_DMA7 | SB_DMASETUP_DMA6 | SB_DMASETUP_DMA5))) {
+ err = (set_codec_parameter(p->chip, 0xaa, 0x00) ||
+ set_codec_parameter(p->chip, 0xff, 0x00));
+ snd_sbdsp_reset(p->chip); /* really! */
+ if (err)
+ goto __fail;
+ set_mode_register(p->chip, 0xc0); /* c0 = STOP */
+ set_mode_register(p->chip, 0x70); /* 70 = RUN */
+ }
+ }
+ result = 0;
+
+ __fail:
+ spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+ return result;
+}
+
+static int snd_sb_csp_load_user(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags)
+{
+ int err;
+ unsigned char *kbuf;
+
+ kbuf = memdup_user(buf, size);
+ if (IS_ERR(kbuf))
+ return PTR_ERR(kbuf);
+
+ err = snd_sb_csp_load(p, kbuf, size, load_flags);
+
+ kfree(kbuf);
+ return err;
+}
+
+static int snd_sb_csp_firmware_load(struct snd_sb_csp *p, int index, int flags)
+{
+ static const char *const names[] = {
+ "sb16/mulaw_main.csp",
+ "sb16/alaw_main.csp",
+ "sb16/ima_adpcm_init.csp",
+ "sb16/ima_adpcm_playback.csp",
+ "sb16/ima_adpcm_capture.csp",
+ };
+ const struct firmware *program;
+
+ BUILD_BUG_ON(ARRAY_SIZE(names) != CSP_PROGRAM_COUNT);
+ program = p->csp_programs[index];
+ if (!program) {
+ int err = request_firmware(&program, names[index],
+ p->chip->card->dev);
+ if (err < 0)
+ return err;
+ p->csp_programs[index] = program;
+ }
+ return snd_sb_csp_load(p, program->data, program->size, flags);
+}
+
+/*
+ * autoload hardware codec if necessary
+ * return 0 if CSP is loaded and ready to run (p->running != 0)
+ */
+static int snd_sb_csp_autoload(struct snd_sb_csp * p, snd_pcm_format_t pcm_sfmt, int play_rec_mode)
+{
+ unsigned long flags;
+ int err = 0;
+
+ /* if CSP is running or manually loaded then exit */
+ if (p->running & (SNDRV_SB_CSP_ST_RUNNING | SNDRV_SB_CSP_ST_LOADED))
+ return -EBUSY;
+
+ /* autoload microcode only if requested hardware codec is not already loaded */
+ if (((1U << (__force int)pcm_sfmt) & p->acc_format) && (play_rec_mode & p->mode)) {
+ p->running = SNDRV_SB_CSP_ST_AUTO;
+ } else {
+ switch (pcm_sfmt) {
+ case SNDRV_PCM_FORMAT_MU_LAW:
+ err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_MULAW, 0);
+ p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW;
+ p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE;
+ break;
+ case SNDRV_PCM_FORMAT_A_LAW:
+ err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_ALAW, 0);
+ p->acc_format = SNDRV_PCM_FMTBIT_A_LAW;
+ p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE;
+ break;
+ case SNDRV_PCM_FORMAT_IMA_ADPCM:
+ err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_ADPCM_INIT,
+ SNDRV_SB_CSP_LOAD_INITBLOCK);
+ if (err)
+ break;
+ if (play_rec_mode == SNDRV_SB_CSP_MODE_DSP_WRITE) {
+ err = snd_sb_csp_firmware_load
+ (p, CSP_PROGRAM_ADPCM_PLAYBACK, 0);
+ p->mode = SNDRV_SB_CSP_MODE_DSP_WRITE;
+ } else {
+ err = snd_sb_csp_firmware_load
+ (p, CSP_PROGRAM_ADPCM_CAPTURE, 0);
+ p->mode = SNDRV_SB_CSP_MODE_DSP_READ;
+ }
+ p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM;
+ break;
+ default:
+ /* Decouple CSP from IRQ and DMAREQ lines */
+ if (p->running & SNDRV_SB_CSP_ST_AUTO) {
+ spin_lock_irqsave(&p->chip->reg_lock, flags);
+ set_mode_register(p->chip, 0xfc);
+ set_mode_register(p->chip, 0x00);
+ spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+ p->running = 0; /* clear autoloaded flag */
+ }
+ return -EINVAL;
+ }
+ if (err) {
+ p->acc_format = 0;
+ p->acc_channels = p->acc_width = p->acc_rates = 0;
+
+ p->running = 0; /* clear autoloaded flag */
+ p->mode = 0;
+ return (err);
+ } else {
+ p->running = SNDRV_SB_CSP_ST_AUTO; /* set autoloaded flag */
+ p->acc_width = SNDRV_SB_CSP_SAMPLE_16BIT; /* only 16 bit data */
+ p->acc_channels = SNDRV_SB_CSP_MONO | SNDRV_SB_CSP_STEREO;
+ p->acc_rates = SNDRV_SB_CSP_RATE_ALL; /* HW codecs accept all rates */
+ }
+
+ }
+ return (p->running & SNDRV_SB_CSP_ST_AUTO) ? 0 : -ENXIO;
+}
+
+/*
+ * start CSP
+ */
+static int snd_sb_csp_start(struct snd_sb_csp * p, int sample_width, int channels)
+{
+ unsigned char s_type; /* sample type */
+ unsigned char mixL, mixR;
+ int result = -EIO;
+ unsigned long flags;
+
+ if (!(p->running & (SNDRV_SB_CSP_ST_LOADED | SNDRV_SB_CSP_ST_AUTO))) {
+ snd_printd("%s: Microcode not loaded\n", __func__);
+ return -ENXIO;
+ }
+ if (p->running & SNDRV_SB_CSP_ST_RUNNING) {
+ snd_printd("%s: CSP already running\n", __func__);
+ return -EBUSY;
+ }
+ if (!(sample_width & p->acc_width)) {
+ snd_printd("%s: Unsupported PCM sample width\n", __func__);
+ return -EINVAL;
+ }
+ if (!(channels & p->acc_channels)) {
+ snd_printd("%s: Invalid number of channels\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Mute PCM volume */
+ spin_lock_irqsave(&p->chip->mixer_lock, flags);
+ mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV);
+ mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1);
+ snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7);
+ snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7);
+ spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+ spin_lock(&p->chip->reg_lock);
+ set_mode_register(p->chip, 0xc0); /* c0 = STOP */
+ set_mode_register(p->chip, 0x70); /* 70 = RUN */
+
+ s_type = 0x00;
+ if (channels == SNDRV_SB_CSP_MONO)
+ s_type = 0x11; /* 000n 000n (n = 1 if mono) */
+ if (sample_width == SNDRV_SB_CSP_SAMPLE_8BIT)
+ s_type |= 0x22; /* 00dX 00dX (d = 1 if 8 bit samples) */
+
+ if (set_codec_parameter(p->chip, 0x81, s_type)) {
+ snd_printd("%s: Set sample type command failed\n", __func__);
+ goto __fail;
+ }
+ if (set_codec_parameter(p->chip, 0x80, 0x00)) {
+ snd_printd("%s: Codec start command failed\n", __func__);
+ goto __fail;
+ }
+ p->run_width = sample_width;
+ p->run_channels = channels;
+
+ p->running |= SNDRV_SB_CSP_ST_RUNNING;
+
+ if (p->mode & SNDRV_SB_CSP_MODE_QSOUND) {
+ set_codec_parameter(p->chip, 0xe0, 0x01);
+ /* enable QSound decoder */
+ set_codec_parameter(p->chip, 0x00, 0xff);
+ set_codec_parameter(p->chip, 0x01, 0xff);
+ p->running |= SNDRV_SB_CSP_ST_QSOUND;
+ /* set QSound startup value */
+ snd_sb_csp_qsound_transfer(p);
+ }
+ result = 0;
+
+ __fail:
+ spin_unlock(&p->chip->reg_lock);
+
+ /* restore PCM volume */
+ spin_lock_irqsave(&p->chip->mixer_lock, flags);
+ snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL);
+ snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR);
+ spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+ return result;
+}
+
+/*
+ * stop CSP
+ */
+static int snd_sb_csp_stop(struct snd_sb_csp * p)
+{
+ int result;
+ unsigned char mixL, mixR;
+ unsigned long flags;
+
+ if (!(p->running & SNDRV_SB_CSP_ST_RUNNING))
+ return 0;
+
+ /* Mute PCM volume */
+ spin_lock_irqsave(&p->chip->mixer_lock, flags);
+ mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV);
+ mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1);
+ snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7);
+ snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7);
+ spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+ spin_lock(&p->chip->reg_lock);
+ if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+ set_codec_parameter(p->chip, 0xe0, 0x01);
+ /* disable QSound decoder */
+ set_codec_parameter(p->chip, 0x00, 0x00);
+ set_codec_parameter(p->chip, 0x01, 0x00);
+
+ p->running &= ~SNDRV_SB_CSP_ST_QSOUND;
+ }
+ result = set_mode_register(p->chip, 0xc0); /* c0 = STOP */
+ spin_unlock(&p->chip->reg_lock);
+
+ /* restore PCM volume */
+ spin_lock_irqsave(&p->chip->mixer_lock, flags);
+ snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL);
+ snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR);
+ spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+ if (!(result))
+ p->running &= ~(SNDRV_SB_CSP_ST_PAUSED | SNDRV_SB_CSP_ST_RUNNING);
+ return result;
+}
+
+/*
+ * pause CSP codec and hold DMA transfer
+ */
+static int snd_sb_csp_pause(struct snd_sb_csp * p)
+{
+ int result;
+ unsigned long flags;
+
+ if (!(p->running & SNDRV_SB_CSP_ST_RUNNING))
+ return -EBUSY;
+
+ spin_lock_irqsave(&p->chip->reg_lock, flags);
+ result = set_codec_parameter(p->chip, 0x80, 0xff);
+ spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+ if (!(result))
+ p->running |= SNDRV_SB_CSP_ST_PAUSED;
+
+ return result;
+}
+
+/*
+ * restart CSP codec and resume DMA transfer
+ */
+static int snd_sb_csp_restart(struct snd_sb_csp * p)
+{
+ int result;
+ unsigned long flags;
+
+ if (!(p->running & SNDRV_SB_CSP_ST_PAUSED))
+ return -EBUSY;
+
+ spin_lock_irqsave(&p->chip->reg_lock, flags);
+ result = set_codec_parameter(p->chip, 0x80, 0x00);
+ spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+ if (!(result))
+ p->running &= ~SNDRV_SB_CSP_ST_PAUSED;
+
+ return result;
+}
+
+/* ------------------------------ */
+
+/*
+ * QSound mixer control for PCM
+ */
+
+#define snd_sb_qsound_switch_info snd_ctl_boolean_mono_info
+
+static int snd_sb_qsound_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = p->q_enabled ? 1 : 0;
+ return 0;
+}
+
+static int snd_sb_qsound_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned char nval;
+
+ nval = ucontrol->value.integer.value[0] & 0x01;
+ spin_lock_irqsave(&p->q_lock, flags);
+ change = p->q_enabled != nval;
+ p->q_enabled = nval;
+ spin_unlock_irqrestore(&p->q_lock, flags);
+ return change;
+}
+
+static int snd_sb_qsound_space_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+ return 0;
+}
+
+static int snd_sb_qsound_space_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->q_lock, flags);
+ ucontrol->value.integer.value[0] = p->qpos_left;
+ ucontrol->value.integer.value[1] = p->qpos_right;
+ spin_unlock_irqrestore(&p->q_lock, flags);
+ return 0;
+}
+
+static int snd_sb_qsound_space_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned char nval1, nval2;
+
+ nval1 = ucontrol->value.integer.value[0];
+ if (nval1 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT)
+ nval1 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+ nval2 = ucontrol->value.integer.value[1];
+ if (nval2 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT)
+ nval2 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+ spin_lock_irqsave(&p->q_lock, flags);
+ change = p->qpos_left != nval1 || p->qpos_right != nval2;
+ p->qpos_left = nval1;
+ p->qpos_right = nval2;
+ p->qpos_changed = change;
+ spin_unlock_irqrestore(&p->q_lock, flags);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_sb_qsound_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "3D Control - Switch",
+ .info = snd_sb_qsound_switch_info,
+ .get = snd_sb_qsound_switch_get,
+ .put = snd_sb_qsound_switch_put
+};
+
+static const struct snd_kcontrol_new snd_sb_qsound_space = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "3D Control - Space",
+ .info = snd_sb_qsound_space_info,
+ .get = snd_sb_qsound_space_get,
+ .put = snd_sb_qsound_space_put
+};
+
+static int snd_sb_qsound_build(struct snd_sb_csp * p)
+{
+ struct snd_card *card;
+ struct snd_kcontrol *kctl;
+ int err;
+
+ if (snd_BUG_ON(!p))
+ return -EINVAL;
+
+ card = p->chip->card;
+ p->qpos_left = p->qpos_right = SNDRV_SB_CSP_QSOUND_MAX_RIGHT / 2;
+ p->qpos_changed = 0;
+
+ spin_lock_init(&p->q_lock);
+
+ kctl = snd_ctl_new1(&snd_sb_qsound_switch, p);
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ goto __error;
+ p->qsound_switch = kctl;
+ kctl = snd_ctl_new1(&snd_sb_qsound_space, p);
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ goto __error;
+ p->qsound_space = kctl;
+
+ return 0;
+
+ __error:
+ snd_sb_qsound_destroy(p);
+ return err;
+}
+
+static void snd_sb_qsound_destroy(struct snd_sb_csp * p)
+{
+ struct snd_card *card;
+ unsigned long flags;
+
+ if (snd_BUG_ON(!p))
+ return;
+
+ card = p->chip->card;
+
+ if (p->qsound_switch) {
+ snd_ctl_remove(card, p->qsound_switch);
+ p->qsound_switch = NULL;
+ }
+ if (p->qsound_space) {
+ snd_ctl_remove(card, p->qsound_space);
+ p->qsound_space = NULL;
+ }
+
+ /* cancel pending transfer of QSound parameters */
+ spin_lock_irqsave (&p->q_lock, flags);
+ p->qpos_changed = 0;
+ spin_unlock_irqrestore (&p->q_lock, flags);
+}
+
+/*
+ * Transfer qsound parameters to CSP,
+ * function should be called from interrupt routine
+ */
+static int snd_sb_csp_qsound_transfer(struct snd_sb_csp * p)
+{
+ int err = -ENXIO;
+
+ spin_lock(&p->q_lock);
+ if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+ set_codec_parameter(p->chip, 0xe0, 0x01);
+ /* left channel */
+ set_codec_parameter(p->chip, 0x00, p->qpos_left);
+ set_codec_parameter(p->chip, 0x02, 0x00);
+ /* right channel */
+ set_codec_parameter(p->chip, 0x00, p->qpos_right);
+ set_codec_parameter(p->chip, 0x03, 0x00);
+ err = 0;
+ }
+ p->qpos_changed = 0;
+ spin_unlock(&p->q_lock);
+ return err;
+}
+
+/* ------------------------------ */
+
+/*
+ * proc interface
+ */
+static int init_proc_entry(struct snd_sb_csp * p, int device)
+{
+ char name[16];
+
+ sprintf(name, "cspD%d", device);
+ snd_card_ro_proc_new(p->chip->card, name, p, info_read);
+ return 0;
+}
+
+static void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+ struct snd_sb_csp *p = entry->private_data;
+
+ snd_iprintf(buffer, "Creative Signal Processor [v%d.%d]\n", (p->version >> 4), (p->version & 0x0f));
+ snd_iprintf(buffer, "State: %cx%c%c%c\n", ((p->running & SNDRV_SB_CSP_ST_QSOUND) ? 'Q' : '-'),
+ ((p->running & SNDRV_SB_CSP_ST_PAUSED) ? 'P' : '-'),
+ ((p->running & SNDRV_SB_CSP_ST_RUNNING) ? 'R' : '-'),
+ ((p->running & SNDRV_SB_CSP_ST_LOADED) ? 'L' : '-'));
+ if (p->running & SNDRV_SB_CSP_ST_LOADED) {
+ snd_iprintf(buffer, "Codec: %s [func #%d]\n", p->codec_name, p->func_nr);
+ snd_iprintf(buffer, "Sample rates: ");
+ if (p->acc_rates == SNDRV_SB_CSP_RATE_ALL) {
+ snd_iprintf(buffer, "All\n");
+ } else {
+ snd_iprintf(buffer, "%s%s%s%s\n",
+ ((p->acc_rates & SNDRV_SB_CSP_RATE_8000) ? "8000Hz " : ""),
+ ((p->acc_rates & SNDRV_SB_CSP_RATE_11025) ? "11025Hz " : ""),
+ ((p->acc_rates & SNDRV_SB_CSP_RATE_22050) ? "22050Hz " : ""),
+ ((p->acc_rates & SNDRV_SB_CSP_RATE_44100) ? "44100Hz" : ""));
+ }
+ if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+ snd_iprintf(buffer, "QSound decoder %sabled\n",
+ p->q_enabled ? "en" : "dis");
+ } else {
+ snd_iprintf(buffer, "PCM format ID: 0x%x (%s/%s) [%s/%s] [%s/%s]\n",
+ p->acc_format,
+ ((p->acc_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? "16bit" : "-"),
+ ((p->acc_width & SNDRV_SB_CSP_SAMPLE_8BIT) ? "8bit" : "-"),
+ ((p->acc_channels & SNDRV_SB_CSP_MONO) ? "mono" : "-"),
+ ((p->acc_channels & SNDRV_SB_CSP_STEREO) ? "stereo" : "-"),
+ ((p->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) ? "playback" : "-"),
+ ((p->mode & SNDRV_SB_CSP_MODE_DSP_READ) ? "capture" : "-"));
+ }
+ }
+ if (p->running & SNDRV_SB_CSP_ST_AUTO) {
+ snd_iprintf(buffer, "Autoloaded Mu-Law, A-Law or Ima-ADPCM hardware codec\n");
+ }
+ if (p->running & SNDRV_SB_CSP_ST_RUNNING) {
+ snd_iprintf(buffer, "Processing %dbit %s PCM samples\n",
+ ((p->run_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? 16 : 8),
+ ((p->run_channels & SNDRV_SB_CSP_MONO) ? "mono" : "stereo"));
+ }
+ if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+ snd_iprintf(buffer, "Qsound position: left = 0x%x, right = 0x%x\n",
+ p->qpos_left, p->qpos_right);
+ }
+}
+
+/* */
+
+EXPORT_SYMBOL(snd_sb_csp_new);
diff --git a/sound/isa/sb/sb16_main.c b/sound/isa/sb/sb16_main.c
new file mode 100644
index 0000000000..a9b87e159b
--- /dev/null
+++ b/sound/isa/sb/sb16_main.c
@@ -0,0 +1,870 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for control of 16-bit SoundBlaster cards and clones
+ * Note: This is very ugly hardware which uses one 8-bit DMA channel and
+ * second 16-bit DMA channel. Unfortunately 8-bit DMA channel can't
+ * transfer 16-bit samples and 16-bit DMA channels can't transfer
+ * 8-bit samples. This make full duplex more complicated than
+ * can be... People, don't buy these soundcards for full 16-bit
+ * duplex!!!
+ * Note: 16-bit wide is assigned to first direction which made request.
+ * With full duplex - playback is preferred with abstract layer.
+ *
+ * Note: Some chip revisions have hardware bug. Changing capture
+ * channel from full-duplex 8bit DMA to 16bit DMA will block
+ * 16bit DMA transfers from DSP chip (capture) until 8bit transfer
+ * to DSP chip (playback) starts. This bug can be avoided with
+ * "16bit DMA Allocation" setting set to Playback or Capture.
+ */
+
+#include <linux/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/sb16_csp.h>
+#include <sound/mpu401.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for control of 16-bit SoundBlaster cards and clones");
+MODULE_LICENSE("GPL");
+
+#define runtime_format_bits(runtime) \
+ ((unsigned int)pcm_format_to_bits((runtime)->format))
+
+#ifdef CONFIG_SND_SB16_CSP
+static void snd_sb16_csp_playback_prepare(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+ if (chip->hardware == SB_HW_16CSP) {
+ struct snd_sb_csp *csp = chip->csp;
+
+ if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+ /* manually loaded codec */
+ if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) &&
+ (runtime_format_bits(runtime) == csp->acc_format)) {
+ /* Supported runtime PCM format for playback */
+ if (csp->ops.csp_use(csp) == 0) {
+ /* If CSP was successfully acquired */
+ goto __start_CSP;
+ }
+ } else if ((csp->mode & SNDRV_SB_CSP_MODE_QSOUND) && (csp->q_enabled)) {
+ /* QSound decoder is loaded and enabled */
+ if (runtime_format_bits(runtime) & (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE)) {
+ /* Only for simple PCM formats */
+ if (csp->ops.csp_use(csp) == 0) {
+ /* If CSP was successfully acquired */
+ goto __start_CSP;
+ }
+ }
+ }
+ } else if (csp->ops.csp_use(csp) == 0) {
+ /* Acquire CSP and try to autoload hardware codec */
+ if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_WRITE)) {
+ /* Unsupported format, release CSP */
+ csp->ops.csp_unuse(csp);
+ } else {
+ __start_CSP:
+ /* Try to start CSP */
+ if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_PLAYBACK_16) ?
+ SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
+ (runtime->channels > 1) ?
+ SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
+ /* Failed, release CSP */
+ csp->ops.csp_unuse(csp);
+ } else {
+ /* Success, CSP acquired and running */
+ chip->open = SNDRV_SB_CSP_MODE_DSP_WRITE;
+ }
+ }
+ }
+ }
+}
+
+static void snd_sb16_csp_capture_prepare(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+ if (chip->hardware == SB_HW_16CSP) {
+ struct snd_sb_csp *csp = chip->csp;
+
+ if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+ /* manually loaded codec */
+ if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) &&
+ (runtime_format_bits(runtime) == csp->acc_format)) {
+ /* Supported runtime PCM format for capture */
+ if (csp->ops.csp_use(csp) == 0) {
+ /* If CSP was successfully acquired */
+ goto __start_CSP;
+ }
+ }
+ } else if (csp->ops.csp_use(csp) == 0) {
+ /* Acquire CSP and try to autoload hardware codec */
+ if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_READ)) {
+ /* Unsupported format, release CSP */
+ csp->ops.csp_unuse(csp);
+ } else {
+ __start_CSP:
+ /* Try to start CSP */
+ if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_CAPTURE_16) ?
+ SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
+ (runtime->channels > 1) ?
+ SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
+ /* Failed, release CSP */
+ csp->ops.csp_unuse(csp);
+ } else {
+ /* Success, CSP acquired and running */
+ chip->open = SNDRV_SB_CSP_MODE_DSP_READ;
+ }
+ }
+ }
+ }
+}
+
+static void snd_sb16_csp_update(struct snd_sb *chip)
+{
+ if (chip->hardware == SB_HW_16CSP) {
+ struct snd_sb_csp *csp = chip->csp;
+
+ if (csp->qpos_changed) {
+ spin_lock(&chip->reg_lock);
+ csp->ops.csp_qsound_transfer (csp);
+ spin_unlock(&chip->reg_lock);
+ }
+ }
+}
+
+static void snd_sb16_csp_playback_open(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+ /* CSP decoders (QSound excluded) support only 16bit transfers */
+ if (chip->hardware == SB_HW_16CSP) {
+ struct snd_sb_csp *csp = chip->csp;
+
+ if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+ /* manually loaded codec */
+ if (csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) {
+ runtime->hw.formats |= csp->acc_format;
+ }
+ } else {
+ /* autoloaded codecs */
+ runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+ SNDRV_PCM_FMTBIT_IMA_ADPCM;
+ }
+ }
+}
+
+static void snd_sb16_csp_playback_close(struct snd_sb *chip)
+{
+ if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_WRITE)) {
+ struct snd_sb_csp *csp = chip->csp;
+
+ if (csp->ops.csp_stop(csp) == 0) {
+ csp->ops.csp_unuse(csp);
+ chip->open = 0;
+ }
+ }
+}
+
+static void snd_sb16_csp_capture_open(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+ /* CSP coders support only 16bit transfers */
+ if (chip->hardware == SB_HW_16CSP) {
+ struct snd_sb_csp *csp = chip->csp;
+
+ if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+ /* manually loaded codec */
+ if (csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) {
+ runtime->hw.formats |= csp->acc_format;
+ }
+ } else {
+ /* autoloaded codecs */
+ runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+ SNDRV_PCM_FMTBIT_IMA_ADPCM;
+ }
+ }
+}
+
+static void snd_sb16_csp_capture_close(struct snd_sb *chip)
+{
+ if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_READ)) {
+ struct snd_sb_csp *csp = chip->csp;
+
+ if (csp->ops.csp_stop(csp) == 0) {
+ csp->ops.csp_unuse(csp);
+ chip->open = 0;
+ }
+ }
+}
+#else
+#define snd_sb16_csp_playback_prepare(chip, runtime) /*nop*/
+#define snd_sb16_csp_capture_prepare(chip, runtime) /*nop*/
+#define snd_sb16_csp_update(chip) /*nop*/
+#define snd_sb16_csp_playback_open(chip, runtime) /*nop*/
+#define snd_sb16_csp_playback_close(chip) /*nop*/
+#define snd_sb16_csp_capture_open(chip, runtime) /*nop*/
+#define snd_sb16_csp_capture_close(chip) /*nop*/
+#endif
+
+
+static void snd_sb16_setup_rate(struct snd_sb *chip,
+ unsigned short rate,
+ int channel)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (chip->mode & (channel == SNDRV_PCM_STREAM_PLAYBACK ? SB_MODE_PLAYBACK_16 : SB_MODE_CAPTURE_16))
+ snd_sb_ack_16bit(chip);
+ else
+ snd_sb_ack_8bit(chip);
+ if (!(chip->mode & SB_RATE_LOCK)) {
+ chip->locked_rate = rate;
+ snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_IN);
+ snd_sbdsp_command(chip, rate >> 8);
+ snd_sbdsp_command(chip, rate & 0xff);
+ snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT);
+ snd_sbdsp_command(chip, rate >> 8);
+ snd_sbdsp_command(chip, rate & 0xff);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static int snd_sb16_playback_prepare(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned char format;
+ unsigned int size, count, dma;
+
+ snd_sb16_csp_playback_prepare(chip, runtime);
+ if (snd_pcm_format_unsigned(runtime->format) > 0) {
+ format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
+ } else {
+ format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
+ }
+
+ snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_PLAYBACK);
+ size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
+ dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
+ snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+ count = snd_pcm_lib_period_bytes(substream);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (chip->mode & SB_MODE_PLAYBACK_16) {
+ count >>= 1;
+ count--;
+ snd_sbdsp_command(chip, SB_DSP4_OUT16_AI);
+ snd_sbdsp_command(chip, format);
+ snd_sbdsp_command(chip, count & 0xff);
+ snd_sbdsp_command(chip, count >> 8);
+ snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+ } else {
+ count--;
+ snd_sbdsp_command(chip, SB_DSP4_OUT8_AI);
+ snd_sbdsp_command(chip, format);
+ snd_sbdsp_command(chip, count & 0xff);
+ snd_sbdsp_command(chip, count >> 8);
+ snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_sb16_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ int result = 0;
+
+ spin_lock(&chip->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ chip->mode |= SB_RATE_LOCK_PLAYBACK;
+ snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
+ /* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
+ if (chip->mode & SB_RATE_LOCK_CAPTURE)
+ snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+ chip->mode &= ~SB_RATE_LOCK_PLAYBACK;
+ break;
+ default:
+ result = -EINVAL;
+ }
+ spin_unlock(&chip->reg_lock);
+ return result;
+}
+
+static int snd_sb16_capture_prepare(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned char format;
+ unsigned int size, count, dma;
+
+ snd_sb16_csp_capture_prepare(chip, runtime);
+ if (snd_pcm_format_unsigned(runtime->format) > 0) {
+ format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
+ } else {
+ format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
+ }
+ snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_CAPTURE);
+ size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
+ dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
+ snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+
+ count = snd_pcm_lib_period_bytes(substream);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (chip->mode & SB_MODE_CAPTURE_16) {
+ count >>= 1;
+ count--;
+ snd_sbdsp_command(chip, SB_DSP4_IN16_AI);
+ snd_sbdsp_command(chip, format);
+ snd_sbdsp_command(chip, count & 0xff);
+ snd_sbdsp_command(chip, count >> 8);
+ snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+ } else {
+ count--;
+ snd_sbdsp_command(chip, SB_DSP4_IN8_AI);
+ snd_sbdsp_command(chip, format);
+ snd_sbdsp_command(chip, count & 0xff);
+ snd_sbdsp_command(chip, count >> 8);
+ snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_sb16_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ int result = 0;
+
+ spin_lock(&chip->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ chip->mode |= SB_RATE_LOCK_CAPTURE;
+ snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
+ /* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
+ if (chip->mode & SB_RATE_LOCK_PLAYBACK)
+ snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+ chip->mode &= ~SB_RATE_LOCK_CAPTURE;
+ break;
+ default:
+ result = -EINVAL;
+ }
+ spin_unlock(&chip->reg_lock);
+ return result;
+}
+
+irqreturn_t snd_sb16dsp_interrupt(int irq, void *dev_id)
+{
+ struct snd_sb *chip = dev_id;
+ unsigned char status;
+ int ok;
+
+ spin_lock(&chip->mixer_lock);
+ status = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS);
+ spin_unlock(&chip->mixer_lock);
+ if ((status & SB_IRQTYPE_MPUIN) && chip->rmidi_callback)
+ chip->rmidi_callback(irq, chip->rmidi->private_data);
+ if (status & SB_IRQTYPE_8BIT) {
+ ok = 0;
+ if (chip->mode & SB_MODE_PLAYBACK_8) {
+ snd_pcm_period_elapsed(chip->playback_substream);
+ snd_sb16_csp_update(chip);
+ ok++;
+ }
+ if (chip->mode & SB_MODE_CAPTURE_8) {
+ snd_pcm_period_elapsed(chip->capture_substream);
+ ok++;
+ }
+ spin_lock(&chip->reg_lock);
+ if (!ok)
+ snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+ snd_sb_ack_8bit(chip);
+ spin_unlock(&chip->reg_lock);
+ }
+ if (status & SB_IRQTYPE_16BIT) {
+ ok = 0;
+ if (chip->mode & SB_MODE_PLAYBACK_16) {
+ snd_pcm_period_elapsed(chip->playback_substream);
+ snd_sb16_csp_update(chip);
+ ok++;
+ }
+ if (chip->mode & SB_MODE_CAPTURE_16) {
+ snd_pcm_period_elapsed(chip->capture_substream);
+ ok++;
+ }
+ spin_lock(&chip->reg_lock);
+ if (!ok)
+ snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+ snd_sb_ack_16bit(chip);
+ spin_unlock(&chip->reg_lock);
+ }
+ return IRQ_HANDLED;
+}
+
+/*
+
+ */
+
+static snd_pcm_uframes_t snd_sb16_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ unsigned int dma;
+ size_t ptr;
+
+ dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
+ ptr = snd_dma_pointer(dma, chip->p_dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sb16_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ unsigned int dma;
+ size_t ptr;
+
+ dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
+ ptr = snd_dma_pointer(dma, chip->c_dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static const struct snd_pcm_hardware snd_sb16_playback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = 0,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+ .rate_min = 4000,
+ .rate_max = 44100,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_sb16_capture =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = 0,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+ .rate_min = 4000,
+ .rate_max = 44100,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/*
+ * open/close
+ */
+
+static int snd_sb16_playback_open(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ spin_lock_irqsave(&chip->open_lock, flags);
+ if (chip->mode & SB_MODE_PLAYBACK) {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return -EAGAIN;
+ }
+ runtime->hw = snd_sb16_playback;
+
+ /* skip if 16 bit DMA was reserved for capture */
+ if (chip->force_mode16 & SB_MODE_CAPTURE_16)
+ goto __skip_16bit;
+
+ if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_CAPTURE_16)) {
+ chip->mode |= SB_MODE_PLAYBACK_16;
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+ /* Vibra16X hack */
+ if (chip->dma16 <= 3) {
+ runtime->hw.buffer_bytes_max =
+ runtime->hw.period_bytes_max = 64 * 1024;
+ } else {
+ snd_sb16_csp_playback_open(chip, runtime);
+ }
+ goto __open_ok;
+ }
+
+ __skip_16bit:
+ if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_CAPTURE_8)) {
+ chip->mode |= SB_MODE_PLAYBACK_8;
+ /* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
+ if (chip->dma16 < 0) {
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+ chip->mode |= SB_MODE_PLAYBACK_16;
+ } else {
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
+ }
+ runtime->hw.buffer_bytes_max =
+ runtime->hw.period_bytes_max = 64 * 1024;
+ goto __open_ok;
+ }
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return -EAGAIN;
+
+ __open_ok:
+ if (chip->hardware == SB_HW_ALS100)
+ runtime->hw.rate_max = 48000;
+ if (chip->hardware == SB_HW_CS5530) {
+ runtime->hw.buffer_bytes_max = 32 * 1024;
+ runtime->hw.periods_min = 2;
+ runtime->hw.rate_min = 44100;
+ }
+ if (chip->mode & SB_RATE_LOCK)
+ runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
+ chip->playback_substream = substream;
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return 0;
+}
+
+static int snd_sb16_playback_close(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+
+ snd_sb16_csp_playback_close(chip);
+ spin_lock_irqsave(&chip->open_lock, flags);
+ chip->playback_substream = NULL;
+ chip->mode &= ~SB_MODE_PLAYBACK;
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return 0;
+}
+
+static int snd_sb16_capture_open(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ spin_lock_irqsave(&chip->open_lock, flags);
+ if (chip->mode & SB_MODE_CAPTURE) {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return -EAGAIN;
+ }
+ runtime->hw = snd_sb16_capture;
+
+ /* skip if 16 bit DMA was reserved for playback */
+ if (chip->force_mode16 & SB_MODE_PLAYBACK_16)
+ goto __skip_16bit;
+
+ if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_16)) {
+ chip->mode |= SB_MODE_CAPTURE_16;
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+ /* Vibra16X hack */
+ if (chip->dma16 <= 3) {
+ runtime->hw.buffer_bytes_max =
+ runtime->hw.period_bytes_max = 64 * 1024;
+ } else {
+ snd_sb16_csp_capture_open(chip, runtime);
+ }
+ goto __open_ok;
+ }
+
+ __skip_16bit:
+ if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_8)) {
+ chip->mode |= SB_MODE_CAPTURE_8;
+ /* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
+ if (chip->dma16 < 0) {
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+ chip->mode |= SB_MODE_CAPTURE_16;
+ } else {
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
+ }
+ runtime->hw.buffer_bytes_max =
+ runtime->hw.period_bytes_max = 64 * 1024;
+ goto __open_ok;
+ }
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return -EAGAIN;
+
+ __open_ok:
+ if (chip->hardware == SB_HW_ALS100)
+ runtime->hw.rate_max = 48000;
+ if (chip->hardware == SB_HW_CS5530) {
+ runtime->hw.buffer_bytes_max = 32 * 1024;
+ runtime->hw.periods_min = 2;
+ runtime->hw.rate_min = 44100;
+ }
+ if (chip->mode & SB_RATE_LOCK)
+ runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
+ chip->capture_substream = substream;
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return 0;
+}
+
+static int snd_sb16_capture_close(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+
+ snd_sb16_csp_capture_close(chip);
+ spin_lock_irqsave(&chip->open_lock, flags);
+ chip->capture_substream = NULL;
+ chip->mode &= ~SB_MODE_CAPTURE;
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return 0;
+}
+
+/*
+ * DMA control interface
+ */
+
+static int snd_sb16_set_dma_mode(struct snd_sb *chip, int what)
+{
+ if (chip->dma8 < 0 || chip->dma16 < 0) {
+ if (snd_BUG_ON(what))
+ return -EINVAL;
+ return 0;
+ }
+ if (what == 0) {
+ chip->force_mode16 = 0;
+ } else if (what == 1) {
+ chip->force_mode16 = SB_MODE_PLAYBACK_16;
+ } else if (what == 2) {
+ chip->force_mode16 = SB_MODE_CAPTURE_16;
+ } else {
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int snd_sb16_get_dma_mode(struct snd_sb *chip)
+{
+ if (chip->dma8 < 0 || chip->dma16 < 0)
+ return 0;
+ switch (chip->force_mode16) {
+ case SB_MODE_PLAYBACK_16:
+ return 1;
+ case SB_MODE_CAPTURE_16:
+ return 2;
+ default:
+ return 0;
+ }
+}
+
+static int snd_sb16_dma_control_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[3] = {
+ "Auto", "Playback", "Capture"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+static int snd_sb16_dma_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.enumerated.item[0] = snd_sb16_get_dma_mode(chip);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_sb16_dma_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ unsigned char nval, oval;
+ int change;
+
+ nval = ucontrol->value.enumerated.item[0];
+ if (nval > 2)
+ return -EINVAL;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ oval = snd_sb16_get_dma_mode(chip);
+ change = nval != oval;
+ snd_sb16_set_dma_mode(chip, nval);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_sb16_dma_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "16-bit DMA Allocation",
+ .info = snd_sb16_dma_control_info,
+ .get = snd_sb16_dma_control_get,
+ .put = snd_sb16_dma_control_put
+};
+
+/*
+ * Initialization part
+ */
+
+int snd_sb16dsp_configure(struct snd_sb * chip)
+{
+ unsigned long flags;
+ unsigned char irqreg = 0, dmareg = 0, mpureg;
+ unsigned char realirq, realdma, realmpureg;
+ /* note: mpu register should be present only on SB16 Vibra soundcards */
+
+ // printk(KERN_DEBUG "codec->irq=%i, codec->dma8=%i, codec->dma16=%i\n", chip->irq, chip->dma8, chip->dma16);
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ mpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP) & ~0x06;
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+ switch (chip->irq) {
+ case 2:
+ case 9:
+ irqreg |= SB_IRQSETUP_IRQ9;
+ break;
+ case 5:
+ irqreg |= SB_IRQSETUP_IRQ5;
+ break;
+ case 7:
+ irqreg |= SB_IRQSETUP_IRQ7;
+ break;
+ case 10:
+ irqreg |= SB_IRQSETUP_IRQ10;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (chip->dma8 >= 0) {
+ switch (chip->dma8) {
+ case 0:
+ dmareg |= SB_DMASETUP_DMA0;
+ break;
+ case 1:
+ dmareg |= SB_DMASETUP_DMA1;
+ break;
+ case 3:
+ dmareg |= SB_DMASETUP_DMA3;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) {
+ switch (chip->dma16) {
+ case 5:
+ dmareg |= SB_DMASETUP_DMA5;
+ break;
+ case 6:
+ dmareg |= SB_DMASETUP_DMA6;
+ break;
+ case 7:
+ dmareg |= SB_DMASETUP_DMA7;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ switch (chip->mpu_port) {
+ case 0x300:
+ mpureg |= 0x04;
+ break;
+ case 0x330:
+ mpureg |= 0x00;
+ break;
+ default:
+ mpureg |= 0x02; /* disable MPU */
+ }
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+
+ snd_sbmixer_write(chip, SB_DSP4_IRQSETUP, irqreg);
+ realirq = snd_sbmixer_read(chip, SB_DSP4_IRQSETUP);
+
+ snd_sbmixer_write(chip, SB_DSP4_DMASETUP, dmareg);
+ realdma = snd_sbmixer_read(chip, SB_DSP4_DMASETUP);
+
+ snd_sbmixer_write(chip, SB_DSP4_MPUSETUP, mpureg);
+ realmpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP);
+
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+ if ((~realirq) & irqreg || (~realdma) & dmareg) {
+ snd_printk(KERN_ERR "SB16 [0x%lx]: unable to set DMA & IRQ (PnP device?)\n", chip->port);
+ snd_printk(KERN_ERR "SB16 [0x%lx]: wanted: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, realirq, realdma, realmpureg);
+ snd_printk(KERN_ERR "SB16 [0x%lx]: got: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, irqreg, dmareg, mpureg);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static const struct snd_pcm_ops snd_sb16_playback_ops = {
+ .open = snd_sb16_playback_open,
+ .close = snd_sb16_playback_close,
+ .prepare = snd_sb16_playback_prepare,
+ .trigger = snd_sb16_playback_trigger,
+ .pointer = snd_sb16_playback_pointer,
+};
+
+static const struct snd_pcm_ops snd_sb16_capture_ops = {
+ .open = snd_sb16_capture_open,
+ .close = snd_sb16_capture_close,
+ .prepare = snd_sb16_capture_prepare,
+ .trigger = snd_sb16_capture_trigger,
+ .pointer = snd_sb16_capture_pointer,
+};
+
+int snd_sb16dsp_pcm(struct snd_sb *chip, int device)
+{
+ struct snd_card *card = chip->card;
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(card, "SB16 DSP", device, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+ sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
+ pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+ pcm->private_data = chip;
+ chip->pcm = pcm;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb16_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb16_capture_ops);
+
+ if (chip->dma16 >= 0 && chip->dma8 != chip->dma16)
+ snd_ctl_add(card, snd_ctl_new1(&snd_sb16_dma_control, chip));
+ else
+ pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, 64*1024, 128*1024);
+ return 0;
+}
+
+const struct snd_pcm_ops *snd_sb16dsp_get_pcm_ops(int direction)
+{
+ return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+ &snd_sb16_playback_ops : &snd_sb16_capture_ops;
+}
+
+EXPORT_SYMBOL(snd_sb16dsp_pcm);
+EXPORT_SYMBOL(snd_sb16dsp_get_pcm_ops);
+EXPORT_SYMBOL(snd_sb16dsp_configure);
+EXPORT_SYMBOL(snd_sb16dsp_interrupt);
diff --git a/sound/isa/sb/sb8.c b/sound/isa/sb/sb8.c
new file mode 100644
index 0000000000..e5ef177716
--- /dev/null
+++ b/sound/isa/sb/sb8.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Sound Blaster 1.0/2.0/Pro");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Sound Blaster soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Sound Blaster soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Sound Blaster soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB8 driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB8 driver.");
+module_param_hw_array(dma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for SB8 driver.");
+
+struct snd_sb8 {
+ struct resource *fm_res; /* used to block FM i/o region for legacy cards */
+ struct snd_sb *chip;
+};
+
+static irqreturn_t snd_sb8_interrupt(int irq, void *dev_id)
+{
+ struct snd_sb *chip = dev_id;
+
+ if (chip->open & SB_OPEN_PCM) {
+ return snd_sb8dsp_interrupt(chip);
+ } else {
+ return snd_sb8dsp_midi_interrupt(chip);
+ }
+}
+
+static int snd_sb8_match(struct device *pdev, unsigned int dev)
+{
+ if (!enable[dev])
+ return 0;
+ if (irq[dev] == SNDRV_AUTO_IRQ) {
+ dev_err(pdev, "please specify irq\n");
+ return 0;
+ }
+ if (dma8[dev] == SNDRV_AUTO_DMA) {
+ dev_err(pdev, "please specify dma8\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int snd_sb8_probe(struct device *pdev, unsigned int dev)
+{
+ struct snd_sb *chip;
+ struct snd_card *card;
+ struct snd_sb8 *acard;
+ struct snd_opl3 *opl3;
+ int err;
+
+ err = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_sb8), &card);
+ if (err < 0)
+ return err;
+ acard = card->private_data;
+
+ /*
+ * Block the 0x388 port to avoid PnP conflicts.
+ * No need to check this value after request_region,
+ * as we never do anything with it.
+ */
+ acard->fm_res = devm_request_region(card->dev, 0x388, 4,
+ "SoundBlaster FM");
+
+ if (port[dev] != SNDRV_AUTO_PORT) {
+ err = snd_sbdsp_create(card, port[dev], irq[dev],
+ snd_sb8_interrupt, dma8[dev],
+ -1, SB_HW_AUTO, &chip);
+ if (err < 0)
+ return err;
+ } else {
+ /* auto-probe legacy ports */
+ static const unsigned long possible_ports[] = {
+ 0x220, 0x240, 0x260,
+ };
+ int i;
+ for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+ err = snd_sbdsp_create(card, possible_ports[i],
+ irq[dev],
+ snd_sb8_interrupt,
+ dma8[dev],
+ -1,
+ SB_HW_AUTO,
+ &chip);
+ if (err >= 0) {
+ port[dev] = possible_ports[i];
+ break;
+ }
+ }
+ if (i >= ARRAY_SIZE(possible_ports))
+ return -EINVAL;
+ }
+ acard->chip = chip;
+
+ if (chip->hardware >= SB_HW_16) {
+ if (chip->hardware == SB_HW_ALS100)
+ snd_printk(KERN_WARNING "ALS100 chip detected at 0x%lx, try snd-als100 module\n",
+ port[dev]);
+ else
+ snd_printk(KERN_WARNING "SB 16 chip detected at 0x%lx, try snd-sb16 module\n",
+ port[dev]);
+ return -ENODEV;
+ }
+
+ err = snd_sb8dsp_pcm(chip, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_sbmixer_new(chip);
+ if (err < 0)
+ return err;
+
+ if (chip->hardware == SB_HW_10 || chip->hardware == SB_HW_20) {
+ err = snd_opl3_create(card, chip->port + 8, 0,
+ OPL3_HW_AUTO, 1, &opl3);
+ if (err < 0)
+ snd_printk(KERN_WARNING "sb8: no OPL device at 0x%lx\n", chip->port + 8);
+ } else {
+ err = snd_opl3_create(card, chip->port, chip->port + 2,
+ OPL3_HW_AUTO, 1, &opl3);
+ if (err < 0) {
+ snd_printk(KERN_WARNING "sb8: no OPL device at 0x%lx-0x%lx\n",
+ chip->port, chip->port + 2);
+ }
+ }
+ if (err >= 0) {
+ err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_sb8dsp_midi(chip, 0);
+ if (err < 0)
+ return err;
+
+ strcpy(card->driver, chip->hardware == SB_HW_PRO ? "SB Pro" : "SB8");
+ strcpy(card->shortname, chip->name);
+ sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+ chip->name,
+ chip->port,
+ irq[dev], dma8[dev]);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+
+ dev_set_drvdata(pdev, card);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_sb8_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_sb8 *acard = card->private_data;
+ struct snd_sb *chip = acard->chip;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ snd_sbmixer_suspend(chip);
+ return 0;
+}
+
+static int snd_sb8_resume(struct device *dev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_sb8 *acard = card->private_data;
+ struct snd_sb *chip = acard->chip;
+
+ snd_sbdsp_reset(chip);
+ snd_sbmixer_resume(chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+#define DEV_NAME "sb8"
+
+static struct isa_driver snd_sb8_driver = {
+ .match = snd_sb8_match,
+ .probe = snd_sb8_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_sb8_suspend,
+ .resume = snd_sb8_resume,
+#endif
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+module_isa_driver(snd_sb8_driver, SNDRV_CARDS);
diff --git a/sound/isa/sb/sb8_main.c b/sound/isa/sb/sb8_main.c
new file mode 100644
index 0000000000..2ed176a5a5
--- /dev/null
+++ b/sound/isa/sb/sb8_main.c
@@ -0,0 +1,592 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Uros Bizjak <uros@kss-loka.si>
+ *
+ * Routines for control of 8-bit SoundBlaster cards and clones
+ * Please note: I don't have access to old SB8 soundcards.
+ *
+ * --
+ *
+ * Thu Apr 29 20:36:17 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
+ * DSP can't respond to commands whilst in "high speed" mode. Caused
+ * glitching during playback. Fixed.
+ *
+ * Wed Jul 12 22:02:55 CEST 2000 Uros Bizjak <uros@kss-loka.si>
+ * Cleaned up and rewrote lowlevel routines.
+ */
+
+#include <linux/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("Routines for control of 8-bit SoundBlaster cards and clones");
+MODULE_LICENSE("GPL");
+
+#define SB8_CLOCK 1000000
+#define SB8_DEN(v) ((SB8_CLOCK + (v) / 2) / (v))
+#define SB8_RATE(v) (SB8_CLOCK / SB8_DEN(v))
+
+static const struct snd_ratnum clock = {
+ .num = SB8_CLOCK,
+ .den_min = 1,
+ .den_max = 256,
+ .den_step = 1,
+};
+
+static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clock = {
+ .nrats = 1,
+ .rats = &clock,
+};
+
+static const struct snd_ratnum stereo_clocks[] = {
+ {
+ .num = SB8_CLOCK,
+ .den_min = SB8_DEN(22050),
+ .den_max = SB8_DEN(22050),
+ .den_step = 1,
+ },
+ {
+ .num = SB8_CLOCK,
+ .den_min = SB8_DEN(11025),
+ .den_max = SB8_DEN(11025),
+ .den_step = 1,
+ }
+};
+
+static int snd_sb8_hw_constraint_rate_channels(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ if (c->min > 1) {
+ unsigned int num = 0, den = 0;
+ int err = snd_interval_ratnum(hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE),
+ 2, stereo_clocks, &num, &den);
+ if (err >= 0 && den) {
+ params->rate_num = num;
+ params->rate_den = den;
+ }
+ return err;
+ }
+ return 0;
+}
+
+static int snd_sb8_hw_constraint_channels_rate(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ if (r->min > SB8_RATE(22050) || r->max <= SB8_RATE(11025)) {
+ struct snd_interval t = { .min = 1, .max = 1 };
+ return snd_interval_refine(hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS), &t);
+ }
+ return 0;
+}
+
+static int snd_sb8_playback_prepare(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int mixreg, rate, size, count;
+ unsigned char format;
+ unsigned char stereo = runtime->channels > 1;
+ int dma;
+
+ rate = runtime->rate;
+ switch (chip->hardware) {
+ case SB_HW_JAZZ16:
+ if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
+ if (chip->mode & SB_MODE_CAPTURE_16)
+ return -EBUSY;
+ else
+ chip->mode |= SB_MODE_PLAYBACK_16;
+ }
+ chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
+ break;
+ case SB_HW_PRO:
+ if (runtime->channels > 1) {
+ if (snd_BUG_ON(rate != SB8_RATE(11025) &&
+ rate != SB8_RATE(22050)))
+ return -EINVAL;
+ chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
+ break;
+ }
+ fallthrough;
+ case SB_HW_201:
+ if (rate > 23000) {
+ chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
+ break;
+ }
+ fallthrough;
+ case SB_HW_20:
+ chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
+ break;
+ case SB_HW_10:
+ chip->playback_format = SB_DSP_OUTPUT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (chip->mode & SB_MODE_PLAYBACK_16) {
+ format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
+ dma = chip->dma16;
+ } else {
+ format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
+ chip->mode |= SB_MODE_PLAYBACK_8;
+ dma = chip->dma8;
+ }
+ size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
+ count = chip->p_period_size = snd_pcm_lib_period_bytes(substream);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON);
+ if (chip->hardware == SB_HW_JAZZ16)
+ snd_sbdsp_command(chip, format);
+ else if (stereo) {
+ /* set playback stereo mode */
+ spin_lock(&chip->mixer_lock);
+ mixreg = snd_sbmixer_read(chip, SB_DSP_STEREO_SW);
+ snd_sbmixer_write(chip, SB_DSP_STEREO_SW, mixreg | 0x02);
+ spin_unlock(&chip->mixer_lock);
+
+ /* Soundblaster hardware programming reference guide, 3-23 */
+ snd_sbdsp_command(chip, SB_DSP_DMA8_EXIT);
+ runtime->dma_area[0] = 0x80;
+ snd_dma_program(dma, runtime->dma_addr, 1, DMA_MODE_WRITE);
+ /* force interrupt */
+ snd_sbdsp_command(chip, SB_DSP_OUTPUT);
+ snd_sbdsp_command(chip, 0);
+ snd_sbdsp_command(chip, 0);
+ }
+ snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
+ if (stereo) {
+ snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
+ spin_lock(&chip->mixer_lock);
+ /* save output filter status and turn it off */
+ mixreg = snd_sbmixer_read(chip, SB_DSP_PLAYBACK_FILT);
+ snd_sbmixer_write(chip, SB_DSP_PLAYBACK_FILT, mixreg | 0x20);
+ spin_unlock(&chip->mixer_lock);
+ /* just use force_mode16 for temporary storate... */
+ chip->force_mode16 = mixreg;
+ } else {
+ snd_sbdsp_command(chip, 256 - runtime->rate_den);
+ }
+ if (chip->playback_format != SB_DSP_OUTPUT) {
+ if (chip->mode & SB_MODE_PLAYBACK_16)
+ count /= 2;
+ count--;
+ snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
+ snd_sbdsp_command(chip, count & 0xff);
+ snd_sbdsp_command(chip, count >> 8);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_dma_program(dma, runtime->dma_addr,
+ size, DMA_MODE_WRITE | DMA_AUTOINIT);
+ return 0;
+}
+
+static int snd_sb8_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ unsigned int count;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ snd_sbdsp_command(chip, chip->playback_format);
+ if (chip->playback_format == SB_DSP_OUTPUT) {
+ count = chip->p_period_size - 1;
+ snd_sbdsp_command(chip, count & 0xff);
+ snd_sbdsp_command(chip, count >> 8);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (chip->playback_format == SB_DSP_HI_OUTPUT_AUTO) {
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_sbdsp_reset(chip);
+ if (runtime->channels > 1) {
+ spin_lock(&chip->mixer_lock);
+ /* restore output filter and set hardware to mono mode */
+ snd_sbmixer_write(chip, SB_DSP_STEREO_SW, chip->force_mode16 & ~0x02);
+ spin_unlock(&chip->mixer_lock);
+ }
+ } else {
+ snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+ }
+ snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_sb8_capture_prepare(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int mixreg, rate, size, count;
+ unsigned char format;
+ unsigned char stereo = runtime->channels > 1;
+ int dma;
+
+ rate = runtime->rate;
+ switch (chip->hardware) {
+ case SB_HW_JAZZ16:
+ if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
+ if (chip->mode & SB_MODE_PLAYBACK_16)
+ return -EBUSY;
+ else
+ chip->mode |= SB_MODE_CAPTURE_16;
+ }
+ chip->capture_format = SB_DSP_LO_INPUT_AUTO;
+ break;
+ case SB_HW_PRO:
+ if (runtime->channels > 1) {
+ if (snd_BUG_ON(rate != SB8_RATE(11025) &&
+ rate != SB8_RATE(22050)))
+ return -EINVAL;
+ chip->capture_format = SB_DSP_HI_INPUT_AUTO;
+ break;
+ }
+ chip->capture_format = (rate > 23000) ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO;
+ break;
+ case SB_HW_201:
+ if (rate > 13000) {
+ chip->capture_format = SB_DSP_HI_INPUT_AUTO;
+ break;
+ }
+ fallthrough;
+ case SB_HW_20:
+ chip->capture_format = SB_DSP_LO_INPUT_AUTO;
+ break;
+ case SB_HW_10:
+ chip->capture_format = SB_DSP_INPUT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (chip->mode & SB_MODE_CAPTURE_16) {
+ format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
+ dma = chip->dma16;
+ } else {
+ format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
+ chip->mode |= SB_MODE_CAPTURE_8;
+ dma = chip->dma8;
+ }
+ size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
+ count = chip->c_period_size = snd_pcm_lib_period_bytes(substream);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+ if (chip->hardware == SB_HW_JAZZ16)
+ snd_sbdsp_command(chip, format);
+ else if (stereo)
+ snd_sbdsp_command(chip, SB_DSP_STEREO_8BIT);
+ snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
+ if (stereo) {
+ snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
+ spin_lock(&chip->mixer_lock);
+ /* save input filter status and turn it off */
+ mixreg = snd_sbmixer_read(chip, SB_DSP_CAPTURE_FILT);
+ snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, mixreg | 0x20);
+ spin_unlock(&chip->mixer_lock);
+ /* just use force_mode16 for temporary storate... */
+ chip->force_mode16 = mixreg;
+ } else {
+ snd_sbdsp_command(chip, 256 - runtime->rate_den);
+ }
+ if (chip->capture_format != SB_DSP_INPUT) {
+ if (chip->mode & SB_MODE_PLAYBACK_16)
+ count /= 2;
+ count--;
+ snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
+ snd_sbdsp_command(chip, count & 0xff);
+ snd_sbdsp_command(chip, count >> 8);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_dma_program(dma, runtime->dma_addr,
+ size, DMA_MODE_READ | DMA_AUTOINIT);
+ return 0;
+}
+
+static int snd_sb8_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ unsigned int count;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ snd_sbdsp_command(chip, chip->capture_format);
+ if (chip->capture_format == SB_DSP_INPUT) {
+ count = chip->c_period_size - 1;
+ snd_sbdsp_command(chip, count & 0xff);
+ snd_sbdsp_command(chip, count >> 8);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (chip->capture_format == SB_DSP_HI_INPUT_AUTO) {
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_sbdsp_reset(chip);
+ if (runtime->channels > 1) {
+ /* restore input filter status */
+ spin_lock(&chip->mixer_lock);
+ snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, chip->force_mode16);
+ spin_unlock(&chip->mixer_lock);
+ /* set hardware to mono mode */
+ snd_sbdsp_command(chip, SB_DSP_MONO_8BIT);
+ }
+ } else {
+ snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+ }
+ snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+irqreturn_t snd_sb8dsp_interrupt(struct snd_sb *chip)
+{
+ struct snd_pcm_substream *substream;
+
+ snd_sb_ack_8bit(chip);
+ switch (chip->mode) {
+ case SB_MODE_PLAYBACK_16: /* ok.. playback is active */
+ if (chip->hardware != SB_HW_JAZZ16)
+ break;
+ fallthrough;
+ case SB_MODE_PLAYBACK_8:
+ substream = chip->playback_substream;
+ if (chip->playback_format == SB_DSP_OUTPUT)
+ snd_sb8_playback_trigger(substream, SNDRV_PCM_TRIGGER_START);
+ snd_pcm_period_elapsed(substream);
+ break;
+ case SB_MODE_CAPTURE_16:
+ if (chip->hardware != SB_HW_JAZZ16)
+ break;
+ fallthrough;
+ case SB_MODE_CAPTURE_8:
+ substream = chip->capture_substream;
+ if (chip->capture_format == SB_DSP_INPUT)
+ snd_sb8_capture_trigger(substream, SNDRV_PCM_TRIGGER_START);
+ snd_pcm_period_elapsed(substream);
+ break;
+ }
+ return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_sb8_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+ int dma;
+
+ if (chip->mode & SB_MODE_PLAYBACK_8)
+ dma = chip->dma8;
+ else if (chip->mode & SB_MODE_PLAYBACK_16)
+ dma = chip->dma16;
+ else
+ return 0;
+ ptr = snd_dma_pointer(dma, chip->p_dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sb8_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+ int dma;
+
+ if (chip->mode & SB_MODE_CAPTURE_8)
+ dma = chip->dma8;
+ else if (chip->mode & SB_MODE_CAPTURE_16)
+ dma = chip->dma16;
+ else
+ return 0;
+ ptr = snd_dma_pointer(dma, chip->c_dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static const struct snd_pcm_hardware snd_sb8_playback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_U8,
+ .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050),
+ .rate_min = 4000,
+ .rate_max = 23000,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = 65536,
+ .period_bytes_min = 64,
+ .period_bytes_max = 65536,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_sb8_capture =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_U8,
+ .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_11025),
+ .rate_min = 4000,
+ .rate_max = 13000,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = 65536,
+ .period_bytes_min = 64,
+ .period_bytes_max = 65536,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/*
+ *
+ */
+
+static int snd_sb8_open(struct snd_pcm_substream *substream)
+{
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->open_lock, flags);
+ if (chip->open) {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return -EAGAIN;
+ }
+ chip->open |= SB_OPEN_PCM;
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ chip->playback_substream = substream;
+ runtime->hw = snd_sb8_playback;
+ } else {
+ chip->capture_substream = substream;
+ runtime->hw = snd_sb8_capture;
+ }
+ switch (chip->hardware) {
+ case SB_HW_JAZZ16:
+ if (chip->dma16 == 5 || chip->dma16 == 7)
+ runtime->hw.formats |= SNDRV_PCM_FMTBIT_S16_LE;
+ runtime->hw.rates |= SNDRV_PCM_RATE_8000_48000;
+ runtime->hw.rate_min = 4000;
+ runtime->hw.rate_max = 50000;
+ runtime->hw.channels_max = 2;
+ break;
+ case SB_HW_PRO:
+ runtime->hw.rate_max = 44100;
+ runtime->hw.channels_max = 2;
+ snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ snd_sb8_hw_constraint_rate_channels, NULL,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+ snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ snd_sb8_hw_constraint_channels_rate, NULL,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+ break;
+ case SB_HW_201:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ runtime->hw.rate_max = 44100;
+ } else {
+ runtime->hw.rate_max = 15000;
+ }
+ break;
+ default:
+ break;
+ }
+ snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_clock);
+ if (chip->dma8 > 3 || chip->dma16 >= 0) {
+ snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 2);
+ snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 2);
+ runtime->hw.buffer_bytes_max = 128 * 1024 * 1024;
+ runtime->hw.period_bytes_max = 128 * 1024 * 1024;
+ }
+ return 0;
+}
+
+static int snd_sb8_close(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip = snd_pcm_substream_chip(substream);
+
+ chip->playback_substream = NULL;
+ chip->capture_substream = NULL;
+ spin_lock_irqsave(&chip->open_lock, flags);
+ chip->open &= ~SB_OPEN_PCM;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ chip->mode &= ~SB_MODE_PLAYBACK;
+ else
+ chip->mode &= ~SB_MODE_CAPTURE;
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return 0;
+}
+
+/*
+ * Initialization part
+ */
+
+static const struct snd_pcm_ops snd_sb8_playback_ops = {
+ .open = snd_sb8_open,
+ .close = snd_sb8_close,
+ .prepare = snd_sb8_playback_prepare,
+ .trigger = snd_sb8_playback_trigger,
+ .pointer = snd_sb8_playback_pointer,
+};
+
+static const struct snd_pcm_ops snd_sb8_capture_ops = {
+ .open = snd_sb8_open,
+ .close = snd_sb8_close,
+ .prepare = snd_sb8_capture_prepare,
+ .trigger = snd_sb8_capture_trigger,
+ .pointer = snd_sb8_capture_pointer,
+};
+
+int snd_sb8dsp_pcm(struct snd_sb *chip, int device)
+{
+ struct snd_card *card = chip->card;
+ struct snd_pcm *pcm;
+ int err;
+ size_t max_prealloc = 64 * 1024;
+
+ err = snd_pcm_new(card, "SB8 DSP", device, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+ sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
+ pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+ pcm->private_data = chip;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb8_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb8_capture_ops);
+
+ if (chip->dma8 > 3 || chip->dma16 >= 0)
+ max_prealloc = 128 * 1024;
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, 64*1024, max_prealloc);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(snd_sb8dsp_pcm);
+EXPORT_SYMBOL(snd_sb8dsp_interrupt);
+ /* sb8_midi.c */
+EXPORT_SYMBOL(snd_sb8dsp_midi_interrupt);
+EXPORT_SYMBOL(snd_sb8dsp_midi);
diff --git a/sound/isa/sb/sb8_midi.c b/sound/isa/sb/sb8_midi.c
new file mode 100644
index 0000000000..618366d5d9
--- /dev/null
+++ b/sound/isa/sb/sb8_midi.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for control of SoundBlaster cards - MIDI interface
+ *
+ * --
+ *
+ * Sun May 9 22:54:38 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
+ * Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from
+ * working.
+ *
+ * Sun May 11 12:34:56 UTC 2003 Clemens Ladisch <clemens@ladisch.de>
+ * Added full duplex UART mode for DSP version 2.0 and later.
+ */
+
+#include <linux/io.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+
+irqreturn_t snd_sb8dsp_midi_interrupt(struct snd_sb *chip)
+{
+ struct snd_rawmidi *rmidi;
+ int max = 64;
+ char byte;
+
+ if (!chip)
+ return IRQ_NONE;
+
+ rmidi = chip->rmidi;
+ if (!rmidi) {
+ inb(SBP(chip, DATA_AVAIL)); /* ack interrupt */
+ return IRQ_NONE;
+ }
+
+ spin_lock(&chip->midi_input_lock);
+ while (max-- > 0) {
+ if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+ byte = inb(SBP(chip, READ));
+ if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
+ snd_rawmidi_receive(chip->midi_substream_input, &byte, 1);
+ }
+ }
+ }
+ spin_unlock(&chip->midi_input_lock);
+ return IRQ_HANDLED;
+}
+
+static int snd_sb8dsp_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip;
+ unsigned int valid_open_flags;
+
+ chip = substream->rmidi->private_data;
+ valid_open_flags = chip->hardware >= SB_HW_20
+ ? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0;
+ spin_lock_irqsave(&chip->open_lock, flags);
+ if (chip->open & ~valid_open_flags) {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return -EAGAIN;
+ }
+ chip->open |= SB_OPEN_MIDI_INPUT;
+ chip->midi_substream_input = substream;
+ if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ snd_sbdsp_reset(chip); /* reset DSP */
+ if (chip->hardware >= SB_HW_20)
+ snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
+ } else {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ }
+ return 0;
+}
+
+static int snd_sb8dsp_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip;
+ unsigned int valid_open_flags;
+
+ chip = substream->rmidi->private_data;
+ valid_open_flags = chip->hardware >= SB_HW_20
+ ? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0;
+ spin_lock_irqsave(&chip->open_lock, flags);
+ if (chip->open & ~valid_open_flags) {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ return -EAGAIN;
+ }
+ chip->open |= SB_OPEN_MIDI_OUTPUT;
+ chip->midi_substream_output = substream;
+ if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ snd_sbdsp_reset(chip); /* reset DSP */
+ if (chip->hardware >= SB_HW_20)
+ snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
+ } else {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ }
+ return 0;
+}
+
+static int snd_sb8dsp_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip;
+
+ chip = substream->rmidi->private_data;
+ spin_lock_irqsave(&chip->open_lock, flags);
+ chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER);
+ chip->midi_substream_input = NULL;
+ if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ snd_sbdsp_reset(chip); /* reset DSP */
+ } else {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ }
+ return 0;
+}
+
+static int snd_sb8dsp_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip;
+
+ chip = substream->rmidi->private_data;
+ del_timer_sync(&chip->midi_timer);
+ spin_lock_irqsave(&chip->open_lock, flags);
+ chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER);
+ chip->midi_substream_output = NULL;
+ if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ snd_sbdsp_reset(chip); /* reset DSP */
+ } else {
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ }
+ return 0;
+}
+
+static void snd_sb8dsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ struct snd_sb *chip;
+
+ chip = substream->rmidi->private_data;
+ spin_lock_irqsave(&chip->open_lock, flags);
+ if (up) {
+ if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) {
+ if (chip->hardware < SB_HW_20)
+ snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
+ chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER;
+ }
+ } else {
+ if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
+ if (chip->hardware < SB_HW_20)
+ snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
+ chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER;
+ }
+ }
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+}
+
+static void snd_sb8dsp_midi_output_write(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ struct snd_sb *chip;
+ char byte;
+ int max = 32;
+
+ /* how big is Tx FIFO? */
+ chip = substream->rmidi->private_data;
+ while (max-- > 0) {
+ spin_lock_irqsave(&chip->open_lock, flags);
+ if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) {
+ chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
+ del_timer(&chip->midi_timer);
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ break;
+ }
+ if (chip->hardware >= SB_HW_20) {
+ int timeout = 8;
+ while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0)
+ ;
+ if (timeout == 0) {
+ /* Tx FIFO full - try again later */
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ break;
+ }
+ outb(byte, SBP(chip, WRITE));
+ } else {
+ snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT);
+ snd_sbdsp_command(chip, byte);
+ }
+ snd_rawmidi_transmit_ack(substream, 1);
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ }
+}
+
+static void snd_sb8dsp_midi_output_timer(struct timer_list *t)
+{
+ struct snd_sb *chip = from_timer(chip, t, midi_timer);
+ struct snd_rawmidi_substream *substream = chip->midi_substream_output;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->open_lock, flags);
+ mod_timer(&chip->midi_timer, 1 + jiffies);
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+ snd_sb8dsp_midi_output_write(substream);
+}
+
+static void snd_sb8dsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ struct snd_sb *chip;
+
+ chip = substream->rmidi->private_data;
+ spin_lock_irqsave(&chip->open_lock, flags);
+ if (up) {
+ if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) {
+ mod_timer(&chip->midi_timer, 1 + jiffies);
+ chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER;
+ }
+ } else {
+ if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) {
+ chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
+ }
+ }
+ spin_unlock_irqrestore(&chip->open_lock, flags);
+
+ if (up)
+ snd_sb8dsp_midi_output_write(substream);
+}
+
+static const struct snd_rawmidi_ops snd_sb8dsp_midi_output =
+{
+ .open = snd_sb8dsp_midi_output_open,
+ .close = snd_sb8dsp_midi_output_close,
+ .trigger = snd_sb8dsp_midi_output_trigger,
+};
+
+static const struct snd_rawmidi_ops snd_sb8dsp_midi_input =
+{
+ .open = snd_sb8dsp_midi_input_open,
+ .close = snd_sb8dsp_midi_input_close,
+ .trigger = snd_sb8dsp_midi_input_trigger,
+};
+
+int snd_sb8dsp_midi(struct snd_sb *chip, int device)
+{
+ struct snd_rawmidi *rmidi;
+ int err;
+
+ err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi);
+ if (err < 0)
+ return err;
+ strcpy(rmidi->name, "SB8 MIDI");
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input);
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT;
+ if (chip->hardware >= SB_HW_20)
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->private_data = chip;
+ timer_setup(&chip->midi_timer, snd_sb8dsp_midi_output_timer, 0);
+ chip->rmidi = rmidi;
+ return 0;
+}
diff --git a/sound/isa/sb/sb_common.c b/sound/isa/sb/sb_common.c
new file mode 100644
index 0000000000..c0e319d142
--- /dev/null
+++ b/sound/isa/sb/sb_common.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Uros Bizjak <uros@kss-loka.si>
+ *
+ * Lowlevel routines for control of Sound Blaster cards
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ALSA lowlevel driver for Sound Blaster cards");
+MODULE_LICENSE("GPL");
+
+#define BUSY_LOOPS 100000
+
+#undef IO_DEBUG
+
+int snd_sbdsp_command(struct snd_sb *chip, unsigned char val)
+{
+ int i;
+#ifdef IO_DEBUG
+ snd_printk(KERN_DEBUG "command 0x%x\n", val);
+#endif
+ for (i = BUSY_LOOPS; i; i--)
+ if ((inb(SBP(chip, STATUS)) & 0x80) == 0) {
+ outb(val, SBP(chip, COMMAND));
+ return 1;
+ }
+ snd_printd("%s [0x%lx]: timeout (0x%x)\n", __func__, chip->port, val);
+ return 0;
+}
+
+int snd_sbdsp_get_byte(struct snd_sb *chip)
+{
+ int val;
+ int i;
+ for (i = BUSY_LOOPS; i; i--) {
+ if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+ val = inb(SBP(chip, READ));
+#ifdef IO_DEBUG
+ snd_printk(KERN_DEBUG "get_byte 0x%x\n", val);
+#endif
+ return val;
+ }
+ }
+ snd_printd("%s [0x%lx]: timeout\n", __func__, chip->port);
+ return -ENODEV;
+}
+
+int snd_sbdsp_reset(struct snd_sb *chip)
+{
+ int i;
+
+ outb(1, SBP(chip, RESET));
+ udelay(10);
+ outb(0, SBP(chip, RESET));
+ udelay(30);
+ for (i = BUSY_LOOPS; i; i--)
+ if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+ if (inb(SBP(chip, READ)) == 0xaa)
+ return 0;
+ else
+ break;
+ }
+ snd_printdd("%s [0x%lx] failed...\n", __func__, chip->port);
+ return -ENODEV;
+}
+
+static int snd_sbdsp_version(struct snd_sb * chip)
+{
+ unsigned int result;
+
+ snd_sbdsp_command(chip, SB_DSP_GET_VERSION);
+ result = (short) snd_sbdsp_get_byte(chip) << 8;
+ result |= (short) snd_sbdsp_get_byte(chip);
+ return result;
+}
+
+static int snd_sbdsp_probe(struct snd_sb * chip)
+{
+ int version;
+ int major, minor;
+ char *str;
+ unsigned long flags;
+
+ /*
+ * initialization sequence
+ */
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (snd_sbdsp_reset(chip) < 0) {
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return -ENODEV;
+ }
+ version = snd_sbdsp_version(chip);
+ if (version < 0) {
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return -ENODEV;
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ major = version >> 8;
+ minor = version & 0xff;
+ snd_printdd("SB [0x%lx]: DSP chip found, version = %i.%i\n",
+ chip->port, major, minor);
+
+ switch (chip->hardware) {
+ case SB_HW_AUTO:
+ switch (major) {
+ case 1:
+ chip->hardware = SB_HW_10;
+ str = "1.0";
+ break;
+ case 2:
+ if (minor) {
+ chip->hardware = SB_HW_201;
+ str = "2.01+";
+ } else {
+ chip->hardware = SB_HW_20;
+ str = "2.0";
+ }
+ break;
+ case 3:
+ chip->hardware = SB_HW_PRO;
+ str = "Pro";
+ break;
+ case 4:
+ chip->hardware = SB_HW_16;
+ str = "16";
+ break;
+ default:
+ snd_printk(KERN_INFO "SB [0x%lx]: unknown DSP chip version %i.%i\n",
+ chip->port, major, minor);
+ return -ENODEV;
+ }
+ break;
+ case SB_HW_ALS100:
+ str = "16 (ALS-100)";
+ break;
+ case SB_HW_ALS4000:
+ str = "16 (ALS-4000)";
+ break;
+ case SB_HW_DT019X:
+ str = "(DT019X/ALS007)";
+ break;
+ case SB_HW_CS5530:
+ str = "16 (CS5530)";
+ break;
+ case SB_HW_JAZZ16:
+ str = "Pro (Jazz16)";
+ break;
+ default:
+ return -ENODEV;
+ }
+ sprintf(chip->name, "Sound Blaster %s", str);
+ chip->version = (major << 8) | minor;
+ return 0;
+}
+
+int snd_sbdsp_create(struct snd_card *card,
+ unsigned long port,
+ int irq,
+ irq_handler_t irq_handler,
+ int dma8,
+ int dma16,
+ unsigned short hardware,
+ struct snd_sb **r_chip)
+{
+ struct snd_sb *chip;
+ int err;
+
+ if (snd_BUG_ON(!r_chip))
+ return -EINVAL;
+ *r_chip = NULL;
+ chip = devm_kzalloc(card->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+ spin_lock_init(&chip->reg_lock);
+ spin_lock_init(&chip->open_lock);
+ spin_lock_init(&chip->midi_input_lock);
+ spin_lock_init(&chip->mixer_lock);
+ chip->irq = -1;
+ chip->dma8 = -1;
+ chip->dma16 = -1;
+ chip->port = port;
+
+ if (devm_request_irq(card->dev, irq, irq_handler,
+ (hardware == SB_HW_ALS4000 ||
+ hardware == SB_HW_CS5530) ?
+ IRQF_SHARED : 0,
+ "SoundBlaster", (void *) chip)) {
+ snd_printk(KERN_ERR "sb: can't grab irq %d\n", irq);
+ return -EBUSY;
+ }
+ chip->irq = irq;
+ card->sync_irq = chip->irq;
+
+ if (hardware == SB_HW_ALS4000)
+ goto __skip_allocation;
+
+ chip->res_port = devm_request_region(card->dev, port, 16,
+ "SoundBlaster");
+ if (!chip->res_port) {
+ snd_printk(KERN_ERR "sb: can't grab port 0x%lx\n", port);
+ return -EBUSY;
+ }
+
+#ifdef CONFIG_ISA
+ if (dma8 >= 0 && snd_devm_request_dma(card->dev, dma8,
+ "SoundBlaster - 8bit")) {
+ snd_printk(KERN_ERR "sb: can't grab DMA8 %d\n", dma8);
+ return -EBUSY;
+ }
+ chip->dma8 = dma8;
+ if (dma16 >= 0) {
+ if (hardware != SB_HW_ALS100 && (dma16 < 5 || dma16 > 7)) {
+ /* no duplex */
+ dma16 = -1;
+ } else if (snd_devm_request_dma(card->dev, dma16,
+ "SoundBlaster - 16bit")) {
+ snd_printk(KERN_ERR "sb: can't grab DMA16 %d\n", dma16);
+ return -EBUSY;
+ }
+ }
+ chip->dma16 = dma16;
+#endif
+
+ __skip_allocation:
+ chip->card = card;
+ chip->hardware = hardware;
+ err = snd_sbdsp_probe(chip);
+ if (err < 0)
+ return err;
+ *r_chip = chip;
+ return 0;
+}
+
+EXPORT_SYMBOL(snd_sbdsp_command);
+EXPORT_SYMBOL(snd_sbdsp_get_byte);
+EXPORT_SYMBOL(snd_sbdsp_reset);
+EXPORT_SYMBOL(snd_sbdsp_create);
+/* sb_mixer.c */
+EXPORT_SYMBOL(snd_sbmixer_write);
+EXPORT_SYMBOL(snd_sbmixer_read);
+EXPORT_SYMBOL(snd_sbmixer_new);
+EXPORT_SYMBOL(snd_sbmixer_add_ctl);
+#ifdef CONFIG_PM
+EXPORT_SYMBOL(snd_sbmixer_suspend);
+EXPORT_SYMBOL(snd_sbmixer_resume);
+#endif
diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c
new file mode 100644
index 0000000000..fffd681e5b
--- /dev/null
+++ b/sound/isa/sb/sb_mixer.c
@@ -0,0 +1,951 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for Sound Blaster mixer control
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/control.h>
+
+#undef IO_DEBUG
+
+void snd_sbmixer_write(struct snd_sb *chip, unsigned char reg, unsigned char data)
+{
+ outb(reg, SBP(chip, MIXER_ADDR));
+ udelay(10);
+ outb(data, SBP(chip, MIXER_DATA));
+ udelay(10);
+#ifdef IO_DEBUG
+ snd_printk(KERN_DEBUG "mixer_write 0x%x 0x%x\n", reg, data);
+#endif
+}
+
+unsigned char snd_sbmixer_read(struct snd_sb *chip, unsigned char reg)
+{
+ unsigned char result;
+
+ outb(reg, SBP(chip, MIXER_ADDR));
+ udelay(10);
+ result = inb(SBP(chip, MIXER_DATA));
+ udelay(10);
+#ifdef IO_DEBUG
+ snd_printk(KERN_DEBUG "mixer_read 0x%x 0x%x\n", reg, result);
+#endif
+ return result;
+}
+
+/*
+ * Single channel mixer element
+ */
+
+static int snd_sbmixer_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 16) & 0xff;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ unsigned char val;
+
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ val = (snd_sbmixer_read(sb, reg) >> shift) & mask;
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ ucontrol->value.integer.value[0] = val;
+ return 0;
+}
+
+static int snd_sbmixer_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 16) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int change;
+ unsigned char val, oval;
+
+ val = (ucontrol->value.integer.value[0] & mask) << shift;
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ oval = snd_sbmixer_read(sb, reg);
+ val = (oval & ~(mask << shift)) | val;
+ change = val != oval;
+ if (change)
+ snd_sbmixer_write(sb, reg, val);
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ return change;
+}
+
+/*
+ * Double channel mixer element
+ */
+
+static int snd_sbmixer_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+
+static int snd_sbmixer_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int left_shift = (kcontrol->private_value >> 16) & 0x07;
+ int right_shift = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ unsigned char left, right;
+
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ left = (snd_sbmixer_read(sb, left_reg) >> left_shift) & mask;
+ right = (snd_sbmixer_read(sb, right_reg) >> right_shift) & mask;
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ ucontrol->value.integer.value[0] = left;
+ ucontrol->value.integer.value[1] = right;
+ return 0;
+}
+
+static int snd_sbmixer_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int left_shift = (kcontrol->private_value >> 16) & 0x07;
+ int right_shift = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int change;
+ unsigned char left, right, oleft, oright;
+
+ left = (ucontrol->value.integer.value[0] & mask) << left_shift;
+ right = (ucontrol->value.integer.value[1] & mask) << right_shift;
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ if (left_reg == right_reg) {
+ oleft = snd_sbmixer_read(sb, left_reg);
+ left = (oleft & ~((mask << left_shift) | (mask << right_shift))) | left | right;
+ change = left != oleft;
+ if (change)
+ snd_sbmixer_write(sb, left_reg, left);
+ } else {
+ oleft = snd_sbmixer_read(sb, left_reg);
+ oright = snd_sbmixer_read(sb, right_reg);
+ left = (oleft & ~(mask << left_shift)) | left;
+ right = (oright & ~(mask << right_shift)) | right;
+ change = left != oleft || right != oright;
+ if (change) {
+ snd_sbmixer_write(sb, left_reg, left);
+ snd_sbmixer_write(sb, right_reg, right);
+ }
+ }
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ return change;
+}
+
+/*
+ * DT-019x / ALS-007 capture/input switch
+ */
+
+static int snd_dt019x_input_sw_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[5] = {
+ "CD", "Mic", "Line", "Synth", "Master"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, 5, texts);
+}
+
+static int snd_dt019x_input_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ unsigned char oval;
+
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ switch (oval & 0x07) {
+ case SB_DT019X_CAP_CD:
+ ucontrol->value.enumerated.item[0] = 0;
+ break;
+ case SB_DT019X_CAP_MIC:
+ ucontrol->value.enumerated.item[0] = 1;
+ break;
+ case SB_DT019X_CAP_LINE:
+ ucontrol->value.enumerated.item[0] = 2;
+ break;
+ case SB_DT019X_CAP_MAIN:
+ ucontrol->value.enumerated.item[0] = 4;
+ break;
+ /* To record the synth on these cards you must record the main. */
+ /* Thus SB_DT019X_CAP_SYNTH == SB_DT019X_CAP_MAIN and would cause */
+ /* duplicate case labels if left uncommented. */
+ /* case SB_DT019X_CAP_SYNTH:
+ * ucontrol->value.enumerated.item[0] = 3;
+ * break;
+ */
+ default:
+ ucontrol->value.enumerated.item[0] = 4;
+ break;
+ }
+ return 0;
+}
+
+static int snd_dt019x_input_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned char nval, oval;
+
+ if (ucontrol->value.enumerated.item[0] > 4)
+ return -EINVAL;
+ switch (ucontrol->value.enumerated.item[0]) {
+ case 0:
+ nval = SB_DT019X_CAP_CD;
+ break;
+ case 1:
+ nval = SB_DT019X_CAP_MIC;
+ break;
+ case 2:
+ nval = SB_DT019X_CAP_LINE;
+ break;
+ case 3:
+ nval = SB_DT019X_CAP_SYNTH;
+ break;
+ case 4:
+ nval = SB_DT019X_CAP_MAIN;
+ break;
+ default:
+ nval = SB_DT019X_CAP_MAIN;
+ }
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
+ change = nval != oval;
+ if (change)
+ snd_sbmixer_write(sb, SB_DT019X_CAPTURE_SW, nval);
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ return change;
+}
+
+/*
+ * ALS4000 mono recording control switch
+ */
+
+static int snd_als4k_mono_capture_route_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[3] = {
+ "L chan only", "R chan only", "L ch/2 + R ch/2"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+static int snd_als4k_mono_capture_route_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ unsigned char oval;
+
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL);
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ oval >>= 6;
+ if (oval > 2)
+ oval = 2;
+
+ ucontrol->value.enumerated.item[0] = oval;
+ return 0;
+}
+
+static int snd_als4k_mono_capture_route_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned char nval, oval;
+
+ if (ucontrol->value.enumerated.item[0] > 2)
+ return -EINVAL;
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL);
+
+ nval = (oval & ~(3 << 6))
+ | (ucontrol->value.enumerated.item[0] << 6);
+ change = nval != oval;
+ if (change)
+ snd_sbmixer_write(sb, SB_ALS4000_MONO_IO_CTRL, nval);
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ return change;
+}
+
+/*
+ * SBPRO input multiplexer
+ */
+
+static int snd_sb8mixer_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[3] = {
+ "Mic", "CD", "Line"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+
+static int snd_sb8mixer_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ unsigned char oval;
+
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ switch ((oval >> 0x01) & 0x03) {
+ case SB_DSP_MIXS_CD:
+ ucontrol->value.enumerated.item[0] = 1;
+ break;
+ case SB_DSP_MIXS_LINE:
+ ucontrol->value.enumerated.item[0] = 2;
+ break;
+ default:
+ ucontrol->value.enumerated.item[0] = 0;
+ break;
+ }
+ return 0;
+}
+
+static int snd_sb8mixer_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int change;
+ unsigned char nval, oval;
+
+ if (ucontrol->value.enumerated.item[0] > 2)
+ return -EINVAL;
+ switch (ucontrol->value.enumerated.item[0]) {
+ case 1:
+ nval = SB_DSP_MIXS_CD;
+ break;
+ case 2:
+ nval = SB_DSP_MIXS_LINE;
+ break;
+ default:
+ nval = SB_DSP_MIXS_MIC;
+ }
+ nval <<= 1;
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
+ nval |= oval & ~0x06;
+ change = nval != oval;
+ if (change)
+ snd_sbmixer_write(sb, SB_DSP_CAPTURE_SOURCE, nval);
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ return change;
+}
+
+/*
+ * SB16 input switch
+ */
+
+static int snd_sb16mixer_info_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 4;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int snd_sb16mixer_get_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg1 = kcontrol->private_value & 0xff;
+ int reg2 = (kcontrol->private_value >> 8) & 0xff;
+ int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+ int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+ unsigned char val1, val2;
+
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ val1 = snd_sbmixer_read(sb, reg1);
+ val2 = snd_sbmixer_read(sb, reg2);
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ ucontrol->value.integer.value[0] = (val1 >> left_shift) & 0x01;
+ ucontrol->value.integer.value[1] = (val2 >> left_shift) & 0x01;
+ ucontrol->value.integer.value[2] = (val1 >> right_shift) & 0x01;
+ ucontrol->value.integer.value[3] = (val2 >> right_shift) & 0x01;
+ return 0;
+}
+
+static int snd_sb16mixer_put_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg1 = kcontrol->private_value & 0xff;
+ int reg2 = (kcontrol->private_value >> 8) & 0xff;
+ int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+ int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+ int change;
+ unsigned char val1, val2, oval1, oval2;
+
+ spin_lock_irqsave(&sb->mixer_lock, flags);
+ oval1 = snd_sbmixer_read(sb, reg1);
+ oval2 = snd_sbmixer_read(sb, reg2);
+ val1 = oval1 & ~((1 << left_shift) | (1 << right_shift));
+ val2 = oval2 & ~((1 << left_shift) | (1 << right_shift));
+ val1 |= (ucontrol->value.integer.value[0] & 1) << left_shift;
+ val2 |= (ucontrol->value.integer.value[1] & 1) << left_shift;
+ val1 |= (ucontrol->value.integer.value[2] & 1) << right_shift;
+ val2 |= (ucontrol->value.integer.value[3] & 1) << right_shift;
+ change = val1 != oval1 || val2 != oval2;
+ if (change) {
+ snd_sbmixer_write(sb, reg1, val1);
+ snd_sbmixer_write(sb, reg2, val2);
+ }
+ spin_unlock_irqrestore(&sb->mixer_lock, flags);
+ return change;
+}
+
+
+/*
+ */
+/*
+ */
+int snd_sbmixer_add_ctl(struct snd_sb *chip, const char *name, int index, int type, unsigned long value)
+{
+ static const struct snd_kcontrol_new newctls[] = {
+ [SB_MIX_SINGLE] = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = snd_sbmixer_info_single,
+ .get = snd_sbmixer_get_single,
+ .put = snd_sbmixer_put_single,
+ },
+ [SB_MIX_DOUBLE] = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = snd_sbmixer_info_double,
+ .get = snd_sbmixer_get_double,
+ .put = snd_sbmixer_put_double,
+ },
+ [SB_MIX_INPUT_SW] = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = snd_sb16mixer_info_input_sw,
+ .get = snd_sb16mixer_get_input_sw,
+ .put = snd_sb16mixer_put_input_sw,
+ },
+ [SB_MIX_CAPTURE_PRO] = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = snd_sb8mixer_info_mux,
+ .get = snd_sb8mixer_get_mux,
+ .put = snd_sb8mixer_put_mux,
+ },
+ [SB_MIX_CAPTURE_DT019X] = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = snd_dt019x_input_sw_info,
+ .get = snd_dt019x_input_sw_get,
+ .put = snd_dt019x_input_sw_put,
+ },
+ [SB_MIX_MONO_CAPTURE_ALS4K] = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = snd_als4k_mono_capture_route_info,
+ .get = snd_als4k_mono_capture_route_get,
+ .put = snd_als4k_mono_capture_route_put,
+ },
+ };
+ struct snd_kcontrol *ctl;
+ int err;
+
+ ctl = snd_ctl_new1(&newctls[type], chip);
+ if (! ctl)
+ return -ENOMEM;
+ strscpy(ctl->id.name, name, sizeof(ctl->id.name));
+ ctl->id.index = index;
+ ctl->private_value = value;
+ err = snd_ctl_add(chip->card, ctl);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+/*
+ * SB 2.0 specific mixer elements
+ */
+
+static const struct sbmix_elem snd_sb20_controls[] = {
+ SB_SINGLE("Master Playback Volume", SB_DSP20_MASTER_DEV, 1, 7),
+ SB_SINGLE("PCM Playback Volume", SB_DSP20_PCM_DEV, 1, 3),
+ SB_SINGLE("Synth Playback Volume", SB_DSP20_FM_DEV, 1, 7),
+ SB_SINGLE("CD Playback Volume", SB_DSP20_CD_DEV, 1, 7)
+};
+
+static const unsigned char snd_sb20_init_values[][2] = {
+ { SB_DSP20_MASTER_DEV, 0 },
+ { SB_DSP20_FM_DEV, 0 },
+};
+
+/*
+ * SB Pro specific mixer elements
+ */
+static const struct sbmix_elem snd_sbpro_controls[] = {
+ SB_DOUBLE("Master Playback Volume",
+ SB_DSP_MASTER_DEV, SB_DSP_MASTER_DEV, 5, 1, 7),
+ SB_DOUBLE("PCM Playback Volume",
+ SB_DSP_PCM_DEV, SB_DSP_PCM_DEV, 5, 1, 7),
+ SB_SINGLE("PCM Playback Filter", SB_DSP_PLAYBACK_FILT, 5, 1),
+ SB_DOUBLE("Synth Playback Volume",
+ SB_DSP_FM_DEV, SB_DSP_FM_DEV, 5, 1, 7),
+ SB_DOUBLE("CD Playback Volume", SB_DSP_CD_DEV, SB_DSP_CD_DEV, 5, 1, 7),
+ SB_DOUBLE("Line Playback Volume",
+ SB_DSP_LINE_DEV, SB_DSP_LINE_DEV, 5, 1, 7),
+ SB_SINGLE("Mic Playback Volume", SB_DSP_MIC_DEV, 1, 3),
+ {
+ .name = "Capture Source",
+ .type = SB_MIX_CAPTURE_PRO
+ },
+ SB_SINGLE("Capture Filter", SB_DSP_CAPTURE_FILT, 5, 1),
+ SB_SINGLE("Capture Low-Pass Filter", SB_DSP_CAPTURE_FILT, 3, 1)
+};
+
+static const unsigned char snd_sbpro_init_values[][2] = {
+ { SB_DSP_MASTER_DEV, 0 },
+ { SB_DSP_PCM_DEV, 0 },
+ { SB_DSP_FM_DEV, 0 },
+};
+
+/*
+ * SB16 specific mixer elements
+ */
+static const struct sbmix_elem snd_sb16_controls[] = {
+ SB_DOUBLE("Master Playback Volume",
+ SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31),
+ SB_DOUBLE("PCM Playback Volume",
+ SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31),
+ SB16_INPUT_SW("Synth Capture Route",
+ SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 6, 5),
+ SB_DOUBLE("Synth Playback Volume",
+ SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31),
+ SB16_INPUT_SW("CD Capture Route",
+ SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 2, 1),
+ SB_DOUBLE("CD Playback Switch",
+ SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
+ SB_DOUBLE("CD Playback Volume",
+ SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31),
+ SB16_INPUT_SW("Mic Capture Route",
+ SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0),
+ SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
+ SB_SINGLE("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31),
+ SB_SINGLE("Beep Volume", SB_DSP4_SPEAKER_DEV, 6, 3),
+ SB_DOUBLE("Capture Volume",
+ SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3),
+ SB_DOUBLE("Playback Volume",
+ SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3),
+ SB16_INPUT_SW("Line Capture Route",
+ SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 4, 3),
+ SB_DOUBLE("Line Playback Switch",
+ SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
+ SB_DOUBLE("Line Playback Volume",
+ SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31),
+ SB_SINGLE("Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1),
+ SB_SINGLE("3D Enhancement Switch", SB_DSP4_3DSE, 0, 1),
+ SB_DOUBLE("Tone Control - Bass",
+ SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15),
+ SB_DOUBLE("Tone Control - Treble",
+ SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15)
+};
+
+static const unsigned char snd_sb16_init_values[][2] = {
+ { SB_DSP4_MASTER_DEV + 0, 0 },
+ { SB_DSP4_MASTER_DEV + 1, 0 },
+ { SB_DSP4_PCM_DEV + 0, 0 },
+ { SB_DSP4_PCM_DEV + 1, 0 },
+ { SB_DSP4_SYNTH_DEV + 0, 0 },
+ { SB_DSP4_SYNTH_DEV + 1, 0 },
+ { SB_DSP4_INPUT_LEFT, 0 },
+ { SB_DSP4_INPUT_RIGHT, 0 },
+ { SB_DSP4_OUTPUT_SW, 0 },
+ { SB_DSP4_SPEAKER_DEV, 0 },
+};
+
+/*
+ * DT019x specific mixer elements
+ */
+static const struct sbmix_elem snd_dt019x_controls[] = {
+ /* ALS4000 below has some parts which we might be lacking,
+ * e.g. snd_als4000_ctl_mono_playback_switch - check it! */
+ SB_DOUBLE("Master Playback Volume",
+ SB_DT019X_MASTER_DEV, SB_DT019X_MASTER_DEV, 4, 0, 15),
+ SB_DOUBLE("PCM Playback Switch",
+ SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1),
+ SB_DOUBLE("PCM Playback Volume",
+ SB_DT019X_PCM_DEV, SB_DT019X_PCM_DEV, 4, 0, 15),
+ SB_DOUBLE("Synth Playback Switch",
+ SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1),
+ SB_DOUBLE("Synth Playback Volume",
+ SB_DT019X_SYNTH_DEV, SB_DT019X_SYNTH_DEV, 4, 0, 15),
+ SB_DOUBLE("CD Playback Switch",
+ SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
+ SB_DOUBLE("CD Playback Volume",
+ SB_DT019X_CD_DEV, SB_DT019X_CD_DEV, 4, 0, 15),
+ SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
+ SB_SINGLE("Mic Playback Volume", SB_DT019X_MIC_DEV, 4, 7),
+ SB_SINGLE("Beep Volume", SB_DT019X_SPKR_DEV, 0, 7),
+ SB_DOUBLE("Line Playback Switch",
+ SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
+ SB_DOUBLE("Line Playback Volume",
+ SB_DT019X_LINE_DEV, SB_DT019X_LINE_DEV, 4, 0, 15),
+ {
+ .name = "Capture Source",
+ .type = SB_MIX_CAPTURE_DT019X
+ }
+};
+
+static const unsigned char snd_dt019x_init_values[][2] = {
+ { SB_DT019X_MASTER_DEV, 0 },
+ { SB_DT019X_PCM_DEV, 0 },
+ { SB_DT019X_SYNTH_DEV, 0 },
+ { SB_DT019X_CD_DEV, 0 },
+ { SB_DT019X_MIC_DEV, 0 }, /* Includes PC-speaker in high nibble */
+ { SB_DT019X_LINE_DEV, 0 },
+ { SB_DSP4_OUTPUT_SW, 0 },
+ { SB_DT019X_OUTPUT_SW2, 0 },
+ { SB_DT019X_CAPTURE_SW, 0x06 },
+};
+
+/*
+ * ALS4000 specific mixer elements
+ */
+static const struct sbmix_elem snd_als4000_controls[] = {
+ SB_DOUBLE("PCM Playback Switch",
+ SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1),
+ SB_DOUBLE("Synth Playback Switch",
+ SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1),
+ SB_SINGLE("Mic Boost (+20dB)", SB_ALS4000_MIC_IN_GAIN, 0, 0x03),
+ SB_SINGLE("Master Mono Playback Switch", SB_ALS4000_MONO_IO_CTRL, 5, 1),
+ {
+ .name = "Master Mono Capture Route",
+ .type = SB_MIX_MONO_CAPTURE_ALS4K
+ },
+ SB_SINGLE("Mono Playback Switch", SB_DT019X_OUTPUT_SW2, 0, 1),
+ SB_SINGLE("Analog Loopback Switch", SB_ALS4000_MIC_IN_GAIN, 7, 0x01),
+ SB_SINGLE("3D Control - Switch", SB_ALS4000_3D_SND_FX, 6, 0x01),
+ SB_SINGLE("Digital Loopback Switch",
+ SB_ALS4000_CR3_CONFIGURATION, 7, 0x01),
+ /* FIXME: functionality of 3D controls might be swapped, I didn't find
+ * a description of how to identify what is supposed to be what */
+ SB_SINGLE("3D Control - Level", SB_ALS4000_3D_SND_FX, 0, 0x07),
+ /* FIXME: maybe there's actually some standard 3D ctrl name for it?? */
+ SB_SINGLE("3D Control - Freq", SB_ALS4000_3D_SND_FX, 4, 0x03),
+ /* FIXME: ALS4000a.pdf mentions BBD (Bucket Brigade Device) time delay,
+ * but what ALSA 3D attribute is that actually? "Center", "Depth",
+ * "Wide" or "Space" or even "Level"? Assuming "Wide" for now... */
+ SB_SINGLE("3D Control - Wide", SB_ALS4000_3D_TIME_DELAY, 0, 0x0f),
+ SB_SINGLE("3D PowerOff Switch", SB_ALS4000_3D_TIME_DELAY, 4, 0x01),
+ SB_SINGLE("Master Playback 8kHz / 20kHz LPF Switch",
+ SB_ALS4000_FMDAC, 5, 0x01),
+#ifdef NOT_AVAILABLE
+ SB_SINGLE("FMDAC Switch (Option ?)", SB_ALS4000_FMDAC, 0, 0x01),
+ SB_SINGLE("QSound Mode", SB_ALS4000_QSOUND, 1, 0x1f),
+#endif
+};
+
+static const unsigned char snd_als4000_init_values[][2] = {
+ { SB_DSP4_MASTER_DEV + 0, 0 },
+ { SB_DSP4_MASTER_DEV + 1, 0 },
+ { SB_DSP4_PCM_DEV + 0, 0 },
+ { SB_DSP4_PCM_DEV + 1, 0 },
+ { SB_DSP4_SYNTH_DEV + 0, 0 },
+ { SB_DSP4_SYNTH_DEV + 1, 0 },
+ { SB_DSP4_SPEAKER_DEV, 0 },
+ { SB_DSP4_OUTPUT_SW, 0 },
+ { SB_DSP4_INPUT_LEFT, 0 },
+ { SB_DSP4_INPUT_RIGHT, 0 },
+ { SB_DT019X_OUTPUT_SW2, 0 },
+ { SB_ALS4000_MIC_IN_GAIN, 0 },
+};
+
+/*
+ */
+static int snd_sbmixer_init(struct snd_sb *chip,
+ const struct sbmix_elem *controls,
+ int controls_count,
+ const unsigned char map[][2],
+ int map_count,
+ char *name)
+{
+ unsigned long flags;
+ struct snd_card *card = chip->card;
+ int idx, err;
+
+ /* mixer reset */
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ snd_sbmixer_write(chip, 0x00, 0x00);
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+ /* mute and zero volume channels */
+ for (idx = 0; idx < map_count; idx++) {
+ spin_lock_irqsave(&chip->mixer_lock, flags);
+ snd_sbmixer_write(chip, map[idx][0], map[idx][1]);
+ spin_unlock_irqrestore(&chip->mixer_lock, flags);
+ }
+
+ for (idx = 0; idx < controls_count; idx++) {
+ err = snd_sbmixer_add_ctl_elem(chip, &controls[idx]);
+ if (err < 0)
+ return err;
+ }
+ snd_component_add(card, name);
+ strcpy(card->mixername, name);
+ return 0;
+}
+
+int snd_sbmixer_new(struct snd_sb *chip)
+{
+ struct snd_card *card;
+ int err;
+
+ if (snd_BUG_ON(!chip || !chip->card))
+ return -EINVAL;
+
+ card = chip->card;
+
+ switch (chip->hardware) {
+ case SB_HW_10:
+ return 0; /* no mixer chip on SB1.x */
+ case SB_HW_20:
+ case SB_HW_201:
+ err = snd_sbmixer_init(chip,
+ snd_sb20_controls,
+ ARRAY_SIZE(snd_sb20_controls),
+ snd_sb20_init_values,
+ ARRAY_SIZE(snd_sb20_init_values),
+ "CTL1335");
+ if (err < 0)
+ return err;
+ break;
+ case SB_HW_PRO:
+ case SB_HW_JAZZ16:
+ err = snd_sbmixer_init(chip,
+ snd_sbpro_controls,
+ ARRAY_SIZE(snd_sbpro_controls),
+ snd_sbpro_init_values,
+ ARRAY_SIZE(snd_sbpro_init_values),
+ "CTL1345");
+ if (err < 0)
+ return err;
+ break;
+ case SB_HW_16:
+ case SB_HW_ALS100:
+ case SB_HW_CS5530:
+ err = snd_sbmixer_init(chip,
+ snd_sb16_controls,
+ ARRAY_SIZE(snd_sb16_controls),
+ snd_sb16_init_values,
+ ARRAY_SIZE(snd_sb16_init_values),
+ "CTL1745");
+ if (err < 0)
+ return err;
+ break;
+ case SB_HW_ALS4000:
+ /* use only the first 16 controls from SB16 */
+ err = snd_sbmixer_init(chip,
+ snd_sb16_controls,
+ 16,
+ snd_sb16_init_values,
+ ARRAY_SIZE(snd_sb16_init_values),
+ "ALS4000");
+ if (err < 0)
+ return err;
+ err = snd_sbmixer_init(chip,
+ snd_als4000_controls,
+ ARRAY_SIZE(snd_als4000_controls),
+ snd_als4000_init_values,
+ ARRAY_SIZE(snd_als4000_init_values),
+ "ALS4000");
+ if (err < 0)
+ return err;
+ break;
+ case SB_HW_DT019X:
+ err = snd_sbmixer_init(chip,
+ snd_dt019x_controls,
+ ARRAY_SIZE(snd_dt019x_controls),
+ snd_dt019x_init_values,
+ ARRAY_SIZE(snd_dt019x_init_values),
+ "DT019X");
+ if (err < 0)
+ return err;
+ break;
+ default:
+ strcpy(card->mixername, "???");
+ }
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static const unsigned char sb20_saved_regs[] = {
+ SB_DSP20_MASTER_DEV,
+ SB_DSP20_PCM_DEV,
+ SB_DSP20_FM_DEV,
+ SB_DSP20_CD_DEV,
+};
+
+static const unsigned char sbpro_saved_regs[] = {
+ SB_DSP_MASTER_DEV,
+ SB_DSP_PCM_DEV,
+ SB_DSP_PLAYBACK_FILT,
+ SB_DSP_FM_DEV,
+ SB_DSP_CD_DEV,
+ SB_DSP_LINE_DEV,
+ SB_DSP_MIC_DEV,
+ SB_DSP_CAPTURE_SOURCE,
+ SB_DSP_CAPTURE_FILT,
+};
+
+static const unsigned char sb16_saved_regs[] = {
+ SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1,
+ SB_DSP4_3DSE,
+ SB_DSP4_BASS_DEV, SB_DSP4_BASS_DEV + 1,
+ SB_DSP4_TREBLE_DEV, SB_DSP4_TREBLE_DEV + 1,
+ SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1,
+ SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT,
+ SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1,
+ SB_DSP4_OUTPUT_SW,
+ SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1,
+ SB_DSP4_LINE_DEV, SB_DSP4_LINE_DEV + 1,
+ SB_DSP4_MIC_DEV,
+ SB_DSP4_SPEAKER_DEV,
+ SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1,
+ SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1,
+ SB_DSP4_MIC_AGC
+};
+
+static const unsigned char dt019x_saved_regs[] = {
+ SB_DT019X_MASTER_DEV,
+ SB_DT019X_PCM_DEV,
+ SB_DT019X_SYNTH_DEV,
+ SB_DT019X_CD_DEV,
+ SB_DT019X_MIC_DEV,
+ SB_DT019X_SPKR_DEV,
+ SB_DT019X_LINE_DEV,
+ SB_DSP4_OUTPUT_SW,
+ SB_DT019X_OUTPUT_SW2,
+ SB_DT019X_CAPTURE_SW,
+};
+
+static const unsigned char als4000_saved_regs[] = {
+ /* please verify in dsheet whether regs to be added
+ are actually real H/W or just dummy */
+ SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1,
+ SB_DSP4_OUTPUT_SW,
+ SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1,
+ SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT,
+ SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1,
+ SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1,
+ SB_DSP4_MIC_DEV,
+ SB_DSP4_SPEAKER_DEV,
+ SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1,
+ SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1,
+ SB_DT019X_OUTPUT_SW2,
+ SB_ALS4000_MONO_IO_CTRL,
+ SB_ALS4000_MIC_IN_GAIN,
+ SB_ALS4000_FMDAC,
+ SB_ALS4000_3D_SND_FX,
+ SB_ALS4000_3D_TIME_DELAY,
+ SB_ALS4000_CR3_CONFIGURATION,
+};
+
+static void save_mixer(struct snd_sb *chip, const unsigned char *regs, int num_regs)
+{
+ unsigned char *val = chip->saved_regs;
+ if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs)))
+ return;
+ for (; num_regs; num_regs--)
+ *val++ = snd_sbmixer_read(chip, *regs++);
+}
+
+static void restore_mixer(struct snd_sb *chip, const unsigned char *regs, int num_regs)
+{
+ unsigned char *val = chip->saved_regs;
+ if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs)))
+ return;
+ for (; num_regs; num_regs--)
+ snd_sbmixer_write(chip, *regs++, *val++);
+}
+
+void snd_sbmixer_suspend(struct snd_sb *chip)
+{
+ switch (chip->hardware) {
+ case SB_HW_20:
+ case SB_HW_201:
+ save_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs));
+ break;
+ case SB_HW_PRO:
+ case SB_HW_JAZZ16:
+ save_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs));
+ break;
+ case SB_HW_16:
+ case SB_HW_ALS100:
+ case SB_HW_CS5530:
+ save_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs));
+ break;
+ case SB_HW_ALS4000:
+ save_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs));
+ break;
+ case SB_HW_DT019X:
+ save_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs));
+ break;
+ default:
+ break;
+ }
+}
+
+void snd_sbmixer_resume(struct snd_sb *chip)
+{
+ switch (chip->hardware) {
+ case SB_HW_20:
+ case SB_HW_201:
+ restore_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs));
+ break;
+ case SB_HW_PRO:
+ case SB_HW_JAZZ16:
+ restore_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs));
+ break;
+ case SB_HW_16:
+ case SB_HW_ALS100:
+ case SB_HW_CS5530:
+ restore_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs));
+ break;
+ case SB_HW_ALS4000:
+ restore_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs));
+ break;
+ case SB_HW_DT019X:
+ restore_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs));
+ break;
+ default:
+ break;
+ }
+}
+#endif
diff --git a/sound/isa/sb/sbawe.c b/sound/isa/sb/sbawe.c
new file mode 100644
index 0000000000..2ec52a3473
--- /dev/null
+++ b/sound/isa/sb/sbawe.c
@@ -0,0 +1,2 @@
+#define SNDRV_SBAWE
+#include "sb16.c"
diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c
new file mode 100644
index 0000000000..60398fced0
--- /dev/null
+++ b/sound/isa/sc6000.c
@@ -0,0 +1,680 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Gallant SC-6000 soundcard. This card is also known as
+ * Audio Excel DSP 16 or Zoltrix AV302.
+ * These cards use CompuMedia ASC-9308 chip + AD1848 codec.
+ * SC-6600 and SC-7000 cards are also supported. They are based on
+ * CompuMedia ASC-9408 chip and CS4231 codec.
+ *
+ * Copyright (C) 2007 Krzysztof Helt <krzysztof.h1@wp.pl>
+ *
+ * I don't have documentation for this card. I used the driver
+ * for OSS/Free included in the kernel source as reference.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/isa.h>
+#include <linux/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.h>
+#include <sound/control.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Krzysztof Helt");
+MODULE_DESCRIPTION("Gallant SC-6000");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220, 0x240 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 11 */
+static long mss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x530, 0xe80 */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+ /* 0x300, 0x310, 0x320, 0x330 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 0 */
+static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0, 1, 3 */
+static bool joystick[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = false };
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for sc-6000 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for sc-6000 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable sc-6000 based soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for sc-6000 driver.");
+module_param_hw_array(mss_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mss_port, "MSS Port # for sc-6000 driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for sc-6000 driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for sc-6000 driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for sc-6000 driver.");
+module_param_hw_array(dma, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma, "DMA # for sc-6000 driver.");
+module_param_array(joystick, bool, NULL, 0444);
+MODULE_PARM_DESC(joystick, "Enable gameport.");
+
+/*
+ * Commands of SC6000'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 /* Unknown command */
+#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 SC6000 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 PFX "sc6000: "
+#define DRV_NAME "SC-6000"
+
+/* hardware dependent functions */
+
+/*
+ * sc6000_irq_to_softcfg - Decode irq number into cfg code.
+ */
+static unsigned char sc6000_irq_to_softcfg(int irq)
+{
+ unsigned char val = 0;
+
+ switch (irq) {
+ case 5:
+ val = 0x28;
+ break;
+ case 7:
+ val = 0x8;
+ break;
+ case 9:
+ val = 0x10;
+ break;
+ case 10:
+ val = 0x18;
+ break;
+ case 11:
+ val = 0x20;
+ break;
+ default:
+ break;
+ }
+ return val;
+}
+
+/*
+ * sc6000_dma_to_softcfg - Decode dma number into cfg code.
+ */
+static unsigned char sc6000_dma_to_softcfg(int dma)
+{
+ unsigned char val = 0;
+
+ switch (dma) {
+ case 0:
+ val = 1;
+ break;
+ case 1:
+ val = 2;
+ break;
+ case 3:
+ val = 3;
+ break;
+ default:
+ break;
+ }
+ return val;
+}
+
+/*
+ * sc6000_mpu_irq_to_softcfg - Decode MPU-401 irq number into cfg code.
+ */
+static unsigned char sc6000_mpu_irq_to_softcfg(int mpu_irq)
+{
+ unsigned char val = 0;
+
+ switch (mpu_irq) {
+ case 5:
+ val = 4;
+ break;
+ case 7:
+ val = 0x44;
+ break;
+ case 9:
+ val = 0x84;
+ break;
+ case 10:
+ val = 0xc4;
+ break;
+ default:
+ break;
+ }
+ return val;
+}
+
+static int sc6000_wait_data(char __iomem *vport)
+{
+ int loop = 1000;
+ unsigned char val = 0;
+
+ do {
+ val = ioread8(vport + DSP_DATAVAIL);
+ if (val & 0x80)
+ return 0;
+ cpu_relax();
+ } while (loop--);
+
+ return -EAGAIN;
+}
+
+static int sc6000_read(char __iomem *vport)
+{
+ if (sc6000_wait_data(vport))
+ return -EBUSY;
+
+ return ioread8(vport + DSP_READ);
+
+}
+
+static int sc6000_write(char __iomem *vport, int cmd)
+{
+ unsigned char val;
+ int loop = 500000;
+
+ do {
+ val = ioread8(vport + DSP_STATUS);
+ /*
+ * DSP ready to receive data if bit 7 of val == 0
+ */
+ if (!(val & 0x80)) {
+ iowrite8(cmd, vport + DSP_COMMAND);
+ return 0;
+ }
+ cpu_relax();
+ } while (loop--);
+
+ snd_printk(KERN_ERR "DSP Command (0x%x) timeout.\n", cmd);
+
+ return -EIO;
+}
+
+static int sc6000_dsp_get_answer(char __iomem *vport, int command,
+ char *data, int data_len)
+{
+ int len = 0;
+
+ if (sc6000_write(vport, command)) {
+ snd_printk(KERN_ERR "CMD 0x%x: failed!\n", command);
+ return -EIO;
+ }
+
+ do {
+ int val = sc6000_read(vport);
+
+ if (val < 0)
+ break;
+
+ data[len++] = val;
+
+ } while (len < data_len);
+
+ /*
+ * 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.
+ */
+ return len ? len : -EIO;
+}
+
+static int sc6000_dsp_reset(char __iomem *vport)
+{
+ iowrite8(1, vport + DSP_RESET);
+ udelay(10);
+ iowrite8(0, vport + DSP_RESET);
+ udelay(20);
+ if (sc6000_read(vport) == 0xaa)
+ return 0;
+ return -ENODEV;
+}
+
+/* detection and initialization */
+static int sc6000_hw_cfg_write(char __iomem *vport, const int *cfg)
+{
+ if (sc6000_write(vport, COMMAND_6C) < 0) {
+ snd_printk(KERN_WARNING "CMD 0x%x: failed!\n", COMMAND_6C);
+ return -EIO;
+ }
+ if (sc6000_write(vport, COMMAND_5C) < 0) {
+ snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_5C);
+ return -EIO;
+ }
+ if (sc6000_write(vport, cfg[0]) < 0) {
+ snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[0]);
+ return -EIO;
+ }
+ if (sc6000_write(vport, cfg[1]) < 0) {
+ snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[1]);
+ return -EIO;
+ }
+ if (sc6000_write(vport, COMMAND_C5) < 0) {
+ snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_C5);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sc6000_cfg_write(char __iomem *vport, unsigned char softcfg)
+{
+
+ if (sc6000_write(vport, WRITE_MDIRQ_CFG)) {
+ snd_printk(KERN_ERR "CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG);
+ return -EIO;
+ }
+ if (sc6000_write(vport, softcfg)) {
+ snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static int sc6000_setup_board(char __iomem *vport, int config)
+{
+ int loop = 10;
+
+ do {
+ if (sc6000_write(vport, COMMAND_88)) {
+ snd_printk(KERN_ERR "CMD 0x%x: failed!\n",
+ COMMAND_88);
+ return -EIO;
+ }
+ } while ((sc6000_wait_data(vport) < 0) && loop--);
+
+ if (sc6000_read(vport) < 0) {
+ snd_printk(KERN_ERR "sc6000_read after CMD 0x%x: failed\n",
+ COMMAND_88);
+ return -EIO;
+ }
+
+ if (sc6000_cfg_write(vport, config))
+ return -ENODEV;
+
+ return 0;
+}
+
+static int sc6000_init_mss(char __iomem *vport, int config,
+ char __iomem *vmss_port, int mss_config)
+{
+ if (sc6000_write(vport, DSP_INIT_MSS)) {
+ snd_printk(KERN_ERR "sc6000_init_mss [0x%x]: failed!\n",
+ DSP_INIT_MSS);
+ return -EIO;
+ }
+
+ msleep(10);
+
+ if (sc6000_cfg_write(vport, config))
+ return -EIO;
+
+ iowrite8(mss_config, vmss_port);
+
+ return 0;
+}
+
+static void sc6000_hw_cfg_encode(char __iomem *vport, int *cfg,
+ long xport, long xmpu,
+ long xmss_port, int joystick)
+{
+ cfg[0] = 0;
+ cfg[1] = 0;
+ if (xport == 0x240)
+ cfg[0] |= 1;
+ if (xmpu != SNDRV_AUTO_PORT) {
+ cfg[0] |= (xmpu & 0x30) >> 2;
+ cfg[1] |= 0x20;
+ }
+ if (xmss_port == 0xe80)
+ cfg[0] |= 0x10;
+ cfg[0] |= 0x40; /* always set */
+ if (!joystick)
+ cfg[0] |= 0x02;
+ cfg[1] |= 0x80; /* enable WSS system */
+ cfg[1] &= ~0x40; /* disable IDE */
+ snd_printd("hw cfg %x, %x\n", cfg[0], cfg[1]);
+}
+
+static int sc6000_init_board(char __iomem *vport,
+ char __iomem *vmss_port, int dev)
+{
+ char answer[15];
+ char version[2];
+ int mss_config = sc6000_irq_to_softcfg(irq[dev]) |
+ sc6000_dma_to_softcfg(dma[dev]);
+ int config = mss_config |
+ sc6000_mpu_irq_to_softcfg(mpu_irq[dev]);
+ int err;
+ int old = 0;
+
+ err = sc6000_dsp_reset(vport);
+ if (err < 0) {
+ snd_printk(KERN_ERR "sc6000_dsp_reset: failed!\n");
+ return err;
+ }
+
+ memset(answer, 0, sizeof(answer));
+ err = sc6000_dsp_get_answer(vport, GET_DSP_COPYRIGHT, answer, 15);
+ if (err <= 0) {
+ snd_printk(KERN_ERR "sc6000_dsp_copyright: failed!\n");
+ return -ENODEV;
+ }
+ /*
+ * My SC-6000 card return "SC-6000" in DSPCopyright, so
+ * if we have something different, we have to be warned.
+ */
+ if (strncmp("SC-6000", answer, 7))
+ snd_printk(KERN_WARNING "Warning: non SC-6000 audio card!\n");
+
+ if (sc6000_dsp_get_answer(vport, GET_DSP_VERSION, version, 2) < 2) {
+ snd_printk(KERN_ERR "sc6000_dsp_version: failed!\n");
+ return -ENODEV;
+ }
+ printk(KERN_INFO PFX "Detected model: %s, DSP version %d.%d\n",
+ answer, version[0], version[1]);
+
+ /* set configuration */
+ sc6000_write(vport, COMMAND_5C);
+ if (sc6000_read(vport) < 0)
+ old = 1;
+
+ if (!old) {
+ int cfg[2];
+ sc6000_hw_cfg_encode(vport, &cfg[0], port[dev], mpu_port[dev],
+ mss_port[dev], joystick[dev]);
+ if (sc6000_hw_cfg_write(vport, cfg) < 0) {
+ snd_printk(KERN_ERR "sc6000_hw_cfg_write: failed!\n");
+ return -EIO;
+ }
+ }
+ err = sc6000_setup_board(vport, config);
+ if (err < 0) {
+ snd_printk(KERN_ERR "sc6000_setup_board: failed!\n");
+ return -ENODEV;
+ }
+
+ sc6000_dsp_reset(vport);
+
+ if (!old) {
+ sc6000_write(vport, COMMAND_60);
+ sc6000_write(vport, 0x02);
+ sc6000_dsp_reset(vport);
+ }
+
+ err = sc6000_setup_board(vport, config);
+ if (err < 0) {
+ snd_printk(KERN_ERR "sc6000_setup_board: failed!\n");
+ return -ENODEV;
+ }
+ err = sc6000_init_mss(vport, config, vmss_port, mss_config);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Cannot initialize "
+ "Microsoft Sound System mode.\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int snd_sc6000_mixer(struct snd_wss *chip)
+{
+ struct snd_card *card = chip->card;
+ struct snd_ctl_elem_id id1, id2;
+ int err;
+
+ memset(&id1, 0, sizeof(id1));
+ memset(&id2, 0, sizeof(id2));
+ id1.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ /* reassign AUX0 to FM */
+ strcpy(id1.name, "Aux Playback Switch");
+ strcpy(id2.name, "FM Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "FM Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ /* reassign AUX1 to CD */
+ strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+ strcpy(id2.name, "CD Playback Switch");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ strcpy(id1.name, "Aux Playback Volume");
+ strcpy(id2.name, "CD Playback Volume");
+ err = snd_ctl_rename_id(card, &id1, &id2);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int snd_sc6000_match(struct device *devptr, unsigned int dev)
+{
+ if (!enable[dev])
+ return 0;
+ if (port[dev] == SNDRV_AUTO_PORT) {
+ printk(KERN_ERR PFX "specify IO port\n");
+ return 0;
+ }
+ if (mss_port[dev] == SNDRV_AUTO_PORT) {
+ printk(KERN_ERR PFX "specify MSS port\n");
+ return 0;
+ }
+ if (port[dev] != 0x220 && port[dev] != 0x240) {
+ printk(KERN_ERR PFX "Port must be 0x220 or 0x240\n");
+ return 0;
+ }
+ if (mss_port[dev] != 0x530 && mss_port[dev] != 0xe80) {
+ printk(KERN_ERR PFX "MSS port must be 0x530 or 0xe80\n");
+ return 0;
+ }
+ if (irq[dev] != SNDRV_AUTO_IRQ && !sc6000_irq_to_softcfg(irq[dev])) {
+ printk(KERN_ERR PFX "invalid IRQ %d\n", irq[dev]);
+ return 0;
+ }
+ if (dma[dev] != SNDRV_AUTO_DMA && !sc6000_dma_to_softcfg(dma[dev])) {
+ printk(KERN_ERR PFX "invalid DMA %d\n", dma[dev]);
+ return 0;
+ }
+ if (mpu_port[dev] != SNDRV_AUTO_PORT &&
+ (mpu_port[dev] & ~0x30L) != 0x300) {
+ printk(KERN_ERR PFX "invalid MPU-401 port %lx\n",
+ mpu_port[dev]);
+ return 0;
+ }
+ if (mpu_port[dev] != SNDRV_AUTO_PORT &&
+ mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] != 0 &&
+ !sc6000_mpu_irq_to_softcfg(mpu_irq[dev])) {
+ printk(KERN_ERR PFX "invalid MPU-401 IRQ %d\n", mpu_irq[dev]);
+ return 0;
+ }
+ return 1;
+}
+
+static void snd_sc6000_free(struct snd_card *card)
+{
+ char __iomem *vport = (char __force __iomem *)card->private_data;
+
+ if (vport)
+ sc6000_setup_board(vport, 0);
+}
+
+static int __snd_sc6000_probe(struct device *devptr, unsigned int dev)
+{
+ static const int possible_irqs[] = { 5, 7, 9, 10, 11, -1 };
+ static const int possible_dmas[] = { 1, 3, 0, -1 };
+ int err;
+ int xirq = irq[dev];
+ int xdma = dma[dev];
+ struct snd_card *card;
+ struct snd_wss *chip;
+ struct snd_opl3 *opl3;
+ char __iomem *vport;
+ char __iomem *vmss_port;
+
+ err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ if (xirq == SNDRV_AUTO_IRQ) {
+ xirq = snd_legacy_find_free_irq(possible_irqs);
+ if (xirq < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+ return -EBUSY;
+ }
+ }
+
+ if (xdma == SNDRV_AUTO_DMA) {
+ xdma = snd_legacy_find_free_dma(possible_dmas);
+ if (xdma < 0) {
+ snd_printk(KERN_ERR PFX "unable to find a free DMA\n");
+ return -EBUSY;
+ }
+ }
+
+ if (!devm_request_region(devptr, port[dev], 0x10, DRV_NAME)) {
+ snd_printk(KERN_ERR PFX
+ "I/O port region is already in use.\n");
+ return -EBUSY;
+ }
+ vport = devm_ioport_map(devptr, port[dev], 0x10);
+ if (!vport) {
+ snd_printk(KERN_ERR PFX
+ "I/O port cannot be iomapped.\n");
+ return -EBUSY;
+ }
+ card->private_data = (void __force *)vport;
+
+ /* to make it marked as used */
+ if (!devm_request_region(devptr, mss_port[dev], 4, DRV_NAME)) {
+ snd_printk(KERN_ERR PFX
+ "SC-6000 port I/O port region is already in use.\n");
+ return -EBUSY;
+ }
+ vmss_port = devm_ioport_map(devptr, mss_port[dev], 4);
+ if (!vmss_port) {
+ snd_printk(KERN_ERR PFX
+ "MSS port I/O cannot be iomapped.\n");
+ return -EBUSY;
+ }
+
+ snd_printd("Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n",
+ port[dev], xirq, xdma,
+ mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]);
+
+ err = sc6000_init_board(vport, vmss_port, dev);
+ if (err < 0)
+ return err;
+ card->private_free = snd_sc6000_free;
+
+ err = snd_wss_create(card, mss_port[dev] + 4, -1, xirq, xdma, -1,
+ WSS_HW_DETECT, 0, &chip);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_pcm(chip, 0);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX
+ "error creating new WSS PCM device\n");
+ return err;
+ }
+ err = snd_wss_mixer(chip);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "error creating new WSS mixer\n");
+ return err;
+ }
+ err = snd_sc6000_mixer(chip);
+ if (err < 0) {
+ snd_printk(KERN_ERR PFX "the mixer rewrite failed\n");
+ return err;
+ }
+ if (snd_opl3_create(card,
+ 0x388, 0x388 + 2,
+ OPL3_HW_AUTO, 0, &opl3) < 0) {
+ snd_printk(KERN_ERR PFX "no OPL device at 0x%x-0x%x ?\n",
+ 0x388, 0x388 + 2);
+ } else {
+ err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+ if (err < 0)
+ return err;
+ }
+
+ if (mpu_port[dev] != SNDRV_AUTO_PORT) {
+ if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+ mpu_irq[dev] = -1;
+ if (snd_mpu401_uart_new(card, 0,
+ MPU401_HW_MPU401,
+ mpu_port[dev], 0,
+ mpu_irq[dev], NULL) < 0)
+ snd_printk(KERN_ERR "no MPU-401 device at 0x%lx ?\n",
+ mpu_port[dev]);
+ }
+
+ strcpy(card->driver, DRV_NAME);
+ strcpy(card->shortname, "SC-6000");
+ sprintf(card->longname, "Gallant SC-6000 at 0x%lx, irq %d, dma %d",
+ mss_port[dev], xirq, xdma);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+
+ dev_set_drvdata(devptr, card);
+ return 0;
+}
+
+static int snd_sc6000_probe(struct device *devptr, unsigned int dev)
+{
+ return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev));
+}
+
+static struct isa_driver snd_sc6000_driver = {
+ .match = snd_sc6000_match,
+ .probe = snd_sc6000_probe,
+ /* FIXME: suspend/resume */
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+
+module_isa_driver(snd_sc6000_driver, SNDRV_CARDS);
diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c
new file mode 100644
index 0000000000..cc56fafd27
--- /dev/null
+++ b/sound/isa/sscape.c
@@ -0,0 +1,1291 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Low-level ALSA driver for the ENSONIQ SoundScape
+ * Copyright (c) by Chris Rankin
+ *
+ * This driver was written in part using information obtained from
+ * the OSS/Free SoundScape driver, written by Hannu Savolainen.
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/pnp.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+
+MODULE_AUTHOR("Chris Rankin");
+MODULE_DESCRIPTION("ENSONIQ SoundScape driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("sndscape.co0");
+MODULE_FIRMWARE("sndscape.co1");
+MODULE_FIRMWARE("sndscape.co2");
+MODULE_FIRMWARE("sndscape.co3");
+MODULE_FIRMWARE("sndscape.co4");
+MODULE_FIRMWARE("scope.cod");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static bool joystick[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index number for SoundScape soundcard");
+
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "Description for SoundScape card");
+
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SoundScape driver.");
+
+module_param_hw_array(wss_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS Port # for SoundScape driver.");
+
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SoundScape driver.");
+
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU401 IRQ # for SoundScape driver.");
+
+module_param_hw_array(dma, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma, "DMA # for SoundScape driver.");
+
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for SoundScape driver.");
+
+module_param_array(joystick, bool, NULL, 0444);
+MODULE_PARM_DESC(joystick, "Enable gameport.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+
+static const struct pnp_card_device_id sscape_pnpids[] = {
+ { .id = "ENS3081", .devs = { { "ENS0000" } } }, /* Soundscape PnP */
+ { .id = "ENS4081", .devs = { { "ENS1011" } } }, /* VIVO90 */
+ { .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, sscape_pnpids);
+#endif
+
+
+#define HOST_CTRL_IO(i) ((i) + 2)
+#define HOST_DATA_IO(i) ((i) + 3)
+#define ODIE_ADDR_IO(i) ((i) + 4)
+#define ODIE_DATA_IO(i) ((i) + 5)
+#define CODEC_IO(i) ((i) + 8)
+
+#define IC_ODIE 1
+#define IC_OPUS 2
+
+#define RX_READY 0x01
+#define TX_READY 0x02
+
+#define CMD_ACK 0x80
+#define CMD_SET_MIDI_VOL 0x84
+#define CMD_GET_MIDI_VOL 0x85
+#define CMD_XXX_MIDI_VOL 0x86
+#define CMD_SET_EXTMIDI 0x8a
+#define CMD_GET_EXTMIDI 0x8b
+#define CMD_SET_MT32 0x8c
+#define CMD_GET_MT32 0x8d
+
+enum GA_REG {
+ GA_INTSTAT_REG = 0,
+ GA_INTENA_REG,
+ GA_DMAA_REG,
+ GA_DMAB_REG,
+ GA_INTCFG_REG,
+ GA_DMACFG_REG,
+ GA_CDCFG_REG,
+ GA_SMCFGA_REG,
+ GA_SMCFGB_REG,
+ GA_HMCTL_REG
+};
+
+#define DMA_8BIT 0x80
+
+
+enum card_type {
+ MEDIA_FX, /* Sequoia S-1000 */
+ SSCAPE, /* Sequoia S-2000 */
+ SSCAPE_PNP,
+ SSCAPE_VIVO,
+};
+
+struct soundscape {
+ spinlock_t lock;
+ unsigned io_base;
+ int ic_type;
+ enum card_type type;
+ struct resource *io_res;
+ struct resource *wss_res;
+ struct snd_wss *chip;
+
+ unsigned char midi_vol;
+};
+
+#define INVALID_IRQ ((unsigned)-1)
+
+
+static inline struct soundscape *get_card_soundscape(struct snd_card *c)
+{
+ return (struct soundscape *) (c->private_data);
+}
+
+/*
+ * Allocates some kernel memory that we can use for DMA.
+ * I think this means that the memory has to map to
+ * contiguous pages of physical memory.
+ */
+static struct snd_dma_buffer *get_dmabuf(struct soundscape *s,
+ struct snd_dma_buffer *buf,
+ unsigned long size)
+{
+ if (buf) {
+ if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV,
+ s->chip->card->dev,
+ size, buf) < 0) {
+ snd_printk(KERN_ERR "sscape: Failed to allocate "
+ "%lu bytes for DMA\n",
+ size);
+ return NULL;
+ }
+ }
+
+ return buf;
+}
+
+/*
+ * Release the DMA-able kernel memory ...
+ */
+static void free_dmabuf(struct snd_dma_buffer *buf)
+{
+ if (buf && buf->area)
+ snd_dma_free_pages(buf);
+}
+
+/*
+ * This function writes to the SoundScape's control registers,
+ * but doesn't do any locking. It's up to the caller to do that.
+ * This is why this function is "unsafe" ...
+ */
+static inline void sscape_write_unsafe(unsigned io_base, enum GA_REG reg,
+ unsigned char val)
+{
+ outb(reg, ODIE_ADDR_IO(io_base));
+ outb(val, ODIE_DATA_IO(io_base));
+}
+
+/*
+ * Write to the SoundScape's control registers, and do the
+ * necessary locking ...
+ */
+static void sscape_write(struct soundscape *s, enum GA_REG reg,
+ unsigned char val)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&s->lock, flags);
+ sscape_write_unsafe(s->io_base, reg, val);
+ spin_unlock_irqrestore(&s->lock, flags);
+}
+
+/*
+ * Read from the SoundScape's control registers, but leave any
+ * locking to the caller. This is why the function is "unsafe" ...
+ */
+static inline unsigned char sscape_read_unsafe(unsigned io_base,
+ enum GA_REG reg)
+{
+ outb(reg, ODIE_ADDR_IO(io_base));
+ return inb(ODIE_DATA_IO(io_base));
+}
+
+/*
+ * Puts the SoundScape into "host" mode, as compared to "MIDI" mode
+ */
+static inline void set_host_mode_unsafe(unsigned io_base)
+{
+ outb(0x0, HOST_CTRL_IO(io_base));
+}
+
+/*
+ * Puts the SoundScape into "MIDI" mode, as compared to "host" mode
+ */
+static inline void set_midi_mode_unsafe(unsigned io_base)
+{
+ outb(0x3, HOST_CTRL_IO(io_base));
+}
+
+/*
+ * Read the SoundScape's host-mode control register, but leave
+ * any locking issues to the caller ...
+ */
+static inline int host_read_unsafe(unsigned io_base)
+{
+ int data = -1;
+ if ((inb(HOST_CTRL_IO(io_base)) & RX_READY) != 0)
+ data = inb(HOST_DATA_IO(io_base));
+
+ return data;
+}
+
+/*
+ * Read the SoundScape's host-mode control register, performing
+ * a limited amount of busy-waiting if the register isn't ready.
+ * Also leaves all locking-issues to the caller ...
+ */
+static int host_read_ctrl_unsafe(unsigned io_base, unsigned timeout)
+{
+ int data;
+
+ while (((data = host_read_unsafe(io_base)) < 0) && (timeout != 0)) {
+ udelay(100);
+ --timeout;
+ } /* while */
+
+ return data;
+}
+
+/*
+ * Write to the SoundScape's host-mode control registers, but
+ * leave any locking issues to the caller ...
+ */
+static inline int host_write_unsafe(unsigned io_base, unsigned char data)
+{
+ if ((inb(HOST_CTRL_IO(io_base)) & TX_READY) != 0) {
+ outb(data, HOST_DATA_IO(io_base));
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Write to the SoundScape's host-mode control registers, performing
+ * a limited amount of busy-waiting if the register isn't ready.
+ * Also leaves all locking-issues to the caller ...
+ */
+static int host_write_ctrl_unsafe(unsigned io_base, unsigned char data,
+ unsigned timeout)
+{
+ int err;
+
+ while (!(err = host_write_unsafe(io_base, data)) && (timeout != 0)) {
+ udelay(100);
+ --timeout;
+ } /* while */
+
+ return err;
+}
+
+
+/*
+ * Check that the MIDI subsystem is operational. If it isn't,
+ * then we will hang the computer if we try to use it ...
+ *
+ * NOTE: This check is based upon observation, not documentation.
+ */
+static inline int verify_mpu401(const struct snd_mpu401 *mpu)
+{
+ return ((inb(MPU401C(mpu)) & 0xc0) == 0x80);
+}
+
+/*
+ * This is apparently the standard way to initialise an MPU-401
+ */
+static inline void initialise_mpu401(const struct snd_mpu401 *mpu)
+{
+ outb(0, MPU401D(mpu));
+}
+
+/*
+ * Tell the SoundScape to activate the AD1845 chip (I think).
+ * The AD1845 detection fails if we *don't* do this, so I
+ * think that this is a good idea ...
+ */
+static void activate_ad1845_unsafe(unsigned io_base)
+{
+ unsigned char val = sscape_read_unsafe(io_base, GA_HMCTL_REG);
+ sscape_write_unsafe(io_base, GA_HMCTL_REG, (val & 0xcf) | 0x10);
+ sscape_write_unsafe(io_base, GA_CDCFG_REG, 0x80);
+}
+
+/*
+ * Tell the SoundScape to begin a DMA transfer using the given channel.
+ * All locking issues are left to the caller.
+ */
+static void sscape_start_dma_unsafe(unsigned io_base, enum GA_REG reg)
+{
+ sscape_write_unsafe(io_base, reg,
+ sscape_read_unsafe(io_base, reg) | 0x01);
+ sscape_write_unsafe(io_base, reg,
+ sscape_read_unsafe(io_base, reg) & 0xfe);
+}
+
+/*
+ * Wait for a DMA transfer to complete. This is a "limited busy-wait",
+ * and all locking issues are left to the caller.
+ */
+static int sscape_wait_dma_unsafe(unsigned io_base, enum GA_REG reg,
+ unsigned timeout)
+{
+ while (!(sscape_read_unsafe(io_base, reg) & 0x01) && (timeout != 0)) {
+ udelay(100);
+ --timeout;
+ } /* while */
+
+ return sscape_read_unsafe(io_base, reg) & 0x01;
+}
+
+/*
+ * Wait for the On-Board Processor to return its start-up
+ * acknowledgement sequence. This wait is too long for
+ * us to perform "busy-waiting", and so we must sleep.
+ * This in turn means that we must not be holding any
+ * spinlocks when we call this function.
+ */
+static int obp_startup_ack(struct soundscape *s, unsigned timeout)
+{
+ unsigned long end_time = jiffies + msecs_to_jiffies(timeout);
+
+ do {
+ unsigned long flags;
+ int x;
+
+ spin_lock_irqsave(&s->lock, flags);
+ x = host_read_unsafe(s->io_base);
+ spin_unlock_irqrestore(&s->lock, flags);
+ if (x == 0xfe || x == 0xff)
+ return 1;
+
+ msleep(10);
+ } while (time_before(jiffies, end_time));
+
+ return 0;
+}
+
+/*
+ * Wait for the host to return its start-up acknowledgement
+ * sequence. This wait is too long for us to perform
+ * "busy-waiting", and so we must sleep. This in turn means
+ * that we must not be holding any spinlocks when we call
+ * this function.
+ */
+static int host_startup_ack(struct soundscape *s, unsigned timeout)
+{
+ unsigned long end_time = jiffies + msecs_to_jiffies(timeout);
+
+ do {
+ unsigned long flags;
+ int x;
+
+ spin_lock_irqsave(&s->lock, flags);
+ x = host_read_unsafe(s->io_base);
+ spin_unlock_irqrestore(&s->lock, flags);
+ if (x == 0xfe)
+ return 1;
+
+ msleep(10);
+ } while (time_before(jiffies, end_time));
+
+ return 0;
+}
+
+/*
+ * Upload a byte-stream into the SoundScape using DMA channel A.
+ */
+static int upload_dma_data(struct soundscape *s, const unsigned char *data,
+ size_t size)
+{
+ unsigned long flags;
+ struct snd_dma_buffer dma;
+ int ret;
+ unsigned char val;
+
+ if (!get_dmabuf(s, &dma, PAGE_ALIGN(32 * 1024)))
+ return -ENOMEM;
+
+ spin_lock_irqsave(&s->lock, flags);
+
+ /*
+ * Reset the board ...
+ */
+ val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+ sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val & 0x3f);
+
+ /*
+ * Enable the DMA channels and configure them ...
+ */
+ val = (s->chip->dma1 << 4) | DMA_8BIT;
+ sscape_write_unsafe(s->io_base, GA_DMAA_REG, val);
+ sscape_write_unsafe(s->io_base, GA_DMAB_REG, 0x20);
+
+ /*
+ * Take the board out of reset ...
+ */
+ val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+ sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 0x80);
+
+ /*
+ * Upload the firmware to the SoundScape
+ * board through the DMA channel ...
+ */
+ while (size != 0) {
+ unsigned long len;
+
+ len = min(size, dma.bytes);
+ memcpy(dma.area, data, len);
+ data += len;
+ size -= len;
+
+ snd_dma_program(s->chip->dma1, dma.addr, len, DMA_MODE_WRITE);
+ sscape_start_dma_unsafe(s->io_base, GA_DMAA_REG);
+ if (!sscape_wait_dma_unsafe(s->io_base, GA_DMAA_REG, 5000)) {
+ /*
+ * Don't forget to release this spinlock we're holding
+ */
+ spin_unlock_irqrestore(&s->lock, flags);
+
+ snd_printk(KERN_ERR
+ "sscape: DMA upload has timed out\n");
+ ret = -EAGAIN;
+ goto _release_dma;
+ }
+ } /* while */
+
+ set_host_mode_unsafe(s->io_base);
+ outb(0x0, s->io_base);
+
+ /*
+ * Boot the board ... (I think)
+ */
+ val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+ sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 0x40);
+ spin_unlock_irqrestore(&s->lock, flags);
+
+ /*
+ * If all has gone well, then the board should acknowledge
+ * the new upload and tell us that it has rebooted OK. We
+ * give it 5 seconds (max) ...
+ */
+ ret = 0;
+ if (!obp_startup_ack(s, 5000)) {
+ snd_printk(KERN_ERR "sscape: No response "
+ "from on-board processor after upload\n");
+ ret = -EAGAIN;
+ } else if (!host_startup_ack(s, 5000)) {
+ snd_printk(KERN_ERR
+ "sscape: SoundScape failed to initialise\n");
+ ret = -EAGAIN;
+ }
+
+_release_dma:
+ /*
+ * NOTE!!! We are NOT holding any spinlocks at this point !!!
+ */
+ sscape_write(s, GA_DMAA_REG, (s->ic_type == IC_OPUS ? 0x40 : 0x70));
+ free_dmabuf(&dma);
+
+ return ret;
+}
+
+/*
+ * Upload the bootblock(?) into the SoundScape. The only
+ * purpose of this block of code seems to be to tell
+ * us which version of the microcode we should be using.
+ */
+static int sscape_upload_bootblock(struct snd_card *card)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+ unsigned long flags;
+ const struct firmware *init_fw = NULL;
+ int data = 0;
+ int ret;
+
+ ret = request_firmware(&init_fw, "scope.cod", card->dev);
+ if (ret < 0) {
+ snd_printk(KERN_ERR "sscape: Error loading scope.cod");
+ return ret;
+ }
+ ret = upload_dma_data(sscape, init_fw->data, init_fw->size);
+
+ release_firmware(init_fw);
+
+ spin_lock_irqsave(&sscape->lock, flags);
+ if (ret == 0)
+ data = host_read_ctrl_unsafe(sscape->io_base, 100);
+
+ if (data & 0x10)
+ sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2f);
+
+ spin_unlock_irqrestore(&sscape->lock, flags);
+
+ data &= 0xf;
+ if (ret == 0 && data > 7) {
+ snd_printk(KERN_ERR
+ "sscape: timeout reading firmware version\n");
+ ret = -EAGAIN;
+ }
+
+ return (ret == 0) ? data : ret;
+}
+
+/*
+ * Upload the microcode into the SoundScape.
+ */
+static int sscape_upload_microcode(struct snd_card *card, int version)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+ const struct firmware *init_fw = NULL;
+ char name[14];
+ int err;
+
+ scnprintf(name, sizeof(name), "sndscape.co%d", version);
+
+ err = request_firmware(&init_fw, name, card->dev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "sscape: Error loading sndscape.co%d",
+ version);
+ return err;
+ }
+ err = upload_dma_data(sscape, init_fw->data, init_fw->size);
+ if (err == 0)
+ snd_printk(KERN_INFO "sscape: MIDI firmware loaded %zu KBs\n",
+ init_fw->size >> 10);
+
+ release_firmware(init_fw);
+
+ return err;
+}
+
+/*
+ * Mixer control for the SoundScape's MIDI device.
+ */
+static int sscape_midi_info(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 127;
+ return 0;
+}
+
+static int sscape_midi_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kctl);
+ struct snd_card *card = chip->card;
+ register struct soundscape *s = get_card_soundscape(card);
+ unsigned long flags;
+
+ spin_lock_irqsave(&s->lock, flags);
+ uctl->value.integer.value[0] = s->midi_vol;
+ spin_unlock_irqrestore(&s->lock, flags);
+ return 0;
+}
+
+static int sscape_midi_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kctl);
+ struct snd_card *card = chip->card;
+ struct soundscape *s = get_card_soundscape(card);
+ unsigned long flags;
+ int change;
+ unsigned char new_val;
+
+ spin_lock_irqsave(&s->lock, flags);
+
+ new_val = uctl->value.integer.value[0] & 127;
+ /*
+ * We need to put the board into HOST mode before we
+ * can send any volume-changing HOST commands ...
+ */
+ set_host_mode_unsafe(s->io_base);
+
+ /*
+ * To successfully change the MIDI volume setting, you seem to
+ * have to write a volume command, write the new volume value,
+ * and then perform another volume-related command. Perhaps the
+ * first command is an "open" and the second command is a "close"?
+ */
+ if (s->midi_vol == new_val) {
+ change = 0;
+ goto __skip_change;
+ }
+ change = host_write_ctrl_unsafe(s->io_base, CMD_SET_MIDI_VOL, 100)
+ && host_write_ctrl_unsafe(s->io_base, new_val, 100)
+ && host_write_ctrl_unsafe(s->io_base, CMD_XXX_MIDI_VOL, 100)
+ && host_write_ctrl_unsafe(s->io_base, new_val, 100);
+ s->midi_vol = new_val;
+__skip_change:
+
+ /*
+ * Take the board out of HOST mode and back into MIDI mode ...
+ */
+ set_midi_mode_unsafe(s->io_base);
+
+ spin_unlock_irqrestore(&s->lock, flags);
+ return change;
+}
+
+static const struct snd_kcontrol_new midi_mixer_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "MIDI",
+ .info = sscape_midi_info,
+ .get = sscape_midi_get,
+ .put = sscape_midi_put
+};
+
+/*
+ * The SoundScape can use two IRQs from a possible set of four.
+ * These IRQs are encoded as bit patterns so that they can be
+ * written to the control registers.
+ */
+static unsigned get_irq_config(int sscape_type, int irq)
+{
+ static const int valid_irq[] = { 9, 5, 7, 10 };
+ static const int old_irq[] = { 9, 7, 5, 15 };
+ unsigned cfg;
+
+ if (sscape_type == MEDIA_FX) {
+ for (cfg = 0; cfg < ARRAY_SIZE(old_irq); ++cfg)
+ if (irq == old_irq[cfg])
+ return cfg;
+ } else {
+ for (cfg = 0; cfg < ARRAY_SIZE(valid_irq); ++cfg)
+ if (irq == valid_irq[cfg])
+ return cfg;
+ }
+
+ return INVALID_IRQ;
+}
+
+/*
+ * Perform certain arcane port-checks to see whether there
+ * is a SoundScape board lurking behind the given ports.
+ */
+static int detect_sscape(struct soundscape *s, long wss_io)
+{
+ unsigned long flags;
+ unsigned d;
+ int retval = 0;
+
+ spin_lock_irqsave(&s->lock, flags);
+
+ /*
+ * The following code is lifted from the original OSS driver,
+ * and as I don't have a datasheet I cannot really comment
+ * on what it is doing...
+ */
+ if ((inb(HOST_CTRL_IO(s->io_base)) & 0x78) != 0)
+ goto _done;
+
+ d = inb(ODIE_ADDR_IO(s->io_base)) & 0xf0;
+ if ((d & 0x80) != 0)
+ goto _done;
+
+ if (d == 0)
+ s->ic_type = IC_ODIE;
+ else if ((d & 0x60) != 0)
+ s->ic_type = IC_OPUS;
+ else
+ goto _done;
+
+ outb(0xfa, ODIE_ADDR_IO(s->io_base));
+ if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0a)
+ goto _done;
+
+ outb(0xfe, ODIE_ADDR_IO(s->io_base));
+ if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0e)
+ goto _done;
+
+ outb(0xfe, ODIE_ADDR_IO(s->io_base));
+ d = inb(ODIE_DATA_IO(s->io_base));
+ if (s->type != SSCAPE_VIVO && (d & 0x9f) != 0x0e)
+ goto _done;
+
+ if (s->ic_type == IC_OPUS)
+ activate_ad1845_unsafe(s->io_base);
+
+ if (s->type == SSCAPE_VIVO)
+ wss_io += 4;
+
+ d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+ sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0);
+
+ /* wait for WSS codec */
+ for (d = 0; d < 500; d++) {
+ if ((inb(wss_io) & 0x80) == 0)
+ break;
+ spin_unlock_irqrestore(&s->lock, flags);
+ msleep(1);
+ spin_lock_irqsave(&s->lock, flags);
+ }
+
+ if ((inb(wss_io) & 0x80) != 0)
+ goto _done;
+
+ if (inb(wss_io + 2) == 0xff)
+ goto _done;
+
+ d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG) & 0x3f;
+ sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d);
+
+ if ((inb(wss_io) & 0x80) != 0)
+ s->type = MEDIA_FX;
+
+ d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+ sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0);
+ /* wait for WSS codec */
+ for (d = 0; d < 500; d++) {
+ if ((inb(wss_io) & 0x80) == 0)
+ break;
+ spin_unlock_irqrestore(&s->lock, flags);
+ msleep(1);
+ spin_lock_irqsave(&s->lock, flags);
+ }
+
+ /*
+ * SoundScape successfully detected!
+ */
+ retval = 1;
+
+_done:
+ spin_unlock_irqrestore(&s->lock, flags);
+ return retval;
+}
+
+/*
+ * ALSA callback function, called when attempting to open the MIDI device.
+ * Check that the MIDI firmware has been loaded, because we don't want
+ * to crash the machine. Also check that someone isn't using the hardware
+ * IOCTL device.
+ */
+static int mpu401_open(struct snd_mpu401 *mpu)
+{
+ if (!verify_mpu401(mpu)) {
+ snd_printk(KERN_ERR "sscape: MIDI disabled, "
+ "please load firmware\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/*
+ * Initialise an MPU-401 subdevice for MIDI support on the SoundScape.
+ */
+static int create_mpu401(struct snd_card *card, int devnum,
+ unsigned long port, int irq)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+ struct snd_rawmidi *rawmidi;
+ int err;
+
+ err = snd_mpu401_uart_new(card, devnum, MPU401_HW_MPU401, port,
+ MPU401_INFO_INTEGRATED, irq, &rawmidi);
+ if (err == 0) {
+ struct snd_mpu401 *mpu = rawmidi->private_data;
+ mpu->open_input = mpu401_open;
+ mpu->open_output = mpu401_open;
+ mpu->private_data = sscape;
+
+ initialise_mpu401(mpu);
+ }
+
+ return err;
+}
+
+
+/*
+ * Create an AD1845 PCM subdevice on the SoundScape. The AD1845
+ * is very much like a CS4231, with a few extra bits. We will
+ * try to support at least some of the extra bits by overriding
+ * some of the CS4231 callback.
+ */
+static int create_ad1845(struct snd_card *card, unsigned port,
+ int irq, int dma1, int dma2)
+{
+ register struct soundscape *sscape = get_card_soundscape(card);
+ struct snd_wss *chip;
+ int err;
+ int codec_type = WSS_HW_DETECT;
+
+ switch (sscape->type) {
+ case MEDIA_FX:
+ case SSCAPE:
+ /*
+ * There are some freak examples of early Soundscape cards
+ * with CS4231 instead of AD1848/CS4248. Unfortunately, the
+ * CS4231 works only in CS4248 compatibility mode on
+ * these cards so force it.
+ */
+ if (sscape->ic_type != IC_OPUS)
+ codec_type = WSS_HW_AD1848;
+ break;
+
+ case SSCAPE_VIVO:
+ port += 4;
+ break;
+ default:
+ break;
+ }
+
+ err = snd_wss_create(card, port, -1, irq, dma1, dma2,
+ codec_type, WSS_HWSHARE_DMA1, &chip);
+ if (!err) {
+ unsigned long flags;
+
+ if (sscape->type != SSCAPE_VIVO) {
+ /*
+ * The input clock frequency on the SoundScape must
+ * be 14.31818 MHz, because we must set this register
+ * to get the playback to sound correct ...
+ */
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_wss_out(chip, AD1845_CLOCK, 0x20);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+
+ }
+
+ err = snd_wss_pcm(chip, 0);
+ if (err < 0) {
+ snd_printk(KERN_ERR "sscape: No PCM device "
+ "for AD1845 chip\n");
+ goto _error;
+ }
+
+ err = snd_wss_mixer(chip);
+ if (err < 0) {
+ snd_printk(KERN_ERR "sscape: No mixer device "
+ "for AD1845 chip\n");
+ goto _error;
+ }
+ if (chip->hardware != WSS_HW_AD1848) {
+ err = snd_wss_timer(chip, 0);
+ if (err < 0) {
+ snd_printk(KERN_ERR "sscape: No timer device "
+ "for AD1845 chip\n");
+ goto _error;
+ }
+ }
+
+ if (sscape->type != SSCAPE_VIVO) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&midi_mixer_ctl, chip));
+ if (err < 0) {
+ snd_printk(KERN_ERR "sscape: Could not create "
+ "MIDI mixer control\n");
+ goto _error;
+ }
+ }
+
+ sscape->chip = chip;
+ }
+
+_error:
+ return err;
+}
+
+
+/*
+ * Create an ALSA soundcard entry for the SoundScape, using
+ * the given list of port, IRQ and DMA resources.
+ */
+static int create_sscape(int dev, struct snd_card *card)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+ unsigned dma_cfg;
+ unsigned irq_cfg;
+ unsigned mpu_irq_cfg;
+ struct resource *io_res;
+ struct resource *wss_res;
+ unsigned long flags;
+ int err;
+ int val;
+ const char *name;
+
+ /*
+ * Grab IO ports that we will need to probe so that we
+ * can detect and control this hardware ...
+ */
+ io_res = devm_request_region(card->dev, port[dev], 8, "SoundScape");
+ if (!io_res) {
+ snd_printk(KERN_ERR
+ "sscape: can't grab port 0x%lx\n", port[dev]);
+ return -EBUSY;
+ }
+ wss_res = NULL;
+ if (sscape->type == SSCAPE_VIVO) {
+ wss_res = devm_request_region(card->dev, wss_port[dev], 4,
+ "SoundScape");
+ if (!wss_res) {
+ snd_printk(KERN_ERR "sscape: can't grab port 0x%lx\n",
+ wss_port[dev]);
+ return -EBUSY;
+ }
+ }
+
+ /*
+ * Grab one DMA channel ...
+ */
+ err = snd_devm_request_dma(card->dev, dma[dev], "SoundScape");
+ if (err < 0) {
+ snd_printk(KERN_ERR "sscape: can't grab DMA %d\n", dma[dev]);
+ return err;
+ }
+
+ spin_lock_init(&sscape->lock);
+ sscape->io_res = io_res;
+ sscape->wss_res = wss_res;
+ sscape->io_base = port[dev];
+
+ if (!detect_sscape(sscape, wss_port[dev])) {
+ printk(KERN_ERR "sscape: hardware not detected at 0x%x\n",
+ sscape->io_base);
+ return -ENODEV;
+ }
+
+ switch (sscape->type) {
+ case MEDIA_FX:
+ name = "MediaFX/SoundFX";
+ break;
+ case SSCAPE:
+ name = "Soundscape";
+ break;
+ case SSCAPE_PNP:
+ name = "Soundscape PnP";
+ break;
+ case SSCAPE_VIVO:
+ name = "Soundscape VIVO";
+ break;
+ default:
+ name = "unknown Soundscape";
+ break;
+ }
+
+ printk(KERN_INFO "sscape: %s card detected at 0x%x, using IRQ %d, DMA %d\n",
+ name, sscape->io_base, irq[dev], dma[dev]);
+
+ /*
+ * Check that the user didn't pass us garbage data ...
+ */
+ irq_cfg = get_irq_config(sscape->type, irq[dev]);
+ if (irq_cfg == INVALID_IRQ) {
+ snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", irq[dev]);
+ return -ENXIO;
+ }
+
+ mpu_irq_cfg = get_irq_config(sscape->type, mpu_irq[dev]);
+ if (mpu_irq_cfg == INVALID_IRQ) {
+ snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", mpu_irq[dev]);
+ return -ENXIO;
+ }
+
+ /*
+ * Tell the on-board devices where their resources are (I think -
+ * I can't be sure without a datasheet ... So many magic values!)
+ */
+ spin_lock_irqsave(&sscape->lock, flags);
+
+ sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e);
+ sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00);
+
+ /*
+ * Enable and configure the DMA channels ...
+ */
+ sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50);
+ dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70);
+ sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg);
+ sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20);
+
+ mpu_irq_cfg |= mpu_irq_cfg << 2;
+ val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xF7;
+ if (joystick[dev])
+ val |= 8;
+ sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0x10);
+ sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, 0xf0 | mpu_irq_cfg);
+ sscape_write_unsafe(sscape->io_base,
+ GA_CDCFG_REG, 0x09 | DMA_8BIT
+ | (dma[dev] << 4) | (irq_cfg << 1));
+ /*
+ * Enable the master IRQ ...
+ */
+ sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80);
+
+ spin_unlock_irqrestore(&sscape->lock, flags);
+
+ /*
+ * We have now enabled the codec chip, and so we should
+ * detect the AD1845 device ...
+ */
+ err = create_ad1845(card, wss_port[dev], irq[dev],
+ dma[dev], dma2[dev]);
+ if (err < 0) {
+ snd_printk(KERN_ERR
+ "sscape: No AD1845 device at 0x%lx, IRQ %d\n",
+ wss_port[dev], irq[dev]);
+ return err;
+ }
+ strcpy(card->driver, "SoundScape");
+ strcpy(card->shortname, name);
+ snprintf(card->longname, sizeof(card->longname),
+ "%s at 0x%lx, IRQ %d, DMA1 %d, DMA2 %d\n",
+ name, sscape->chip->port, sscape->chip->irq,
+ sscape->chip->dma1, sscape->chip->dma2);
+
+#define MIDI_DEVNUM 0
+ if (sscape->type != SSCAPE_VIVO) {
+ err = sscape_upload_bootblock(card);
+ if (err >= 0)
+ err = sscape_upload_microcode(card, err);
+
+ if (err == 0) {
+ err = create_mpu401(card, MIDI_DEVNUM, port[dev],
+ mpu_irq[dev]);
+ if (err < 0) {
+ snd_printk(KERN_ERR "sscape: Failed to create "
+ "MPU-401 device at 0x%lx\n",
+ port[dev]);
+ return err;
+ }
+
+ /*
+ * Initialize mixer
+ */
+ spin_lock_irqsave(&sscape->lock, flags);
+ sscape->midi_vol = 0;
+ host_write_ctrl_unsafe(sscape->io_base,
+ CMD_SET_MIDI_VOL, 100);
+ host_write_ctrl_unsafe(sscape->io_base,
+ sscape->midi_vol, 100);
+ host_write_ctrl_unsafe(sscape->io_base,
+ CMD_XXX_MIDI_VOL, 100);
+ host_write_ctrl_unsafe(sscape->io_base,
+ sscape->midi_vol, 100);
+ host_write_ctrl_unsafe(sscape->io_base,
+ CMD_SET_EXTMIDI, 100);
+ host_write_ctrl_unsafe(sscape->io_base,
+ 0, 100);
+ host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100);
+
+ set_midi_mode_unsafe(sscape->io_base);
+ spin_unlock_irqrestore(&sscape->lock, flags);
+ }
+ }
+
+ return 0;
+}
+
+
+static int snd_sscape_match(struct device *pdev, unsigned int i)
+{
+ /*
+ * Make sure we were given ALL of the other parameters.
+ */
+ if (port[i] == SNDRV_AUTO_PORT)
+ return 0;
+
+ if (irq[i] == SNDRV_AUTO_IRQ ||
+ mpu_irq[i] == SNDRV_AUTO_IRQ ||
+ dma[i] == SNDRV_AUTO_DMA) {
+ printk(KERN_INFO
+ "sscape: insufficient parameters, "
+ "need IO, IRQ, MPU-IRQ and DMA\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int snd_sscape_probe(struct device *pdev, unsigned int dev)
+{
+ struct snd_card *card;
+ struct soundscape *sscape;
+ int ret;
+
+ ret = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct soundscape), &card);
+ if (ret < 0)
+ return ret;
+
+ sscape = get_card_soundscape(card);
+ sscape->type = SSCAPE;
+
+ dma[dev] &= 0x03;
+
+ ret = create_sscape(dev, card);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_card_register(card);
+ if (ret < 0) {
+ snd_printk(KERN_ERR "sscape: Failed to register sound card\n");
+ return ret;
+ }
+ dev_set_drvdata(pdev, card);
+ return 0;
+}
+
+#define DEV_NAME "sscape"
+
+static struct isa_driver snd_sscape_driver = {
+ .match = snd_sscape_match,
+ .probe = snd_sscape_probe,
+ /* FIXME: suspend/resume */
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+#ifdef CONFIG_PNP
+static inline int get_next_autoindex(int i)
+{
+ while (i < SNDRV_CARDS && port[i] != SNDRV_AUTO_PORT)
+ ++i;
+ return i;
+}
+
+
+static int sscape_pnp_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ static int idx = 0;
+ struct pnp_dev *dev;
+ struct snd_card *card;
+ struct soundscape *sscape;
+ int ret;
+
+ /*
+ * Allow this function to fail *quietly* if all the ISA PnP
+ * devices were configured using module parameters instead.
+ */
+ idx = get_next_autoindex(idx);
+ if (idx >= SNDRV_CARDS)
+ return -ENOSPC;
+
+ /*
+ * Check that we still have room for another sound card ...
+ */
+ dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+ if (!dev)
+ return -ENODEV;
+
+ if (!pnp_is_active(dev)) {
+ if (pnp_activate_dev(dev) < 0) {
+ snd_printk(KERN_INFO "sscape: device is inactive\n");
+ return -EBUSY;
+ }
+ }
+
+ /*
+ * Create a new ALSA sound card entry, in anticipation
+ * of detecting our hardware ...
+ */
+ ret = snd_devm_card_new(&pcard->card->dev,
+ index[idx], id[idx], THIS_MODULE,
+ sizeof(struct soundscape), &card);
+ if (ret < 0)
+ return ret;
+
+ sscape = get_card_soundscape(card);
+
+ /*
+ * Identify card model ...
+ */
+ if (!strncmp("ENS4081", pid->id, 7))
+ sscape->type = SSCAPE_VIVO;
+ else
+ sscape->type = SSCAPE_PNP;
+
+ /*
+ * Read the correct parameters off the ISA PnP bus ...
+ */
+ port[idx] = pnp_port_start(dev, 0);
+ irq[idx] = pnp_irq(dev, 0);
+ mpu_irq[idx] = pnp_irq(dev, 1);
+ dma[idx] = pnp_dma(dev, 0) & 0x03;
+ if (sscape->type == SSCAPE_PNP) {
+ dma2[idx] = dma[idx];
+ wss_port[idx] = CODEC_IO(port[idx]);
+ } else {
+ wss_port[idx] = pnp_port_start(dev, 1);
+ dma2[idx] = pnp_dma(dev, 1);
+ }
+
+ ret = create_sscape(idx, card);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_card_register(card);
+ if (ret < 0) {
+ snd_printk(KERN_ERR "sscape: Failed to register sound card\n");
+ return ret;
+ }
+
+ pnp_set_card_drvdata(pcard, card);
+ ++idx;
+ return 0;
+}
+
+static struct pnp_card_driver sscape_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
+ .name = "sscape",
+ .id_table = sscape_pnpids,
+ .probe = sscape_pnp_detect,
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init sscape_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&snd_sscape_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+
+ err = pnp_register_card_driver(&sscape_pnpc_driver);
+ if (!err)
+ pnp_registered = 1;
+
+ if (isa_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit sscape_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ pnp_unregister_card_driver(&sscape_pnpc_driver);
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&snd_sscape_driver);
+}
+
+module_init(sscape_init);
+module_exit(sscape_exit);
diff --git a/sound/isa/wavefront/Makefile b/sound/isa/wavefront/Makefile
new file mode 100644
index 0000000000..b8406dce81
--- /dev/null
+++ b/sound/isa/wavefront/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-wavefront-objs := wavefront.o wavefront_fx.o wavefront_synth.o wavefront_midi.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_WAVEFRONT) += snd-wavefront.o
diff --git a/sound/isa/wavefront/wavefront.c b/sound/isa/wavefront/wavefront.c
new file mode 100644
index 0000000000..e6e46a0266
--- /dev/null
+++ b/sound/isa/wavefront/wavefront.c
@@ -0,0 +1,642 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA card-level driver for Turtle Beach Wavefront cards
+ * (Maui,Tropez,Tropez+)
+ *
+ * Copyright (c) 1997-1999 by Paul Barton-Davis <pbd@op.net>
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/opl3.h>
+#include <sound/wss.h>
+#include <sound/snd_wavefront.h>
+
+MODULE_AUTHOR("Paul Barton-Davis <pbd@op.net>");
+MODULE_DESCRIPTION("Turtle Beach Wavefront");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long cs4232_pcm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int cs4232_pcm_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */
+static long cs4232_mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int cs4232_mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */
+static long ics2115_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int ics2115_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,9,11,12,15 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
+static bool use_cs4232_midi[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for WaveFront soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for WaveFront soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable WaveFront soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for WaveFront soundcards.");
+#endif
+module_param_hw_array(cs4232_pcm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(cs4232_pcm_port, "Port # for CS4232 PCM interface.");
+module_param_hw_array(cs4232_pcm_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(cs4232_pcm_irq, "IRQ # for CS4232 PCM interface.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for CS4232 PCM interface.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for CS4232 PCM interface.");
+module_param_hw_array(cs4232_mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(cs4232_mpu_port, "port # for CS4232 MPU-401 interface.");
+module_param_hw_array(cs4232_mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(cs4232_mpu_irq, "IRQ # for CS4232 MPU-401 interface.");
+module_param_hw_array(ics2115_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(ics2115_irq, "IRQ # for ICS2115.");
+module_param_hw_array(ics2115_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(ics2115_port, "Port # for ICS2115.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port #.");
+module_param_array(use_cs4232_midi, bool, NULL, 0444);
+MODULE_PARM_DESC(use_cs4232_midi, "Use CS4232 MPU-401 interface (inaccessibly located inside your computer)");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+
+static const struct pnp_card_device_id snd_wavefront_pnpids[] = {
+ /* Tropez */
+ { .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } },
+ /* Tropez+ */
+ { .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } },
+ { .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_wavefront_pnpids);
+
+static int
+snd_wavefront_pnp (int dev, snd_wavefront_card_t *acard, struct pnp_card_link *card,
+ const struct pnp_card_device_id *id)
+{
+ struct pnp_dev *pdev;
+ int err;
+
+ /* Check for each logical device. */
+
+ /* CS4232 chip (aka "windows sound system") is logical device 0 */
+
+ acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL);
+ if (acard->wss == NULL)
+ return -EBUSY;
+
+ /* there is a game port at logical device 1, but we ignore it completely */
+
+ /* the control interface is logical device 2, but we ignore it
+ completely. in fact, nobody even seems to know what it
+ does.
+ */
+
+ /* Only configure the CS4232 MIDI interface if its been
+ specifically requested. It is logical device 3.
+ */
+
+ if (use_cs4232_midi[dev]) {
+ acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+ if (acard->mpu == NULL)
+ return -EBUSY;
+ }
+
+ /* The ICS2115 synth is logical device 4 */
+
+ acard->synth = pnp_request_card_device(card, id->devs[3].id, NULL);
+ if (acard->synth == NULL)
+ return -EBUSY;
+
+ /* PCM/FM initialization */
+
+ pdev = acard->wss;
+
+ /* An interesting note from the Tropez+ FAQ:
+
+ Q. [Ports] Why is the base address of the WSS I/O ports off by 4?
+
+ A. WSS I/O requires a block of 8 I/O addresses ("ports"). Of these, the first
+ 4 are used to identify and configure the board. With the advent of PnP,
+ these first 4 addresses have become obsolete, and software applications
+ only use the last 4 addresses to control the codec chip. Therefore, the
+ base address setting "skips past" the 4 unused addresses.
+
+ */
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "PnP WSS pnp configure failure\n");
+ return err;
+ }
+
+ cs4232_pcm_port[dev] = pnp_port_start(pdev, 0);
+ fm_port[dev] = pnp_port_start(pdev, 1);
+ dma1[dev] = pnp_dma(pdev, 0);
+ dma2[dev] = pnp_dma(pdev, 1);
+ cs4232_pcm_irq[dev] = pnp_irq(pdev, 0);
+
+ /* Synth initialization */
+
+ pdev = acard->synth;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "PnP ICS2115 pnp configure failure\n");
+ return err;
+ }
+
+ ics2115_port[dev] = pnp_port_start(pdev, 0);
+ ics2115_irq[dev] = pnp_irq(pdev, 0);
+
+ /* CS4232 MPU initialization. Configure this only if
+ explicitly requested, since its physically inaccessible and
+ consumes another IRQ.
+ */
+
+ if (use_cs4232_midi[dev]) {
+
+ pdev = acard->mpu;
+
+ err = pnp_activate_dev(pdev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "PnP MPU401 pnp configure failure\n");
+ cs4232_mpu_port[dev] = SNDRV_AUTO_PORT;
+ } else {
+ cs4232_mpu_port[dev] = pnp_port_start(pdev, 0);
+ cs4232_mpu_irq[dev] = pnp_irq(pdev, 0);
+ }
+
+ snd_printk (KERN_INFO "CS4232 MPU: port=0x%lx, irq=%i\n",
+ cs4232_mpu_port[dev],
+ cs4232_mpu_irq[dev]);
+ }
+
+ snd_printdd ("CS4232: pcm port=0x%lx, fm port=0x%lx, dma1=%i, dma2=%i, irq=%i\nICS2115: port=0x%lx, irq=%i\n",
+ cs4232_pcm_port[dev],
+ fm_port[dev],
+ dma1[dev],
+ dma2[dev],
+ cs4232_pcm_irq[dev],
+ ics2115_port[dev],
+ ics2115_irq[dev]);
+
+ return 0;
+}
+
+#endif /* CONFIG_PNP */
+
+static irqreturn_t snd_wavefront_ics2115_interrupt(int irq, void *dev_id)
+{
+ snd_wavefront_card_t *acard;
+
+ acard = (snd_wavefront_card_t *) dev_id;
+
+ if (acard == NULL)
+ return IRQ_NONE;
+
+ if (acard->wavefront.interrupts_are_midi) {
+ snd_wavefront_midi_interrupt (acard);
+ } else {
+ snd_wavefront_internal_interrupt (acard);
+ }
+ return IRQ_HANDLED;
+}
+
+static struct snd_hwdep *snd_wavefront_new_synth(struct snd_card *card,
+ int hw_dev,
+ snd_wavefront_card_t *acard)
+{
+ struct snd_hwdep *wavefront_synth;
+
+ if (snd_wavefront_detect (acard) < 0) {
+ return NULL;
+ }
+
+ if (snd_wavefront_start (&acard->wavefront) < 0) {
+ return NULL;
+ }
+
+ if (snd_hwdep_new(card, "WaveFront", hw_dev, &wavefront_synth) < 0)
+ return NULL;
+ strcpy (wavefront_synth->name,
+ "WaveFront (ICS2115) wavetable synthesizer");
+ wavefront_synth->ops.open = snd_wavefront_synth_open;
+ wavefront_synth->ops.release = snd_wavefront_synth_release;
+ wavefront_synth->ops.ioctl = snd_wavefront_synth_ioctl;
+
+ return wavefront_synth;
+}
+
+static struct snd_hwdep *snd_wavefront_new_fx(struct snd_card *card,
+ int hw_dev,
+ snd_wavefront_card_t *acard,
+ unsigned long port)
+
+{
+ struct snd_hwdep *fx_processor;
+
+ if (snd_wavefront_fx_start (&acard->wavefront)) {
+ snd_printk (KERN_ERR "cannot initialize YSS225 FX processor");
+ return NULL;
+ }
+
+ if (snd_hwdep_new (card, "YSS225", hw_dev, &fx_processor) < 0)
+ return NULL;
+ sprintf (fx_processor->name, "YSS225 FX Processor at 0x%lx", port);
+ fx_processor->ops.open = snd_wavefront_fx_open;
+ fx_processor->ops.release = snd_wavefront_fx_release;
+ fx_processor->ops.ioctl = snd_wavefront_fx_ioctl;
+
+ return fx_processor;
+}
+
+static snd_wavefront_mpu_id internal_id = internal_mpu;
+static snd_wavefront_mpu_id external_id = external_mpu;
+
+static struct snd_rawmidi *snd_wavefront_new_midi(struct snd_card *card,
+ int midi_dev,
+ snd_wavefront_card_t *acard,
+ unsigned long port,
+ snd_wavefront_mpu_id mpu)
+
+{
+ struct snd_rawmidi *rmidi;
+ static int first = 1;
+
+ if (first) {
+ first = 0;
+ acard->wavefront.midi.base = port;
+ if (snd_wavefront_midi_start (acard)) {
+ snd_printk (KERN_ERR "cannot initialize MIDI interface\n");
+ return NULL;
+ }
+ }
+
+ if (snd_rawmidi_new (card, "WaveFront MIDI", midi_dev, 1, 1, &rmidi) < 0)
+ return NULL;
+
+ if (mpu == internal_mpu) {
+ strcpy(rmidi->name, "WaveFront MIDI (Internal)");
+ rmidi->private_data = &internal_id;
+ } else {
+ strcpy(rmidi->name, "WaveFront MIDI (External)");
+ rmidi->private_data = &external_id;
+ }
+
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_wavefront_midi_output);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_wavefront_midi_input);
+
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+ SNDRV_RAWMIDI_INFO_INPUT |
+ SNDRV_RAWMIDI_INFO_DUPLEX;
+
+ return rmidi;
+}
+
+static int snd_wavefront_card_new(struct device *pdev, int dev,
+ struct snd_card **cardp)
+{
+ struct snd_card *card;
+ snd_wavefront_card_t *acard;
+ int err;
+
+ err = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+ sizeof(snd_wavefront_card_t), &card);
+ if (err < 0)
+ return err;
+
+ acard = card->private_data;
+ acard->wavefront.irq = -1;
+ spin_lock_init(&acard->wavefront.irq_lock);
+ init_waitqueue_head(&acard->wavefront.interrupt_sleeper);
+ spin_lock_init(&acard->wavefront.midi.open);
+ spin_lock_init(&acard->wavefront.midi.virtual);
+ acard->wavefront.card = card;
+
+ *cardp = card;
+ return 0;
+}
+
+static int
+snd_wavefront_probe (struct snd_card *card, int dev)
+{
+ snd_wavefront_card_t *acard = card->private_data;
+ struct snd_wss *chip;
+ struct snd_hwdep *wavefront_synth;
+ struct snd_rawmidi *ics2115_internal_rmidi = NULL;
+ struct snd_rawmidi *ics2115_external_rmidi = NULL;
+ struct snd_hwdep *fx_processor;
+ int hw_dev = 0, midi_dev = 0, err;
+
+ /* --------- PCM --------------- */
+
+ err = snd_wss_create(card, cs4232_pcm_port[dev], -1,
+ cs4232_pcm_irq[dev], dma1[dev], dma2[dev],
+ WSS_HW_DETECT, 0, &chip);
+ if (err < 0) {
+ snd_printk(KERN_ERR "can't allocate WSS device\n");
+ return err;
+ }
+
+ err = snd_wss_pcm(chip, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_wss_timer(chip, 0);
+ if (err < 0)
+ return err;
+
+ /* ---------- OPL3 synth --------- */
+
+ if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+ struct snd_opl3 *opl3;
+
+ err = snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+ OPL3_HW_OPL3_CS, 0, &opl3);
+ if (err < 0) {
+ snd_printk (KERN_ERR "can't allocate or detect OPL3 synth\n");
+ return err;
+ }
+
+ err = snd_opl3_hwdep_new(opl3, hw_dev, 1, NULL);
+ if (err < 0)
+ return err;
+ hw_dev++;
+ }
+
+ /* ------- ICS2115 Wavetable synth ------- */
+
+ acard->wavefront.res_base =
+ devm_request_region(card->dev, ics2115_port[dev], 16,
+ "ICS2115");
+ if (acard->wavefront.res_base == NULL) {
+ snd_printk(KERN_ERR "unable to grab ICS2115 i/o region 0x%lx-0x%lx\n",
+ ics2115_port[dev], ics2115_port[dev] + 16 - 1);
+ return -EBUSY;
+ }
+ if (devm_request_irq(card->dev, ics2115_irq[dev],
+ snd_wavefront_ics2115_interrupt,
+ 0, "ICS2115", acard)) {
+ snd_printk(KERN_ERR "unable to use ICS2115 IRQ %d\n", ics2115_irq[dev]);
+ return -EBUSY;
+ }
+
+ acard->wavefront.irq = ics2115_irq[dev];
+ card->sync_irq = acard->wavefront.irq;
+ acard->wavefront.base = ics2115_port[dev];
+
+ wavefront_synth = snd_wavefront_new_synth(card, hw_dev, acard);
+ if (wavefront_synth == NULL) {
+ snd_printk (KERN_ERR "can't create WaveFront synth device\n");
+ return -ENOMEM;
+ }
+
+ strcpy (wavefront_synth->name, "ICS2115 Wavetable MIDI Synthesizer");
+ wavefront_synth->iface = SNDRV_HWDEP_IFACE_ICS2115;
+ hw_dev++;
+
+ /* --------- Mixer ------------ */
+
+ err = snd_wss_mixer(chip);
+ if (err < 0) {
+ snd_printk (KERN_ERR "can't allocate mixer device\n");
+ return err;
+ }
+
+ /* -------- CS4232 MPU-401 interface -------- */
+
+ if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) {
+ err = snd_mpu401_uart_new(card, midi_dev, MPU401_HW_CS4232,
+ cs4232_mpu_port[dev], 0,
+ cs4232_mpu_irq[dev], NULL);
+ if (err < 0) {
+ snd_printk (KERN_ERR "can't allocate CS4232 MPU-401 device\n");
+ return err;
+ }
+ midi_dev++;
+ }
+
+ /* ------ ICS2115 internal MIDI ------------ */
+
+ if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) {
+ ics2115_internal_rmidi =
+ snd_wavefront_new_midi (card,
+ midi_dev,
+ acard,
+ ics2115_port[dev],
+ internal_mpu);
+ if (ics2115_internal_rmidi == NULL) {
+ snd_printk (KERN_ERR "can't setup ICS2115 internal MIDI device\n");
+ return -ENOMEM;
+ }
+ midi_dev++;
+ }
+
+ /* ------ ICS2115 external MIDI ------------ */
+
+ if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) {
+ ics2115_external_rmidi =
+ snd_wavefront_new_midi (card,
+ midi_dev,
+ acard,
+ ics2115_port[dev],
+ external_mpu);
+ if (ics2115_external_rmidi == NULL) {
+ snd_printk (KERN_ERR "can't setup ICS2115 external MIDI device\n");
+ return -ENOMEM;
+ }
+ midi_dev++;
+ }
+
+ /* FX processor for Tropez+ */
+
+ if (acard->wavefront.has_fx) {
+ fx_processor = snd_wavefront_new_fx (card,
+ hw_dev,
+ acard,
+ ics2115_port[dev]);
+ if (fx_processor == NULL) {
+ snd_printk (KERN_ERR "can't setup FX device\n");
+ return -ENOMEM;
+ }
+
+ hw_dev++;
+
+ strcpy(card->driver, "Tropez+");
+ strcpy(card->shortname, "Turtle Beach Tropez+");
+ } else {
+ /* Need a way to distinguish between Maui and Tropez */
+ strcpy(card->driver, "WaveFront");
+ strcpy(card->shortname, "Turtle Beach WaveFront");
+ }
+
+ /* ----- Register the card --------- */
+
+ /* Not safe to include "Turtle Beach" in longname, due to
+ length restrictions
+ */
+
+ sprintf(card->longname, "%s PCM 0x%lx irq %d dma %d",
+ card->driver,
+ chip->port,
+ cs4232_pcm_irq[dev],
+ dma1[dev]);
+
+ if (dma2[dev] >= 0 && dma2[dev] < 8)
+ sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]);
+
+ if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) {
+ sprintf (card->longname + strlen (card->longname),
+ " MPU-401 0x%lx irq %d",
+ cs4232_mpu_port[dev],
+ cs4232_mpu_irq[dev]);
+ }
+
+ sprintf (card->longname + strlen (card->longname),
+ " SYNTH 0x%lx irq %d",
+ ics2115_port[dev],
+ ics2115_irq[dev]);
+
+ return snd_card_register(card);
+}
+
+static int snd_wavefront_isa_match(struct device *pdev,
+ unsigned int dev)
+{
+ if (!enable[dev])
+ return 0;
+#ifdef CONFIG_PNP
+ if (isapnp[dev])
+ return 0;
+#endif
+ if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR "specify CS4232 port\n");
+ return 0;
+ }
+ if (ics2115_port[dev] == SNDRV_AUTO_PORT) {
+ snd_printk(KERN_ERR "specify ICS2115 port\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int snd_wavefront_isa_probe(struct device *pdev,
+ unsigned int dev)
+{
+ struct snd_card *card;
+ int err;
+
+ err = snd_wavefront_card_new(pdev, dev, &card);
+ if (err < 0)
+ return err;
+ err = snd_wavefront_probe(card, dev);
+ if (err < 0)
+ return err;
+
+ dev_set_drvdata(pdev, card);
+ return 0;
+}
+
+#define DEV_NAME "wavefront"
+
+static struct isa_driver snd_wavefront_driver = {
+ .match = snd_wavefront_isa_match,
+ .probe = snd_wavefront_isa_probe,
+ /* FIXME: suspend, resume */
+ .driver = {
+ .name = DEV_NAME
+ },
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_wavefront_pnp_detect(struct pnp_card_link *pcard,
+ const struct pnp_card_device_id *pid)
+{
+ static int dev;
+ struct snd_card *card;
+ int res;
+
+ for ( ; dev < SNDRV_CARDS; dev++) {
+ if (enable[dev] && isapnp[dev])
+ break;
+ }
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ res = snd_wavefront_card_new(&pcard->card->dev, dev, &card);
+ if (res < 0)
+ return res;
+
+ if (snd_wavefront_pnp (dev, card->private_data, pcard, pid) < 0) {
+ if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) {
+ snd_printk (KERN_ERR "isapnp detection failed\n");
+ return -ENODEV;
+ }
+ }
+
+ res = snd_wavefront_probe(card, dev);
+ if (res < 0)
+ return res;
+
+ pnp_set_card_drvdata(pcard, card);
+ dev++;
+ return 0;
+}
+
+static struct pnp_card_driver wavefront_pnpc_driver = {
+ .flags = PNP_DRIVER_RES_DISABLE,
+ .name = "wavefront",
+ .id_table = snd_wavefront_pnpids,
+ .probe = snd_wavefront_pnp_detect,
+ /* FIXME: suspend,resume */
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_wavefront_init(void)
+{
+ int err;
+
+ err = isa_register_driver(&snd_wavefront_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+ if (!err)
+ isa_registered = 1;
+
+ err = pnp_register_card_driver(&wavefront_pnpc_driver);
+ if (!err)
+ pnp_registered = 1;
+
+ if (isa_registered)
+ err = 0;
+#endif
+ return err;
+}
+
+static void __exit alsa_card_wavefront_exit(void)
+{
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ pnp_unregister_card_driver(&wavefront_pnpc_driver);
+ if (isa_registered)
+#endif
+ isa_unregister_driver(&snd_wavefront_driver);
+}
+
+module_init(alsa_card_wavefront_init)
+module_exit(alsa_card_wavefront_exit)
diff --git a/sound/isa/wavefront/wavefront_fx.c b/sound/isa/wavefront/wavefront_fx.c
new file mode 100644
index 0000000000..3c21324b2a
--- /dev/null
+++ b/sound/isa/wavefront/wavefront_fx.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 1998-2002 by Paul Davis <pbd@op.net>
+ */
+
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <sound/core.h>
+#include <sound/snd_wavefront.h>
+#include <sound/initval.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 */
+
+#define WAIT_IDLE 0xff
+
+static int
+wavefront_fx_idle (snd_wavefront_t *dev)
+
+{
+ 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) {
+ snd_printk ("FX device never idle.\n");
+ return 0;
+ }
+
+ return (1);
+}
+
+static void
+wavefront_fx_mute (snd_wavefront_t *dev, int onoff)
+
+{
+ if (!wavefront_fx_idle(dev)) {
+ return;
+ }
+
+ outb (onoff ? 0x02 : 0x00, dev->fx_op);
+}
+
+static int
+wavefront_fx_memset (snd_wavefront_t *dev,
+ int page,
+ int addr,
+ int cnt,
+ unsigned short *data)
+{
+ if (page < 0 || page > 7) {
+ snd_printk ("FX memset: "
+ "page must be >= 0 and <= 7\n");
+ return -EINVAL;
+ }
+
+ if (addr < 0 || addr > 0x7f) {
+ snd_printk ("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);
+
+ snd_printk ("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 (!wavefront_fx_idle (dev)) {
+ break;
+ }
+ }
+
+ if (i != cnt) {
+ snd_printk ("FX memset "
+ "(0x%x, 0x%x, 0x%lx, %d) incomplete\n",
+ page, addr, (unsigned long) data, cnt);
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+int
+snd_wavefront_fx_detect (snd_wavefront_t *dev)
+
+{
+ /* This is a crude check, but its the best one I have for now.
+ Certainly on the Maui and the Tropez, wavefront_fx_idle() will
+ report "never idle", which suggests that this test should
+ work OK.
+ */
+
+ if (inb (dev->fx_status) & 0x80) {
+ snd_printk ("Hmm, probably a Maui or Tropez.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+snd_wavefront_fx_open (struct snd_hwdep *hw, struct file *file)
+
+{
+ if (!try_module_get(hw->card->module))
+ return -EFAULT;
+ file->private_data = hw;
+ return 0;
+}
+
+int
+snd_wavefront_fx_release (struct snd_hwdep *hw, struct file *file)
+
+{
+ module_put(hw->card->module);
+ return 0;
+}
+
+int
+snd_wavefront_fx_ioctl (struct snd_hwdep *sdev, struct file *file,
+ unsigned int cmd, unsigned long arg)
+
+{
+ struct snd_card *card;
+ snd_wavefront_card_t *acard;
+ snd_wavefront_t *dev;
+ wavefront_fx_info r;
+ unsigned short *page_data = NULL;
+ unsigned short *pd;
+ int err = 0;
+
+ card = sdev->card;
+ if (snd_BUG_ON(!card))
+ return -ENODEV;
+ if (snd_BUG_ON(!card->private_data))
+ return -ENODEV;
+
+ acard = card->private_data;
+ dev = &acard->wavefront;
+
+ if (copy_from_user (&r, (void __user *)arg, sizeof (wavefront_fx_info)))
+ return -EFAULT;
+
+ switch (r.request) {
+ case WFFX_MUTE:
+ wavefront_fx_mute (dev, r.data[0]);
+ return -EIO;
+
+ case WFFX_MEMSET:
+ if (r.data[2] <= 0) {
+ snd_printk ("cannot write "
+ "<= 0 bytes to FX\n");
+ return -EIO;
+ } else if (r.data[2] == 1) {
+ pd = (unsigned short *) &r.data[3];
+ } else {
+ if (r.data[2] > 256) {
+ snd_printk ("cannot write "
+ "> 512 bytes to FX\n");
+ return -EIO;
+ }
+ page_data = memdup_user((unsigned char __user *)
+ r.data[3],
+ r.data[2] * sizeof(short));
+ if (IS_ERR(page_data))
+ return PTR_ERR(page_data);
+ pd = page_data;
+ }
+
+ err = wavefront_fx_memset (dev,
+ r.data[0], /* page */
+ r.data[1], /* addr */
+ r.data[2], /* cnt */
+ pd);
+ kfree(page_data);
+ break;
+
+ default:
+ snd_printk ("FX: ioctl %d not yet supported\n",
+ r.request);
+ return -ENOTTY;
+ }
+ return err;
+}
+
+/* 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.
+
+ This is the approach of just dumping the whole I/O
+ sequence as a series of port/value pairs and a simple loop
+ that outputs it.
+*/
+
+int
+snd_wavefront_fx_start (snd_wavefront_t *dev)
+{
+ unsigned int i;
+ int err;
+ const struct firmware *firmware = NULL;
+
+ if (dev->fx_initialized)
+ return 0;
+
+ err = request_firmware(&firmware, "yamaha/yss225_registers.bin",
+ dev->card->dev);
+ if (err < 0) {
+ err = -1;
+ goto out;
+ }
+
+ for (i = 0; i + 1 < firmware->size; i += 2) {
+ if (firmware->data[i] >= 8 && firmware->data[i] < 16) {
+ outb(firmware->data[i + 1],
+ dev->base + firmware->data[i]);
+ } else if (firmware->data[i] == WAIT_IDLE) {
+ if (!wavefront_fx_idle(dev)) {
+ err = -1;
+ goto out;
+ }
+ } else {
+ snd_printk(KERN_ERR "invalid address"
+ " in register data\n");
+ err = -1;
+ goto out;
+ }
+ }
+
+ dev->fx_initialized = 1;
+ err = 0;
+
+out:
+ release_firmware(firmware);
+ return err;
+}
+
+MODULE_FIRMWARE("yamaha/yss225_registers.bin");
diff --git a/sound/isa/wavefront/wavefront_midi.c b/sound/isa/wavefront/wavefront_midi.c
new file mode 100644
index 0000000000..72e775ac7a
--- /dev/null
+++ b/sound/isa/wavefront/wavefront_midi.c
@@ -0,0 +1,576 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) by Paul Barton-Davis 1998-1999
+ */
+
+/* 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 and 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
+ * busses to be used completely independently, giving 32 channels of
+ * MIDI routing, 16 to the WaveFront synth and 16 to the external MIDI
+ * bus. The devices are named /dev/snd/midiCnD0 and /dev/snd/midiCnD1,
+ * where `n' is the card number. Note that the device numbers may be
+ * something other than 0 and 1 if the CS4232 UART/MPU-401 interface
+ * is enabled.
+ *
+ * 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 lowlevel/isa/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.
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/snd_wavefront.h>
+
+static inline int
+wf_mpu_status (snd_wavefront_midi_t *midi)
+
+{
+ return inb (midi->mpu_status_port);
+}
+
+static inline int
+input_avail (snd_wavefront_midi_t *midi)
+
+{
+ return !(wf_mpu_status(midi) & INPUT_AVAIL);
+}
+
+static inline int
+output_ready (snd_wavefront_midi_t *midi)
+
+{
+ return !(wf_mpu_status(midi) & OUTPUT_READY);
+}
+
+static inline int
+read_data (snd_wavefront_midi_t *midi)
+
+{
+ return inb (midi->mpu_data_port);
+}
+
+static inline void
+write_data (snd_wavefront_midi_t *midi, unsigned char byte)
+
+{
+ outb (byte, midi->mpu_data_port);
+}
+
+static snd_wavefront_midi_t *
+get_wavefront_midi (struct snd_rawmidi_substream *substream)
+
+{
+ struct snd_card *card;
+ snd_wavefront_card_t *acard;
+
+ if (substream == NULL || substream->rmidi == NULL)
+ return NULL;
+
+ card = substream->rmidi->card;
+
+ if (card == NULL)
+ return NULL;
+
+ if (card->private_data == NULL)
+ return NULL;
+
+ acard = card->private_data;
+
+ return &acard->wavefront.midi;
+}
+
+static void snd_wavefront_midi_output_write(snd_wavefront_card_t *card)
+{
+ snd_wavefront_midi_t *midi = &card->wavefront.midi;
+ snd_wavefront_mpu_id mpu;
+ unsigned long flags;
+ unsigned char midi_byte;
+ int max = 256, mask = 1;
+ int timeout;
+
+ /* Its not OK to try to change the status of "virtuality" of
+ the MIDI interface while we're outputting stuff. See
+ snd_wavefront_midi_{enable,disable}_virtual () for the
+ other half of this.
+
+ The first loop attempts to flush any data from the
+ current output device, and then the second
+ emits the switch byte (if necessary), and starts
+ outputting data for the output device currently in use.
+ */
+
+ if (midi->substream_output[midi->output_mpu] == NULL) {
+ goto __second;
+ }
+
+ while (max > 0) {
+
+ /* XXX fix me - no hard timing loops allowed! */
+
+ for (timeout = 30000; timeout > 0; timeout--) {
+ if (output_ready (midi))
+ break;
+ }
+
+ spin_lock_irqsave (&midi->virtual, flags);
+ if ((midi->mode[midi->output_mpu] & MPU401_MODE_OUTPUT) == 0) {
+ spin_unlock_irqrestore (&midi->virtual, flags);
+ goto __second;
+ }
+ if (output_ready (midi)) {
+ if (snd_rawmidi_transmit(midi->substream_output[midi->output_mpu], &midi_byte, 1) == 1) {
+ if (!midi->isvirtual ||
+ (midi_byte != WF_INTERNAL_SWITCH &&
+ midi_byte != WF_EXTERNAL_SWITCH))
+ write_data(midi, midi_byte);
+ max--;
+ } else {
+ if (midi->istimer) {
+ if (--midi->istimer <= 0)
+ del_timer(&midi->timer);
+ }
+ midi->mode[midi->output_mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
+ spin_unlock_irqrestore (&midi->virtual, flags);
+ goto __second;
+ }
+ } else {
+ spin_unlock_irqrestore (&midi->virtual, flags);
+ return;
+ }
+ spin_unlock_irqrestore (&midi->virtual, flags);
+ }
+
+ __second:
+
+ if (midi->substream_output[!midi->output_mpu] == NULL) {
+ return;
+ }
+
+ while (max > 0) {
+
+ /* XXX fix me - no hard timing loops allowed! */
+
+ for (timeout = 30000; timeout > 0; timeout--) {
+ if (output_ready (midi))
+ break;
+ }
+
+ spin_lock_irqsave (&midi->virtual, flags);
+ if (!midi->isvirtual)
+ mask = 0;
+ mpu = midi->output_mpu ^ mask;
+ mask = 0; /* don't invert the value from now */
+ if ((midi->mode[mpu] & MPU401_MODE_OUTPUT) == 0) {
+ spin_unlock_irqrestore (&midi->virtual, flags);
+ return;
+ }
+ if (snd_rawmidi_transmit_empty(midi->substream_output[mpu]))
+ goto __timer;
+ if (output_ready (midi)) {
+ if (mpu != midi->output_mpu) {
+ write_data(midi, mpu == internal_mpu ?
+ WF_INTERNAL_SWITCH :
+ WF_EXTERNAL_SWITCH);
+ midi->output_mpu = mpu;
+ } else if (snd_rawmidi_transmit(midi->substream_output[mpu], &midi_byte, 1) == 1) {
+ if (!midi->isvirtual ||
+ (midi_byte != WF_INTERNAL_SWITCH &&
+ midi_byte != WF_EXTERNAL_SWITCH))
+ write_data(midi, midi_byte);
+ max--;
+ } else {
+ __timer:
+ if (midi->istimer) {
+ if (--midi->istimer <= 0)
+ del_timer(&midi->timer);
+ }
+ midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
+ spin_unlock_irqrestore (&midi->virtual, flags);
+ return;
+ }
+ } else {
+ spin_unlock_irqrestore (&midi->virtual, flags);
+ return;
+ }
+ spin_unlock_irqrestore (&midi->virtual, flags);
+ }
+}
+
+static int snd_wavefront_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ snd_wavefront_midi_t *midi;
+ snd_wavefront_mpu_id mpu;
+
+ if (snd_BUG_ON(!substream || !substream->rmidi))
+ return -ENXIO;
+ if (snd_BUG_ON(!substream->rmidi->private_data))
+ return -ENXIO;
+
+ mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+ midi = get_wavefront_midi(substream);
+ if (!midi)
+ return -EIO;
+
+ spin_lock_irqsave (&midi->open, flags);
+ midi->mode[mpu] |= MPU401_MODE_INPUT;
+ midi->substream_input[mpu] = substream;
+ spin_unlock_irqrestore (&midi->open, flags);
+
+ return 0;
+}
+
+static int snd_wavefront_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ snd_wavefront_midi_t *midi;
+ snd_wavefront_mpu_id mpu;
+
+ if (snd_BUG_ON(!substream || !substream->rmidi))
+ return -ENXIO;
+ if (snd_BUG_ON(!substream->rmidi->private_data))
+ return -ENXIO;
+
+ mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+ midi = get_wavefront_midi(substream);
+ if (!midi)
+ return -EIO;
+
+ spin_lock_irqsave (&midi->open, flags);
+ midi->mode[mpu] |= MPU401_MODE_OUTPUT;
+ midi->substream_output[mpu] = substream;
+ spin_unlock_irqrestore (&midi->open, flags);
+
+ return 0;
+}
+
+static int snd_wavefront_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ snd_wavefront_midi_t *midi;
+ snd_wavefront_mpu_id mpu;
+
+ if (snd_BUG_ON(!substream || !substream->rmidi))
+ return -ENXIO;
+ if (snd_BUG_ON(!substream->rmidi->private_data))
+ return -ENXIO;
+
+ mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+ midi = get_wavefront_midi(substream);
+ if (!midi)
+ return -EIO;
+
+ spin_lock_irqsave (&midi->open, flags);
+ midi->mode[mpu] &= ~MPU401_MODE_INPUT;
+ spin_unlock_irqrestore (&midi->open, flags);
+
+ return 0;
+}
+
+static int snd_wavefront_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ unsigned long flags;
+ snd_wavefront_midi_t *midi;
+ snd_wavefront_mpu_id mpu;
+
+ if (snd_BUG_ON(!substream || !substream->rmidi))
+ return -ENXIO;
+ if (snd_BUG_ON(!substream->rmidi->private_data))
+ return -ENXIO;
+
+ mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+ midi = get_wavefront_midi(substream);
+ if (!midi)
+ return -EIO;
+
+ spin_lock_irqsave (&midi->open, flags);
+ midi->mode[mpu] &= ~MPU401_MODE_OUTPUT;
+ spin_unlock_irqrestore (&midi->open, flags);
+ return 0;
+}
+
+static void snd_wavefront_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ snd_wavefront_midi_t *midi;
+ snd_wavefront_mpu_id mpu;
+
+ if (substream == NULL || substream->rmidi == NULL)
+ return;
+
+ if (substream->rmidi->private_data == NULL)
+ return;
+
+ mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+ midi = get_wavefront_midi(substream);
+ if (!midi)
+ return;
+
+ spin_lock_irqsave (&midi->virtual, flags);
+ if (up) {
+ midi->mode[mpu] |= MPU401_MODE_INPUT_TRIGGER;
+ } else {
+ midi->mode[mpu] &= ~MPU401_MODE_INPUT_TRIGGER;
+ }
+ spin_unlock_irqrestore (&midi->virtual, flags);
+}
+
+static void snd_wavefront_midi_output_timer(struct timer_list *t)
+{
+ snd_wavefront_midi_t *midi = from_timer(midi, t, timer);
+ snd_wavefront_card_t *card = midi->timer_card;
+ unsigned long flags;
+
+ spin_lock_irqsave (&midi->virtual, flags);
+ mod_timer(&midi->timer, 1 + jiffies);
+ spin_unlock_irqrestore (&midi->virtual, flags);
+ snd_wavefront_midi_output_write(card);
+}
+
+static void snd_wavefront_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ unsigned long flags;
+ snd_wavefront_midi_t *midi;
+ snd_wavefront_mpu_id mpu;
+
+ if (substream == NULL || substream->rmidi == NULL)
+ return;
+
+ if (substream->rmidi->private_data == NULL)
+ return;
+
+ mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+ midi = get_wavefront_midi(substream);
+ if (!midi)
+ return;
+
+ spin_lock_irqsave (&midi->virtual, flags);
+ if (up) {
+ if ((midi->mode[mpu] & MPU401_MODE_OUTPUT_TRIGGER) == 0) {
+ if (!midi->istimer) {
+ timer_setup(&midi->timer,
+ snd_wavefront_midi_output_timer,
+ 0);
+ mod_timer(&midi->timer, 1 + jiffies);
+ }
+ midi->istimer++;
+ midi->mode[mpu] |= MPU401_MODE_OUTPUT_TRIGGER;
+ }
+ } else {
+ midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
+ }
+ spin_unlock_irqrestore (&midi->virtual, flags);
+
+ if (up)
+ snd_wavefront_midi_output_write((snd_wavefront_card_t *)substream->rmidi->card->private_data);
+}
+
+void
+snd_wavefront_midi_interrupt (snd_wavefront_card_t *card)
+
+{
+ unsigned long flags;
+ snd_wavefront_midi_t *midi;
+ static struct snd_rawmidi_substream *substream = NULL;
+ static int mpu = external_mpu;
+ int max = 128;
+ unsigned char byte;
+
+ midi = &card->wavefront.midi;
+
+ if (!input_avail (midi)) { /* not for us */
+ snd_wavefront_midi_output_write(card);
+ return;
+ }
+
+ spin_lock_irqsave (&midi->virtual, flags);
+ while (--max) {
+
+ if (input_avail (midi)) {
+ byte = read_data (midi);
+
+ if (midi->isvirtual) {
+ if (byte == WF_EXTERNAL_SWITCH) {
+ substream = midi->substream_input[external_mpu];
+ mpu = external_mpu;
+ } else if (byte == WF_INTERNAL_SWITCH) {
+ substream = midi->substream_output[internal_mpu];
+ mpu = internal_mpu;
+ } /* else just leave it as it is */
+ } else {
+ substream = midi->substream_input[internal_mpu];
+ mpu = internal_mpu;
+ }
+
+ if (substream == NULL) {
+ continue;
+ }
+
+ if (midi->mode[mpu] & MPU401_MODE_INPUT_TRIGGER) {
+ snd_rawmidi_receive(substream, &byte, 1);
+ }
+ } else {
+ break;
+ }
+ }
+ spin_unlock_irqrestore (&midi->virtual, flags);
+
+ snd_wavefront_midi_output_write(card);
+}
+
+void
+snd_wavefront_midi_enable_virtual (snd_wavefront_card_t *card)
+
+{
+ unsigned long flags;
+
+ spin_lock_irqsave (&card->wavefront.midi.virtual, flags);
+ card->wavefront.midi.isvirtual = 1;
+ card->wavefront.midi.output_mpu = internal_mpu;
+ card->wavefront.midi.input_mpu = internal_mpu;
+ spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags);
+}
+
+void
+snd_wavefront_midi_disable_virtual (snd_wavefront_card_t *card)
+
+{
+ unsigned long flags;
+
+ spin_lock_irqsave (&card->wavefront.midi.virtual, flags);
+ // snd_wavefront_midi_input_close (card->ics2115_external_rmidi);
+ // snd_wavefront_midi_output_close (card->ics2115_external_rmidi);
+ card->wavefront.midi.isvirtual = 0;
+ spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags);
+}
+
+int
+snd_wavefront_midi_start (snd_wavefront_card_t *card)
+
+{
+ int ok, i;
+ unsigned char rbuf[4], wbuf[4];
+ snd_wavefront_t *dev;
+ snd_wavefront_midi_t *midi;
+
+ dev = &card->wavefront;
+ midi = &dev->midi;
+
+ /* The ICS2115 MPU-401 interface doesn't do anything
+ until its set into UART mode.
+ */
+
+ /* XXX fix me - no hard timing loops allowed! */
+
+ for (i = 0; i < 30000 && !output_ready (midi); i++);
+
+ if (!output_ready (midi)) {
+ snd_printk ("MIDI interface not ready for command\n");
+ return -1;
+ }
+
+ /* Any interrupts received from now on
+ are owned by the MIDI side of things.
+ */
+
+ dev->interrupts_are_midi = 1;
+
+ outb (UART_MODE_ON, midi->mpu_command_port);
+
+ for (ok = 0, i = 50000; i > 0 && !ok; i--) {
+ if (input_avail (midi)) {
+ if (read_data (midi) == MPU_ACK) {
+ ok = 1;
+ break;
+ }
+ }
+ }
+
+ if (!ok) {
+ snd_printk ("cannot set UART mode for MIDI interface");
+ dev->interrupts_are_midi = 0;
+ return -1;
+ }
+
+ /* Route external MIDI to WaveFront synth (by default) */
+
+ if (snd_wavefront_cmd (dev, WFC_MISYNTH_ON, rbuf, wbuf)) {
+ snd_printk ("can't enable MIDI-IN-2-synth routing.\n");
+ /* XXX error ? */
+ }
+
+ /* Turn on Virtual MIDI, but first *always* turn it off,
+ since otherwise consecutive 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 (snd_wavefront_cmd (dev, WFC_VMIDI_OFF, rbuf, wbuf)) {
+ snd_printk ("virtual MIDI mode not disabled\n");
+ return 0; /* We're OK, but missing the external MIDI dev */
+ }
+
+ snd_wavefront_midi_enable_virtual (card);
+
+ if (snd_wavefront_cmd (dev, WFC_VMIDI_ON, rbuf, wbuf)) {
+ snd_printk ("cannot enable virtual MIDI mode.\n");
+ snd_wavefront_midi_disable_virtual (card);
+ }
+ return 0;
+}
+
+const struct snd_rawmidi_ops snd_wavefront_midi_output =
+{
+ .open = snd_wavefront_midi_output_open,
+ .close = snd_wavefront_midi_output_close,
+ .trigger = snd_wavefront_midi_output_trigger,
+};
+
+const struct snd_rawmidi_ops snd_wavefront_midi_input =
+{
+ .open = snd_wavefront_midi_input_open,
+ .close = snd_wavefront_midi_input_close,
+ .trigger = snd_wavefront_midi_input_trigger,
+};
+
diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c
new file mode 100644
index 0000000000..13ce96148f
--- /dev/null
+++ b/sound/isa/wavefront/wavefront_synth.c
@@ -0,0 +1,2222 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) by Paul Barton-Davis 1998-1999
+ *
+ * Some portions of this file are taken from work that is
+ * copyright (C) by Hannu Savolainen 1993-1996
+ */
+
+/*
+ * An ALSA lowlevel driver for Turtle Beach ICS2115 wavetable synth
+ * (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.
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/sched/signal.h>
+#include <linux/firmware.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/snd_wavefront.h>
+#include <sound/initval.h>
+
+static int wf_raw = 0; /* we normally check for "raw state" to firmware
+ loading. if non-zero, 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 = 0; /* 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 */
+
+#define DEFAULT_OSPATH "wavefront.os"
+static char *ospath = DEFAULT_OSPATH; /* the firmware file name */
+
+static int wait_usecs = 150; /* This magic number seems to give pretty optimal
+ throughput based on my limited experimentation.
+ If you want to play around with it and find a better
+ value, be my guest. Remember, the idea is to
+ get a number that causes us to just busy wait
+ for as many WaveFront commands as possible, without
+ coming up with a number so large that we hog the
+ whole CPU.
+
+ Specifically, with this number, out of about 134,000
+ status waits, only about 250 result in a sleep.
+ */
+
+static int sleep_interval = 100; /* HZ/sleep_interval seconds per sleep */
+static int sleep_tries = 50; /* number of times we'll try to sleep */
+
+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, 0444);
+MODULE_PARM_DESC(wf_raw, "if non-zero, assume that we need to boot the OS");
+module_param(fx_raw, int, 0444);
+MODULE_PARM_DESC(fx_raw, "if non-zero, assume that the FX process needs help");
+module_param(debug_default, int, 0444);
+MODULE_PARM_DESC(debug_default, "debug parameters for card initialization");
+module_param(wait_usecs, int, 0444);
+MODULE_PARM_DESC(wait_usecs, "how long to wait without sleeping, usecs");
+module_param(sleep_interval, int, 0444);
+MODULE_PARM_DESC(sleep_interval, "how long to sleep when waiting for reply");
+module_param(sleep_tries, int, 0444);
+MODULE_PARM_DESC(sleep_tries, "how many times to try sleeping during a wait");
+module_param(ospath, charp, 0444);
+MODULE_PARM_DESC(ospath, "pathname to processed ICS2115 OS firmware");
+module_param(reset_time, int, 0444);
+MODULE_PARM_DESC(reset_time, "how long to wait for a reset to take effect");
+module_param(ramcheck_time, int, 0444);
+MODULE_PARM_DESC(ramcheck_time, "how many seconds to wait for the RAM test");
+module_param(osrun_time, int, 0444);
+MODULE_PARM_DESC(osrun_time, "how many seconds to wait for the ICS2115 OS");
+
+/* 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
+
+#define DPRINT(cond, ...) \
+ if ((dev->debug & (cond)) == (cond)) { \
+ snd_printk (__VA_ARGS__); \
+ }
+#else
+#define DPRINT(cond, args...)
+#endif /* WF_DEBUG */
+
+#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
+
+static int wavefront_delete_sample (snd_wavefront_t *, int sampnum);
+static int wavefront_find_free_sample (snd_wavefront_t *);
+
+struct wavefront_command {
+ int cmd;
+ char *action;
+ unsigned int read_cnt;
+ unsigned int write_cnt;
+ int need_ack;
+};
+
+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" },
+ { 0x0 }
+};
+
+#define NEEDS_ACK 1
+
+static struct 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 snd_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 struct 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 NULL;
+}
+
+static inline int
+wavefront_status (snd_wavefront_t *dev)
+
+{
+ return inb (dev->status_port);
+}
+
+static int
+wavefront_sleep (int limit)
+
+{
+ schedule_timeout_interruptible(limit);
+
+ return signal_pending(current);
+}
+
+static int
+wavefront_wait (snd_wavefront_t *dev, int mask)
+
+{
+ int i;
+
+ /* Spin for a short period of time, because >99% of all
+ requests to the WaveFront can be serviced inline like this.
+ */
+
+ for (i = 0; i < wait_usecs; i += 5) {
+ if (wavefront_status (dev) & mask) {
+ return 1;
+ }
+ udelay(5);
+ }
+
+ for (i = 0; i < sleep_tries; i++) {
+
+ if (wavefront_status (dev) & mask) {
+ return 1;
+ }
+
+ if (wavefront_sleep (HZ/sleep_interval)) {
+ return (0);
+ }
+ }
+
+ return (0);
+}
+
+static int
+wavefront_read (snd_wavefront_t *dev)
+
+{
+ if (wavefront_wait (dev, STAT_CAN_READ))
+ return inb (dev->data_port);
+
+ DPRINT (WF_DEBUG_DATA, "read timeout.\n");
+
+ return -1;
+}
+
+static int
+wavefront_write (snd_wavefront_t *dev, unsigned char data)
+
+{
+ if (wavefront_wait (dev, STAT_CAN_WRITE)) {
+ outb (data, dev->data_port);
+ return 0;
+ }
+
+ DPRINT (WF_DEBUG_DATA, "write timeout.\n");
+
+ return -1;
+}
+
+int
+snd_wavefront_cmd (snd_wavefront_t *dev,
+ int cmd, unsigned char *rbuf, unsigned char *wbuf)
+
+{
+ int ack;
+ unsigned int i;
+ int c;
+ struct wavefront_command *wfcmd;
+
+ wfcmd = wavefront_get_command(cmd);
+ if (!wfcmd) {
+ snd_printk ("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 long) 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 (dev, 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 (dev, 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++) {
+
+ c = wavefront_read(dev);
+ if (c == -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) {
+ c = wavefront_read(dev);
+ if (c == -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.
+ */
+
+ ack = wavefront_read(dev);
+ if (ack == 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 */
+
+ err = wavefront_read(dev);
+ if (err == -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)
+{
+ unsigned 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)
+
+{
+ unsigned 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;
+
+ /* 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 (snd_wavefront_t *dev, int sample_num)
+
+{
+ unsigned char wbuf[2];
+ int x;
+
+ wbuf[0] = sample_num & 0x7f;
+ wbuf[1] = sample_num >> 7;
+
+ x = snd_wavefront_cmd(dev, WFC_DELETE_SAMPLE, NULL, wbuf);
+ if (!x)
+ dev->sample_status[sample_num] = WF_ST_EMPTY;
+
+ return x;
+}
+
+static int
+wavefront_get_sample_status (snd_wavefront_t *dev, int assume_rom)
+
+{
+ int i;
+ unsigned char rbuf[32], wbuf[32];
+ unsigned int sc_real, sc_alias, sc_multi;
+
+ /* check sample status */
+
+ if (snd_wavefront_cmd (dev, WFC_GET_NSAMPLES, rbuf, wbuf)) {
+ snd_printk ("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 (snd_wavefront_cmd (dev, WFC_IDENTIFY_SAMPLE_TYPE, rbuf, wbuf)) {
+ snd_printk(KERN_WARNING "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:
+ snd_printk ("unknown sample type for "
+ "slot %d (0x%x)\n",
+ i, rbuf[0]);
+ }
+
+ if (rbuf[0] != WF_ST_EMPTY) {
+ dev->samples_used++;
+ }
+ }
+
+ snd_printk ("%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 (snd_wavefront_t *dev)
+
+{
+ 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;
+
+ x = snd_wavefront_cmd(dev, WFC_UPLOAD_PATCH, patchbuf,
+ patchnum);
+ if (x == 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 {
+ snd_printk ("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++;
+ }
+
+ }
+ snd_printk ("%d patch slots filled, %d in use\n", cnt, cnt2);
+
+ return (0);
+}
+
+static int
+wavefront_get_program_status (snd_wavefront_t *dev)
+
+{
+ 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;
+
+ x = snd_wavefront_cmd(dev, WFC_UPLOAD_PROGRAM, progbuf,
+ &prognum);
+ if (x == 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 {
+ snd_printk ("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++;
+ }
+ }
+
+ snd_printk ("%d programs slots in use\n", cnt);
+
+ return (0);
+}
+
+static int
+wavefront_send_patch (snd_wavefront_t *dev, 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);
+
+ if (header->number >= ARRAY_SIZE(dev->patch_status))
+ return -EINVAL;
+
+ dev->patch_status[header->number] |= WF_SLOT_FILLED;
+
+ bptr = munge_int32 (header->number, buf, 2);
+ munge_buf ((unsigned char *)&header->hdr.p, bptr, WF_PATCH_BYTES);
+
+ if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PATCH, NULL, buf)) {
+ snd_printk ("download patch failed\n");
+ return -EIO;
+ }
+
+ return (0);
+}
+
+static int
+wavefront_send_program (snd_wavefront_t *dev, wavefront_patch_info *header)
+
+{
+ unsigned char buf[WF_PROGRAM_BYTES+1];
+ int i;
+
+ DPRINT (WF_DEBUG_LOAD_PATCH, "downloading program %d\n",
+ header->number);
+
+ if (header->number >= ARRAY_SIZE(dev->prog_status))
+ return -EINVAL;
+
+ 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 (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PROGRAM, NULL, buf)) {
+ snd_printk ("download patch failed\n");
+ return -EIO;
+ }
+
+ return (0);
+}
+
+static int
+wavefront_freemem (snd_wavefront_t *dev)
+
+{
+ char rbuf[8];
+
+ if (snd_wavefront_cmd (dev, WFC_REPORT_FREE_MEMORY, rbuf, NULL)) {
+ snd_printk ("can't get memory stats.\n");
+ return -1;
+ } else {
+ return demunge_int32 (rbuf, 4);
+ }
+}
+
+static int
+wavefront_send_sample (snd_wavefront_t *dev,
+ wavefront_patch_info *header,
+ u16 __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.
+ */
+
+ u16 sample_short = 0;
+ u32 length;
+ u16 __user *data_end = NULL;
+ unsigned int i;
+ const unsigned 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 0x%lx\n",
+ header->size ? "" : "header ",
+ header->number, header->subkey,
+ header->size,
+ (unsigned long) header->dataptr);
+
+ if (header->number == WAVEFRONT_FIND_FREE_SAMPLE_SLOT) {
+ int x;
+
+ x = wavefront_find_free_sample(dev);
+ if (x < 0)
+ return -ENOMEM;
+ snd_printk ("unspecified sample => %d\n", x);
+ header->number = x;
+ }
+
+ if (header->number >= WF_MAX_SAMPLE)
+ return -EINVAL;
+
+ 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) {
+ snd_printk ("sample slot %d "
+ "write protected\n",
+ header->number);
+ return -EACCES;
+ }
+ }
+
+ wavefront_delete_sample (dev, header->number);
+ }
+
+ if (header->size) {
+ dev->freemem = wavefront_freemem (dev);
+
+ if (dev->freemem < (int)header->size) {
+ snd_printk ("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) {
+ snd_printk ("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 (*((u32 *) &header->hdr.s.sampleStartOffset),
+ shptr, 4);
+ shptr = munge_int32 (*((u32 *) &header->hdr.s.loopStartOffset),
+ shptr, 4);
+ shptr = munge_int32 (*((u32 *) &header->hdr.s.loopEndOffset),
+ shptr, 4);
+ shptr = munge_int32 (*((u32 *) &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 (snd_wavefront_cmd (dev,
+ header->size ?
+ WFC_DOWNLOAD_SAMPLE : WFC_DOWNLOAD_SAMPLE_HEADER,
+ NULL, sample_hdr)) {
+ snd_printk ("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 = ALIGN(length - written, 8);
+ }
+
+ if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_BLOCK, NULL, NULL)) {
+ snd_printk ("download block "
+ "request refused.\n");
+ return -EIO;
+ }
+
+ for (i = 0; i < blocksize; i++) {
+
+ if (dataptr < data_end) {
+
+ if (get_user(sample_short, dataptr))
+ return -EFAULT;
+ 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.
+ */
+
+ dma_ack = wavefront_read(dev);
+ if (dma_ack != WF_DMA_ACK) {
+ if (dma_ack == -1) {
+ snd_printk ("upload sample "
+ "DMA ack timeout\n");
+ return -EIO;
+ } else {
+ snd_printk ("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 (snd_wavefront_t *dev, 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);
+
+ if (header->number >= WF_MAX_SAMPLE)
+ return -EINVAL;
+
+ 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 (snd_wavefront_cmd (dev, WFC_DOWNLOAD_SAMPLE_ALIAS, NULL, alias_hdr)) {
+ snd_printk ("download alias failed.\n");
+ return -EIO;
+ }
+
+ dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_ALIAS);
+
+ return (0);
+}
+
+static int
+wavefront_send_multisample (snd_wavefront_t *dev, wavefront_patch_info *header)
+{
+ int i;
+ int num_samples;
+ unsigned char *msample_hdr;
+
+ if (header->number >= WF_MAX_SAMPLE)
+ return -EINVAL;
+
+ msample_hdr = kmalloc(WF_MSAMPLE_BYTES, GFP_KERNEL);
+ if (! msample_hdr)
+ return -ENOMEM;
+
+ 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 (snd_wavefront_cmd (dev, WFC_DOWNLOAD_MULTISAMPLE,
+ (unsigned char *) (long) ((num_samples*2)+3),
+ msample_hdr)) {
+ snd_printk ("download of multisample failed.\n");
+ kfree(msample_hdr);
+ return -EIO;
+ }
+
+ dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_MULTISAMPLE);
+
+ kfree(msample_hdr);
+ return (0);
+}
+
+static int
+wavefront_fetch_multisample (snd_wavefront_t *dev,
+ 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 (snd_wavefront_cmd (dev, WFC_UPLOAD_MULTISAMPLE, log_ns, number)) {
+ snd_printk ("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++) {
+ char d[2];
+ int val;
+
+ val = wavefront_read(dev);
+ if (val == -1) {
+ snd_printk ("upload multisample failed "
+ "during sample loop.\n");
+ return -EIO;
+ }
+ d[0] = val;
+
+ val = wavefront_read(dev);
+ if (val == -1) {
+ snd_printk ("upload multisample failed "
+ "during sample loop.\n");
+ return -EIO;
+ }
+ d[1] = val;
+
+ 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 (snd_wavefront_t *dev, 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 (snd_wavefront_cmd (dev, WFC_DOWNLOAD_EDRUM_PROGRAM, NULL, drumbuf)) {
+ snd_printk ("download drum failed.\n");
+ return -EIO;
+ }
+
+ return (0);
+}
+
+static int
+wavefront_find_free_sample (snd_wavefront_t *dev)
+
+{
+ int i;
+
+ for (i = 0; i < WF_MAX_SAMPLE; i++) {
+ if (!(dev->sample_status[i] & WF_SLOT_FILLED)) {
+ return i;
+ }
+ }
+ snd_printk ("no free sample slots!\n");
+ return -1;
+}
+
+#if 0
+static int
+wavefront_find_free_patch (snd_wavefront_t *dev)
+
+{
+ int i;
+
+ for (i = 0; i < WF_MAX_PATCH; i++) {
+ if (!(dev->patch_status[i] & WF_SLOT_FILLED)) {
+ return i;
+ }
+ }
+ snd_printk ("no free patch slots!\n");
+ return -1;
+}
+#endif
+
+static int
+wavefront_load_patch (snd_wavefront_t *dev, const char __user *addr)
+{
+ wavefront_patch_info *header;
+ int err;
+
+ header = kmalloc(sizeof(*header), GFP_KERNEL);
+ if (! header)
+ return -ENOMEM;
+
+ if (copy_from_user (header, addr, sizeof(wavefront_patch_info) -
+ sizeof(wavefront_any))) {
+ snd_printk ("bad address for load patch.\n");
+ err = -EFAULT;
+ goto __error;
+ }
+
+ 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 (&header->hdr.s, header->hdrptr,
+ sizeof (wavefront_sample))) {
+ err = -EFAULT;
+ break;
+ }
+
+ err = wavefront_send_sample (dev, header, header->dataptr, 0);
+ break;
+
+ case WF_ST_MULTISAMPLE:
+
+ if (copy_from_user (&header->hdr.s, header->hdrptr,
+ sizeof (wavefront_multisample))) {
+ err = -EFAULT;
+ break;
+ }
+
+ err = wavefront_send_multisample (dev, header);
+ break;
+
+ case WF_ST_ALIAS:
+
+ if (copy_from_user (&header->hdr.a, header->hdrptr,
+ sizeof (wavefront_alias))) {
+ err = -EFAULT;
+ break;
+ }
+
+ err = wavefront_send_alias (dev, header);
+ break;
+
+ case WF_ST_DRUM:
+ if (copy_from_user (&header->hdr.d, header->hdrptr,
+ sizeof (wavefront_drum))) {
+ err = -EFAULT;
+ break;
+ }
+
+ err = wavefront_send_drum (dev, header);
+ break;
+
+ case WF_ST_PATCH:
+ if (copy_from_user (&header->hdr.p, header->hdrptr,
+ sizeof (wavefront_patch))) {
+ err = -EFAULT;
+ break;
+ }
+
+ err = wavefront_send_patch (dev, header);
+ break;
+
+ case WF_ST_PROGRAM:
+ if (copy_from_user (&header->hdr.pr, header->hdrptr,
+ sizeof (wavefront_program))) {
+ err = -EFAULT;
+ break;
+ }
+
+ err = wavefront_send_program (dev, header);
+ break;
+
+ default:
+ snd_printk ("unknown patch type %d.\n",
+ header->subkey);
+ err = -EINVAL;
+ break;
+ }
+
+ __error:
+ kfree(header);
+ return err;
+}
+
+/***********************************************************************
+WaveFront: hardware-dependent interface
+***********************************************************************/
+
+static void
+process_sample_hdr (u8 *buf)
+
+{
+ wavefront_sample s;
+ u8 *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.
+ */
+
+ *((u32 *) &s.sampleStartOffset) = demunge_int32 (ptr, 4); ptr += 4;
+ *((u32 *) &s.loopStartOffset) = demunge_int32 (ptr, 4); ptr += 4;
+ *((u32 *) &s.loopEndOffset) = demunge_int32 (ptr, 4); ptr += 4;
+ *((u32 *) &s.sampleEndOffset) = demunge_int32 (ptr, 4); ptr += 4;
+ *((u32 *) &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 (snd_wavefront_card_t *acard,
+ wavefront_control *wc)
+
+{
+ snd_wavefront_t *dev = &acard->wavefront;
+ 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:
+ snd_printk ("interrupts disabled.\n");
+ outb (0x80|0x20, dev->control_port);
+ dev->interrupts_are_midi = 1;
+ return 0;
+
+ case WFC_ENABLE_INTERRUPTS:
+ snd_printk ("interrupts enabled.\n");
+ outb (0x80|0x40|0x20, dev->control_port);
+ dev->interrupts_are_midi = 1;
+ return 0;
+
+ case WFC_INTERRUPT_STATUS:
+ wc->rbuf[0] = dev->interrupts_are_midi;
+ 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) {
+ snd_printk ("invalid slot ID %d\n",
+ i);
+ wc->status = EINVAL;
+ return -EINVAL;
+ }
+ wc->rbuf[0] = dev->sample_status[i];
+ wc->status = 0;
+ return 0;
+
+ case WFC_DEBUG_DRIVER:
+ dev->debug = wc->wbuf[0];
+ snd_printk ("debug = 0x%x\n", dev->debug);
+ return 0;
+
+ case WFC_UPLOAD_PATCH:
+ munge_int32 (*((u32 *) 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 snd_wavefront_cmd() alone.
+ */
+ wc->status = wavefront_fetch_multisample
+ (dev, (wavefront_patch_info *) wc->rbuf);
+ return 0;
+
+ case WFC_UPLOAD_SAMPLE_ALIAS:
+ snd_printk ("support for sample alias upload "
+ "being considered.\n");
+ wc->status = EINVAL;
+ return -EINVAL;
+ }
+
+ wc->status = snd_wavefront_cmd (dev, 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:
+ snd_printk ("support for "
+ "sample aliases still "
+ "being considered.\n");
+ break;
+
+ case WFC_VMIDI_OFF:
+ snd_wavefront_midi_disable_virtual (acard);
+ break;
+
+ case WFC_VMIDI_ON:
+ snd_wavefront_midi_enable_virtual (acard);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int
+snd_wavefront_synth_open (struct snd_hwdep *hw, struct file *file)
+
+{
+ if (!try_module_get(hw->card->module))
+ return -EFAULT;
+ file->private_data = hw;
+ return 0;
+}
+
+int
+snd_wavefront_synth_release (struct snd_hwdep *hw, struct file *file)
+
+{
+ module_put(hw->card->module);
+ return 0;
+}
+
+int
+snd_wavefront_synth_ioctl (struct snd_hwdep *hw, struct file *file,
+ unsigned int cmd, unsigned long arg)
+
+{
+ struct snd_card *card;
+ snd_wavefront_t *dev;
+ snd_wavefront_card_t *acard;
+ wavefront_control *wc;
+ void __user *argp = (void __user *)arg;
+ int err;
+
+ card = (struct snd_card *) hw->card;
+
+ if (snd_BUG_ON(!card))
+ return -ENODEV;
+ if (snd_BUG_ON(!card->private_data))
+ return -ENODEV;
+
+ acard = card->private_data;
+ dev = &acard->wavefront;
+
+ switch (cmd) {
+ case WFCTL_LOAD_SPP:
+ if (wavefront_load_patch (dev, argp) != 0) {
+ return -EIO;
+ }
+ break;
+
+ case WFCTL_WFCMD:
+ wc = memdup_user(argp, sizeof(*wc));
+ if (IS_ERR(wc))
+ return PTR_ERR(wc);
+
+ if (wavefront_synth_control (acard, wc) < 0)
+ err = -EIO;
+ else if (copy_to_user (argp, wc, sizeof (*wc)))
+ err = -EFAULT;
+ else
+ err = 0;
+ kfree(wc);
+ return err;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+/***********************************************************************/
+/* WaveFront: interface for card-level wavefront module */
+/***********************************************************************/
+
+void
+snd_wavefront_internal_interrupt (snd_wavefront_card_t *card)
+{
+ snd_wavefront_t *dev = &card->wavefront;
+
+ /*
+ 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(dev) & (STAT_INTR_READ|STAT_INTR_WRITE)) == 0) {
+ return;
+ }
+
+ spin_lock(&dev->irq_lock);
+ dev->irq_ok = 1;
+ dev->irq_cnt++;
+ spin_unlock(&dev->irq_lock);
+ wake_up(&dev->interrupt_sleeper);
+}
+
+/* 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
+snd_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:
+ snd_printk ("invalid IRQ %d\n", irq);
+ bits = -1;
+ }
+
+ return bits;
+}
+
+static void
+wavefront_should_cause_interrupt (snd_wavefront_t *dev,
+ int val, int port, unsigned long timeout)
+
+{
+ wait_queue_entry_t wait;
+
+ init_waitqueue_entry(&wait, current);
+ spin_lock_irq(&dev->irq_lock);
+ add_wait_queue(&dev->interrupt_sleeper, &wait);
+ dev->irq_ok = 0;
+ outb (val,port);
+ spin_unlock_irq(&dev->irq_lock);
+ while (!dev->irq_ok && time_before(jiffies, timeout)) {
+ schedule_timeout_uninterruptible(1);
+ barrier();
+ }
+}
+
+static int
+wavefront_reset_to_cleanliness (snd_wavefront_t *dev)
+
+{
+ int bits;
+ int hwv[2];
+
+ /* IRQ already checked */
+
+ bits = snd_wavefront_interrupt_bits (dev->irq);
+
+ /* 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(dev, 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) {
+ snd_printk ("intr not received after h/w un-reset.\n");
+ goto gone_bad;
+ }
+
+ /* 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(dev, WFC_HARDWARE_VERSION,
+ dev->data_port, ramcheck_time*HZ);
+
+ if (!dev->irq_ok) {
+ snd_printk ("post-RAM-check interrupt not received.\n");
+ goto gone_bad;
+ }
+
+ if (!wavefront_wait (dev, STAT_CAN_READ)) {
+ snd_printk ("no response to HW version cmd.\n");
+ goto gone_bad;
+ }
+
+ hwv[0] = wavefront_read(dev);
+ if (hwv[0] == -1) {
+ snd_printk ("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.
+ */
+
+ hwv[0] = wavefront_read(dev);
+ if (hwv[0] == -1) {
+ snd_printk ("on-board RAM test failed "
+ "(bad error code).\n");
+ } else {
+ snd_printk ("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 */
+
+ hwv[1] = wavefront_read(dev);
+ if (hwv[1] == -1) {
+ snd_printk ("incorrect h/w response.\n");
+ goto gone_bad;
+ }
+
+ snd_printk ("hardware version %d.%d\n",
+ hwv[0], hwv[1]);
+
+ return 0;
+
+
+ gone_bad:
+ return (1);
+}
+
+static int
+wavefront_download_firmware (snd_wavefront_t *dev, char *path)
+
+{
+ const unsigned char *buf;
+ int len, err;
+ int section_cnt_downloaded = 0;
+ const struct firmware *firmware;
+
+ err = request_firmware(&firmware, path, dev->card->dev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "firmware (%s) download failed!!!\n", path);
+ return 1;
+ }
+
+ len = 0;
+ buf = firmware->data;
+ for (;;) {
+ int section_length = *(signed char *)buf;
+ if (section_length == 0)
+ break;
+ if (section_length < 0 || section_length > WF_SECTION_MAX) {
+ snd_printk(KERN_ERR
+ "invalid firmware section length %d\n",
+ section_length);
+ goto failure;
+ }
+ buf++;
+ len++;
+
+ if (firmware->size < len + section_length) {
+ snd_printk(KERN_ERR "firmware section read error.\n");
+ goto failure;
+ }
+
+ /* Send command */
+ if (wavefront_write(dev, WFC_DOWNLOAD_OS))
+ goto failure;
+
+ for (; section_length; section_length--) {
+ if (wavefront_write(dev, *buf))
+ goto failure;
+ buf++;
+ len++;
+ }
+
+ /* get ACK */
+ if (!wavefront_wait(dev, STAT_CAN_READ)) {
+ snd_printk(KERN_ERR "time out for firmware ACK.\n");
+ goto failure;
+ }
+ err = inb(dev->data_port);
+ if (err != WF_ACK) {
+ snd_printk(KERN_ERR
+ "download of section #%d not "
+ "acknowledged, ack = 0x%x\n",
+ section_cnt_downloaded + 1, err);
+ goto failure;
+ }
+
+ section_cnt_downloaded++;
+ }
+
+ release_firmware(firmware);
+ return 0;
+
+ failure:
+ release_firmware(firmware);
+ snd_printk(KERN_ERR "firmware download failed!!!\n");
+ return 1;
+}
+
+
+static int
+wavefront_do_reset (snd_wavefront_t *dev)
+
+{
+ char voices[1];
+
+ if (wavefront_reset_to_cleanliness (dev)) {
+ snd_printk ("hw reset failed.\n");
+ goto gone_bad;
+ }
+
+ if (dev->israw) {
+ if (wavefront_download_firmware (dev, 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 (dev, WFC_NOOP,
+ dev->data_port,
+ (osrun_time*HZ));
+
+ if (!dev->irq_ok) {
+ snd_printk ("no post-OS interrupt.\n");
+ goto gone_bad;
+ }
+
+ /* Now, do it again ! */
+
+ wavefront_should_cause_interrupt (dev, WFC_NOOP,
+ dev->data_port, (10*HZ));
+
+ if (!dev->irq_ok) {
+ snd_printk ("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);
+ }
+
+ /* SETUPSND.EXE asks for sample memory config here, but since i
+ have no idea how to interpret the result, we'll forget
+ about it.
+ */
+
+ dev->freemem = wavefront_freemem(dev);
+ if (dev->freemem < 0)
+ goto gone_bad;
+
+ snd_printk ("available DRAM %dk\n", dev->freemem / 1024);
+
+ if (wavefront_write (dev, 0xf0) ||
+ wavefront_write (dev, 1) ||
+ (wavefront_read (dev) < 0)) {
+ dev->debug = 0;
+ snd_printk ("MPU emulation mode not set.\n");
+ goto gone_bad;
+ }
+
+ voices[0] = 32;
+
+ if (snd_wavefront_cmd (dev, WFC_SET_NVOICES, NULL, voices)) {
+ snd_printk ("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_are_midi = 0;
+ return 1;
+}
+
+int
+snd_wavefront_start (snd_wavefront_t *dev)
+
+{
+ int samples_are_from_rom;
+
+ /* IMPORTANT: assumes that snd_wavefront_detect() and/or
+ wavefront_reset_to_cleanliness() has already been called
+ */
+
+ 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 (dev)) {
+ return -1;
+ }
+ }
+ /* Check for FX device, present only on Tropez+ */
+
+ dev->has_fx = (snd_wavefront_fx_detect (dev) == 0);
+
+ if (dev->has_fx && fx_raw) {
+ snd_wavefront_fx_start (dev);
+ }
+
+ wavefront_get_sample_status (dev, samples_are_from_rom);
+ wavefront_get_program_status (dev);
+ wavefront_get_patch_status (dev);
+
+ /* Start normal operation: unreset, master interrupt enabled, no mute
+ */
+
+ outb (0x80|0x40|0x20, dev->control_port);
+
+ return (0);
+}
+
+int
+snd_wavefront_detect (snd_wavefront_card_t *card)
+
+{
+ unsigned char rbuf[4], wbuf[4];
+ snd_wavefront_t *dev = &card->wavefront;
+
+ /* returns zero if a WaveFront card is successfully detected.
+ negative otherwise.
+ */
+
+ dev->israw = 0;
+ dev->has_fx = 0;
+ dev->debug = debug_default;
+ dev->interrupts_are_midi = 0;
+ dev->irq_cnt = 0;
+ dev->rom_samples_rdonly = 1;
+
+ if (snd_wavefront_cmd (dev, WFC_FIRMWARE_VERSION, rbuf, wbuf) == 0) {
+
+ dev->fw_version[0] = rbuf[0];
+ dev->fw_version[1] = rbuf[1];
+
+ snd_printk ("firmware %d.%d already loaded.\n",
+ rbuf[0], rbuf[1]);
+
+ /* check that a command actually works */
+
+ if (snd_wavefront_cmd (dev, WFC_HARDWARE_VERSION,
+ rbuf, wbuf) == 0) {
+ dev->hw_version[0] = rbuf[0];
+ dev->hw_version[1] = rbuf[1];
+ } else {
+ snd_printk ("not raw, but no "
+ "hardware version!\n");
+ return -1;
+ }
+
+ if (!wf_raw) {
+ return 0;
+ } else {
+ snd_printk ("reloading firmware as you requested.\n");
+ dev->israw = 1;
+ }
+
+ } else {
+
+ dev->israw = 1;
+ snd_printk ("no response to firmware probe, assume raw.\n");
+
+ }
+
+ return 0;
+}
+
+MODULE_FIRMWARE(DEFAULT_OSPATH);
diff --git a/sound/isa/wss/Makefile b/sound/isa/wss/Makefile
new file mode 100644
index 0000000000..34d0636b3d
--- /dev/null
+++ b/sound/isa/wss/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for ALSA
+# Copyright (c) 2008 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-wss-lib-objs := wss_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_WSS_LIB) += snd-wss-lib.o
+
diff --git a/sound/isa/wss/wss_lib.c b/sound/isa/wss/wss_lib.c
new file mode 100644
index 0000000000..026061b55e
--- /dev/null
+++ b/sound/isa/wss/wss_lib.c
@@ -0,0 +1,2202 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * Routines for control of CS4231(A)/CS4232/InterWave & compatible chips
+ *
+ * Bugs:
+ * - sometimes record brokes playback with WSS portion of
+ * Yamaha OPL3-SA3 chip
+ * - CS4231 (GUS MAX) - still trouble with occasional noises
+ * - broken initialization?
+ */
+
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for control of CS4231(A)/CS4232/InterWave & compatible chips");
+MODULE_LICENSE("GPL");
+
+#if 0
+#define SNDRV_DEBUG_MCE
+#endif
+
+/*
+ * Some variables
+ */
+
+static const unsigned char freq_bits[14] = {
+ /* 5510 */ 0x00 | CS4231_XTAL2,
+ /* 6620 */ 0x0E | CS4231_XTAL2,
+ /* 8000 */ 0x00 | CS4231_XTAL1,
+ /* 9600 */ 0x0E | CS4231_XTAL1,
+ /* 11025 */ 0x02 | CS4231_XTAL2,
+ /* 16000 */ 0x02 | CS4231_XTAL1,
+ /* 18900 */ 0x04 | CS4231_XTAL2,
+ /* 22050 */ 0x06 | CS4231_XTAL2,
+ /* 27042 */ 0x04 | CS4231_XTAL1,
+ /* 32000 */ 0x06 | CS4231_XTAL1,
+ /* 33075 */ 0x0C | CS4231_XTAL2,
+ /* 37800 */ 0x08 | CS4231_XTAL2,
+ /* 44100 */ 0x0A | CS4231_XTAL2,
+ /* 48000 */ 0x0C | CS4231_XTAL1
+};
+
+static const unsigned int rates[14] = {
+ 5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050,
+ 27042, 32000, 33075, 37800, 44100, 48000
+};
+
+static const struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static int snd_wss_xrate(struct snd_pcm_runtime *runtime)
+{
+ return snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_rates);
+}
+
+static const unsigned char snd_wss_original_image[32] =
+{
+ 0x00, /* 00/00 - lic */
+ 0x00, /* 01/01 - ric */
+ 0x9f, /* 02/02 - la1ic */
+ 0x9f, /* 03/03 - ra1ic */
+ 0x9f, /* 04/04 - la2ic */
+ 0x9f, /* 05/05 - ra2ic */
+ 0xbf, /* 06/06 - loc */
+ 0xbf, /* 07/07 - roc */
+ 0x20, /* 08/08 - pdfr */
+ CS4231_AUTOCALIB, /* 09/09 - ic */
+ 0x00, /* 0a/10 - pc */
+ 0x00, /* 0b/11 - ti */
+ CS4231_MODE2, /* 0c/12 - mi */
+ 0xfc, /* 0d/13 - lbc */
+ 0x00, /* 0e/14 - pbru */
+ 0x00, /* 0f/15 - pbrl */
+ 0x80, /* 10/16 - afei */
+ 0x01, /* 11/17 - afeii */
+ 0x9f, /* 12/18 - llic */
+ 0x9f, /* 13/19 - rlic */
+ 0x00, /* 14/20 - tlb */
+ 0x00, /* 15/21 - thb */
+ 0x00, /* 16/22 - la3mic/reserved */
+ 0x00, /* 17/23 - ra3mic/reserved */
+ 0x00, /* 18/24 - afs */
+ 0x00, /* 19/25 - lamoc/version */
+ 0xcf, /* 1a/26 - mioc */
+ 0x00, /* 1b/27 - ramoc/reserved */
+ 0x20, /* 1c/28 - cdfr */
+ 0x00, /* 1d/29 - res4 */
+ 0x00, /* 1e/30 - cbru */
+ 0x00, /* 1f/31 - cbrl */
+};
+
+static const unsigned char snd_opti93x_original_image[32] =
+{
+ 0x00, /* 00/00 - l_mixout_outctrl */
+ 0x00, /* 01/01 - r_mixout_outctrl */
+ 0x88, /* 02/02 - l_cd_inctrl */
+ 0x88, /* 03/03 - r_cd_inctrl */
+ 0x88, /* 04/04 - l_a1/fm_inctrl */
+ 0x88, /* 05/05 - r_a1/fm_inctrl */
+ 0x80, /* 06/06 - l_dac_inctrl */
+ 0x80, /* 07/07 - r_dac_inctrl */
+ 0x00, /* 08/08 - ply_dataform_reg */
+ 0x00, /* 09/09 - if_conf */
+ 0x00, /* 0a/10 - pin_ctrl */
+ 0x00, /* 0b/11 - err_init_reg */
+ 0x0a, /* 0c/12 - id_reg */
+ 0x00, /* 0d/13 - reserved */
+ 0x00, /* 0e/14 - ply_upcount_reg */
+ 0x00, /* 0f/15 - ply_lowcount_reg */
+ 0x88, /* 10/16 - reserved/l_a1_inctrl */
+ 0x88, /* 11/17 - reserved/r_a1_inctrl */
+ 0x88, /* 12/18 - l_line_inctrl */
+ 0x88, /* 13/19 - r_line_inctrl */
+ 0x88, /* 14/20 - l_mic_inctrl */
+ 0x88, /* 15/21 - r_mic_inctrl */
+ 0x80, /* 16/22 - l_out_outctrl */
+ 0x80, /* 17/23 - r_out_outctrl */
+ 0x00, /* 18/24 - reserved */
+ 0x00, /* 19/25 - reserved */
+ 0x00, /* 1a/26 - reserved */
+ 0x00, /* 1b/27 - reserved */
+ 0x00, /* 1c/28 - cap_dataform_reg */
+ 0x00, /* 1d/29 - reserved */
+ 0x00, /* 1e/30 - cap_upcount_reg */
+ 0x00 /* 1f/31 - cap_lowcount_reg */
+};
+
+/*
+ * Basic I/O functions
+ */
+
+static inline void wss_outb(struct snd_wss *chip, u8 offset, u8 val)
+{
+ outb(val, chip->port + offset);
+}
+
+static inline u8 wss_inb(struct snd_wss *chip, u8 offset)
+{
+ return inb(chip->port + offset);
+}
+
+static void snd_wss_wait(struct snd_wss *chip)
+{
+ int timeout;
+
+ for (timeout = 250;
+ timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+ timeout--)
+ udelay(100);
+}
+
+static void snd_wss_dout(struct snd_wss *chip, unsigned char reg,
+ unsigned char value)
+{
+ int timeout;
+
+ for (timeout = 250;
+ timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+ timeout--)
+ udelay(10);
+ wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+ wss_outb(chip, CS4231P(REG), value);
+ mb();
+}
+
+void snd_wss_out(struct snd_wss *chip, unsigned char reg, unsigned char value)
+{
+ snd_wss_wait(chip);
+#ifdef CONFIG_SND_DEBUG
+ if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+ snd_printk(KERN_DEBUG "out: auto calibration time out "
+ "- reg = 0x%x, value = 0x%x\n", reg, value);
+#endif
+ wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+ wss_outb(chip, CS4231P(REG), value);
+ chip->image[reg] = value;
+ mb();
+ snd_printdd("codec out - reg 0x%x = 0x%x\n",
+ chip->mce_bit | reg, value);
+}
+EXPORT_SYMBOL(snd_wss_out);
+
+unsigned char snd_wss_in(struct snd_wss *chip, unsigned char reg)
+{
+ snd_wss_wait(chip);
+#ifdef CONFIG_SND_DEBUG
+ if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+ snd_printk(KERN_DEBUG "in: auto calibration time out "
+ "- reg = 0x%x\n", reg);
+#endif
+ wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+ mb();
+ return wss_inb(chip, CS4231P(REG));
+}
+EXPORT_SYMBOL(snd_wss_in);
+
+void snd_cs4236_ext_out(struct snd_wss *chip, unsigned char reg,
+ unsigned char val)
+{
+ wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
+ wss_outb(chip, CS4231P(REG),
+ reg | (chip->image[CS4236_EXT_REG] & 0x01));
+ wss_outb(chip, CS4231P(REG), val);
+ chip->eimage[CS4236_REG(reg)] = val;
+#if 0
+ printk(KERN_DEBUG "ext out : reg = 0x%x, val = 0x%x\n", reg, val);
+#endif
+}
+EXPORT_SYMBOL(snd_cs4236_ext_out);
+
+unsigned char snd_cs4236_ext_in(struct snd_wss *chip, unsigned char reg)
+{
+ wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
+ wss_outb(chip, CS4231P(REG),
+ reg | (chip->image[CS4236_EXT_REG] & 0x01));
+#if 1
+ return wss_inb(chip, CS4231P(REG));
+#else
+ {
+ unsigned char res;
+ res = wss_inb(chip, CS4231P(REG));
+ printk(KERN_DEBUG "ext in : reg = 0x%x, val = 0x%x\n",
+ reg, res);
+ return res;
+ }
+#endif
+}
+EXPORT_SYMBOL(snd_cs4236_ext_in);
+
+#if 0
+
+static void snd_wss_debug(struct snd_wss *chip)
+{
+ printk(KERN_DEBUG
+ "CS4231 REGS: INDEX = 0x%02x "
+ " STATUS = 0x%02x\n",
+ wss_inb(chip, CS4231P(REGSEL)),
+ wss_inb(chip, CS4231P(STATUS)));
+ printk(KERN_DEBUG
+ " 0x00: left input = 0x%02x "
+ " 0x10: alt 1 (CFIG 2) = 0x%02x\n",
+ snd_wss_in(chip, 0x00),
+ snd_wss_in(chip, 0x10));
+ printk(KERN_DEBUG
+ " 0x01: right input = 0x%02x "
+ " 0x11: alt 2 (CFIG 3) = 0x%02x\n",
+ snd_wss_in(chip, 0x01),
+ snd_wss_in(chip, 0x11));
+ printk(KERN_DEBUG
+ " 0x02: GF1 left input = 0x%02x "
+ " 0x12: left line in = 0x%02x\n",
+ snd_wss_in(chip, 0x02),
+ snd_wss_in(chip, 0x12));
+ printk(KERN_DEBUG
+ " 0x03: GF1 right input = 0x%02x "
+ " 0x13: right line in = 0x%02x\n",
+ snd_wss_in(chip, 0x03),
+ snd_wss_in(chip, 0x13));
+ printk(KERN_DEBUG
+ " 0x04: CD left input = 0x%02x "
+ " 0x14: timer low = 0x%02x\n",
+ snd_wss_in(chip, 0x04),
+ snd_wss_in(chip, 0x14));
+ printk(KERN_DEBUG
+ " 0x05: CD right input = 0x%02x "
+ " 0x15: timer high = 0x%02x\n",
+ snd_wss_in(chip, 0x05),
+ snd_wss_in(chip, 0x15));
+ printk(KERN_DEBUG
+ " 0x06: left output = 0x%02x "
+ " 0x16: left MIC (PnP) = 0x%02x\n",
+ snd_wss_in(chip, 0x06),
+ snd_wss_in(chip, 0x16));
+ printk(KERN_DEBUG
+ " 0x07: right output = 0x%02x "
+ " 0x17: right MIC (PnP) = 0x%02x\n",
+ snd_wss_in(chip, 0x07),
+ snd_wss_in(chip, 0x17));
+ printk(KERN_DEBUG
+ " 0x08: playback format = 0x%02x "
+ " 0x18: IRQ status = 0x%02x\n",
+ snd_wss_in(chip, 0x08),
+ snd_wss_in(chip, 0x18));
+ printk(KERN_DEBUG
+ " 0x09: iface (CFIG 1) = 0x%02x "
+ " 0x19: left line out = 0x%02x\n",
+ snd_wss_in(chip, 0x09),
+ snd_wss_in(chip, 0x19));
+ printk(KERN_DEBUG
+ " 0x0a: pin control = 0x%02x "
+ " 0x1a: mono control = 0x%02x\n",
+ snd_wss_in(chip, 0x0a),
+ snd_wss_in(chip, 0x1a));
+ printk(KERN_DEBUG
+ " 0x0b: init & status = 0x%02x "
+ " 0x1b: right line out = 0x%02x\n",
+ snd_wss_in(chip, 0x0b),
+ snd_wss_in(chip, 0x1b));
+ printk(KERN_DEBUG
+ " 0x0c: revision & mode = 0x%02x "
+ " 0x1c: record format = 0x%02x\n",
+ snd_wss_in(chip, 0x0c),
+ snd_wss_in(chip, 0x1c));
+ printk(KERN_DEBUG
+ " 0x0d: loopback = 0x%02x "
+ " 0x1d: var freq (PnP) = 0x%02x\n",
+ snd_wss_in(chip, 0x0d),
+ snd_wss_in(chip, 0x1d));
+ printk(KERN_DEBUG
+ " 0x0e: ply upr count = 0x%02x "
+ " 0x1e: ply lwr count = 0x%02x\n",
+ snd_wss_in(chip, 0x0e),
+ snd_wss_in(chip, 0x1e));
+ printk(KERN_DEBUG
+ " 0x0f: rec upr count = 0x%02x "
+ " 0x1f: rec lwr count = 0x%02x\n",
+ snd_wss_in(chip, 0x0f),
+ snd_wss_in(chip, 0x1f));
+}
+
+#endif
+
+/*
+ * CS4231 detection / MCE routines
+ */
+
+static void snd_wss_busy_wait(struct snd_wss *chip)
+{
+ int timeout;
+
+ /* huh.. looks like this sequence is proper for CS4231A chip (GUS MAX) */
+ for (timeout = 5; timeout > 0; timeout--)
+ wss_inb(chip, CS4231P(REGSEL));
+ /* end of cleanup sequence */
+ for (timeout = 25000;
+ timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+ timeout--)
+ udelay(10);
+}
+
+void snd_wss_mce_up(struct snd_wss *chip)
+{
+ unsigned long flags;
+ int timeout;
+
+ snd_wss_wait(chip);
+#ifdef CONFIG_SND_DEBUG
+ if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+ snd_printk(KERN_DEBUG
+ "mce_up - auto calibration time out (0)\n");
+#endif
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ chip->mce_bit |= CS4231_MCE;
+ timeout = wss_inb(chip, CS4231P(REGSEL));
+ if (timeout == 0x80)
+ snd_printk(KERN_DEBUG "mce_up [0x%lx]: "
+ "serious init problem - codec still busy\n",
+ chip->port);
+ if (!(timeout & CS4231_MCE))
+ wss_outb(chip, CS4231P(REGSEL),
+ chip->mce_bit | (timeout & 0x1f));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+EXPORT_SYMBOL(snd_wss_mce_up);
+
+void snd_wss_mce_down(struct snd_wss *chip)
+{
+ unsigned long flags;
+ unsigned long end_time;
+ int timeout;
+ int hw_mask = WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK | WSS_HW_AD1848;
+
+ snd_wss_busy_wait(chip);
+
+#ifdef CONFIG_SND_DEBUG
+ if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+ snd_printk(KERN_DEBUG "mce_down [0x%lx] - "
+ "auto calibration time out (0)\n",
+ (long)CS4231P(REGSEL));
+#endif
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ chip->mce_bit &= ~CS4231_MCE;
+ timeout = wss_inb(chip, CS4231P(REGSEL));
+ wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (timeout == 0x80)
+ snd_printk(KERN_DEBUG "mce_down [0x%lx]: "
+ "serious init problem - codec still busy\n",
+ chip->port);
+ if ((timeout & CS4231_MCE) == 0 || !(chip->hardware & hw_mask))
+ return;
+
+ /*
+ * Wait for (possible -- during init auto-calibration may not be set)
+ * calibration process to start. Needs up to 5 sample periods on AD1848
+ * which at the slowest possible rate of 5.5125 kHz means 907 us.
+ */
+ msleep(1);
+
+ snd_printdd("(1) jiffies = %lu\n", jiffies);
+
+ /* check condition up to 250 ms */
+ end_time = jiffies + msecs_to_jiffies(250);
+ while (snd_wss_in(chip, CS4231_TEST_INIT) &
+ CS4231_CALIB_IN_PROGRESS) {
+
+ if (time_after(jiffies, end_time)) {
+ snd_printk(KERN_ERR "mce_down - "
+ "auto calibration time out (2)\n");
+ return;
+ }
+ msleep(1);
+ }
+
+ snd_printdd("(2) jiffies = %lu\n", jiffies);
+
+ /* check condition up to 100 ms */
+ end_time = jiffies + msecs_to_jiffies(100);
+ while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
+ if (time_after(jiffies, end_time)) {
+ snd_printk(KERN_ERR "mce_down - auto calibration time out (3)\n");
+ return;
+ }
+ msleep(1);
+ }
+
+ snd_printdd("(3) jiffies = %lu\n", jiffies);
+ snd_printd("mce_down - exit = 0x%x\n", wss_inb(chip, CS4231P(REGSEL)));
+}
+EXPORT_SYMBOL(snd_wss_mce_down);
+
+static unsigned int snd_wss_get_count(unsigned char format, unsigned int size)
+{
+ switch (format & 0xe0) {
+ case CS4231_LINEAR_16:
+ case CS4231_LINEAR_16_BIG:
+ size >>= 1;
+ break;
+ case CS4231_ADPCM_16:
+ return size >> 2;
+ }
+ if (format & CS4231_STEREO)
+ size >>= 1;
+ return size;
+}
+
+static int snd_wss_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+ int result = 0;
+ unsigned int what;
+ struct snd_pcm_substream *s;
+ int do_start;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ do_start = 1; break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ do_start = 0; break;
+ default:
+ return -EINVAL;
+ }
+
+ what = 0;
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s == chip->playback_substream) {
+ what |= CS4231_PLAYBACK_ENABLE;
+ snd_pcm_trigger_done(s, substream);
+ } else if (s == chip->capture_substream) {
+ what |= CS4231_RECORD_ENABLE;
+ snd_pcm_trigger_done(s, substream);
+ }
+ }
+ spin_lock(&chip->reg_lock);
+ if (do_start) {
+ chip->image[CS4231_IFACE_CTRL] |= what;
+ if (chip->trigger)
+ chip->trigger(chip, what, 1);
+ } else {
+ chip->image[CS4231_IFACE_CTRL] &= ~what;
+ if (chip->trigger)
+ chip->trigger(chip, what, 0);
+ }
+ snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+ spin_unlock(&chip->reg_lock);
+#if 0
+ snd_wss_debug(chip);
+#endif
+ return result;
+}
+
+/*
+ * CODEC I/O
+ */
+
+static unsigned char snd_wss_get_rate(unsigned int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rates); i++)
+ if (rate == rates[i])
+ return freq_bits[i];
+ // snd_BUG();
+ return freq_bits[ARRAY_SIZE(rates) - 1];
+}
+
+static unsigned char snd_wss_get_format(struct snd_wss *chip,
+ snd_pcm_format_t format,
+ int channels)
+{
+ unsigned char rformat;
+
+ rformat = CS4231_LINEAR_8;
+ switch (format) {
+ case SNDRV_PCM_FORMAT_MU_LAW: rformat = CS4231_ULAW_8; break;
+ case SNDRV_PCM_FORMAT_A_LAW: rformat = CS4231_ALAW_8; break;
+ case SNDRV_PCM_FORMAT_S16_LE: rformat = CS4231_LINEAR_16; break;
+ case SNDRV_PCM_FORMAT_S16_BE: rformat = CS4231_LINEAR_16_BIG; break;
+ case SNDRV_PCM_FORMAT_IMA_ADPCM: rformat = CS4231_ADPCM_16; break;
+ }
+ if (channels > 1)
+ rformat |= CS4231_STEREO;
+#if 0
+ snd_printk(KERN_DEBUG "get_format: 0x%x (mode=0x%x)\n", format, mode);
+#endif
+ return rformat;
+}
+
+static void snd_wss_calibrate_mute(struct snd_wss *chip, int mute)
+{
+ unsigned long flags;
+
+ mute = mute ? 0x80 : 0;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (chip->calibrate_mute == mute) {
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return;
+ }
+ if (!mute) {
+ snd_wss_dout(chip, CS4231_LEFT_INPUT,
+ chip->image[CS4231_LEFT_INPUT]);
+ snd_wss_dout(chip, CS4231_RIGHT_INPUT,
+ chip->image[CS4231_RIGHT_INPUT]);
+ snd_wss_dout(chip, CS4231_LOOPBACK,
+ chip->image[CS4231_LOOPBACK]);
+ } else {
+ snd_wss_dout(chip, CS4231_LEFT_INPUT,
+ 0);
+ snd_wss_dout(chip, CS4231_RIGHT_INPUT,
+ 0);
+ snd_wss_dout(chip, CS4231_LOOPBACK,
+ 0xfd);
+ }
+
+ snd_wss_dout(chip, CS4231_AUX1_LEFT_INPUT,
+ mute | chip->image[CS4231_AUX1_LEFT_INPUT]);
+ snd_wss_dout(chip, CS4231_AUX1_RIGHT_INPUT,
+ mute | chip->image[CS4231_AUX1_RIGHT_INPUT]);
+ snd_wss_dout(chip, CS4231_AUX2_LEFT_INPUT,
+ mute | chip->image[CS4231_AUX2_LEFT_INPUT]);
+ snd_wss_dout(chip, CS4231_AUX2_RIGHT_INPUT,
+ mute | chip->image[CS4231_AUX2_RIGHT_INPUT]);
+ snd_wss_dout(chip, CS4231_LEFT_OUTPUT,
+ mute | chip->image[CS4231_LEFT_OUTPUT]);
+ snd_wss_dout(chip, CS4231_RIGHT_OUTPUT,
+ mute | chip->image[CS4231_RIGHT_OUTPUT]);
+ if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+ snd_wss_dout(chip, CS4231_LEFT_LINE_IN,
+ mute | chip->image[CS4231_LEFT_LINE_IN]);
+ snd_wss_dout(chip, CS4231_RIGHT_LINE_IN,
+ mute | chip->image[CS4231_RIGHT_LINE_IN]);
+ snd_wss_dout(chip, CS4231_MONO_CTRL,
+ mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]);
+ }
+ if (chip->hardware == WSS_HW_INTERWAVE) {
+ snd_wss_dout(chip, CS4231_LEFT_MIC_INPUT,
+ mute | chip->image[CS4231_LEFT_MIC_INPUT]);
+ snd_wss_dout(chip, CS4231_RIGHT_MIC_INPUT,
+ mute | chip->image[CS4231_RIGHT_MIC_INPUT]);
+ snd_wss_dout(chip, CS4231_LINE_LEFT_OUTPUT,
+ mute | chip->image[CS4231_LINE_LEFT_OUTPUT]);
+ snd_wss_dout(chip, CS4231_LINE_RIGHT_OUTPUT,
+ mute | chip->image[CS4231_LINE_RIGHT_OUTPUT]);
+ }
+ chip->calibrate_mute = mute;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_wss_playback_format(struct snd_wss *chip,
+ struct snd_pcm_hw_params *params,
+ unsigned char pdfr)
+{
+ unsigned long flags;
+ int full_calib = 1;
+
+ mutex_lock(&chip->mce_mutex);
+ if (chip->hardware == WSS_HW_CS4231A ||
+ (chip->hardware & WSS_HW_CS4232_MASK)) {
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (pdfr & 0x0f)) { /* rate is same? */
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1] | 0x10);
+ chip->image[CS4231_PLAYBK_FORMAT] = pdfr;
+ snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+ chip->image[CS4231_PLAYBK_FORMAT]);
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1] &= ~0x10);
+ udelay(100); /* Fixes audible clicks at least on GUS MAX */
+ full_calib = 0;
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ } else if (chip->hardware == WSS_HW_AD1845) {
+ unsigned rate = params_rate(params);
+
+ /*
+ * Program the AD1845 correctly for the playback stream.
+ * Note that we do NOT need to toggle the MCE bit because
+ * the PLAYBACK_ENABLE bit of the Interface Configuration
+ * register is set.
+ *
+ * NOTE: We seem to need to write to the MSB before the LSB
+ * to get the correct sample frequency.
+ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_wss_out(chip, CS4231_PLAYBK_FORMAT, (pdfr & 0xf0));
+ snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff);
+ snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff);
+ full_calib = 0;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ }
+ if (full_calib) {
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (chip->hardware != WSS_HW_INTERWAVE && !chip->single_dma) {
+ if (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE)
+ pdfr = (pdfr & 0xf0) |
+ (chip->image[CS4231_REC_FORMAT] & 0x0f);
+ } else {
+ chip->image[CS4231_PLAYBK_FORMAT] = pdfr;
+ }
+ snd_wss_out(chip, CS4231_PLAYBK_FORMAT, pdfr);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (chip->hardware == WSS_HW_OPL3SA2)
+ udelay(100); /* this seems to help */
+ snd_wss_mce_down(chip);
+ }
+ mutex_unlock(&chip->mce_mutex);
+}
+
+static void snd_wss_capture_format(struct snd_wss *chip,
+ struct snd_pcm_hw_params *params,
+ unsigned char cdfr)
+{
+ unsigned long flags;
+ int full_calib = 1;
+
+ mutex_lock(&chip->mce_mutex);
+ if (chip->hardware == WSS_HW_CS4231A ||
+ (chip->hardware & WSS_HW_CS4232_MASK)) {
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (cdfr & 0x0f) || /* rate is same? */
+ (chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1] | 0x20);
+ snd_wss_out(chip, CS4231_REC_FORMAT,
+ chip->image[CS4231_REC_FORMAT] = cdfr);
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1] &= ~0x20);
+ full_calib = 0;
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ } else if (chip->hardware == WSS_HW_AD1845) {
+ unsigned rate = params_rate(params);
+
+ /*
+ * Program the AD1845 correctly for the capture stream.
+ * Note that we do NOT need to toggle the MCE bit because
+ * the PLAYBACK_ENABLE bit of the Interface Configuration
+ * register is set.
+ *
+ * NOTE: We seem to need to write to the MSB before the LSB
+ * to get the correct sample frequency.
+ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_wss_out(chip, CS4231_REC_FORMAT, (cdfr & 0xf0));
+ snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff);
+ snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff);
+ full_calib = 0;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ }
+ if (full_calib) {
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (chip->hardware != WSS_HW_INTERWAVE &&
+ !(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
+ if (chip->single_dma)
+ snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr);
+ else
+ snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+ (chip->image[CS4231_PLAYBK_FORMAT] & 0xf0) |
+ (cdfr & 0x0f));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ }
+ if (chip->hardware & WSS_HW_AD1848_MASK)
+ snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr);
+ else
+ snd_wss_out(chip, CS4231_REC_FORMAT, cdfr);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+ }
+ mutex_unlock(&chip->mce_mutex);
+}
+
+/*
+ * Timer interface
+ */
+
+static unsigned long snd_wss_timer_resolution(struct snd_timer *timer)
+{
+ struct snd_wss *chip = snd_timer_chip(timer);
+ if (chip->hardware & WSS_HW_CS4236B_MASK)
+ return 14467;
+ else
+ return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920;
+}
+
+static int snd_wss_timer_start(struct snd_timer *timer)
+{
+ unsigned long flags;
+ unsigned int ticks;
+ struct snd_wss *chip = snd_timer_chip(timer);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ticks = timer->sticks;
+ if ((chip->image[CS4231_ALT_FEATURE_1] & CS4231_TIMER_ENABLE) == 0 ||
+ (unsigned char)(ticks >> 8) != chip->image[CS4231_TIMER_HIGH] ||
+ (unsigned char)ticks != chip->image[CS4231_TIMER_LOW]) {
+ chip->image[CS4231_TIMER_HIGH] = (unsigned char) (ticks >> 8);
+ snd_wss_out(chip, CS4231_TIMER_HIGH,
+ chip->image[CS4231_TIMER_HIGH]);
+ chip->image[CS4231_TIMER_LOW] = (unsigned char) ticks;
+ snd_wss_out(chip, CS4231_TIMER_LOW,
+ chip->image[CS4231_TIMER_LOW]);
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1] |
+ CS4231_TIMER_ENABLE);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_wss_timer_stop(struct snd_timer *timer)
+{
+ unsigned long flags;
+ struct snd_wss *chip = snd_timer_chip(timer);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE;
+ snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+ chip->image[CS4231_ALT_FEATURE_1]);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static void snd_wss_init(struct snd_wss *chip)
+{
+ unsigned long flags;
+
+ snd_wss_calibrate_mute(chip, 1);
+ snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+ snd_printk(KERN_DEBUG "init: (1)\n");
+#endif
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE |
+ CS4231_PLAYBACK_PIO |
+ CS4231_RECORD_ENABLE |
+ CS4231_RECORD_PIO |
+ CS4231_CALIB_MODE);
+ chip->image[CS4231_IFACE_CTRL] |= CS4231_AUTOCALIB;
+ snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+ snd_printk(KERN_DEBUG "init: (2)\n");
+#endif
+
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ chip->image[CS4231_IFACE_CTRL] &= ~CS4231_AUTOCALIB;
+ snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+ snd_wss_out(chip,
+ CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1]);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+ snd_printk(KERN_DEBUG "init: (3) - afei = 0x%x\n",
+ chip->image[CS4231_ALT_FEATURE_1]);
+#endif
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_wss_out(chip, CS4231_ALT_FEATURE_2,
+ chip->image[CS4231_ALT_FEATURE_2]);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+ chip->image[CS4231_PLAYBK_FORMAT]);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+ snd_printk(KERN_DEBUG "init: (4)\n");
+#endif
+
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (!(chip->hardware & WSS_HW_AD1848_MASK))
+ snd_wss_out(chip, CS4231_REC_FORMAT,
+ chip->image[CS4231_REC_FORMAT]);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+ snd_wss_calibrate_mute(chip, 0);
+
+#ifdef SNDRV_DEBUG_MCE
+ snd_printk(KERN_DEBUG "init: (5)\n");
+#endif
+}
+
+static int snd_wss_open(struct snd_wss *chip, unsigned int mode)
+{
+ unsigned long flags;
+
+ mutex_lock(&chip->open_mutex);
+ if ((chip->mode & mode) ||
+ ((chip->mode & WSS_MODE_OPEN) && chip->single_dma)) {
+ mutex_unlock(&chip->open_mutex);
+ return -EAGAIN;
+ }
+ if (chip->mode & WSS_MODE_OPEN) {
+ chip->mode |= mode;
+ mutex_unlock(&chip->open_mutex);
+ return 0;
+ }
+ /* ok. now enable and ack CODEC IRQ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+ snd_wss_out(chip, CS4231_IRQ_STATUS,
+ CS4231_PLAYBACK_IRQ |
+ CS4231_RECORD_IRQ |
+ CS4231_TIMER_IRQ);
+ snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+ }
+ wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */
+ wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */
+ chip->image[CS4231_PIN_CTRL] |= CS4231_IRQ_ENABLE;
+ snd_wss_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
+ if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+ snd_wss_out(chip, CS4231_IRQ_STATUS,
+ CS4231_PLAYBACK_IRQ |
+ CS4231_RECORD_IRQ |
+ CS4231_TIMER_IRQ);
+ snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ chip->mode = mode;
+ mutex_unlock(&chip->open_mutex);
+ return 0;
+}
+
+static void snd_wss_close(struct snd_wss *chip, unsigned int mode)
+{
+ unsigned long flags;
+
+ mutex_lock(&chip->open_mutex);
+ chip->mode &= ~mode;
+ if (chip->mode & WSS_MODE_OPEN) {
+ mutex_unlock(&chip->open_mutex);
+ return;
+ }
+ /* disable IRQ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (!(chip->hardware & WSS_HW_AD1848_MASK))
+ snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+ wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */
+ wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */
+ chip->image[CS4231_PIN_CTRL] &= ~CS4231_IRQ_ENABLE;
+ snd_wss_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
+
+ /* now disable record & playback */
+
+ if (chip->image[CS4231_IFACE_CTRL] & (CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
+ CS4231_RECORD_ENABLE | CS4231_RECORD_PIO)) {
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
+ CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
+ snd_wss_out(chip, CS4231_IFACE_CTRL,
+ chip->image[CS4231_IFACE_CTRL]);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_down(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ }
+
+ /* clear IRQ again */
+ if (!(chip->hardware & WSS_HW_AD1848_MASK))
+ snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+ wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */
+ wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ chip->mode = 0;
+ mutex_unlock(&chip->open_mutex);
+}
+
+/*
+ * timer open/close
+ */
+
+static int snd_wss_timer_open(struct snd_timer *timer)
+{
+ struct snd_wss *chip = snd_timer_chip(timer);
+ snd_wss_open(chip, WSS_MODE_TIMER);
+ return 0;
+}
+
+static int snd_wss_timer_close(struct snd_timer *timer)
+{
+ struct snd_wss *chip = snd_timer_chip(timer);
+ snd_wss_close(chip, WSS_MODE_TIMER);
+ return 0;
+}
+
+static const struct snd_timer_hardware snd_wss_timer_table =
+{
+ .flags = SNDRV_TIMER_HW_AUTO,
+ .resolution = 9945,
+ .ticks = 65535,
+ .open = snd_wss_timer_open,
+ .close = snd_wss_timer_close,
+ .c_resolution = snd_wss_timer_resolution,
+ .start = snd_wss_timer_start,
+ .stop = snd_wss_timer_stop,
+};
+
+/*
+ * ok.. exported functions..
+ */
+
+static int snd_wss_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+ unsigned char new_pdfr;
+
+ new_pdfr = snd_wss_get_format(chip, params_format(hw_params),
+ params_channels(hw_params)) |
+ snd_wss_get_rate(params_rate(hw_params));
+ chip->set_playback_format(chip, hw_params, new_pdfr);
+ return 0;
+}
+
+static int snd_wss_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long flags;
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ chip->p_dma_size = size;
+ chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO);
+ snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+ count = snd_wss_get_count(chip->image[CS4231_PLAYBK_FORMAT], count) - 1;
+ snd_wss_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
+ snd_wss_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+#if 0
+ snd_wss_debug(chip);
+#endif
+ return 0;
+}
+
+static int snd_wss_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+ unsigned char new_cdfr;
+
+ new_cdfr = snd_wss_get_format(chip, params_format(hw_params),
+ params_channels(hw_params)) |
+ snd_wss_get_rate(params_rate(hw_params));
+ chip->set_capture_format(chip, hw_params, new_cdfr);
+ return 0;
+}
+
+static int snd_wss_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long flags;
+ unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+ unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ chip->c_dma_size = size;
+ chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
+ snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+ if (chip->hardware & WSS_HW_AD1848_MASK)
+ count = snd_wss_get_count(chip->image[CS4231_PLAYBK_FORMAT],
+ count);
+ else
+ count = snd_wss_get_count(chip->image[CS4231_REC_FORMAT],
+ count);
+ count--;
+ if (chip->single_dma && chip->hardware != WSS_HW_INTERWAVE) {
+ snd_wss_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
+ snd_wss_out(chip, CS4231_PLY_UPR_CNT,
+ (unsigned char) (count >> 8));
+ } else {
+ snd_wss_out(chip, CS4231_REC_LWR_CNT, (unsigned char) count);
+ snd_wss_out(chip, CS4231_REC_UPR_CNT,
+ (unsigned char) (count >> 8));
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+void snd_wss_overrange(struct snd_wss *chip)
+{
+ unsigned long flags;
+ unsigned char res;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ res = snd_wss_in(chip, CS4231_TEST_INIT);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (res & (0x08 | 0x02)) /* detect overrange only above 0dB; may be user selectable? */
+ chip->capture_substream->runtime->overrange++;
+}
+EXPORT_SYMBOL(snd_wss_overrange);
+
+irqreturn_t snd_wss_interrupt(int irq, void *dev_id)
+{
+ struct snd_wss *chip = dev_id;
+ unsigned char status;
+
+ if (chip->hardware & WSS_HW_AD1848_MASK)
+ /* pretend it was the only possible irq for AD1848 */
+ status = CS4231_PLAYBACK_IRQ;
+ else
+ status = snd_wss_in(chip, CS4231_IRQ_STATUS);
+ if (status & CS4231_TIMER_IRQ) {
+ if (chip->timer)
+ snd_timer_interrupt(chip->timer, chip->timer->sticks);
+ }
+ if (chip->single_dma && chip->hardware != WSS_HW_INTERWAVE) {
+ if (status & CS4231_PLAYBACK_IRQ) {
+ if (chip->mode & WSS_MODE_PLAY) {
+ if (chip->playback_substream)
+ snd_pcm_period_elapsed(chip->playback_substream);
+ }
+ if (chip->mode & WSS_MODE_RECORD) {
+ if (chip->capture_substream) {
+ snd_wss_overrange(chip);
+ snd_pcm_period_elapsed(chip->capture_substream);
+ }
+ }
+ }
+ } else {
+ if (status & CS4231_PLAYBACK_IRQ) {
+ if (chip->playback_substream)
+ snd_pcm_period_elapsed(chip->playback_substream);
+ }
+ if (status & CS4231_RECORD_IRQ) {
+ if (chip->capture_substream) {
+ snd_wss_overrange(chip);
+ snd_pcm_period_elapsed(chip->capture_substream);
+ }
+ }
+ }
+
+ spin_lock(&chip->reg_lock);
+ status = ~CS4231_ALL_IRQS | ~status;
+ if (chip->hardware & WSS_HW_AD1848_MASK)
+ wss_outb(chip, CS4231P(STATUS), 0);
+ else
+ snd_wss_out(chip, CS4231_IRQ_STATUS, status);
+ spin_unlock(&chip->reg_lock);
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(snd_wss_interrupt);
+
+static snd_pcm_uframes_t snd_wss_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+
+ if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE))
+ return 0;
+ ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_wss_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+
+ if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE))
+ return 0;
+ ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size);
+ return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static int snd_ad1848_probe(struct snd_wss *chip)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+ unsigned long flags;
+ unsigned char r;
+ unsigned short hardware = 0;
+ int err = 0;
+ int i;
+
+ while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
+ if (time_after(jiffies, timeout))
+ return -ENODEV;
+ cond_resched();
+ }
+ spin_lock_irqsave(&chip->reg_lock, flags);
+
+ /* set CS423x MODE 1 */
+ snd_wss_dout(chip, CS4231_MISC_INFO, 0);
+
+ snd_wss_dout(chip, CS4231_RIGHT_INPUT, 0x45); /* 0x55 & ~0x10 */
+ r = snd_wss_in(chip, CS4231_RIGHT_INPUT);
+ if (r != 0x45) {
+ /* RMGE always high on AD1847 */
+ if ((r & ~CS4231_ENABLE_MIC_GAIN) != 0x45) {
+ err = -ENODEV;
+ goto out;
+ }
+ hardware = WSS_HW_AD1847;
+ } else {
+ snd_wss_dout(chip, CS4231_LEFT_INPUT, 0xaa);
+ r = snd_wss_in(chip, CS4231_LEFT_INPUT);
+ /* L/RMGE always low on AT2320 */
+ if ((r | CS4231_ENABLE_MIC_GAIN) != 0xaa) {
+ err = -ENODEV;
+ goto out;
+ }
+ }
+
+ /* clear pending IRQ */
+ wss_inb(chip, CS4231P(STATUS));
+ wss_outb(chip, CS4231P(STATUS), 0);
+ mb();
+
+ if ((chip->hardware & WSS_HW_TYPE_MASK) != WSS_HW_DETECT)
+ goto out;
+
+ if (hardware) {
+ chip->hardware = hardware;
+ goto out;
+ }
+
+ r = snd_wss_in(chip, CS4231_MISC_INFO);
+
+ /* set CS423x MODE 2 */
+ snd_wss_dout(chip, CS4231_MISC_INFO, CS4231_MODE2);
+ for (i = 0; i < 16; i++) {
+ if (snd_wss_in(chip, i) != snd_wss_in(chip, 16 + i)) {
+ /* we have more than 16 registers: check ID */
+ if ((r & 0xf) != 0xa)
+ goto out_mode;
+ /*
+ * on CMI8330, CS4231_VERSION is volume control and
+ * can be set to 0
+ */
+ snd_wss_dout(chip, CS4231_VERSION, 0);
+ r = snd_wss_in(chip, CS4231_VERSION) & 0xe7;
+ if (!r)
+ chip->hardware = WSS_HW_CMI8330;
+ goto out_mode;
+ }
+ }
+ if (r & 0x80)
+ chip->hardware = WSS_HW_CS4248;
+ else
+ chip->hardware = WSS_HW_AD1848;
+out_mode:
+ snd_wss_dout(chip, CS4231_MISC_INFO, 0);
+out:
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return err;
+}
+
+static int snd_wss_probe(struct snd_wss *chip)
+{
+ unsigned long flags;
+ int i, id, rev, regnum;
+ unsigned char *ptr;
+ unsigned int hw;
+
+ id = snd_ad1848_probe(chip);
+ if (id < 0)
+ return id;
+
+ hw = chip->hardware;
+ if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) {
+ for (i = 0; i < 50; i++) {
+ mb();
+ if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+ msleep(2);
+ else {
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_wss_out(chip, CS4231_MISC_INFO,
+ CS4231_MODE2);
+ id = snd_wss_in(chip, CS4231_MISC_INFO) & 0x0f;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (id == 0x0a)
+ break; /* this is valid value */
+ }
+ }
+ snd_printdd("wss: port = 0x%lx, id = 0x%x\n", chip->port, id);
+ if (id != 0x0a)
+ return -ENODEV; /* no valid device found */
+
+ rev = snd_wss_in(chip, CS4231_VERSION) & 0xe7;
+ snd_printdd("CS4231: VERSION (I25) = 0x%x\n", rev);
+ if (rev == 0x80) {
+ unsigned char tmp = snd_wss_in(chip, 23);
+ snd_wss_out(chip, 23, ~tmp);
+ if (snd_wss_in(chip, 23) != tmp)
+ chip->hardware = WSS_HW_AD1845;
+ else
+ chip->hardware = WSS_HW_CS4231;
+ } else if (rev == 0xa0) {
+ chip->hardware = WSS_HW_CS4231A;
+ } else if (rev == 0xa2) {
+ chip->hardware = WSS_HW_CS4232;
+ } else if (rev == 0xb2) {
+ chip->hardware = WSS_HW_CS4232A;
+ } else if (rev == 0x83) {
+ chip->hardware = WSS_HW_CS4236;
+ } else if (rev == 0x03) {
+ chip->hardware = WSS_HW_CS4236B;
+ } else {
+ snd_printk(KERN_ERR
+ "unknown CS chip with version 0x%x\n", rev);
+ return -ENODEV; /* unknown CS4231 chip? */
+ }
+ }
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ wss_inb(chip, CS4231P(STATUS)); /* clear any pendings IRQ */
+ wss_outb(chip, CS4231P(STATUS), 0);
+ mb();
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ if (!(chip->hardware & WSS_HW_AD1848_MASK))
+ chip->image[CS4231_MISC_INFO] = CS4231_MODE2;
+ switch (chip->hardware) {
+ case WSS_HW_INTERWAVE:
+ chip->image[CS4231_MISC_INFO] = CS4231_IW_MODE3;
+ break;
+ case WSS_HW_CS4235:
+ case WSS_HW_CS4236B:
+ case WSS_HW_CS4237B:
+ case WSS_HW_CS4238B:
+ case WSS_HW_CS4239:
+ if (hw == WSS_HW_DETECT3)
+ chip->image[CS4231_MISC_INFO] = CS4231_4236_MODE3;
+ else
+ chip->hardware = WSS_HW_CS4236;
+ break;
+ }
+
+ chip->image[CS4231_IFACE_CTRL] =
+ (chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA) |
+ (chip->single_dma ? CS4231_SINGLE_DMA : 0);
+ if (chip->hardware != WSS_HW_OPTI93X) {
+ chip->image[CS4231_ALT_FEATURE_1] = 0x80;
+ chip->image[CS4231_ALT_FEATURE_2] =
+ chip->hardware == WSS_HW_INTERWAVE ? 0xc2 : 0x01;
+ }
+ /* enable fine grained frequency selection */
+ if (chip->hardware == WSS_HW_AD1845)
+ chip->image[AD1845_PWR_DOWN] = 8;
+
+ ptr = (unsigned char *) &chip->image;
+ regnum = (chip->hardware & WSS_HW_AD1848_MASK) ? 16 : 32;
+ snd_wss_mce_down(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ for (i = 0; i < regnum; i++) /* ok.. fill all registers */
+ snd_wss_out(chip, i, *ptr++);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ snd_wss_mce_up(chip);
+ snd_wss_mce_down(chip);
+
+ mdelay(2);
+
+ /* ok.. try check hardware version for CS4236+ chips */
+ if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) {
+ if (chip->hardware == WSS_HW_CS4236B) {
+ rev = snd_cs4236_ext_in(chip, CS4236_VERSION);
+ snd_cs4236_ext_out(chip, CS4236_VERSION, 0xff);
+ id = snd_cs4236_ext_in(chip, CS4236_VERSION);
+ snd_cs4236_ext_out(chip, CS4236_VERSION, rev);
+ snd_printdd("CS4231: ext version; rev = 0x%x, id = 0x%x\n", rev, id);
+ if ((id & 0x1f) == 0x1d) { /* CS4235 */
+ chip->hardware = WSS_HW_CS4235;
+ switch (id >> 5) {
+ case 4:
+ case 5:
+ case 6:
+ break;
+ default:
+ snd_printk(KERN_WARNING
+ "unknown CS4235 chip "
+ "(enhanced version = 0x%x)\n",
+ id);
+ }
+ } else if ((id & 0x1f) == 0x0b) { /* CS4236/B */
+ switch (id >> 5) {
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ chip->hardware = WSS_HW_CS4236B;
+ break;
+ default:
+ snd_printk(KERN_WARNING
+ "unknown CS4236 chip "
+ "(enhanced version = 0x%x)\n",
+ id);
+ }
+ } else if ((id & 0x1f) == 0x08) { /* CS4237B */
+ chip->hardware = WSS_HW_CS4237B;
+ switch (id >> 5) {
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ break;
+ default:
+ snd_printk(KERN_WARNING
+ "unknown CS4237B chip "
+ "(enhanced version = 0x%x)\n",
+ id);
+ }
+ } else if ((id & 0x1f) == 0x09) { /* CS4238B */
+ chip->hardware = WSS_HW_CS4238B;
+ switch (id >> 5) {
+ case 5:
+ case 6:
+ case 7:
+ break;
+ default:
+ snd_printk(KERN_WARNING
+ "unknown CS4238B chip "
+ "(enhanced version = 0x%x)\n",
+ id);
+ }
+ } else if ((id & 0x1f) == 0x1e) { /* CS4239 */
+ chip->hardware = WSS_HW_CS4239;
+ switch (id >> 5) {
+ case 4:
+ case 5:
+ case 6:
+ break;
+ default:
+ snd_printk(KERN_WARNING
+ "unknown CS4239 chip "
+ "(enhanced version = 0x%x)\n",
+ id);
+ }
+ } else {
+ snd_printk(KERN_WARNING
+ "unknown CS4236/CS423xB chip "
+ "(enhanced version = 0x%x)\n", id);
+ }
+ }
+ }
+ return 0; /* all things are ok.. */
+}
+
+/*
+
+ */
+
+static const struct snd_pcm_hardware snd_wss_playback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
+ .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 5510,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_wss_capture =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
+ .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 5510,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/*
+
+ */
+
+static int snd_wss_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ runtime->hw = snd_wss_playback;
+
+ /* hardware limitation of older chipsets */
+ if (chip->hardware & WSS_HW_AD1848_MASK)
+ runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM |
+ SNDRV_PCM_FMTBIT_S16_BE);
+
+ /* hardware bug in InterWave chipset */
+ if (chip->hardware == WSS_HW_INTERWAVE && chip->dma1 > 3)
+ runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_MU_LAW;
+
+ /* hardware limitation of cheap chips */
+ if (chip->hardware == WSS_HW_CS4235 ||
+ chip->hardware == WSS_HW_CS4239)
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE;
+
+ snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max);
+ snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max);
+
+ if (chip->claim_dma) {
+ err = chip->claim_dma(chip, chip->dma_private_data, chip->dma1);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_wss_open(chip, WSS_MODE_PLAY);
+ if (err < 0) {
+ if (chip->release_dma)
+ chip->release_dma(chip, chip->dma_private_data, chip->dma1);
+ return err;
+ }
+ chip->playback_substream = substream;
+ snd_pcm_set_sync(substream);
+ chip->rate_constraint(runtime);
+ return 0;
+}
+
+static int snd_wss_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ runtime->hw = snd_wss_capture;
+
+ /* hardware limitation of older chipsets */
+ if (chip->hardware & WSS_HW_AD1848_MASK)
+ runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM |
+ SNDRV_PCM_FMTBIT_S16_BE);
+
+ /* hardware limitation of cheap chips */
+ if (chip->hardware == WSS_HW_CS4235 ||
+ chip->hardware == WSS_HW_CS4239 ||
+ chip->hardware == WSS_HW_OPTI93X)
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S16_LE;
+
+ snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
+ snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max);
+
+ if (chip->claim_dma) {
+ err = chip->claim_dma(chip, chip->dma_private_data, chip->dma2);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_wss_open(chip, WSS_MODE_RECORD);
+ if (err < 0) {
+ if (chip->release_dma)
+ chip->release_dma(chip, chip->dma_private_data, chip->dma2);
+ return err;
+ }
+ chip->capture_substream = substream;
+ snd_pcm_set_sync(substream);
+ chip->rate_constraint(runtime);
+ return 0;
+}
+
+static int snd_wss_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+
+ chip->playback_substream = NULL;
+ snd_wss_close(chip, WSS_MODE_PLAY);
+ return 0;
+}
+
+static int snd_wss_capture_close(struct snd_pcm_substream *substream)
+{
+ struct snd_wss *chip = snd_pcm_substream_chip(substream);
+
+ chip->capture_substream = NULL;
+ snd_wss_close(chip, WSS_MODE_RECORD);
+ return 0;
+}
+
+static void snd_wss_thinkpad_twiddle(struct snd_wss *chip, int on)
+{
+ int tmp;
+
+ if (!chip->thinkpad_flag)
+ return;
+
+ outb(0x1c, AD1848_THINKPAD_CTL_PORT1);
+ tmp = inb(AD1848_THINKPAD_CTL_PORT2);
+
+ if (on)
+ /* turn it on */
+ tmp |= AD1848_THINKPAD_CS4248_ENABLE_BIT;
+ else
+ /* turn it off */
+ tmp &= ~AD1848_THINKPAD_CS4248_ENABLE_BIT;
+
+ outb(tmp, AD1848_THINKPAD_CTL_PORT2);
+}
+
+#ifdef CONFIG_PM
+
+/* lowlevel suspend callback for CS4231 */
+static void snd_wss_suspend(struct snd_wss *chip)
+{
+ int reg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ for (reg = 0; reg < 32; reg++)
+ chip->image[reg] = snd_wss_in(chip, reg);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (chip->thinkpad_flag)
+ snd_wss_thinkpad_twiddle(chip, 0);
+}
+
+/* lowlevel resume callback for CS4231 */
+static void snd_wss_resume(struct snd_wss *chip)
+{
+ int reg;
+ unsigned long flags;
+ /* int timeout; */
+
+ if (chip->thinkpad_flag)
+ snd_wss_thinkpad_twiddle(chip, 1);
+ snd_wss_mce_up(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ for (reg = 0; reg < 32; reg++) {
+ switch (reg) {
+ case CS4231_VERSION:
+ break;
+ default:
+ snd_wss_out(chip, reg, chip->image[reg]);
+ break;
+ }
+ }
+ /* Yamaha needs this to resume properly */
+ if (chip->hardware == WSS_HW_OPL3SA2)
+ snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+ chip->image[CS4231_PLAYBK_FORMAT]);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+#if 1
+ snd_wss_mce_down(chip);
+#else
+ /* The following is a workaround to avoid freeze after resume on TP600E.
+ This is the first half of copy of snd_wss_mce_down(), but doesn't
+ include rescheduling. -- iwai
+ */
+ snd_wss_busy_wait(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ chip->mce_bit &= ~CS4231_MCE;
+ timeout = wss_inb(chip, CS4231P(REGSEL));
+ wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (timeout == 0x80)
+ snd_printk(KERN_ERR "down [0x%lx]: serious init problem "
+ "- codec still busy\n", chip->port);
+ if ((timeout & CS4231_MCE) == 0 ||
+ !(chip->hardware & (WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK))) {
+ return;
+ }
+ snd_wss_busy_wait(chip);
+#endif
+}
+#endif /* CONFIG_PM */
+
+const char *snd_wss_chip_id(struct snd_wss *chip)
+{
+ switch (chip->hardware) {
+ case WSS_HW_CS4231:
+ return "CS4231";
+ case WSS_HW_CS4231A:
+ return "CS4231A";
+ case WSS_HW_CS4232:
+ return "CS4232";
+ case WSS_HW_CS4232A:
+ return "CS4232A";
+ case WSS_HW_CS4235:
+ return "CS4235";
+ case WSS_HW_CS4236:
+ return "CS4236";
+ case WSS_HW_CS4236B:
+ return "CS4236B";
+ case WSS_HW_CS4237B:
+ return "CS4237B";
+ case WSS_HW_CS4238B:
+ return "CS4238B";
+ case WSS_HW_CS4239:
+ return "CS4239";
+ case WSS_HW_INTERWAVE:
+ return "AMD InterWave";
+ case WSS_HW_OPL3SA2:
+ return chip->card->shortname;
+ case WSS_HW_AD1845:
+ return "AD1845";
+ case WSS_HW_OPTI93X:
+ return "OPTi 93x";
+ case WSS_HW_AD1847:
+ return "AD1847";
+ case WSS_HW_AD1848:
+ return "AD1848";
+ case WSS_HW_CS4248:
+ return "CS4248";
+ case WSS_HW_CMI8330:
+ return "CMI8330/C3D";
+ default:
+ return "???";
+ }
+}
+EXPORT_SYMBOL(snd_wss_chip_id);
+
+static int snd_wss_new(struct snd_card *card,
+ unsigned short hardware,
+ unsigned short hwshare,
+ struct snd_wss **rchip)
+{
+ struct snd_wss *chip;
+
+ *rchip = NULL;
+ chip = devm_kzalloc(card->dev, sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+ chip->hardware = hardware;
+ chip->hwshare = hwshare;
+
+ spin_lock_init(&chip->reg_lock);
+ mutex_init(&chip->mce_mutex);
+ mutex_init(&chip->open_mutex);
+ chip->card = card;
+ chip->rate_constraint = snd_wss_xrate;
+ chip->set_playback_format = snd_wss_playback_format;
+ chip->set_capture_format = snd_wss_capture_format;
+ if (chip->hardware == WSS_HW_OPTI93X)
+ memcpy(&chip->image, &snd_opti93x_original_image,
+ sizeof(snd_opti93x_original_image));
+ else
+ memcpy(&chip->image, &snd_wss_original_image,
+ sizeof(snd_wss_original_image));
+ if (chip->hardware & WSS_HW_AD1848_MASK) {
+ chip->image[CS4231_PIN_CTRL] = 0;
+ chip->image[CS4231_TEST_INIT] = 0;
+ }
+
+ *rchip = chip;
+ return 0;
+}
+
+int snd_wss_create(struct snd_card *card,
+ unsigned long port,
+ unsigned long cport,
+ int irq, int dma1, int dma2,
+ unsigned short hardware,
+ unsigned short hwshare,
+ struct snd_wss **rchip)
+{
+ struct snd_wss *chip;
+ int err;
+
+ err = snd_wss_new(card, hardware, hwshare, &chip);
+ if (err < 0)
+ return err;
+
+ chip->irq = -1;
+ chip->dma1 = -1;
+ chip->dma2 = -1;
+
+ chip->res_port = devm_request_region(card->dev, port, 4, "WSS");
+ if (!chip->res_port) {
+ snd_printk(KERN_ERR "wss: can't grab port 0x%lx\n", port);
+ return -EBUSY;
+ }
+ chip->port = port;
+ if ((long)cport >= 0) {
+ chip->res_cport = devm_request_region(card->dev, cport, 8,
+ "CS4232 Control");
+ if (!chip->res_cport) {
+ snd_printk(KERN_ERR
+ "wss: can't grab control port 0x%lx\n", cport);
+ return -ENODEV;
+ }
+ }
+ chip->cport = cport;
+ if (!(hwshare & WSS_HWSHARE_IRQ))
+ if (devm_request_irq(card->dev, irq, snd_wss_interrupt, 0,
+ "WSS", (void *) chip)) {
+ snd_printk(KERN_ERR "wss: can't grab IRQ %d\n", irq);
+ return -EBUSY;
+ }
+ chip->irq = irq;
+ card->sync_irq = chip->irq;
+ if (!(hwshare & WSS_HWSHARE_DMA1) &&
+ snd_devm_request_dma(card->dev, dma1, "WSS - 1")) {
+ snd_printk(KERN_ERR "wss: can't grab DMA1 %d\n", dma1);
+ return -EBUSY;
+ }
+ chip->dma1 = dma1;
+ if (!(hwshare & WSS_HWSHARE_DMA2) && dma1 != dma2 && dma2 >= 0 &&
+ snd_devm_request_dma(card->dev, dma2, "WSS - 2")) {
+ snd_printk(KERN_ERR "wss: can't grab DMA2 %d\n", dma2);
+ return -EBUSY;
+ }
+ if (dma1 == dma2 || dma2 < 0) {
+ chip->single_dma = 1;
+ chip->dma2 = chip->dma1;
+ } else
+ chip->dma2 = dma2;
+
+ if (hardware == WSS_HW_THINKPAD) {
+ chip->thinkpad_flag = 1;
+ chip->hardware = WSS_HW_DETECT; /* reset */
+ snd_wss_thinkpad_twiddle(chip, 1);
+ }
+
+ /* global setup */
+ if (snd_wss_probe(chip) < 0)
+ return -ENODEV;
+ snd_wss_init(chip);
+
+#if 0
+ if (chip->hardware & WSS_HW_CS4232_MASK) {
+ if (chip->res_cport == NULL)
+ snd_printk(KERN_ERR "CS4232 control port features are "
+ "not accessible\n");
+ }
+#endif
+
+#ifdef CONFIG_PM
+ /* Power Management */
+ chip->suspend = snd_wss_suspend;
+ chip->resume = snd_wss_resume;
+#endif
+
+ *rchip = chip;
+ return 0;
+}
+EXPORT_SYMBOL(snd_wss_create);
+
+static const struct snd_pcm_ops snd_wss_playback_ops = {
+ .open = snd_wss_playback_open,
+ .close = snd_wss_playback_close,
+ .hw_params = snd_wss_playback_hw_params,
+ .prepare = snd_wss_playback_prepare,
+ .trigger = snd_wss_trigger,
+ .pointer = snd_wss_playback_pointer,
+};
+
+static const struct snd_pcm_ops snd_wss_capture_ops = {
+ .open = snd_wss_capture_open,
+ .close = snd_wss_capture_close,
+ .hw_params = snd_wss_capture_hw_params,
+ .prepare = snd_wss_capture_prepare,
+ .trigger = snd_wss_trigger,
+ .pointer = snd_wss_capture_pointer,
+};
+
+int snd_wss_pcm(struct snd_wss *chip, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(chip->card, "WSS", device, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_wss_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_wss_capture_ops);
+
+ /* global setup */
+ pcm->private_data = chip;
+ pcm->info_flags = 0;
+ if (chip->single_dma)
+ pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+ if (chip->hardware != WSS_HW_INTERWAVE)
+ pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+ strcpy(pcm->name, snd_wss_chip_id(chip));
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, chip->card->dev,
+ 64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
+
+ chip->pcm = pcm;
+ return 0;
+}
+EXPORT_SYMBOL(snd_wss_pcm);
+
+static void snd_wss_timer_free(struct snd_timer *timer)
+{
+ struct snd_wss *chip = timer->private_data;
+ chip->timer = NULL;
+}
+
+int snd_wss_timer(struct snd_wss *chip, int device)
+{
+ struct snd_timer *timer;
+ struct snd_timer_id tid;
+ int err;
+
+ /* Timer initialization */
+ tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+ tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+ tid.card = chip->card->number;
+ tid.device = device;
+ tid.subdevice = 0;
+ err = snd_timer_new(chip->card, "CS4231", &tid, &timer);
+ if (err < 0)
+ return err;
+ strcpy(timer->name, snd_wss_chip_id(chip));
+ timer->private_data = chip;
+ timer->private_free = snd_wss_timer_free;
+ timer->hw = snd_wss_timer_table;
+ chip->timer = timer;
+ return 0;
+}
+EXPORT_SYMBOL(snd_wss_timer);
+
+/*
+ * MIXER part
+ */
+
+static int snd_wss_info_mux(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[4] = {
+ "Line", "Aux", "Mic", "Mix"
+ };
+ static const char * const opl3sa_texts[4] = {
+ "Line", "CD", "Mic", "Mix"
+ };
+ static const char * const gusmax_texts[4] = {
+ "Line", "Synth", "Mic", "Mix"
+ };
+ const char * const *ptexts = texts;
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+
+ if (snd_BUG_ON(!chip->card))
+ return -EINVAL;
+ if (!strcmp(chip->card->driver, "GUS MAX"))
+ ptexts = gusmax_texts;
+ switch (chip->hardware) {
+ case WSS_HW_INTERWAVE:
+ ptexts = gusmax_texts;
+ break;
+ case WSS_HW_OPTI93X:
+ case WSS_HW_OPL3SA2:
+ ptexts = opl3sa_texts;
+ break;
+ }
+ return snd_ctl_enum_info(uinfo, 2, 4, ptexts);
+}
+
+static int snd_wss_get_mux(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.enumerated.item[0] = (chip->image[CS4231_LEFT_INPUT] & CS4231_MIXS_ALL) >> 6;
+ ucontrol->value.enumerated.item[1] = (chip->image[CS4231_RIGHT_INPUT] & CS4231_MIXS_ALL) >> 6;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return 0;
+}
+
+static int snd_wss_put_mux(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ unsigned short left, right;
+ int change;
+
+ if (ucontrol->value.enumerated.item[0] > 3 ||
+ ucontrol->value.enumerated.item[1] > 3)
+ return -EINVAL;
+ left = ucontrol->value.enumerated.item[0] << 6;
+ right = ucontrol->value.enumerated.item[1] << 6;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ left = (chip->image[CS4231_LEFT_INPUT] & ~CS4231_MIXS_ALL) | left;
+ right = (chip->image[CS4231_RIGHT_INPUT] & ~CS4231_MIXS_ALL) | right;
+ change = left != chip->image[CS4231_LEFT_INPUT] ||
+ right != chip->image[CS4231_RIGHT_INPUT];
+ snd_wss_out(chip, CS4231_LEFT_INPUT, left);
+ snd_wss_out(chip, CS4231_RIGHT_INPUT, right);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+
+int snd_wss_info_single(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+EXPORT_SYMBOL(snd_wss_info_single);
+
+int snd_wss_get_single(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (invert)
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ return 0;
+}
+EXPORT_SYMBOL(snd_wss_get_single);
+
+int snd_wss_put_single(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0xff;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0xff;
+ int change;
+ unsigned short val;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+ if (invert)
+ val = mask - val;
+ val <<= shift;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ val = (chip->image[reg] & ~(mask << shift)) | val;
+ change = val != chip->image[reg];
+ snd_wss_out(chip, reg, val);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+EXPORT_SYMBOL(snd_wss_put_single);
+
+int snd_wss_info_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+
+ uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = mask;
+ return 0;
+}
+EXPORT_SYMBOL(snd_wss_info_double);
+
+int snd_wss_get_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
+ ucontrol->value.integer.value[1] = (chip->image[right_reg] >> shift_right) & mask;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ if (invert) {
+ ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+ }
+ return 0;
+}
+EXPORT_SYMBOL(snd_wss_get_double);
+
+int snd_wss_put_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+ int left_reg = kcontrol->private_value & 0xff;
+ int right_reg = (kcontrol->private_value >> 8) & 0xff;
+ int shift_left = (kcontrol->private_value >> 16) & 0x07;
+ int shift_right = (kcontrol->private_value >> 19) & 0x07;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ int invert = (kcontrol->private_value >> 22) & 1;
+ int change;
+ unsigned short val1, val2;
+
+ val1 = ucontrol->value.integer.value[0] & mask;
+ val2 = ucontrol->value.integer.value[1] & mask;
+ if (invert) {
+ val1 = mask - val1;
+ val2 = mask - val2;
+ }
+ val1 <<= shift_left;
+ val2 <<= shift_right;
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ if (left_reg != right_reg) {
+ val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+ val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
+ change = val1 != chip->image[left_reg] ||
+ val2 != chip->image[right_reg];
+ snd_wss_out(chip, left_reg, val1);
+ snd_wss_out(chip, right_reg, val2);
+ } else {
+ mask = (mask << shift_left) | (mask << shift_right);
+ val1 = (chip->image[left_reg] & ~mask) | val1 | val2;
+ change = val1 != chip->image[left_reg];
+ snd_wss_out(chip, left_reg, val1);
+ }
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ return change;
+}
+EXPORT_SYMBOL(snd_wss_put_double);
+
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+
+static const struct snd_kcontrol_new snd_wss_controls[] = {
+WSS_DOUBLE("PCM Playback Switch", 0,
+ CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+ CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+ db_scale_6bit),
+WSS_DOUBLE("Aux Playback Switch", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 0,
+ CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+ db_scale_5bit_12db_max),
+WSS_DOUBLE("Aux Playback Switch", 1,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 1,
+ CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+ db_scale_5bit_12db_max),
+WSS_DOUBLE_TLV("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT,
+ 0, 0, 15, 0, db_scale_rec_gain),
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = snd_wss_info_mux,
+ .get = snd_wss_get_mux,
+ .put = snd_wss_put_mux,
+},
+WSS_DOUBLE("Mic Boost (+20dB)", 0,
+ CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5, 1, 0),
+WSS_SINGLE("Loopback Capture Switch", 0,
+ CS4231_LOOPBACK, 0, 1, 0),
+WSS_SINGLE_TLV("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1,
+ db_scale_6bit),
+WSS_DOUBLE("Line Playback Switch", 0,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Line Playback Volume", 0,
+ CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1,
+ db_scale_5bit_12db_max),
+WSS_SINGLE("Beep Playback Switch", 0,
+ CS4231_MONO_CTRL, 7, 1, 1),
+WSS_SINGLE_TLV("Beep Playback Volume", 0,
+ CS4231_MONO_CTRL, 0, 15, 1,
+ db_scale_4bit),
+WSS_SINGLE("Mono Output Playback Switch", 0,
+ CS4231_MONO_CTRL, 6, 1, 1),
+WSS_SINGLE("Beep Bypass Playback Switch", 0,
+ CS4231_MONO_CTRL, 5, 1, 0),
+};
+
+int snd_wss_mixer(struct snd_wss *chip)
+{
+ struct snd_card *card;
+ unsigned int idx;
+ int err;
+ int count = ARRAY_SIZE(snd_wss_controls);
+
+ if (snd_BUG_ON(!chip || !chip->pcm))
+ return -EINVAL;
+
+ card = chip->card;
+
+ strcpy(card->mixername, chip->pcm->name);
+
+ /* Use only the first 11 entries on AD1848 */
+ if (chip->hardware & WSS_HW_AD1848_MASK)
+ count = 11;
+ /* There is no loopback on OPTI93X */
+ else if (chip->hardware == WSS_HW_OPTI93X)
+ count = 9;
+
+ for (idx = 0; idx < count; idx++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&snd_wss_controls[idx],
+ chip));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(snd_wss_mixer);
+
+const struct snd_pcm_ops *snd_wss_get_pcm_ops(int direction)
+{
+ return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+ &snd_wss_playback_ops : &snd_wss_capture_ops;
+}
+EXPORT_SYMBOL(snd_wss_get_pcm_ops);