From 4617721c502b2ddaa4e324e86da4997edf738fa5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 09:55:01 -0400 Subject: ftrace: Synchronize the initialization of ftrace_ops There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt --- sound/pci/Kconfig | 936 ++++ sound/pci/Makefile | 82 + sound/pci/ac97/Makefile | 11 + sound/pci/ac97/ac97_codec.c | 2996 +++++++++++++ sound/pci/ac97/ac97_id.h | 51 + sound/pci/ac97/ac97_local.h | 26 + sound/pci/ac97/ac97_patch.c | 3915 +++++++++++++++++ sound/pci/ac97/ac97_patch.h | 80 + sound/pci/ac97/ac97_pcm.c | 733 ++++ sound/pci/ac97/ac97_proc.c | 465 ++ sound/pci/ad1889.c | 918 ++++ sound/pci/ad1889.h | 190 + sound/pci/ak4531_codec.c | 467 ++ sound/pci/ali5451/Makefile | 10 + sound/pci/ali5451/ali5451.c | 2150 ++++++++++ sound/pci/als300.c | 734 ++++ sound/pci/als4000.c | 973 +++++ sound/pci/asihpi/Makefile | 6 + sound/pci/asihpi/asihpi.c | 2971 +++++++++++++ sound/pci/asihpi/hpi.h | 1708 ++++++++ sound/pci/asihpi/hpi6000.c | 1804 ++++++++ sound/pci/asihpi/hpi6000.h | 59 + sound/pci/asihpi/hpi6205.c | 2219 ++++++++++ sound/pci/asihpi/hpi6205.h | 92 + sound/pci/asihpi/hpi_internal.h | 1419 ++++++ sound/pci/asihpi/hpi_version.h | 33 + sound/pci/asihpi/hpicmn.c | 710 +++ sound/pci/asihpi/hpicmn.h | 71 + sound/pci/asihpi/hpidebug.c | 67 + sound/pci/asihpi/hpidebug.h | 90 + sound/pci/asihpi/hpidspcd.c | 129 + sound/pci/asihpi/hpidspcd.h | 95 + sound/pci/asihpi/hpifunc.c | 2868 +++++++++++++ sound/pci/asihpi/hpimsginit.c | 120 + sound/pci/asihpi/hpimsginit.h | 35 + sound/pci/asihpi/hpimsgx.c | 795 ++++ sound/pci/asihpi/hpimsgx.h | 25 + sound/pci/asihpi/hpioctl.c | 579 +++ sound/pci/asihpi/hpioctl.h | 27 + sound/pci/asihpi/hpios.c | 72 + sound/pci/asihpi/hpios.h | 154 + sound/pci/asihpi/hpipcida.h | 26 + sound/pci/atiixp.c | 1619 +++++++ sound/pci/atiixp_modem.c | 1246 ++++++ sound/pci/au88x0/Makefile | 8 + sound/pci/au88x0/au8810.c | 17 + sound/pci/au88x0/au8810.h | 225 + sound/pci/au88x0/au8820.c | 15 + sound/pci/au88x0/au8820.h | 205 + sound/pci/au88x0/au8830.c | 18 + sound/pci/au88x0/au8830.h | 252 ++ sound/pci/au88x0/au88x0.c | 325 ++ sound/pci/au88x0/au88x0.h | 281 ++ sound/pci/au88x0/au88x0_a3d.c | 906 ++++ sound/pci/au88x0/au88x0_a3d.h | 111 + sound/pci/au88x0/au88x0_a3ddata.c | 79 + sound/pci/au88x0/au88x0_core.c | 2862 +++++++++++++ sound/pci/au88x0/au88x0_eq.c | 922 ++++ sound/pci/au88x0/au88x0_eq.h | 44 + sound/pci/au88x0/au88x0_eqdata.c | 117 + sound/pci/au88x0/au88x0_game.c | 121 + sound/pci/au88x0/au88x0_mixer.c | 45 + sound/pci/au88x0/au88x0_mpu401.c | 98 + sound/pci/au88x0/au88x0_pcm.c | 682 +++ sound/pci/au88x0/au88x0_synth.c | 400 ++ sound/pci/au88x0/au88x0_wt.h | 66 + sound/pci/au88x0/au88x0_xtalk.c | 783 ++++ sound/pci/au88x0/au88x0_xtalk.h | 49 + sound/pci/aw2/Makefile | 4 + sound/pci/aw2/aw2-alsa.c | 639 +++ sound/pci/aw2/aw2-saa7146.c | 448 ++ sound/pci/aw2/aw2-saa7146.h | 93 + sound/pci/aw2/aw2-tsl.c | 97 + sound/pci/aw2/saa7146.h | 155 + sound/pci/azt3328.c | 2631 ++++++++++++ sound/pci/azt3328.h | 343 ++ sound/pci/bt87x.c | 918 ++++ sound/pci/ca0106/Makefile | 5 + sound/pci/ca0106/ca0106.h | 726 ++++ sound/pci/ca0106/ca0106_main.c | 1838 ++++++++ sound/pci/ca0106/ca0106_mixer.c | 902 ++++ sound/pci/ca0106/ca0106_proc.c | 427 ++ sound/pci/ca0106/ca_midi.c | 281 ++ sound/pci/ca0106/ca_midi.h | 52 + sound/pci/cmipci.c | 3293 ++++++++++++++ sound/pci/cs4281.c | 1983 +++++++++ sound/pci/cs46xx/Makefile | 11 + sound/pci/cs46xx/cs46xx.c | 142 + sound/pci/cs46xx/cs46xx.h | 1732 ++++++++ sound/pci/cs46xx/cs46xx_dsp_scb_types.h | 1198 ++++++ sound/pci/cs46xx/cs46xx_dsp_spos.h | 213 + sound/pci/cs46xx/cs46xx_dsp_task_types.h | 237 ++ sound/pci/cs46xx/cs46xx_lib.c | 3914 +++++++++++++++++ sound/pci/cs46xx/cs46xx_lib.h | 195 + sound/pci/cs46xx/dsp_spos.c | 1965 +++++++++ sound/pci/cs46xx/dsp_spos.h | 216 + sound/pci/cs46xx/dsp_spos_scb_lib.c | 1749 ++++++++ sound/pci/cs5530.c | 228 + sound/pci/cs5535audio/Makefile | 11 + sound/pci/cs5535audio/cs5535audio.c | 357 ++ sound/pci/cs5535audio/cs5535audio.h | 140 + sound/pci/cs5535audio/cs5535audio_olpc.c | 187 + sound/pci/cs5535audio/cs5535audio_pcm.c | 431 ++ sound/pci/cs5535audio/cs5535audio_pm.c | 100 + sound/pci/ctxfi/Makefile | 6 + sound/pci/ctxfi/ct20k1reg.h | 631 +++ sound/pci/ctxfi/ct20k2reg.h | 86 + sound/pci/ctxfi/ctamixer.c | 481 +++ sound/pci/ctxfi/ctamixer.h | 95 + sound/pci/ctxfi/ctatc.c | 1795 ++++++++ sound/pci/ctxfi/ctatc.h | 156 + sound/pci/ctxfi/ctdaio.c | 743 ++++ sound/pci/ctxfi/ctdaio.h | 124 + sound/pci/ctxfi/cthardware.c | 89 + sound/pci/ctxfi/cthardware.h | 216 + sound/pci/ctxfi/cthw20k1.c | 2251 ++++++++++ sound/pci/ctxfi/cthw20k1.h | 22 + sound/pci/ctxfi/cthw20k2.c | 2389 +++++++++++ sound/pci/ctxfi/cthw20k2.h | 22 + sound/pci/ctxfi/ctimap.c | 108 + sound/pci/ctxfi/ctimap.h | 36 + sound/pci/ctxfi/ctmixer.c | 1272 ++++++ sound/pci/ctxfi/ctmixer.h | 69 + sound/pci/ctxfi/ctpcm.c | 476 +++ sound/pci/ctxfi/ctpcm.h | 23 + sound/pci/ctxfi/ctresource.c | 289 ++ sound/pci/ctxfi/ctresource.h | 69 + sound/pci/ctxfi/ctsrc.c | 851 ++++ sound/pci/ctxfi/ctsrc.h | 148 + sound/pci/ctxfi/cttimer.c | 422 ++ sound/pci/ctxfi/cttimer.h | 30 + sound/pci/ctxfi/ctvmem.c | 238 ++ sound/pci/ctxfi/ctvmem.h | 60 + sound/pci/ctxfi/xfi.c | 158 + sound/pci/echoaudio/Makefile | 35 + sound/pci/echoaudio/darla20.c | 89 + sound/pci/echoaudio/darla20_dsp.c | 116 + sound/pci/echoaudio/darla24.c | 96 + sound/pci/echoaudio/darla24_dsp.c | 150 + sound/pci/echoaudio/echo3g.c | 110 + sound/pci/echoaudio/echo3g_dsp.c | 117 + sound/pci/echoaudio/echoaudio.c | 2212 ++++++++++ sound/pci/echoaudio/echoaudio.h | 549 +++ sound/pci/echoaudio/echoaudio_3g.c | 415 ++ sound/pci/echoaudio/echoaudio_dsp.c | 1141 +++++ sound/pci/echoaudio/echoaudio_dsp.h | 683 +++ sound/pci/echoaudio/echoaudio_gml.c | 189 + sound/pci/echoaudio/gina20.c | 93 + sound/pci/echoaudio/gina20_dsp.c | 202 + sound/pci/echoaudio/gina24.c | 117 + sound/pci/echoaudio/gina24_dsp.c | 331 ++ sound/pci/echoaudio/indigo.c | 95 + sound/pci/echoaudio/indigo_dsp.c | 152 + sound/pci/echoaudio/indigo_express_dsp.c | 107 + sound/pci/echoaudio/indigodj.c | 95 + sound/pci/echoaudio/indigodj_dsp.c | 152 + sound/pci/echoaudio/indigodjx.c | 96 + sound/pci/echoaudio/indigodjx_dsp.c | 55 + sound/pci/echoaudio/indigoio.c | 96 + sound/pci/echoaudio/indigoio_dsp.c | 122 + sound/pci/echoaudio/indigoiox.c | 98 + sound/pci/echoaudio/indigoiox_dsp.c | 55 + sound/pci/echoaudio/layla20.c | 103 + sound/pci/echoaudio/layla20_dsp.c | 276 ++ sound/pci/echoaudio/layla24.c | 115 + sound/pci/echoaudio/layla24_dsp.c | 381 ++ sound/pci/echoaudio/mia.c | 109 + sound/pci/echoaudio/mia_dsp.c | 212 + sound/pci/echoaudio/midi.c | 311 ++ sound/pci/echoaudio/mona.c | 132 + sound/pci/echoaudio/mona_dsp.c | 407 ++ sound/pci/emu10k1/Makefile | 17 + sound/pci/emu10k1/emu10k1.c | 241 ++ sound/pci/emu10k1/emu10k1_callback.c | 522 +++ sound/pci/emu10k1/emu10k1_main.c | 1792 ++++++++ sound/pci/emu10k1/emu10k1_patch.c | 170 + sound/pci/emu10k1/emu10k1_synth.c | 99 + sound/pci/emu10k1/emu10k1_synth_local.h | 29 + sound/pci/emu10k1/emu10k1x.c | 1533 +++++++ sound/pci/emu10k1/emufx.c | 2722 ++++++++++++ sound/pci/emu10k1/emumixer.c | 2376 +++++++++++ sound/pci/emu10k1/emumpu401.c | 363 ++ sound/pci/emu10k1/emupcm.c | 1838 ++++++++ sound/pci/emu10k1/emuproc.c | 726 ++++ sound/pci/emu10k1/io.c | 716 ++++ sound/pci/emu10k1/irq.c | 162 + sound/pci/emu10k1/memory.c | 649 +++ sound/pci/emu10k1/p16v.c | 826 ++++ sound/pci/emu10k1/p16v.h | 228 + sound/pci/emu10k1/p17v.h | 143 + sound/pci/emu10k1/timer.c | 89 + sound/pci/emu10k1/tina2.h | 17 + sound/pci/emu10k1/voice.c | 136 + sound/pci/ens1370.c | 2350 ++++++++++ sound/pci/ens1371.c | 3 + sound/pci/es1938.c | 1781 ++++++++ sound/pci/es1968.c | 2817 ++++++++++++ sound/pci/fm801.c | 1405 ++++++ sound/pci/ice1712/Makefile | 13 + sound/pci/ice1712/ak4xxx.c | 170 + sound/pci/ice1712/amp.c | 83 + sound/pci/ice1712/amp.h | 34 + sound/pci/ice1712/aureon.c | 2257 ++++++++++ sound/pci/ice1712/aureon.h | 51 + sound/pci/ice1712/delta.c | 913 ++++ sound/pci/ice1712/delta.h | 152 + sound/pci/ice1712/envy24ht.h | 206 + sound/pci/ice1712/ews.c | 1070 +++++ sound/pci/ice1712/ews.h | 72 + sound/pci/ice1712/hoontech.c | 366 ++ sound/pci/ice1712/hoontech.h | 64 + sound/pci/ice1712/ice1712.c | 2733 ++++++++++++ sound/pci/ice1712/ice1712.h | 525 +++ sound/pci/ice1712/ice1724.c | 2758 ++++++++++++ sound/pci/ice1712/juli.c | 662 +++ sound/pci/ice1712/juli.h | 11 + sound/pci/ice1712/maya44.c | 742 ++++ sound/pci/ice1712/maya44.h | 11 + sound/pci/ice1712/phase.c | 948 +++++ sound/pci/ice1712/phase.h | 39 + sound/pci/ice1712/pontis.c | 795 ++++ sound/pci/ice1712/pontis.h | 19 + sound/pci/ice1712/prodigy192.c | 783 ++++ sound/pci/ice1712/prodigy192.h | 20 + sound/pci/ice1712/prodigy_hifi.c | 1249 ++++++ sound/pci/ice1712/prodigy_hifi.h | 24 + sound/pci/ice1712/psc724.c | 447 ++ sound/pci/ice1712/psc724.h | 14 + sound/pci/ice1712/quartet.c | 1069 +++++ sound/pci/ice1712/quartet.h | 11 + sound/pci/ice1712/revo.c | 631 +++ sound/pci/ice1712/revo.h | 41 + sound/pci/ice1712/se.c | 752 ++++ sound/pci/ice1712/se.h | 16 + sound/pci/ice1712/stac946x.h | 26 + sound/pci/ice1712/vt1720_mobo.c | 124 + sound/pci/ice1712/vt1720_mobo.h | 27 + sound/pci/ice1712/wm8766.c | 332 ++ sound/pci/ice1712/wm8766.h | 147 + sound/pci/ice1712/wm8776.c | 601 +++ sound/pci/ice1712/wm8776.h | 209 + sound/pci/ice1712/wtm.c | 630 +++ sound/pci/ice1712/wtm.h | 21 + sound/pci/intel8x0.c | 3265 ++++++++++++++ sound/pci/intel8x0m.c | 1283 ++++++ sound/pci/korg1212/Makefile | 10 + sound/pci/korg1212/korg1212.c | 2264 ++++++++++ sound/pci/lola/Makefile | 5 + sound/pci/lola/lola.c | 714 ++++ sound/pci/lola/lola.h | 511 +++ sound/pci/lola/lola_clock.c | 310 ++ sound/pci/lola/lola_mixer.c | 842 ++++ sound/pci/lola/lola_pcm.c | 681 +++ sound/pci/lola/lola_proc.c | 203 + sound/pci/lx6464es/Makefile | 3 + sound/pci/lx6464es/lx6464es.c | 1041 +++++ sound/pci/lx6464es/lx6464es.h | 95 + sound/pci/lx6464es/lx_core.c | 1123 +++++ sound/pci/lx6464es/lx_core.h | 219 + sound/pci/lx6464es/lx_defs.h | 361 ++ sound/pci/maestro3.c | 2712 ++++++++++++ sound/pci/mixart/Makefile | 9 + sound/pci/mixart/mixart.c | 1390 ++++++ sound/pci/mixart/mixart.h | 207 + sound/pci/mixart/mixart_core.c | 584 +++ sound/pci/mixart/mixart_core.h | 565 +++ sound/pci/mixart/mixart_hwdep.c | 584 +++ sound/pci/mixart/mixart_hwdep.h | 142 + sound/pci/mixart/mixart_mixer.c | 1190 ++++++ sound/pci/mixart/mixart_mixer.h | 18 + sound/pci/nm256/Makefile | 10 + sound/pci/nm256/nm256.c | 1653 +++++++ sound/pci/nm256/nm256_coef.c | 4608 ++++++++++++++++++++ sound/pci/oxygen/Makefile | 11 + sound/pci/oxygen/ak4396.h | 45 + sound/pci/oxygen/cm9780.h | 64 + sound/pci/oxygen/cs2000.h | 84 + sound/pci/oxygen/cs4245.h | 111 + sound/pci/oxygen/cs4362a.h | 70 + sound/pci/oxygen/cs4398.h | 70 + sound/pci/oxygen/oxygen.c | 1116 +++++ sound/pci/oxygen/oxygen.h | 257 ++ sound/pci/oxygen/oxygen_io.c | 280 ++ sound/pci/oxygen/oxygen_lib.c | 797 ++++ sound/pci/oxygen/oxygen_mixer.c | 1105 +++++ sound/pci/oxygen/oxygen_pcm.c | 772 ++++ sound/pci/oxygen/oxygen_regs.h | 459 ++ sound/pci/oxygen/pcm1796.h | 58 + sound/pci/oxygen/se6x.c | 146 + sound/pci/oxygen/virtuoso.c | 91 + sound/pci/oxygen/wm8766.h | 74 + sound/pci/oxygen/wm8776.h | 174 + sound/pci/oxygen/wm8785.h | 46 + sound/pci/oxygen/xonar.h | 55 + sound/pci/oxygen/xonar_cs43xx.c | 439 ++ sound/pci/oxygen/xonar_dg.c | 285 ++ sound/pci/oxygen/xonar_dg.h | 57 + sound/pci/oxygen/xonar_dg_mixer.c | 456 ++ sound/pci/oxygen/xonar_hdmi.c | 117 + sound/pci/oxygen/xonar_lib.c | 122 + sound/pci/oxygen/xonar_pcm179x.c | 1348 ++++++ sound/pci/oxygen/xonar_wm87x6.c | 1322 ++++++ sound/pci/pcxhr/Makefile | 3 + sound/pci/pcxhr/pcxhr.c | 1650 +++++++ sound/pci/pcxhr/pcxhr.h | 201 + sound/pci/pcxhr/pcxhr_core.c | 1326 ++++++ sound/pci/pcxhr/pcxhr_core.h | 192 + sound/pci/pcxhr/pcxhr_hwdep.c | 411 ++ sound/pci/pcxhr/pcxhr_hwdep.h | 27 + sound/pci/pcxhr/pcxhr_mix22.c | 825 ++++ sound/pci/pcxhr/pcxhr_mix22.h | 47 + sound/pci/pcxhr/pcxhr_mixer.c | 1232 ++++++ sound/pci/pcxhr/pcxhr_mixer.h | 16 + sound/pci/riptide/Makefile | 4 + sound/pci/riptide/riptide.c | 2172 ++++++++++ sound/pci/rme32.c | 1877 ++++++++ sound/pci/rme96.c | 2442 +++++++++++ sound/pci/rme9652/Makefile | 14 + sound/pci/rme9652/hdsp.c | 5375 +++++++++++++++++++++++ sound/pci/rme9652/hdspm.c | 6864 ++++++++++++++++++++++++++++++ sound/pci/rme9652/rme9652.c | 2521 +++++++++++ sound/pci/sis7019.c | 1382 ++++++ sound/pci/sis7019.h | 330 ++ sound/pci/sonicvibes.c | 1449 +++++++ sound/pci/trident/Makefile | 10 + sound/pci/trident/trident.c | 152 + sound/pci/trident/trident.h | 422 ++ sound/pci/trident/trident_main.c | 3841 +++++++++++++++++ sound/pci/trident/trident_memory.c | 269 ++ sound/pci/via82xx.c | 2564 +++++++++++ sound/pci/via82xx_modem.c | 1160 +++++ sound/pci/vx222/Makefile | 9 + sound/pci/vx222/vx222.c | 234 + sound/pci/vx222/vx222.h | 103 + sound/pci/vx222/vx222_ops.c | 1024 +++++ sound/pci/ymfpci/Makefile | 10 + sound/pci/ymfpci/ymfpci.c | 366 ++ sound/pci/ymfpci/ymfpci.h | 410 ++ sound/pci/ymfpci/ymfpci_main.c | 2317 ++++++++++ 339 files changed, 218939 insertions(+) create mode 100644 sound/pci/Kconfig create mode 100644 sound/pci/Makefile create mode 100644 sound/pci/ac97/Makefile create mode 100644 sound/pci/ac97/ac97_codec.c create mode 100644 sound/pci/ac97/ac97_id.h create mode 100644 sound/pci/ac97/ac97_local.h create mode 100644 sound/pci/ac97/ac97_patch.c create mode 100644 sound/pci/ac97/ac97_patch.h create mode 100644 sound/pci/ac97/ac97_pcm.c create mode 100644 sound/pci/ac97/ac97_proc.c create mode 100644 sound/pci/ad1889.c create mode 100644 sound/pci/ad1889.h create mode 100644 sound/pci/ak4531_codec.c create mode 100644 sound/pci/ali5451/Makefile create mode 100644 sound/pci/ali5451/ali5451.c create mode 100644 sound/pci/als300.c create mode 100644 sound/pci/als4000.c create mode 100644 sound/pci/asihpi/Makefile create mode 100644 sound/pci/asihpi/asihpi.c create mode 100644 sound/pci/asihpi/hpi.h create mode 100644 sound/pci/asihpi/hpi6000.c create mode 100644 sound/pci/asihpi/hpi6000.h create mode 100644 sound/pci/asihpi/hpi6205.c create mode 100644 sound/pci/asihpi/hpi6205.h create mode 100644 sound/pci/asihpi/hpi_internal.h create mode 100644 sound/pci/asihpi/hpi_version.h create mode 100644 sound/pci/asihpi/hpicmn.c create mode 100644 sound/pci/asihpi/hpicmn.h create mode 100644 sound/pci/asihpi/hpidebug.c create mode 100644 sound/pci/asihpi/hpidebug.h create mode 100644 sound/pci/asihpi/hpidspcd.c create mode 100644 sound/pci/asihpi/hpidspcd.h create mode 100644 sound/pci/asihpi/hpifunc.c create mode 100644 sound/pci/asihpi/hpimsginit.c create mode 100644 sound/pci/asihpi/hpimsginit.h create mode 100644 sound/pci/asihpi/hpimsgx.c create mode 100644 sound/pci/asihpi/hpimsgx.h create mode 100644 sound/pci/asihpi/hpioctl.c create mode 100644 sound/pci/asihpi/hpioctl.h create mode 100644 sound/pci/asihpi/hpios.c create mode 100644 sound/pci/asihpi/hpios.h create mode 100644 sound/pci/asihpi/hpipcida.h create mode 100644 sound/pci/atiixp.c create mode 100644 sound/pci/atiixp_modem.c create mode 100644 sound/pci/au88x0/Makefile create mode 100644 sound/pci/au88x0/au8810.c create mode 100644 sound/pci/au88x0/au8810.h create mode 100644 sound/pci/au88x0/au8820.c create mode 100644 sound/pci/au88x0/au8820.h create mode 100644 sound/pci/au88x0/au8830.c create mode 100644 sound/pci/au88x0/au8830.h create mode 100644 sound/pci/au88x0/au88x0.c create mode 100644 sound/pci/au88x0/au88x0.h create mode 100644 sound/pci/au88x0/au88x0_a3d.c create mode 100644 sound/pci/au88x0/au88x0_a3d.h create mode 100644 sound/pci/au88x0/au88x0_a3ddata.c create mode 100644 sound/pci/au88x0/au88x0_core.c create mode 100644 sound/pci/au88x0/au88x0_eq.c create mode 100644 sound/pci/au88x0/au88x0_eq.h create mode 100644 sound/pci/au88x0/au88x0_eqdata.c create mode 100644 sound/pci/au88x0/au88x0_game.c create mode 100644 sound/pci/au88x0/au88x0_mixer.c create mode 100644 sound/pci/au88x0/au88x0_mpu401.c create mode 100644 sound/pci/au88x0/au88x0_pcm.c create mode 100644 sound/pci/au88x0/au88x0_synth.c create mode 100644 sound/pci/au88x0/au88x0_wt.h create mode 100644 sound/pci/au88x0/au88x0_xtalk.c create mode 100644 sound/pci/au88x0/au88x0_xtalk.h create mode 100644 sound/pci/aw2/Makefile create mode 100644 sound/pci/aw2/aw2-alsa.c create mode 100644 sound/pci/aw2/aw2-saa7146.c create mode 100644 sound/pci/aw2/aw2-saa7146.h create mode 100644 sound/pci/aw2/aw2-tsl.c create mode 100644 sound/pci/aw2/saa7146.h create mode 100644 sound/pci/azt3328.c create mode 100644 sound/pci/azt3328.h create mode 100644 sound/pci/bt87x.c create mode 100644 sound/pci/ca0106/Makefile create mode 100644 sound/pci/ca0106/ca0106.h create mode 100644 sound/pci/ca0106/ca0106_main.c create mode 100644 sound/pci/ca0106/ca0106_mixer.c create mode 100644 sound/pci/ca0106/ca0106_proc.c create mode 100644 sound/pci/ca0106/ca_midi.c create mode 100644 sound/pci/ca0106/ca_midi.h create mode 100644 sound/pci/cmipci.c create mode 100644 sound/pci/cs4281.c create mode 100644 sound/pci/cs46xx/Makefile create mode 100644 sound/pci/cs46xx/cs46xx.c create mode 100644 sound/pci/cs46xx/cs46xx.h create mode 100644 sound/pci/cs46xx/cs46xx_dsp_scb_types.h create mode 100644 sound/pci/cs46xx/cs46xx_dsp_spos.h create mode 100644 sound/pci/cs46xx/cs46xx_dsp_task_types.h create mode 100644 sound/pci/cs46xx/cs46xx_lib.c create mode 100644 sound/pci/cs46xx/cs46xx_lib.h create mode 100644 sound/pci/cs46xx/dsp_spos.c create mode 100644 sound/pci/cs46xx/dsp_spos.h create mode 100644 sound/pci/cs46xx/dsp_spos_scb_lib.c create mode 100644 sound/pci/cs5530.c create mode 100644 sound/pci/cs5535audio/Makefile create mode 100644 sound/pci/cs5535audio/cs5535audio.c create mode 100644 sound/pci/cs5535audio/cs5535audio.h create mode 100644 sound/pci/cs5535audio/cs5535audio_olpc.c create mode 100644 sound/pci/cs5535audio/cs5535audio_pcm.c create mode 100644 sound/pci/cs5535audio/cs5535audio_pm.c create mode 100644 sound/pci/ctxfi/Makefile create mode 100644 sound/pci/ctxfi/ct20k1reg.h create mode 100644 sound/pci/ctxfi/ct20k2reg.h create mode 100644 sound/pci/ctxfi/ctamixer.c create mode 100644 sound/pci/ctxfi/ctamixer.h create mode 100644 sound/pci/ctxfi/ctatc.c create mode 100644 sound/pci/ctxfi/ctatc.h create mode 100644 sound/pci/ctxfi/ctdaio.c create mode 100644 sound/pci/ctxfi/ctdaio.h create mode 100644 sound/pci/ctxfi/cthardware.c create mode 100644 sound/pci/ctxfi/cthardware.h create mode 100644 sound/pci/ctxfi/cthw20k1.c create mode 100644 sound/pci/ctxfi/cthw20k1.h create mode 100644 sound/pci/ctxfi/cthw20k2.c create mode 100644 sound/pci/ctxfi/cthw20k2.h create mode 100644 sound/pci/ctxfi/ctimap.c create mode 100644 sound/pci/ctxfi/ctimap.h create mode 100644 sound/pci/ctxfi/ctmixer.c create mode 100644 sound/pci/ctxfi/ctmixer.h create mode 100644 sound/pci/ctxfi/ctpcm.c create mode 100644 sound/pci/ctxfi/ctpcm.h create mode 100644 sound/pci/ctxfi/ctresource.c create mode 100644 sound/pci/ctxfi/ctresource.h create mode 100644 sound/pci/ctxfi/ctsrc.c create mode 100644 sound/pci/ctxfi/ctsrc.h create mode 100644 sound/pci/ctxfi/cttimer.c create mode 100644 sound/pci/ctxfi/cttimer.h create mode 100644 sound/pci/ctxfi/ctvmem.c create mode 100644 sound/pci/ctxfi/ctvmem.h create mode 100644 sound/pci/ctxfi/xfi.c create mode 100644 sound/pci/echoaudio/Makefile create mode 100644 sound/pci/echoaudio/darla20.c create mode 100644 sound/pci/echoaudio/darla20_dsp.c create mode 100644 sound/pci/echoaudio/darla24.c create mode 100644 sound/pci/echoaudio/darla24_dsp.c create mode 100644 sound/pci/echoaudio/echo3g.c create mode 100644 sound/pci/echoaudio/echo3g_dsp.c create mode 100644 sound/pci/echoaudio/echoaudio.c create mode 100644 sound/pci/echoaudio/echoaudio.h create mode 100644 sound/pci/echoaudio/echoaudio_3g.c create mode 100644 sound/pci/echoaudio/echoaudio_dsp.c create mode 100644 sound/pci/echoaudio/echoaudio_dsp.h create mode 100644 sound/pci/echoaudio/echoaudio_gml.c create mode 100644 sound/pci/echoaudio/gina20.c create mode 100644 sound/pci/echoaudio/gina20_dsp.c create mode 100644 sound/pci/echoaudio/gina24.c create mode 100644 sound/pci/echoaudio/gina24_dsp.c create mode 100644 sound/pci/echoaudio/indigo.c create mode 100644 sound/pci/echoaudio/indigo_dsp.c create mode 100644 sound/pci/echoaudio/indigo_express_dsp.c create mode 100644 sound/pci/echoaudio/indigodj.c create mode 100644 sound/pci/echoaudio/indigodj_dsp.c create mode 100644 sound/pci/echoaudio/indigodjx.c create mode 100644 sound/pci/echoaudio/indigodjx_dsp.c create mode 100644 sound/pci/echoaudio/indigoio.c create mode 100644 sound/pci/echoaudio/indigoio_dsp.c create mode 100644 sound/pci/echoaudio/indigoiox.c create mode 100644 sound/pci/echoaudio/indigoiox_dsp.c create mode 100644 sound/pci/echoaudio/layla20.c create mode 100644 sound/pci/echoaudio/layla20_dsp.c create mode 100644 sound/pci/echoaudio/layla24.c create mode 100644 sound/pci/echoaudio/layla24_dsp.c create mode 100644 sound/pci/echoaudio/mia.c create mode 100644 sound/pci/echoaudio/mia_dsp.c create mode 100644 sound/pci/echoaudio/midi.c create mode 100644 sound/pci/echoaudio/mona.c create mode 100644 sound/pci/echoaudio/mona_dsp.c create mode 100644 sound/pci/emu10k1/Makefile create mode 100644 sound/pci/emu10k1/emu10k1.c create mode 100644 sound/pci/emu10k1/emu10k1_callback.c create mode 100644 sound/pci/emu10k1/emu10k1_main.c create mode 100644 sound/pci/emu10k1/emu10k1_patch.c create mode 100644 sound/pci/emu10k1/emu10k1_synth.c create mode 100644 sound/pci/emu10k1/emu10k1_synth_local.h create mode 100644 sound/pci/emu10k1/emu10k1x.c create mode 100644 sound/pci/emu10k1/emufx.c create mode 100644 sound/pci/emu10k1/emumixer.c create mode 100644 sound/pci/emu10k1/emumpu401.c create mode 100644 sound/pci/emu10k1/emupcm.c create mode 100644 sound/pci/emu10k1/emuproc.c create mode 100644 sound/pci/emu10k1/io.c create mode 100644 sound/pci/emu10k1/irq.c create mode 100644 sound/pci/emu10k1/memory.c create mode 100644 sound/pci/emu10k1/p16v.c create mode 100644 sound/pci/emu10k1/p16v.h create mode 100644 sound/pci/emu10k1/p17v.h create mode 100644 sound/pci/emu10k1/timer.c create mode 100644 sound/pci/emu10k1/tina2.h create mode 100644 sound/pci/emu10k1/voice.c create mode 100644 sound/pci/ens1370.c create mode 100644 sound/pci/ens1371.c create mode 100644 sound/pci/es1938.c create mode 100644 sound/pci/es1968.c create mode 100644 sound/pci/fm801.c create mode 100644 sound/pci/ice1712/Makefile create mode 100644 sound/pci/ice1712/ak4xxx.c create mode 100644 sound/pci/ice1712/amp.c create mode 100644 sound/pci/ice1712/amp.h create mode 100644 sound/pci/ice1712/aureon.c create mode 100644 sound/pci/ice1712/aureon.h create mode 100644 sound/pci/ice1712/delta.c create mode 100644 sound/pci/ice1712/delta.h create mode 100644 sound/pci/ice1712/envy24ht.h create mode 100644 sound/pci/ice1712/ews.c create mode 100644 sound/pci/ice1712/ews.h create mode 100644 sound/pci/ice1712/hoontech.c create mode 100644 sound/pci/ice1712/hoontech.h create mode 100644 sound/pci/ice1712/ice1712.c create mode 100644 sound/pci/ice1712/ice1712.h create mode 100644 sound/pci/ice1712/ice1724.c create mode 100644 sound/pci/ice1712/juli.c create mode 100644 sound/pci/ice1712/juli.h create mode 100644 sound/pci/ice1712/maya44.c create mode 100644 sound/pci/ice1712/maya44.h create mode 100644 sound/pci/ice1712/phase.c create mode 100644 sound/pci/ice1712/phase.h create mode 100644 sound/pci/ice1712/pontis.c create mode 100644 sound/pci/ice1712/pontis.h create mode 100644 sound/pci/ice1712/prodigy192.c create mode 100644 sound/pci/ice1712/prodigy192.h create mode 100644 sound/pci/ice1712/prodigy_hifi.c create mode 100644 sound/pci/ice1712/prodigy_hifi.h create mode 100644 sound/pci/ice1712/psc724.c create mode 100644 sound/pci/ice1712/psc724.h create mode 100644 sound/pci/ice1712/quartet.c create mode 100644 sound/pci/ice1712/quartet.h create mode 100644 sound/pci/ice1712/revo.c create mode 100644 sound/pci/ice1712/revo.h create mode 100644 sound/pci/ice1712/se.c create mode 100644 sound/pci/ice1712/se.h create mode 100644 sound/pci/ice1712/stac946x.h create mode 100644 sound/pci/ice1712/vt1720_mobo.c create mode 100644 sound/pci/ice1712/vt1720_mobo.h create mode 100644 sound/pci/ice1712/wm8766.c create mode 100644 sound/pci/ice1712/wm8766.h create mode 100644 sound/pci/ice1712/wm8776.c create mode 100644 sound/pci/ice1712/wm8776.h create mode 100644 sound/pci/ice1712/wtm.c create mode 100644 sound/pci/ice1712/wtm.h create mode 100644 sound/pci/intel8x0.c create mode 100644 sound/pci/intel8x0m.c create mode 100644 sound/pci/korg1212/Makefile create mode 100644 sound/pci/korg1212/korg1212.c create mode 100644 sound/pci/lola/Makefile create mode 100644 sound/pci/lola/lola.c create mode 100644 sound/pci/lola/lola.h create mode 100644 sound/pci/lola/lola_clock.c create mode 100644 sound/pci/lola/lola_mixer.c create mode 100644 sound/pci/lola/lola_pcm.c create mode 100644 sound/pci/lola/lola_proc.c create mode 100644 sound/pci/lx6464es/Makefile create mode 100644 sound/pci/lx6464es/lx6464es.c create mode 100644 sound/pci/lx6464es/lx6464es.h create mode 100644 sound/pci/lx6464es/lx_core.c create mode 100644 sound/pci/lx6464es/lx_core.h create mode 100644 sound/pci/lx6464es/lx_defs.h create mode 100644 sound/pci/maestro3.c create mode 100644 sound/pci/mixart/Makefile create mode 100644 sound/pci/mixart/mixart.c create mode 100644 sound/pci/mixart/mixart.h create mode 100644 sound/pci/mixart/mixart_core.c create mode 100644 sound/pci/mixart/mixart_core.h create mode 100644 sound/pci/mixart/mixart_hwdep.c create mode 100644 sound/pci/mixart/mixart_hwdep.h create mode 100644 sound/pci/mixart/mixart_mixer.c create mode 100644 sound/pci/mixart/mixart_mixer.h create mode 100644 sound/pci/nm256/Makefile create mode 100644 sound/pci/nm256/nm256.c create mode 100644 sound/pci/nm256/nm256_coef.c create mode 100644 sound/pci/oxygen/Makefile create mode 100644 sound/pci/oxygen/ak4396.h create mode 100644 sound/pci/oxygen/cm9780.h create mode 100644 sound/pci/oxygen/cs2000.h create mode 100644 sound/pci/oxygen/cs4245.h create mode 100644 sound/pci/oxygen/cs4362a.h create mode 100644 sound/pci/oxygen/cs4398.h create mode 100644 sound/pci/oxygen/oxygen.c create mode 100644 sound/pci/oxygen/oxygen.h create mode 100644 sound/pci/oxygen/oxygen_io.c create mode 100644 sound/pci/oxygen/oxygen_lib.c create mode 100644 sound/pci/oxygen/oxygen_mixer.c create mode 100644 sound/pci/oxygen/oxygen_pcm.c create mode 100644 sound/pci/oxygen/oxygen_regs.h create mode 100644 sound/pci/oxygen/pcm1796.h create mode 100644 sound/pci/oxygen/se6x.c create mode 100644 sound/pci/oxygen/virtuoso.c create mode 100644 sound/pci/oxygen/wm8766.h create mode 100644 sound/pci/oxygen/wm8776.h create mode 100644 sound/pci/oxygen/wm8785.h create mode 100644 sound/pci/oxygen/xonar.h create mode 100644 sound/pci/oxygen/xonar_cs43xx.c create mode 100644 sound/pci/oxygen/xonar_dg.c create mode 100644 sound/pci/oxygen/xonar_dg.h create mode 100644 sound/pci/oxygen/xonar_dg_mixer.c create mode 100644 sound/pci/oxygen/xonar_hdmi.c create mode 100644 sound/pci/oxygen/xonar_lib.c create mode 100644 sound/pci/oxygen/xonar_pcm179x.c create mode 100644 sound/pci/oxygen/xonar_wm87x6.c create mode 100644 sound/pci/pcxhr/Makefile create mode 100644 sound/pci/pcxhr/pcxhr.c create mode 100644 sound/pci/pcxhr/pcxhr.h create mode 100644 sound/pci/pcxhr/pcxhr_core.c create mode 100644 sound/pci/pcxhr/pcxhr_core.h create mode 100644 sound/pci/pcxhr/pcxhr_hwdep.c create mode 100644 sound/pci/pcxhr/pcxhr_hwdep.h create mode 100644 sound/pci/pcxhr/pcxhr_mix22.c create mode 100644 sound/pci/pcxhr/pcxhr_mix22.h create mode 100644 sound/pci/pcxhr/pcxhr_mixer.c create mode 100644 sound/pci/pcxhr/pcxhr_mixer.h create mode 100644 sound/pci/riptide/Makefile create mode 100644 sound/pci/riptide/riptide.c create mode 100644 sound/pci/rme32.c create mode 100644 sound/pci/rme96.c create mode 100644 sound/pci/rme9652/Makefile create mode 100644 sound/pci/rme9652/hdsp.c create mode 100644 sound/pci/rme9652/hdspm.c create mode 100644 sound/pci/rme9652/rme9652.c create mode 100644 sound/pci/sis7019.c create mode 100644 sound/pci/sis7019.h create mode 100644 sound/pci/sonicvibes.c create mode 100644 sound/pci/trident/Makefile create mode 100644 sound/pci/trident/trident.c create mode 100644 sound/pci/trident/trident.h create mode 100644 sound/pci/trident/trident_main.c create mode 100644 sound/pci/trident/trident_memory.c create mode 100644 sound/pci/via82xx.c create mode 100644 sound/pci/via82xx_modem.c create mode 100644 sound/pci/vx222/Makefile create mode 100644 sound/pci/vx222/vx222.c create mode 100644 sound/pci/vx222/vx222.h create mode 100644 sound/pci/vx222/vx222_ops.c create mode 100644 sound/pci/ymfpci/Makefile create mode 100644 sound/pci/ymfpci/ymfpci.c create mode 100644 sound/pci/ymfpci/ymfpci.h create mode 100644 sound/pci/ymfpci/ymfpci_main.c (limited to 'sound/pci') diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig new file mode 100644 index 000000000..6366f72b3 --- /dev/null +++ b/sound/pci/Kconfig @@ -0,0 +1,936 @@ +# SPDX-License-Identifier: GPL-2.0-only +# ALSA PCI drivers + +menuconfig SND_PCI + bool "PCI sound devices" + depends on PCI + default y + help + Support for sound devices connected via the PCI bus. + +if SND_PCI + +config SND_AD1889 + tristate "Analog Devices AD1889" + select SND_AC97_CODEC + help + Say Y here to include support for the integrated AC97 sound + device found in particular on the Hewlett-Packard [BCJ]-xxx0 + class PA-RISC workstations, using the AD1819 codec. + + To compile this as a module, choose M here: the module + will be called snd-ad1889. + +config SND_ALS300 + tristate "Avance Logic ALS300/ALS300+" + select SND_PCM + select SND_AC97_CODEC + select SND_OPL3_LIB + depends on ZONE_DMA && HAS_IOPORT + help + Say 'Y' or 'M' to include support for Avance Logic ALS300/ALS300+ + + To compile this driver as a module, choose M here: the module + will be called snd-als300 + +config SND_ALS4000 + tristate "Avance Logic ALS4000" + depends on ISA_DMA_API + depends on HAS_IOPORT + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + select SND_SB_COMMON + help + Say Y here to include support for soundcards based on Avance Logic + ALS4000 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-als4000. + +config SND_ALI5451 + tristate "ALi M5451 PCI Audio Controller" + select SND_MPU401_UART + select SND_AC97_CODEC + depends on ZONE_DMA && HAS_IOPORT + help + Say Y here to include support for the integrated AC97 sound + device on motherboards using the ALi M5451 Audio Controller + (M1535/M1535D/M1535+/M1535D+ south bridges). Newer chipsets + use the "Intel/SiS/nVidia/AMD/ALi AC97 Controller" driver. + + To compile this driver as a module, choose M here: the module + will be called snd-ali5451. + +config SND_ASIHPI + tristate "AudioScience ASIxxxx" + depends on X86 + select FW_LOADER + select SND_PCM + select SND_HWDEP + help + Say Y here to include support for AudioScience ASI sound cards. + + To compile this driver as a module, choose M here: the module + will be called snd-asihpi. + +config SND_ATIIXP + tristate "ATI IXP AC97 Controller" + select SND_AC97_CODEC + help + Say Y here to include support for the integrated AC97 sound + device on motherboards with ATI chipsets (ATI IXP 150/200/250/ + 300/400). + + To compile this driver as a module, choose M here: the module + will be called snd-atiixp. + +config SND_ATIIXP_MODEM + tristate "ATI IXP Modem" + select SND_AC97_CODEC + help + Say Y here to include support for the integrated MC97 modem on + motherboards with ATI chipsets (ATI IXP 150/200/250). + + To compile this driver as a module, choose M here: the module + will be called snd-atiixp-modem. + +config SND_AU8810 + tristate "Aureal Advantage" + depends on HAS_IOPORT + select SND_MPU401_UART + select SND_AC97_CODEC + help + Say Y here to include support for Aureal Advantage soundcards. + + Supported features: Hardware Mixer, SRC, EQ and SPDIF output. + 3D support code is in place, but not yet useable. For more info, + email the ALSA developer list, or . + + To compile this driver as a module, choose M here: the module + will be called snd-au8810. + +config SND_AU8820 + tristate "Aureal Vortex" + depends on HAS_IOPORT + select SND_MPU401_UART + select SND_AC97_CODEC + help + Say Y here to include support for Aureal Vortex soundcards. + + Supported features: Hardware Mixer and SRC. For more info, email + the ALSA developer list, or . + + To compile this driver as a module, choose M here: the module + will be called snd-au8820. + +config SND_AU8830 + tristate "Aureal Vortex 2" + depends on HAS_IOPORT + select SND_MPU401_UART + select SND_AC97_CODEC + help + Say Y here to include support for Aureal Vortex 2 soundcards. + + Supported features: Hardware Mixer, SRC, EQ and SPDIF output. + 3D support code is in place, but not yet useable. For more info, + email the ALSA developer list, or . + + To compile this driver as a module, choose M here: the module + will be called snd-au8830. + +config SND_AW2 + tristate "Emagic Audiowerk 2" + help + Say Y here to include support for Emagic Audiowerk 2 soundcards. + + Supported features: Analog and SPDIF output. Analog or SPDIF input. + Note: Switch between analog and digital input does not always work. + It can produce continuous noise. The workaround is to switch again + (and again) between digital and analog input until it works. + + To compile this driver as a module, choose M here: the module + will be called snd-aw2. + + +config SND_AZT3328 + tristate "Aztech AZF3328 / PCI168" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + select SND_RAWMIDI + select SND_AC97_CODEC + select SND_TIMER + depends on ZONE_DMA && HAS_IOPORT + help + Say Y here to include support for Aztech AZF3328 (PCI168) + soundcards. + + Supported features: AC97-"conformant" mixer, MPU401/OPL3, analog I/O + (16bit/8bit, many sample rates [<= 66.2kHz], NO hardware mixing), + Digital Enhanced Game Port, 1.024MHz multimedia sequencer timer, + ext. codec (I2S port), onboard amp (4W/4Ohms/ch), suspend/resume. + + To compile this driver as a module, choose M here: the module + will be called snd-azt3328. + +config SND_BT87X + tristate "Bt87x Audio Capture" + select SND_PCM + help + If you want to record audio from TV cards based on + Brooktree Bt878/Bt879 chips, say Y here and read + . + + To compile this driver as a module, choose M here: the module + will be called snd-bt87x. + +config SND_BT87X_OVERCLOCK + bool "Bt87x Audio overclocking" + depends on SND_BT87X + help + Say Y here if 448000 Hz isn't enough for you and you want to + record from the analog input with up to 1792000 Hz. + + Higher sample rates won't hurt your hardware, but audio + quality may suffer. + +config SND_CA0106 + tristate "SB Audigy LS / Live 24bit" + depends on HAS_IOPORT + select SND_AC97_CODEC + select SND_RAWMIDI + select SND_VMASTER + help + Say Y here to include support for the Sound Blaster Audigy LS + and Live 24bit. + + To compile this driver as a module, choose M here: the module + will be called snd-ca0106. + +config SND_CMIPCI + tristate "C-Media 8338, 8738, 8768, 8770" + depends on HAS_IOPORT + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + If you want to use soundcards based on C-Media CMI8338, CMI8738, + CMI8768 or CMI8770 chips, say Y here and read + . + + To compile this driver as a module, choose M here: the module + will be called snd-cmipci. + +config SND_OXYGEN_LIB + tristate + +config SND_OXYGEN + tristate "C-Media 8786, 8787, 8788 (Oxygen)" + depends on HAS_IOPORT + select SND_OXYGEN_LIB + select SND_PCM + select SND_MPU401_UART + help + Say Y here to include support for sound cards based on the + C-Media CMI8788 (Oxygen HD Audio) chip: + * Asound A-8788 + * Asus Xonar DG/DGX + * AuzenTech X-Meridian + * AuzenTech X-Meridian 2G + * Bgears b-Enspirer + * Club3D Theatron DTS + * HT-Omega Claro (plus) + * HT-Omega Claro halo (XT) + * Kuroutoshikou CMI8787-HG2PCI + * Razer Barracuda AC-1 + * Sondigo Inferno + * TempoTec/MediaTek HiFier Fantasia + * TempoTec/MediaTek HiFier Serenade + + To compile this driver as a module, choose M here: the module + will be called snd-oxygen. + +config SND_CS4281 + tristate "Cirrus Logic (Sound Fusion) CS4281" + depends on HAS_IOPORT + select SND_OPL3_LIB + select SND_RAWMIDI + select SND_AC97_CODEC + help + Say Y here to include support for Cirrus Logic CS4281 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-cs4281. + +config SND_CS46XX + tristate "Cirrus Logic (Sound Fusion) CS4280/CS461x/CS462x/CS463x" + depends on HAS_IOPORT + select SND_RAWMIDI + select SND_AC97_CODEC + select FW_LOADER + help + Say Y here to include support for Cirrus Logic CS4610/CS4612/ + CS4614/CS4615/CS4622/CS4624/CS4630/CS4280 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-cs46xx. + +config SND_CS46XX_NEW_DSP + bool "Cirrus Logic (Sound Fusion) New DSP support" + depends on SND_CS46XX + default y + help + Say Y here to use a new DSP image for SPDIF and dual codecs. + + This works better than the old code, so say Y. + +config SND_CS5530 + tristate "CS5530 Audio" + depends on ISA_DMA_API && (X86_32 || COMPILE_TEST) + depends on !M68K + select SND_SB16_DSP + help + Say Y here to include support for audio on Cyrix/NatSemi CS5530 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-cs5530. + +config SND_CS5535AUDIO + tristate "CS5535/CS5536 Audio" + depends on X86_32 || MIPS || COMPILE_TEST + depends on HAS_IOPORT + depends on GPIOLIB_LEGACY || !OLPC + select SND_PCM + select SND_AC97_CODEC + help + Say Y here to include support for audio on CS5535 chips. It is + referred to as NS CS5535 IO or AMD CS5535 IO companion in + various literature. This driver also supports the CS5536 audio + device. However, for both chips, on certain boards, you may + need to use ac97_quirk=hp_only if your board has physically + mapped headphone out to master output. If that works for you, + send lspci -vvv output to the mailing list so that your board + can be identified in the quirks list. + + To compile this driver as a module, choose M here: the module + will be called snd-cs5535audio. + +config SND_CTXFI + tristate "Creative Sound Blaster X-Fi" + depends on HAS_IOPORT + select SND_PCM + help + If you want to use soundcards based on Creative Sound Blastr X-Fi + boards with 20k1 or 20k2 chips, say Y here. + + To compile this driver as a module, choose M here: the module + will be called snd-ctxfi. + +config SND_DARLA20 + tristate "(Echoaudio) Darla20" + select FW_LOADER + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Darla. + + To compile this driver as a module, choose M here: the module + will be called snd-darla20 + +config SND_GINA20 + tristate "(Echoaudio) Gina20" + select FW_LOADER + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Gina. + + To compile this driver as a module, choose M here: the module + will be called snd-gina20 + +config SND_LAYLA20 + tristate "(Echoaudio) Layla20" + select FW_LOADER + select SND_RAWMIDI + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Layla. + + To compile this driver as a module, choose M here: the module + will be called snd-layla20 + +config SND_DARLA24 + tristate "(Echoaudio) Darla24" + select FW_LOADER + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Darla24. + + To compile this driver as a module, choose M here: the module + will be called snd-darla24 + +config SND_GINA24 + tristate "(Echoaudio) Gina24" + select FW_LOADER + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Gina24. + + To compile this driver as a module, choose M here: the module + will be called snd-gina24 + +config SND_LAYLA24 + tristate "(Echoaudio) Layla24" + select FW_LOADER + select SND_RAWMIDI + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Layla24. + + To compile this driver as a module, choose M here: the module + will be called snd-layla24 + +config SND_MONA + tristate "(Echoaudio) Mona" + select FW_LOADER + select SND_RAWMIDI + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Mona. + + To compile this driver as a module, choose M here: the module + will be called snd-mona + +config SND_MIA + tristate "(Echoaudio) Mia" + select FW_LOADER + select SND_RAWMIDI + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Mia and Mia-midi. + + To compile this driver as a module, choose M here: the module + will be called snd-mia + +config SND_ECHO3G + tristate "(Echoaudio) 3G cards" + select FW_LOADER + select SND_RAWMIDI + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Gina3G and Layla3G. + + To compile this driver as a module, choose M here: the module + will be called snd-echo3g + +config SND_INDIGO + tristate "(Echoaudio) Indigo" + select FW_LOADER + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Indigo. + + To compile this driver as a module, choose M here: the module + will be called snd-indigo + +config SND_INDIGOIO + tristate "(Echoaudio) Indigo IO" + select FW_LOADER + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Indigo IO. + + To compile this driver as a module, choose M here: the module + will be called snd-indigoio + +config SND_INDIGODJ + tristate "(Echoaudio) Indigo DJ" + select FW_LOADER + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Indigo DJ. + + To compile this driver as a module, choose M here: the module + will be called snd-indigodj + +config SND_INDIGOIOX + tristate "(Echoaudio) Indigo IOx" + select FW_LOADER + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Indigo IOx. + + To compile this driver as a module, choose M here: the module + will be called snd-indigoiox + +config SND_INDIGODJX + tristate "(Echoaudio) Indigo DJx" + select FW_LOADER + select SND_PCM + help + Say 'Y' or 'M' to include support for Echoaudio Indigo DJx. + + To compile this driver as a module, choose M here: the module + will be called snd-indigodjx + +config SND_EMU10K1 + tristate "Emu10k1 (SB Live!, Audigy, E-MU APS/0404/1010/1212/1616/1820)" + select FW_LOADER + select SND_HWDEP + select SND_RAWMIDI + select SND_AC97_CODEC + select SND_TIMER + select SND_SEQ_DEVICE if SND_SEQUENCER != n + depends on ZONE_DMA && HAS_IOPORT + help + Say Y to include support for Sound Blaster PCI 512, Live!, + Audigy and E-MU APS/0404/1010/1212/1616/1820 soundcards. + + The confusing multitude of mixer controls is documented in + and + . + + To compile this driver as a module, choose M here: the module + will be called snd-emu10k1. + +# select SEQ stuff to min(SND_SEQUENCER,SND_XXX) +config SND_EMU10K1_SEQ + def_tristate SND_SEQUENCER && SND_EMU10K1 + select SND_SEQ_MIDI_EMUL + select SND_SEQ_VIRMIDI + select SND_SYNTH_EMUX + +config SND_EMU10K1X + tristate "Emu10k1X (Dell OEM Version)" + select SND_AC97_CODEC + select SND_RAWMIDI + depends on ZONE_DMA && HAS_IOPORT + help + Say Y here to include support for the Dell OEM version of the + Sound Blaster Live!. + + To compile this driver as a module, choose M here: the module + will be called snd-emu10k1x. + +config SND_ENS1370 + tristate "(Creative) Ensoniq AudioPCI 1370" + depends on HAS_IOPORT + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for Ensoniq AudioPCI ES1370 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-ens1370. + +config SND_ENS1371 + tristate "(Creative) Ensoniq AudioPCI 1371/1373" + depends on HAS_IOPORT + select SND_RAWMIDI + select SND_AC97_CODEC + help + Say Y here to include support for Ensoniq AudioPCI ES1371 chips and + Sound Blaster PCI 64 or 128 soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-ens1371. + +config SND_ES1938 + tristate "ESS ES1938/1946/1969 (Solo-1)" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_AC97_CODEC + depends on ZONE_DMA && HAS_IOPORT + help + Say Y here to include support for soundcards based on ESS Solo-1 + (ES1938, ES1946, ES1969) chips. + + To compile this driver as a module, choose M here: the module + will be called snd-es1938. + +config SND_ES1968 + tristate "ESS ES1968/1978 (Maestro-1/2/2E)" + select SND_MPU401_UART + select SND_AC97_CODEC + depends on ZONE_DMA && HAS_IOPORT + help + Say Y here to include support for soundcards based on ESS Maestro + 1/2/2E chips. + + To compile this driver as a module, choose M here: the module + will be called snd-es1968. + +config SND_ES1968_INPUT + bool "Enable input device for es1968 volume buttons" + depends on SND_ES1968 + depends on INPUT=y || INPUT=SND_ES1968 + help + If you say Y here, you will get an input device which reports + keypresses for the volume buttons connected to the es1968 chip. + If you say N the buttons will directly control the master volume. + It is recommended to say Y. + +config SND_ES1968_RADIO + bool "Enable TEA5757 radio tuner support for es1968" + depends on SND_ES1968 + depends on MEDIA_RADIO_SUPPORT + depends on VIDEO_DEV=y || VIDEO_DEV=SND_ES1968 + select RADIO_ADAPTERS + select RADIO_TEA575X + + help + Say Y here to include support for TEA5757 radio tuner integrated on + some MediaForte cards (e.g. SF64-PCE2). + +config SND_FM801 + tristate "ForteMedia FM801" + depends on HAS_IOPORT + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_AC97_CODEC + help + Say Y here to include support for soundcards based on the ForteMedia + FM801 chip. + + To compile this driver as a module, choose M here: the module + will be called snd-fm801. + +config SND_FM801_TEA575X_BOOL + bool "ForteMedia FM801 + TEA5757 tuner" + depends on SND_FM801 + depends on MEDIA_RADIO_SUPPORT + depends on VIDEO_DEV=y || VIDEO_DEV=SND_FM801 + select RADIO_ADAPTERS + select RADIO_TEA575X + help + Say Y here to include support for soundcards based on the ForteMedia + FM801 chip with a TEA5757 tuner (MediaForte SF256-PCS, SF256-PCP and + SF64-PCR) into the snd-fm801 driver. + +config SND_HDSP + tristate "RME Hammerfall DSP Audio" + select FW_LOADER + select SND_HWDEP + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for RME Hammerfall DSP Audio + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-hdsp. + +comment "Don't forget to add built-in firmwares for HDSP driver" + depends on SND_HDSP=y + +config SND_HDSPM + tristate "RME Hammerfall DSP MADI/RayDAT/AIO" + select SND_HWDEP + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for RME Hammerfall DSP MADI, + RayDAT and AIO soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-hdspm. + +config SND_ICE1712 + tristate "ICEnsemble ICE1712 (Envy24)" + select SND_MPU401_UART + select SND_AC97_CODEC + select BITREVERSE + depends on ZONE_DMA && HAS_IOPORT + help + Say Y here to include support for soundcards based on the + ICE1712 (Envy24) chip. + + Currently supported hardware is: M-Audio Delta 1010(LT), + DiO 2496, 66, 44, 410, Audiophile 24/96; Digigram VX442; + TerraTec EWX 24/96, EWS 88MT/D, DMX 6Fire, Phase 88; + Hoontech SoundTrack DSP 24/Value/Media7.1; Event EZ8; + Lionstracs Mediastation, Terrasoniq TS 88. + + To compile this driver as a module, choose M here: the module + will be called snd-ice1712. + +config SND_ICE1724 + tristate "ICE/VT1724/1720 (Envy24HT/PT)" + depends on HAS_IOPORT + select SND_RAWMIDI + select SND_AC97_CODEC + select SND_VMASTER + help + Say Y here to include support for soundcards based on + ICE/VT1724/1720 (Envy24HT/PT) chips. + + Currently supported hardware is: AMP AUDIO2000; M-Audio + Revolution 5.1, 7.1, Audiophile 192; TerraTec Aureon 5.1 Sky, + 7.1 Space/Universe, Phase 22/28; Onkyo SE-90PCI, SE-200PCI; + AudioTrak Prodigy 192, 7.1 (HIFI/LT/XT), HD2; Hercules + Fortissimo IV; ESI Juli@; Pontis MS300; EGO-SYS WaveTerminal + 192M; Albatron K8X800 Pro II; Chaintech ZNF3-150/250, 9CJS, + AV-710; Shuttle SN25P; Philips PSC724 Ultimate Edge. + + To compile this driver as a module, choose M here: the module + will be called snd-ice1724. + +config SND_INTEL8X0 + tristate "Intel/SiS/nVidia/AMD/ALi AC97 Controller" + select SND_AC97_CODEC + help + Say Y here to include support for the integrated AC97 sound + device on motherboards with Intel/SiS/nVidia/AMD chipsets, or + ALi chipsets using the M5455 Audio Controller. (There is a + separate driver for ALi M5451 Audio Controllers.) + + To compile this driver as a module, choose M here: the module + will be called snd-intel8x0. + +config SND_INTEL8X0M + tristate "Intel/SiS/nVidia/AMD MC97 Modem" + select SND_AC97_CODEC + help + Say Y here to include support for the integrated MC97 modem on + motherboards with Intel/SiS/nVidia/AMD chipsets. + + To compile this driver as a module, choose M here: the module + will be called snd-intel8x0m. + +config SND_KORG1212 + tristate "Korg 1212 IO" + select SND_PCM + help + Say Y here to include support for Korg 1212IO soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-korg1212. + +config SND_LOLA + tristate "Digigram Lola" + select SND_PCM + help + Say Y to include support for Digigram Lola boards. + + To compile this driver as a module, choose M here: the module + will be called snd-lola. + +config SND_LX6464ES + tristate "Digigram LX6464ES" + depends on HAS_IOPORT_MAP + select SND_PCM + help + Say Y here to include support for Digigram LX6464ES boards. + + To compile this driver as a module, choose M here: the module + will be called snd-lx6464es. + + +config SND_MAESTRO3 + tristate "ESS Allegro/Maestro3" + select SND_AC97_CODEC + depends on ZONE_DMA && HAS_IOPORT + help + Say Y here to include support for soundcards based on ESS Maestro 3 + (Allegro) chips. + + To compile this driver as a module, choose M here: the module + will be called snd-maestro3. + +config SND_MAESTRO3_INPUT + bool "Enable input device for maestro3 volume buttons" + depends on SND_MAESTRO3 + depends on INPUT=y || INPUT=SND_MAESTRO3 + help + If you say Y here, you will get an input device which reports + keypresses for the volume buttons connected to the maestro3 chip. + If you say N the buttons will directly control the master volume. + It is recommended to say Y. + +config SND_MIXART + tristate "Digigram miXart" + select FW_LOADER + select SND_HWDEP + select SND_PCM + help + If you want to use Digigram miXart soundcards, say Y here and + read . + + To compile this driver as a module, choose M here: the module + will be called snd-mixart. + +config SND_NM256 + tristate "NeoMagic NM256AV/ZX" + select SND_AC97_CODEC + help + Say Y here to include support for NeoMagic NM256AV/ZX chips. + + To compile this driver as a module, choose M here: the module + will be called snd-nm256. + +config SND_PCXHR + tristate "Digigram PCXHR" + depends on HAS_IOPORT + select FW_LOADER + select SND_PCM + select SND_HWDEP + help + Say Y here to include support for Digigram PCXHR boards. + + To compile this driver as a module, choose M here: the module + will be called snd-pcxhr. + +config SND_RIPTIDE + tristate "Conexant Riptide" + depends on HAS_IOPORT + select FW_LOADER + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_AC97_CODEC + help + Say 'Y' or 'M' to include support for Conexant Riptide chip. + + To compile this driver as a module, choose M here: the module + will be called snd-riptide + +config SND_RME32 + tristate "RME Digi32, 32/8, 32 PRO" + select SND_PCM + help + Say Y to include support for RME Digi32, Digi32 PRO and + Digi32/8 (Sek'd Prodif32, Prodif96 and Prodif Gold) audio + devices. + + To compile this driver as a module, choose M here: the module + will be called snd-rme32. + +config SND_RME96 + tristate "RME Digi96, 96/8, 96/8 PRO" + select SND_PCM + help + Say Y here to include support for RME Digi96, Digi96/8 and + Digi96/8 PRO/PAD/PST soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-rme96. + +config SND_RME9652 + tristate "RME Digi9652 (Hammerfall)" + select SND_PCM + help + Say Y here to include support for RME Hammerfall (RME + Digi9652/Digi9636) soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-rme9652. + +config SND_SE6X + tristate "Studio Evolution SE6X" + depends on SND_OXYGEN=n && SND_VIRTUOSO=n # PCI ID conflict + depends on HAS_IOPORT + select SND_OXYGEN_LIB + select SND_PCM + select SND_MPU401_UART + help + Say Y or M here only if you actually have this sound card. + +config SND_SIS7019 + tristate "SiS 7019 Audio Accelerator" + depends on X86_32 + select SND_AC97_CODEC + depends on ZONE_DMA + help + Say Y here to include support for the SiS 7019 Audio Accelerator. + + To compile this driver as a module, choose M here: the module + will be called snd-sis7019. + +config SND_SONICVIBES + tristate "S3 SonicVibes" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_AC97_CODEC + depends on ZONE_DMA && HAS_IOPORT + help + Say Y here to include support for soundcards based on the S3 + SonicVibes chip. + + To compile this driver as a module, choose M here: the module + will be called snd-sonicvibes. + +config SND_TRIDENT + tristate "Trident 4D-Wave DX/NX; SiS 7018" + select SND_MPU401_UART + select SND_AC97_CODEC + depends on ZONE_DMA && HAS_IOPORT + help + Say Y here to include support for soundcards based on Trident + 4D-Wave DX/NX or SiS 7018 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-trident. + +config SND_VIA82XX + tristate "VIA 82C686A/B, 8233/8235 AC97 Controller" + depends on HAS_IOPORT + select SND_MPU401_UART + select SND_AC97_CODEC + help + Say Y here to include support for the integrated AC97 sound + device on motherboards with VIA chipsets. + + To compile this driver as a module, choose M here: the module + will be called snd-via82xx. + +config SND_VIA82XX_MODEM + tristate "VIA 82C686A/B, 8233 based Modems" + depends on HAS_IOPORT + select SND_AC97_CODEC + help + Say Y here to include support for the integrated MC97 modem on + motherboards with VIA chipsets. + + To compile this driver as a module, choose M here: the module + will be called snd-via82xx-modem. + +config SND_VIRTUOSO + tristate "Asus Virtuoso 66/100/200 (Xonar)" + depends on HAS_IOPORT + select SND_OXYGEN_LIB + select SND_PCM + select SND_MPU401_UART + select SND_JACK + help + Say Y here to include support for sound cards based on the + Asus AV66/AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X, DS, DSX, + Essence ST (Deluxe), and Essence STX (II). + Support for the HDAV1.3 (Deluxe) and HDAV1.3 Slim is experimental; + for the Xense, missing. + + To compile this driver as a module, choose M here: the module + will be called snd-virtuoso. + +config SND_VX222 + tristate "Digigram VX222" + depends on HAS_IOPORT + select SND_VX_LIB + help + Say Y here to include support for Digigram VX222 soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-vx222. + +config SND_YMFPCI + tristate "Yamaha YMF724/740/744/754" + depends on HAS_IOPORT + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_AC97_CODEC + select SND_TIMER + help + Say Y here to include support for Yamaha PCI audio chips - + YMF724, YMF724F, YMF740, YMF740C, YMF744, YMF754. + + To compile this driver as a module, choose M here: the module + will be called snd-ymfpci. + +endif # SND_PCI diff --git a/sound/pci/Makefile b/sound/pci/Makefile new file mode 100644 index 000000000..9d5e8e12a --- /dev/null +++ b/sound/pci/Makefile @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-ad1889-y := ad1889.o +snd-als300-y := als300.o +snd-als4000-y := als4000.o +snd-atiixp-y := atiixp.o +snd-atiixp-modem-y := atiixp_modem.o +snd-azt3328-y := azt3328.o +snd-bt87x-y := bt87x.o +snd-cmipci-y := cmipci.o +snd-cs4281-y := cs4281.o +snd-cs5530-y := cs5530.o +snd-ens1370-y := ens1370.o ak4531_codec.o +snd-ens1371-y := ens1371.o +snd-es1938-y := es1938.o +snd-es1968-y := es1968.o +snd-fm801-y := fm801.o +snd-intel8x0-y := intel8x0.o +snd-intel8x0m-y := intel8x0m.o +snd-maestro3-y := maestro3.o +snd-rme32-y := rme32.o +snd-rme96-y := rme96.o +snd-sis7019-y := sis7019.o +snd-sonicvibes-y := sonicvibes.o +snd-via82xx-y := via82xx.o +snd-via82xx-modem-y := via82xx_modem.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_AD1889) += snd-ad1889.o +obj-$(CONFIG_SND_ALS300) += snd-als300.o +obj-$(CONFIG_SND_ALS4000) += snd-als4000.o +obj-$(CONFIG_SND_ATIIXP) += snd-atiixp.o +obj-$(CONFIG_SND_ATIIXP_MODEM) += snd-atiixp-modem.o +obj-$(CONFIG_SND_AZT3328) += snd-azt3328.o +obj-$(CONFIG_SND_BT87X) += snd-bt87x.o +obj-$(CONFIG_SND_CMIPCI) += snd-cmipci.o +obj-$(CONFIG_SND_CS4281) += snd-cs4281.o +obj-$(CONFIG_SND_CS5530) += snd-cs5530.o +obj-$(CONFIG_SND_ENS1370) += snd-ens1370.o +obj-$(CONFIG_SND_ENS1371) += snd-ens1371.o +obj-$(CONFIG_SND_ES1938) += snd-es1938.o +obj-$(CONFIG_SND_ES1968) += snd-es1968.o +obj-$(CONFIG_SND_FM801) += snd-fm801.o +obj-$(CONFIG_SND_INTEL8X0) += snd-intel8x0.o +obj-$(CONFIG_SND_INTEL8X0M) += snd-intel8x0m.o +obj-$(CONFIG_SND_MAESTRO3) += snd-maestro3.o +obj-$(CONFIG_SND_RME32) += snd-rme32.o +obj-$(CONFIG_SND_RME96) += snd-rme96.o +obj-$(CONFIG_SND_SIS7019) += snd-sis7019.o +obj-$(CONFIG_SND_SONICVIBES) += snd-sonicvibes.o +obj-$(CONFIG_SND_VIA82XX) += snd-via82xx.o +obj-$(CONFIG_SND_VIA82XX_MODEM) += snd-via82xx-modem.o + +obj-$(CONFIG_SND) += \ + ac97/ \ + ali5451/ \ + asihpi/ \ + au88x0/ \ + aw2/ \ + ctxfi/ \ + ca0106/ \ + cs46xx/ \ + cs5535audio/ \ + lola/ \ + lx6464es/ \ + echoaudio/ \ + emu10k1/ \ + ice1712/ \ + korg1212/ \ + mixart/ \ + nm256/ \ + oxygen/ \ + pcxhr/ \ + riptide/ \ + rme9652/ \ + trident/ \ + ymfpci/ \ + vx222/ diff --git a/sound/pci/ac97/Makefile b/sound/pci/ac97/Makefile new file mode 100644 index 000000000..c74e769f3 --- /dev/null +++ b/sound/pci/ac97/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-ac97-codec-y := ac97_codec.o ac97_pcm.o +snd-ac97-codec-$(CONFIG_SND_PROC_FS) += ac97_proc.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_AC97_CODEC) += snd-ac97-codec.o diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c new file mode 100644 index 000000000..0bb65be02 --- /dev/null +++ b/sound/pci/ac97/ac97_codec.c @@ -0,0 +1,2996 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ac97_id.h" + +#include "ac97_patch.c" + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Universal interface for Audio Codec '97"); +MODULE_LICENSE("GPL"); + +static bool enable_loopback; + +module_param(enable_loopback, bool, 0444); +MODULE_PARM_DESC(enable_loopback, "Enable AC97 ADC/DAC Loopback Control"); + +#ifdef CONFIG_SND_AC97_POWER_SAVE +static int power_save = CONFIG_SND_AC97_POWER_SAVE_DEFAULT; +module_param(power_save, int, 0644); +MODULE_PARM_DESC(power_save, "Automatic power-saving timeout " + "(in second, 0 = disable)."); +#endif +/* + + */ + +struct ac97_codec_id { + unsigned int id; + unsigned int mask; + const char *name; + int (*patch)(struct snd_ac97 *ac97); + int (*mpatch)(struct snd_ac97 *ac97); + unsigned int flags; +}; + +static const struct ac97_codec_id snd_ac97_codec_id_vendors[] = { +{ 0x41445300, 0xffffff00, "Analog Devices", NULL, NULL }, +{ 0x414b4d00, 0xffffff00, "Asahi Kasei", NULL, NULL }, +{ 0x414c4300, 0xffffff00, "Realtek", NULL, NULL }, +{ 0x414c4700, 0xffffff00, "Realtek", NULL, NULL }, +/* + * This is an _inofficial_ Aztech Labs entry + * (value might differ from unknown official Aztech ID), + * currently used by the AC97 emulation of the almost-AC97 PCI168 card. + */ +{ 0x415a5400, 0xffffff00, "Aztech Labs (emulated)", NULL, NULL }, +{ 0x434d4900, 0xffffff00, "C-Media Electronics", NULL, NULL }, +{ 0x43525900, 0xffffff00, "Cirrus Logic", NULL, NULL }, +{ 0x43585400, 0xffffff00, "Conexant", NULL, NULL }, +{ 0x44543000, 0xffffff00, "Diamond Technology", NULL, NULL }, +{ 0x454d4300, 0xffffff00, "eMicro", NULL, NULL }, +{ 0x45838300, 0xffffff00, "ESS Technology", NULL, NULL }, +{ 0x48525300, 0xffffff00, "Intersil", NULL, NULL }, +{ 0x49434500, 0xffffff00, "ICEnsemble", NULL, NULL }, +{ 0x49544500, 0xffffff00, "ITE Tech.Inc", NULL, NULL }, +{ 0x4e534300, 0xffffff00, "National Semiconductor", NULL, NULL }, +{ 0x50534300, 0xffffff00, "Philips", NULL, NULL }, +{ 0x53494c00, 0xffffff00, "Silicon Laboratory", NULL, NULL }, +{ 0x53544d00, 0xffffff00, "STMicroelectronics", NULL, NULL }, +{ 0x54524100, 0xffffff00, "TriTech", NULL, NULL }, +{ 0x54584e00, 0xffffff00, "Texas Instruments", NULL, NULL }, +{ 0x56494100, 0xffffff00, "VIA Technologies", NULL, NULL }, +{ 0x57454300, 0xffffff00, "Winbond", NULL, NULL }, +{ 0x574d4c00, 0xffffff00, "Wolfson", NULL, NULL }, +{ 0x594d4800, 0xffffff00, "Yamaha", NULL, NULL }, +{ 0x83847600, 0xffffff00, "SigmaTel", NULL, NULL }, +{ 0, 0, NULL, NULL, NULL } +}; + +static const struct ac97_codec_id snd_ac97_codec_ids[] = { +{ 0x41445303, 0xffffffff, "AD1819", patch_ad1819, NULL }, +{ 0x41445340, 0xffffffff, "AD1881", patch_ad1881, NULL }, +{ 0x41445348, 0xffffffff, "AD1881A", patch_ad1881, NULL }, +{ 0x41445360, 0xffffffff, "AD1885", patch_ad1885, NULL }, +{ 0x41445361, 0xffffffff, "AD1886", patch_ad1886, NULL }, +{ 0x41445362, 0xffffffff, "AD1887", patch_ad1881, NULL }, +{ 0x41445363, 0xffffffff, "AD1886A", patch_ad1881, NULL }, +{ 0x41445368, 0xffffffff, "AD1888", patch_ad1888, NULL }, +{ 0x41445370, 0xffffffff, "AD1980", patch_ad1980, NULL }, +{ 0x41445372, 0xffffffff, "AD1981A", patch_ad1981a, NULL }, +{ 0x41445374, 0xffffffff, "AD1981B", patch_ad1981b, NULL }, +{ 0x41445375, 0xffffffff, "AD1985", patch_ad1985, NULL }, +{ 0x41445378, 0xffffffff, "AD1986", patch_ad1986, NULL }, +{ 0x414b4d00, 0xffffffff, "AK4540", NULL, NULL }, +{ 0x414b4d01, 0xffffffff, "AK4542", NULL, NULL }, +{ 0x414b4d02, 0xffffffff, "AK4543", NULL, NULL }, +{ 0x414b4d06, 0xffffffff, "AK4544A", NULL, NULL }, +{ 0x414b4d07, 0xffffffff, "AK4545", NULL, NULL }, +{ 0x414c4300, 0xffffff00, "ALC100,100P", NULL, NULL }, +{ 0x414c4710, 0xfffffff0, "ALC200,200P", NULL, NULL }, +{ 0x414c4721, 0xffffffff, "ALC650D", NULL, NULL }, /* already patched */ +{ 0x414c4722, 0xffffffff, "ALC650E", NULL, NULL }, /* already patched */ +{ 0x414c4723, 0xffffffff, "ALC650F", NULL, NULL }, /* already patched */ +{ 0x414c4720, 0xfffffff0, "ALC650", patch_alc650, NULL }, +{ 0x414c4730, 0xffffffff, "ALC101", NULL, NULL }, +{ 0x414c4740, 0xfffffff0, "ALC202", NULL, NULL }, +{ 0x414c4750, 0xfffffff0, "ALC250", NULL, NULL }, +{ 0x414c4760, 0xfffffff0, "ALC655", patch_alc655, NULL }, +{ 0x414c4770, 0xfffffff0, "ALC203", patch_alc203, NULL }, +{ 0x414c4781, 0xffffffff, "ALC658D", NULL, NULL }, /* already patched */ +{ 0x414c4780, 0xfffffff0, "ALC658", patch_alc655, NULL }, +{ 0x414c4790, 0xfffffff0, "ALC850", patch_alc850, NULL }, +{ 0x415a5401, 0xffffffff, "AZF3328", patch_aztech_azf3328, NULL }, +{ 0x434d4941, 0xffffffff, "CMI9738", patch_cm9738, NULL }, +{ 0x434d4961, 0xffffffff, "CMI9739", patch_cm9739, NULL }, +{ 0x434d4969, 0xffffffff, "CMI9780", patch_cm9780, NULL }, +{ 0x434d4978, 0xffffffff, "CMI9761A", patch_cm9761, NULL }, +{ 0x434d4982, 0xffffffff, "CMI9761B", patch_cm9761, NULL }, +{ 0x434d4983, 0xffffffff, "CMI9761A+", patch_cm9761, NULL }, +{ 0x43525900, 0xfffffff8, "CS4297", NULL, NULL }, +{ 0x43525910, 0xfffffff8, "CS4297A", patch_cirrus_spdif, NULL }, +{ 0x43525920, 0xfffffff8, "CS4298", patch_cirrus_spdif, NULL }, +{ 0x43525928, 0xfffffff8, "CS4294", NULL, NULL }, +{ 0x43525930, 0xfffffff8, "CS4299", patch_cirrus_cs4299, NULL }, +{ 0x43525948, 0xfffffff8, "CS4201", NULL, NULL }, +{ 0x43525958, 0xfffffff8, "CS4205", patch_cirrus_spdif, NULL }, +{ 0x43525960, 0xfffffff8, "CS4291", NULL, NULL }, +{ 0x43525970, 0xfffffff8, "CS4202", NULL, NULL }, +{ 0x43585421, 0xffffffff, "HSD11246", NULL, NULL }, // SmartMC II +{ 0x43585428, 0xfffffff8, "Cx20468", patch_conexant, NULL }, // SmartAMC fixme: the mask might be different +{ 0x43585430, 0xffffffff, "Cx20468-31", patch_conexant, NULL }, +{ 0x43585431, 0xffffffff, "Cx20551", patch_cx20551, NULL }, +{ 0x44543031, 0xfffffff0, "DT0398", NULL, NULL }, +{ 0x454d4328, 0xffffffff, "EM28028", NULL, NULL }, // same as TR28028? +{ 0x45838308, 0xffffffff, "ESS1988", NULL, NULL }, +{ 0x48525300, 0xffffff00, "HMP9701", NULL, NULL }, +{ 0x49434501, 0xffffffff, "ICE1230", NULL, NULL }, +{ 0x49434511, 0xffffffff, "ICE1232", NULL, NULL }, // alias VIA VT1611A? +{ 0x49434514, 0xffffffff, "ICE1232A", NULL, NULL }, +{ 0x49434551, 0xffffffff, "VT1616", patch_vt1616, NULL }, +{ 0x49434552, 0xffffffff, "VT1616i", patch_vt1616, NULL }, // VT1616 compatible (chipset integrated) +{ 0x49544520, 0xffffffff, "IT2226E", NULL, NULL }, +{ 0x49544561, 0xffffffff, "IT2646E", patch_it2646, NULL }, +{ 0x4e534300, 0xffffffff, "LM4540,43,45,46,48", NULL, NULL }, // only guess --jk +{ 0x4e534331, 0xffffffff, "LM4549", NULL, NULL }, +{ 0x4e534350, 0xffffffff, "LM4550", patch_lm4550, NULL }, // volume wrap fix +{ 0x53494c20, 0xffffffe0, "Si3036,8", mpatch_si3036, mpatch_si3036, AC97_MODEM_PATCH }, +{ 0x53544d02, 0xffffffff, "ST7597", NULL, NULL }, +{ 0x54524102, 0xffffffff, "TR28022", NULL, NULL }, +{ 0x54524103, 0xffffffff, "TR28023", NULL, NULL }, +{ 0x54524106, 0xffffffff, "TR28026", NULL, NULL }, +{ 0x54524108, 0xffffffff, "TR28028", patch_tritech_tr28028, NULL }, // added by xin jin [07/09/99] +{ 0x54524123, 0xffffffff, "TR28602", NULL, NULL }, // only guess --jk [TR28023 = eMicro EM28023 (new CT1297)] +{ 0x54584e03, 0xffffffff, "TLV320AIC27", NULL, NULL }, +{ 0x54584e20, 0xffffffff, "TLC320AD9xC", NULL, NULL }, +{ 0x56494120, 0xfffffff0, "VIA1613", patch_vt1613, NULL }, +{ 0x56494161, 0xffffffff, "VIA1612A", NULL, NULL }, // modified ICE1232 with S/PDIF +{ 0x56494170, 0xffffffff, "VIA1617A", patch_vt1617a, NULL }, // modified VT1616 with S/PDIF +{ 0x56494182, 0xffffffff, "VIA1618", patch_vt1618, NULL }, +{ 0x57454301, 0xffffffff, "W83971D", NULL, NULL }, +{ 0x574d4c00, 0xffffffff, "WM9701,WM9701A", NULL, NULL }, +{ 0x574d4C03, 0xffffffff, "WM9703,WM9707,WM9708,WM9717", patch_wolfson03, NULL}, +{ 0x574d4C04, 0xffffffff, "WM9704M,WM9704Q", patch_wolfson04, NULL}, +{ 0x574d4C05, 0xffffffff, "WM9705,WM9710", patch_wolfson05, NULL}, +{ 0x574d4C09, 0xffffffff, "WM9709", NULL, NULL}, +{ 0x574d4C12, 0xffffffff, "WM9711,WM9712,WM9715", patch_wolfson11, NULL}, +{ 0x574d4c13, 0xffffffff, "WM9713,WM9714", patch_wolfson13, NULL, AC97_DEFAULT_POWER_OFF}, +{ 0x594d4800, 0xffffffff, "YMF743", patch_yamaha_ymf743, NULL }, +{ 0x594d4802, 0xffffffff, "YMF752", NULL, NULL }, +{ 0x594d4803, 0xffffffff, "YMF753", patch_yamaha_ymf753, NULL }, +{ 0x83847600, 0xffffffff, "STAC9700,83,84", patch_sigmatel_stac9700, NULL }, +{ 0x83847604, 0xffffffff, "STAC9701,3,4,5", NULL, NULL }, +{ 0x83847605, 0xffffffff, "STAC9704", NULL, NULL }, +{ 0x83847608, 0xffffffff, "STAC9708,11", patch_sigmatel_stac9708, NULL }, +{ 0x83847609, 0xffffffff, "STAC9721,23", patch_sigmatel_stac9721, NULL }, +{ 0x83847644, 0xffffffff, "STAC9744", patch_sigmatel_stac9744, NULL }, +{ 0x83847650, 0xffffffff, "STAC9750,51", NULL, NULL }, // patch? +{ 0x83847652, 0xffffffff, "STAC9752,53", NULL, NULL }, // patch? +{ 0x83847656, 0xffffffff, "STAC9756,57", patch_sigmatel_stac9756, NULL }, +{ 0x83847658, 0xffffffff, "STAC9758,59", patch_sigmatel_stac9758, NULL }, +{ 0x83847666, 0xffffffff, "STAC9766,67", NULL, NULL }, // patch? +{ 0, 0, NULL, NULL, NULL } +}; + + +static void update_power_regs(struct snd_ac97 *ac97); +#ifdef CONFIG_SND_AC97_POWER_SAVE +#define ac97_is_power_save_mode(ac97) \ + ((ac97->scaps & AC97_SCAP_POWER_SAVE) && power_save) +#else +#define ac97_is_power_save_mode(ac97) 0 +#endif + +#define ac97_err(ac97, fmt, args...) \ + dev_err((ac97)->bus->card->dev, fmt, ##args) +#define ac97_warn(ac97, fmt, args...) \ + dev_warn((ac97)->bus->card->dev, fmt, ##args) +#define ac97_dbg(ac97, fmt, args...) \ + dev_dbg((ac97)->bus->card->dev, fmt, ##args) + +/* + * I/O routines + */ + +static int snd_ac97_valid_reg(struct snd_ac97 *ac97, unsigned short reg) +{ + /* filter some registers for buggy codecs */ + switch (ac97->id) { + case AC97_ID_ST_AC97_ID4: + if (reg == 0x08) + return 0; + fallthrough; + case AC97_ID_ST7597: + if (reg == 0x22 || reg == 0x7a) + return 1; + fallthrough; + case AC97_ID_AK4540: + case AC97_ID_AK4542: + if (reg <= 0x1c || reg == 0x20 || reg == 0x26 || reg >= 0x7c) + return 1; + return 0; + case AC97_ID_AD1819: /* AD1819 */ + case AC97_ID_AD1881: /* AD1881 */ + case AC97_ID_AD1881A: /* AD1881A */ + if (reg >= 0x3a && reg <= 0x6e) /* 0x59 */ + return 0; + return 1; + case AC97_ID_AD1885: /* AD1885 */ + case AC97_ID_AD1886: /* AD1886 */ + case AC97_ID_AD1886A: /* AD1886A - !!verify!! --jk */ + case AC97_ID_AD1887: /* AD1887 - !!verify!! --jk */ + if (reg == 0x5a) + return 1; + if (reg >= 0x3c && reg <= 0x6e) /* 0x59 */ + return 0; + return 1; + case AC97_ID_STAC9700: + case AC97_ID_STAC9704: + case AC97_ID_STAC9705: + case AC97_ID_STAC9708: + case AC97_ID_STAC9721: + case AC97_ID_STAC9744: + case AC97_ID_STAC9756: + if (reg <= 0x3a || reg >= 0x5a) + return 1; + return 0; + } + return 1; +} + +/** + * snd_ac97_write - write a value on the given register + * @ac97: the ac97 instance + * @reg: the register to change + * @value: the value to set + * + * Writes a value on the given register. This will invoke the write + * callback directly after the register check. + * This function doesn't change the register cache unlike + * #snd_ca97_write_cache(), so use this only when you don't want to + * reflect the change to the suspend/resume state. + */ +void snd_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short value) +{ + if (!snd_ac97_valid_reg(ac97, reg)) + return; + if ((ac97->id & 0xffffff00) == AC97_ID_ALC100) { + /* Fix H/W bug of ALC100/100P */ + if (reg == AC97_MASTER || reg == AC97_HEADPHONE) + ac97->bus->ops->write(ac97, AC97_RESET, 0); /* reset audio codec */ + } + ac97->bus->ops->write(ac97, reg, value); +} + +EXPORT_SYMBOL(snd_ac97_write); + +/** + * snd_ac97_read - read a value from the given register + * + * @ac97: the ac97 instance + * @reg: the register to read + * + * Reads a value from the given register. This will invoke the read + * callback directly after the register check. + * + * Return: The read value. + */ +unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + if (!snd_ac97_valid_reg(ac97, reg)) + return 0; + return ac97->bus->ops->read(ac97, reg); +} + +/* read a register - return the cached value if already read */ +static inline unsigned short snd_ac97_read_cache(struct snd_ac97 *ac97, unsigned short reg) +{ + if (! test_bit(reg, ac97->reg_accessed)) { + ac97->regs[reg] = ac97->bus->ops->read(ac97, reg); + // set_bit(reg, ac97->reg_accessed); + } + return ac97->regs[reg]; +} + +EXPORT_SYMBOL(snd_ac97_read); + +/** + * snd_ac97_write_cache - write a value on the given register and update the cache + * @ac97: the ac97 instance + * @reg: the register to change + * @value: the value to set + * + * Writes a value on the given register and updates the register + * cache. The cached values are used for the cached-read and the + * suspend/resume. + */ +void snd_ac97_write_cache(struct snd_ac97 *ac97, unsigned short reg, unsigned short value) +{ + if (!snd_ac97_valid_reg(ac97, reg)) + return; + guard(mutex)(&ac97->reg_mutex); + ac97->regs[reg] = value; + ac97->bus->ops->write(ac97, reg, value); + set_bit(reg, ac97->reg_accessed); +} + +EXPORT_SYMBOL(snd_ac97_write_cache); + +/** + * snd_ac97_update - update the value on the given register + * @ac97: the ac97 instance + * @reg: the register to change + * @value: the value to set + * + * Compares the value with the register cache and updates the value + * only when the value is changed. + * + * Return: 1 if the value is changed, 0 if no change, or a negative + * code on failure. + */ +int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value) +{ + int change; + + if (!snd_ac97_valid_reg(ac97, reg)) + return -EINVAL; + guard(mutex)(&ac97->reg_mutex); + change = ac97->regs[reg] != value; + if (change) { + ac97->regs[reg] = value; + ac97->bus->ops->write(ac97, reg, value); + } + set_bit(reg, ac97->reg_accessed); + return change; +} + +EXPORT_SYMBOL(snd_ac97_update); + +/** + * snd_ac97_update_bits - update the bits on the given register + * @ac97: the ac97 instance + * @reg: the register to change + * @mask: the bit-mask to change + * @value: the value to set + * + * Updates the masked-bits on the given register only when the value + * is changed. + * + * Return: 1 if the bits are changed, 0 if no change, or a negative + * code on failure. + */ +int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value) +{ + if (!snd_ac97_valid_reg(ac97, reg)) + return -EINVAL; + guard(mutex)(&ac97->reg_mutex); + return snd_ac97_update_bits_nolock(ac97, reg, mask, value); +} + +EXPORT_SYMBOL(snd_ac97_update_bits); + +/* no lock version - see snd_ac97_update_bits() */ +int snd_ac97_update_bits_nolock(struct snd_ac97 *ac97, unsigned short reg, + unsigned short mask, unsigned short value) +{ + int change; + unsigned short old, new; + + old = snd_ac97_read_cache(ac97, reg); + new = (old & ~mask) | (value & mask); + change = old != new; + if (change) { + ac97->regs[reg] = new; + ac97->bus->ops->write(ac97, reg, new); + } + set_bit(reg, ac97->reg_accessed); + return change; +} + +static int snd_ac97_ad18xx_update_pcm_bits(struct snd_ac97 *ac97, int codec, unsigned short mask, unsigned short value) +{ + int change; + unsigned short old, new, cfg; + + guard(mutex)(&ac97->page_mutex); + old = ac97->spec.ad18xx.pcmreg[codec]; + new = (old & ~mask) | (value & mask); + change = old != new; + if (change) { + guard(mutex)(&ac97->reg_mutex); + cfg = snd_ac97_read_cache(ac97, AC97_AD_SERIAL_CFG); + ac97->spec.ad18xx.pcmreg[codec] = new; + /* select single codec */ + ac97->bus->ops->write(ac97, AC97_AD_SERIAL_CFG, + (cfg & ~0x7000) | + ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]); + /* update PCM bits */ + ac97->bus->ops->write(ac97, AC97_PCM, new); + /* select all codecs */ + ac97->bus->ops->write(ac97, AC97_AD_SERIAL_CFG, + cfg | 0x7000); + } + return change; +} + +/* + * Controls + */ + +static int snd_ac97_info_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value; + + return snd_ctl_enum_info(uinfo, e->shift_l == e->shift_r ? 1 : 2, + e->mask, e->texts); +} + +static int snd_ac97_get_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value; + unsigned short val, bitmask; + + for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + ; + val = snd_ac97_read_cache(ac97, e->reg); + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1); + if (e->shift_l != e->shift_r) + ucontrol->value.enumerated.item[1] = (val >> e->shift_r) & (bitmask - 1); + + return 0; +} + +static int snd_ac97_put_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value; + unsigned short val; + unsigned short mask, bitmask; + + for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + ; + if (ucontrol->value.enumerated.item[0] > e->mask - 1) + return -EINVAL; + val = ucontrol->value.enumerated.item[0] << e->shift_l; + mask = (bitmask - 1) << e->shift_l; + if (e->shift_l != e->shift_r) { + if (ucontrol->value.enumerated.item[1] > e->mask - 1) + return -EINVAL; + val |= ucontrol->value.enumerated.item[1] << e->shift_r; + mask |= (bitmask - 1) << e->shift_r; + } + return snd_ac97_update_bits(ac97, e->reg, mask, val); +} + +/* save/restore ac97 v2.3 paging */ +static int snd_ac97_page_save(struct snd_ac97 *ac97, int reg, struct snd_kcontrol *kcontrol) +{ + int page_save = -1; + if ((kcontrol->private_value & (1<<25)) && + (ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23 && + (reg >= 0x60 && reg < 0x70)) { + unsigned short page = (kcontrol->private_value >> 26) & 0x0f; + mutex_lock(&ac97->page_mutex); /* lock paging */ + page_save = snd_ac97_read(ac97, AC97_INT_PAGING) & AC97_PAGE_MASK; + snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page); + } + return page_save; +} + +static void snd_ac97_page_restore(struct snd_ac97 *ac97, int page_save) +{ + if (page_save >= 0) { + snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page_save); + mutex_unlock(&ac97->page_mutex); /* unlock paging */ + } +} + +/* volume and switch controls */ +static int snd_ac97_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + int shift = (kcontrol->private_value >> 8) & 0x0f; + int rshift = (kcontrol->private_value >> 12) & 0x0f; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = shift == rshift ? 1 : 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_ac97_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0x0f; + int rshift = (kcontrol->private_value >> 12) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0x01; + int page_save; + + page_save = snd_ac97_page_save(ac97, reg, kcontrol); + ucontrol->value.integer.value[0] = (snd_ac97_read_cache(ac97, reg) >> shift) & mask; + if (shift != rshift) + ucontrol->value.integer.value[1] = (snd_ac97_read_cache(ac97, reg) >> rshift) & mask; + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + if (shift != rshift) + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + snd_ac97_page_restore(ac97, page_save); + return 0; +} + +static int snd_ac97_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0x0f; + int rshift = (kcontrol->private_value >> 12) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0x01; + int err, page_save; + unsigned short val, val2, val_mask; + + page_save = snd_ac97_page_save(ac97, reg, kcontrol); + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val_mask = mask << shift; + val = val << shift; + if (shift != rshift) { + val2 = (ucontrol->value.integer.value[1] & mask); + if (invert) + val2 = mask - val2; + val_mask |= mask << rshift; + val |= val2 << rshift; + } + err = snd_ac97_update_bits(ac97, reg, val_mask, val); + snd_ac97_page_restore(ac97, page_save); +#ifdef CONFIG_SND_AC97_POWER_SAVE + /* check analog mixer power-down */ + if ((val_mask & AC97_PD_EAPD) && + (kcontrol->private_value & (1<<30))) { + if (val & AC97_PD_EAPD) + ac97->power_up &= ~(1 << (reg>>1)); + else + ac97->power_up |= 1 << (reg>>1); + update_power_regs(ac97); + } +#endif + return err; +} + +static const struct snd_kcontrol_new snd_ac97_controls_tone[2] = { +AC97_SINGLE("Tone Control - Bass", AC97_MASTER_TONE, 8, 15, 1), +AC97_SINGLE("Tone Control - Treble", AC97_MASTER_TONE, 0, 15, 1) +}; + +static const struct snd_kcontrol_new snd_ac97_controls_pc_beep[2] = { +AC97_SINGLE("Beep Playback Switch", AC97_PC_BEEP, 15, 1, 1), +AC97_SINGLE("Beep Playback Volume", AC97_PC_BEEP, 1, 15, 1) +}; + +static const struct snd_kcontrol_new snd_ac97_controls_mic_boost = + AC97_SINGLE("Mic Boost (+20dB)", AC97_MIC, 6, 1, 0); + + +static const char* std_rec_sel[] = {"Mic", "CD", "Video", "Aux", "Line", "Mix", "Mix Mono", "Phone"}; +static const char* std_3d_path[] = {"pre 3D", "post 3D"}; +static const char* std_mix[] = {"Mix", "Mic"}; +static const char* std_mic[] = {"Mic1", "Mic2"}; + +static const struct ac97_enum std_enum[] = { +AC97_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, std_rec_sel), +AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, std_3d_path), +AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, std_mix), +AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, std_mic), +}; + +static const struct snd_kcontrol_new snd_ac97_control_capture_src = +AC97_ENUM("Capture Source", std_enum[0]); + +static const struct snd_kcontrol_new snd_ac97_control_capture_vol = +AC97_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0); + +static const struct snd_kcontrol_new snd_ac97_controls_mic_capture[2] = { +AC97_SINGLE("Mic Capture Switch", AC97_REC_GAIN_MIC, 15, 1, 1), +AC97_SINGLE("Mic Capture Volume", AC97_REC_GAIN_MIC, 0, 15, 0) +}; + +enum { + AC97_GENERAL_PCM_OUT = 0, + AC97_GENERAL_STEREO_ENHANCEMENT, + AC97_GENERAL_3D, + AC97_GENERAL_LOUDNESS, + AC97_GENERAL_MONO, + AC97_GENERAL_MIC, + AC97_GENERAL_LOOPBACK +}; + +static const struct snd_kcontrol_new snd_ac97_controls_general[7] = { +AC97_ENUM("PCM Out Path & Mute", std_enum[1]), +AC97_SINGLE("Simulated Stereo Enhancement", AC97_GENERAL_PURPOSE, 14, 1, 0), +AC97_SINGLE("3D Control - Switch", AC97_GENERAL_PURPOSE, 13, 1, 0), +AC97_SINGLE("Loudness (bass boost)", AC97_GENERAL_PURPOSE, 12, 1, 0), +AC97_ENUM("Mono Output Select", std_enum[2]), +AC97_ENUM("Mic Select", std_enum[3]), +AC97_SINGLE("ADC/DAC Loopback", AC97_GENERAL_PURPOSE, 7, 1, 0) +}; + +static const struct snd_kcontrol_new snd_ac97_controls_3d[2] = { +AC97_SINGLE("3D Control - Center", AC97_3D_CONTROL, 8, 15, 0), +AC97_SINGLE("3D Control - Depth", AC97_3D_CONTROL, 0, 15, 0) +}; + +static const struct snd_kcontrol_new snd_ac97_controls_center[2] = { +AC97_SINGLE("Center Playback Switch", AC97_CENTER_LFE_MASTER, 7, 1, 1), +AC97_SINGLE("Center Playback Volume", AC97_CENTER_LFE_MASTER, 0, 31, 1) +}; + +static const struct snd_kcontrol_new snd_ac97_controls_lfe[2] = { +AC97_SINGLE("LFE Playback Switch", AC97_CENTER_LFE_MASTER, 15, 1, 1), +AC97_SINGLE("LFE Playback Volume", AC97_CENTER_LFE_MASTER, 8, 31, 1) +}; + +static const struct snd_kcontrol_new snd_ac97_control_eapd = +AC97_SINGLE("External Amplifier", AC97_POWERDOWN, 15, 1, 1); + +static const struct snd_kcontrol_new snd_ac97_controls_modem_switches[2] = { +AC97_SINGLE("Off-hook Switch", AC97_GPIO_STATUS, 0, 1, 0), +AC97_SINGLE("Caller ID Switch", AC97_GPIO_STATUS, 2, 1, 0) +}; + +/* change the existing EAPD control as inverted */ +static void set_inv_eapd(struct snd_ac97 *ac97, struct snd_kcontrol *kctl) +{ + kctl->private_value = AC97_SINGLE_VALUE(AC97_POWERDOWN, 15, 1, 0); + snd_ac97_update_bits(ac97, AC97_POWERDOWN, (1<<15), (1<<15)); /* EAPD up */ + ac97->scaps |= AC97_SCAP_INV_EAPD; +} + +static int snd_ac97_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_ac97_spdif_cmask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL | + IEC958_AES0_NONAUDIO | + IEC958_AES0_CON_EMPHASIS_5015 | + IEC958_AES0_CON_NOT_COPYRIGHT; + ucontrol->value.iec958.status[1] = IEC958_AES1_CON_CATEGORY | + IEC958_AES1_CON_ORIGINAL; + ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS; + return 0; +} + +static int snd_ac97_spdif_pmask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + /* FIXME: AC'97 spec doesn't say which bits are used for what */ + ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL | + IEC958_AES0_NONAUDIO | + IEC958_AES0_PRO_FS | + IEC958_AES0_PRO_EMPHASIS_5015; + return 0; +} + +static int snd_ac97_spdif_default_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ac97->reg_mutex); + ucontrol->value.iec958.status[0] = ac97->spdif_status & 0xff; + ucontrol->value.iec958.status[1] = (ac97->spdif_status >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (ac97->spdif_status >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (ac97->spdif_status >> 24) & 0xff; + return 0; +} + +static int snd_ac97_spdif_default_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned int new = 0; + unsigned short val = 0; + int change; + + new = val = ucontrol->value.iec958.status[0] & (IEC958_AES0_PROFESSIONAL|IEC958_AES0_NONAUDIO); + if (ucontrol->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL) { + new |= ucontrol->value.iec958.status[0] & (IEC958_AES0_PRO_FS|IEC958_AES0_PRO_EMPHASIS_5015); + switch (new & IEC958_AES0_PRO_FS) { + case IEC958_AES0_PRO_FS_44100: val |= 0<<12; break; + case IEC958_AES0_PRO_FS_48000: val |= 2<<12; break; + case IEC958_AES0_PRO_FS_32000: val |= 3<<12; break; + default: val |= 1<<12; break; + } + if ((new & IEC958_AES0_PRO_EMPHASIS) == IEC958_AES0_PRO_EMPHASIS_5015) + val |= 1<<3; + } else { + new |= ucontrol->value.iec958.status[0] & (IEC958_AES0_CON_EMPHASIS_5015|IEC958_AES0_CON_NOT_COPYRIGHT); + new |= ((ucontrol->value.iec958.status[1] & (IEC958_AES1_CON_CATEGORY|IEC958_AES1_CON_ORIGINAL)) << 8); + new |= ((ucontrol->value.iec958.status[3] & IEC958_AES3_CON_FS) << 24); + if ((new & IEC958_AES0_CON_EMPHASIS) == IEC958_AES0_CON_EMPHASIS_5015) + val |= 1<<3; + if (!(new & IEC958_AES0_CON_NOT_COPYRIGHT)) + val |= 1<<2; + val |= ((new >> 8) & 0xff) << 4; // category + original + switch ((new >> 24) & 0xff) { + case IEC958_AES3_CON_FS_44100: val |= 0<<12; break; + case IEC958_AES3_CON_FS_48000: val |= 2<<12; break; + case IEC958_AES3_CON_FS_32000: val |= 3<<12; break; + default: val |= 1<<12; break; + } + } + + guard(mutex)(&ac97->reg_mutex); + change = ac97->spdif_status != new; + ac97->spdif_status = new; + + if (ac97->flags & AC97_CS_SPDIF) { + int x = (val >> 12) & 0x03; + switch (x) { + case 0: x = 1; break; // 44.1 + case 2: x = 0; break; // 48.0 + default: x = 0; break; // illegal. + } + change |= snd_ac97_update_bits_nolock(ac97, AC97_CSR_SPDIF, 0x3fff, ((val & 0xcfff) | (x << 12))); + } else if (ac97->flags & AC97_CX_SPDIF) { + int v; + v = new & (IEC958_AES0_CON_EMPHASIS_5015|IEC958_AES0_CON_NOT_COPYRIGHT) ? 0 : AC97_CXR_COPYRGT; + v |= new & IEC958_AES0_NONAUDIO ? AC97_CXR_SPDIF_AC3 : AC97_CXR_SPDIF_PCM; + change |= snd_ac97_update_bits_nolock(ac97, AC97_CXR_AUDIO_MISC, + AC97_CXR_SPDIF_MASK | AC97_CXR_COPYRGT, + v); + } else if (ac97->id == AC97_ID_YMF743) { + change |= snd_ac97_update_bits_nolock(ac97, + AC97_YMF7X3_DIT_CTRL, + 0xff38, + ((val << 4) & 0xff00) | + ((val << 2) & 0x0038)); + } else { + unsigned short extst = snd_ac97_read_cache(ac97, AC97_EXTENDED_STATUS); + snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); /* turn off */ + + change |= snd_ac97_update_bits_nolock(ac97, AC97_SPDIF, 0x3fff, val); + if (extst & AC97_EA_SPDIF) { + snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); /* turn on again */ + } + } + + return change; +} + +static int snd_ac97_put_spsa(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + // int invert = (kcontrol->private_value >> 24) & 0xff; + unsigned short value, old, new; + int change; + + value = (ucontrol->value.integer.value[0] & mask); + + guard(mutex)(&ac97->reg_mutex); + mask <<= shift; + value <<= shift; + old = snd_ac97_read_cache(ac97, reg); + new = (old & ~mask) | value; + change = old != new; + + if (change) { + unsigned short extst = snd_ac97_read_cache(ac97, AC97_EXTENDED_STATUS); + snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); /* turn off */ + change = snd_ac97_update_bits_nolock(ac97, reg, mask, value); + if (extst & AC97_EA_SPDIF) + snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); /* turn on again */ + } + return change; +} + +static const struct snd_kcontrol_new snd_ac97_controls_spdif[5] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK), + .info = snd_ac97_spdif_mask_info, + .get = snd_ac97_spdif_cmask_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK), + .info = snd_ac97_spdif_mask_info, + .get = snd_ac97_spdif_pmask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_ac97_spdif_mask_info, + .get = snd_ac97_spdif_default_get, + .put = snd_ac97_spdif_default_put, + }, + + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),AC97_EXTENDED_STATUS, 2, 1, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "AC97-SPSA", + .info = snd_ac97_info_volsw, + .get = snd_ac97_get_volsw, + .put = snd_ac97_put_spsa, + .private_value = AC97_SINGLE_VALUE(AC97_EXTENDED_STATUS, 4, 3, 0) + }, +}; + +#define AD18XX_PCM_BITS(xname, codec, lshift, rshift, mask) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_ad18xx_pcm_info_bits, \ + .get = snd_ac97_ad18xx_pcm_get_bits, .put = snd_ac97_ad18xx_pcm_put_bits, \ + .private_value = (codec) | ((lshift) << 8) | ((rshift) << 12) | ((mask) << 16) } + +static int snd_ac97_ad18xx_pcm_info_bits(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int mask = (kcontrol->private_value >> 16) & 0x0f; + int lshift = (kcontrol->private_value >> 8) & 0x0f; + int rshift = (kcontrol->private_value >> 12) & 0x0f; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + if (lshift != rshift && (ac97->flags & AC97_STEREO_MUTES)) + uinfo->count = 2; + else + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_ac97_ad18xx_pcm_get_bits(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->private_value & 3; + int lshift = (kcontrol->private_value >> 8) & 0x0f; + int rshift = (kcontrol->private_value >> 12) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + + ucontrol->value.integer.value[0] = mask - ((ac97->spec.ad18xx.pcmreg[codec] >> lshift) & mask); + if (lshift != rshift && (ac97->flags & AC97_STEREO_MUTES)) + ucontrol->value.integer.value[1] = mask - ((ac97->spec.ad18xx.pcmreg[codec] >> rshift) & mask); + return 0; +} + +static int snd_ac97_ad18xx_pcm_put_bits(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->private_value & 3; + int lshift = (kcontrol->private_value >> 8) & 0x0f; + int rshift = (kcontrol->private_value >> 12) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + unsigned short val, valmask; + + val = (mask - (ucontrol->value.integer.value[0] & mask)) << lshift; + valmask = mask << lshift; + if (lshift != rshift && (ac97->flags & AC97_STEREO_MUTES)) { + val |= (mask - (ucontrol->value.integer.value[1] & mask)) << rshift; + valmask |= mask << rshift; + } + return snd_ac97_ad18xx_update_pcm_bits(ac97, codec, valmask, val); +} + +#define AD18XX_PCM_VOLUME(xname, codec) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_ad18xx_pcm_info_volume, \ + .get = snd_ac97_ad18xx_pcm_get_volume, .put = snd_ac97_ad18xx_pcm_put_volume, \ + .private_value = codec } + +static int snd_ac97_ad18xx_pcm_info_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 = 31; + return 0; +} + +static int snd_ac97_ad18xx_pcm_get_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->private_value & 3; + + guard(mutex)(&ac97->page_mutex); + ucontrol->value.integer.value[0] = 31 - ((ac97->spec.ad18xx.pcmreg[codec] >> 8) & 31); + ucontrol->value.integer.value[1] = 31 - ((ac97->spec.ad18xx.pcmreg[codec] >> 0) & 31); + return 0; +} + +static int snd_ac97_ad18xx_pcm_put_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->private_value & 3; + unsigned short val1, val2; + + val1 = 31 - (ucontrol->value.integer.value[0] & 31); + val2 = 31 - (ucontrol->value.integer.value[1] & 31); + return snd_ac97_ad18xx_update_pcm_bits(ac97, codec, 0x1f1f, (val1 << 8) | val2); +} + +static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_pcm[2] = { +AD18XX_PCM_BITS("PCM Playback Switch", 0, 15, 7, 1), +AD18XX_PCM_VOLUME("PCM Playback Volume", 0) +}; + +static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_surround[2] = { +AD18XX_PCM_BITS("Surround Playback Switch", 1, 15, 7, 1), +AD18XX_PCM_VOLUME("Surround Playback Volume", 1) +}; + +static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_center[2] = { +AD18XX_PCM_BITS("Center Playback Switch", 2, 15, 15, 1), +AD18XX_PCM_BITS("Center Playback Volume", 2, 8, 8, 31) +}; + +static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_lfe[2] = { +AD18XX_PCM_BITS("LFE Playback Switch", 2, 7, 7, 1), +AD18XX_PCM_BITS("LFE Playback Volume", 2, 0, 0, 31) +}; + +/* + * + */ + +static void snd_ac97_powerdown(struct snd_ac97 *ac97); + +static int snd_ac97_bus_free(struct snd_ac97_bus *bus) +{ + if (bus) { + snd_ac97_bus_proc_done(bus); + kfree(bus->pcms); + if (bus->private_free) + bus->private_free(bus); + kfree(bus); + } + return 0; +} + +static int snd_ac97_bus_dev_free(struct snd_device *device) +{ + struct snd_ac97_bus *bus = device->device_data; + return snd_ac97_bus_free(bus); +} + +static int snd_ac97_free(struct snd_ac97 *ac97) +{ + if (ac97) { +#ifdef CONFIG_SND_AC97_POWER_SAVE + cancel_delayed_work_sync(&ac97->power_work); +#endif + snd_ac97_proc_done(ac97); + if (ac97->bus) + ac97->bus->codec[ac97->num] = NULL; + if (ac97->private_free) + ac97->private_free(ac97); + kfree(ac97); + } + return 0; +} + +static int snd_ac97_dev_free(struct snd_device *device) +{ + struct snd_ac97 *ac97 = device->device_data; + snd_ac97_powerdown(ac97); /* for avoiding click noises during shut down */ + return snd_ac97_free(ac97); +} + +static int snd_ac97_try_volume_mix(struct snd_ac97 * ac97, int reg) +{ + unsigned short val, mask = AC97_MUTE_MASK_MONO; + + if (! snd_ac97_valid_reg(ac97, reg)) + return 0; + + switch (reg) { + case AC97_MASTER_TONE: + return ac97->caps & AC97_BC_BASS_TREBLE ? 1 : 0; + case AC97_HEADPHONE: + return ac97->caps & AC97_BC_HEADPHONE ? 1 : 0; + case AC97_REC_GAIN_MIC: + return ac97->caps & AC97_BC_DEDICATED_MIC ? 1 : 0; + case AC97_3D_CONTROL: + if (ac97->caps & AC97_BC_3D_TECH_ID_MASK) { + val = snd_ac97_read(ac97, reg); + /* if nonzero - fixed and we can't set it */ + return val == 0; + } + return 0; + case AC97_CENTER_LFE_MASTER: /* center */ + if ((ac97->ext_id & AC97_EI_CDAC) == 0) + return 0; + break; + case AC97_CENTER_LFE_MASTER+1: /* lfe */ + if ((ac97->ext_id & AC97_EI_LDAC) == 0) + return 0; + reg = AC97_CENTER_LFE_MASTER; + mask = 0x0080; + break; + case AC97_SURROUND_MASTER: + if ((ac97->ext_id & AC97_EI_SDAC) == 0) + return 0; + break; + } + + val = snd_ac97_read(ac97, reg); + if (!(val & mask)) { + /* nothing seems to be here - mute flag is not set */ + /* try another test */ + snd_ac97_write_cache(ac97, reg, val | mask); + val = snd_ac97_read(ac97, reg); + val = snd_ac97_read(ac97, reg); + if (!(val & mask)) + return 0; /* nothing here */ + } + return 1; /* success, useable */ +} + +static void check_volume_resolution(struct snd_ac97 *ac97, int reg, unsigned char *lo_max, unsigned char *hi_max) +{ + unsigned short cbit[3] = { 0x20, 0x10, 0x01 }; + unsigned char max[3] = { 63, 31, 15 }; + int i; + + /* first look up the static resolution table */ + if (ac97->res_table) { + const struct snd_ac97_res_table *tbl; + for (tbl = ac97->res_table; tbl->reg; tbl++) { + if (tbl->reg == reg) { + *lo_max = tbl->bits & 0xff; + *hi_max = (tbl->bits >> 8) & 0xff; + return; + } + } + } + + *lo_max = *hi_max = 0; + for (i = 0 ; i < ARRAY_SIZE(cbit); i++) { + unsigned short val; + snd_ac97_write( + ac97, reg, + AC97_MUTE_MASK_STEREO | cbit[i] | (cbit[i] << 8) + ); + /* Do the read twice due to buffers on some ac97 codecs. + * e.g. The STAC9704 returns exactly what you wrote to the register + * if you read it immediately. This causes the detect routine to fail. + */ + val = snd_ac97_read(ac97, reg); + val = snd_ac97_read(ac97, reg); + if (! *lo_max && (val & 0x7f) == cbit[i]) + *lo_max = max[i]; + if (! *hi_max && ((val >> 8) & 0x7f) == cbit[i]) + *hi_max = max[i]; + if (*lo_max && *hi_max) + break; + } +} + +static int snd_ac97_try_bit(struct snd_ac97 * ac97, int reg, int bit) +{ + unsigned short mask, val, orig, res; + + mask = 1 << bit; + orig = snd_ac97_read(ac97, reg); + val = orig ^ mask; + snd_ac97_write(ac97, reg, val); + res = snd_ac97_read(ac97, reg); + snd_ac97_write_cache(ac97, reg, orig); + return res == val; +} + +/* check the volume resolution of center/lfe */ +static void snd_ac97_change_volume_params2(struct snd_ac97 * ac97, int reg, int shift, unsigned char *max) +{ + unsigned short val, val1; + + *max = 63; + val = AC97_MUTE_MASK_STEREO | (0x20 << shift); + snd_ac97_write(ac97, reg, val); + val1 = snd_ac97_read(ac97, reg); + if (val != val1) { + *max = 31; + } + /* reset volume to zero */ + snd_ac97_write_cache(ac97, reg, AC97_MUTE_MASK_STEREO); +} + +static inline int printable(unsigned int x) +{ + x &= 0xff; + if (x < ' ' || x >= 0x71) { + if (x <= 0x89) + return x - 0x71 + 'A'; + return '?'; + } + return x; +} + +static struct snd_kcontrol *snd_ac97_cnew(const struct snd_kcontrol_new *_template, + struct snd_ac97 * ac97) +{ + struct snd_kcontrol_new template; + memcpy(&template, _template, sizeof(template)); + template.index = ac97->num; + return snd_ctl_new1(&template, ac97); +} + +/* + * create mute switch(es) for normal stereo controls + */ +static int snd_ac97_cmute_new_stereo(struct snd_card *card, char *name, int reg, + int check_stereo, int check_amix, + struct snd_ac97 *ac97) +{ + struct snd_kcontrol *kctl; + int err; + unsigned short val, val1, mute_mask; + + if (! snd_ac97_valid_reg(ac97, reg)) + return 0; + + mute_mask = AC97_MUTE_MASK_MONO; + val = snd_ac97_read(ac97, reg); + if (check_stereo || (ac97->flags & AC97_STEREO_MUTES)) { + /* check whether both mute bits work */ + val1 = val | AC97_MUTE_MASK_STEREO; + snd_ac97_write(ac97, reg, val1); + if (val1 == snd_ac97_read(ac97, reg)) + mute_mask = AC97_MUTE_MASK_STEREO; + } + if (mute_mask == AC97_MUTE_MASK_STEREO) { + struct snd_kcontrol_new tmp = AC97_DOUBLE(name, reg, 15, 7, 1, 1); + if (check_amix) + tmp.private_value |= (1 << 30); + tmp.index = ac97->num; + kctl = snd_ctl_new1(&tmp, ac97); + } else { + struct snd_kcontrol_new tmp = AC97_SINGLE(name, reg, 15, 1, 1); + if (check_amix) + tmp.private_value |= (1 << 30); + tmp.index = ac97->num; + kctl = snd_ctl_new1(&tmp, ac97); + } + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + /* mute as default */ + snd_ac97_write_cache(ac97, reg, val | mute_mask); + return 0; +} + +/* + * set dB information + */ +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 unsigned int *find_db_scale(unsigned int maxval) +{ + switch (maxval) { + case 0x0f: return db_scale_4bit; + case 0x1f: return db_scale_5bit; + case 0x3f: return db_scale_6bit; + } + return NULL; +} + +static void set_tlv_db_scale(struct snd_kcontrol *kctl, const unsigned int *tlv) +{ + kctl->tlv.p = tlv; + if (tlv) + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; +} + +/* + * create a volume for normal stereo/mono controls + */ +static int snd_ac97_cvol_new(struct snd_card *card, char *name, int reg, unsigned int lo_max, + unsigned int hi_max, struct snd_ac97 *ac97) +{ + int err; + struct snd_kcontrol *kctl; + + if (! snd_ac97_valid_reg(ac97, reg)) + return 0; + if (hi_max) { + /* invert */ + struct snd_kcontrol_new tmp = AC97_DOUBLE(name, reg, 8, 0, lo_max, 1); + tmp.index = ac97->num; + kctl = snd_ctl_new1(&tmp, ac97); + } else { + /* invert */ + struct snd_kcontrol_new tmp = AC97_SINGLE(name, reg, 0, lo_max, 1); + tmp.index = ac97->num; + kctl = snd_ctl_new1(&tmp, ac97); + } + if (!kctl) + return -ENOMEM; + if (reg >= AC97_PHONE && reg <= AC97_PCM) + set_tlv_db_scale(kctl, db_scale_5bit_12db_max); + else + set_tlv_db_scale(kctl, find_db_scale(lo_max)); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + snd_ac97_write_cache( + ac97, reg, + (snd_ac97_read(ac97, reg) & AC97_MUTE_MASK_STEREO) + | lo_max | (hi_max << 8) + ); + return 0; +} + +/* + * create a mute-switch and a volume for normal stereo/mono controls + */ +static int snd_ac97_cmix_new_stereo(struct snd_card *card, const char *pfx, + int reg, int check_stereo, int check_amix, + struct snd_ac97 *ac97) +{ + int err; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + unsigned char lo_max, hi_max; + + if (! snd_ac97_valid_reg(ac97, reg)) + return 0; + + if (snd_ac97_try_bit(ac97, reg, 15)) { + sprintf(name, "%s Switch", pfx); + err = snd_ac97_cmute_new_stereo(card, name, reg, + check_stereo, check_amix, + ac97); + if (err < 0) + return err; + } + check_volume_resolution(ac97, reg, &lo_max, &hi_max); + if (lo_max) { + sprintf(name, "%s Volume", pfx); + err = snd_ac97_cvol_new(card, name, reg, lo_max, hi_max, ac97); + if (err < 0) + return err; + } + return 0; +} + +#define snd_ac97_cmix_new(card, pfx, reg, acheck, ac97) \ + snd_ac97_cmix_new_stereo(card, pfx, reg, 0, acheck, ac97) +#define snd_ac97_cmute_new(card, name, reg, acheck, ac97) \ + snd_ac97_cmute_new_stereo(card, name, reg, 0, acheck, ac97) + +static unsigned int snd_ac97_determine_spdif_rates(struct snd_ac97 *ac97); + +static int snd_ac97_mixer_build(struct snd_ac97 * ac97) +{ + struct snd_card *card = ac97->bus->card; + struct snd_kcontrol *kctl; + int err; + unsigned int idx; + unsigned char max; + + /* build master controls */ + /* AD claims to remove this control from AD1887, although spec v2.2 does not allow this */ + if (snd_ac97_try_volume_mix(ac97, AC97_MASTER)) { + if (ac97->flags & AC97_HAS_NO_MASTER_VOL) + err = snd_ac97_cmute_new(card, "Master Playback Switch", + AC97_MASTER, 0, ac97); + else + err = snd_ac97_cmix_new(card, "Master Playback", + AC97_MASTER, 0, ac97); + if (err < 0) + return err; + } + + ac97->regs[AC97_CENTER_LFE_MASTER] = AC97_MUTE_MASK_STEREO; + + /* build center controls */ + if ((snd_ac97_try_volume_mix(ac97, AC97_CENTER_LFE_MASTER)) + && !(ac97->flags & AC97_AD_MULTI)) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_center[0], ac97)); + if (err < 0) + return err; + err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_center[1], ac97)); + if (err < 0) + return err; + snd_ac97_change_volume_params2(ac97, AC97_CENTER_LFE_MASTER, 0, &max); + kctl->private_value &= ~(0xff << 16); + kctl->private_value |= (int)max << 16; + set_tlv_db_scale(kctl, find_db_scale(max)); + snd_ac97_write_cache(ac97, AC97_CENTER_LFE_MASTER, ac97->regs[AC97_CENTER_LFE_MASTER] | max); + } + + /* build LFE controls */ + if ((snd_ac97_try_volume_mix(ac97, AC97_CENTER_LFE_MASTER+1)) + && !(ac97->flags & AC97_AD_MULTI)) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_lfe[0], ac97)); + if (err < 0) + return err; + err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_lfe[1], ac97)); + if (err < 0) + return err; + snd_ac97_change_volume_params2(ac97, AC97_CENTER_LFE_MASTER, 8, &max); + kctl->private_value &= ~(0xff << 16); + kctl->private_value |= (int)max << 16; + set_tlv_db_scale(kctl, find_db_scale(max)); + snd_ac97_write_cache(ac97, AC97_CENTER_LFE_MASTER, ac97->regs[AC97_CENTER_LFE_MASTER] | max << 8); + } + + /* build surround controls */ + if ((snd_ac97_try_volume_mix(ac97, AC97_SURROUND_MASTER)) + && !(ac97->flags & AC97_AD_MULTI)) { + /* Surround Master (0x38) is with stereo mutes */ + err = snd_ac97_cmix_new_stereo(card, "Surround Playback", + AC97_SURROUND_MASTER, 1, 0, + ac97); + if (err < 0) + return err; + } + + /* build headphone controls */ + if (snd_ac97_try_volume_mix(ac97, AC97_HEADPHONE)) { + err = snd_ac97_cmix_new(card, "Headphone Playback", + AC97_HEADPHONE, 0, ac97); + if (err < 0) + return err; + } + + /* build master mono controls */ + if (snd_ac97_try_volume_mix(ac97, AC97_MASTER_MONO)) { + err = snd_ac97_cmix_new(card, "Master Mono Playback", + AC97_MASTER_MONO, 0, ac97); + if (err < 0) + return err; + } + + /* build master tone controls */ + if (!(ac97->flags & AC97_HAS_NO_TONE)) { + if (snd_ac97_try_volume_mix(ac97, AC97_MASTER_TONE)) { + for (idx = 0; idx < 2; idx++) { + kctl = snd_ac97_cnew(&snd_ac97_controls_tone[idx], ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + if (ac97->id == AC97_ID_YMF743 || + ac97->id == AC97_ID_YMF753) { + kctl->private_value &= ~(0xff << 16); + kctl->private_value |= 7 << 16; + } + } + snd_ac97_write_cache(ac97, AC97_MASTER_TONE, 0x0f0f); + } + } + + /* build Beep controls */ + if (!(ac97->flags & AC97_HAS_NO_PC_BEEP) && + ((ac97->flags & AC97_HAS_PC_BEEP) || + snd_ac97_try_volume_mix(ac97, AC97_PC_BEEP))) { + for (idx = 0; idx < 2; idx++) { + kctl = snd_ac97_cnew(&snd_ac97_controls_pc_beep[idx], ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + set_tlv_db_scale(kctl, db_scale_4bit); + snd_ac97_write_cache( + ac97, + AC97_PC_BEEP, + (snd_ac97_read(ac97, AC97_PC_BEEP) + | AC97_MUTE_MASK_MONO | 0x001e) + ); + } + + /* build Phone controls */ + if (!(ac97->flags & AC97_HAS_NO_PHONE)) { + if (snd_ac97_try_volume_mix(ac97, AC97_PHONE)) { + err = snd_ac97_cmix_new(card, "Phone Playback", + AC97_PHONE, 1, ac97); + if (err < 0) + return err; + } + } + + /* build MIC controls */ + if (!(ac97->flags & AC97_HAS_NO_MIC)) { + if (snd_ac97_try_volume_mix(ac97, AC97_MIC)) { + err = snd_ac97_cmix_new(card, "Mic Playback", + AC97_MIC, 1, ac97); + if (err < 0) + return err; + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_mic_boost, ac97)); + if (err < 0) + return err; + } + } + + /* build Line controls */ + if (snd_ac97_try_volume_mix(ac97, AC97_LINE)) { + err = snd_ac97_cmix_new(card, "Line Playback", + AC97_LINE, 1, ac97); + if (err < 0) + return err; + } + + /* build CD controls */ + if (!(ac97->flags & AC97_HAS_NO_CD)) { + if (snd_ac97_try_volume_mix(ac97, AC97_CD)) { + err = snd_ac97_cmix_new(card, "CD Playback", + AC97_CD, 1, ac97); + if (err < 0) + return err; + } + } + + /* build Video controls */ + if (!(ac97->flags & AC97_HAS_NO_VIDEO)) { + if (snd_ac97_try_volume_mix(ac97, AC97_VIDEO)) { + err = snd_ac97_cmix_new(card, "Video Playback", + AC97_VIDEO, 1, ac97); + if (err < 0) + return err; + } + } + + /* build Aux controls */ + if (!(ac97->flags & AC97_HAS_NO_AUX)) { + if (snd_ac97_try_volume_mix(ac97, AC97_AUX)) { + err = snd_ac97_cmix_new(card, "Aux Playback", + AC97_AUX, 1, ac97); + if (err < 0) + return err; + } + } + + /* build PCM controls */ + if (ac97->flags & AC97_AD_MULTI) { + unsigned short init_val; + if (ac97->flags & AC97_STEREO_MUTES) + init_val = 0x9f9f; + else + init_val = 0x9f1f; + for (idx = 0; idx < 2; idx++) { + kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_pcm[idx], ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + set_tlv_db_scale(kctl, db_scale_5bit); + ac97->spec.ad18xx.pcmreg[0] = init_val; + if (ac97->scaps & AC97_SCAP_SURROUND_DAC) { + for (idx = 0; idx < 2; idx++) { + kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_surround[idx], ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + set_tlv_db_scale(kctl, db_scale_5bit); + ac97->spec.ad18xx.pcmreg[1] = init_val; + } + if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC) { + for (idx = 0; idx < 2; idx++) { + kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_center[idx], ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + set_tlv_db_scale(kctl, db_scale_5bit); + for (idx = 0; idx < 2; idx++) { + kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_lfe[idx], ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + set_tlv_db_scale(kctl, db_scale_5bit); + ac97->spec.ad18xx.pcmreg[2] = init_val; + } + snd_ac97_write_cache(ac97, AC97_PCM, init_val); + } else { + if (!(ac97->flags & AC97_HAS_NO_STD_PCM)) { + if (ac97->flags & AC97_HAS_NO_PCM_VOL) + err = snd_ac97_cmute_new(card, + "PCM Playback Switch", + AC97_PCM, 0, ac97); + else + err = snd_ac97_cmix_new(card, "PCM Playback", + AC97_PCM, 0, ac97); + if (err < 0) + return err; + } + } + + /* build Capture controls */ + if (!(ac97->flags & AC97_HAS_NO_REC_GAIN)) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_src, ac97)); + if (err < 0) + return err; + if (snd_ac97_try_bit(ac97, AC97_REC_GAIN, 15)) { + err = snd_ac97_cmute_new(card, "Capture Switch", + AC97_REC_GAIN, 0, ac97); + if (err < 0) + return err; + } + kctl = snd_ac97_cnew(&snd_ac97_control_capture_vol, ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + set_tlv_db_scale(kctl, db_scale_rec_gain); + snd_ac97_write_cache(ac97, AC97_REC_SEL, 0x0000); + snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x0000); + } + /* build MIC Capture controls */ + if (snd_ac97_try_volume_mix(ac97, AC97_REC_GAIN_MIC)) { + for (idx = 0; idx < 2; idx++) { + kctl = snd_ac97_cnew(&snd_ac97_controls_mic_capture[idx], ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + set_tlv_db_scale(kctl, db_scale_rec_gain); + snd_ac97_write_cache(ac97, AC97_REC_GAIN_MIC, 0x0000); + } + + /* build PCM out path & mute control */ + if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 15)) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_PCM_OUT], ac97)); + if (err < 0) + return err; + } + + /* build Simulated Stereo Enhancement control */ + if (ac97->caps & AC97_BC_SIM_STEREO) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_STEREO_ENHANCEMENT], ac97)); + if (err < 0) + return err; + } + + /* build 3D Stereo Enhancement control */ + if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 13)) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_3D], ac97)); + if (err < 0) + return err; + } + + /* build Loudness control */ + if (ac97->caps & AC97_BC_LOUDNESS) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_LOUDNESS], ac97)); + if (err < 0) + return err; + } + + /* build Mono output select control */ + if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 9)) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_MONO], ac97)); + if (err < 0) + return err; + } + + /* build Mic select control */ + if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 8)) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_MIC], ac97)); + if (err < 0) + return err; + } + + /* build ADC/DAC loopback control */ + if (enable_loopback && snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 7)) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_LOOPBACK], ac97)); + if (err < 0) + return err; + } + + snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, ~AC97_GP_DRSS_MASK, 0x0000); + + /* build 3D controls */ + if (ac97->build_ops->build_3d) { + ac97->build_ops->build_3d(ac97); + } else { + if (snd_ac97_try_volume_mix(ac97, AC97_3D_CONTROL)) { + unsigned short val; + val = 0x0707; + snd_ac97_write(ac97, AC97_3D_CONTROL, val); + val = snd_ac97_read(ac97, AC97_3D_CONTROL); + val = val == 0x0606; + kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + if (val) + kctl->private_value = AC97_3D_CONTROL | (9 << 8) | (7 << 16); + kctl = snd_ac97_cnew(&snd_ac97_controls_3d[1], ac97); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + if (val) + kctl->private_value = AC97_3D_CONTROL | (1 << 8) | (7 << 16); + snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000); + } + } + + /* build S/PDIF controls */ + + /* Hack for ASUS P5P800-VM, which does not indicate S/PDIF capability */ + if (ac97->subsystem_vendor == 0x1043 && + ac97->subsystem_device == 0x810f) + ac97->ext_id |= AC97_EI_SPDIF; + + if ((ac97->ext_id & AC97_EI_SPDIF) && !(ac97->scaps & AC97_SCAP_NO_SPDIF)) { + if (ac97->build_ops->build_spdif) { + err = ac97->build_ops->build_spdif(ac97); + if (err < 0) + return err; + } else { + for (idx = 0; idx < 5; idx++) { + err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_spdif[idx], ac97)); + if (err < 0) + return err; + } + if (ac97->build_ops->build_post_spdif) { + err = ac97->build_ops->build_post_spdif(ac97); + if (err < 0) + return err; + } + /* set default PCM S/PDIF params */ + /* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */ + snd_ac97_write_cache(ac97, AC97_SPDIF, 0x2a20); + ac97->rates[AC97_RATES_SPDIF] = snd_ac97_determine_spdif_rates(ac97); + } + ac97->spdif_status = SNDRV_PCM_DEFAULT_CON_SPDIF; + } + + /* build chip specific controls */ + if (ac97->build_ops->build_specific) { + err = ac97->build_ops->build_specific(ac97); + if (err < 0) + return err; + } + + if (snd_ac97_try_bit(ac97, AC97_POWERDOWN, 15)) { + kctl = snd_ac97_cnew(&snd_ac97_control_eapd, ac97); + if (! kctl) + return -ENOMEM; + if (ac97->scaps & AC97_SCAP_INV_EAPD) + set_inv_eapd(ac97, kctl); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + + return 0; +} + +static int snd_ac97_modem_build(struct snd_card *card, struct snd_ac97 * ac97) +{ + int err, idx; + + /* + ac97_dbg(ac97, "AC97_GPIO_CFG = %x\n", + snd_ac97_read(ac97,AC97_GPIO_CFG)); + */ + snd_ac97_write(ac97, AC97_GPIO_CFG, 0xffff & ~(AC97_GPIO_LINE1_OH)); + snd_ac97_write(ac97, AC97_GPIO_POLARITY, 0xffff & ~(AC97_GPIO_LINE1_OH)); + snd_ac97_write(ac97, AC97_GPIO_STICKY, 0xffff); + snd_ac97_write(ac97, AC97_GPIO_WAKEUP, 0x0); + snd_ac97_write(ac97, AC97_MISC_AFE, 0x0); + + /* build modem switches */ + for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_modem_switches); idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_ac97_controls_modem_switches[idx], ac97)); + if (err < 0) + return err; + } + + /* build chip specific controls */ + if (ac97->build_ops->build_specific) { + err = ac97->build_ops->build_specific(ac97); + if (err < 0) + return err; + } + + return 0; +} + +static int snd_ac97_test_rate(struct snd_ac97 *ac97, int reg, int shadow_reg, int rate) +{ + unsigned short val; + unsigned int tmp; + + tmp = ((unsigned int)rate * ac97->bus->clock) / 48000; + snd_ac97_write_cache(ac97, reg, tmp & 0xffff); + if (shadow_reg) + snd_ac97_write_cache(ac97, shadow_reg, tmp & 0xffff); + val = snd_ac97_read(ac97, reg); + return val == (tmp & 0xffff); +} + +static void snd_ac97_determine_rates(struct snd_ac97 *ac97, int reg, int shadow_reg, unsigned int *r_result) +{ + unsigned int result = 0; + unsigned short saved; + + if (ac97->bus->no_vra) { + *r_result = SNDRV_PCM_RATE_48000; + if ((ac97->flags & AC97_DOUBLE_RATE) && + reg == AC97_PCM_FRONT_DAC_RATE) + *r_result |= SNDRV_PCM_RATE_96000; + return; + } + + saved = snd_ac97_read(ac97, reg); + if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE) + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, + AC97_EA_DRA, 0); + /* test a non-standard rate */ + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 11000)) + result |= SNDRV_PCM_RATE_CONTINUOUS; + /* let's try to obtain standard rates */ + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 8000)) + result |= SNDRV_PCM_RATE_8000; + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 11025)) + result |= SNDRV_PCM_RATE_11025; + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 16000)) + result |= SNDRV_PCM_RATE_16000; + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 22050)) + result |= SNDRV_PCM_RATE_22050; + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 32000)) + result |= SNDRV_PCM_RATE_32000; + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 44100)) + result |= SNDRV_PCM_RATE_44100; + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 48000)) + result |= SNDRV_PCM_RATE_48000; + if ((ac97->flags & AC97_DOUBLE_RATE) && + reg == AC97_PCM_FRONT_DAC_RATE) { + /* test standard double rates */ + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, + AC97_EA_DRA, AC97_EA_DRA); + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 64000 / 2)) + result |= SNDRV_PCM_RATE_64000; + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 88200 / 2)) + result |= SNDRV_PCM_RATE_88200; + if (snd_ac97_test_rate(ac97, reg, shadow_reg, 96000 / 2)) + result |= SNDRV_PCM_RATE_96000; + /* some codecs don't support variable double rates */ + if (!snd_ac97_test_rate(ac97, reg, shadow_reg, 76100 / 2)) + result &= ~SNDRV_PCM_RATE_CONTINUOUS; + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, + AC97_EA_DRA, 0); + } + /* restore the default value */ + snd_ac97_write_cache(ac97, reg, saved); + if (shadow_reg) + snd_ac97_write_cache(ac97, shadow_reg, saved); + *r_result = result; +} + +/* check AC97_SPDIF register to accept which sample rates */ +static unsigned int snd_ac97_determine_spdif_rates(struct snd_ac97 *ac97) +{ + unsigned int result = 0; + int i; + static const unsigned short ctl_bits[] = { + AC97_SC_SPSR_44K, AC97_SC_SPSR_32K, AC97_SC_SPSR_48K + }; + static const unsigned int rate_bits[] = { + SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_32000, SNDRV_PCM_RATE_48000 + }; + + for (i = 0; i < (int)ARRAY_SIZE(ctl_bits); i++) { + snd_ac97_update_bits(ac97, AC97_SPDIF, AC97_SC_SPSR_MASK, ctl_bits[i]); + if ((snd_ac97_read(ac97, AC97_SPDIF) & AC97_SC_SPSR_MASK) == ctl_bits[i]) + result |= rate_bits[i]; + } + return result; +} + +/* look for the codec id table matching with the given id */ +static const struct ac97_codec_id *look_for_codec_id(const struct ac97_codec_id *table, + unsigned int id) +{ + const struct ac97_codec_id *pid; + + for (pid = table; pid->id; pid++) + if (pid->id == (id & pid->mask)) + return pid; + return NULL; +} + +void snd_ac97_get_name(struct snd_ac97 *ac97, unsigned int id, char *name, + size_t maxlen, int modem) +{ + const struct ac97_codec_id *pid; + + sprintf(name, "0x%x %c%c%c", id, + printable(id >> 24), + printable(id >> 16), + printable(id >> 8)); + pid = look_for_codec_id(snd_ac97_codec_id_vendors, id); + if (! pid) + return; + + strscpy(name, pid->name, maxlen); + if (ac97 && pid->patch) { + if ((modem && (pid->flags & AC97_MODEM_PATCH)) || + (! modem && ! (pid->flags & AC97_MODEM_PATCH))) + pid->patch(ac97); + } + + pid = look_for_codec_id(snd_ac97_codec_ids, id); + if (pid) { + strlcat(name, " ", maxlen); + strlcat(name, pid->name, maxlen); + if (pid->mask != 0xffffffff) + sprintf(name + strlen(name), " rev %u", id & ~pid->mask); + if (ac97 && pid->patch) { + if ((modem && (pid->flags & AC97_MODEM_PATCH)) || + (! modem && ! (pid->flags & AC97_MODEM_PATCH))) + pid->patch(ac97); + } + } else { + int l = strlen(name); + snprintf(name + l, maxlen - l, " id %x", id & 0xff); + } +} + +/** + * snd_ac97_get_short_name - retrieve codec name + * @ac97: the codec instance + * + * Return: The short identifying name of the codec. + */ +const char *snd_ac97_get_short_name(struct snd_ac97 *ac97) +{ + const struct ac97_codec_id *pid; + + for (pid = snd_ac97_codec_ids; pid->id; pid++) + if (pid->id == (ac97->id & pid->mask)) + return pid->name; + return "unknown codec"; +} + +EXPORT_SYMBOL(snd_ac97_get_short_name); + +/* wait for a while until registers are accessible after RESET + * return 0 if ok, negative not ready + */ +static int ac97_reset_wait(struct snd_ac97 *ac97, int timeout, int with_modem) +{ + unsigned long end_time; + unsigned short val; + + end_time = jiffies + timeout; + do { + + /* use preliminary reads to settle the communication */ + snd_ac97_read(ac97, AC97_RESET); + snd_ac97_read(ac97, AC97_VENDOR_ID1); + snd_ac97_read(ac97, AC97_VENDOR_ID2); + /* modem? */ + if (with_modem) { + val = snd_ac97_read(ac97, AC97_EXTENDED_MID); + if (val != 0xffff && (val & 1) != 0) + return 0; + } + if (ac97->scaps & AC97_SCAP_DETECT_BY_VENDOR) { + /* probably only Xbox issue - all registers are read as zero */ + val = snd_ac97_read(ac97, AC97_VENDOR_ID1); + if (val != 0 && val != 0xffff) + return 0; + } else { + /* because the PCM or MASTER volume registers can be modified, + * the REC_GAIN register is used for tests + */ + /* test if we can write to the record gain volume register */ + snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x8a05); + if ((snd_ac97_read(ac97, AC97_REC_GAIN) & 0x7fff) == 0x0a05) + return 0; + } + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + return -ENODEV; +} + +/** + * snd_ac97_bus - create an AC97 bus component + * @card: the card instance + * @num: the bus number + * @ops: the bus callbacks table + * @private_data: private data pointer for the new instance + * @rbus: the pointer to store the new AC97 bus instance. + * + * Creates an AC97 bus component. An struct snd_ac97_bus instance is newly + * allocated and initialized. + * + * The ops table must include valid callbacks (at least read and + * write). The other callbacks, wait and reset, are not mandatory. + * + * The clock is set to 48000. If another clock is needed, set + * ``(*rbus)->clock`` manually. + * + * The AC97 bus instance is registered as a low-level device, so you don't + * have to release it manually. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_bus(struct snd_card *card, int num, + const struct snd_ac97_bus_ops *ops, + void *private_data, struct snd_ac97_bus **rbus) +{ + int err; + struct snd_ac97_bus *bus; + static const struct snd_device_ops dev_ops = { + .dev_free = snd_ac97_bus_dev_free, + }; + + if (snd_BUG_ON(!card)) + return -EINVAL; + bus = kzalloc_obj(*bus); + if (bus == NULL) + return -ENOMEM; + bus->card = card; + bus->num = num; + bus->ops = ops; + bus->private_data = private_data; + bus->clock = 48000; + spin_lock_init(&bus->bus_lock); + snd_ac97_bus_proc_init(bus); + err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops); + if (err < 0) { + snd_ac97_bus_free(bus); + return err; + } + if (rbus) + *rbus = bus; + return 0; +} + +EXPORT_SYMBOL(snd_ac97_bus); + +/* stop no dev release warning */ +static void ac97_device_release(struct device * dev) +{ +} + +/* register ac97 codec to bus */ +static int snd_ac97_dev_register(struct snd_device *device) +{ + struct snd_ac97 *ac97 = device->device_data; + int err; + + ac97->dev.bus = &ac97_bus_type; + ac97->dev.parent = ac97->bus->card->dev; + ac97->dev.release = ac97_device_release; + dev_set_name(&ac97->dev, "%d-%d:%s", + ac97->bus->card->number, ac97->num, + snd_ac97_get_short_name(ac97)); + err = device_register(&ac97->dev); + if (err < 0) { + ac97_err(ac97, "Can't register ac97 bus\n"); + put_device(&ac97->dev); + ac97->dev.bus = NULL; + return err; + } + return 0; +} + +/* disconnect ac97 codec */ +static int snd_ac97_dev_disconnect(struct snd_device *device) +{ + struct snd_ac97 *ac97 = device->device_data; + if (ac97->dev.bus) + device_unregister(&ac97->dev); + return 0; +} + +/* build_ops to do nothing */ +static const struct snd_ac97_build_ops null_build_ops; + +#ifdef CONFIG_SND_AC97_POWER_SAVE +static void do_update_power(struct work_struct *work) +{ + update_power_regs( + container_of(work, struct snd_ac97, power_work.work)); +} +#endif + +/** + * snd_ac97_mixer - create an Codec97 component + * @bus: the AC97 bus which codec is attached to + * @template: the template of ac97, including index, callbacks and + * the private data. + * @rac97: the pointer to store the new ac97 instance. + * + * Creates an Codec97 component. An struct snd_ac97 instance is newly + * allocated and initialized from the template. The codec + * is then initialized by the standard procedure. + * + * The template must include the codec number (num) and address (addr), + * and the private data (private_data). + * + * The ac97 instance is registered as a low-level device, so you don't + * have to release it manually. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97) +{ + int err; + struct snd_ac97 *ac97; + struct snd_card *card; + char name[64]; + unsigned long end_time; + unsigned int reg; + const struct ac97_codec_id *pid; + static const struct snd_device_ops ops = { + .dev_free = snd_ac97_dev_free, + .dev_register = snd_ac97_dev_register, + .dev_disconnect = snd_ac97_dev_disconnect, + }; + + if (snd_BUG_ON(!bus || !template || !rac97)) + return -EINVAL; + *rac97 = NULL; + if (snd_BUG_ON(template->num >= 4)) + return -EINVAL; + if (bus->codec[template->num]) + return -EBUSY; + + card = bus->card; + ac97 = kzalloc_obj(*ac97); + if (ac97 == NULL) + return -ENOMEM; + ac97->private_data = template->private_data; + ac97->private_free = template->private_free; + ac97->bus = bus; + ac97->pci = template->pci; + ac97->num = template->num; + ac97->addr = template->addr; + ac97->scaps = template->scaps; + ac97->res_table = template->res_table; + bus->codec[ac97->num] = ac97; + mutex_init(&ac97->reg_mutex); + mutex_init(&ac97->page_mutex); +#ifdef CONFIG_SND_AC97_POWER_SAVE + INIT_DELAYED_WORK(&ac97->power_work, do_update_power); +#endif + +#ifdef CONFIG_PCI + if (ac97->pci) { + pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_VENDOR_ID, &ac97->subsystem_vendor); + pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_ID, &ac97->subsystem_device); + } +#endif + if (bus->ops->reset) { + bus->ops->reset(ac97); + goto __access_ok; + } + + ac97->id = snd_ac97_read(ac97, AC97_VENDOR_ID1) << 16; + ac97->id |= snd_ac97_read(ac97, AC97_VENDOR_ID2); + if (ac97->id && ac97->id != (unsigned int)-1) { + pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id); + if (pid && (pid->flags & AC97_DEFAULT_POWER_OFF)) + goto __access_ok; + } + + /* reset to defaults */ + if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO)) + snd_ac97_write(ac97, AC97_RESET, 0); + if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM)) + snd_ac97_write(ac97, AC97_EXTENDED_MID, 0); + if (bus->ops->wait) + bus->ops->wait(ac97); + else { + udelay(50); + if (ac97->scaps & AC97_SCAP_SKIP_AUDIO) + err = ac97_reset_wait(ac97, msecs_to_jiffies(500), 1); + else { + err = ac97_reset_wait(ac97, msecs_to_jiffies(500), 0); + if (err < 0) + err = ac97_reset_wait(ac97, + msecs_to_jiffies(500), 1); + } + if (err < 0) { + ac97_warn(ac97, "AC'97 %d does not respond - RESET\n", + ac97->num); + /* proceed anyway - it's often non-critical */ + } + } + __access_ok: + ac97->id = snd_ac97_read(ac97, AC97_VENDOR_ID1) << 16; + ac97->id |= snd_ac97_read(ac97, AC97_VENDOR_ID2); + if (! (ac97->scaps & AC97_SCAP_DETECT_BY_VENDOR) && + (ac97->id == 0x00000000 || ac97->id == 0xffffffff)) { + ac97_err(ac97, + "AC'97 %d access is not valid [0x%x], removing mixer.\n", + ac97->num, ac97->id); + snd_ac97_free(ac97); + return -EIO; + } + pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id); + if (pid) + ac97->flags |= pid->flags; + + /* test for AC'97 */ + if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO) && !(ac97->scaps & AC97_SCAP_AUDIO)) { + /* test if we can write to the record gain volume register */ + snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x8a06); + err = snd_ac97_read(ac97, AC97_REC_GAIN); + if ((err & 0x7fff) == 0x0a06) + ac97->scaps |= AC97_SCAP_AUDIO; + } + if (ac97->scaps & AC97_SCAP_AUDIO) { + ac97->caps = snd_ac97_read(ac97, AC97_RESET); + ac97->ext_id = snd_ac97_read(ac97, AC97_EXTENDED_ID); + if (ac97->ext_id == 0xffff) /* invalid combination */ + ac97->ext_id = 0; + } + + /* test for MC'97 */ + if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM) && !(ac97->scaps & AC97_SCAP_MODEM)) { + ac97->ext_mid = snd_ac97_read(ac97, AC97_EXTENDED_MID); + if (ac97->ext_mid == 0xffff) /* invalid combination */ + ac97->ext_mid = 0; + if (ac97->ext_mid & 1) + ac97->scaps |= AC97_SCAP_MODEM; + } + + if (!ac97_is_audio(ac97) && !ac97_is_modem(ac97)) { + if (!(ac97->scaps & (AC97_SCAP_SKIP_AUDIO|AC97_SCAP_SKIP_MODEM))) + ac97_err(ac97, + "AC'97 %d access error (not audio or modem codec)\n", + ac97->num); + snd_ac97_free(ac97); + return -EACCES; + } + + if (bus->ops->reset) // FIXME: always skipping? + goto __ready_ok; + + /* FIXME: add powerdown control */ + if (ac97_is_audio(ac97)) { + /* nothing should be in powerdown mode */ + snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0); + if (! (ac97->flags & AC97_DEFAULT_POWER_OFF)) { + snd_ac97_write_cache(ac97, AC97_RESET, 0); /* reset to defaults */ + udelay(100); + snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0); + } + /* nothing should be in powerdown mode */ + snd_ac97_write_cache(ac97, AC97_GENERAL_PURPOSE, 0); + end_time = jiffies + msecs_to_jiffies(5000); + do { + if ((snd_ac97_read(ac97, AC97_POWERDOWN) & 0x0f) == 0x0f) + goto __ready_ok; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + ac97_warn(ac97, + "AC'97 %d analog subsections not ready\n", ac97->num); + } + + /* FIXME: add powerdown control */ + if (ac97_is_modem(ac97)) { + unsigned char tmp; + + /* nothing should be in powerdown mode */ + /* note: it's important to set the rate at first */ + tmp = AC97_MEA_GPIO; + if (ac97->ext_mid & AC97_MEI_LINE1) { + snd_ac97_write_cache(ac97, AC97_LINE1_RATE, 8000); + tmp |= AC97_MEA_ADC1 | AC97_MEA_DAC1; + } + if (ac97->ext_mid & AC97_MEI_LINE2) { + snd_ac97_write_cache(ac97, AC97_LINE2_RATE, 8000); + tmp |= AC97_MEA_ADC2 | AC97_MEA_DAC2; + } + if (ac97->ext_mid & AC97_MEI_HANDSET) { + snd_ac97_write_cache(ac97, AC97_HANDSET_RATE, 8000); + tmp |= AC97_MEA_HADC | AC97_MEA_HDAC; + } + snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0); + udelay(100); + /* nothing should be in powerdown mode */ + snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0); + end_time = jiffies + msecs_to_jiffies(100); + do { + if ((snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS) & tmp) == tmp) + goto __ready_ok; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + ac97_warn(ac97, + "MC'97 %d converters and GPIO not ready (0x%x)\n", + ac97->num, + snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS)); + } + + __ready_ok: + if (ac97_is_audio(ac97)) + ac97->addr = (ac97->ext_id & AC97_EI_ADDR_MASK) >> AC97_EI_ADDR_SHIFT; + else + ac97->addr = (ac97->ext_mid & AC97_MEI_ADDR_MASK) >> AC97_MEI_ADDR_SHIFT; + if (ac97->ext_id & 0x01c9) { /* L/R, MIC, SDAC, LDAC VRA support */ + reg = snd_ac97_read(ac97, AC97_EXTENDED_STATUS); + reg |= ac97->ext_id & 0x01c0; /* LDAC/SDAC/CDAC */ + if (! bus->no_vra) + reg |= ac97->ext_id & 0x0009; /* VRA/VRM */ + snd_ac97_write_cache(ac97, AC97_EXTENDED_STATUS, reg); + } + if ((ac97->ext_id & AC97_EI_DRA) && bus->dra) { + /* Intel controllers require double rate data to be put in + * slots 7+8, so let's hope the codec supports it. */ + snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, AC97_GP_DRSS_MASK, AC97_GP_DRSS_78); + if ((snd_ac97_read(ac97, AC97_GENERAL_PURPOSE) & AC97_GP_DRSS_MASK) == AC97_GP_DRSS_78) + ac97->flags |= AC97_DOUBLE_RATE; + /* restore to slots 10/11 to avoid the confliction with surrounds */ + snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, AC97_GP_DRSS_MASK, 0); + } + if (ac97->ext_id & AC97_EI_VRA) { /* VRA support */ + snd_ac97_determine_rates(ac97, AC97_PCM_FRONT_DAC_RATE, 0, &ac97->rates[AC97_RATES_FRONT_DAC]); + snd_ac97_determine_rates(ac97, AC97_PCM_LR_ADC_RATE, 0, &ac97->rates[AC97_RATES_ADC]); + } else { + ac97->rates[AC97_RATES_FRONT_DAC] = SNDRV_PCM_RATE_48000; + if (ac97->flags & AC97_DOUBLE_RATE) + ac97->rates[AC97_RATES_FRONT_DAC] |= SNDRV_PCM_RATE_96000; + ac97->rates[AC97_RATES_ADC] = SNDRV_PCM_RATE_48000; + } + if (ac97->ext_id & AC97_EI_SPDIF) { + /* codec specific code (patch) should override these values */ + ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_32000; + } + if (ac97->ext_id & AC97_EI_VRM) { /* MIC VRA support */ + snd_ac97_determine_rates(ac97, AC97_PCM_MIC_ADC_RATE, 0, &ac97->rates[AC97_RATES_MIC_ADC]); + } else { + ac97->rates[AC97_RATES_MIC_ADC] = SNDRV_PCM_RATE_48000; + } + if (ac97->ext_id & AC97_EI_SDAC) { /* SDAC support */ + snd_ac97_determine_rates(ac97, AC97_PCM_SURR_DAC_RATE, AC97_PCM_FRONT_DAC_RATE, &ac97->rates[AC97_RATES_SURR_DAC]); + ac97->scaps |= AC97_SCAP_SURROUND_DAC; + } + if (ac97->ext_id & AC97_EI_LDAC) { /* LDAC support */ + snd_ac97_determine_rates(ac97, AC97_PCM_LFE_DAC_RATE, AC97_PCM_FRONT_DAC_RATE, &ac97->rates[AC97_RATES_LFE_DAC]); + ac97->scaps |= AC97_SCAP_CENTER_LFE_DAC; + } + /* additional initializations */ + if (bus->ops->init) + bus->ops->init(ac97); + snd_ac97_get_name(ac97, ac97->id, name, sizeof(name), !ac97_is_audio(ac97)); + snd_ac97_get_name(NULL, ac97->id, name, sizeof(name), !ac97_is_audio(ac97)); // ac97->id might be changed in the special setup code + if (! ac97->build_ops) + ac97->build_ops = &null_build_ops; + + if (ac97_is_audio(ac97)) { + char comp[16]; + if (card->mixername[0] == '\0') { + strscpy(card->mixername, name); + } else { + if (strlen(card->mixername) + 1 + strlen(name) + 1 <= sizeof(card->mixername)) { + strcat(card->mixername, ","); + strcat(card->mixername, name); + } + } + sprintf(comp, "AC97a:%08x", ac97->id); + err = snd_component_add(card, comp); + if (err < 0) { + snd_ac97_free(ac97); + return err; + } + if (snd_ac97_mixer_build(ac97) < 0) { + snd_ac97_free(ac97); + return -ENOMEM; + } + } + if (ac97_is_modem(ac97)) { + char comp[16]; + if (card->mixername[0] == '\0') { + strscpy(card->mixername, name); + } else { + if (strlen(card->mixername) + 1 + strlen(name) + 1 <= sizeof(card->mixername)) { + strcat(card->mixername, ","); + strcat(card->mixername, name); + } + } + sprintf(comp, "AC97m:%08x", ac97->id); + err = snd_component_add(card, comp); + if (err < 0) { + snd_ac97_free(ac97); + return err; + } + if (snd_ac97_modem_build(card, ac97) < 0) { + snd_ac97_free(ac97); + return -ENOMEM; + } + } + if (ac97_is_audio(ac97)) + update_power_regs(ac97); + snd_ac97_proc_init(ac97); + err = snd_device_new(card, SNDRV_DEV_CODEC, ac97, &ops); + if (err < 0) { + snd_ac97_free(ac97); + return err; + } + *rac97 = ac97; + return 0; +} + +EXPORT_SYMBOL(snd_ac97_mixer); + +/* + * Power down the chip. + * + * MASTER and HEADPHONE registers are muted but the register cache values + * are not changed, so that the values can be restored in snd_ac97_resume(). + */ +static void snd_ac97_powerdown(struct snd_ac97 *ac97) +{ + unsigned short power; + + if (ac97_is_audio(ac97)) { + /* some codecs have stereo mute bits */ + snd_ac97_write(ac97, AC97_MASTER, 0x9f9f); + snd_ac97_write(ac97, AC97_HEADPHONE, 0x9f9f); + } + + /* surround, CLFE, mic powerdown */ + power = ac97->regs[AC97_EXTENDED_STATUS]; + if (ac97->scaps & AC97_SCAP_SURROUND_DAC) + power |= AC97_EA_PRJ; + if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC) + power |= AC97_EA_PRI | AC97_EA_PRK; + power |= AC97_EA_PRL; + snd_ac97_write(ac97, AC97_EXTENDED_STATUS, power); + + /* powerdown external amplifier */ + if (ac97->scaps & AC97_SCAP_INV_EAPD) + power = ac97->regs[AC97_POWERDOWN] & ~AC97_PD_EAPD; + else if (! (ac97->scaps & AC97_SCAP_EAPD_LED)) + power = ac97->regs[AC97_POWERDOWN] | AC97_PD_EAPD; + power |= AC97_PD_PR6; /* Headphone amplifier powerdown */ + power |= AC97_PD_PR0 | AC97_PD_PR1; /* ADC & DAC powerdown */ + snd_ac97_write(ac97, AC97_POWERDOWN, power); + udelay(100); + power |= AC97_PD_PR2; /* Analog Mixer powerdown (Vref on) */ + snd_ac97_write(ac97, AC97_POWERDOWN, power); + if (ac97_is_power_save_mode(ac97)) { + power |= AC97_PD_PR3; /* Analog Mixer powerdown */ + snd_ac97_write(ac97, AC97_POWERDOWN, power); + udelay(100); + /* AC-link powerdown, internal Clk disable */ + /* FIXME: this may cause click noises on some boards */ + power |= AC97_PD_PR4 | AC97_PD_PR5; + snd_ac97_write(ac97, AC97_POWERDOWN, power); + } +} + + +struct ac97_power_reg { + unsigned short reg; + unsigned short power_reg; + unsigned short mask; +}; + +enum { PWIDX_ADC, PWIDX_FRONT, PWIDX_CLFE, PWIDX_SURR, PWIDX_MIC, PWIDX_SIZE }; + +static const struct ac97_power_reg power_regs[PWIDX_SIZE] = { + [PWIDX_ADC] = { AC97_PCM_LR_ADC_RATE, AC97_POWERDOWN, AC97_PD_PR0}, + [PWIDX_FRONT] = { AC97_PCM_FRONT_DAC_RATE, AC97_POWERDOWN, AC97_PD_PR1}, + [PWIDX_CLFE] = { AC97_PCM_LFE_DAC_RATE, AC97_EXTENDED_STATUS, + AC97_EA_PRI | AC97_EA_PRK}, + [PWIDX_SURR] = { AC97_PCM_SURR_DAC_RATE, AC97_EXTENDED_STATUS, + AC97_EA_PRJ}, + [PWIDX_MIC] = { AC97_PCM_MIC_ADC_RATE, AC97_EXTENDED_STATUS, + AC97_EA_PRL}, +}; + +#ifdef CONFIG_SND_AC97_POWER_SAVE +/** + * snd_ac97_update_power - update the powerdown register + * @ac97: the codec instance + * @reg: the rate register, e.g. AC97_PCM_FRONT_DAC_RATE + * @powerup: non-zero when power up the part + * + * Update the AC97 powerdown register bits of the given part. + * + * Return: Zero. + */ +int snd_ac97_update_power(struct snd_ac97 *ac97, int reg, int powerup) +{ + int i; + + if (! ac97) + return 0; + + if (reg) { + /* SPDIF requires DAC power, too */ + if (reg == AC97_SPDIF) + reg = AC97_PCM_FRONT_DAC_RATE; + for (i = 0; i < PWIDX_SIZE; i++) { + if (power_regs[i].reg == reg) { + if (powerup) + ac97->power_up |= (1 << i); + else + ac97->power_up &= ~(1 << i); + break; + } + } + } + + if (ac97_is_power_save_mode(ac97) && !powerup) + /* adjust power-down bits after two seconds delay + * (for avoiding loud click noises for many (OSS) apps + * that open/close frequently) + */ + schedule_delayed_work(&ac97->power_work, secs_to_jiffies(power_save)); + else { + cancel_delayed_work(&ac97->power_work); + update_power_regs(ac97); + } + + return 0; +} + +EXPORT_SYMBOL(snd_ac97_update_power); +#endif /* CONFIG_SND_AC97_POWER_SAVE */ + +static void update_power_regs(struct snd_ac97 *ac97) +{ + unsigned int power_up, bits; + int i; + + power_up = (1 << PWIDX_FRONT) | (1 << PWIDX_ADC); + power_up |= (1 << PWIDX_MIC); + if (ac97->scaps & AC97_SCAP_SURROUND_DAC) + power_up |= (1 << PWIDX_SURR); + if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC) + power_up |= (1 << PWIDX_CLFE); +#ifdef CONFIG_SND_AC97_POWER_SAVE + if (ac97_is_power_save_mode(ac97)) + power_up = ac97->power_up; +#endif + if (power_up) { + if (ac97->regs[AC97_POWERDOWN] & AC97_PD_PR2) { + /* needs power-up analog mix and vref */ + snd_ac97_update_bits(ac97, AC97_POWERDOWN, + AC97_PD_PR3, 0); + msleep(1); + snd_ac97_update_bits(ac97, AC97_POWERDOWN, + AC97_PD_PR2, 0); + } + } + for (i = 0; i < PWIDX_SIZE; i++) { + if (power_up & (1 << i)) + bits = 0; + else + bits = power_regs[i].mask; + snd_ac97_update_bits(ac97, power_regs[i].power_reg, + power_regs[i].mask, bits); + } + if (! power_up) { + if (! (ac97->regs[AC97_POWERDOWN] & AC97_PD_PR2)) { + /* power down analog mix and vref */ + snd_ac97_update_bits(ac97, AC97_POWERDOWN, + AC97_PD_PR2, AC97_PD_PR2); + snd_ac97_update_bits(ac97, AC97_POWERDOWN, + AC97_PD_PR3, AC97_PD_PR3); + } + } +} + + +#ifdef CONFIG_PM +/** + * snd_ac97_suspend - General suspend function for AC97 codec + * @ac97: the ac97 instance + * + * Suspends the codec, power down the chip. + */ +void snd_ac97_suspend(struct snd_ac97 *ac97) +{ + if (! ac97) + return; + if (ac97->build_ops->suspend) + ac97->build_ops->suspend(ac97); +#ifdef CONFIG_SND_AC97_POWER_SAVE + cancel_delayed_work_sync(&ac97->power_work); +#endif + snd_ac97_powerdown(ac97); +} + +EXPORT_SYMBOL(snd_ac97_suspend); + +/* + * restore ac97 status + */ +static void snd_ac97_restore_status(struct snd_ac97 *ac97) +{ + int i; + + for (i = 2; i < 0x7c ; i += 2) { + if (i == AC97_POWERDOWN || i == AC97_EXTENDED_ID) + continue; + /* restore only accessible registers + * some chip (e.g. nm256) may hang up when unsupported registers + * are accessed..! + */ + if (test_bit(i, ac97->reg_accessed)) { + snd_ac97_write(ac97, i, ac97->regs[i]); + snd_ac97_read(ac97, i); + } + } +} + +/* + * restore IEC958 status + */ +static void snd_ac97_restore_iec958(struct snd_ac97 *ac97) +{ + if (ac97->ext_id & AC97_EI_SPDIF) { + if (ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_SPDIF) { + /* reset spdif status */ + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); + snd_ac97_write(ac97, AC97_EXTENDED_STATUS, ac97->regs[AC97_EXTENDED_STATUS]); + if (ac97->flags & AC97_CS_SPDIF) + snd_ac97_write(ac97, AC97_CSR_SPDIF, ac97->regs[AC97_CSR_SPDIF]); + else + snd_ac97_write(ac97, AC97_SPDIF, ac97->regs[AC97_SPDIF]); + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); /* turn on again */ + } + } +} + +/** + * snd_ac97_resume - General resume function for AC97 codec + * @ac97: the ac97 instance + * + * Do the standard resume procedure, power up and restoring the + * old register values. + */ +void snd_ac97_resume(struct snd_ac97 *ac97) +{ + unsigned long end_time; + + if (! ac97) + return; + + if (ac97->bus->ops->reset) { + ac97->bus->ops->reset(ac97); + goto __reset_ready; + } + + snd_ac97_write(ac97, AC97_POWERDOWN, 0); + if (! (ac97->flags & AC97_DEFAULT_POWER_OFF)) { + if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO)) + snd_ac97_write(ac97, AC97_RESET, 0); + else if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM)) + snd_ac97_write(ac97, AC97_EXTENDED_MID, 0); + udelay(100); + snd_ac97_write(ac97, AC97_POWERDOWN, 0); + } + snd_ac97_write(ac97, AC97_GENERAL_PURPOSE, 0); + + snd_ac97_write(ac97, AC97_POWERDOWN, ac97->regs[AC97_POWERDOWN]); + if (ac97_is_audio(ac97)) { + ac97->bus->ops->write(ac97, AC97_MASTER, 0x8101); + end_time = jiffies + msecs_to_jiffies(100); + do { + if (snd_ac97_read(ac97, AC97_MASTER) == 0x8101) + break; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + /* FIXME: extra delay */ + ac97->bus->ops->write(ac97, AC97_MASTER, AC97_MUTE_MASK_MONO); + if (snd_ac97_read(ac97, AC97_MASTER) != AC97_MUTE_MASK_MONO) + msleep(250); + } else { + end_time = jiffies + msecs_to_jiffies(100); + do { + unsigned short val = snd_ac97_read(ac97, AC97_EXTENDED_MID); + if (val != 0xffff && (val & 1) != 0) + break; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + } +__reset_ready: + + if (ac97->bus->ops->init) + ac97->bus->ops->init(ac97); + + if (ac97->build_ops->resume) + ac97->build_ops->resume(ac97); + else { + snd_ac97_restore_status(ac97); + snd_ac97_restore_iec958(ac97); + } +} + +EXPORT_SYMBOL(snd_ac97_resume); +#endif + + +/* + * Hardware tuning + */ +static void set_ctl_name(char *dst, const char *src, const char *suffix) +{ + const size_t msize = SNDRV_CTL_ELEM_ID_NAME_MAXLEN; + + if (suffix) { + if (snprintf(dst, msize, "%s %s", src, suffix) >= msize) + pr_warn("ALSA: AC97 control name '%s %s' truncated to '%s'\n", + src, suffix, dst); + } else { + if (strscpy(dst, src, msize) < 0) + pr_warn("ALSA: AC97 control name '%s' truncated to '%s'\n", + src, dst); + } +} + +/* remove the control with the given name and optional suffix */ +static int snd_ac97_remove_ctl(struct snd_ac97 *ac97, const char *name, + const char *suffix) +{ + struct snd_ctl_elem_id id; + memset(&id, 0, sizeof(id)); + set_ctl_name(id.name, name, suffix); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + return snd_ctl_remove_id(ac97->bus->card, &id); +} + +static struct snd_kcontrol *ctl_find(struct snd_ac97 *ac97, const char *name, const char *suffix) +{ + struct snd_ctl_elem_id sid; + memset(&sid, 0, sizeof(sid)); + set_ctl_name(sid.name, name, suffix); + sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + return snd_ctl_find_id(ac97->bus->card, &sid); +} + +/* rename the control with the given name and optional suffix */ +static int snd_ac97_rename_ctl(struct snd_ac97 *ac97, const char *src, + const char *dst, const char *suffix) +{ + struct snd_kcontrol *kctl = ctl_find(ac97, src, suffix); + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + if (kctl) { + set_ctl_name(name, dst, suffix); + snd_ctl_rename(ac97->bus->card, kctl, name); + return 0; + } + return -ENOENT; +} + +/* rename both Volume and Switch controls - don't check the return value */ +static void snd_ac97_rename_vol_ctl(struct snd_ac97 *ac97, const char *src, + const char *dst) +{ + snd_ac97_rename_ctl(ac97, src, dst, "Switch"); + snd_ac97_rename_ctl(ac97, src, dst, "Volume"); +} + +/* swap controls */ +static int snd_ac97_swap_ctl(struct snd_ac97 *ac97, const char *s1, + const char *s2, const char *suffix) +{ + struct snd_kcontrol *kctl1, *kctl2; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + kctl1 = ctl_find(ac97, s1, suffix); + kctl2 = ctl_find(ac97, s2, suffix); + if (kctl1 && kctl2) { + set_ctl_name(name, s2, suffix); + snd_ctl_rename(ac97->bus->card, kctl1, name); + + set_ctl_name(name, s1, suffix); + snd_ctl_rename(ac97->bus->card, kctl2, name); + + return 0; + } + return -ENOENT; +} + +#if 1 +/* bind hp and master controls instead of using only hp control */ +static int bind_hp_volsw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + int err = snd_ac97_put_volsw(kcontrol, ucontrol); + if (err > 0) { + unsigned long priv_saved = kcontrol->private_value; + kcontrol->private_value = (kcontrol->private_value & ~0xff) | AC97_HEADPHONE; + snd_ac97_put_volsw(kcontrol, ucontrol); + kcontrol->private_value = priv_saved; + } + return err; +} + +/* ac97 tune: bind Master and Headphone controls */ +static int tune_hp_only(struct snd_ac97 *ac97) +{ + struct snd_kcontrol *msw = ctl_find(ac97, "Master Playback Switch", NULL); + struct snd_kcontrol *mvol = ctl_find(ac97, "Master Playback Volume", NULL); + if (! msw || ! mvol) + return -ENOENT; + msw->put = bind_hp_volsw_put; + mvol->put = bind_hp_volsw_put; + snd_ac97_remove_ctl(ac97, "Headphone Playback", "Switch"); + snd_ac97_remove_ctl(ac97, "Headphone Playback", "Volume"); + return 0; +} + +#else +/* ac97 tune: use Headphone control as master */ +static int tune_hp_only(struct snd_ac97 *ac97) +{ + if (ctl_find(ac97, "Headphone Playback Switch", NULL) == NULL) + return -ENOENT; + snd_ac97_remove_ctl(ac97, "Master Playback", "Switch"); + snd_ac97_remove_ctl(ac97, "Master Playback", "Volume"); + snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Master Playback"); + return 0; +} +#endif + +/* ac97 tune: swap Headphone and Master controls */ +static int tune_swap_hp(struct snd_ac97 *ac97) +{ + if (ctl_find(ac97, "Headphone Playback Switch", NULL) == NULL) + return -ENOENT; + snd_ac97_rename_vol_ctl(ac97, "Master Playback", "Line-Out Playback"); + snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Master Playback"); + return 0; +} + +/* ac97 tune: swap Surround and Master controls */ +static int tune_swap_surround(struct snd_ac97 *ac97) +{ + if (snd_ac97_swap_ctl(ac97, "Master Playback", "Surround Playback", "Switch") || + snd_ac97_swap_ctl(ac97, "Master Playback", "Surround Playback", "Volume")) + return -ENOENT; + return 0; +} + +/* ac97 tune: set up mic sharing for AD codecs */ +static int tune_ad_sharing(struct snd_ac97 *ac97) +{ + unsigned short scfg; + if ((ac97->id & 0xffffff00) != 0x41445300) { + ac97_err(ac97, "ac97_quirk AD_SHARING is only for AD codecs\n"); + return -EINVAL; + } + /* Turn on OMS bit to route microphone to back panel */ + scfg = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG); + snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, scfg | 0x0200); + return 0; +} + +static const struct snd_kcontrol_new snd_ac97_alc_jack_detect = +AC97_SINGLE("Jack Detect", AC97_ALC650_CLOCK, 5, 1, 0); + +/* ac97 tune: set up ALC jack-select */ +static int tune_alc_jack(struct snd_ac97 *ac97) +{ + if ((ac97->id & 0xffffff00) != 0x414c4700) { + ac97_err(ac97, + "ac97_quirk ALC_JACK is only for Realtek codecs\n"); + return -EINVAL; + } + snd_ac97_update_bits(ac97, 0x7a, 0x20, 0x20); /* select jack detect function */ + snd_ac97_update_bits(ac97, 0x7a, 0x01, 0x01); /* Line-out auto mute */ + if (ac97->id == AC97_ID_ALC658D) + snd_ac97_update_bits(ac97, 0x74, 0x0800, 0x0800); + return snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&snd_ac97_alc_jack_detect, ac97)); +} + +/* ac97 tune: inversed EAPD bit */ +static int tune_inv_eapd(struct snd_ac97 *ac97) +{ + struct snd_kcontrol *kctl = ctl_find(ac97, "External Amplifier", NULL); + if (! kctl) + return -ENOENT; + set_inv_eapd(ac97, kctl); + return 0; +} + +static int master_mute_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + int err = snd_ac97_put_volsw(kcontrol, ucontrol); + if (err > 0) { + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int shift = (kcontrol->private_value >> 8) & 0x0f; + int rshift = (kcontrol->private_value >> 12) & 0x0f; + unsigned short mask; + if (shift != rshift) + mask = AC97_MUTE_MASK_STEREO; + else + mask = AC97_MUTE_MASK_MONO; + snd_ac97_update_bits(ac97, AC97_POWERDOWN, AC97_PD_EAPD, + (ac97->regs[AC97_MASTER] & mask) == mask ? + AC97_PD_EAPD : 0); + } + return err; +} + +/* ac97 tune: EAPD controls mute LED bound with the master mute */ +static int tune_mute_led(struct snd_ac97 *ac97) +{ + struct snd_kcontrol *msw = ctl_find(ac97, "Master Playback Switch", NULL); + if (! msw) + return -ENOENT; + msw->put = master_mute_sw_put; + snd_ac97_remove_ctl(ac97, "External Amplifier", NULL); + snd_ac97_update_bits( + ac97, AC97_POWERDOWN, + AC97_PD_EAPD, AC97_PD_EAPD /* mute LED on */ + ); + ac97->scaps |= AC97_SCAP_EAPD_LED; + return 0; +} + +static int hp_master_mute_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int err = bind_hp_volsw_put(kcontrol, ucontrol); + if (err > 0) { + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int shift = (kcontrol->private_value >> 8) & 0x0f; + int rshift = (kcontrol->private_value >> 12) & 0x0f; + unsigned short mask; + if (shift != rshift) + mask = AC97_MUTE_MASK_STEREO; + else + mask = AC97_MUTE_MASK_MONO; + snd_ac97_update_bits(ac97, AC97_POWERDOWN, AC97_PD_EAPD, + (ac97->regs[AC97_MASTER] & mask) == mask ? + AC97_PD_EAPD : 0); + } + return err; +} + +static int tune_hp_mute_led(struct snd_ac97 *ac97) +{ + struct snd_kcontrol *msw = ctl_find(ac97, "Master Playback Switch", NULL); + struct snd_kcontrol *mvol = ctl_find(ac97, "Master Playback Volume", NULL); + if (! msw || ! mvol) + return -ENOENT; + msw->put = hp_master_mute_sw_put; + mvol->put = bind_hp_volsw_put; + snd_ac97_remove_ctl(ac97, "External Amplifier", NULL); + snd_ac97_remove_ctl(ac97, "Headphone Playback", "Switch"); + snd_ac97_remove_ctl(ac97, "Headphone Playback", "Volume"); + snd_ac97_update_bits( + ac97, AC97_POWERDOWN, + AC97_PD_EAPD, AC97_PD_EAPD /* mute LED on */ + ); + return 0; +} + +struct quirk_table { + const char *name; + int (*func)(struct snd_ac97 *); +}; + +static const struct quirk_table applicable_quirks[] = { + { "none", NULL }, + { "hp_only", tune_hp_only }, + { "swap_hp", tune_swap_hp }, + { "swap_surround", tune_swap_surround }, + { "ad_sharing", tune_ad_sharing }, + { "alc_jack", tune_alc_jack }, + { "inv_eapd", tune_inv_eapd }, + { "mute_led", tune_mute_led }, + { "hp_mute_led", tune_hp_mute_led }, +}; + +/* apply the quirk with the given type */ +static int apply_quirk(struct snd_ac97 *ac97, int type) +{ + if (type <= 0) + return 0; + else if (type >= ARRAY_SIZE(applicable_quirks)) + return -EINVAL; + if (applicable_quirks[type].func) + return applicable_quirks[type].func(ac97); + return 0; +} + +/* apply the quirk with the given name */ +static int apply_quirk_str(struct snd_ac97 *ac97, const char *typestr) +{ + int i; + const struct quirk_table *q; + + for (i = 0; i < ARRAY_SIZE(applicable_quirks); i++) { + q = &applicable_quirks[i]; + if (q->name && ! strcmp(typestr, q->name)) + return apply_quirk(ac97, i); + } + /* for compatibility, accept the numbers, too */ + if (*typestr >= '0' && *typestr <= '9') + return apply_quirk(ac97, (int)simple_strtoul(typestr, NULL, 10)); + return -EINVAL; +} + +/** + * snd_ac97_tune_hardware - tune up the hardware + * @ac97: the ac97 instance + * @quirk: quirk list + * @override: explicit quirk value (overrides the list if non-NULL) + * + * Do some workaround for each pci device, such as renaming of the + * headphone (true line-out) control as "Master". + * The quirk-list must be terminated with a zero-filled entry. + * + * Return: Zero if successful, or a negative error code on failure. + */ + +int snd_ac97_tune_hardware(struct snd_ac97 *ac97, + const struct ac97_quirk *quirk, const char *override) +{ + int result; + + /* quirk overriden? */ + if (override && strcmp(override, "-1") && strcmp(override, "default")) { + result = apply_quirk_str(ac97, override); + if (result < 0) + ac97_err(ac97, "applying quirk type %s failed (%d)\n", + override, result); + return result; + } + + if (! quirk) + return -EINVAL; + + for (; quirk->subvendor; quirk++) { + if (quirk->subvendor != ac97->subsystem_vendor) + continue; + if ((! quirk->mask && quirk->subdevice == ac97->subsystem_device) || + quirk->subdevice == (quirk->mask & ac97->subsystem_device)) { + if (quirk->codec_id && quirk->codec_id != ac97->id) + continue; + ac97_dbg(ac97, "ac97 quirk for %s (%04x:%04x)\n", + quirk->name, ac97->subsystem_vendor, + ac97->subsystem_device); + result = apply_quirk(ac97, quirk->type); + if (result < 0) + ac97_err(ac97, + "applying quirk type %d for %s failed (%d)\n", + quirk->type, quirk->name, result); + return result; + } + } + return 0; +} + +EXPORT_SYMBOL(snd_ac97_tune_hardware); diff --git a/sound/pci/ac97/ac97_id.h b/sound/pci/ac97/ac97_id.h new file mode 100644 index 000000000..ed6ed048b --- /dev/null +++ b/sound/pci/ac97/ac97_id.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + */ + +#define AC97_ID_AK4540 0x414b4d00 +#define AC97_ID_AK4542 0x414b4d01 +#define AC97_ID_AD1819 0x41445303 +#define AC97_ID_AD1881 0x41445340 +#define AC97_ID_AD1881A 0x41445348 +#define AC97_ID_AD1885 0x41445360 +#define AC97_ID_AD1886 0x41445361 +#define AC97_ID_AD1887 0x41445362 +#define AC97_ID_AD1886A 0x41445363 +#define AC97_ID_AD1980 0x41445370 +#define AC97_ID_TR28028 0x54524108 +#define AC97_ID_STAC9700 0x83847600 +#define AC97_ID_STAC9704 0x83847604 +#define AC97_ID_STAC9705 0x83847605 +#define AC97_ID_STAC9708 0x83847608 +#define AC97_ID_STAC9721 0x83847609 +#define AC97_ID_STAC9744 0x83847644 +#define AC97_ID_STAC9756 0x83847656 +#define AC97_ID_CS4297A 0x43525910 +#define AC97_ID_CS4299 0x43525930 +#define AC97_ID_CS4201 0x43525948 +#define AC97_ID_CS4205 0x43525958 +#define AC97_ID_CS_MASK 0xfffffff8 /* bit 0-2: rev */ +#define AC97_ID_ALC100 0x414c4300 +#define AC97_ID_ALC650 0x414c4720 +#define AC97_ID_ALC650D 0x414c4721 +#define AC97_ID_ALC650E 0x414c4722 +#define AC97_ID_ALC650F 0x414c4723 +#define AC97_ID_ALC655 0x414c4760 +#define AC97_ID_ALC658 0x414c4780 +#define AC97_ID_ALC658D 0x414c4781 +#define AC97_ID_ALC850 0x414c4790 +#define AC97_ID_YMF743 0x594d4800 +#define AC97_ID_YMF753 0x594d4803 +#define AC97_ID_VT1616 0x49434551 +#define AC97_ID_CM9738 0x434d4941 +#define AC97_ID_CM9739 0x434d4961 +#define AC97_ID_CM9761_78 0x434d4978 +#define AC97_ID_CM9761_82 0x434d4982 +#define AC97_ID_CM9761_83 0x434d4983 +#define AC97_ID_ST7597 0x53544d02 +#define AC97_ID_ST_AC97_ID4 0x53544d04 diff --git a/sound/pci/ac97/ac97_local.h b/sound/pci/ac97/ac97_local.h new file mode 100644 index 000000000..965284eb4 --- /dev/null +++ b/sound/pci/ac97/ac97_local.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + */ + +void snd_ac97_get_name(struct snd_ac97 *ac97, unsigned int id, char *name, + size_t maxlen, int modem); +int snd_ac97_update_bits_nolock(struct snd_ac97 *ac97, unsigned short reg, + unsigned short mask, unsigned short value); + +/* ac97_proc.c */ +#ifdef CONFIG_SND_PROC_FS +void snd_ac97_bus_proc_init(struct snd_ac97_bus * ac97); +void snd_ac97_bus_proc_done(struct snd_ac97_bus * ac97); +void snd_ac97_proc_init(struct snd_ac97 * ac97); +void snd_ac97_proc_done(struct snd_ac97 * ac97); +#else +#define snd_ac97_bus_proc_init(ac97_bus_t) do { } while (0) +#define snd_ac97_bus_proc_done(ac97_bus_t) do { } while (0) +#define snd_ac97_proc_init(ac97_t) do { } while (0) +#define snd_ac97_proc_done(ac97_t) do { } while (0) +#endif diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c new file mode 100644 index 000000000..64cc39dd2 --- /dev/null +++ b/sound/pci/ac97/ac97_patch.c @@ -0,0 +1,3915 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com) and to datasheets + * for specific codecs. + */ + +#include "ac97_local.h" +#include "ac97_patch.h" + +/* + * Forward declarations + */ + +static struct snd_kcontrol *snd_ac97_find_mixer_ctl(struct snd_ac97 *ac97, + const char *name); +static int snd_ac97_add_vmaster(struct snd_ac97 *ac97, char *name, + const unsigned int *tlv, + const char * const *followers); + +/* + * Chip specific initialization + */ + +static int patch_build_controls(struct snd_ac97 * ac97, const struct snd_kcontrol_new *controls, int count) +{ + int idx, err; + + for (idx = 0; idx < count; idx++) { + err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&controls[idx], ac97)); + if (err < 0) + return err; + } + return 0; +} + +/* replace with a new TLV */ +static void reset_tlv(struct snd_ac97 *ac97, const char *name, + const unsigned int *tlv) +{ + struct snd_kcontrol *kctl; + + kctl = snd_ctl_find_id_mixer(ac97->bus->card, name); + if (kctl && kctl->tlv.p) + kctl->tlv.p = tlv; +} + +/* set to the page, update bits and restore the page */ +static int ac97_update_bits_page(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value, unsigned short page) +{ + unsigned short page_save; + int ret; + + guard(mutex)(&ac97->page_mutex); + page_save = snd_ac97_read(ac97, AC97_INT_PAGING) & AC97_PAGE_MASK; + snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page); + ret = snd_ac97_update_bits(ac97, reg, mask, value); + snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page_save); + return ret; +} + +/* + * shared line-in/mic controls + */ +static int ac97_surround_jack_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "Shared", "Independent" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int ac97_surround_jack_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = ac97->indep_surround; + return 0; +} + +static int ac97_surround_jack_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned char indep = !!ucontrol->value.enumerated.item[0]; + + if (indep != ac97->indep_surround) { + ac97->indep_surround = indep; + if (ac97->build_ops->update_jacks) + ac97->build_ops->update_jacks(ac97); + return 1; + } + return 0; +} + +static int ac97_channel_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "2ch", "4ch", "6ch", "8ch" }; + + return snd_ctl_enum_info(uinfo, 1, kcontrol->private_value, texts); +} + +static int ac97_channel_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = ac97->channel_mode; + return 0; +} + +static int ac97_channel_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned char mode = ucontrol->value.enumerated.item[0]; + + if (mode >= kcontrol->private_value) + return -EINVAL; + + if (mode != ac97->channel_mode) { + ac97->channel_mode = mode; + if (ac97->build_ops->update_jacks) + ac97->build_ops->update_jacks(ac97); + return 1; + } + return 0; +} + +#define AC97_SURROUND_JACK_MODE_CTL \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Surround Jack Mode", \ + .info = ac97_surround_jack_mode_info, \ + .get = ac97_surround_jack_mode_get, \ + .put = ac97_surround_jack_mode_put, \ + } +/* 6ch */ +#define AC97_CHANNEL_MODE_CTL \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Channel Mode", \ + .info = ac97_channel_mode_info, \ + .get = ac97_channel_mode_get, \ + .put = ac97_channel_mode_put, \ + .private_value = 3, \ + } +/* 4ch */ +#define AC97_CHANNEL_MODE_4CH_CTL \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Channel Mode", \ + .info = ac97_channel_mode_info, \ + .get = ac97_channel_mode_get, \ + .put = ac97_channel_mode_put, \ + .private_value = 2, \ + } +/* 8ch */ +#define AC97_CHANNEL_MODE_8CH_CTL \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Channel Mode", \ + .info = ac97_channel_mode_info, \ + .get = ac97_channel_mode_get, \ + .put = ac97_channel_mode_put, \ + .private_value = 4, \ + } + +static inline int is_surround_on(struct snd_ac97 *ac97) +{ + return ac97->channel_mode >= 1; +} + +static inline int is_clfe_on(struct snd_ac97 *ac97) +{ + return ac97->channel_mode >= 2; +} + +/* system has shared jacks with surround out enabled */ +static inline int is_shared_surrout(struct snd_ac97 *ac97) +{ + return !ac97->indep_surround && is_surround_on(ac97); +} + +/* system has shared jacks with center/lfe out enabled */ +static inline int is_shared_clfeout(struct snd_ac97 *ac97) +{ + return !ac97->indep_surround && is_clfe_on(ac97); +} + +/* system has shared jacks with line in enabled */ +static inline int is_shared_linein(struct snd_ac97 *ac97) +{ + return !ac97->indep_surround && !is_surround_on(ac97); +} + +/* system has shared jacks with mic in enabled */ +static inline int is_shared_micin(struct snd_ac97 *ac97) +{ + return !ac97->indep_surround && !is_clfe_on(ac97); +} + +static inline int alc850_is_aux_back_surround(struct snd_ac97 *ac97) +{ + return is_surround_on(ac97); +} + +/* The following snd_ac97_ymf753_... items added by David Shust (dshust@shustring.com) */ +/* Modified for YMF743 by Keita Maehara */ + +/* It is possible to indicate to the Yamaha YMF7x3 the type of + speakers being used. */ + +static int snd_ac97_ymf7x3_info_speaker(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "Standard", "Small", "Smaller" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_ac97_ymf7x3_get_speaker(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_YMF7X3_3D_MODE_SEL]; + val = (val >> 10) & 3; + if (val > 0) /* 0 = invalid */ + val--; + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int snd_ac97_ymf7x3_put_speaker(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + val = (ucontrol->value.enumerated.item[0] + 1) << 10; + return snd_ac97_update(ac97, AC97_YMF7X3_3D_MODE_SEL, val); +} + +static const struct snd_kcontrol_new snd_ac97_ymf7x3_controls_speaker = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "3D Control - Speaker", + .info = snd_ac97_ymf7x3_info_speaker, + .get = snd_ac97_ymf7x3_get_speaker, + .put = snd_ac97_ymf7x3_put_speaker, +}; + +/* It is possible to indicate to the Yamaha YMF7x3 the source to + direct to the S/PDIF output. */ +static int snd_ac97_ymf7x3_spdif_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "AC-Link", "A/D Converter" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_ac97_ymf7x3_spdif_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_YMF7X3_DIT_CTRL]; + ucontrol->value.enumerated.item[0] = (val >> 1) & 1; + return 0; +} + +static int snd_ac97_ymf7x3_spdif_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + val = ucontrol->value.enumerated.item[0] << 1; + return snd_ac97_update_bits(ac97, AC97_YMF7X3_DIT_CTRL, 0x0002, val); +} + +static int patch_yamaha_ymf7x3_3d(struct snd_ac97 *ac97) +{ + struct snd_kcontrol *kctl; + int err; + + kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97); + err = snd_ctl_add(ac97->bus->card, kctl); + if (err < 0) + return err; + strscpy(kctl->id.name, "3D Control - Wide"); + kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 9, 7, 0); + snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000); + err = snd_ctl_add(ac97->bus->card, + snd_ac97_cnew(&snd_ac97_ymf7x3_controls_speaker, + ac97)); + if (err < 0) + return err; + snd_ac97_write_cache(ac97, AC97_YMF7X3_3D_MODE_SEL, 0x0c00); + return 0; +} + +static const struct snd_kcontrol_new snd_ac97_yamaha_ymf743_controls_spdif[3] = +{ + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), + AC97_YMF7X3_DIT_CTRL, 0, 1, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Source", + .info = snd_ac97_ymf7x3_spdif_source_info, + .get = snd_ac97_ymf7x3_spdif_source_get, + .put = snd_ac97_ymf7x3_spdif_source_put, + }, + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("", NONE, NONE) "Mute", + AC97_YMF7X3_DIT_CTRL, 2, 1, 1) +}; + +static int patch_yamaha_ymf743_build_spdif(struct snd_ac97 *ac97) +{ + int err; + + err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3); + if (err < 0) + return err; + err = patch_build_controls(ac97, + snd_ac97_yamaha_ymf743_controls_spdif, 3); + if (err < 0) + return err; + /* set default PCM S/PDIF params */ + /* PCM audio,no copyright,no preemphasis,PCM coder,original */ + snd_ac97_write_cache(ac97, AC97_YMF7X3_DIT_CTRL, 0xa201); + return 0; +} + +static const struct snd_ac97_build_ops patch_yamaha_ymf743_ops = { + .build_spdif = patch_yamaha_ymf743_build_spdif, + .build_3d = patch_yamaha_ymf7x3_3d, +}; + +static int patch_yamaha_ymf743(struct snd_ac97 *ac97) +{ + ac97->build_ops = &patch_yamaha_ymf743_ops; + ac97->caps |= AC97_BC_BASS_TREBLE; + ac97->caps |= 0x04 << 10; /* Yamaha 3D enhancement */ + ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */ + ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */ + return 0; +} + +/* The AC'97 spec states that the S/PDIF signal is to be output at pin 48. + The YMF753 will output the S/PDIF signal to pin 43, 47 (EAPD), or 48. + By default, no output pin is selected, and the S/PDIF signal is not output. + There is also a bit to mute S/PDIF output in a vendor-specific register. */ +static int snd_ac97_ymf753_spdif_output_pin_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { "Disabled", "Pin 43", "Pin 48" }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_ac97_ymf753_spdif_output_pin_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_YMF7X3_DIT_CTRL]; + ucontrol->value.enumerated.item[0] = (val & 0x0008) ? 2 : (val & 0x0020) ? 1 : 0; + return 0; +} + +static int snd_ac97_ymf753_spdif_output_pin_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + val = (ucontrol->value.enumerated.item[0] == 2) ? 0x0008 : + (ucontrol->value.enumerated.item[0] == 1) ? 0x0020 : 0; + return snd_ac97_update_bits(ac97, AC97_YMF7X3_DIT_CTRL, 0x0028, val); + /* The following can be used to direct S/PDIF output to pin 47 (EAPD). + snd_ac97_write_cache(ac97, 0x62, snd_ac97_read(ac97, 0x62) | 0x0008); */ +} + +static const struct snd_kcontrol_new snd_ac97_ymf753_controls_spdif[3] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + .info = snd_ac97_ymf7x3_spdif_source_info, + .get = snd_ac97_ymf7x3_spdif_source_get, + .put = snd_ac97_ymf7x3_spdif_source_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Output Pin", + .info = snd_ac97_ymf753_spdif_output_pin_info, + .get = snd_ac97_ymf753_spdif_output_pin_get, + .put = snd_ac97_ymf753_spdif_output_pin_put, + }, + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("", NONE, NONE) "Mute", + AC97_YMF7X3_DIT_CTRL, 2, 1, 1) +}; + +static int patch_yamaha_ymf753_post_spdif(struct snd_ac97 * ac97) +{ + int err; + + err = patch_build_controls(ac97, snd_ac97_ymf753_controls_spdif, ARRAY_SIZE(snd_ac97_ymf753_controls_spdif)); + if (err < 0) + return err; + return 0; +} + +static const struct snd_ac97_build_ops patch_yamaha_ymf753_ops = { + .build_3d = patch_yamaha_ymf7x3_3d, + .build_post_spdif = patch_yamaha_ymf753_post_spdif +}; + +static int patch_yamaha_ymf753(struct snd_ac97 * ac97) +{ + /* Patch for Yamaha YMF753, Copyright (c) by David Shust, dshust@shustring.com. + This chip has nonstandard and extended behaviour with regard to its S/PDIF output. + The AC'97 spec states that the S/PDIF signal is to be output at pin 48. + The YMF753 will ouput the S/PDIF signal to pin 43, 47 (EAPD), or 48. + By default, no output pin is selected, and the S/PDIF signal is not output. + There is also a bit to mute S/PDIF output in a vendor-specific register. + */ + ac97->build_ops = &patch_yamaha_ymf753_ops; + ac97->caps |= AC97_BC_BASS_TREBLE; + ac97->caps |= 0x04 << 10; /* Yamaha 3D enhancement */ + return 0; +} + +/* + * May 2, 2003 Liam Girdwood + * removed broken wolfson00 patch. + * added support for WM9705,WM9708,WM9709,WM9710,WM9711,WM9712 and WM9717. + */ + +static const struct snd_kcontrol_new wm97xx_snd_ac97_controls[] = { +AC97_DOUBLE("Front Playback Volume", AC97_WM97XX_FMIXER_VOL, 8, 0, 31, 1), +AC97_SINGLE("Front Playback Switch", AC97_WM97XX_FMIXER_VOL, 15, 1, 1), +}; + +static int patch_wolfson_wm9703_specific(struct snd_ac97 * ac97) +{ + /* This is known to work for the ViewSonic ViewPad 1000 + * Randolph Bentson + * WM9703/9707/9708/9717 + */ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm97xx_snd_ac97_controls); i++) { + err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm97xx_snd_ac97_controls[i], ac97)); + if (err < 0) + return err; + } + snd_ac97_write_cache(ac97, AC97_WM97XX_FMIXER_VOL, 0x0808); + return 0; +} + +static const struct snd_ac97_build_ops patch_wolfson_wm9703_ops = { + .build_specific = patch_wolfson_wm9703_specific, +}; + +static int patch_wolfson03(struct snd_ac97 * ac97) +{ + ac97->build_ops = &patch_wolfson_wm9703_ops; + return 0; +} + +static const struct snd_kcontrol_new wm9704_snd_ac97_controls[] = { +AC97_DOUBLE("Front Playback Volume", AC97_WM97XX_FMIXER_VOL, 8, 0, 31, 1), +AC97_SINGLE("Front Playback Switch", AC97_WM97XX_FMIXER_VOL, 15, 1, 1), +AC97_DOUBLE("Rear Playback Volume", AC97_WM9704_RMIXER_VOL, 8, 0, 31, 1), +AC97_SINGLE("Rear Playback Switch", AC97_WM9704_RMIXER_VOL, 15, 1, 1), +AC97_DOUBLE("Rear DAC Volume", AC97_WM9704_RPCM_VOL, 8, 0, 31, 1), +AC97_DOUBLE("Surround Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1), +}; + +static int patch_wolfson_wm9704_specific(struct snd_ac97 * ac97) +{ + int err, i; + for (i = 0; i < ARRAY_SIZE(wm9704_snd_ac97_controls); i++) { + err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm9704_snd_ac97_controls[i], ac97)); + if (err < 0) + return err; + } + /* patch for DVD noise */ + snd_ac97_write_cache(ac97, AC97_WM9704_TEST, 0x0200); + return 0; +} + +static const struct snd_ac97_build_ops patch_wolfson_wm9704_ops = { + .build_specific = patch_wolfson_wm9704_specific, +}; + +static int patch_wolfson04(struct snd_ac97 * ac97) +{ + /* WM9704M/9704Q */ + ac97->build_ops = &patch_wolfson_wm9704_ops; + return 0; +} + +static int patch_wolfson05(struct snd_ac97 * ac97) +{ + /* WM9705, WM9710 */ + ac97->build_ops = &patch_wolfson_wm9703_ops; +#ifdef CONFIG_TOUCHSCREEN_WM9705 + /* WM9705 touchscreen uses AUX and VIDEO for touch */ + ac97->flags |= AC97_HAS_NO_VIDEO | AC97_HAS_NO_AUX; +#endif + return 0; +} + +static const char* wm9711_alc_select[] = {"None", "Left", "Right", "Stereo"}; +static const char* wm9711_alc_mix[] = {"Stereo", "Right", "Left", "None"}; +static const char* wm9711_out3_src[] = {"Left", "VREF", "Left + Right", "Mono"}; +static const char* wm9711_out3_lrsrc[] = {"Master Mix", "Headphone Mix"}; +static const char* wm9711_rec_adc[] = {"Stereo", "Left", "Right", "Mute"}; +static const char* wm9711_base[] = {"Linear Control", "Adaptive Boost"}; +static const char* wm9711_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"}; +static const char* wm9711_mic[] = {"Mic 1", "Differential", "Mic 2", "Stereo"}; +static const char* wm9711_rec_sel[] = + {"Mic 1", "NC", "NC", "Master Mix", "Line", "Headphone Mix", "Phone Mix", "Phone"}; +static const char* wm9711_ng_type[] = {"Constant Gain", "Mute"}; + +static const struct ac97_enum wm9711_enum[] = { +AC97_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9711_alc_select), +AC97_ENUM_SINGLE(AC97_VIDEO, 10, 4, wm9711_alc_mix), +AC97_ENUM_SINGLE(AC97_AUX, 9, 4, wm9711_out3_src), +AC97_ENUM_SINGLE(AC97_AUX, 8, 2, wm9711_out3_lrsrc), +AC97_ENUM_SINGLE(AC97_REC_SEL, 12, 4, wm9711_rec_adc), +AC97_ENUM_SINGLE(AC97_MASTER_TONE, 15, 2, wm9711_base), +AC97_ENUM_DOUBLE(AC97_REC_GAIN, 14, 6, 2, wm9711_rec_gain), +AC97_ENUM_SINGLE(AC97_MIC, 5, 4, wm9711_mic), +AC97_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, wm9711_rec_sel), +AC97_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9711_ng_type), +}; + +static const struct snd_kcontrol_new wm9711_snd_ac97_controls[] = { +AC97_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), +AC97_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), +AC97_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0), +AC97_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0), +AC97_ENUM("ALC Function", wm9711_enum[0]), +AC97_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 1), +AC97_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 1), +AC97_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0), +AC97_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0), +AC97_ENUM("ALC NG Type", wm9711_enum[9]), +AC97_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 1), + +AC97_SINGLE("Side Tone Switch", AC97_VIDEO, 15, 1, 1), +AC97_SINGLE("Side Tone Volume", AC97_VIDEO, 12, 7, 1), +AC97_ENUM("ALC Headphone Mux", wm9711_enum[1]), +AC97_SINGLE("ALC Headphone Volume", AC97_VIDEO, 7, 7, 1), + +AC97_SINGLE("Out3 Switch", AC97_AUX, 15, 1, 1), +AC97_SINGLE("Out3 ZC Switch", AC97_AUX, 7, 1, 0), +AC97_ENUM("Out3 Mux", wm9711_enum[2]), +AC97_ENUM("Out3 LR Mux", wm9711_enum[3]), +AC97_SINGLE("Out3 Volume", AC97_AUX, 0, 31, 1), + +AC97_SINGLE("Beep to Headphone Switch", AC97_PC_BEEP, 15, 1, 1), +AC97_SINGLE("Beep to Headphone Volume", AC97_PC_BEEP, 12, 7, 1), +AC97_SINGLE("Beep to Side Tone Switch", AC97_PC_BEEP, 11, 1, 1), +AC97_SINGLE("Beep to Side Tone Volume", AC97_PC_BEEP, 8, 7, 1), +AC97_SINGLE("Beep to Phone Switch", AC97_PC_BEEP, 7, 1, 1), +AC97_SINGLE("Beep to Phone Volume", AC97_PC_BEEP, 4, 7, 1), + +AC97_SINGLE("Aux to Headphone Switch", AC97_CD, 15, 1, 1), +AC97_SINGLE("Aux to Headphone Volume", AC97_CD, 12, 7, 1), +AC97_SINGLE("Aux to Side Tone Switch", AC97_CD, 11, 1, 1), +AC97_SINGLE("Aux to Side Tone Volume", AC97_CD, 8, 7, 1), +AC97_SINGLE("Aux to Phone Switch", AC97_CD, 7, 1, 1), +AC97_SINGLE("Aux to Phone Volume", AC97_CD, 4, 7, 1), + +AC97_SINGLE("Phone to Headphone Switch", AC97_PHONE, 15, 1, 1), +AC97_SINGLE("Phone to Master Switch", AC97_PHONE, 14, 1, 1), + +AC97_SINGLE("Line to Headphone Switch", AC97_LINE, 15, 1, 1), +AC97_SINGLE("Line to Master Switch", AC97_LINE, 14, 1, 1), +AC97_SINGLE("Line to Phone Switch", AC97_LINE, 13, 1, 1), + +AC97_SINGLE("PCM Playback to Headphone Switch", AC97_PCM, 15, 1, 1), +AC97_SINGLE("PCM Playback to Master Switch", AC97_PCM, 14, 1, 1), +AC97_SINGLE("PCM Playback to Phone Switch", AC97_PCM, 13, 1, 1), + +AC97_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0), +AC97_ENUM("Capture to Phone Mux", wm9711_enum[4]), +AC97_SINGLE("Capture to Phone 20dB Boost Switch", AC97_REC_SEL, 11, 1, 1), +AC97_ENUM("Capture Select", wm9711_enum[8]), + +AC97_SINGLE("3D Upper Cut-off Switch", AC97_3D_CONTROL, 5, 1, 1), +AC97_SINGLE("3D Lower Cut-off Switch", AC97_3D_CONTROL, 4, 1, 1), + +AC97_ENUM("Bass Control", wm9711_enum[5]), +AC97_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1), +AC97_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1), +AC97_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0), + +AC97_SINGLE("ADC Switch", AC97_REC_GAIN, 15, 1, 1), +AC97_ENUM("Capture Volume Steps", wm9711_enum[6]), +AC97_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 63, 1), +AC97_SINGLE("Capture ZC Switch", AC97_REC_GAIN, 7, 1, 0), + +AC97_SINGLE("Mic 1 to Phone Switch", AC97_MIC, 14, 1, 1), +AC97_SINGLE("Mic 2 to Phone Switch", AC97_MIC, 13, 1, 1), +AC97_ENUM("Mic Select Source", wm9711_enum[7]), +AC97_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1), +AC97_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1), +AC97_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0), + +AC97_SINGLE("Master Left Inv Switch", AC97_MASTER, 6, 1, 0), +AC97_SINGLE("Master ZC Switch", AC97_MASTER, 7, 1, 0), +AC97_SINGLE("Headphone ZC Switch", AC97_HEADPHONE, 7, 1, 0), +AC97_SINGLE("Mono ZC Switch", AC97_MASTER_MONO, 7, 1, 0), +}; + +static int patch_wolfson_wm9711_specific(struct snd_ac97 * ac97) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm9711_snd_ac97_controls); i++) { + err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm9711_snd_ac97_controls[i], ac97)); + if (err < 0) + return err; + } + snd_ac97_write_cache(ac97, AC97_CODEC_CLASS_REV, 0x0808); + snd_ac97_write_cache(ac97, AC97_PCI_SVID, 0x0808); + snd_ac97_write_cache(ac97, AC97_VIDEO, 0x0808); + snd_ac97_write_cache(ac97, AC97_AUX, 0x0808); + snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x0808); + snd_ac97_write_cache(ac97, AC97_CD, 0x0000); + return 0; +} + +static const struct snd_ac97_build_ops patch_wolfson_wm9711_ops = { + .build_specific = patch_wolfson_wm9711_specific, +}; + +static int patch_wolfson11(struct snd_ac97 * ac97) +{ + /* WM9711, WM9712 */ + ac97->build_ops = &patch_wolfson_wm9711_ops; + + ac97->flags |= AC97_HAS_NO_REC_GAIN | AC97_STEREO_MUTES | AC97_HAS_NO_MIC | + AC97_HAS_NO_PC_BEEP | AC97_HAS_NO_VIDEO | AC97_HAS_NO_CD; + + return 0; +} + +static const char* wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"}; +static const char* wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"}; +static const char* wm9713_rec_src[] = + {"Mic 1", "Mic 2", "Line", "Mono In", "Headphone Mix", "Master Mix", + "Mono Mix", "Zh"}; +static const char* wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"}; +static const char* wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"}; +static const char* wm9713_mono_pga[] = {"Vmid", "Zh", "Mono Mix", "Inv 1"}; +static const char* wm9713_spk_pga[] = + {"Vmid", "Zh", "Headphone Mix", "Master Mix", "Inv", "NC", "NC", "NC"}; +static const char* wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone Mix", "NC"}; +static const char* wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "NC"}; +static const char* wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "NC"}; +static const char* wm9713_dac_inv[] = + {"Off", "Mono Mix", "Master Mix", "Headphone Mix L", "Headphone Mix R", + "Headphone Mix Mono", "NC", "Vmid"}; +static const char* wm9713_base[] = {"Linear Control", "Adaptive Boost"}; +static const char* wm9713_ng_type[] = {"Constant Gain", "Mute"}; + +static const struct ac97_enum wm9713_enum[] = { +AC97_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), +AC97_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), +AC97_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), +AC97_ENUM_DOUBLE(AC97_VIDEO, 3, 0, 8, wm9713_rec_src), +AC97_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), +AC97_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), +AC97_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), +AC97_ENUM_DOUBLE(AC97_REC_GAIN, 11, 8, 8, wm9713_spk_pga), +AC97_ENUM_DOUBLE(AC97_REC_GAIN, 6, 4, 4, wm9713_hp_pga), +AC97_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), +AC97_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), +AC97_ENUM_DOUBLE(AC97_REC_GAIN_MIC, 13, 10, 8, wm9713_dac_inv), +AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_base), +AC97_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), +}; + +static const struct snd_kcontrol_new wm13_snd_ac97_controls[] = { +AC97_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1), +AC97_SINGLE("Line In to Headphone Switch", AC97_PC_BEEP, 15, 1, 1), +AC97_SINGLE("Line In to Master Switch", AC97_PC_BEEP, 14, 1, 1), +AC97_SINGLE("Line In to Mono Switch", AC97_PC_BEEP, 13, 1, 1), + +AC97_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1), +AC97_SINGLE("PCM Playback to Headphone Switch", AC97_PHONE, 15, 1, 1), +AC97_SINGLE("PCM Playback to Master Switch", AC97_PHONE, 14, 1, 1), +AC97_SINGLE("PCM Playback to Mono Switch", AC97_PHONE, 13, 1, 1), + +AC97_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1), +AC97_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1), +AC97_SINGLE("Mic 1 to Mono Switch", AC97_LINE, 7, 1, 1), +AC97_SINGLE("Mic 2 to Mono Switch", AC97_LINE, 6, 1, 1), +AC97_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0), +AC97_ENUM("Mic to Headphone Mux", wm9713_enum[0]), +AC97_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1), + +AC97_SINGLE("Capture Switch", AC97_CD, 15, 1, 1), +AC97_ENUM("Capture Volume Steps", wm9713_enum[4]), +AC97_DOUBLE("Capture Volume", AC97_CD, 8, 0, 15, 0), +AC97_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0), + +AC97_ENUM("Capture to Headphone Mux", wm9713_enum[1]), +AC97_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1), +AC97_ENUM("Capture to Mono Mux", wm9713_enum[2]), +AC97_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0), +AC97_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0), +AC97_ENUM("Capture Select", wm9713_enum[3]), + +AC97_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), +AC97_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), +AC97_SINGLE("ALC Decay Time ", AC97_CODEC_CLASS_REV, 4, 15, 0), +AC97_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0), +AC97_ENUM("ALC Function", wm9713_enum[5]), +AC97_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0), +AC97_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0), +AC97_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0), +AC97_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0), +AC97_ENUM("ALC NG Type", wm9713_enum[13]), +AC97_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0), + +AC97_DOUBLE("Master ZC Switch", AC97_MASTER, 14, 6, 1, 0), +AC97_DOUBLE("Headphone ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0), +AC97_DOUBLE("Out3/4 ZC Switch", AC97_MASTER_MONO, 14, 6, 1, 0), +AC97_SINGLE("Master Right Switch", AC97_MASTER, 7, 1, 1), +AC97_SINGLE("Headphone Right Switch", AC97_HEADPHONE, 7, 1, 1), +AC97_SINGLE("Out3/4 Right Switch", AC97_MASTER_MONO, 7, 1, 1), + +AC97_SINGLE("Mono In to Headphone Switch", AC97_MASTER_TONE, 15, 1, 1), +AC97_SINGLE("Mono In to Master Switch", AC97_MASTER_TONE, 14, 1, 1), +AC97_SINGLE("Mono In Volume", AC97_MASTER_TONE, 8, 31, 1), +AC97_SINGLE("Mono Switch", AC97_MASTER_TONE, 7, 1, 1), +AC97_SINGLE("Mono ZC Switch", AC97_MASTER_TONE, 6, 1, 0), +AC97_SINGLE("Mono Volume", AC97_MASTER_TONE, 0, 31, 1), + +AC97_SINGLE("Beep to Headphone Switch", AC97_AUX, 15, 1, 1), +AC97_SINGLE("Beep to Headphone Volume", AC97_AUX, 12, 7, 1), +AC97_SINGLE("Beep to Master Switch", AC97_AUX, 11, 1, 1), +AC97_SINGLE("Beep to Master Volume", AC97_AUX, 8, 7, 1), +AC97_SINGLE("Beep to Mono Switch", AC97_AUX, 7, 1, 1), +AC97_SINGLE("Beep to Mono Volume", AC97_AUX, 4, 7, 1), + +AC97_SINGLE("Voice to Headphone Switch", AC97_PCM, 15, 1, 1), +AC97_SINGLE("Voice to Headphone Volume", AC97_PCM, 12, 7, 1), +AC97_SINGLE("Voice to Master Switch", AC97_PCM, 11, 1, 1), +AC97_SINGLE("Voice to Master Volume", AC97_PCM, 8, 7, 1), +AC97_SINGLE("Voice to Mono Switch", AC97_PCM, 7, 1, 1), +AC97_SINGLE("Voice to Mono Volume", AC97_PCM, 4, 7, 1), + +AC97_SINGLE("Aux to Headphone Switch", AC97_REC_SEL, 15, 1, 1), +AC97_SINGLE("Aux to Headphone Volume", AC97_REC_SEL, 12, 7, 1), +AC97_SINGLE("Aux to Master Switch", AC97_REC_SEL, 11, 1, 1), +AC97_SINGLE("Aux to Master Volume", AC97_REC_SEL, 8, 7, 1), +AC97_SINGLE("Aux to Mono Switch", AC97_REC_SEL, 7, 1, 1), +AC97_SINGLE("Aux to Mono Volume", AC97_REC_SEL, 4, 7, 1), + +AC97_ENUM("Mono Input Mux", wm9713_enum[6]), +AC97_ENUM("Master Input Mux", wm9713_enum[7]), +AC97_ENUM("Headphone Input Mux", wm9713_enum[8]), +AC97_ENUM("Out 3 Input Mux", wm9713_enum[9]), +AC97_ENUM("Out 4 Input Mux", wm9713_enum[10]), + +AC97_ENUM("Bass Control", wm9713_enum[12]), +AC97_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1), +AC97_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1), +AC97_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0), +AC97_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1), +AC97_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1), +}; + +static const struct snd_kcontrol_new wm13_snd_ac97_controls_3d[] = { +AC97_ENUM("Inv Input Mux", wm9713_enum[11]), +AC97_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0), +AC97_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0), +AC97_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1), +}; + +static int patch_wolfson_wm9713_3d (struct snd_ac97 * ac97) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_3d); i++) { + err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_3d[i], ac97)); + if (err < 0) + return err; + } + return 0; +} + +static int patch_wolfson_wm9713_specific(struct snd_ac97 * ac97) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls); i++) { + err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls[i], ac97)); + if (err < 0) + return err; + } + snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x0808); + snd_ac97_write_cache(ac97, AC97_PHONE, 0x0808); + snd_ac97_write_cache(ac97, AC97_MIC, 0x0808); + snd_ac97_write_cache(ac97, AC97_LINE, 0x00da); + snd_ac97_write_cache(ac97, AC97_CD, 0x0808); + snd_ac97_write_cache(ac97, AC97_VIDEO, 0xd612); + snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x1ba0); + return 0; +} + +#ifdef CONFIG_PM +static void patch_wolfson_wm9713_suspend (struct snd_ac97 * ac97) +{ + snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xfeff); + snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0xffff); +} + +static void patch_wolfson_wm9713_resume (struct snd_ac97 * ac97) +{ + snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00); + snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810); + snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0); +} +#endif + +static const struct snd_ac97_build_ops patch_wolfson_wm9713_ops = { + .build_specific = patch_wolfson_wm9713_specific, + .build_3d = patch_wolfson_wm9713_3d, +#ifdef CONFIG_PM + .suspend = patch_wolfson_wm9713_suspend, + .resume = patch_wolfson_wm9713_resume +#endif +}; + +static int patch_wolfson13(struct snd_ac97 * ac97) +{ + /* WM9713, WM9714 */ + ac97->build_ops = &patch_wolfson_wm9713_ops; + + ac97->flags |= AC97_HAS_NO_REC_GAIN | AC97_STEREO_MUTES | AC97_HAS_NO_PHONE | + AC97_HAS_NO_PC_BEEP | AC97_HAS_NO_VIDEO | AC97_HAS_NO_CD | AC97_HAS_NO_TONE | + AC97_HAS_NO_STD_PCM; + ac97->scaps &= ~AC97_SCAP_MODEM; + + snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00); + snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810); + snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0); + + return 0; +} + +/* + * Tritech codec + */ +static int patch_tritech_tr28028(struct snd_ac97 * ac97) +{ + snd_ac97_write_cache(ac97, 0x26, 0x0300); + snd_ac97_write_cache(ac97, 0x26, 0x0000); + snd_ac97_write_cache(ac97, AC97_SURROUND_MASTER, 0x0000); + snd_ac97_write_cache(ac97, AC97_SPDIF, 0x0000); + return 0; +} + +/* + * Sigmatel STAC97xx codecs + */ +static int patch_sigmatel_stac9700_3d(struct snd_ac97 * ac97) +{ + struct snd_kcontrol *kctl; + int err; + + err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97)); + if (err < 0) + return err; + strscpy(kctl->id.name, "3D Control Sigmatel - Depth"); + kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 2, 3, 0); + snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000); + return 0; +} + +static int patch_sigmatel_stac9708_3d(struct snd_ac97 * ac97) +{ + struct snd_kcontrol *kctl; + int err; + + kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97); + err = snd_ctl_add(ac97->bus->card, kctl); + if (err < 0) + return err; + strscpy(kctl->id.name, "3D Control Sigmatel - Depth"); + kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 0, 3, 0); + kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97); + err = snd_ctl_add(ac97->bus->card, kctl); + if (err < 0) + return err; + strscpy(kctl->id.name, "3D Control Sigmatel - Rear Depth"); + kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 2, 3, 0); + snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000); + return 0; +} + +static const struct snd_kcontrol_new snd_ac97_sigmatel_4speaker = +AC97_SINGLE("Sigmatel 4-Speaker Stereo Playback Switch", + AC97_SIGMATEL_DAC2INVERT, 2, 1, 0); + +/* "Sigmatel " removed due to excessive name length: */ +static const struct snd_kcontrol_new snd_ac97_sigmatel_phaseinvert = +AC97_SINGLE("Surround Phase Inversion Playback Switch", + AC97_SIGMATEL_DAC2INVERT, 3, 1, 0); + +static const struct snd_kcontrol_new snd_ac97_sigmatel_controls[] = { +AC97_SINGLE("Sigmatel DAC 6dB Attenuate", AC97_SIGMATEL_ANALOG, 1, 1, 0), +AC97_SINGLE("Sigmatel ADC 6dB Attenuate", AC97_SIGMATEL_ANALOG, 0, 1, 0) +}; + +static int patch_sigmatel_stac97xx_specific(struct snd_ac97 * ac97) +{ + int err; + + snd_ac97_write_cache(ac97, AC97_SIGMATEL_ANALOG, snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG) & ~0x0003); + if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_ANALOG, 1)) { + err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[0], 1); + if (err < 0) + return err; + } + if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_ANALOG, 0)) { + err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[1], 1); + if (err < 0) + return err; + } + if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 2)) { + err = patch_build_controls(ac97, &snd_ac97_sigmatel_4speaker, 1); + if (err < 0) + return err; + } + if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 3)) { + err = patch_build_controls(ac97, &snd_ac97_sigmatel_phaseinvert, 1); + if (err < 0) + return err; + } + return 0; +} + +static const struct snd_ac97_build_ops patch_sigmatel_stac9700_ops = { + .build_3d = patch_sigmatel_stac9700_3d, + .build_specific = patch_sigmatel_stac97xx_specific +}; + +static int patch_sigmatel_stac9700(struct snd_ac97 * ac97) +{ + ac97->build_ops = &patch_sigmatel_stac9700_ops; + return 0; +} + +static int snd_ac97_stac9708_put_bias(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int err; + + guard(mutex)(&ac97->page_mutex); + snd_ac97_write(ac97, AC97_SIGMATEL_BIAS1, 0xabba); + err = snd_ac97_update_bits(ac97, AC97_SIGMATEL_BIAS2, 0x0010, + (ucontrol->value.integer.value[0] & 1) << 4); + snd_ac97_write(ac97, AC97_SIGMATEL_BIAS1, 0); + return err; +} + +static const struct snd_kcontrol_new snd_ac97_stac9708_bias_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sigmatel Output Bias Switch", + .info = snd_ac97_info_volsw, + .get = snd_ac97_get_volsw, + .put = snd_ac97_stac9708_put_bias, + .private_value = AC97_SINGLE_VALUE(AC97_SIGMATEL_BIAS2, 4, 1, 0), +}; + +static int patch_sigmatel_stac9708_specific(struct snd_ac97 *ac97) +{ + int err; + + /* the register bit is writable, but the function is not implemented: */ + snd_ac97_remove_ctl(ac97, "PCM Out Path & Mute", NULL); + + snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Sigmatel Surround Playback"); + err = patch_build_controls(ac97, &snd_ac97_stac9708_bias_control, 1); + if (err < 0) + return err; + return patch_sigmatel_stac97xx_specific(ac97); +} + +static const struct snd_ac97_build_ops patch_sigmatel_stac9708_ops = { + .build_3d = patch_sigmatel_stac9708_3d, + .build_specific = patch_sigmatel_stac9708_specific +}; + +static int patch_sigmatel_stac9708(struct snd_ac97 * ac97) +{ + unsigned int codec72, codec6c; + + ac97->build_ops = &patch_sigmatel_stac9708_ops; + ac97->caps |= 0x10; /* HP (sigmatel surround) support */ + + codec72 = snd_ac97_read(ac97, AC97_SIGMATEL_BIAS2) & 0x8000; + codec6c = snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG); + + if ((codec72==0) && (codec6c==0)) { + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x1000); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0007); + } else if ((codec72==0x8000) && (codec6c==0)) { + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x1001); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_DAC2INVERT, 0x0008); + } else if ((codec72==0x8000) && (codec6c==0x0080)) { + /* nothing */ + } + snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000); + return 0; +} + +static int patch_sigmatel_stac9721(struct snd_ac97 * ac97) +{ + ac97->build_ops = &patch_sigmatel_stac9700_ops; + if (snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG) == 0) { + // patch for SigmaTel + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x4000); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002); + } + snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000); + return 0; +} + +static int patch_sigmatel_stac9744(struct snd_ac97 * ac97) +{ + // patch for SigmaTel + ac97->build_ops = &patch_sigmatel_stac9700_ops; + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x0000); /* is this correct? --jk */ + snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000); + return 0; +} + +static int patch_sigmatel_stac9756(struct snd_ac97 * ac97) +{ + // patch for SigmaTel + ac97->build_ops = &patch_sigmatel_stac9700_ops; + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x0000); /* is this correct? --jk */ + snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002); + snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000); + return 0; +} + +static int snd_ac97_stac9758_output_jack_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[5] = { + "Input/Disabled", "Front Output", + "Rear Output", "Center/LFE Output", "Mixer Output" }; + + return snd_ctl_enum_info(uinfo, 1, 5, texts); +} + +static int snd_ac97_stac9758_output_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value; + unsigned short val; + + val = ac97->regs[AC97_SIGMATEL_OUTSEL] >> shift; + if (!(val & 4)) + ucontrol->value.enumerated.item[0] = 0; + else + ucontrol->value.enumerated.item[0] = 1 + (val & 3); + return 0; +} + +static int snd_ac97_stac9758_output_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value; + unsigned short val; + + if (ucontrol->value.enumerated.item[0] > 4) + return -EINVAL; + if (ucontrol->value.enumerated.item[0] == 0) + val = 0; + else + val = 4 | (ucontrol->value.enumerated.item[0] - 1); + return ac97_update_bits_page(ac97, AC97_SIGMATEL_OUTSEL, + 7 << shift, val << shift, 0); +} + +static int snd_ac97_stac9758_input_jack_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[7] = { + "Mic2 Jack", "Mic1 Jack", "Line In Jack", + "Front Jack", "Rear Jack", "Center/LFE Jack", "Mute" }; + + return snd_ctl_enum_info(uinfo, 1, 7, texts); +} + +static int snd_ac97_stac9758_input_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value; + unsigned short val; + + val = ac97->regs[AC97_SIGMATEL_INSEL]; + ucontrol->value.enumerated.item[0] = (val >> shift) & 7; + return 0; +} + +static int snd_ac97_stac9758_input_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value; + + return ac97_update_bits_page(ac97, AC97_SIGMATEL_INSEL, 7 << shift, + ucontrol->value.enumerated.item[0] << shift, 0); +} + +static int snd_ac97_stac9758_phonesel_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "None", "Front Jack", "Rear Jack" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_ac97_stac9758_phonesel_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = ac97->regs[AC97_SIGMATEL_IOMISC] & 3; + return 0; +} + +static int snd_ac97_stac9758_phonesel_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + return ac97_update_bits_page(ac97, AC97_SIGMATEL_IOMISC, 3, + ucontrol->value.enumerated.item[0], 0); +} + +#define STAC9758_OUTPUT_JACK(xname, shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_ac97_stac9758_output_jack_info, \ + .get = snd_ac97_stac9758_output_jack_get, \ + .put = snd_ac97_stac9758_output_jack_put, \ + .private_value = shift } +#define STAC9758_INPUT_JACK(xname, shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_ac97_stac9758_input_jack_info, \ + .get = snd_ac97_stac9758_input_jack_get, \ + .put = snd_ac97_stac9758_input_jack_put, \ + .private_value = shift } +static const struct snd_kcontrol_new snd_ac97_sigmatel_stac9758_controls[] = { + STAC9758_OUTPUT_JACK("Mic1 Jack", 1), + STAC9758_OUTPUT_JACK("LineIn Jack", 4), + STAC9758_OUTPUT_JACK("Front Jack", 7), + STAC9758_OUTPUT_JACK("Rear Jack", 10), + STAC9758_OUTPUT_JACK("Center/LFE Jack", 13), + STAC9758_INPUT_JACK("Mic Input Source", 0), + STAC9758_INPUT_JACK("Line Input Source", 8), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Amp", + .info = snd_ac97_stac9758_phonesel_info, + .get = snd_ac97_stac9758_phonesel_get, + .put = snd_ac97_stac9758_phonesel_put + }, + AC97_SINGLE("Exchange Center/LFE", AC97_SIGMATEL_IOMISC, 4, 1, 0), + AC97_SINGLE("Headphone +3dB Boost", AC97_SIGMATEL_IOMISC, 8, 1, 0) +}; + +static int patch_sigmatel_stac9758_specific(struct snd_ac97 *ac97) +{ + int err; + + err = patch_sigmatel_stac97xx_specific(ac97); + if (err < 0) + return err; + err = patch_build_controls(ac97, snd_ac97_sigmatel_stac9758_controls, + ARRAY_SIZE(snd_ac97_sigmatel_stac9758_controls)); + if (err < 0) + return err; + /* DAC-A direct */ + snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Front Playback"); + /* DAC-A to Mix = PCM */ + /* DAC-B direct = Surround */ + /* DAC-B to Mix */ + snd_ac97_rename_vol_ctl(ac97, "Video Playback", "Surround Mix Playback"); + /* DAC-C direct = Center/LFE */ + + return 0; +} + +static const struct snd_ac97_build_ops patch_sigmatel_stac9758_ops = { + .build_3d = patch_sigmatel_stac9700_3d, + .build_specific = patch_sigmatel_stac9758_specific +}; + +static int patch_sigmatel_stac9758(struct snd_ac97 * ac97) +{ + static const unsigned short regs[4] = { + AC97_SIGMATEL_OUTSEL, + AC97_SIGMATEL_IOMISC, + AC97_SIGMATEL_INSEL, + AC97_SIGMATEL_VARIOUS + }; + static const unsigned short def_regs[4] = { + /* OUTSEL */ 0xd794, /* CL:CL, SR:SR, LO:MX, LI:DS, MI:DS */ + /* IOMISC */ 0x2001, + /* INSEL */ 0x0201, /* LI:LI, MI:M1 */ + /* VARIOUS */ 0x0040 + }; + static const unsigned short m675_regs[4] = { + /* OUTSEL */ 0xfc70, /* CL:MX, SR:MX, LO:DS, LI:MX, MI:DS */ + /* IOMISC */ 0x2102, /* HP amp on */ + /* INSEL */ 0x0203, /* LI:LI, MI:FR */ + /* VARIOUS */ 0x0041 /* stereo mic */ + }; + const unsigned short *pregs = def_regs; + int i; + + /* Gateway M675 notebook */ + if (ac97->pci && + ac97->subsystem_vendor == 0x107b && + ac97->subsystem_device == 0x0601) + pregs = m675_regs; + + // patch for SigmaTel + ac97->build_ops = &patch_sigmatel_stac9758_ops; + /* FIXME: assume only page 0 for writing cache */ + snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR); + for (i = 0; i < 4; i++) + snd_ac97_write_cache(ac97, regs[i], pregs[i]); + + ac97->flags |= AC97_STEREO_MUTES; + return 0; +} + +/* + * Cirrus Logic CS42xx codecs + */ +static const struct snd_kcontrol_new snd_ac97_cirrus_controls_spdif[2] = { + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), AC97_CSR_SPDIF, 15, 1, 0), + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "AC97-SPSA", AC97_CSR_ACMODE, 0, 3, 0) +}; + +static int patch_cirrus_build_spdif(struct snd_ac97 * ac97) +{ + int err; + + /* con mask, pro mask, default */ + err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3); + if (err < 0) + return err; + /* switch, spsa */ + err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[0], 1); + if (err < 0) + return err; + switch (ac97->id & AC97_ID_CS_MASK) { + case AC97_ID_CS4205: + err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[1], 1); + if (err < 0) + return err; + break; + } + /* set default PCM S/PDIF params */ + /* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */ + snd_ac97_write_cache(ac97, AC97_CSR_SPDIF, 0x0a20); + return 0; +} + +static const struct snd_ac97_build_ops patch_cirrus_ops = { + .build_spdif = patch_cirrus_build_spdif +}; + +static int patch_cirrus_spdif(struct snd_ac97 * ac97) +{ + /* Basically, the cs4201/cs4205/cs4297a has non-standard sp/dif registers. + WHY CAN'T ANYONE FOLLOW THE BLOODY SPEC? *sigh* + - sp/dif EA ID is not set, but sp/dif is always present. + - enable/disable is spdif register bit 15. + - sp/dif control register is 0x68. differs from AC97: + - valid is bit 14 (vs 15) + - no DRS + - only 44.1/48k [00 = 48, 01=44,1] (AC97 is 00=44.1, 10=48) + - sp/dif ssource select is in 0x5e bits 0,1. + */ + + ac97->build_ops = &patch_cirrus_ops; + ac97->flags |= AC97_CS_SPDIF; + ac97->rates[AC97_RATES_SPDIF] &= ~SNDRV_PCM_RATE_32000; + ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */ + snd_ac97_write_cache(ac97, AC97_CSR_ACMODE, 0x0080); + return 0; +} + +static int patch_cirrus_cs4299(struct snd_ac97 * ac97) +{ + /* force the detection of PC Beep */ + ac97->flags |= AC97_HAS_PC_BEEP; + + return patch_cirrus_spdif(ac97); +} + +/* + * Conexant codecs + */ +static const struct snd_kcontrol_new snd_ac97_conexant_controls_spdif[1] = { + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), AC97_CXR_AUDIO_MISC, 3, 1, 0), +}; + +static int patch_conexant_build_spdif(struct snd_ac97 * ac97) +{ + int err; + + /* con mask, pro mask, default */ + err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3); + if (err < 0) + return err; + /* switch */ + err = patch_build_controls(ac97, &snd_ac97_conexant_controls_spdif[0], 1); + if (err < 0) + return err; + /* set default PCM S/PDIF params */ + /* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */ + snd_ac97_write_cache(ac97, AC97_CXR_AUDIO_MISC, + snd_ac97_read(ac97, AC97_CXR_AUDIO_MISC) & ~(AC97_CXR_SPDIFEN|AC97_CXR_COPYRGT|AC97_CXR_SPDIF_MASK)); + return 0; +} + +static const struct snd_ac97_build_ops patch_conexant_ops = { + .build_spdif = patch_conexant_build_spdif +}; + +static int patch_conexant(struct snd_ac97 * ac97) +{ + ac97->build_ops = &patch_conexant_ops; + ac97->flags |= AC97_CX_SPDIF; + ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */ + ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */ + return 0; +} + +static int patch_cx20551(struct snd_ac97 *ac97) +{ + snd_ac97_update_bits(ac97, 0x5c, 0x01, 0x01); + return 0; +} + +/* + * Analog Devices AD18xx, AD19xx codecs + */ +#ifdef CONFIG_PM +static void ad18xx_resume(struct snd_ac97 *ac97) +{ + static const unsigned short setup_regs[] = { + AC97_AD_MISC, AC97_AD_SERIAL_CFG, AC97_AD_JACK_SPDIF, + }; + int i, codec; + + for (i = 0; i < (int)ARRAY_SIZE(setup_regs); i++) { + unsigned short reg = setup_regs[i]; + if (test_bit(reg, ac97->reg_accessed)) { + snd_ac97_write(ac97, reg, ac97->regs[reg]); + snd_ac97_read(ac97, reg); + } + } + + if (! (ac97->flags & AC97_AD_MULTI)) + /* normal restore */ + snd_ac97_restore_status(ac97); + else { + /* restore the AD18xx codec configurations */ + for (codec = 0; codec < 3; codec++) { + if (! ac97->spec.ad18xx.id[codec]) + continue; + /* select single codec */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, + ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]); + ac97->bus->ops->write(ac97, AC97_AD_CODEC_CFG, ac97->spec.ad18xx.codec_cfg[codec]); + } + /* select all codecs */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000); + + /* restore status */ + for (i = 2; i < 0x7c ; i += 2) { + if (i == AC97_POWERDOWN || i == AC97_EXTENDED_ID) + continue; + if (test_bit(i, ac97->reg_accessed)) { + /* handle multi codecs for AD18xx */ + if (i == AC97_PCM) { + for (codec = 0; codec < 3; codec++) { + if (! ac97->spec.ad18xx.id[codec]) + continue; + /* select single codec */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, + ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]); + /* update PCM bits */ + ac97->bus->ops->write(ac97, AC97_PCM, ac97->spec.ad18xx.pcmreg[codec]); + } + /* select all codecs */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000); + continue; + } else if (i == AC97_AD_TEST || + i == AC97_AD_CODEC_CFG || + i == AC97_AD_SERIAL_CFG) + continue; /* ignore */ + } + snd_ac97_write(ac97, i, ac97->regs[i]); + snd_ac97_read(ac97, i); + } + } + + snd_ac97_restore_iec958(ac97); +} + +static void ad1888_resume(struct snd_ac97 *ac97) +{ + ad18xx_resume(ac97); + snd_ac97_write_cache(ac97, AC97_CODEC_CLASS_REV, 0x8080); +} + +#endif + +static const struct snd_ac97_res_table ad1819_restbl[] = { + { AC97_PHONE, 0x9f1f }, + { AC97_MIC, 0x9f1f }, + { AC97_LINE, 0x9f1f }, + { AC97_CD, 0x9f1f }, + { AC97_VIDEO, 0x9f1f }, + { AC97_AUX, 0x9f1f }, + { AC97_PCM, 0x9f1f }, + { } /* terminator */ +}; + +static int patch_ad1819(struct snd_ac97 * ac97) +{ + unsigned short scfg; + + // patch for Analog Devices + scfg = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG); + snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, scfg | 0x7000); /* select all codecs */ + ac97->res_table = ad1819_restbl; + return 0; +} + +static unsigned short patch_ad1881_unchained(struct snd_ac97 * ac97, int idx, unsigned short mask) +{ + unsigned short val; + + // test for unchained codec + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, mask); + snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0000); /* ID0C, ID1C, SDIE = off */ + val = snd_ac97_read(ac97, AC97_VENDOR_ID2); + if ((val & 0xff40) != 0x5340) + return 0; + ac97->spec.ad18xx.unchained[idx] = mask; + ac97->spec.ad18xx.id[idx] = val; + ac97->spec.ad18xx.codec_cfg[idx] = 0x0000; + return mask; +} + +static int patch_ad1881_chained1(struct snd_ac97 * ac97, int idx, unsigned short codec_bits) +{ + static const int cfg_bits[3] = { 1<<12, 1<<14, 1<<13 }; + unsigned short val; + + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, cfg_bits[idx]); + snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0004); // SDIE + val = snd_ac97_read(ac97, AC97_VENDOR_ID2); + if ((val & 0xff40) != 0x5340) + return 0; + if (codec_bits) + snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, codec_bits); + ac97->spec.ad18xx.chained[idx] = cfg_bits[idx]; + ac97->spec.ad18xx.id[idx] = val; + ac97->spec.ad18xx.codec_cfg[idx] = codec_bits ? codec_bits : 0x0004; + return 1; +} + +static void patch_ad1881_chained(struct snd_ac97 * ac97, int unchained_idx, int cidx1, int cidx2) +{ + // already detected? + if (ac97->spec.ad18xx.unchained[cidx1] || ac97->spec.ad18xx.chained[cidx1]) + cidx1 = -1; + if (ac97->spec.ad18xx.unchained[cidx2] || ac97->spec.ad18xx.chained[cidx2]) + cidx2 = -1; + if (cidx1 < 0 && cidx2 < 0) + return; + // test for chained codecs + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, + ac97->spec.ad18xx.unchained[unchained_idx]); + snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0002); // ID1C + ac97->spec.ad18xx.codec_cfg[unchained_idx] = 0x0002; + if (cidx1 >= 0) { + if (cidx2 < 0) + patch_ad1881_chained1(ac97, cidx1, 0); + else if (patch_ad1881_chained1(ac97, cidx1, 0x0006)) // SDIE | ID1C + patch_ad1881_chained1(ac97, cidx2, 0); + else if (patch_ad1881_chained1(ac97, cidx2, 0x0006)) // SDIE | ID1C + patch_ad1881_chained1(ac97, cidx1, 0); + } else if (cidx2 >= 0) { + patch_ad1881_chained1(ac97, cidx2, 0); + } +} + +static const struct snd_ac97_build_ops patch_ad1881_build_ops = { +#ifdef CONFIG_PM + .resume = ad18xx_resume +#endif +}; + +static int patch_ad1881(struct snd_ac97 * ac97) +{ + static const char cfg_idxs[3][2] = { + {2, 1}, + {0, 2}, + {0, 1} + }; + + // patch for Analog Devices + unsigned short codecs[3]; + unsigned short val; + int idx, num; + + val = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG); + snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, val); + codecs[0] = patch_ad1881_unchained(ac97, 0, (1<<12)); + codecs[1] = patch_ad1881_unchained(ac97, 1, (1<<14)); + codecs[2] = patch_ad1881_unchained(ac97, 2, (1<<13)); + + if (! (codecs[0] || codecs[1] || codecs[2])) + goto __end; + + for (idx = 0; idx < 3; idx++) + if (ac97->spec.ad18xx.unchained[idx]) + patch_ad1881_chained(ac97, idx, cfg_idxs[idx][0], cfg_idxs[idx][1]); + + if (ac97->spec.ad18xx.id[1]) { + ac97->flags |= AC97_AD_MULTI; + ac97->scaps |= AC97_SCAP_SURROUND_DAC; + } + if (ac97->spec.ad18xx.id[2]) { + ac97->flags |= AC97_AD_MULTI; + ac97->scaps |= AC97_SCAP_CENTER_LFE_DAC; + } + + __end: + /* select all codecs */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000); + /* check if only one codec is present */ + for (idx = num = 0; idx < 3; idx++) + if (ac97->spec.ad18xx.id[idx]) + num++; + if (num == 1) { + /* ok, deselect all ID bits */ + snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0000); + ac97->spec.ad18xx.codec_cfg[0] = + ac97->spec.ad18xx.codec_cfg[1] = + ac97->spec.ad18xx.codec_cfg[2] = 0x0000; + } + /* required for AD1886/AD1885 combination */ + ac97->ext_id = snd_ac97_read(ac97, AC97_EXTENDED_ID); + if (ac97->spec.ad18xx.id[0]) { + ac97->id &= 0xffff0000; + ac97->id |= ac97->spec.ad18xx.id[0]; + } + ac97->build_ops = &patch_ad1881_build_ops; + return 0; +} + +static const struct snd_kcontrol_new snd_ac97_controls_ad1885[] = { + AC97_SINGLE("Digital Mono Direct", AC97_AD_MISC, 11, 1, 0), + /* AC97_SINGLE("Digital Audio Mode", AC97_AD_MISC, 12, 1, 0), */ /* seems problematic */ + AC97_SINGLE("Low Power Mixer", AC97_AD_MISC, 14, 1, 0), + AC97_SINGLE("Zero Fill DAC", AC97_AD_MISC, 15, 1, 0), + AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 9, 1, 1), /* inverted */ + AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 8, 1, 1), /* inverted */ +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_6bit_6db_max, -8850, 150, 0); + +static int patch_ad1885_specific(struct snd_ac97 * ac97) +{ + int err; + + err = patch_build_controls(ac97, snd_ac97_controls_ad1885, ARRAY_SIZE(snd_ac97_controls_ad1885)); + if (err < 0) + return err; + reset_tlv(ac97, "Headphone Playback Volume", + db_scale_6bit_6db_max); + return 0; +} + +static const struct snd_ac97_build_ops patch_ad1885_build_ops = { + .build_specific = &patch_ad1885_specific, +#ifdef CONFIG_PM + .resume = ad18xx_resume +#endif +}; + +static int patch_ad1885(struct snd_ac97 * ac97) +{ + patch_ad1881(ac97); + /* This is required to deal with the Intel D815EEAL2 */ + /* i.e. Line out is actually headphone out from codec */ + + /* set default */ + snd_ac97_write_cache(ac97, AC97_AD_MISC, 0x0404); + + ac97->build_ops = &patch_ad1885_build_ops; + return 0; +} + +static int patch_ad1886_specific(struct snd_ac97 * ac97) +{ + reset_tlv(ac97, "Headphone Playback Volume", + db_scale_6bit_6db_max); + return 0; +} + +static const struct snd_ac97_build_ops patch_ad1886_build_ops = { + .build_specific = &patch_ad1886_specific, +#ifdef CONFIG_PM + .resume = ad18xx_resume +#endif +}; + +static int patch_ad1886(struct snd_ac97 * ac97) +{ + patch_ad1881(ac97); + /* Presario700 workaround */ + /* for Jack Sense/SPDIF Register misetting causing */ + snd_ac97_write_cache(ac97, AC97_AD_JACK_SPDIF, 0x0010); + ac97->build_ops = &patch_ad1886_build_ops; + return 0; +} + +/* MISC bits (AD1888/AD1980/AD1985 register 0x76) */ +#define AC97_AD198X_MBC 0x0003 /* mic boost */ +#define AC97_AD198X_MBC_20 0x0000 /* +20dB */ +#define AC97_AD198X_MBC_10 0x0001 /* +10dB */ +#define AC97_AD198X_MBC_30 0x0002 /* +30dB */ +#define AC97_AD198X_VREFD 0x0004 /* VREF high-Z */ +#define AC97_AD198X_VREFH 0x0008 /* 0=2.25V, 1=3.7V */ +#define AC97_AD198X_VREF_0 0x000c /* 0V (AD1985 only) */ +#define AC97_AD198X_VREF_MASK (AC97_AD198X_VREFH | AC97_AD198X_VREFD) +#define AC97_AD198X_VREF_SHIFT 2 +#define AC97_AD198X_SRU 0x0010 /* sample rate unlock */ +#define AC97_AD198X_LOSEL 0x0020 /* LINE_OUT amplifiers input select */ +#define AC97_AD198X_2MIC 0x0040 /* 2-channel mic select */ +#define AC97_AD198X_SPRD 0x0080 /* SPREAD enable */ +#define AC97_AD198X_DMIX0 0x0100 /* downmix mode: */ + /* 0 = 6-to-4, 1 = 6-to-2 downmix */ +#define AC97_AD198X_DMIX1 0x0200 /* downmix mode: 1 = enabled */ +#define AC97_AD198X_HPSEL 0x0400 /* headphone amplifier input select */ +#define AC97_AD198X_CLDIS 0x0800 /* center/lfe disable */ +#define AC97_AD198X_LODIS 0x1000 /* LINE_OUT disable */ +#define AC97_AD198X_MSPLT 0x2000 /* mute split */ +#define AC97_AD198X_AC97NC 0x4000 /* AC97 no compatible mode */ +#define AC97_AD198X_DACZ 0x8000 /* DAC zero-fill mode */ + +/* MISC 1 bits (AD1986 register 0x76) */ +#define AC97_AD1986_MBC 0x0003 /* mic boost */ +#define AC97_AD1986_MBC_20 0x0000 /* +20dB */ +#define AC97_AD1986_MBC_10 0x0001 /* +10dB */ +#define AC97_AD1986_MBC_30 0x0002 /* +30dB */ +#define AC97_AD1986_LISEL0 0x0004 /* LINE_IN select bit 0 */ +#define AC97_AD1986_LISEL1 0x0008 /* LINE_IN select bit 1 */ +#define AC97_AD1986_LISEL_MASK (AC97_AD1986_LISEL1 | AC97_AD1986_LISEL0) +#define AC97_AD1986_LISEL_LI 0x0000 /* LINE_IN pins as LINE_IN source */ +#define AC97_AD1986_LISEL_SURR 0x0004 /* SURROUND pins as LINE_IN source */ +#define AC97_AD1986_LISEL_MIC 0x0008 /* MIC_1/2 pins as LINE_IN source */ +#define AC97_AD1986_SRU 0x0010 /* sample rate unlock */ +#define AC97_AD1986_SOSEL 0x0020 /* SURROUND_OUT amplifiers input sel */ +#define AC97_AD1986_2MIC 0x0040 /* 2-channel mic select */ +#define AC97_AD1986_SPRD 0x0080 /* SPREAD enable */ +#define AC97_AD1986_DMIX0 0x0100 /* downmix mode: */ + /* 0 = 6-to-4, 1 = 6-to-2 downmix */ +#define AC97_AD1986_DMIX1 0x0200 /* downmix mode: 1 = enabled */ +#define AC97_AD1986_CLDIS 0x0800 /* center/lfe disable */ +#define AC97_AD1986_SODIS 0x1000 /* SURROUND_OUT disable */ +#define AC97_AD1986_MSPLT 0x2000 /* mute split (read only 1) */ +#define AC97_AD1986_AC97NC 0x4000 /* AC97 no compatible mode (r/o 1) */ +#define AC97_AD1986_DACZ 0x8000 /* DAC zero-fill mode */ + +/* MISC 2 bits (AD1986 register 0x70) */ +#define AC97_AD_MISC2 0x70 /* Misc Control Bits 2 (AD1986) */ + +#define AC97_AD1986_CVREF0 0x0004 /* C/LFE VREF_OUT 2.25V */ +#define AC97_AD1986_CVREF1 0x0008 /* C/LFE VREF_OUT 0V */ +#define AC97_AD1986_CVREF2 0x0010 /* C/LFE VREF_OUT 3.7V */ +#define AC97_AD1986_CVREF_MASK \ + (AC97_AD1986_CVREF2 | AC97_AD1986_CVREF1 | AC97_AD1986_CVREF0) +#define AC97_AD1986_JSMAP 0x0020 /* Jack Sense Mapping 1 = alternate */ +#define AC97_AD1986_MMDIS 0x0080 /* Mono Mute Disable */ +#define AC97_AD1986_MVREF0 0x0400 /* MIC VREF_OUT 2.25V */ +#define AC97_AD1986_MVREF1 0x0800 /* MIC VREF_OUT 0V */ +#define AC97_AD1986_MVREF2 0x1000 /* MIC VREF_OUT 3.7V */ +#define AC97_AD1986_MVREF_MASK \ + (AC97_AD1986_MVREF2 | AC97_AD1986_MVREF1 | AC97_AD1986_MVREF0) + +/* MISC 3 bits (AD1986 register 0x7a) */ +#define AC97_AD_MISC3 0x7a /* Misc Control Bits 3 (AD1986) */ + +#define AC97_AD1986_MMIX 0x0004 /* Mic Mix, left/right */ +#define AC97_AD1986_GPO 0x0008 /* General Purpose Out */ +#define AC97_AD1986_LOHPEN 0x0010 /* LINE_OUT headphone drive */ +#define AC97_AD1986_LVREF0 0x0100 /* LINE_OUT VREF_OUT 2.25V */ +#define AC97_AD1986_LVREF1 0x0200 /* LINE_OUT VREF_OUT 0V */ +#define AC97_AD1986_LVREF2 0x0400 /* LINE_OUT VREF_OUT 3.7V */ +#define AC97_AD1986_LVREF_MASK \ + (AC97_AD1986_LVREF2 | AC97_AD1986_LVREF1 | AC97_AD1986_LVREF0) +#define AC97_AD1986_JSINVA 0x0800 /* Jack Sense Invert SENSE_A */ +#define AC97_AD1986_LOSEL 0x1000 /* LINE_OUT amplifiers input select */ +#define AC97_AD1986_HPSEL0 0x2000 /* Headphone amplifiers */ + /* input select Surround DACs */ +#define AC97_AD1986_HPSEL1 0x4000 /* Headphone amplifiers input */ + /* select C/LFE DACs */ +#define AC97_AD1986_JSINVB 0x8000 /* Jack Sense Invert SENSE_B */ + +/* Serial Config bits (AD1986 register 0x74) (incomplete) */ +#define AC97_AD1986_OMS0 0x0100 /* Optional Mic Selector bit 0 */ +#define AC97_AD1986_OMS1 0x0200 /* Optional Mic Selector bit 1 */ +#define AC97_AD1986_OMS2 0x0400 /* Optional Mic Selector bit 2 */ +#define AC97_AD1986_OMS_MASK \ + (AC97_AD1986_OMS2 | AC97_AD1986_OMS1 | AC97_AD1986_OMS0) +#define AC97_AD1986_OMS_M 0x0000 /* MIC_1/2 pins are MIC sources */ +#define AC97_AD1986_OMS_L 0x0100 /* LINE_IN pins are MIC sources */ +#define AC97_AD1986_OMS_C 0x0200 /* Center/LFE pins are MCI sources */ +#define AC97_AD1986_OMS_MC 0x0400 /* Mix of MIC and C/LFE pins */ + /* are MIC sources */ +#define AC97_AD1986_OMS_ML 0x0500 /* MIX of MIC and LINE_IN pins */ + /* are MIC sources */ +#define AC97_AD1986_OMS_LC 0x0600 /* MIX of LINE_IN and C/LFE pins */ + /* are MIC sources */ +#define AC97_AD1986_OMS_MLC 0x0700 /* MIX of MIC, LINE_IN, C/LFE pins */ + /* are MIC sources */ + + +static int snd_ac97_ad198x_spdif_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "AC-Link", "A/D Converter" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_ac97_ad198x_spdif_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_AD_SERIAL_CFG]; + ucontrol->value.enumerated.item[0] = (val >> 2) & 1; + return 0; +} + +static int snd_ac97_ad198x_spdif_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + val = ucontrol->value.enumerated.item[0] << 2; + return snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x0004, val); +} + +static const struct snd_kcontrol_new snd_ac97_ad198x_spdif_source = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + .info = snd_ac97_ad198x_spdif_source_info, + .get = snd_ac97_ad198x_spdif_source_get, + .put = snd_ac97_ad198x_spdif_source_put, +}; + +static int patch_ad198x_post_spdif(struct snd_ac97 * ac97) +{ + return patch_build_controls(ac97, &snd_ac97_ad198x_spdif_source, 1); +} + +static const struct snd_kcontrol_new snd_ac97_ad1981x_jack_sense[] = { + AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 11, 1, 0), + AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0), +}; + +/* deny list to avoid HP/Line jack-sense controls + * (SS vendor << 16 | device) + */ +static const unsigned int ad1981_jacks_denylist[] = { + 0x10140523, /* Thinkpad R40 */ + 0x10140534, /* Thinkpad X31 */ + 0x10140537, /* Thinkpad T41p */ + 0x1014053e, /* Thinkpad R40e */ + 0x10140554, /* Thinkpad T42p/R50p */ + 0x10140567, /* Thinkpad T43p 2668-G7U */ + 0x10140581, /* Thinkpad X41-2527 */ + 0x10280160, /* Dell Dimension 2400 */ + 0x104380b0, /* Asus A7V8X-MX */ + 0x11790241, /* Toshiba Satellite A-15 S127 */ + 0x1179ff10, /* Toshiba P500 */ + 0x144dc01a, /* Samsung NP-X20C004/SEG */ + 0 /* end */ +}; + +static int check_list(struct snd_ac97 *ac97, const unsigned int *list) +{ + u32 subid = ((u32)ac97->subsystem_vendor << 16) | ac97->subsystem_device; + for (; *list; list++) + if (*list == subid) + return 1; + return 0; +} + +static int patch_ad1981a_specific(struct snd_ac97 * ac97) +{ + if (check_list(ac97, ad1981_jacks_denylist)) + return 0; + return patch_build_controls(ac97, snd_ac97_ad1981x_jack_sense, + ARRAY_SIZE(snd_ac97_ad1981x_jack_sense)); +} + +static const struct snd_ac97_build_ops patch_ad1981a_build_ops = { + .build_post_spdif = patch_ad198x_post_spdif, + .build_specific = patch_ad1981a_specific, +#ifdef CONFIG_PM + .resume = ad18xx_resume +#endif +}; + +/* allow list to enable HP jack-sense bits + * (SS vendor << 16 | device) + */ +static const unsigned int ad1981_jacks_allowlist[] = { + 0x0e11005a, /* HP nc4000/4010 */ + 0x103c0890, /* HP nc6000 */ + 0x103c0938, /* HP nc4220 */ + 0x103c099c, /* HP nx6110 */ + 0x103c0944, /* HP nc6220 */ + 0x103c0934, /* HP nc8220 */ + 0x103c006d, /* HP nx9105 */ + 0x103c300d, /* HP Compaq dc5100 SFF(PT003AW) */ + 0x17340088, /* FSC Scenic-W */ + 0 /* end */ +}; + +static void check_ad1981_hp_jack_sense(struct snd_ac97 *ac97) +{ + if (check_list(ac97, ad1981_jacks_allowlist)) + /* enable headphone jack sense */ + snd_ac97_update_bits(ac97, AC97_AD_JACK_SPDIF, 1<<11, 1<<11); +} + +static int patch_ad1981a(struct snd_ac97 *ac97) +{ + patch_ad1881(ac97); + ac97->build_ops = &patch_ad1981a_build_ops; + snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD198X_MSPLT, AC97_AD198X_MSPLT); + ac97->flags |= AC97_STEREO_MUTES; + check_ad1981_hp_jack_sense(ac97); + return 0; +} + +static const struct snd_kcontrol_new snd_ac97_ad198x_2cmic = +AC97_SINGLE("Stereo Mic", AC97_AD_MISC, 6, 1, 0); + +static int patch_ad1981b_specific(struct snd_ac97 *ac97) +{ + int err; + + err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1); + if (err < 0) + return err; + if (check_list(ac97, ad1981_jacks_denylist)) + return 0; + return patch_build_controls(ac97, snd_ac97_ad1981x_jack_sense, + ARRAY_SIZE(snd_ac97_ad1981x_jack_sense)); +} + +static const struct snd_ac97_build_ops patch_ad1981b_build_ops = { + .build_post_spdif = patch_ad198x_post_spdif, + .build_specific = patch_ad1981b_specific, +#ifdef CONFIG_PM + .resume = ad18xx_resume +#endif +}; + +static int patch_ad1981b(struct snd_ac97 *ac97) +{ + patch_ad1881(ac97); + ac97->build_ops = &patch_ad1981b_build_ops; + snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD198X_MSPLT, AC97_AD198X_MSPLT); + ac97->flags |= AC97_STEREO_MUTES; + check_ad1981_hp_jack_sense(ac97); + return 0; +} + +#define snd_ac97_ad1888_lohpsel_info snd_ctl_boolean_mono_info + +static int snd_ac97_ad1888_lohpsel_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_AD_MISC]; + ucontrol->value.integer.value[0] = !(val & AC97_AD198X_LOSEL); + if (ac97->spec.ad18xx.lo_as_master) + ucontrol->value.integer.value[0] = + !ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_ac97_ad1888_lohpsel_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = !ucontrol->value.integer.value[0]; + if (ac97->spec.ad18xx.lo_as_master) + val = !val; + val = val ? (AC97_AD198X_LOSEL | AC97_AD198X_HPSEL) : 0; + return snd_ac97_update_bits(ac97, AC97_AD_MISC, + AC97_AD198X_LOSEL | AC97_AD198X_HPSEL, val); +} + +static int snd_ac97_ad1888_downmix_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = {"Off", "6 -> 4", "6 -> 2"}; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_ac97_ad1888_downmix_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_AD_MISC]; + if (!(val & AC97_AD198X_DMIX1)) + ucontrol->value.enumerated.item[0] = 0; + else + ucontrol->value.enumerated.item[0] = 1 + ((val >> 8) & 1); + return 0; +} + +static int snd_ac97_ad1888_downmix_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + if (ucontrol->value.enumerated.item[0] == 0) + val = 0; + else + val = AC97_AD198X_DMIX1 | + ((ucontrol->value.enumerated.item[0] - 1) << 8); + return snd_ac97_update_bits(ac97, AC97_AD_MISC, + AC97_AD198X_DMIX0 | AC97_AD198X_DMIX1, val); +} + +static void ad1888_update_jacks(struct snd_ac97 *ac97) +{ + unsigned short val = 0; + /* clear LODIS if shared jack is to be used for Surround out */ + if (!ac97->spec.ad18xx.lo_as_master && is_shared_linein(ac97)) + val |= (1 << 12); + /* clear CLDIS if shared jack is to be used for C/LFE out */ + if (is_shared_micin(ac97)) + val |= (1 << 11); + /* shared Line-In */ + snd_ac97_update_bits(ac97, AC97_AD_MISC, (1 << 11) | (1 << 12), val); +} + +static const struct snd_kcontrol_new snd_ac97_ad1888_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Exchange Front/Surround", + .info = snd_ac97_ad1888_lohpsel_info, + .get = snd_ac97_ad1888_lohpsel_get, + .put = snd_ac97_ad1888_lohpsel_put + }, + AC97_SINGLE("V_REFOUT Enable", AC97_AD_MISC, AC97_AD_VREFD_SHIFT, 1, 1), + AC97_SINGLE("High Pass Filter Enable", AC97_AD_TEST2, + AC97_AD_HPFD_SHIFT, 1, 1), + AC97_SINGLE("Spread Front to Surround and Center/LFE", AC97_AD_MISC, 7, 1, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Downmix", + .info = snd_ac97_ad1888_downmix_info, + .get = snd_ac97_ad1888_downmix_get, + .put = snd_ac97_ad1888_downmix_put + }, + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_CTL, + + AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0), + AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0), +}; + +static int patch_ad1888_specific(struct snd_ac97 *ac97) +{ + if (!ac97->spec.ad18xx.lo_as_master) { + /* rename 0x04 as "Master" and 0x02 as "Master Surround" */ + snd_ac97_rename_vol_ctl(ac97, "Master Playback", + "Master Surround Playback"); + snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", + "Master Playback"); + } + return patch_build_controls(ac97, snd_ac97_ad1888_controls, ARRAY_SIZE(snd_ac97_ad1888_controls)); +} + +static const struct snd_ac97_build_ops patch_ad1888_build_ops = { + .build_post_spdif = patch_ad198x_post_spdif, + .build_specific = patch_ad1888_specific, +#ifdef CONFIG_PM + .resume = ad1888_resume, +#endif + .update_jacks = ad1888_update_jacks, +}; + +static int patch_ad1888(struct snd_ac97 * ac97) +{ + unsigned short misc; + + patch_ad1881(ac97); + ac97->build_ops = &patch_ad1888_build_ops; + + /* + * LO can be used as a real line-out on some devices, + * and we need to revert the front/surround mixer switches + */ + if (ac97->subsystem_vendor == 0x1043 && + ac97->subsystem_device == 0x1193) /* ASUS A9T laptop */ + ac97->spec.ad18xx.lo_as_master = 1; + + misc = snd_ac97_read(ac97, AC97_AD_MISC); + /* AD-compatible mode */ + /* Stereo mutes enabled */ + misc |= AC97_AD198X_MSPLT | AC97_AD198X_AC97NC; + if (!ac97->spec.ad18xx.lo_as_master) + /* Switch FRONT/SURROUND LINE-OUT/HP-OUT default connection */ + /* it seems that most vendors connect line-out connector to + * headphone out of AC'97 + */ + misc |= AC97_AD198X_LOSEL | AC97_AD198X_HPSEL; + + snd_ac97_write_cache(ac97, AC97_AD_MISC, misc); + ac97->flags |= AC97_STEREO_MUTES; + return 0; +} + +static int patch_ad1980_specific(struct snd_ac97 *ac97) +{ + int err; + + err = patch_ad1888_specific(ac97); + if (err < 0) + return err; + return patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1); +} + +static const struct snd_ac97_build_ops patch_ad1980_build_ops = { + .build_post_spdif = patch_ad198x_post_spdif, + .build_specific = patch_ad1980_specific, +#ifdef CONFIG_PM + .resume = ad18xx_resume, +#endif + .update_jacks = ad1888_update_jacks, +}; + +static int patch_ad1980(struct snd_ac97 * ac97) +{ + patch_ad1888(ac97); + ac97->build_ops = &patch_ad1980_build_ops; + return 0; +} + +static int snd_ac97_ad1985_vrefout_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "High-Z", "3.7 V", "2.25 V", "0 V" + }; + + return snd_ctl_enum_info(uinfo, 1, 4, texts); +} + +static int snd_ac97_ad1985_vrefout_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + static const int reg2ctrl[4] = {2, 0, 1, 3}; + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + val = (ac97->regs[AC97_AD_MISC] & AC97_AD198X_VREF_MASK) + >> AC97_AD198X_VREF_SHIFT; + ucontrol->value.enumerated.item[0] = reg2ctrl[val]; + return 0; +} + +static int snd_ac97_ad1985_vrefout_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + static const int ctrl2reg[4] = {1, 2, 0, 3}; + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + if (ucontrol->value.enumerated.item[0] > 3) + return -EINVAL; + val = ctrl2reg[ucontrol->value.enumerated.item[0]] + << AC97_AD198X_VREF_SHIFT; + return snd_ac97_update_bits(ac97, AC97_AD_MISC, + AC97_AD198X_VREF_MASK, val); +} + +static const struct snd_kcontrol_new snd_ac97_ad1985_controls[] = { + AC97_SINGLE("Exchange Center/LFE", AC97_AD_SERIAL_CFG, 3, 1, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Exchange Front/Surround", + .info = snd_ac97_ad1888_lohpsel_info, + .get = snd_ac97_ad1888_lohpsel_get, + .put = snd_ac97_ad1888_lohpsel_put + }, + AC97_SINGLE("High Pass Filter Enable", AC97_AD_TEST2, 12, 1, 1), + AC97_SINGLE("Spread Front to Surround and Center/LFE", + AC97_AD_MISC, 7, 1, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Downmix", + .info = snd_ac97_ad1888_downmix_info, + .get = snd_ac97_ad1888_downmix_get, + .put = snd_ac97_ad1888_downmix_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "V_REFOUT", + .info = snd_ac97_ad1985_vrefout_info, + .get = snd_ac97_ad1985_vrefout_get, + .put = snd_ac97_ad1985_vrefout_put + }, + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_CTL, + + AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0), + AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0), +}; + +static void ad1985_update_jacks(struct snd_ac97 *ac97) +{ + ad1888_update_jacks(ac97); + /* clear OMS if shared jack is to be used for C/LFE out */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 1 << 9, + is_shared_micin(ac97) ? 1 << 9 : 0); +} + +static int patch_ad1985_specific(struct snd_ac97 *ac97) +{ + int err; + + /* rename 0x04 as "Master" and 0x02 as "Master Surround" */ + snd_ac97_rename_vol_ctl(ac97, "Master Playback", + "Master Surround Playback"); + snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Master Playback"); + + err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1); + if (err < 0) + return err; + + return patch_build_controls(ac97, snd_ac97_ad1985_controls, + ARRAY_SIZE(snd_ac97_ad1985_controls)); +} + +static const struct snd_ac97_build_ops patch_ad1985_build_ops = { + .build_post_spdif = patch_ad198x_post_spdif, + .build_specific = patch_ad1985_specific, +#ifdef CONFIG_PM + .resume = ad18xx_resume, +#endif + .update_jacks = ad1985_update_jacks, +}; + +static int patch_ad1985(struct snd_ac97 * ac97) +{ + unsigned short misc; + + patch_ad1881(ac97); + ac97->build_ops = &patch_ad1985_build_ops; + misc = snd_ac97_read(ac97, AC97_AD_MISC); + /* switch front/surround line-out/hp-out */ + /* AD-compatible mode */ + /* Stereo mutes enabled */ + snd_ac97_write_cache(ac97, AC97_AD_MISC, misc | + AC97_AD198X_LOSEL | + AC97_AD198X_HPSEL | + AC97_AD198X_MSPLT | + AC97_AD198X_AC97NC); + ac97->flags |= AC97_STEREO_MUTES; + + /* update current jack configuration */ + ad1985_update_jacks(ac97); + + /* on AD1985 rev. 3, AC'97 revision bits are zero */ + ac97->ext_id = (ac97->ext_id & ~AC97_EI_REV_MASK) | AC97_EI_REV_23; + return 0; +} + +#define snd_ac97_ad1986_bool_info snd_ctl_boolean_mono_info + +static int snd_ac97_ad1986_lososel_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_AD_MISC3]; + ucontrol->value.integer.value[0] = (val & AC97_AD1986_LOSEL) != 0; + return 0; +} + +static int snd_ac97_ad1986_lososel_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int ret0; + int ret1; + int sprd = (ac97->regs[AC97_AD_MISC] & AC97_AD1986_SPRD) != 0; + + ret0 = snd_ac97_update_bits(ac97, AC97_AD_MISC3, AC97_AD1986_LOSEL, + ucontrol->value.integer.value[0] != 0 + ? AC97_AD1986_LOSEL : 0); + if (ret0 < 0) + return ret0; + + /* SOSEL is set to values of "Spread" or "Exchange F/S" controls */ + ret1 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SOSEL, + (ucontrol->value.integer.value[0] != 0 + || sprd) + ? AC97_AD1986_SOSEL : 0); + if (ret1 < 0) + return ret1; + + return (ret0 > 0 || ret1 > 0) ? 1 : 0; +} + +static int snd_ac97_ad1986_spread_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_AD_MISC]; + ucontrol->value.integer.value[0] = (val & AC97_AD1986_SPRD) != 0; + return 0; +} + +static int snd_ac97_ad1986_spread_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + int ret0; + int ret1; + int sprd = (ac97->regs[AC97_AD_MISC3] & AC97_AD1986_LOSEL) != 0; + + ret0 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SPRD, + ucontrol->value.integer.value[0] != 0 + ? AC97_AD1986_SPRD : 0); + if (ret0 < 0) + return ret0; + + /* SOSEL is set to values of "Spread" or "Exchange F/S" controls */ + ret1 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SOSEL, + (ucontrol->value.integer.value[0] != 0 + || sprd) + ? AC97_AD1986_SOSEL : 0); + if (ret1 < 0) + return ret1; + + return (ret0 > 0 || ret1 > 0) ? 1 : 0; +} + +static int snd_ac97_ad1986_miclisel_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = ac97->spec.ad18xx.swap_mic_linein; + return 0; +} + +static int snd_ac97_ad1986_miclisel_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned char swap = ucontrol->value.integer.value[0] != 0; + + if (swap != ac97->spec.ad18xx.swap_mic_linein) { + ac97->spec.ad18xx.swap_mic_linein = swap; + if (ac97->build_ops->update_jacks) + ac97->build_ops->update_jacks(ac97); + return 1; + } + return 0; +} + +static int snd_ac97_ad1986_vrefout_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* Use MIC_1/2 V_REFOUT as the "get" value */ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + unsigned short reg = ac97->regs[AC97_AD_MISC2]; + if ((reg & AC97_AD1986_MVREF0) != 0) + val = 2; + else if ((reg & AC97_AD1986_MVREF1) != 0) + val = 3; + else if ((reg & AC97_AD1986_MVREF2) != 0) + val = 1; + else + val = 0; + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int snd_ac97_ad1986_vrefout_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short cval; + unsigned short lval; + unsigned short mval; + int cret; + int lret; + int mret; + + switch (ucontrol->value.enumerated.item[0]) + { + case 0: /* High-Z */ + cval = 0; + lval = 0; + mval = 0; + break; + case 1: /* 3.7 V */ + cval = AC97_AD1986_CVREF2; + lval = AC97_AD1986_LVREF2; + mval = AC97_AD1986_MVREF2; + break; + case 2: /* 2.25 V */ + cval = AC97_AD1986_CVREF0; + lval = AC97_AD1986_LVREF0; + mval = AC97_AD1986_MVREF0; + break; + case 3: /* 0 V */ + cval = AC97_AD1986_CVREF1; + lval = AC97_AD1986_LVREF1; + mval = AC97_AD1986_MVREF1; + break; + default: + return -EINVAL; + } + + cret = snd_ac97_update_bits(ac97, AC97_AD_MISC2, + AC97_AD1986_CVREF_MASK, cval); + if (cret < 0) + return cret; + lret = snd_ac97_update_bits(ac97, AC97_AD_MISC3, + AC97_AD1986_LVREF_MASK, lval); + if (lret < 0) + return lret; + mret = snd_ac97_update_bits(ac97, AC97_AD_MISC2, + AC97_AD1986_MVREF_MASK, mval); + if (mret < 0) + return mret; + + return (cret > 0 || lret > 0 || mret > 0) ? 1 : 0; +} + +static const struct snd_kcontrol_new snd_ac97_ad1986_controls[] = { + AC97_SINGLE("Exchange Center/LFE", AC97_AD_SERIAL_CFG, 3, 1, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Exchange Front/Surround", + .info = snd_ac97_ad1986_bool_info, + .get = snd_ac97_ad1986_lososel_get, + .put = snd_ac97_ad1986_lososel_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Exchange Mic/Line In", + .info = snd_ac97_ad1986_bool_info, + .get = snd_ac97_ad1986_miclisel_get, + .put = snd_ac97_ad1986_miclisel_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Spread Front to Surround and Center/LFE", + .info = snd_ac97_ad1986_bool_info, + .get = snd_ac97_ad1986_spread_get, + .put = snd_ac97_ad1986_spread_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Downmix", + .info = snd_ac97_ad1888_downmix_info, + .get = snd_ac97_ad1888_downmix_get, + .put = snd_ac97_ad1888_downmix_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "V_REFOUT", + .info = snd_ac97_ad1985_vrefout_info, + .get = snd_ac97_ad1986_vrefout_get, + .put = snd_ac97_ad1986_vrefout_put + }, + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_CTL, + + AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0), + AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0) +}; + +static void ad1986_update_jacks(struct snd_ac97 *ac97) +{ + unsigned short misc_val = 0; + unsigned short ser_val; + + /* disable SURROUND and CENTER/LFE if not surround mode */ + if (!is_surround_on(ac97)) + misc_val |= AC97_AD1986_SODIS; + if (!is_clfe_on(ac97)) + misc_val |= AC97_AD1986_CLDIS; + + /* select line input (default=LINE_IN, SURROUND or MIC_1/2) */ + if (is_shared_linein(ac97)) + misc_val |= AC97_AD1986_LISEL_SURR; + else if (ac97->spec.ad18xx.swap_mic_linein != 0) + misc_val |= AC97_AD1986_LISEL_MIC; + snd_ac97_update_bits(ac97, AC97_AD_MISC, + AC97_AD1986_SODIS | AC97_AD1986_CLDIS | + AC97_AD1986_LISEL_MASK, + misc_val); + + /* select microphone input (MIC_1/2, Center/LFE or LINE_IN) */ + if (is_shared_micin(ac97)) + ser_val = AC97_AD1986_OMS_C; + else if (ac97->spec.ad18xx.swap_mic_linein != 0) + ser_val = AC97_AD1986_OMS_L; + else + ser_val = AC97_AD1986_OMS_M; + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, + AC97_AD1986_OMS_MASK, + ser_val); +} + +static int patch_ad1986_specific(struct snd_ac97 *ac97) +{ + int err; + + err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1); + if (err < 0) + return err; + + return patch_build_controls(ac97, snd_ac97_ad1986_controls, + ARRAY_SIZE(snd_ac97_ad1985_controls)); +} + +static const struct snd_ac97_build_ops patch_ad1986_build_ops = { + .build_post_spdif = patch_ad198x_post_spdif, + .build_specific = patch_ad1986_specific, +#ifdef CONFIG_PM + .resume = ad18xx_resume, +#endif + .update_jacks = ad1986_update_jacks, +}; + +static int patch_ad1986(struct snd_ac97 * ac97) +{ + patch_ad1881(ac97); + ac97->build_ops = &patch_ad1986_build_ops; + ac97->flags |= AC97_STEREO_MUTES; + + /* update current jack configuration */ + ad1986_update_jacks(ac97); + + return 0; +} + +/* + * realtek ALC203: use mono-out for pin 37 + */ +static int patch_alc203(struct snd_ac97 *ac97) +{ + snd_ac97_update_bits(ac97, 0x7a, 0x400, 0x400); + return 0; +} + +/* + * realtek ALC65x/850 codecs + */ +static void alc650_update_jacks(struct snd_ac97 *ac97) +{ + int shared; + + /* shared Line-In / Surround Out */ + shared = is_shared_surrout(ac97); + snd_ac97_update_bits(ac97, AC97_ALC650_MULTICH, 1 << 9, + shared ? (1 << 9) : 0); + /* update shared Mic In / Center/LFE Out */ + shared = is_shared_clfeout(ac97); + /* disable/enable vref */ + snd_ac97_update_bits(ac97, AC97_ALC650_CLOCK, 1 << 12, + shared ? (1 << 12) : 0); + /* turn on/off center-on-mic */ + snd_ac97_update_bits(ac97, AC97_ALC650_MULTICH, 1 << 10, + shared ? (1 << 10) : 0); + /* GPIO0 high for mic */ + snd_ac97_update_bits(ac97, AC97_ALC650_GPIO_STATUS, 0x100, + shared ? 0 : 0x100); +} + +static int alc650_swap_surround_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + struct snd_pcm_chmap *map = ac97->chmaps[SNDRV_PCM_STREAM_PLAYBACK]; + + if (map) { + if (ucontrol->value.integer.value[0]) + map->chmap = snd_pcm_std_chmaps; + else + map->chmap = snd_pcm_alt_chmaps; + } + return snd_ac97_put_volsw(kcontrol, ucontrol); +} + +static const struct snd_kcontrol_new snd_ac97_controls_alc650[] = { + AC97_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0), + AC97_SINGLE("Surround Down Mix", AC97_ALC650_MULTICH, 1, 1, 0), + AC97_SINGLE("Center/LFE Down Mix", AC97_ALC650_MULTICH, 2, 1, 0), + AC97_SINGLE("Exchange Center/LFE", AC97_ALC650_MULTICH, 3, 1, 0), + /* 4: Analog Input To Surround */ + /* 5: Analog Input To Center/LFE */ + /* 6: Independent Master Volume Right */ + /* 7: Independent Master Volume Left */ + /* 8: reserved */ + /* 9: Line-In/Surround share */ + /* 10: Mic/CLFE share */ + /* 11-13: in IEC958 controls */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Swap Surround Slot", + .info = snd_ac97_info_volsw, + .get = snd_ac97_get_volsw, + .put = alc650_swap_surround_put, + .private_value = AC97_SINGLE_VALUE(AC97_ALC650_MULTICH, 14, 1, 0), + }, +#if 0 /* always set in patch_alc650 */ + AC97_SINGLE("IEC958 Input Clock Enable", AC97_ALC650_CLOCK, 0, 1, 0), + AC97_SINGLE("IEC958 Input Pin Enable", AC97_ALC650_CLOCK, 1, 1, 0), + AC97_SINGLE("Surround DAC Switch", AC97_ALC650_SURR_DAC_VOL, 15, 1, 1), + AC97_DOUBLE("Surround DAC Volume", AC97_ALC650_SURR_DAC_VOL, 8, 0, 31, 1), + AC97_SINGLE("Center/LFE DAC Switch", AC97_ALC650_LFE_DAC_VOL, 15, 1, 1), + AC97_DOUBLE("Center/LFE DAC Volume", AC97_ALC650_LFE_DAC_VOL, 8, 0, 31, 1), +#endif + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_CTL, +}; + +static const struct snd_kcontrol_new snd_ac97_spdif_controls_alc650[] = { + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_ALC650_MULTICH, 11, 1, 0), + AC97_SINGLE("Analog to IEC958 Output", AC97_ALC650_MULTICH, 12, 1, 0), + /* disable this controls since it doesn't work as expected */ + /* AC97_SINGLE("IEC958 Input Monitor", AC97_ALC650_MULTICH, 13, 1, 0), */ +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_5bit_3db_max, -4350, 150, 0); + +static int patch_alc650_specific(struct snd_ac97 * ac97) +{ + int err; + + err = patch_build_controls(ac97, snd_ac97_controls_alc650, ARRAY_SIZE(snd_ac97_controls_alc650)); + if (err < 0) + return err; + if (ac97->ext_id & AC97_EI_SPDIF) { + err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc650, ARRAY_SIZE(snd_ac97_spdif_controls_alc650)); + if (err < 0) + return err; + } + if (ac97->id != AC97_ID_ALC650F) + reset_tlv(ac97, "Master Playback Volume", + db_scale_5bit_3db_max); + return 0; +} + +static const struct snd_ac97_build_ops patch_alc650_ops = { + .build_specific = patch_alc650_specific, + .update_jacks = alc650_update_jacks +}; + +static int patch_alc650(struct snd_ac97 * ac97) +{ + unsigned short val; + + ac97->build_ops = &patch_alc650_ops; + + /* determine the revision */ + val = snd_ac97_read(ac97, AC97_ALC650_REVISION) & 0x3f; + if (val < 3) + ac97->id = 0x414c4720; /* Old version */ + else if (val < 0x10) + ac97->id = 0x414c4721; /* D version */ + else if (val < 0x20) + ac97->id = 0x414c4722; /* E version */ + else if (val < 0x30) + ac97->id = 0x414c4723; /* F version */ + + /* revision E or F */ + /* FIXME: what about revision D ? */ + ac97->spec.dev_flags = (ac97->id == 0x414c4722 || + ac97->id == 0x414c4723); + + /* enable AC97_ALC650_GPIO_SETUP, AC97_ALC650_CLOCK for R/W */ + snd_ac97_write_cache(ac97, AC97_ALC650_GPIO_STATUS, + snd_ac97_read(ac97, AC97_ALC650_GPIO_STATUS) | 0x8000); + + /* Enable SPDIF-IN only on Rev.E and above */ + val = snd_ac97_read(ac97, AC97_ALC650_CLOCK); + /* SPDIF IN with pin 47 */ + if (ac97->spec.dev_flags && + /* ASUS A6KM requires EAPD */ + ! (ac97->subsystem_vendor == 0x1043 && + ac97->subsystem_device == 0x1103)) + val |= 0x03; /* enable */ + else + val &= ~0x03; /* disable */ + snd_ac97_write_cache(ac97, AC97_ALC650_CLOCK, val); + + /* set default: slot 3,4,7,8,6,9 + spdif-in monitor off, analog-spdif off, spdif-in off + center on mic off, surround on line-in off + downmix off, duplicate front off + */ + snd_ac97_write_cache(ac97, AC97_ALC650_MULTICH, 0); + + /* set GPIO0 for mic bias */ + /* GPIO0 pin output, no interrupt, high */ + snd_ac97_write_cache(ac97, AC97_ALC650_GPIO_SETUP, + snd_ac97_read(ac97, AC97_ALC650_GPIO_SETUP) | 0x01); + snd_ac97_write_cache(ac97, AC97_ALC650_GPIO_STATUS, + (snd_ac97_read(ac97, AC97_ALC650_GPIO_STATUS) | 0x100) & ~0x10); + + /* full DAC volume */ + snd_ac97_write_cache(ac97, AC97_ALC650_SURR_DAC_VOL, 0x0808); + snd_ac97_write_cache(ac97, AC97_ALC650_LFE_DAC_VOL, 0x0808); + return 0; +} + +static void alc655_update_jacks(struct snd_ac97 *ac97) +{ + int shared; + + /* shared Line-In / Surround Out */ + shared = is_shared_surrout(ac97); + ac97_update_bits_page(ac97, AC97_ALC650_MULTICH, 1 << 9, + shared ? (1 << 9) : 0, 0); + /* update shared Mic In / Center/LFE Out */ + shared = is_shared_clfeout(ac97); + /* misc control; vrefout disable */ + snd_ac97_update_bits(ac97, AC97_ALC650_CLOCK, 1 << 12, + shared ? (1 << 12) : 0); + ac97_update_bits_page(ac97, AC97_ALC650_MULTICH, 1 << 10, + shared ? (1 << 10) : 0, 0); +} + +static const struct snd_kcontrol_new snd_ac97_controls_alc655[] = { + AC97_PAGE_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0, 0), + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_CTL, +}; + +static int alc655_iec958_route_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts_655[3] = { + "PCM", "Analog In", "IEC958 In" + }; + static const char * const texts_658[4] = { + "PCM", "Analog1 In", "Analog2 In", "IEC958 In" + }; + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + if (ac97->spec.dev_flags) + return snd_ctl_enum_info(uinfo, 1, 4, texts_658); + else + return snd_ctl_enum_info(uinfo, 1, 3, texts_655); +} + +static int alc655_iec958_route_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_ALC650_MULTICH]; + val = (val >> 12) & 3; + if (ac97->spec.dev_flags && val == 3) + val = 0; + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int alc655_iec958_route_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + return ac97_update_bits_page(ac97, AC97_ALC650_MULTICH, 3 << 12, + (unsigned short)ucontrol->value.enumerated.item[0] << 12, + 0); +} + +static const struct snd_kcontrol_new snd_ac97_spdif_controls_alc655[] = { + AC97_PAGE_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_ALC650_MULTICH, 11, 1, 0, 0), + /* disable this controls since it doesn't work as expected */ + /* AC97_PAGE_SINGLE("IEC958 Input Monitor", AC97_ALC650_MULTICH, 14, 1, 0, 0), */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + .info = alc655_iec958_route_info, + .get = alc655_iec958_route_get, + .put = alc655_iec958_route_put, + }, +}; + +static int patch_alc655_specific(struct snd_ac97 * ac97) +{ + int err; + + err = patch_build_controls(ac97, snd_ac97_controls_alc655, ARRAY_SIZE(snd_ac97_controls_alc655)); + if (err < 0) + return err; + if (ac97->ext_id & AC97_EI_SPDIF) { + err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc655, ARRAY_SIZE(snd_ac97_spdif_controls_alc655)); + if (err < 0) + return err; + } + return 0; +} + +static const struct snd_ac97_build_ops patch_alc655_ops = { + .build_specific = patch_alc655_specific, + .update_jacks = alc655_update_jacks +}; + +static int patch_alc655(struct snd_ac97 * ac97) +{ + unsigned int val; + + if (ac97->id == AC97_ID_ALC658) { + ac97->spec.dev_flags = 1; /* ALC658 */ + if ((snd_ac97_read(ac97, AC97_ALC650_REVISION) & 0x3f) == 2) { + ac97->id = AC97_ID_ALC658D; + ac97->spec.dev_flags = 2; + } + } + + ac97->build_ops = &patch_alc655_ops; + + /* assume only page 0 for writing cache */ + snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR); + + /* adjust default values */ + val = snd_ac97_read(ac97, 0x7a); /* misc control */ + if (ac97->spec.dev_flags) /* ALC658 */ + val &= ~(1 << 1); /* Pin 47 is spdif input pin */ + else { /* ALC655 */ + if (ac97->subsystem_vendor == 0x1462 && + (ac97->subsystem_device == 0x0131 || /* MSI S270 laptop */ + ac97->subsystem_device == 0x0161 || /* LG K1 Express */ + ac97->subsystem_device == 0x0351 || /* MSI L725 laptop */ + ac97->subsystem_device == 0x0471 || /* MSI L720 laptop */ + ac97->subsystem_device == 0x0061)) /* MSI S250 laptop */ + val &= ~(1 << 1); /* Pin 47 is EAPD (for internal speaker) */ + else + val |= (1 << 1); /* Pin 47 is spdif input pin */ + /* this seems missing on some hardwares */ + ac97->ext_id |= AC97_EI_SPDIF; + } + val &= ~(1 << 12); /* vref enable */ + snd_ac97_write_cache(ac97, 0x7a, val); + /* set default: spdif-in enabled, + spdif-in monitor off, spdif-in PCM off + center on mic off, surround on line-in off + duplicate front off + */ + snd_ac97_write_cache(ac97, AC97_ALC650_MULTICH, 1<<15); + + /* full DAC volume */ + snd_ac97_write_cache(ac97, AC97_ALC650_SURR_DAC_VOL, 0x0808); + snd_ac97_write_cache(ac97, AC97_ALC650_LFE_DAC_VOL, 0x0808); + + /* update undocumented bit... */ + if (ac97->id == AC97_ID_ALC658D) + snd_ac97_update_bits(ac97, 0x74, 0x0800, 0x0800); + + return 0; +} + + +#define AC97_ALC850_JACK_SELECT 0x76 +#define AC97_ALC850_MISC1 0x7a +#define AC97_ALC850_MULTICH 0x6a + +static void alc850_update_jacks(struct snd_ac97 *ac97) +{ + int shared; + int aux_is_back_surround; + + /* shared Line-In / Surround Out */ + shared = is_shared_surrout(ac97); + /* SURR 1kOhm (bit4), Amp (bit5) */ + snd_ac97_update_bits(ac97, AC97_ALC850_MISC1, (1<<4)|(1<<5), + shared ? (1<<5) : (1<<4)); + /* LINE-IN = 0, SURROUND = 2 */ + snd_ac97_update_bits(ac97, AC97_ALC850_JACK_SELECT, 7 << 12, + shared ? (2<<12) : (0<<12)); + /* update shared Mic In / Center/LFE Out */ + shared = is_shared_clfeout(ac97); + /* Vref disable (bit12), 1kOhm (bit13) */ + snd_ac97_update_bits(ac97, AC97_ALC850_MISC1, (1<<12)|(1<<13), + shared ? (1<<12) : (1<<13)); + /* MIC-IN = 1, CENTER-LFE = 5 */ + snd_ac97_update_bits(ac97, AC97_ALC850_JACK_SELECT, 7 << 4, + shared ? (5<<4) : (1<<4)); + + aux_is_back_surround = alc850_is_aux_back_surround(ac97); + /* Aux is Back Surround */ + snd_ac97_update_bits(ac97, AC97_ALC850_MULTICH, 1 << 10, + aux_is_back_surround ? (1<<10) : (0<<10)); +} + +static const struct snd_kcontrol_new snd_ac97_controls_alc850[] = { + AC97_PAGE_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0, 0), + AC97_SINGLE("Mic Front Input Switch", AC97_ALC850_JACK_SELECT, 15, 1, 1), + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_8CH_CTL, +}; + +static int patch_alc850_specific(struct snd_ac97 *ac97) +{ + int err; + + err = patch_build_controls(ac97, snd_ac97_controls_alc850, ARRAY_SIZE(snd_ac97_controls_alc850)); + if (err < 0) + return err; + if (ac97->ext_id & AC97_EI_SPDIF) { + err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc655, ARRAY_SIZE(snd_ac97_spdif_controls_alc655)); + if (err < 0) + return err; + } + return 0; +} + +static const struct snd_ac97_build_ops patch_alc850_ops = { + .build_specific = patch_alc850_specific, + .update_jacks = alc850_update_jacks +}; + +static int patch_alc850(struct snd_ac97 *ac97) +{ + ac97->build_ops = &patch_alc850_ops; + + ac97->spec.dev_flags = 0; /* for IEC958 playback route - ALC655 compatible */ + ac97->flags |= AC97_HAS_8CH; + + /* assume only page 0 for writing cache */ + snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR); + + /* adjust default values */ + /* set default: spdif-in enabled, + spdif-in monitor off, spdif-in PCM off + center on mic off, surround on line-in off + duplicate front off + NB default bit 10=0 = Aux is Capture, not Back Surround + */ + snd_ac97_write_cache(ac97, AC97_ALC650_MULTICH, 1<<15); + /* SURR_OUT: on, Surr 1kOhm: on, Surr Amp: off, Front 1kOhm: off + * Front Amp: on, Vref: enable, Center 1kOhm: on, Mix: on + */ + snd_ac97_write_cache(ac97, 0x7a, (1<<1)|(1<<4)|(0<<5)|(1<<6)| + (1<<7)|(0<<12)|(1<<13)|(0<<14)); + /* detection UIO2,3: all path floating, UIO3: MIC, Vref2: disable, + * UIO1: FRONT, Vref3: disable, UIO3: LINE, Front-Mic: mute + */ + snd_ac97_write_cache(ac97, 0x76, (0<<0)|(0<<2)|(1<<4)|(1<<7)|(2<<8)| + (1<<11)|(0<<12)|(1<<15)); + + /* full DAC volume */ + snd_ac97_write_cache(ac97, AC97_ALC650_SURR_DAC_VOL, 0x0808); + snd_ac97_write_cache(ac97, AC97_ALC650_LFE_DAC_VOL, 0x0808); + return 0; +} + +static int patch_aztech_azf3328_specific(struct snd_ac97 *ac97) +{ + struct snd_kcontrol *kctl_3d_center = + snd_ac97_find_mixer_ctl(ac97, "3D Control - Center"); + struct snd_kcontrol *kctl_3d_depth = + snd_ac97_find_mixer_ctl(ac97, "3D Control - Depth"); + + /* + * 3D register is different from AC97 standard layout + * (also do some renaming, to resemble Windows driver naming) + */ + if (kctl_3d_center) { + kctl_3d_center->private_value = + AC97_SINGLE_VALUE(AC97_3D_CONTROL, 1, 0x07, 0); + snd_ac97_rename_vol_ctl(ac97, + "3D Control - Center", "3D Control - Width" + ); + } + if (kctl_3d_depth) + kctl_3d_depth->private_value = + AC97_SINGLE_VALUE(AC97_3D_CONTROL, 8, 0x03, 0); + + /* Aztech Windows driver calls the + equivalent control "Modem Playback", thus rename it: */ + snd_ac97_rename_vol_ctl(ac97, + "Master Mono Playback", "Modem Playback" + ); + snd_ac97_rename_vol_ctl(ac97, + "Headphone Playback", "FM Synth Playback" + ); + + return 0; +} + +static const struct snd_ac97_build_ops patch_aztech_azf3328_ops = { + .build_specific = patch_aztech_azf3328_specific +}; + +static int patch_aztech_azf3328(struct snd_ac97 *ac97) +{ + ac97->build_ops = &patch_aztech_azf3328_ops; + return 0; +} + +/* + * C-Media CM97xx codecs + */ +static void cm9738_update_jacks(struct snd_ac97 *ac97) +{ + /* shared Line-In / Surround Out */ + snd_ac97_update_bits(ac97, AC97_CM9738_VENDOR_CTRL, 1 << 10, + is_shared_surrout(ac97) ? (1 << 10) : 0); +} + +static const struct snd_kcontrol_new snd_ac97_cm9738_controls[] = { + AC97_SINGLE("Duplicate Front", AC97_CM9738_VENDOR_CTRL, 13, 1, 0), + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_4CH_CTL, +}; + +static int patch_cm9738_specific(struct snd_ac97 * ac97) +{ + return patch_build_controls(ac97, snd_ac97_cm9738_controls, ARRAY_SIZE(snd_ac97_cm9738_controls)); +} + +static const struct snd_ac97_build_ops patch_cm9738_ops = { + .build_specific = patch_cm9738_specific, + .update_jacks = cm9738_update_jacks +}; + +static int patch_cm9738(struct snd_ac97 * ac97) +{ + ac97->build_ops = &patch_cm9738_ops; + /* FIXME: can anyone confirm below? */ + /* CM9738 has no PCM volume although the register reacts */ + ac97->flags |= AC97_HAS_NO_PCM_VOL; + snd_ac97_write_cache(ac97, AC97_PCM, 0x8000); + + return 0; +} + +static int snd_ac97_cmedia_spdif_playback_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "Analog", "Digital" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_ac97_cmedia_spdif_playback_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ac97->regs[AC97_CM9739_SPDIF_CTRL]; + ucontrol->value.enumerated.item[0] = (val >> 1) & 0x01; + return 0; +} + +static int snd_ac97_cmedia_spdif_playback_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + return snd_ac97_update_bits(ac97, AC97_CM9739_SPDIF_CTRL, + 0x01 << 1, + (ucontrol->value.enumerated.item[0] & 0x01) << 1); +} + +static const struct snd_kcontrol_new snd_ac97_cm9739_controls_spdif[] = { + /* BIT 0: SPDI_EN - always true */ + { /* BIT 1: SPDIFS */ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + .info = snd_ac97_cmedia_spdif_playback_source_info, + .get = snd_ac97_cmedia_spdif_playback_source_get, + .put = snd_ac97_cmedia_spdif_playback_source_put, + }, + /* BIT 2: IG_SPIV */ + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Valid Switch", AC97_CM9739_SPDIF_CTRL, 2, 1, 0), + /* BIT 3: SPI2F */ + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Monitor", AC97_CM9739_SPDIF_CTRL, 3, 1, 0), + /* BIT 4: SPI2SDI */ + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_CM9739_SPDIF_CTRL, 4, 1, 0), + /* BIT 8: SPD32 - 32bit SPDIF - not supported yet */ +}; + +static void cm9739_update_jacks(struct snd_ac97 *ac97) +{ + /* shared Line-In / Surround Out */ + snd_ac97_update_bits(ac97, AC97_CM9739_MULTI_CHAN, 1 << 10, + is_shared_surrout(ac97) ? (1 << 10) : 0); + /* shared Mic In / Center/LFE Out **/ + snd_ac97_update_bits(ac97, AC97_CM9739_MULTI_CHAN, 0x3000, + is_shared_clfeout(ac97) ? 0x1000 : 0x2000); +} + +static const struct snd_kcontrol_new snd_ac97_cm9739_controls[] = { + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_CTL, +}; + +static int patch_cm9739_specific(struct snd_ac97 * ac97) +{ + return patch_build_controls(ac97, snd_ac97_cm9739_controls, ARRAY_SIZE(snd_ac97_cm9739_controls)); +} + +static int patch_cm9739_post_spdif(struct snd_ac97 * ac97) +{ + return patch_build_controls(ac97, snd_ac97_cm9739_controls_spdif, ARRAY_SIZE(snd_ac97_cm9739_controls_spdif)); +} + +static const struct snd_ac97_build_ops patch_cm9739_ops = { + .build_specific = patch_cm9739_specific, + .build_post_spdif = patch_cm9739_post_spdif, + .update_jacks = cm9739_update_jacks +}; + +static int patch_cm9739(struct snd_ac97 * ac97) +{ + unsigned short val; + + ac97->build_ops = &patch_cm9739_ops; + + /* CM9739/A has no Master and PCM volume although the register reacts */ + ac97->flags |= AC97_HAS_NO_MASTER_VOL | AC97_HAS_NO_PCM_VOL; + snd_ac97_write_cache(ac97, AC97_MASTER, 0x8000); + snd_ac97_write_cache(ac97, AC97_PCM, 0x8000); + + /* check spdif */ + val = snd_ac97_read(ac97, AC97_EXTENDED_STATUS); + if (val & AC97_EA_SPCV) { + /* enable spdif in */ + snd_ac97_write_cache(ac97, AC97_CM9739_SPDIF_CTRL, + snd_ac97_read(ac97, AC97_CM9739_SPDIF_CTRL) | 0x01); + ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */ + } else { + ac97->ext_id &= ~AC97_EI_SPDIF; /* disable extended-id */ + ac97->rates[AC97_RATES_SPDIF] = 0; + } + + /* set-up multi channel */ + /* bit 14: 0 = SPDIF, 1 = EAPD */ + /* bit 13: enable internal vref output for mic */ + /* bit 12: disable center/lfe (switchable) */ + /* bit 10: disable surround/line (switchable) */ + /* bit 9: mix 2 surround off */ + /* bit 4: undocumented; 0 mutes the CM9739A, which defaults to 1 */ + /* bit 3: undocumented; surround? */ + /* bit 0: dB */ + val = snd_ac97_read(ac97, AC97_CM9739_MULTI_CHAN) & (1 << 4); + val |= (1 << 3); + val |= (1 << 13); + if (! (ac97->ext_id & AC97_EI_SPDIF)) + val |= (1 << 14); + snd_ac97_write_cache(ac97, AC97_CM9739_MULTI_CHAN, val); + + /* FIXME: set up GPIO */ + snd_ac97_write_cache(ac97, 0x70, 0x0100); + snd_ac97_write_cache(ac97, 0x72, 0x0020); + /* Special exception for ASUS W1000/CMI9739. It does not have an SPDIF in. */ + if (ac97->pci && + ac97->subsystem_vendor == 0x1043 && + ac97->subsystem_device == 0x1843) { + snd_ac97_write_cache(ac97, AC97_CM9739_SPDIF_CTRL, + snd_ac97_read(ac97, AC97_CM9739_SPDIF_CTRL) & ~0x01); + snd_ac97_write_cache(ac97, AC97_CM9739_MULTI_CHAN, + snd_ac97_read(ac97, AC97_CM9739_MULTI_CHAN) | (1 << 14)); + } + + return 0; +} + +#define AC97_CM9761_MULTI_CHAN 0x64 +#define AC97_CM9761_FUNC 0x66 +#define AC97_CM9761_SPDIF_CTRL 0x6c + +static void cm9761_update_jacks(struct snd_ac97 *ac97) +{ + /* FIXME: check the bits for each model + * model 83 is confirmed to work + */ + static const unsigned short surr_on[3][2] = { + { 0x0008, 0x0000 }, /* 9761-78 & 82 */ + { 0x0000, 0x0008 }, /* 9761-82 rev.B */ + { 0x0000, 0x0008 }, /* 9761-83 */ + }; + static const unsigned short clfe_on[3][2] = { + { 0x0000, 0x1000 }, /* 9761-78 & 82 */ + { 0x1000, 0x0000 }, /* 9761-82 rev.B */ + { 0x0000, 0x1000 }, /* 9761-83 */ + }; + static const unsigned short surr_shared[3][2] = { + { 0x0000, 0x0400 }, /* 9761-78 & 82 */ + { 0x0000, 0x0400 }, /* 9761-82 rev.B */ + { 0x0000, 0x0400 }, /* 9761-83 */ + }; + static const unsigned short clfe_shared[3][2] = { + { 0x2000, 0x0880 }, /* 9761-78 & 82 */ + { 0x0000, 0x2880 }, /* 9761-82 rev.B */ + { 0x2000, 0x0800 }, /* 9761-83 */ + }; + unsigned short val = 0; + + val |= surr_on[ac97->spec.dev_flags][is_surround_on(ac97)]; + val |= clfe_on[ac97->spec.dev_flags][is_clfe_on(ac97)]; + val |= surr_shared[ac97->spec.dev_flags][is_shared_surrout(ac97)]; + val |= clfe_shared[ac97->spec.dev_flags][is_shared_clfeout(ac97)]; + + snd_ac97_update_bits(ac97, AC97_CM9761_MULTI_CHAN, 0x3c88, val); +} + +static const struct snd_kcontrol_new snd_ac97_cm9761_controls[] = { + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_CTL, +}; + +static int cm9761_spdif_out_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "AC-Link", "ADC", "SPDIF-In" }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int cm9761_spdif_out_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + if (ac97->regs[AC97_CM9761_FUNC] & 0x1) + ucontrol->value.enumerated.item[0] = 2; /* SPDIF-loopback */ + else if (ac97->regs[AC97_CM9761_SPDIF_CTRL] & 0x2) + ucontrol->value.enumerated.item[0] = 1; /* ADC loopback */ + else + ucontrol->value.enumerated.item[0] = 0; /* AC-link */ + return 0; +} + +static int cm9761_spdif_out_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.enumerated.item[0] == 2) + return snd_ac97_update_bits(ac97, AC97_CM9761_FUNC, 0x1, 0x1); + snd_ac97_update_bits(ac97, AC97_CM9761_FUNC, 0x1, 0); + return snd_ac97_update_bits(ac97, AC97_CM9761_SPDIF_CTRL, 0x2, + ucontrol->value.enumerated.item[0] == 1 ? 0x2 : 0); +} + +static const char * const cm9761_dac_clock[] = { + "AC-Link", "SPDIF-In", "Both" +}; +static const struct ac97_enum cm9761_dac_clock_enum = + AC97_ENUM_SINGLE(AC97_CM9761_SPDIF_CTRL, 9, 3, cm9761_dac_clock); + +static const struct snd_kcontrol_new snd_ac97_cm9761_controls_spdif[] = { + { /* BIT 1: SPDIFS */ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + .info = cm9761_spdif_out_source_info, + .get = cm9761_spdif_out_source_get, + .put = cm9761_spdif_out_source_put, + }, + /* BIT 2: IG_SPIV */ + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Valid Switch", AC97_CM9761_SPDIF_CTRL, 2, 1, 0), + /* BIT 3: SPI2F */ + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Monitor", AC97_CM9761_SPDIF_CTRL, 3, 1, 0), + /* BIT 4: SPI2SDI */ + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_CM9761_SPDIF_CTRL, 4, 1, 0), + /* BIT 9-10: DAC_CTL */ + AC97_ENUM("DAC Clock Source", cm9761_dac_clock_enum), +}; + +static int patch_cm9761_post_spdif(struct snd_ac97 * ac97) +{ + return patch_build_controls(ac97, snd_ac97_cm9761_controls_spdif, ARRAY_SIZE(snd_ac97_cm9761_controls_spdif)); +} + +static int patch_cm9761_specific(struct snd_ac97 * ac97) +{ + return patch_build_controls(ac97, snd_ac97_cm9761_controls, ARRAY_SIZE(snd_ac97_cm9761_controls)); +} + +static const struct snd_ac97_build_ops patch_cm9761_ops = { + .build_specific = patch_cm9761_specific, + .build_post_spdif = patch_cm9761_post_spdif, + .update_jacks = cm9761_update_jacks +}; + +static int patch_cm9761(struct snd_ac97 *ac97) +{ + unsigned short val; + + /* CM9761 has no PCM volume although the register reacts */ + /* Master volume seems to have _some_ influence on the analog + * input sounds + */ + ac97->flags |= /*AC97_HAS_NO_MASTER_VOL |*/ AC97_HAS_NO_PCM_VOL; + snd_ac97_write_cache(ac97, AC97_MASTER, 0x8808); + snd_ac97_write_cache(ac97, AC97_PCM, 0x8808); + + ac97->spec.dev_flags = 0; /* 1 = model 82 revision B, 2 = model 83 */ + if (ac97->id == AC97_ID_CM9761_82) { + unsigned short tmp; + /* check page 1, reg 0x60 */ + val = snd_ac97_read(ac97, AC97_INT_PAGING); + snd_ac97_write_cache(ac97, AC97_INT_PAGING, (val & ~0x0f) | 0x01); + tmp = snd_ac97_read(ac97, 0x60); + ac97->spec.dev_flags = tmp & 1; /* revision B? */ + snd_ac97_write_cache(ac97, AC97_INT_PAGING, val); + } else if (ac97->id == AC97_ID_CM9761_83) + ac97->spec.dev_flags = 2; + + ac97->build_ops = &patch_cm9761_ops; + + /* enable spdif */ + /* force the SPDIF bit in ext_id - codec doesn't set this bit! */ + ac97->ext_id |= AC97_EI_SPDIF; + /* to be sure: we overwrite the ext status bits */ + snd_ac97_write_cache(ac97, AC97_EXTENDED_STATUS, 0x05c0); + /* Don't set 0x0200 here. This results in the silent analog output */ + snd_ac97_write_cache(ac97, AC97_CM9761_SPDIF_CTRL, 0x0001); /* enable spdif-in */ + ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */ + + /* set-up multi channel */ + /* bit 15: pc master beep off + * bit 14: pin47 = EAPD/SPDIF + * bit 13: vref ctl [= cm9739] + * bit 12: CLFE control (reverted on rev B) + * bit 11: Mic/center share (reverted on rev B) + * bit 10: suddound/line share + * bit 9: Analog-in mix -> surround + * bit 8: Analog-in mix -> CLFE + * bit 7: Mic/LFE share (mic/center/lfe) + * bit 5: vref select (9761A) + * bit 4: front control + * bit 3: surround control (revereted with rev B) + * bit 2: front mic + * bit 1: stereo mic + * bit 0: mic boost level (0=20dB, 1=30dB) + */ + +#if 0 + if (ac97->spec.dev_flags) + val = 0x0214; + else + val = 0x321c; +#endif + val = snd_ac97_read(ac97, AC97_CM9761_MULTI_CHAN); + val |= (1 << 4); /* front on */ + snd_ac97_write_cache(ac97, AC97_CM9761_MULTI_CHAN, val); + + /* FIXME: set up GPIO */ + snd_ac97_write_cache(ac97, 0x70, 0x0100); + snd_ac97_write_cache(ac97, 0x72, 0x0020); + + return 0; +} + +#define AC97_CM9780_SIDE 0x60 +#define AC97_CM9780_JACK 0x62 +#define AC97_CM9780_MIXER 0x64 +#define AC97_CM9780_MULTI_CHAN 0x66 +#define AC97_CM9780_SPDIF 0x6c + +static const char * const cm9780_ch_select[] = { + "Front", "Side", "Center/LFE", "Rear" +}; +static const struct ac97_enum cm9780_ch_select_enum = + AC97_ENUM_SINGLE(AC97_CM9780_MULTI_CHAN, 6, 4, cm9780_ch_select); +static const struct snd_kcontrol_new cm9780_controls[] = { + AC97_DOUBLE("Side Playback Switch", AC97_CM9780_SIDE, 15, 7, 1, 1), + AC97_DOUBLE("Side Playback Volume", AC97_CM9780_SIDE, 8, 0, 31, 0), + AC97_ENUM("Side Playback Route", cm9780_ch_select_enum), +}; + +static int patch_cm9780_specific(struct snd_ac97 *ac97) +{ + return patch_build_controls(ac97, cm9780_controls, ARRAY_SIZE(cm9780_controls)); +} + +static const struct snd_ac97_build_ops patch_cm9780_ops = { + .build_specific = patch_cm9780_specific, + .build_post_spdif = patch_cm9761_post_spdif /* identical with CM9761 */ +}; + +static int patch_cm9780(struct snd_ac97 *ac97) +{ + unsigned short val; + + ac97->build_ops = &patch_cm9780_ops; + + /* enable spdif */ + if (ac97->ext_id & AC97_EI_SPDIF) { + ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */ + val = snd_ac97_read(ac97, AC97_CM9780_SPDIF); + val |= 0x1; /* SPDI_EN */ + snd_ac97_write_cache(ac97, AC97_CM9780_SPDIF, val); + } + + return 0; +} + +/* + * VIA VT1613 codec + */ +static const struct snd_kcontrol_new snd_ac97_controls_vt1613[] = { +AC97_SINGLE("DC Offset removal", 0x5a, 10, 1, 0), +}; + +static int patch_vt1613_specific(struct snd_ac97 *ac97) +{ + return patch_build_controls(ac97, &snd_ac97_controls_vt1613[0], + ARRAY_SIZE(snd_ac97_controls_vt1613)); +}; + +static const struct snd_ac97_build_ops patch_vt1613_ops = { + .build_specific = patch_vt1613_specific +}; + +static int patch_vt1613(struct snd_ac97 *ac97) +{ + ac97->build_ops = &patch_vt1613_ops; + + ac97->flags |= AC97_HAS_NO_VIDEO; + ac97->caps |= AC97_BC_HEADPHONE; + + return 0; +} + +/* + * VIA VT1616 codec + */ +static const struct snd_kcontrol_new snd_ac97_controls_vt1616[] = { +AC97_SINGLE("DC Offset removal", 0x5a, 10, 1, 0), +AC97_SINGLE("Alternate Level to Surround Out", 0x5a, 15, 1, 0), +AC97_SINGLE("Downmix LFE and Center to Front", 0x5a, 12, 1, 0), +AC97_SINGLE("Downmix Surround to Front", 0x5a, 11, 1, 0), +}; + +static const char * const follower_vols_vt1616[] = { + "Front Playback Volume", + "Surround Playback Volume", + "Center Playback Volume", + "LFE Playback Volume", + NULL +}; + +static const char * const follower_sws_vt1616[] = { + "Front Playback Switch", + "Surround Playback Switch", + "Center Playback Switch", + "LFE Playback Switch", + NULL +}; + +/* find a mixer control element with the given name */ +static struct snd_kcontrol *snd_ac97_find_mixer_ctl(struct snd_ac97 *ac97, + const char *name) +{ + return snd_ctl_find_id_mixer(ac97->bus->card, name); +} + +/* create a virtual master control and add followers */ +static int snd_ac97_add_vmaster(struct snd_ac97 *ac97, char *name, + const unsigned int *tlv, + const char * const *followers) +{ + struct snd_kcontrol *kctl; + int err; + + kctl = snd_ctl_make_virtual_master(name, tlv); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(ac97->bus->card, kctl); + if (err < 0) + return err; + + return snd_ctl_add_followers(ac97->bus->card, kctl, followers); +} + +static int patch_vt1616_specific(struct snd_ac97 * ac97) +{ + struct snd_kcontrol *kctl; + int err; + + if (snd_ac97_try_bit(ac97, 0x5a, 9)) { + err = patch_build_controls(ac97, &snd_ac97_controls_vt1616[0], 1); + if (err < 0) + return err; + } + err = patch_build_controls(ac97, &snd_ac97_controls_vt1616[1], ARRAY_SIZE(snd_ac97_controls_vt1616) - 1); + if (err < 0) + return err; + + /* There is already a misnamed master switch. Rename it. */ + kctl = snd_ac97_find_mixer_ctl(ac97, "Master Playback Volume"); + if (!kctl) + return -EINVAL; + + snd_ac97_rename_vol_ctl(ac97, "Master Playback", "Front Playback"); + + err = snd_ac97_add_vmaster(ac97, "Master Playback Volume", + kctl->tlv.p, follower_vols_vt1616); + if (err < 0) + return err; + + err = snd_ac97_add_vmaster(ac97, "Master Playback Switch", + NULL, follower_sws_vt1616); + if (err < 0) + return err; + + return 0; +} + +static const struct snd_ac97_build_ops patch_vt1616_ops = { + .build_specific = patch_vt1616_specific +}; + +static int patch_vt1616(struct snd_ac97 * ac97) +{ + ac97->build_ops = &patch_vt1616_ops; + return 0; +} + +/* + * VT1617A codec + */ + +/* + * unfortunately, the vt1617a stashes the twiddlers required for + * noodling the i/o jacks on 2 different regs. that means that we can't + * use the easy way provided by AC97_ENUM_DOUBLE() we have to write + * are own funcs. + * + * NB: this is absolutely and utterly different from the vt1618. dunno + * about the 1616. + */ + +/* copied from ac97_surround_jack_mode_info() */ +static int snd_ac97_vt1617a_smart51_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + /* ordering in this list reflects vt1617a docs for Reg 20 and + * 7a and Table 6 that lays out the matrix NB WRT Table6: SM51 + * is SM51EN *AND* it's Bit14, not Bit15 so the table is very + * counter-intuitive */ + + static const char * const texts[] = {"LineIn Mic1", "LineIn Mic1 Mic3", + "Surr LFE/C Mic3", "LineIn LFE/C Mic3", + "LineIn Mic2", "LineIn Mic2 Mic1", + "Surr LFE Mic1", "Surr LFE Mic1 Mic2"}; + + return snd_ctl_enum_info(uinfo, 1, 8, texts); +} + +static int snd_ac97_vt1617a_smart51_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ushort usSM51, usMS; + + struct snd_ac97 *pac97; + + pac97 = snd_kcontrol_chip(kcontrol); /* grab codec handle */ + + /* grab our desired bits, then mash them together in a manner + * consistent with Table 6 on page 17 in the 1617a docs */ + + usSM51 = snd_ac97_read(pac97, 0x7a) >> 14; + usMS = snd_ac97_read(pac97, 0x20) >> 8; + + ucontrol->value.enumerated.item[0] = (usSM51 << 1) + usMS; + + return 0; +} + +static int snd_ac97_vt1617a_smart51_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ushort usSM51, usMS, usReg; + + struct snd_ac97 *pac97; + + pac97 = snd_kcontrol_chip(kcontrol); /* grab codec handle */ + + usSM51 = ucontrol->value.enumerated.item[0] >> 1; + usMS = ucontrol->value.enumerated.item[0] & 1; + + /* push our values into the register - consider that things will be left + * in a funky state if the write fails */ + + usReg = snd_ac97_read(pac97, 0x7a); + snd_ac97_write_cache(pac97, 0x7a, (usReg & 0x3FFF) + (usSM51 << 14)); + usReg = snd_ac97_read(pac97, 0x20); + snd_ac97_write_cache(pac97, 0x20, (usReg & 0xFEFF) + (usMS << 8)); + + return 0; +} + +static const struct snd_kcontrol_new snd_ac97_controls_vt1617a[] = { + + AC97_SINGLE("Center/LFE Exchange", 0x5a, 8, 1, 0), + /* + * These are used to enable/disable surround sound on motherboards + * that have 3 bidirectional analog jacks + */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Smart 5.1 Select", + .info = snd_ac97_vt1617a_smart51_info, + .get = snd_ac97_vt1617a_smart51_get, + .put = snd_ac97_vt1617a_smart51_put, + }, +}; + +static int patch_vt1617a(struct snd_ac97 * ac97) +{ + int err = 0; + int val; + + /* we choose to not fail out at this point, but we tell the + caller when we return */ + + err = patch_build_controls(ac97, &snd_ac97_controls_vt1617a[0], + ARRAY_SIZE(snd_ac97_controls_vt1617a)); + + /* bring analog power consumption to normal by turning off the + * headphone amplifier, like WinXP driver for EPIA SP + */ + /* We need to check the bit before writing it. + * On some (many?) hardwares, setting bit actually clears it! + */ + val = snd_ac97_read(ac97, 0x5c); + if (!(val & 0x20)) + snd_ac97_write_cache(ac97, 0x5c, 0x20); + + ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */ + ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000; + ac97->build_ops = &patch_vt1616_ops; + + return err; +} + +/* VIA VT1618 8 CHANNEL AC97 CODEC + * + * VIA implements 'Smart 5.1' completely differently on the 1618 than + * it does on the 1617a. awesome! They seem to have sourced this + * particular revision of the technology from somebody else, it's + * called Universal Audio Jack and it shows up on some other folk's chips + * as well. + * + * ordering in this list reflects vt1618 docs for Reg 60h and + * the block diagram, DACs are as follows: + * + * OUT_O -> Front, + * OUT_1 -> Surround, + * OUT_2 -> C/LFE + * + * Unlike the 1617a, each OUT has a consistent set of mappings + * for all bitpatterns other than 00: + * + * 01 Unmixed Output + * 10 Line In + * 11 Mic In + * + * Special Case of 00: + * + * OUT_0 Mixed Output + * OUT_1 Reserved + * OUT_2 Reserved + * + * I have no idea what the hell Reserved does, but on an MSI + * CN700T, i have to set it to get 5.1 output - YMMV, bad + * shit may happen. + * + * If other chips use Universal Audio Jack, then this code might be applicable + * to them. + */ + +struct vt1618_uaj_item { + unsigned short mask; + unsigned short shift; + const char * const items[4]; +}; + +/* This list reflects the vt1618 docs for Vendor Defined Register 0x60. */ + +static const struct vt1618_uaj_item vt1618_uaj[3] = { + { + /* speaker jack */ + .mask = 0x03, + .shift = 0, + .items = { + "Speaker Out", "DAC Unmixed Out", "Line In", "Mic In" + } + }, + { + /* line jack */ + .mask = 0x0c, + .shift = 2, + .items = { + "Surround Out", "DAC Unmixed Out", "Line In", "Mic In" + } + }, + { + /* mic jack */ + .mask = 0x30, + .shift = 4, + .items = { + "Center LFE Out", "DAC Unmixed Out", "Line In", "Mic In" + }, + }, +}; + +static int snd_ac97_vt1618_UAJ_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 4, + vt1618_uaj[kcontrol->private_value].items); +} + +/* All of the vt1618 Universal Audio Jack twiddlers are on + * Vendor Defined Register 0x60, page 0. The bits, and thus + * the mask, are the only thing that changes + */ +static int snd_ac97_vt1618_UAJ_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned short datpag, uaj; + struct snd_ac97 *pac97 = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&pac97->page_mutex); + + datpag = snd_ac97_read(pac97, AC97_INT_PAGING) & AC97_PAGE_MASK; + snd_ac97_update_bits(pac97, AC97_INT_PAGING, AC97_PAGE_MASK, 0); + + uaj = snd_ac97_read(pac97, 0x60) & + vt1618_uaj[kcontrol->private_value].mask; + + snd_ac97_update_bits(pac97, AC97_INT_PAGING, AC97_PAGE_MASK, datpag); + + ucontrol->value.enumerated.item[0] = uaj >> + vt1618_uaj[kcontrol->private_value].shift; + + return 0; +} + +static int snd_ac97_vt1618_UAJ_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return ac97_update_bits_page(snd_kcontrol_chip(kcontrol), 0x60, + vt1618_uaj[kcontrol->private_value].mask, + ucontrol->value.enumerated.item[0]<< + vt1618_uaj[kcontrol->private_value].shift, + 0); +} + +/* config aux in jack - not found on 3 jack motherboards or soundcards */ + +static int snd_ac97_vt1618_aux_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const txt_aux[] = {"Aux In", "Back Surr Out"}; + + return snd_ctl_enum_info(uinfo, 1, 2, txt_aux); +} + +static int snd_ac97_vt1618_aux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = + (snd_ac97_read(snd_kcontrol_chip(kcontrol), 0x5c) & 0x0008)>>3; + return 0; +} + +static int snd_ac97_vt1618_aux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* toggle surround rear dac power */ + + snd_ac97_update_bits(snd_kcontrol_chip(kcontrol), 0x5c, 0x0008, + ucontrol->value.enumerated.item[0] << 3); + + /* toggle aux in surround rear out jack */ + + return snd_ac97_update_bits(snd_kcontrol_chip(kcontrol), 0x76, 0x0008, + ucontrol->value.enumerated.item[0] << 3); +} + +static const struct snd_kcontrol_new snd_ac97_controls_vt1618[] = { + AC97_SINGLE("Exchange Center/LFE", 0x5a, 8, 1, 0), + AC97_SINGLE("DC Offset", 0x5a, 10, 1, 0), + AC97_SINGLE("Soft Mute", 0x5c, 0, 1, 1), + AC97_SINGLE("Headphone Amp", 0x5c, 5, 1, 1), + AC97_DOUBLE("Back Surr Volume", 0x5e, 8, 0, 31, 1), + AC97_SINGLE("Back Surr Switch", 0x5e, 15, 1, 1), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Speaker Jack Mode", + .info = snd_ac97_vt1618_UAJ_info, + .get = snd_ac97_vt1618_UAJ_get, + .put = snd_ac97_vt1618_UAJ_put, + .private_value = 0 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Jack Mode", + .info = snd_ac97_vt1618_UAJ_info, + .get = snd_ac97_vt1618_UAJ_get, + .put = snd_ac97_vt1618_UAJ_put, + .private_value = 1 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Jack Mode", + .info = snd_ac97_vt1618_UAJ_info, + .get = snd_ac97_vt1618_UAJ_get, + .put = snd_ac97_vt1618_UAJ_put, + .private_value = 2 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Aux Jack Mode", + .info = snd_ac97_vt1618_aux_info, + .get = snd_ac97_vt1618_aux_get, + .put = snd_ac97_vt1618_aux_put, + } +}; + +static int patch_vt1618(struct snd_ac97 *ac97) +{ + return patch_build_controls(ac97, snd_ac97_controls_vt1618, + ARRAY_SIZE(snd_ac97_controls_vt1618)); +} + +/* + */ +static void it2646_update_jacks(struct snd_ac97 *ac97) +{ + /* shared Line-In / Surround Out */ + snd_ac97_update_bits(ac97, 0x76, 1 << 9, + is_shared_surrout(ac97) ? (1<<9) : 0); + /* shared Mic / Center/LFE Out */ + snd_ac97_update_bits(ac97, 0x76, 1 << 10, + is_shared_clfeout(ac97) ? (1<<10) : 0); +} + +static const struct snd_kcontrol_new snd_ac97_controls_it2646[] = { + AC97_SURROUND_JACK_MODE_CTL, + AC97_CHANNEL_MODE_CTL, +}; + +static const struct snd_kcontrol_new snd_ac97_spdif_controls_it2646[] = { + AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), 0x76, 11, 1, 0), + AC97_SINGLE("Analog to IEC958 Output", 0x76, 12, 1, 0), + AC97_SINGLE("IEC958 Input Monitor", 0x76, 13, 1, 0), +}; + +static int patch_it2646_specific(struct snd_ac97 * ac97) +{ + int err; + err = patch_build_controls(ac97, snd_ac97_controls_it2646, ARRAY_SIZE(snd_ac97_controls_it2646)); + if (err < 0) + return err; + err = patch_build_controls(ac97, snd_ac97_spdif_controls_it2646, ARRAY_SIZE(snd_ac97_spdif_controls_it2646)); + if (err < 0) + return err; + return 0; +} + +static const struct snd_ac97_build_ops patch_it2646_ops = { + .build_specific = patch_it2646_specific, + .update_jacks = it2646_update_jacks +}; + +static int patch_it2646(struct snd_ac97 * ac97) +{ + ac97->build_ops = &patch_it2646_ops; + /* full DAC volume */ + snd_ac97_write_cache(ac97, 0x5E, 0x0808); + snd_ac97_write_cache(ac97, 0x7A, 0x0808); + return 0; +} + +/* + * Si3036 codec + */ + +#define AC97_SI3036_CHIP_ID 0x5a +#define AC97_SI3036_LINE_CFG 0x5c + +static const struct snd_kcontrol_new snd_ac97_controls_si3036[] = { +AC97_DOUBLE("Modem Speaker Volume", 0x5c, 14, 12, 3, 1) +}; + +static int patch_si3036_specific(struct snd_ac97 * ac97) +{ + int idx, err; + for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_si3036); idx++) { + err = snd_ctl_add(ac97->bus->card, snd_ctl_new1(&snd_ac97_controls_si3036[idx], ac97)); + if (err < 0) + return err; + } + return 0; +} + +static const struct snd_ac97_build_ops patch_si3036_ops = { + .build_specific = patch_si3036_specific, +}; + +static int mpatch_si3036(struct snd_ac97 * ac97) +{ + ac97->build_ops = &patch_si3036_ops; + snd_ac97_write_cache(ac97, 0x5c, 0xf210 ); + snd_ac97_write_cache(ac97, 0x68, 0); + return 0; +} + +/* + * LM 4550 Codec + * + * We use a static resolution table since LM4550 codec cannot be + * properly autoprobed to determine the resolution via + * check_volume_resolution(). + */ + +static const struct snd_ac97_res_table lm4550_restbl[] = { + { AC97_MASTER, 0x1f1f }, + { AC97_HEADPHONE, 0x1f1f }, + { AC97_MASTER_MONO, 0x001f }, + { AC97_PC_BEEP, 0x001f }, /* LSB is ignored */ + { AC97_PHONE, 0x001f }, + { AC97_MIC, 0x001f }, + { AC97_LINE, 0x1f1f }, + { AC97_CD, 0x1f1f }, + { AC97_VIDEO, 0x1f1f }, + { AC97_AUX, 0x1f1f }, + { AC97_PCM, 0x1f1f }, + { AC97_REC_GAIN, 0x0f0f }, + { } /* terminator */ +}; + +static int patch_lm4550(struct snd_ac97 *ac97) +{ + ac97->res_table = lm4550_restbl; + return 0; +} diff --git a/sound/pci/ac97/ac97_patch.h b/sound/pci/ac97/ac97_patch.h new file mode 100644 index 000000000..a8cd89cfd --- /dev/null +++ b/sound/pci/ac97/ac97_patch.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + */ + +#define AC97_SINGLE_VALUE(reg,shift,mask,invert) \ + ((reg) | ((shift) << 8) | ((shift) << 12) | ((mask) << 16) | \ + ((invert) << 24)) +#define AC97_PAGE_SINGLE_VALUE(reg,shift,mask,invert,page) \ + (AC97_SINGLE_VALUE(reg,shift,mask,invert) | (1<<25) | ((page) << 26)) +#define AC97_SINGLE(xname, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_ac97_info_volsw, \ + .get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \ + .private_value = AC97_SINGLE_VALUE(reg, shift, mask, invert) } +#define AC97_PAGE_SINGLE(xname, reg, shift, mask, invert, page) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_ac97_info_volsw, \ + .get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \ + .private_value = AC97_PAGE_SINGLE_VALUE(reg, shift, mask, invert, page) } +#define AC97_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = snd_ac97_info_volsw, \ + .get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \ + .private_value = (reg) | ((shift_left) << 8) | ((shift_right) << 12) | ((mask) << 16) | ((invert) << 24) } + +/* enum control */ +struct ac97_enum { + unsigned char reg; + unsigned char shift_l; + unsigned char shift_r; + unsigned short mask; + const char * const *texts; +}; + +#define AC97_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, xtexts) \ +{ .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ + .mask = xmask, .texts = xtexts } +#define AC97_ENUM_SINGLE(xreg, xshift, xmask, xtexts) \ + AC97_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xtexts) +#define AC97_ENUM(xname, xenum) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_ac97_info_enum_double, \ + .get = snd_ac97_get_enum_double, .put = snd_ac97_put_enum_double, \ + .private_value = (unsigned long)&xenum } + +/* ac97_codec.c */ +static const struct snd_kcontrol_new snd_ac97_controls_3d[]; +static const struct snd_kcontrol_new snd_ac97_controls_spdif[]; +static struct snd_kcontrol *snd_ac97_cnew(const struct snd_kcontrol_new *_template, + struct snd_ac97 * ac97); +static int snd_ac97_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +static int snd_ac97_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int snd_ac97_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int snd_ac97_try_bit(struct snd_ac97 * ac97, int reg, int bit); +static int snd_ac97_remove_ctl(struct snd_ac97 *ac97, const char *name, + const char *suffix); +static int snd_ac97_rename_ctl(struct snd_ac97 *ac97, const char *src, + const char *dst, const char *suffix); +static int snd_ac97_swap_ctl(struct snd_ac97 *ac97, const char *s1, + const char *s2, const char *suffix); +static void snd_ac97_rename_vol_ctl(struct snd_ac97 *ac97, const char *src, + const char *dst); +#ifdef CONFIG_PM +static void snd_ac97_restore_status(struct snd_ac97 *ac97); +static void snd_ac97_restore_iec958(struct snd_ac97 *ac97); +#endif +static int snd_ac97_info_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +static int snd_ac97_get_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int snd_ac97_put_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); diff --git a/sound/pci/ac97/ac97_pcm.c b/sound/pci/ac97/ac97_pcm.c new file mode 100644 index 000000000..cbfbabb02 --- /dev/null +++ b/sound/pci/ac97/ac97_pcm.c @@ -0,0 +1,733 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com) and to datasheets + * for specific codecs. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "ac97_id.h" +#include "ac97_local.h" + +/* + * PCM support + */ + +static const unsigned char rate_reg_tables[2][4][9] = { +{ + /* standard rates */ + { + /* 3&4 front, 7&8 rear, 6&9 center/lfe */ + AC97_PCM_FRONT_DAC_RATE, /* slot 3 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 4 */ + 0xff, /* slot 5 */ + AC97_PCM_LFE_DAC_RATE, /* slot 6 */ + AC97_PCM_SURR_DAC_RATE, /* slot 7 */ + AC97_PCM_SURR_DAC_RATE, /* slot 8 */ + AC97_PCM_LFE_DAC_RATE, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + }, + { + /* 7&8 front, 6&9 rear, 10&11 center/lfe */ + 0xff, /* slot 3 */ + 0xff, /* slot 4 */ + 0xff, /* slot 5 */ + AC97_PCM_SURR_DAC_RATE, /* slot 6 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 7 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 8 */ + AC97_PCM_SURR_DAC_RATE, /* slot 9 */ + AC97_PCM_LFE_DAC_RATE, /* slot 10 */ + AC97_PCM_LFE_DAC_RATE, /* slot 11 */ + }, + { + /* 6&9 front, 10&11 rear, 3&4 center/lfe */ + AC97_PCM_LFE_DAC_RATE, /* slot 3 */ + AC97_PCM_LFE_DAC_RATE, /* slot 4 */ + 0xff, /* slot 5 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 6 */ + 0xff, /* slot 7 */ + 0xff, /* slot 8 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 9 */ + AC97_PCM_SURR_DAC_RATE, /* slot 10 */ + AC97_PCM_SURR_DAC_RATE, /* slot 11 */ + }, + { + /* 10&11 front, 3&4 rear, 7&8 center/lfe */ + AC97_PCM_SURR_DAC_RATE, /* slot 3 */ + AC97_PCM_SURR_DAC_RATE, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + AC97_PCM_LFE_DAC_RATE, /* slot 7 */ + AC97_PCM_LFE_DAC_RATE, /* slot 8 */ + 0xff, /* slot 9 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 10 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 11 */ + }, +}, +{ + /* double rates */ + { + /* 3&4 front, 7&8 front (t+1) */ + AC97_PCM_FRONT_DAC_RATE, /* slot 3 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 7 */ + AC97_PCM_FRONT_DAC_RATE, /* slot 8 */ + 0xff, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + }, + { + /* not specified in the specification */ + 0xff, /* slot 3 */ + 0xff, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + 0xff, /* slot 7 */ + 0xff, /* slot 8 */ + 0xff, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + }, + { + 0xff, /* slot 3 */ + 0xff, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + 0xff, /* slot 7 */ + 0xff, /* slot 8 */ + 0xff, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + }, + { + 0xff, /* slot 3 */ + 0xff, /* slot 4 */ + 0xff, /* slot 5 */ + 0xff, /* slot 6 */ + 0xff, /* slot 7 */ + 0xff, /* slot 8 */ + 0xff, /* slot 9 */ + 0xff, /* slot 10 */ + 0xff, /* slot 11 */ + } +}}; + +/* FIXME: more various mappings for ADC? */ +static const unsigned char rate_cregs[9] = { + AC97_PCM_LR_ADC_RATE, /* 3 */ + AC97_PCM_LR_ADC_RATE, /* 4 */ + 0xff, /* 5 */ + AC97_PCM_MIC_ADC_RATE, /* 6 */ + 0xff, /* 7 */ + 0xff, /* 8 */ + 0xff, /* 9 */ + 0xff, /* 10 */ + 0xff, /* 11 */ +}; + +static unsigned char get_slot_reg(struct ac97_pcm *pcm, unsigned short cidx, + unsigned short slot, int dbl) +{ + if (slot < 3) + return 0xff; + if (slot > 11) + return 0xff; + if (pcm->spdif) + return AC97_SPDIF; /* pseudo register */ + if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) + return rate_reg_tables[dbl][pcm->r[dbl].rate_table[cidx]][slot - 3]; + else + return rate_cregs[slot - 3]; +} + +static int set_spdif_rate(struct snd_ac97 *ac97, unsigned short rate) +{ + unsigned short old, bits, reg, mask; + unsigned int sbits; + + if (! (ac97->ext_id & AC97_EI_SPDIF)) + return -ENODEV; + + /* TODO: double rate support */ + if (ac97->flags & AC97_CS_SPDIF) { + switch (rate) { + case 48000: bits = 0; break; + case 44100: bits = 1 << AC97_SC_SPSR_SHIFT; break; + default: /* invalid - disable output */ + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); + return -EINVAL; + } + reg = AC97_CSR_SPDIF; + mask = 1 << AC97_SC_SPSR_SHIFT; + } else { + if (ac97->id == AC97_ID_CM9739 && rate != 48000) { + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); + return -EINVAL; + } + switch (rate) { + case 44100: bits = AC97_SC_SPSR_44K; break; + case 48000: bits = AC97_SC_SPSR_48K; break; + case 32000: bits = AC97_SC_SPSR_32K; break; + default: /* invalid - disable output */ + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); + return -EINVAL; + } + reg = AC97_SPDIF; + mask = AC97_SC_SPSR_MASK; + } + + guard(mutex)(&ac97->reg_mutex); + old = snd_ac97_read(ac97, reg) & mask; + if (old != bits) { + snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); + snd_ac97_update_bits_nolock(ac97, reg, mask, bits); + /* update the internal spdif bits */ + sbits = ac97->spdif_status; + if (sbits & IEC958_AES0_PROFESSIONAL) { + sbits &= ~IEC958_AES0_PRO_FS; + switch (rate) { + case 44100: sbits |= IEC958_AES0_PRO_FS_44100; break; + case 48000: sbits |= IEC958_AES0_PRO_FS_48000; break; + case 32000: sbits |= IEC958_AES0_PRO_FS_32000; break; + } + } else { + sbits &= ~(IEC958_AES3_CON_FS << 24); + switch (rate) { + case 44100: sbits |= IEC958_AES3_CON_FS_44100<<24; break; + case 48000: sbits |= IEC958_AES3_CON_FS_48000<<24; break; + case 32000: sbits |= IEC958_AES3_CON_FS_32000<<24; break; + } + } + ac97->spdif_status = sbits; + } + snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); + return 0; +} + +/** + * snd_ac97_set_rate - change the rate of the given input/output. + * @ac97: the ac97 instance + * @reg: the register to change + * @rate: the sample rate to set + * + * Changes the rate of the given input/output on the codec. + * If the codec doesn't support VAR, the rate must be 48000 (except + * for SPDIF). + * + * The valid registers are AC97_PCM_MIC_ADC_RATE, + * AC97_PCM_FRONT_DAC_RATE, AC97_PCM_LR_ADC_RATE. + * AC97_PCM_SURR_DAC_RATE and AC97_PCM_LFE_DAC_RATE are accepted + * if the codec supports them. + * AC97_SPDIF is accepted as a pseudo register to modify the SPDIF + * status bits. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate) +{ + int dbl; + unsigned int tmp; + + dbl = rate > 48000; + if (dbl) { + if (!(ac97->flags & AC97_DOUBLE_RATE)) + return -EINVAL; + if (reg != AC97_PCM_FRONT_DAC_RATE) + return -EINVAL; + } + + snd_ac97_update_power(ac97, reg, 1); + switch (reg) { + case AC97_PCM_MIC_ADC_RATE: + if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRM) == 0) /* MIC VRA */ + if (rate != 48000) + return -EINVAL; + break; + case AC97_PCM_FRONT_DAC_RATE: + case AC97_PCM_LR_ADC_RATE: + if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRA) == 0) /* VRA */ + if (rate != 48000 && rate != 96000) + return -EINVAL; + break; + case AC97_PCM_SURR_DAC_RATE: + if (! (ac97->scaps & AC97_SCAP_SURROUND_DAC)) + return -EINVAL; + break; + case AC97_PCM_LFE_DAC_RATE: + if (! (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)) + return -EINVAL; + break; + case AC97_SPDIF: + /* special case */ + return set_spdif_rate(ac97, rate); + default: + return -EINVAL; + } + if (dbl) + rate /= 2; + tmp = (rate * ac97->bus->clock) / 48000; + if (tmp > 65535) + return -EINVAL; + if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE) + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, + AC97_EA_DRA, dbl ? AC97_EA_DRA : 0); + snd_ac97_update(ac97, reg, tmp & 0xffff); + snd_ac97_read(ac97, reg); + if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE) { + /* Intel controllers require double rate data to be put in + * slots 7+8 + */ + snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, + AC97_GP_DRSS_MASK, + dbl ? AC97_GP_DRSS_78 : 0); + snd_ac97_read(ac97, AC97_GENERAL_PURPOSE); + } + return 0; +} + +EXPORT_SYMBOL(snd_ac97_set_rate); + +static unsigned short get_pslots(struct snd_ac97 *ac97, unsigned char *rate_table, unsigned short *spdif_slots) +{ + if (!ac97_is_audio(ac97)) + return 0; + if (ac97_is_rev22(ac97) || ac97_can_amap(ac97)) { + unsigned short slots = 0; + if (ac97_is_rev22(ac97)) { + /* Note: it's simply emulation of AMAP behaviour */ + u16 es; + es = ac97->regs[AC97_EXTENDED_ID] &= ~AC97_EI_DACS_SLOT_MASK; + switch (ac97->addr) { + case 1: + case 2: es |= (1<addr) { + case 0: + slots |= (1<scaps & AC97_SCAP_SURROUND_DAC) + slots |= (1<scaps & AC97_SCAP_CENTER_LFE_DAC) + slots |= (1<ext_id & AC97_EI_SPDIF) { + if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC)) + *spdif_slots = (1<scaps & AC97_SCAP_CENTER_LFE_DAC)) + *spdif_slots = (1<scaps & AC97_SCAP_SURROUND_DAC) + slots |= (1<ext_id & AC97_EI_SPDIF) { + if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC)) + *spdif_slots = (1<ext_id & AC97_EI_SPDIF) + *spdif_slots = (1<scaps & AC97_SCAP_SURROUND_DAC) + slots |= (1<scaps & AC97_SCAP_CENTER_LFE_DAC) + slots |= (1<ext_id & AC97_EI_SPDIF) { + if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC)) + *spdif_slots = (1<scaps & AC97_SCAP_CENTER_LFE_DAC)) + *spdif_slots = (1<r[dbl].codec[cidx]->rates[idx]; + } + if (!dbl) + rates &= ~(SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000); + return rates; +} + +/** + * snd_ac97_pcm_assign - assign AC97 slots to given PCM streams + * @bus: the ac97 bus instance + * @pcms_count: count of PCMs to be assigned + * @pcms: PCMs to be assigned + * + * It assigns available AC97 slots for given PCMs. If none or only + * some slots are available, pcm->xxx.slots and pcm->xxx.rslots[] members + * are reduced and might be zero. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_pcm_assign(struct snd_ac97_bus *bus, + unsigned short pcms_count, + const struct ac97_pcm *pcms) +{ + int i, j, k; + const struct ac97_pcm *pcm; + struct ac97_pcm *rpcms, *rpcm; + unsigned short avail_slots[2][4]; + unsigned char rate_table[2][4]; + unsigned short tmp, slots; + unsigned short spdif_slots[4]; + unsigned int rates; + struct snd_ac97 *codec; + + rpcms = kzalloc_objs(struct ac97_pcm, pcms_count); + if (rpcms == NULL) + return -ENOMEM; + memset(avail_slots, 0, sizeof(avail_slots)); + memset(rate_table, 0, sizeof(rate_table)); + memset(spdif_slots, 0, sizeof(spdif_slots)); + for (i = 0; i < 4; i++) { + codec = bus->codec[i]; + if (!codec) + continue; + avail_slots[0][i] = get_pslots(codec, &rate_table[0][i], &spdif_slots[i]); + avail_slots[1][i] = get_cslots(codec); + if (!(codec->scaps & AC97_SCAP_INDEP_SDIN)) { + for (j = 0; j < i; j++) { + if (bus->codec[j]) + avail_slots[1][i] &= ~avail_slots[1][j]; + } + } + } + /* first step - exclusive devices */ + for (i = 0; i < pcms_count; i++) { + pcm = &pcms[i]; + rpcm = &rpcms[i]; + /* low-level driver thinks that it's more clever */ + if (pcm->copy_flag) { + *rpcm = *pcm; + continue; + } + rpcm->stream = pcm->stream; + rpcm->exclusive = pcm->exclusive; + rpcm->spdif = pcm->spdif; + rpcm->private_value = pcm->private_value; + rpcm->bus = bus; + rpcm->rates = ~0; + slots = pcm->r[0].slots; + for (j = 0; j < 4 && slots; j++) { + if (!bus->codec[j]) + continue; + rates = ~0; + if (pcm->spdif && pcm->stream == 0) + tmp = spdif_slots[j]; + else + tmp = avail_slots[pcm->stream][j]; + if (pcm->exclusive) { + /* exclusive access */ + tmp &= slots; + for (k = 0; k < i; k++) { + if (rpcm->stream == rpcms[k].stream) + tmp &= ~rpcms[k].r[0].rslots[j]; + } + } else { + /* non-exclusive access */ + tmp &= pcm->r[0].slots; + } + if (tmp) { + rpcm->r[0].rslots[j] = tmp; + rpcm->r[0].codec[j] = bus->codec[j]; + rpcm->r[0].rate_table[j] = rate_table[pcm->stream][j]; + if (bus->no_vra) + rates = SNDRV_PCM_RATE_48000; + else + rates = get_rates(rpcm, j, tmp, 0); + if (pcm->exclusive) + avail_slots[pcm->stream][j] &= ~tmp; + } + slots &= ~tmp; + rpcm->r[0].slots |= tmp; + rpcm->rates &= rates; + } + /* for double rate, we check the first codec only */ + if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK && + bus->codec[0] && (bus->codec[0]->flags & AC97_DOUBLE_RATE) && + rate_table[pcm->stream][0] == 0) { + tmp = (1<r[1].slots) == tmp) { + rpcm->r[1].slots = tmp; + rpcm->r[1].rslots[0] = tmp; + rpcm->r[1].rate_table[0] = 0; + rpcm->r[1].codec[0] = bus->codec[0]; + if (pcm->exclusive) + avail_slots[pcm->stream][0] &= ~tmp; + if (bus->no_vra) + rates = SNDRV_PCM_RATE_96000; + else + rates = get_rates(rpcm, 0, tmp, 1); + rpcm->rates |= rates; + } + } + if (rpcm->rates == ~0) + rpcm->rates = 0; /* not used */ + } + bus->pcms_count = pcms_count; + bus->pcms = rpcms; + return 0; +} + +EXPORT_SYMBOL(snd_ac97_pcm_assign); + +/** + * snd_ac97_pcm_open - opens the given AC97 pcm + * @pcm: the ac97 pcm instance + * @rate: rate in Hz, if codec does not support VRA, this value must be 48000Hz + * @cfg: output stream characteristics + * @slots: a subset of allocated slots (snd_ac97_pcm_assign) for this pcm + * + * It locks the specified slots and sets the given rate to AC97 registers. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_pcm_open(struct ac97_pcm *pcm, unsigned int rate, + enum ac97_pcm_cfg cfg, unsigned short slots) +{ + struct snd_ac97_bus *bus; + int i, cidx, r, ok_flag; + unsigned int reg_ok[4] = {0,0,0,0}; + unsigned char reg; + int err = 0; + + r = rate > 48000; + bus = pcm->bus; + if (cfg == AC97_PCM_CFG_SPDIF) { + for (cidx = 0; cidx < 4; cidx++) + if (bus->codec[cidx] && (bus->codec[cidx]->ext_id & AC97_EI_SPDIF)) { + err = set_spdif_rate(bus->codec[cidx], rate); + if (err < 0) + return err; + } + } + scoped_guard(spinlock_irq, &pcm->bus->bus_lock) { + for (i = 3; i < 12; i++) { + if (!(slots & (1 << i))) + continue; + ok_flag = 0; + for (cidx = 0; cidx < 4; cidx++) { + if (bus->used_slots[pcm->stream][cidx] & (1 << i)) { + err = -EBUSY; + goto error; + } + if (pcm->r[r].rslots[cidx] & (1 << i)) { + bus->used_slots[pcm->stream][cidx] |= (1 << i); + ok_flag++; + } + } + if (!ok_flag) { + dev_err(bus->card->dev, + "cannot find configuration for AC97 slot %i\n", + i); + err = -EAGAIN; + goto error; + } + } + pcm->cur_dbl = r; + } + for (i = 3; i < 12; i++) { + if (!(slots & (1 << i))) + continue; + for (cidx = 0; cidx < 4; cidx++) { + if (pcm->r[r].rslots[cidx] & (1 << i)) { + reg = get_slot_reg(pcm, cidx, i, r); + if (reg == 0xff) { + dev_err(bus->card->dev, + "invalid AC97 slot %i?\n", i); + continue; + } + if (reg_ok[cidx] & (1 << (reg - AC97_PCM_FRONT_DAC_RATE))) + continue; + dev_dbg(bus->card->dev, + "setting ac97 reg 0x%x to rate %d\n", + reg, rate); + err = snd_ac97_set_rate(pcm->r[r].codec[cidx], reg, rate); + if (err < 0) + dev_err(bus->card->dev, + "error in snd_ac97_set_rate: cidx=%d, reg=0x%x, rate=%d, err=%d\n", + cidx, reg, rate, err); + else + reg_ok[cidx] |= (1 << (reg - AC97_PCM_FRONT_DAC_RATE)); + } + } + } + pcm->aslots = slots; + return 0; + + error: + pcm->aslots = slots; + snd_ac97_pcm_close(pcm); + return err; +} + +EXPORT_SYMBOL(snd_ac97_pcm_open); + +/** + * snd_ac97_pcm_close - closes the given AC97 pcm + * @pcm: the ac97 pcm instance + * + * It frees the locked AC97 slots. + * + * Return: Zero. + */ +int snd_ac97_pcm_close(struct ac97_pcm *pcm) +{ + struct snd_ac97_bus *bus; + unsigned short slots = pcm->aslots; + int i, cidx; + +#ifdef CONFIG_SND_AC97_POWER_SAVE + int r = pcm->cur_dbl; + for (i = 3; i < 12; i++) { + if (!(slots & (1 << i))) + continue; + for (cidx = 0; cidx < 4; cidx++) { + if (pcm->r[r].rslots[cidx] & (1 << i)) { + int reg = get_slot_reg(pcm, cidx, i, r); + snd_ac97_update_power(pcm->r[r].codec[cidx], + reg, 0); + } + } + } +#endif + + bus = pcm->bus; + guard(spinlock_irq)(&pcm->bus->bus_lock); + for (i = 3; i < 12; i++) { + if (!(slots & (1 << i))) + continue; + for (cidx = 0; cidx < 4; cidx++) + bus->used_slots[pcm->stream][cidx] &= ~(1 << i); + } + pcm->aslots = 0; + pcm->cur_dbl = 0; + return 0; +} + +EXPORT_SYMBOL(snd_ac97_pcm_close); + +static int double_rate_hw_constraint_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + if (channels->min > 2) { + static const struct snd_interval single_rates = { + .min = 1, + .max = 48000, + }; + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + return snd_interval_refine(rate, &single_rates); + } + return 0; +} + +static int double_rate_hw_constraint_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (rate->min > 48000) { + static const struct snd_interval double_rate_channels = { + .min = 2, + .max = 2, + }; + struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + return snd_interval_refine(channels, &double_rate_channels); + } + return 0; +} + +/** + * snd_ac97_pcm_double_rate_rules - set double rate constraints + * @runtime: the runtime of the ac97 front playback pcm + * + * Installs the hardware constraint rules to prevent using double rates and + * more than two channels at the same time. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_pcm_double_rate_rules(struct snd_pcm_runtime *runtime) +{ + int err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + double_rate_hw_constraint_rate, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + double_rate_hw_constraint_channels, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + return err; +} + +EXPORT_SYMBOL(snd_ac97_pcm_double_rate_rules); diff --git a/sound/pci/ac97/ac97_proc.c b/sound/pci/ac97/ac97_proc.c new file mode 100644 index 000000000..1c9d76994 --- /dev/null +++ b/sound/pci/ac97/ac97_proc.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + */ + +#include + +#include +#include +#include +#include "ac97_local.h" +#include "ac97_id.h" + +/* + * proc interface + */ + +static void snd_ac97_proc_read_functions(struct snd_ac97 *ac97, struct snd_info_buffer *buffer) +{ + int header = 0, function; + unsigned short info, sense_info; + static const char *function_names[12] = { + "Master Out", "AUX Out", "Center/LFE Out", "SPDIF Out", + "Phone In", "Mic 1", "Mic 2", "Line In", "CD In", "Video In", + "Aux In", "Mono Out" + }; + static const char *locations[8] = { + "Rear I/O Panel", "Front Panel", "Motherboard", "Dock/External", + "reserved", "reserved", "reserved", "NC/unused" + }; + + for (function = 0; function < 12; ++function) { + snd_ac97_write(ac97, AC97_FUNC_SELECT, function << 1); + info = snd_ac97_read(ac97, AC97_FUNC_INFO); + if (!(info & 0x0001)) + continue; + if (!header) { + snd_iprintf(buffer, "\n Gain Inverted Buffer delay Location\n"); + header = 1; + } + sense_info = snd_ac97_read(ac97, AC97_SENSE_INFO); + snd_iprintf(buffer, "%-17s: %3d.%d dBV %c %2d/fs %s\n", + function_names[function], + (info & 0x8000 ? -1 : 1) * ((info & 0x7000) >> 12) * 3 / 2, + ((info & 0x0800) >> 11) * 5, + info & 0x0400 ? 'X' : '-', + (info & 0x03e0) >> 5, + locations[sense_info >> 13]); + } +} + +static const char *snd_ac97_stereo_enhancements[] = +{ + /* 0 */ "No 3D Stereo Enhancement", + /* 1 */ "Analog Devices Phat Stereo", + /* 2 */ "Creative Stereo Enhancement", + /* 3 */ "National Semi 3D Stereo Enhancement", + /* 4 */ "YAMAHA Ymersion", + /* 5 */ "BBE 3D Stereo Enhancement", + /* 6 */ "Crystal Semi 3D Stereo Enhancement", + /* 7 */ "Qsound QXpander", + /* 8 */ "Spatializer 3D Stereo Enhancement", + /* 9 */ "SRS 3D Stereo Enhancement", + /* 10 */ "Platform Tech 3D Stereo Enhancement", + /* 11 */ "AKM 3D Audio", + /* 12 */ "Aureal Stereo Enhancement", + /* 13 */ "Aztech 3D Enhancement", + /* 14 */ "Binaura 3D Audio Enhancement", + /* 15 */ "ESS Technology Stereo Enhancement", + /* 16 */ "Harman International VMAx", + /* 17 */ "Nvidea/IC Ensemble/KS Waves 3D Stereo Enhancement", + /* 18 */ "Philips Incredible Sound", + /* 19 */ "Texas Instruments 3D Stereo Enhancement", + /* 20 */ "VLSI Technology 3D Stereo Enhancement", + /* 21 */ "TriTech 3D Stereo Enhancement", + /* 22 */ "Realtek 3D Stereo Enhancement", + /* 23 */ "Samsung 3D Stereo Enhancement", + /* 24 */ "Wolfson Microelectronics 3D Enhancement", + /* 25 */ "Delta Integration 3D Enhancement", + /* 26 */ "SigmaTel 3D Enhancement", + /* 27 */ "IC Ensemble/KS Waves", + /* 28 */ "Rockwell 3D Stereo Enhancement", + /* 29 */ "Reserved 29", + /* 30 */ "Reserved 30", + /* 31 */ "Reserved 31" +}; + +static void snd_ac97_proc_read_main(struct snd_ac97 *ac97, struct snd_info_buffer *buffer, int subidx) +{ + char name[64]; + unsigned short val, tmp, ext, mext; + static const char *spdif_slots[4] = { " SPDIF=3/4", " SPDIF=7/8", " SPDIF=6/9", " SPDIF=10/11" }; + static const char *spdif_rates[4] = { " Rate=44.1kHz", " Rate=res", " Rate=48kHz", " Rate=32kHz" }; + static const char *spdif_rates_cs4205[4] = { " Rate=48kHz", " Rate=44.1kHz", " Rate=res", " Rate=res" }; + static const char *double_rate_slots[4] = { "10/11", "7/8", "reserved", "reserved" }; + + snd_ac97_get_name(NULL, ac97->id, name, sizeof(name), 0); + snd_iprintf(buffer, "%d-%d/%d: %s\n\n", ac97->addr, ac97->num, subidx, name); + + if ((ac97->scaps & AC97_SCAP_AUDIO) == 0) + goto __modem; + + snd_iprintf(buffer, "PCI Subsys Vendor: 0x%04x\n", + ac97->subsystem_vendor); + snd_iprintf(buffer, "PCI Subsys Device: 0x%04x\n\n", + ac97->subsystem_device); + + snd_iprintf(buffer, "Flags: %x\n", ac97->flags); + + if ((ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23) { + val = snd_ac97_read(ac97, AC97_INT_PAGING); + snd_ac97_update_bits(ac97, AC97_INT_PAGING, + AC97_PAGE_MASK, AC97_PAGE_1); + tmp = snd_ac97_read(ac97, AC97_CODEC_CLASS_REV); + snd_iprintf(buffer, "Revision : 0x%02x\n", tmp & 0xff); + snd_iprintf(buffer, "Compat. Class : 0x%02x\n", (tmp >> 8) & 0x1f); + snd_iprintf(buffer, "Subsys. Vendor ID: 0x%04x\n", + snd_ac97_read(ac97, AC97_PCI_SVID)); + snd_iprintf(buffer, "Subsys. ID : 0x%04x\n\n", + snd_ac97_read(ac97, AC97_PCI_SID)); + snd_ac97_update_bits(ac97, AC97_INT_PAGING, + AC97_PAGE_MASK, val & AC97_PAGE_MASK); + } + + // val = snd_ac97_read(ac97, AC97_RESET); + val = ac97->caps; + snd_iprintf(buffer, "Capabilities :%s%s%s%s%s%s\n", + val & AC97_BC_DEDICATED_MIC ? " -dedicated MIC PCM IN channel-" : "", + val & AC97_BC_RESERVED1 ? " -reserved1-" : "", + val & AC97_BC_BASS_TREBLE ? " -bass & treble-" : "", + val & AC97_BC_SIM_STEREO ? " -simulated stereo-" : "", + val & AC97_BC_HEADPHONE ? " -headphone out-" : "", + val & AC97_BC_LOUDNESS ? " -loudness-" : ""); + tmp = ac97->caps & AC97_BC_DAC_MASK; + snd_iprintf(buffer, "DAC resolution : %s%s%s%s\n", + tmp == AC97_BC_16BIT_DAC ? "16-bit" : "", + tmp == AC97_BC_18BIT_DAC ? "18-bit" : "", + tmp == AC97_BC_20BIT_DAC ? "20-bit" : "", + tmp == AC97_BC_DAC_MASK ? "???" : ""); + tmp = ac97->caps & AC97_BC_ADC_MASK; + snd_iprintf(buffer, "ADC resolution : %s%s%s%s\n", + tmp == AC97_BC_16BIT_ADC ? "16-bit" : "", + tmp == AC97_BC_18BIT_ADC ? "18-bit" : "", + tmp == AC97_BC_20BIT_ADC ? "20-bit" : "", + tmp == AC97_BC_ADC_MASK ? "???" : ""); + snd_iprintf(buffer, "3D enhancement : %s\n", + snd_ac97_stereo_enhancements[(val >> 10) & 0x1f]); + snd_iprintf(buffer, "\nCurrent setup\n"); + val = snd_ac97_read(ac97, AC97_MIC); + snd_iprintf(buffer, "Mic gain : %s [%s]\n", val & 0x0040 ? "+20dB" : "+0dB", ac97->regs[AC97_MIC] & 0x0040 ? "+20dB" : "+0dB"); + val = snd_ac97_read(ac97, AC97_GENERAL_PURPOSE); + snd_iprintf(buffer, "POP path : %s 3D\n" + "Sim. stereo : %s\n" + "3D enhancement : %s\n" + "Loudness : %s\n" + "Mono output : %s\n" + "Mic select : %s\n" + "ADC/DAC loopback : %s\n", + val & 0x8000 ? "post" : "pre", + str_on_off(val & 0x4000), + str_on_off(val & 0x2000), + str_on_off(val & 0x1000), + val & 0x0200 ? "Mic" : "MIX", + val & 0x0100 ? "Mic2" : "Mic1", + str_on_off(val & 0x0080)); + if (ac97->ext_id & AC97_EI_DRA) + snd_iprintf(buffer, "Double rate slots: %s\n", + double_rate_slots[(val >> 10) & 3]); + + ext = snd_ac97_read(ac97, AC97_EXTENDED_ID); + if (ext == 0) + goto __modem; + + snd_iprintf(buffer, "Extended ID : codec=%i rev=%i%s%s%s%s DSA=%i%s%s%s%s\n", + (ext & AC97_EI_ADDR_MASK) >> AC97_EI_ADDR_SHIFT, + (ext & AC97_EI_REV_MASK) >> AC97_EI_REV_SHIFT, + ext & AC97_EI_AMAP ? " AMAP" : "", + ext & AC97_EI_LDAC ? " LDAC" : "", + ext & AC97_EI_SDAC ? " SDAC" : "", + ext & AC97_EI_CDAC ? " CDAC" : "", + (ext & AC97_EI_DACS_SLOT_MASK) >> AC97_EI_DACS_SLOT_SHIFT, + ext & AC97_EI_VRM ? " VRM" : "", + ext & AC97_EI_SPDIF ? " SPDIF" : "", + ext & AC97_EI_DRA ? " DRA" : "", + ext & AC97_EI_VRA ? " VRA" : ""); + val = snd_ac97_read(ac97, AC97_EXTENDED_STATUS); + snd_iprintf(buffer, "Extended status :%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", + val & AC97_EA_PRL ? " PRL" : "", + val & AC97_EA_PRK ? " PRK" : "", + val & AC97_EA_PRJ ? " PRJ" : "", + val & AC97_EA_PRI ? " PRI" : "", + val & AC97_EA_SPCV ? " SPCV" : "", + val & AC97_EA_MDAC ? " MADC" : "", + val & AC97_EA_LDAC ? " LDAC" : "", + val & AC97_EA_SDAC ? " SDAC" : "", + val & AC97_EA_CDAC ? " CDAC" : "", + ext & AC97_EI_SPDIF ? spdif_slots[(val & AC97_EA_SPSA_SLOT_MASK) >> AC97_EA_SPSA_SLOT_SHIFT] : "", + val & AC97_EA_VRM ? " VRM" : "", + val & AC97_EA_SPDIF ? " SPDIF" : "", + val & AC97_EA_DRA ? " DRA" : "", + val & AC97_EA_VRA ? " VRA" : ""); + if (ext & AC97_EI_VRA) { /* VRA */ + val = snd_ac97_read(ac97, AC97_PCM_FRONT_DAC_RATE); + snd_iprintf(buffer, "PCM front DAC : %iHz\n", val); + if (ext & AC97_EI_SDAC) { + val = snd_ac97_read(ac97, AC97_PCM_SURR_DAC_RATE); + snd_iprintf(buffer, "PCM Surr DAC : %iHz\n", val); + } + if (ext & AC97_EI_LDAC) { + val = snd_ac97_read(ac97, AC97_PCM_LFE_DAC_RATE); + snd_iprintf(buffer, "PCM LFE DAC : %iHz\n", val); + } + val = snd_ac97_read(ac97, AC97_PCM_LR_ADC_RATE); + snd_iprintf(buffer, "PCM ADC : %iHz\n", val); + } + if (ext & AC97_EI_VRM) { + val = snd_ac97_read(ac97, AC97_PCM_MIC_ADC_RATE); + snd_iprintf(buffer, "PCM MIC ADC : %iHz\n", val); + } + if ((ext & AC97_EI_SPDIF) || (ac97->flags & AC97_CS_SPDIF) || + (ac97->id == AC97_ID_YMF743)) { + if (ac97->flags & AC97_CS_SPDIF) + val = snd_ac97_read(ac97, AC97_CSR_SPDIF); + else if (ac97->id == AC97_ID_YMF743) { + val = snd_ac97_read(ac97, AC97_YMF7X3_DIT_CTRL); + val = 0x2000 | (val & 0xff00) >> 4 | (val & 0x38) >> 2; + } else + val = snd_ac97_read(ac97, AC97_SPDIF); + + snd_iprintf(buffer, "SPDIF Control :%s%s%s%s Category=0x%x Generation=%i%s%s%s\n", + val & AC97_SC_PRO ? " PRO" : " Consumer", + val & AC97_SC_NAUDIO ? " Non-audio" : " PCM", + val & AC97_SC_COPY ? "" : " Copyright", + val & AC97_SC_PRE ? " Preemph50/15" : "", + (val & AC97_SC_CC_MASK) >> AC97_SC_CC_SHIFT, + (val & AC97_SC_L) >> 11, + (ac97->flags & AC97_CS_SPDIF) ? + spdif_rates_cs4205[(val & AC97_SC_SPSR_MASK) >> AC97_SC_SPSR_SHIFT] : + spdif_rates[(val & AC97_SC_SPSR_MASK) >> AC97_SC_SPSR_SHIFT], + (ac97->flags & AC97_CS_SPDIF) ? + (val & AC97_SC_DRS ? " Validity" : "") : + (val & AC97_SC_DRS ? " DRS" : ""), + (ac97->flags & AC97_CS_SPDIF) ? + (val & AC97_SC_V ? " Enabled" : "") : + (val & AC97_SC_V ? " Validity" : "")); + /* ALC650 specific*/ + if ((ac97->id & 0xfffffff0) == 0x414c4720 && + (snd_ac97_read(ac97, AC97_ALC650_CLOCK) & 0x01)) { + val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS2); + if (val & AC97_ALC650_CLOCK_LOCK) { + val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS1); + snd_iprintf(buffer, "SPDIF In Status :%s%s%s%s Category=0x%x Generation=%i", + val & AC97_ALC650_PRO ? " PRO" : " Consumer", + val & AC97_ALC650_NAUDIO ? " Non-audio" : " PCM", + val & AC97_ALC650_COPY ? "" : " Copyright", + val & AC97_ALC650_PRE ? " Preemph50/15" : "", + (val & AC97_ALC650_CC_MASK) >> AC97_ALC650_CC_SHIFT, + (val & AC97_ALC650_L) >> 15); + val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS2); + snd_iprintf(buffer, "%s Accuracy=%i%s%s\n", + spdif_rates[(val & AC97_ALC650_SPSR_MASK) >> AC97_ALC650_SPSR_SHIFT], + (val & AC97_ALC650_CLOCK_ACCURACY) >> AC97_ALC650_CLOCK_SHIFT, + (val & AC97_ALC650_CLOCK_LOCK ? " Locked" : " Unlocked"), + (val & AC97_ALC650_V ? " Validity?" : "")); + } else { + snd_iprintf(buffer, "SPDIF In Status : Not Locked\n"); + } + } + } + if ((ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23) { + val = snd_ac97_read(ac97, AC97_INT_PAGING); + snd_ac97_update_bits(ac97, AC97_INT_PAGING, + AC97_PAGE_MASK, AC97_PAGE_1); + snd_ac97_proc_read_functions(ac97, buffer); + snd_ac97_update_bits(ac97, AC97_INT_PAGING, + AC97_PAGE_MASK, val & AC97_PAGE_MASK); + } + + + __modem: + mext = snd_ac97_read(ac97, AC97_EXTENDED_MID); + if (mext == 0) + return; + + snd_iprintf(buffer, "Extended modem ID: codec=%i%s%s%s%s%s\n", + (mext & AC97_MEI_ADDR_MASK) >> AC97_MEI_ADDR_SHIFT, + mext & AC97_MEI_CID2 ? " CID2" : "", + mext & AC97_MEI_CID1 ? " CID1" : "", + mext & AC97_MEI_HANDSET ? " HSET" : "", + mext & AC97_MEI_LINE2 ? " LIN2" : "", + mext & AC97_MEI_LINE1 ? " LIN1" : ""); + val = snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS); + snd_iprintf(buffer, "Modem status :%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", + val & AC97_MEA_GPIO ? " GPIO" : "", + val & AC97_MEA_MREF ? " MREF" : "", + val & AC97_MEA_ADC1 ? " ADC1" : "", + val & AC97_MEA_DAC1 ? " DAC1" : "", + val & AC97_MEA_ADC2 ? " ADC2" : "", + val & AC97_MEA_DAC2 ? " DAC2" : "", + val & AC97_MEA_HADC ? " HADC" : "", + val & AC97_MEA_HDAC ? " HDAC" : "", + val & AC97_MEA_PRA ? " PRA(GPIO)" : "", + val & AC97_MEA_PRB ? " PRB(res)" : "", + val & AC97_MEA_PRC ? " PRC(ADC1)" : "", + val & AC97_MEA_PRD ? " PRD(DAC1)" : "", + val & AC97_MEA_PRE ? " PRE(ADC2)" : "", + val & AC97_MEA_PRF ? " PRF(DAC2)" : "", + val & AC97_MEA_PRG ? " PRG(HADC)" : "", + val & AC97_MEA_PRH ? " PRH(HDAC)" : ""); + if (mext & AC97_MEI_LINE1) { + val = snd_ac97_read(ac97, AC97_LINE1_RATE); + snd_iprintf(buffer, "Line1 rate : %iHz\n", val); + } + if (mext & AC97_MEI_LINE2) { + val = snd_ac97_read(ac97, AC97_LINE2_RATE); + snd_iprintf(buffer, "Line2 rate : %iHz\n", val); + } + if (mext & AC97_MEI_HANDSET) { + val = snd_ac97_read(ac97, AC97_HANDSET_RATE); + snd_iprintf(buffer, "Headset rate : %iHz\n", val); + } +} + +static void snd_ac97_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_ac97 *ac97 = entry->private_data; + + guard(mutex)(&ac97->page_mutex); + if ((ac97->id & 0xffffff40) == AC97_ID_AD1881) { // Analog Devices AD1881/85/86 + int idx; + for (idx = 0; idx < 3; idx++) + if (ac97->spec.ad18xx.id[idx]) { + /* select single codec */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, + ac97->spec.ad18xx.unchained[idx] | ac97->spec.ad18xx.chained[idx]); + snd_ac97_proc_read_main(ac97, buffer, idx); + snd_iprintf(buffer, "\n\n"); + } + /* select all codecs */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000); + + snd_iprintf(buffer, "\nAD18XX configuration\n"); + snd_iprintf(buffer, "Unchained : 0x%04x,0x%04x,0x%04x\n", + ac97->spec.ad18xx.unchained[0], + ac97->spec.ad18xx.unchained[1], + ac97->spec.ad18xx.unchained[2]); + snd_iprintf(buffer, "Chained : 0x%04x,0x%04x,0x%04x\n", + ac97->spec.ad18xx.chained[0], + ac97->spec.ad18xx.chained[1], + ac97->spec.ad18xx.chained[2]); + } else { + snd_ac97_proc_read_main(ac97, buffer, 0); + } +} + +#ifdef CONFIG_SND_DEBUG +/* direct register write for debugging */ +static void snd_ac97_proc_regs_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_ac97 *ac97 = entry->private_data; + char line[64]; + unsigned int reg, val; + + guard(mutex)(&ac97->page_mutex); + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x", ®, &val) != 2) + continue; + /* register must be even */ + if (reg < 0x80 && (reg & 1) == 0 && val <= 0xffff) + snd_ac97_write_cache(ac97, reg, val); + } +} +#endif + +static void snd_ac97_proc_regs_read_main(struct snd_ac97 *ac97, struct snd_info_buffer *buffer, int subidx) +{ + int reg, val; + + for (reg = 0; reg < 0x80; reg += 2) { + val = snd_ac97_read(ac97, reg); + snd_iprintf(buffer, "%i:%02x = %04x\n", subidx, reg, val); + } +} + +static void snd_ac97_proc_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ac97 *ac97 = entry->private_data; + + guard(mutex)(&ac97->page_mutex); + if ((ac97->id & 0xffffff40) == AC97_ID_AD1881) { // Analog Devices AD1881/85/86 + + int idx; + for (idx = 0; idx < 3; idx++) + if (ac97->spec.ad18xx.id[idx]) { + /* select single codec */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, + ac97->spec.ad18xx.unchained[idx] | ac97->spec.ad18xx.chained[idx]); + snd_ac97_proc_regs_read_main(ac97, buffer, idx); + } + /* select all codecs */ + snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000); + } else { + snd_ac97_proc_regs_read_main(ac97, buffer, 0); + } +} + +void snd_ac97_proc_init(struct snd_ac97 * ac97) +{ + struct snd_info_entry *entry; + char name[32]; + const char *prefix; + + if (ac97->bus->proc == NULL) + return; + prefix = ac97_is_audio(ac97) ? "ac97" : "mc97"; + sprintf(name, "%s#%d-%d", prefix, ac97->addr, ac97->num); + entry = snd_info_create_card_entry(ac97->bus->card, name, + ac97->bus->proc); + if (entry) + snd_info_set_text_ops(entry, ac97, snd_ac97_proc_read); + ac97->proc = entry; + sprintf(name, "%s#%d-%d+regs", prefix, ac97->addr, ac97->num); + entry = snd_info_create_card_entry(ac97->bus->card, name, + ac97->bus->proc); + if (entry) { + snd_info_set_text_ops(entry, ac97, snd_ac97_proc_regs_read); +#ifdef CONFIG_SND_DEBUG + entry->mode |= 0200; + entry->c.text.write = snd_ac97_proc_regs_write; +#endif + } + ac97->proc_regs = entry; +} + +void snd_ac97_proc_done(struct snd_ac97 * ac97) +{ + snd_info_free_entry(ac97->proc_regs); + ac97->proc_regs = NULL; + snd_info_free_entry(ac97->proc); + ac97->proc = NULL; +} + +void snd_ac97_bus_proc_init(struct snd_ac97_bus * bus) +{ + struct snd_info_entry *entry; + char name[32]; + + sprintf(name, "codec97#%d", bus->num); + entry = snd_info_create_card_entry(bus->card, name, + bus->card->proc_root); + if (entry) + entry->mode = S_IFDIR | 0555; + bus->proc = entry; +} + +void snd_ac97_bus_proc_done(struct snd_ac97_bus * bus) +{ + snd_info_free_entry(bus->proc); + bus->proc = NULL; +} diff --git a/sound/pci/ad1889.c b/sound/pci/ad1889.c new file mode 100644 index 000000000..f4ec404c0 --- /dev/null +++ b/sound/pci/ad1889.c @@ -0,0 +1,918 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Analog Devices 1889 audio driver + * + * This is a driver for the AD1889 PCI audio chipset found + * on the HP PA-RISC [BCJ]-xxx0 workstations. + * + * Copyright (C) 2004-2005, Kyle McMartin + * Copyright (C) 2005, Thibaut Varene + * Based on the OSS AD1889 driver by Randolph Chung + * + * TODO: + * Do we need to take care of CCS register? + * Maybe we could use finer grained locking (separate locks for pb/cap)? + * Wishlist: + * Control Interface (mixer) support + * Better AC97 support (VSR...)? + * PM support + * MIDI support + * Game Port support + * SG DMA support (this will need *a lot* of work) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ad1889.h" +#include "ac97/ac97_id.h" + +#define AD1889_DRVVER "Version: 1.7" + +MODULE_AUTHOR("Kyle McMartin , Thibaut Varene "); +MODULE_DESCRIPTION("Analog Devices AD1889 ALSA sound driver"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the AD1889 soundcard."); + +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the AD1889 soundcard."); + +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable AD1889 soundcard."); + +static char *ac97_quirk[SNDRV_CARDS]; +module_param_array(ac97_quirk, charp, NULL, 0444); +MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware."); + +#define DEVNAME "ad1889" +#define PFX DEVNAME ": " + +/* keep track of some hw registers */ +struct ad1889_register_state { + u16 reg; /* reg setup */ + u32 addr; /* dma base address */ + unsigned long size; /* DMA buffer size */ +}; + +struct snd_ad1889 { + struct snd_card *card; + struct pci_dev *pci; + + int irq; + unsigned long bar; + void __iomem *iobase; + + struct snd_ac97 *ac97; + struct snd_ac97_bus *ac97_bus; + struct snd_pcm *pcm; + struct snd_info_entry *proc; + + struct snd_pcm_substream *psubs; + struct snd_pcm_substream *csubs; + + /* playback register state */ + struct ad1889_register_state wave; + struct ad1889_register_state ramc; + + spinlock_t lock; +}; + +static inline u16 +ad1889_readw(struct snd_ad1889 *chip, unsigned reg) +{ + return readw(chip->iobase + reg); +} + +static inline void +ad1889_writew(struct snd_ad1889 *chip, unsigned reg, u16 val) +{ + writew(val, chip->iobase + reg); +} + +static inline u32 +ad1889_readl(struct snd_ad1889 *chip, unsigned reg) +{ + return readl(chip->iobase + reg); +} + +static inline void +ad1889_writel(struct snd_ad1889 *chip, unsigned reg, u32 val) +{ + writel(val, chip->iobase + reg); +} + +static inline void +ad1889_unmute(struct snd_ad1889 *chip) +{ + u16 st; + st = ad1889_readw(chip, AD_DS_WADA) & + ~(AD_DS_WADA_RWAM | AD_DS_WADA_LWAM); + ad1889_writew(chip, AD_DS_WADA, st); + ad1889_readw(chip, AD_DS_WADA); +} + +static inline void +ad1889_mute(struct snd_ad1889 *chip) +{ + u16 st; + st = ad1889_readw(chip, AD_DS_WADA) | AD_DS_WADA_RWAM | AD_DS_WADA_LWAM; + ad1889_writew(chip, AD_DS_WADA, st); + ad1889_readw(chip, AD_DS_WADA); +} + +static inline void +ad1889_load_adc_buffer_address(struct snd_ad1889 *chip, u32 address) +{ + ad1889_writel(chip, AD_DMA_ADCBA, address); + ad1889_writel(chip, AD_DMA_ADCCA, address); +} + +static inline void +ad1889_load_adc_buffer_count(struct snd_ad1889 *chip, u32 count) +{ + ad1889_writel(chip, AD_DMA_ADCBC, count); + ad1889_writel(chip, AD_DMA_ADCCC, count); +} + +static inline void +ad1889_load_adc_interrupt_count(struct snd_ad1889 *chip, u32 count) +{ + ad1889_writel(chip, AD_DMA_ADCIB, count); + ad1889_writel(chip, AD_DMA_ADCIC, count); +} + +static inline void +ad1889_load_wave_buffer_address(struct snd_ad1889 *chip, u32 address) +{ + ad1889_writel(chip, AD_DMA_WAVBA, address); + ad1889_writel(chip, AD_DMA_WAVCA, address); +} + +static inline void +ad1889_load_wave_buffer_count(struct snd_ad1889 *chip, u32 count) +{ + ad1889_writel(chip, AD_DMA_WAVBC, count); + ad1889_writel(chip, AD_DMA_WAVCC, count); +} + +static inline void +ad1889_load_wave_interrupt_count(struct snd_ad1889 *chip, u32 count) +{ + ad1889_writel(chip, AD_DMA_WAVIB, count); + ad1889_writel(chip, AD_DMA_WAVIC, count); +} + +static void +ad1889_channel_reset(struct snd_ad1889 *chip, unsigned int channel) +{ + u16 reg; + + if (channel & AD_CHAN_WAV) { + /* Disable wave channel */ + reg = ad1889_readw(chip, AD_DS_WSMC) & ~AD_DS_WSMC_WAEN; + ad1889_writew(chip, AD_DS_WSMC, reg); + chip->wave.reg = reg; + + /* disable IRQs */ + reg = ad1889_readw(chip, AD_DMA_WAV); + reg &= AD_DMA_IM_DIS; + reg &= ~AD_DMA_LOOP; + ad1889_writew(chip, AD_DMA_WAV, reg); + + /* clear IRQ and address counters and pointers */ + ad1889_load_wave_buffer_address(chip, 0x0); + ad1889_load_wave_buffer_count(chip, 0x0); + ad1889_load_wave_interrupt_count(chip, 0x0); + + /* flush */ + ad1889_readw(chip, AD_DMA_WAV); + } + + if (channel & AD_CHAN_ADC) { + /* Disable ADC channel */ + reg = ad1889_readw(chip, AD_DS_RAMC) & ~AD_DS_RAMC_ADEN; + ad1889_writew(chip, AD_DS_RAMC, reg); + chip->ramc.reg = reg; + + reg = ad1889_readw(chip, AD_DMA_ADC); + reg &= AD_DMA_IM_DIS; + reg &= ~AD_DMA_LOOP; + ad1889_writew(chip, AD_DMA_ADC, reg); + + ad1889_load_adc_buffer_address(chip, 0x0); + ad1889_load_adc_buffer_count(chip, 0x0); + ad1889_load_adc_interrupt_count(chip, 0x0); + + /* flush */ + ad1889_readw(chip, AD_DMA_ADC); + } +} + +static u16 +snd_ad1889_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct snd_ad1889 *chip = ac97->private_data; + return ad1889_readw(chip, AD_AC97_BASE + reg); +} + +static void +snd_ad1889_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) +{ + struct snd_ad1889 *chip = ac97->private_data; + ad1889_writew(chip, AD_AC97_BASE + reg, val); +} + +static int +snd_ad1889_ac97_ready(struct snd_ad1889 *chip) +{ + int retry = 400; /* average needs 352 msec */ + + while (!(ad1889_readw(chip, AD_AC97_ACIC) & AD_AC97_ACIC_ACRDY) + && --retry) + usleep_range(1000, 2000); + if (!retry) { + dev_err(chip->card->dev, "[%s] Link is not ready.\n", + __func__); + return -EIO; + } + dev_dbg(chip->card->dev, "[%s] ready after %d ms\n", __func__, 400 - retry); + + return 0; +} + +static const struct snd_pcm_hardware snd_ad1889_playback_hw = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, /* docs say 7000, but we're lazy */ + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = PERIOD_BYTES_MAX, + .periods_min = PERIODS_MIN, + .periods_max = PERIODS_MAX, + /*.fifo_size = 0,*/ +}; + +static const struct snd_pcm_hardware snd_ad1889_capture_hw = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, /* docs say we could to VSR, but we're lazy */ + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = PERIOD_BYTES_MAX, + .periods_min = PERIODS_MIN, + .periods_max = PERIODS_MAX, + /*.fifo_size = 0,*/ +}; + +static int +snd_ad1889_playback_open(struct snd_pcm_substream *ss) +{ + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; + + chip->psubs = ss; + rt->hw = snd_ad1889_playback_hw; + + return 0; +} + +static int +snd_ad1889_capture_open(struct snd_pcm_substream *ss) +{ + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; + + chip->csubs = ss; + rt->hw = snd_ad1889_capture_hw; + + return 0; +} + +static int +snd_ad1889_playback_close(struct snd_pcm_substream *ss) +{ + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + chip->psubs = NULL; + return 0; +} + +static int +snd_ad1889_capture_close(struct snd_pcm_substream *ss) +{ + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + chip->csubs = NULL; + return 0; +} + +static int +snd_ad1889_playback_prepare(struct snd_pcm_substream *ss) +{ + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(ss); + unsigned int count = snd_pcm_lib_period_bytes(ss); + u16 reg; + + ad1889_channel_reset(chip, AD_CHAN_WAV); + + reg = ad1889_readw(chip, AD_DS_WSMC); + + /* Mask out 16-bit / Stereo */ + reg &= ~(AD_DS_WSMC_WA16 | AD_DS_WSMC_WAST); + + if (snd_pcm_format_width(rt->format) == 16) + reg |= AD_DS_WSMC_WA16; + + if (rt->channels > 1) + reg |= AD_DS_WSMC_WAST; + + /* let's make sure we don't clobber ourselves */ + guard(spinlock_irq)(&chip->lock); + + chip->wave.size = size; + chip->wave.reg = reg; + chip->wave.addr = rt->dma_addr; + + ad1889_writew(chip, AD_DS_WSMC, chip->wave.reg); + + /* Set sample rates on the codec */ + ad1889_writew(chip, AD_DS_WAS, rt->rate); + + /* Set up DMA */ + ad1889_load_wave_buffer_address(chip, chip->wave.addr); + ad1889_load_wave_buffer_count(chip, size); + ad1889_load_wave_interrupt_count(chip, count); + + /* writes flush */ + ad1889_readw(chip, AD_DS_WSMC); + + dev_dbg(chip->card->dev, + "prepare playback: addr = 0x%x, count = %u, size = %u, reg = 0x%x, rate = %u\n", + chip->wave.addr, count, size, reg, rt->rate); + return 0; +} + +static int +snd_ad1889_capture_prepare(struct snd_pcm_substream *ss) +{ + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(ss); + unsigned int count = snd_pcm_lib_period_bytes(ss); + u16 reg; + + ad1889_channel_reset(chip, AD_CHAN_ADC); + + reg = ad1889_readw(chip, AD_DS_RAMC); + + /* Mask out 16-bit / Stereo */ + reg &= ~(AD_DS_RAMC_AD16 | AD_DS_RAMC_ADST); + + if (snd_pcm_format_width(rt->format) == 16) + reg |= AD_DS_RAMC_AD16; + + if (rt->channels > 1) + reg |= AD_DS_RAMC_ADST; + + /* let's make sure we don't clobber ourselves */ + guard(spinlock_irq)(&chip->lock); + + chip->ramc.size = size; + chip->ramc.reg = reg; + chip->ramc.addr = rt->dma_addr; + + ad1889_writew(chip, AD_DS_RAMC, chip->ramc.reg); + + /* Set up DMA */ + ad1889_load_adc_buffer_address(chip, chip->ramc.addr); + ad1889_load_adc_buffer_count(chip, size); + ad1889_load_adc_interrupt_count(chip, count); + + /* writes flush */ + ad1889_readw(chip, AD_DS_RAMC); + + dev_dbg(chip->card->dev, + "prepare capture: addr = 0x%x, count = %u, size = %u, reg = 0x%x, rate = %u\n", + chip->ramc.addr, count, size, reg, rt->rate); + return 0; +} + +/* this is called in atomic context with IRQ disabled. + Must be as fast as possible and not sleep. + DMA should be *triggered* by this call. + The WSMC "WAEN" bit triggers DMA Wave On/Off */ +static int +snd_ad1889_playback_trigger(struct snd_pcm_substream *ss, int cmd) +{ + u16 wsmc; + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + + wsmc = ad1889_readw(chip, AD_DS_WSMC); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* enable DMA loop & interrupts */ + ad1889_writew(chip, AD_DMA_WAV, AD_DMA_LOOP | AD_DMA_IM_CNT); + wsmc |= AD_DS_WSMC_WAEN; + /* 1 to clear CHSS bit */ + ad1889_writel(chip, AD_DMA_CHSS, AD_DMA_CHSS_WAVS); + ad1889_unmute(chip); + break; + case SNDRV_PCM_TRIGGER_STOP: + ad1889_mute(chip); + wsmc &= ~AD_DS_WSMC_WAEN; + break; + default: + snd_BUG(); + return -EINVAL; + } + + chip->wave.reg = wsmc; + ad1889_writew(chip, AD_DS_WSMC, wsmc); + ad1889_readw(chip, AD_DS_WSMC); /* flush */ + + /* reset the chip when STOP - will disable IRQs */ + if (cmd == SNDRV_PCM_TRIGGER_STOP) + ad1889_channel_reset(chip, AD_CHAN_WAV); + + return 0; +} + +/* this is called in atomic context with IRQ disabled. + Must be as fast as possible and not sleep. + DMA should be *triggered* by this call. + The RAMC "ADEN" bit triggers DMA ADC On/Off */ +static int +snd_ad1889_capture_trigger(struct snd_pcm_substream *ss, int cmd) +{ + u16 ramc; + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + + ramc = ad1889_readw(chip, AD_DS_RAMC); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* enable DMA loop & interrupts */ + ad1889_writew(chip, AD_DMA_ADC, AD_DMA_LOOP | AD_DMA_IM_CNT); + ramc |= AD_DS_RAMC_ADEN; + /* 1 to clear CHSS bit */ + ad1889_writel(chip, AD_DMA_CHSS, AD_DMA_CHSS_ADCS); + break; + case SNDRV_PCM_TRIGGER_STOP: + ramc &= ~AD_DS_RAMC_ADEN; + break; + default: + return -EINVAL; + } + + chip->ramc.reg = ramc; + ad1889_writew(chip, AD_DS_RAMC, ramc); + ad1889_readw(chip, AD_DS_RAMC); /* flush */ + + /* reset the chip when STOP - will disable IRQs */ + if (cmd == SNDRV_PCM_TRIGGER_STOP) + ad1889_channel_reset(chip, AD_CHAN_ADC); + + return 0; +} + +/* Called in atomic context with IRQ disabled */ +static snd_pcm_uframes_t +snd_ad1889_playback_pointer(struct snd_pcm_substream *ss) +{ + size_t ptr = 0; + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + + if (unlikely(!(chip->wave.reg & AD_DS_WSMC_WAEN))) + return 0; + + ptr = ad1889_readl(chip, AD_DMA_WAVCA); + ptr -= chip->wave.addr; + + if (snd_BUG_ON(ptr >= chip->wave.size)) + return 0; + + return bytes_to_frames(ss->runtime, ptr); +} + +/* Called in atomic context with IRQ disabled */ +static snd_pcm_uframes_t +snd_ad1889_capture_pointer(struct snd_pcm_substream *ss) +{ + size_t ptr = 0; + struct snd_ad1889 *chip = snd_pcm_substream_chip(ss); + + if (unlikely(!(chip->ramc.reg & AD_DS_RAMC_ADEN))) + return 0; + + ptr = ad1889_readl(chip, AD_DMA_ADCCA); + ptr -= chip->ramc.addr; + + if (snd_BUG_ON(ptr >= chip->ramc.size)) + return 0; + + return bytes_to_frames(ss->runtime, ptr); +} + +static const struct snd_pcm_ops snd_ad1889_playback_ops = { + .open = snd_ad1889_playback_open, + .close = snd_ad1889_playback_close, + .prepare = snd_ad1889_playback_prepare, + .trigger = snd_ad1889_playback_trigger, + .pointer = snd_ad1889_playback_pointer, +}; + +static const struct snd_pcm_ops snd_ad1889_capture_ops = { + .open = snd_ad1889_capture_open, + .close = snd_ad1889_capture_close, + .prepare = snd_ad1889_capture_prepare, + .trigger = snd_ad1889_capture_trigger, + .pointer = snd_ad1889_capture_pointer, +}; + +static irqreturn_t +snd_ad1889_interrupt(int irq, void *dev_id) +{ + unsigned long st; + struct snd_ad1889 *chip = dev_id; + + st = ad1889_readl(chip, AD_DMA_DISR); + + /* clear ISR */ + ad1889_writel(chip, AD_DMA_DISR, st); + + st &= AD_INTR_MASK; + + if (unlikely(!st)) + return IRQ_NONE; + + if (st & (AD_DMA_DISR_PMAI|AD_DMA_DISR_PTAI)) + dev_dbg(chip->card->dev, + "Unexpected master or target abort interrupt!\n"); + + if ((st & AD_DMA_DISR_WAVI) && chip->psubs) + snd_pcm_period_elapsed(chip->psubs); + if ((st & AD_DMA_DISR_ADCI) && chip->csubs) + snd_pcm_period_elapsed(chip->csubs); + + return IRQ_HANDLED; +} + +static int +snd_ad1889_pcm_init(struct snd_ad1889 *chip, int device) +{ + int err; + struct snd_pcm *pcm; + + err = snd_pcm_new(chip->card, chip->card->driver, device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_ad1889_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_ad1889_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + strscpy(pcm->name, chip->card->shortname); + + chip->pcm = pcm; + chip->psubs = NULL; + chip->csubs = NULL; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + BUFFER_BYTES_MAX / 2, BUFFER_BYTES_MAX); + + return 0; +} + +static void +snd_ad1889_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_ad1889 *chip = entry->private_data; + u16 reg; + int tmp; + + reg = ad1889_readw(chip, AD_DS_WSMC); + snd_iprintf(buffer, "Wave output: %s\n", + str_enabled_disabled(reg & AD_DS_WSMC_WAEN)); + snd_iprintf(buffer, "Wave Channels: %s\n", + (reg & AD_DS_WSMC_WAST) ? "stereo" : "mono"); + snd_iprintf(buffer, "Wave Quality: %d-bit linear\n", + (reg & AD_DS_WSMC_WA16) ? 16 : 8); + + /* WARQ is at offset 12 */ + tmp = (reg & AD_DS_WSMC_WARQ) ? + ((((reg & AD_DS_WSMC_WARQ) >> 12) & 0x01) ? 12 : 18) : 4; + tmp /= (reg & AD_DS_WSMC_WAST) ? 2 : 1; + + snd_iprintf(buffer, "Wave FIFO: %d %s words\n\n", tmp, + (reg & AD_DS_WSMC_WAST) ? "stereo" : "mono"); + + + snd_iprintf(buffer, "Synthesis output: %s\n", + str_enabled_disabled(reg & AD_DS_WSMC_SYEN)); + + /* SYRQ is at offset 4 */ + tmp = (reg & AD_DS_WSMC_SYRQ) ? + ((((reg & AD_DS_WSMC_SYRQ) >> 4) & 0x01) ? 12 : 18) : 4; + tmp /= (reg & AD_DS_WSMC_WAST) ? 2 : 1; + + snd_iprintf(buffer, "Synthesis FIFO: %d %s words\n\n", tmp, + (reg & AD_DS_WSMC_WAST) ? "stereo" : "mono"); + + reg = ad1889_readw(chip, AD_DS_RAMC); + snd_iprintf(buffer, "ADC input: %s\n", + str_enabled_disabled(reg & AD_DS_RAMC_ADEN)); + snd_iprintf(buffer, "ADC Channels: %s\n", + (reg & AD_DS_RAMC_ADST) ? "stereo" : "mono"); + snd_iprintf(buffer, "ADC Quality: %d-bit linear\n", + (reg & AD_DS_RAMC_AD16) ? 16 : 8); + + /* ACRQ is at offset 4 */ + tmp = (reg & AD_DS_RAMC_ACRQ) ? + ((((reg & AD_DS_RAMC_ACRQ) >> 4) & 0x01) ? 12 : 18) : 4; + tmp /= (reg & AD_DS_RAMC_ADST) ? 2 : 1; + + snd_iprintf(buffer, "ADC FIFO: %d %s words\n\n", tmp, + (reg & AD_DS_RAMC_ADST) ? "stereo" : "mono"); + + snd_iprintf(buffer, "Resampler input: %s\n", + str_enabled_disabled(reg & AD_DS_RAMC_REEN)); + + /* RERQ is at offset 12 */ + tmp = (reg & AD_DS_RAMC_RERQ) ? + ((((reg & AD_DS_RAMC_RERQ) >> 12) & 0x01) ? 12 : 18) : 4; + tmp /= (reg & AD_DS_RAMC_ADST) ? 2 : 1; + + snd_iprintf(buffer, "Resampler FIFO: %d %s words\n\n", tmp, + (reg & AD_DS_WSMC_WAST) ? "stereo" : "mono"); + + + /* doc says LSB represents -1.5dB, but the max value (-94.5dB) + suggests that LSB is -3dB, which is more coherent with the logarithmic + nature of the dB scale */ + reg = ad1889_readw(chip, AD_DS_WADA); + snd_iprintf(buffer, "Left: %s, -%d dB\n", + (reg & AD_DS_WADA_LWAM) ? "mute" : "unmute", + ((reg & AD_DS_WADA_LWAA) >> 8) * 3); + reg = ad1889_readw(chip, AD_DS_WADA); + snd_iprintf(buffer, "Right: %s, -%d dB\n", + (reg & AD_DS_WADA_RWAM) ? "mute" : "unmute", + (reg & AD_DS_WADA_RWAA) * 3); + + reg = ad1889_readw(chip, AD_DS_WAS); + snd_iprintf(buffer, "Wave samplerate: %u Hz\n", reg); + reg = ad1889_readw(chip, AD_DS_RES); + snd_iprintf(buffer, "Resampler samplerate: %u Hz\n", reg); +} + +static void +snd_ad1889_proc_init(struct snd_ad1889 *chip) +{ + snd_card_ro_proc_new(chip->card, chip->card->driver, + chip, snd_ad1889_proc_read); +} + +static const struct ac97_quirk ac97_quirks[] = { + { + .subvendor = 0x11d4, /* AD */ + .subdevice = 0x1889, /* AD1889 */ + .codec_id = AC97_ID_AD1819, + .name = "AD1889", + .type = AC97_TUNE_HP_ONLY + }, + { } /* terminator */ +}; + +static void +snd_ad1889_ac97_xinit(struct snd_ad1889 *chip) +{ + u16 reg; + + reg = ad1889_readw(chip, AD_AC97_ACIC); + reg |= AD_AC97_ACIC_ACRD; /* Reset Disable */ + ad1889_writew(chip, AD_AC97_ACIC, reg); + ad1889_readw(chip, AD_AC97_ACIC); /* flush posted write */ + udelay(10); + /* Interface Enable */ + reg |= AD_AC97_ACIC_ACIE; + ad1889_writew(chip, AD_AC97_ACIC, reg); + + snd_ad1889_ac97_ready(chip); + + /* Audio Stream Output | Variable Sample Rate Mode */ + reg = ad1889_readw(chip, AD_AC97_ACIC); + reg |= AD_AC97_ACIC_ASOE | AD_AC97_ACIC_VSRM; + ad1889_writew(chip, AD_AC97_ACIC, reg); + ad1889_readw(chip, AD_AC97_ACIC); /* flush posted write */ + +} + +static int +snd_ad1889_ac97_init(struct snd_ad1889 *chip, const char *quirk_override) +{ + int err; + struct snd_ac97_template ac97; + static const struct snd_ac97_bus_ops ops = { + .write = snd_ad1889_ac97_write, + .read = snd_ad1889_ac97_read, + }; + + /* doing that here, it works. */ + snd_ad1889_ac97_xinit(chip); + + err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.pci = chip->pci; + + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97); + if (err < 0) + return err; + + snd_ac97_tune_hardware(chip->ac97, ac97_quirks, quirk_override); + + return 0; +} + +static void +snd_ad1889_free(struct snd_card *card) +{ + struct snd_ad1889 *chip = card->private_data; + + guard(spinlock_irq)(&chip->lock); + + ad1889_mute(chip); + + /* Turn off interrupt on count and zero DMA registers */ + ad1889_channel_reset(chip, AD_CHAN_WAV | AD_CHAN_ADC); + + /* clear DISR. If we don't, we'd better jump off the Eiffel Tower */ + ad1889_writel(chip, AD_DMA_DISR, AD_DMA_DISR_PTAI | AD_DMA_DISR_PMAI); + ad1889_readl(chip, AD_DMA_DISR); /* flush, dammit! */ +} + +static int +snd_ad1889_create(struct snd_card *card, struct pci_dev *pci) +{ + struct snd_ad1889 *chip = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + /* check PCI availability (32bit DMA) */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32))) { + dev_err(card->dev, "error setting 32-bit DMA mask.\n"); + return -ENXIO; + } + + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + /* (1) PCI resource allocation */ + chip->iobase = pcim_iomap_region(pci, 0, card->driver); + if (IS_ERR(chip->iobase)) + return PTR_ERR(chip->iobase); + + chip->bar = pci_resource_start(pci, 0); + + pci_set_master(pci); + + spin_lock_init(&chip->lock); /* only now can we call ad1889_free */ + + if (devm_request_irq(&pci->dev, pci->irq, snd_ad1889_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "cannot obtain IRQ %d\n", pci->irq); + return -EBUSY; + } + + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_ad1889_free; + + /* (2) initialization of the chip hardware */ + ad1889_writew(chip, AD_DS_CCS, AD_DS_CCS_CLKEN); /* turn on clock */ + ad1889_readw(chip, AD_DS_CCS); /* flush posted write */ + + usleep_range(10000, 11000); + + /* enable Master and Target abort interrupts */ + ad1889_writel(chip, AD_DMA_DISR, AD_DMA_DISR_PMAE | AD_DMA_DISR_PTAE); + + return 0; +} + +static int +__snd_ad1889_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + int err; + static int devno; + struct snd_card *card; + struct snd_ad1889 *chip; + + /* (1) */ + if (devno >= SNDRV_CARDS) + return -ENODEV; + if (!enable[devno]) { + devno++; + return -ENOENT; + } + + /* (2) */ + err = snd_devm_card_new(&pci->dev, index[devno], id[devno], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + strscpy(card->driver, "AD1889"); + strscpy(card->shortname, "Analog Devices AD1889"); + + /* (3) */ + err = snd_ad1889_create(card, pci); + if (err < 0) + return err; + + /* (4) */ + sprintf(card->longname, "%s at 0x%lx irq %i", + card->shortname, chip->bar, chip->irq); + + /* (5) */ + /* register AC97 mixer */ + err = snd_ad1889_ac97_init(chip, ac97_quirk[devno]); + if (err < 0) + return err; + + err = snd_ad1889_pcm_init(chip, 0); + if (err < 0) + return err; + + /* register proc interface */ + snd_ad1889_proc_init(chip); + + /* (6) */ + err = snd_card_register(card); + if (err < 0) + return err; + + /* (7) */ + pci_set_drvdata(pci, card); + + devno++; + return 0; +} + +static int snd_ad1889_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_ad1889_probe(pci, pci_id)); +} + +static const struct pci_device_id snd_ad1889_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ANALOG_DEVICES, PCI_DEVICE_ID_AD1889JS) }, + { 0, }, +}; +MODULE_DEVICE_TABLE(pci, snd_ad1889_ids); + +static struct pci_driver ad1889_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_ad1889_ids, + .probe = snd_ad1889_probe, +}; + +module_pci_driver(ad1889_pci_driver); diff --git a/sound/pci/ad1889.h b/sound/pci/ad1889.h new file mode 100644 index 000000000..d6e8d6c19 --- /dev/null +++ b/sound/pci/ad1889.h @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Analog Devices 1889 audio driver + * Copyright (C) 2004, Kyle McMartin + */ + +#ifndef __AD1889_H__ +#define __AD1889_H__ + +#define AD_DS_WSMC 0x00 /* wave/synthesis channel mixer control */ +#define AD_DS_WSMC_SYEN 0x0004 /* synthesis channel enable */ +#define AD_DS_WSMC_SYRQ 0x0030 /* synth. fifo request point */ +#define AD_DS_WSMC_WA16 0x0100 /* wave channel 16bit select */ +#define AD_DS_WSMC_WAST 0x0200 /* wave channel stereo select */ +#define AD_DS_WSMC_WAEN 0x0400 /* wave channel enable */ +#define AD_DS_WSMC_WARQ 0x3000 /* wave fifo request point */ + +#define AD_DS_RAMC 0x02 /* resampler/ADC channel mixer control */ +#define AD_DS_RAMC_AD16 0x0001 /* ADC channel 16bit select */ +#define AD_DS_RAMC_ADST 0x0002 /* ADC channel stereo select */ +#define AD_DS_RAMC_ADEN 0x0004 /* ADC channel enable */ +#define AD_DS_RAMC_ACRQ 0x0030 /* ADC fifo request point */ +#define AD_DS_RAMC_REEN 0x0400 /* resampler channel enable */ +#define AD_DS_RAMC_RERQ 0x3000 /* res. fifo request point */ + +#define AD_DS_WADA 0x04 /* wave channel mix attenuation */ +#define AD_DS_WADA_RWAM 0x0080 /* right wave mute */ +#define AD_DS_WADA_RWAA 0x001f /* right wave attenuation */ +#define AD_DS_WADA_LWAM 0x8000 /* left wave mute */ +#define AD_DS_WADA_LWAA 0x3e00 /* left wave attenuation */ + +#define AD_DS_SYDA 0x06 /* synthesis channel mix attenuation */ +#define AD_DS_SYDA_RSYM 0x0080 /* right synthesis mute */ +#define AD_DS_SYDA_RSYA 0x001f /* right synthesis attenuation */ +#define AD_DS_SYDA_LSYM 0x8000 /* left synthesis mute */ +#define AD_DS_SYDA_LSYA 0x3e00 /* left synthesis attenuation */ + +#define AD_DS_WAS 0x08 /* wave channel sample rate */ +#define AD_DS_WAS_WAS 0xffff /* sample rate mask */ + +#define AD_DS_RES 0x0a /* resampler channel sample rate */ +#define AD_DS_RES_RES 0xffff /* sample rate mask */ + +#define AD_DS_CCS 0x0c /* chip control/status */ +#define AD_DS_CCS_ADO 0x0001 /* ADC channel overflow */ +#define AD_DS_CCS_REO 0x0002 /* resampler channel overflow */ +#define AD_DS_CCS_SYU 0x0004 /* synthesis channel underflow */ +#define AD_DS_CCS_WAU 0x0008 /* wave channel underflow */ +/* bits 4 -> 7, 9, 11 -> 14 reserved */ +#define AD_DS_CCS_XTD 0x0100 /* xtd delay control (4096 clock cycles) */ +#define AD_DS_CCS_PDALL 0x0400 /* power */ +#define AD_DS_CCS_CLKEN 0x8000 /* clock */ + +#define AD_DMA_RESBA 0x40 /* RES base address */ +#define AD_DMA_RESCA 0x44 /* RES current address */ +#define AD_DMA_RESBC 0x48 /* RES base count */ +#define AD_DMA_RESCC 0x4c /* RES current count */ + +#define AD_DMA_ADCBA 0x50 /* ADC base address */ +#define AD_DMA_ADCCA 0x54 /* ADC current address */ +#define AD_DMA_ADCBC 0x58 /* ADC base count */ +#define AD_DMA_ADCCC 0x5c /* ADC current count */ + +#define AD_DMA_SYNBA 0x60 /* synth base address */ +#define AD_DMA_SYNCA 0x64 /* synth current address */ +#define AD_DMA_SYNBC 0x68 /* synth base count */ +#define AD_DMA_SYNCC 0x6c /* synth current count */ + +#define AD_DMA_WAVBA 0x70 /* wave base address */ +#define AD_DMA_WAVCA 0x74 /* wave current address */ +#define AD_DMA_WAVBC 0x78 /* wave base count */ +#define AD_DMA_WAVCC 0x7c /* wave current count */ + +#define AD_DMA_RESIC 0x80 /* RES dma interrupt current byte count */ +#define AD_DMA_RESIB 0x84 /* RES dma interrupt base byte count */ + +#define AD_DMA_ADCIC 0x88 /* ADC dma interrupt current byte count */ +#define AD_DMA_ADCIB 0x8c /* ADC dma interrupt base byte count */ + +#define AD_DMA_SYNIC 0x90 /* synth dma interrupt current byte count */ +#define AD_DMA_SYNIB 0x94 /* synth dma interrupt base byte count */ + +#define AD_DMA_WAVIC 0x98 /* wave dma interrupt current byte count */ +#define AD_DMA_WAVIB 0x9c /* wave dma interrupt base byte count */ + +#define AD_DMA_ICC 0xffffff /* current byte count mask */ +#define AD_DMA_IBC 0xffffff /* base byte count mask */ +/* bits 24 -> 31 reserved */ + +/* 4 bytes pad */ +#define AD_DMA_ADC 0xa8 /* ADC dma control and status */ +#define AD_DMA_SYNTH 0xb0 /* Synth dma control and status */ +#define AD_DMA_WAV 0xb8 /* wave dma control and status */ +#define AD_DMA_RES 0xa0 /* Resample dma control and status */ + +#define AD_DMA_SGDE 0x0001 /* SGD mode enable */ +#define AD_DMA_LOOP 0x0002 /* loop enable */ +#define AD_DMA_IM 0x000c /* interrupt mode mask */ +#define AD_DMA_IM_DIS (~AD_DMA_IM) /* disable */ +#define AD_DMA_IM_CNT 0x0004 /* interrupt on count */ +#define AD_DMA_IM_SGD 0x0008 /* interrupt on SGD flag */ +#define AD_DMA_IM_EOL 0x000c /* interrupt on End of Linked List */ +#define AD_DMA_SGDS 0x0030 /* SGD status */ +#define AD_DMA_SFLG 0x0040 /* SGD flag */ +#define AD_DMA_EOL 0x0080 /* SGD end of list */ +/* bits 8 -> 15 reserved */ + +#define AD_DMA_DISR 0xc0 /* dma interrupt status */ +#define AD_DMA_DISR_RESI 0x000001 /* resampler channel interrupt */ +#define AD_DMA_DISR_ADCI 0x000002 /* ADC channel interrupt */ +#define AD_DMA_DISR_SYNI 0x000004 /* synthesis channel interrupt */ +#define AD_DMA_DISR_WAVI 0x000008 /* wave channel interrupt */ +/* bits 4, 5 reserved */ +#define AD_DMA_DISR_SEPS 0x000040 /* serial eeprom status */ +/* bits 7 -> 13 reserved */ +#define AD_DMA_DISR_PMAI 0x004000 /* pci master abort interrupt */ +#define AD_DMA_DISR_PTAI 0x008000 /* pci target abort interrupt */ +#define AD_DMA_DISR_PTAE 0x010000 /* pci target abort interrupt enable */ +#define AD_DMA_DISR_PMAE 0x020000 /* pci master abort interrupt enable */ +/* bits 19 -> 31 reserved */ + +/* interrupt mask */ +#define AD_INTR_MASK (AD_DMA_DISR_RESI|AD_DMA_DISR_ADCI| \ + AD_DMA_DISR_WAVI|AD_DMA_DISR_SYNI| \ + AD_DMA_DISR_PMAI|AD_DMA_DISR_PTAI) + +#define AD_DMA_CHSS 0xc4 /* dma channel stop status */ +#define AD_DMA_CHSS_RESS 0x000001 /* resampler channel stopped */ +#define AD_DMA_CHSS_ADCS 0x000002 /* ADC channel stopped */ +#define AD_DMA_CHSS_SYNS 0x000004 /* synthesis channel stopped */ +#define AD_DMA_CHSS_WAVS 0x000008 /* wave channel stopped */ + +#define AD_GPIO_IPC 0xc8 /* gpio port control */ +#define AD_GPIO_OP 0xca /* gpio output port status */ +#define AD_GPIO_IP 0xcc /* gpio input port status */ + +#define AD_AC97_BASE 0x100 /* ac97 base register */ + +#define AD_AC97_RESET 0x100 /* reset */ + +#define AD_AC97_PWR_CTL 0x126 /* == AC97_POWERDOWN */ +#define AD_AC97_PWR_ADC 0x0001 /* ADC ready status */ +#define AD_AC97_PWR_DAC 0x0002 /* DAC ready status */ +#define AD_AC97_PWR_PR0 0x0100 /* PR0 (ADC) powerdown */ +#define AD_AC97_PWR_PR1 0x0200 /* PR1 (DAC) powerdown */ + +#define AD_MISC_CTL 0x176 /* misc control */ +#define AD_MISC_CTL_DACZ 0x8000 /* set for zero fill, unset for repeat */ +#define AD_MISC_CTL_ARSR 0x0001 /* set for SR1, unset for SR0 */ +#define AD_MISC_CTL_ALSR 0x0100 +#define AD_MISC_CTL_DLSR 0x0400 +#define AD_MISC_CTL_DRSR 0x0004 + +#define AD_AC97_SR0 0x178 /* sample rate 0, 0xbb80 == 48K */ +#define AD_AC97_SR0_48K 0xbb80 /* 48KHz */ +#define AD_AC97_SR1 0x17a /* sample rate 1 */ + +#define AD_AC97_ACIC 0x180 /* ac97 codec interface control */ +#define AD_AC97_ACIC_ACIE 0x0001 /* analog codec interface enable */ +#define AD_AC97_ACIC_ACRD 0x0002 /* analog codec reset disable */ +#define AD_AC97_ACIC_ASOE 0x0004 /* audio stream output enable */ +#define AD_AC97_ACIC_VSRM 0x0008 /* variable sample rate mode */ +#define AD_AC97_ACIC_FSDH 0x0100 /* force SDATA_OUT high */ +#define AD_AC97_ACIC_FSYH 0x0200 /* force sync high */ +#define AD_AC97_ACIC_ACRDY 0x8000 /* analog codec ready status */ +/* bits 10 -> 14 reserved */ + + +#define AD_DS_MEMSIZE 512 +#define AD_OPL_MEMSIZE 16 +#define AD_MIDI_MEMSIZE 16 + +#define AD_WAV_STATE 0 +#define AD_ADC_STATE 1 +#define AD_MAX_STATES 2 + +#define AD_CHAN_WAV 0x0001 +#define AD_CHAN_ADC 0x0002 +#define AD_CHAN_RES 0x0004 +#define AD_CHAN_SYN 0x0008 + + +/* The chip would support 4 GB buffers and 16 MB periods, + * but let's not overdo it ... */ +#define BUFFER_BYTES_MAX (256 * 1024) +#define PERIOD_BYTES_MIN 32 +#define PERIOD_BYTES_MAX (BUFFER_BYTES_MAX / 2) +#define PERIODS_MIN 2 +#define PERIODS_MAX (BUFFER_BYTES_MAX / PERIOD_BYTES_MIN) + +#endif /* __AD1889_H__ */ diff --git a/sound/pci/ak4531_codec.c b/sound/pci/ak4531_codec.c new file mode 100644 index 000000000..6f35b616e --- /dev/null +++ b/sound/pci/ak4531_codec.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Universal routines for AK4531 codec + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Universal routines for AK4531 codec"); +MODULE_LICENSE("GPL"); +*/ + +static void snd_ak4531_proc_init(struct snd_card *card, struct snd_ak4531 *ak4531); + +/* + * + */ + +#if 0 + +static void snd_ak4531_dump(struct snd_ak4531 *ak4531) +{ + int idx; + + for (idx = 0; idx < 0x19; idx++) + printk(KERN_DEBUG "ak4531 0x%x: 0x%x\n", + idx, ak4531->regs[idx]); +} + +#endif + +/* + * + */ + +#define AK4531_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_ak4531_info_single, \ + .get = snd_ak4531_get_single, .put = snd_ak4531_put_single, \ + .private_value = reg | (shift << 16) | (mask << 24) | (invert << 22) } +#define AK4531_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_ak4531_info_single, \ + .get = snd_ak4531_get_single, .put = snd_ak4531_put_single, \ + .private_value = reg | (shift << 16) | (mask << 24) | (invert << 22), \ + .tlv = { .p = (xtlv) } } + +static int snd_ak4531_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_ak4531_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 16) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int val; + + guard(mutex)(&ak4531->reg_mutex); + val = (ak4531->regs[reg] >> shift) & mask; + if (invert) { + val = mask - val; + } + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_ak4531_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 16) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + int val; + + val = ucontrol->value.integer.value[0] & mask; + if (invert) { + val = mask - val; + } + val <<= shift; + guard(mutex)(&ak4531->reg_mutex); + val = (ak4531->regs[reg] & ~(mask << shift)) | val; + change = val != ak4531->regs[reg]; + ak4531->write(ak4531, reg, ak4531->regs[reg] = val); + return change; +} + +#define AK4531_DOUBLE(xname, xindex, left_reg, right_reg, left_shift, right_shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_ak4531_info_double, \ + .get = snd_ak4531_get_double, .put = snd_ak4531_put_double, \ + .private_value = left_reg | (right_reg << 8) | (left_shift << 16) | (right_shift << 19) | (mask << 24) | (invert << 22) } +#define AK4531_DOUBLE_TLV(xname, xindex, left_reg, right_reg, left_shift, right_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_ak4531_info_double, \ + .get = snd_ak4531_get_double, .put = snd_ak4531_put_double, \ + .private_value = left_reg | (right_reg << 8) | (left_shift << 16) | (right_shift << 19) | (mask << 24) | (invert << 22), \ + .tlv = { .p = (xtlv) } } + +static int snd_ak4531_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_ak4531_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol); + 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 invert = (kcontrol->private_value >> 22) & 1; + int left, right; + + guard(mutex)(&ak4531->reg_mutex); + left = (ak4531->regs[left_reg] >> left_shift) & mask; + right = (ak4531->regs[right_reg] >> right_shift) & mask; + if (invert) { + left = mask - left; + right = mask - right; + } + ucontrol->value.integer.value[0] = left; + ucontrol->value.integer.value[1] = right; + return 0; +} + +static int snd_ak4531_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol); + 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 invert = (kcontrol->private_value >> 22) & 1; + int change; + int left, right; + + left = ucontrol->value.integer.value[0] & mask; + right = ucontrol->value.integer.value[1] & mask; + if (invert) { + left = mask - left; + right = mask - right; + } + left <<= left_shift; + right <<= right_shift; + guard(mutex)(&ak4531->reg_mutex); + if (left_reg == right_reg) { + left = (ak4531->regs[left_reg] & ~((mask << left_shift) | (mask << right_shift))) | left | right; + change = left != ak4531->regs[left_reg]; + ak4531->write(ak4531, left_reg, ak4531->regs[left_reg] = left); + } else { + left = (ak4531->regs[left_reg] & ~(mask << left_shift)) | left; + right = (ak4531->regs[right_reg] & ~(mask << right_shift)) | right; + change = left != ak4531->regs[left_reg] || right != ak4531->regs[right_reg]; + ak4531->write(ak4531, left_reg, ak4531->regs[left_reg] = left); + ak4531->write(ak4531, right_reg, ak4531->regs[right_reg] = right); + } + return change; +} + +#define AK4531_INPUT_SW(xname, xindex, reg1, reg2, left_shift, right_shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_ak4531_info_input_sw, \ + .get = snd_ak4531_get_input_sw, .put = snd_ak4531_put_input_sw, \ + .private_value = reg1 | (reg2 << 8) | (left_shift << 16) | (right_shift << 24) } + +static int snd_ak4531_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_ak4531_get_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol); + 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; + + guard(mutex)(&ak4531->reg_mutex); + ucontrol->value.integer.value[0] = (ak4531->regs[reg1] >> left_shift) & 1; + ucontrol->value.integer.value[1] = (ak4531->regs[reg2] >> left_shift) & 1; + ucontrol->value.integer.value[2] = (ak4531->regs[reg1] >> right_shift) & 1; + ucontrol->value.integer.value[3] = (ak4531->regs[reg2] >> right_shift) & 1; + return 0; +} + +static int snd_ak4531_put_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol); + 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; + int val1, val2; + + guard(mutex)(&ak4531->reg_mutex); + val1 = ak4531->regs[reg1] & ~((1 << left_shift) | (1 << right_shift)); + val2 = ak4531->regs[reg2] & ~((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 != ak4531->regs[reg1] || val2 != ak4531->regs[reg2]; + ak4531->write(ak4531, reg1, ak4531->regs[reg1] = val1); + ak4531->write(ak4531, reg2, ak4531->regs[reg2] = val2); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_master, -6200, 200, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_mono, -2800, 400, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_input, -5000, 200, 0); + +static const struct snd_kcontrol_new snd_ak4531_controls[] = { + +AK4531_DOUBLE_TLV("Master Playback Switch", 0, + AK4531_LMASTER, AK4531_RMASTER, 7, 7, 1, 1, + db_scale_master), +AK4531_DOUBLE("Master Playback Volume", 0, AK4531_LMASTER, AK4531_RMASTER, 0, 0, 0x1f, 1), + +AK4531_SINGLE_TLV("Master Mono Playback Switch", 0, AK4531_MONO_OUT, 7, 1, 1, + db_scale_mono), +AK4531_SINGLE("Master Mono Playback Volume", 0, AK4531_MONO_OUT, 0, 0x07, 1), + +AK4531_DOUBLE("PCM Switch", 0, AK4531_LVOICE, AK4531_RVOICE, 7, 7, 1, 1), +AK4531_DOUBLE_TLV("PCM Volume", 0, AK4531_LVOICE, AK4531_RVOICE, 0, 0, 0x1f, 1, + db_scale_input), +AK4531_DOUBLE("PCM Playback Switch", 0, AK4531_OUT_SW2, AK4531_OUT_SW2, 3, 2, 1, 0), +AK4531_DOUBLE("PCM Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 2, 2, 1, 0), + +AK4531_DOUBLE("PCM Switch", 1, AK4531_LFM, AK4531_RFM, 7, 7, 1, 1), +AK4531_DOUBLE_TLV("PCM Volume", 1, AK4531_LFM, AK4531_RFM, 0, 0, 0x1f, 1, + db_scale_input), +AK4531_DOUBLE("PCM Playback Switch", 1, AK4531_OUT_SW1, AK4531_OUT_SW1, 6, 5, 1, 0), +AK4531_INPUT_SW("PCM Capture Route", 1, AK4531_LIN_SW1, AK4531_RIN_SW1, 6, 5), + +AK4531_DOUBLE("CD Switch", 0, AK4531_LCD, AK4531_RCD, 7, 7, 1, 1), +AK4531_DOUBLE_TLV("CD Volume", 0, AK4531_LCD, AK4531_RCD, 0, 0, 0x1f, 1, + db_scale_input), +AK4531_DOUBLE("CD Playback Switch", 0, AK4531_OUT_SW1, AK4531_OUT_SW1, 2, 1, 1, 0), +AK4531_INPUT_SW("CD Capture Route", 0, AK4531_LIN_SW1, AK4531_RIN_SW1, 2, 1), + +AK4531_DOUBLE("Line Switch", 0, AK4531_LLINE, AK4531_RLINE, 7, 7, 1, 1), +AK4531_DOUBLE_TLV("Line Volume", 0, AK4531_LLINE, AK4531_RLINE, 0, 0, 0x1f, 1, + db_scale_input), +AK4531_DOUBLE("Line Playback Switch", 0, AK4531_OUT_SW1, AK4531_OUT_SW1, 4, 3, 1, 0), +AK4531_INPUT_SW("Line Capture Route", 0, AK4531_LIN_SW1, AK4531_RIN_SW1, 4, 3), + +AK4531_DOUBLE("Aux Switch", 0, AK4531_LAUXA, AK4531_RAUXA, 7, 7, 1, 1), +AK4531_DOUBLE_TLV("Aux Volume", 0, AK4531_LAUXA, AK4531_RAUXA, 0, 0, 0x1f, 1, + db_scale_input), +AK4531_DOUBLE("Aux Playback Switch", 0, AK4531_OUT_SW2, AK4531_OUT_SW2, 5, 4, 1, 0), +AK4531_INPUT_SW("Aux Capture Route", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 4, 3), + +AK4531_SINGLE("Mono Switch", 0, AK4531_MONO1, 7, 1, 1), +AK4531_SINGLE_TLV("Mono Volume", 0, AK4531_MONO1, 0, 0x1f, 1, db_scale_input), +AK4531_SINGLE("Mono Playback Switch", 0, AK4531_OUT_SW2, 0, 1, 0), +AK4531_DOUBLE("Mono Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 0, 0, 1, 0), + +AK4531_SINGLE("Mono Switch", 1, AK4531_MONO2, 7, 1, 1), +AK4531_SINGLE_TLV("Mono Volume", 1, AK4531_MONO2, 0, 0x1f, 1, db_scale_input), +AK4531_SINGLE("Mono Playback Switch", 1, AK4531_OUT_SW2, 1, 1, 0), +AK4531_DOUBLE("Mono Capture Switch", 1, AK4531_LIN_SW2, AK4531_RIN_SW2, 1, 1, 1, 0), + +AK4531_SINGLE_TLV("Mic Volume", 0, AK4531_MIC, 0, 0x1f, 1, db_scale_input), +AK4531_SINGLE("Mic Switch", 0, AK4531_MIC, 7, 1, 1), +AK4531_SINGLE("Mic Playback Switch", 0, AK4531_OUT_SW1, 0, 1, 0), +AK4531_DOUBLE("Mic Capture Switch", 0, AK4531_LIN_SW1, AK4531_RIN_SW1, 0, 0, 1, 0), + +AK4531_DOUBLE("Mic Bypass Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 7, 7, 1, 0), +AK4531_DOUBLE("Mono1 Bypass Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 6, 6, 1, 0), +AK4531_DOUBLE("Mono2 Bypass Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 5, 5, 1, 0), + +AK4531_SINGLE("AD Input Select", 0, AK4531_AD_IN, 0, 1, 0), +AK4531_SINGLE("Mic Boost (+30dB)", 0, AK4531_MIC_GAIN, 0, 1, 0) +}; + +static int snd_ak4531_free(struct snd_ak4531 *ak4531) +{ + if (ak4531) { + if (ak4531->private_free) + ak4531->private_free(ak4531); + kfree(ak4531); + } + return 0; +} + +static int snd_ak4531_dev_free(struct snd_device *device) +{ + struct snd_ak4531 *ak4531 = device->device_data; + return snd_ak4531_free(ak4531); +} + +static const u8 snd_ak4531_initial_map[0x19 + 1] = { + 0x9f, /* 00: Master Volume Lch */ + 0x9f, /* 01: Master Volume Rch */ + 0x9f, /* 02: Voice Volume Lch */ + 0x9f, /* 03: Voice Volume Rch */ + 0x9f, /* 04: FM Volume Lch */ + 0x9f, /* 05: FM Volume Rch */ + 0x9f, /* 06: CD Audio Volume Lch */ + 0x9f, /* 07: CD Audio Volume Rch */ + 0x9f, /* 08: Line Volume Lch */ + 0x9f, /* 09: Line Volume Rch */ + 0x9f, /* 0a: Aux Volume Lch */ + 0x9f, /* 0b: Aux Volume Rch */ + 0x9f, /* 0c: Mono1 Volume */ + 0x9f, /* 0d: Mono2 Volume */ + 0x9f, /* 0e: Mic Volume */ + 0x87, /* 0f: Mono-out Volume */ + 0x00, /* 10: Output Mixer SW1 */ + 0x00, /* 11: Output Mixer SW2 */ + 0x00, /* 12: Lch Input Mixer SW1 */ + 0x00, /* 13: Rch Input Mixer SW1 */ + 0x00, /* 14: Lch Input Mixer SW2 */ + 0x00, /* 15: Rch Input Mixer SW2 */ + 0x00, /* 16: Reset & Power Down */ + 0x00, /* 17: Clock Select */ + 0x00, /* 18: AD Input Select */ + 0x01 /* 19: Mic Amp Setup */ +}; + +int snd_ak4531_mixer(struct snd_card *card, + struct snd_ak4531 *_ak4531, + struct snd_ak4531 **rak4531) +{ + unsigned int idx; + int err; + struct snd_ak4531 *ak4531; + static const struct snd_device_ops ops = { + .dev_free = snd_ak4531_dev_free, + }; + + if (snd_BUG_ON(!card || !_ak4531)) + return -EINVAL; + if (rak4531) + *rak4531 = NULL; + ak4531 = kzalloc_obj(*ak4531); + if (ak4531 == NULL) + return -ENOMEM; + *ak4531 = *_ak4531; + mutex_init(&ak4531->reg_mutex); + err = snd_component_add(card, "AK4531"); + if (err < 0) { + snd_ak4531_free(ak4531); + return err; + } + strscpy(card->mixername, "Asahi Kasei AK4531"); + ak4531->write(ak4531, AK4531_RESET, 0x03); /* no RST, PD */ + udelay(100); + ak4531->write(ak4531, AK4531_CLOCK, 0x00); /* CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off LRCLK2 PLL */ + for (idx = 0; idx <= 0x19; idx++) { + if (idx == AK4531_RESET || idx == AK4531_CLOCK) + continue; + ak4531->write(ak4531, idx, ak4531->regs[idx] = snd_ak4531_initial_map[idx]); /* recording source is mixer */ + } + for (idx = 0; idx < ARRAY_SIZE(snd_ak4531_controls); idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_ak4531_controls[idx], ak4531)); + if (err < 0) { + snd_ak4531_free(ak4531); + return err; + } + } + snd_ak4531_proc_init(card, ak4531); + err = snd_device_new(card, SNDRV_DEV_CODEC, ak4531, &ops); + if (err < 0) { + snd_ak4531_free(ak4531); + return err; + } + +#if 0 + snd_ak4531_dump(ak4531); +#endif + if (rak4531) + *rak4531 = ak4531; + return 0; +} + +/* + * power management + */ +#ifdef CONFIG_PM +void snd_ak4531_suspend(struct snd_ak4531 *ak4531) +{ + /* mute */ + ak4531->write(ak4531, AK4531_LMASTER, 0x9f); + ak4531->write(ak4531, AK4531_RMASTER, 0x9f); + /* powerdown */ + ak4531->write(ak4531, AK4531_RESET, 0x01); +} + +void snd_ak4531_resume(struct snd_ak4531 *ak4531) +{ + int idx; + + /* initialize */ + ak4531->write(ak4531, AK4531_RESET, 0x03); + udelay(100); + ak4531->write(ak4531, AK4531_CLOCK, 0x00); + /* restore mixer registers */ + for (idx = 0; idx <= 0x19; idx++) { + if (idx == AK4531_RESET || idx == AK4531_CLOCK) + continue; + ak4531->write(ak4531, idx, ak4531->regs[idx]); + } +} +#endif + +/* + * /proc interface + */ + +static void snd_ak4531_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ak4531 *ak4531 = entry->private_data; + + snd_iprintf(buffer, "Asahi Kasei AK4531\n\n"); + snd_iprintf(buffer, "Recording source : %s\n" + "MIC gain : %s\n", + ak4531->regs[AK4531_AD_IN] & 1 ? "external" : "mixer", + ak4531->regs[AK4531_MIC_GAIN] & 1 ? "+30dB" : "+0dB"); +} + +static void +snd_ak4531_proc_init(struct snd_card *card, struct snd_ak4531 *ak4531) +{ + snd_card_ro_proc_new(card, "ak4531", ak4531, snd_ak4531_proc_read); +} diff --git a/sound/pci/ali5451/Makefile b/sound/pci/ali5451/Makefile new file mode 100644 index 000000000..e319a4c1d --- /dev/null +++ b/sound/pci/ali5451/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-ali5451-y := ali5451.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_ALI5451) += snd-ali5451.o diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c new file mode 100644 index 000000000..f07c5f4c2 --- /dev/null +++ b/sound/pci/ali5451/ali5451.c @@ -0,0 +1,2150 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Matt Wu + * Apr 26, 2001 + * Routines for control of ALi pci audio M5451 + * + * BUGS: + * -- + * + * TODO: + * -- + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Matt Wu "); +MODULE_DESCRIPTION("ALI M5451"); +MODULE_LICENSE("GPL"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static int pcm_channels = 32; +static bool spdif; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for ALI M5451 PCI Audio."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for ALI M5451 PCI Audio."); +module_param(pcm_channels, int, 0444); +MODULE_PARM_DESC(pcm_channels, "PCM Channels"); +module_param(spdif, bool, 0444); +MODULE_PARM_DESC(spdif, "Support SPDIF I/O"); + +/* just for backward compatibility */ +static bool enable; +module_param(enable, bool, 0444); + + +/* + * Constants definition + */ + +#define DEVICE_ID_ALI5451 ((PCI_VENDOR_ID_AL<<16)|PCI_DEVICE_ID_AL_M5451) + + +#define ALI_CHANNELS 32 + +#define ALI_PCM_IN_CHANNEL 31 +#define ALI_SPDIF_IN_CHANNEL 19 +#define ALI_SPDIF_OUT_CHANNEL 15 +#define ALI_CENTER_CHANNEL 24 +#define ALI_LEF_CHANNEL 23 +#define ALI_SURR_LEFT_CHANNEL 26 +#define ALI_SURR_RIGHT_CHANNEL 25 +#define ALI_MODEM_IN_CHANNEL 21 +#define ALI_MODEM_OUT_CHANNEL 20 + +#define SNDRV_ALI_VOICE_TYPE_PCM 01 +#define SNDRV_ALI_VOICE_TYPE_OTH 02 + +#define ALI_5451_V02 0x02 + +/* + * Direct Registers + */ + +#define ALI_LEGACY_DMAR0 0x00 /* ADR0 */ +#define ALI_LEGACY_DMAR4 0x04 /* CNT0 */ +#define ALI_LEGACY_DMAR11 0x0b /* MOD */ +#define ALI_LEGACY_DMAR15 0x0f /* MMR */ +#define ALI_MPUR0 0x20 +#define ALI_MPUR1 0x21 +#define ALI_MPUR2 0x22 +#define ALI_MPUR3 0x23 + +#define ALI_AC97_WRITE 0x40 +#define ALI_AC97_READ 0x44 + +#define ALI_SCTRL 0x48 +#define ALI_SPDIF_OUT_ENABLE 0x20 +#define ALI_SCTRL_LINE_IN2 (1 << 9) +#define ALI_SCTRL_GPIO_IN2 (1 << 13) +#define ALI_SCTRL_LINE_OUT_EN (1 << 20) +#define ALI_SCTRL_GPIO_OUT_EN (1 << 23) +#define ALI_SCTRL_CODEC1_READY (1 << 24) +#define ALI_SCTRL_CODEC2_READY (1 << 25) +#define ALI_AC97_GPIO 0x4c +#define ALI_AC97_GPIO_ENABLE 0x8000 +#define ALI_AC97_GPIO_DATA_SHIFT 16 +#define ALI_SPDIF_CS 0x70 +#define ALI_SPDIF_CTRL 0x74 +#define ALI_SPDIF_IN_FUNC_ENABLE 0x02 +#define ALI_SPDIF_IN_CH_STATUS 0x40 +#define ALI_SPDIF_OUT_CH_STATUS 0xbf +#define ALI_START 0x80 +#define ALI_STOP 0x84 +#define ALI_CSPF 0x90 +#define ALI_AINT 0x98 +#define ALI_GC_CIR 0xa0 + #define ENDLP_IE 0x00001000 + #define MIDLP_IE 0x00002000 +#define ALI_AINTEN 0xa4 +#define ALI_VOLUME 0xa8 +#define ALI_SBDELTA_DELTA_R 0xac +#define ALI_MISCINT 0xb0 + #define ADDRESS_IRQ 0x00000020 + #define TARGET_REACHED 0x00008000 + #define MIXER_OVERFLOW 0x00000800 + #define MIXER_UNDERFLOW 0x00000400 + #define GPIO_IRQ 0x01000000 +#define ALI_SBBL_SBCL 0xc0 +#define ALI_SBCTRL_SBE2R_SBDD 0xc4 +#define ALI_STIMER 0xc8 +#define ALI_GLOBAL_CONTROL 0xd4 +#define ALI_SPDIF_OUT_SEL_PCM 0x00000400 /* bit 10 */ +#define ALI_SPDIF_IN_SUPPORT 0x00000800 /* bit 11 */ +#define ALI_SPDIF_OUT_CH_ENABLE 0x00008000 /* bit 15 */ +#define ALI_SPDIF_IN_CH_ENABLE 0x00080000 /* bit 19 */ +#define ALI_PCM_IN_ENABLE 0x80000000 /* bit 31 */ + +#define ALI_CSO_ALPHA_FMS 0xe0 +#define ALI_LBA 0xe4 +#define ALI_ESO_DELTA 0xe8 +#define ALI_GVSEL_PAN_VOC_CTRL_EC 0xf0 +#define ALI_EBUF1 0xf4 +#define ALI_EBUF2 0xf8 + +#define ALI_REG(codec, x) ((codec)->port + x) + +#define MAX_CODECS 2 + + +struct snd_ali; +struct snd_ali_voice; + +struct snd_ali_channel_control { + /* register data */ + struct REGDATA { + unsigned int start; + unsigned int stop; + unsigned int aint; + unsigned int ainten; + } data; + + /* register addresses */ + struct REGS { + unsigned int start; + unsigned int stop; + unsigned int aint; + unsigned int ainten; + unsigned int ac97read; + unsigned int ac97write; + } regs; + +}; + +struct snd_ali_voice { + unsigned int number; + unsigned int use :1, + pcm :1, + midi :1, + mode :1, + synth :1, + running :1; + + /* PCM data */ + struct snd_ali *codec; + struct snd_pcm_substream *substream; + struct snd_ali_voice *extra; + + int eso; /* final ESO value for channel */ + int count; /* runtime->period_size */ + + /* --- */ + + void *private_data; + void (*private_free)(void *private_data); +}; + + +struct snd_alidev { + + struct snd_ali_voice voices[ALI_CHANNELS]; + + unsigned int chcnt; /* num of opened channels */ + unsigned int chmap; /* bitmap for opened channels */ + unsigned int synthcount; + +}; + + +#define ALI_GLOBAL_REGS 56 +#define ALI_CHANNEL_REGS 8 +struct snd_ali_image { + u32 regs[ALI_GLOBAL_REGS]; + u32 channel_regs[ALI_CHANNELS][ALI_CHANNEL_REGS]; +}; + + +struct snd_ali { + int irq; + unsigned long port; + unsigned char revision; + + unsigned int hw_initialized :1; + unsigned int spdif_support :1; + + struct pci_dev *pci; + struct pci_dev *pci_m1533; + struct pci_dev *pci_m7101; + + struct snd_card *card; + struct snd_pcm *pcm[MAX_CODECS]; + struct snd_alidev synth; + struct snd_ali_channel_control chregs; + + /* S/PDIF Mask */ + unsigned int spdif_mask; + + unsigned int spurious_irq_count; + unsigned int spurious_irq_max_delta; + + unsigned int num_of_codecs; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97[MAX_CODECS]; + unsigned short ac97_ext_id; + unsigned short ac97_ext_status; + + spinlock_t reg_lock; + spinlock_t voice_alloc; + + struct snd_ali_image image; +}; + +static const struct pci_device_id snd_ali_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M5451) }, + { } +}; +MODULE_DEVICE_TABLE(pci, snd_ali_ids); + +static void snd_ali_clear_voices(struct snd_ali *, unsigned int, unsigned int); +static unsigned short snd_ali_codec_peek(struct snd_ali *, int, unsigned short); +static void snd_ali_codec_poke(struct snd_ali *, int, unsigned short, + unsigned short); + +/* + * AC97 ACCESS + */ + +static inline unsigned int snd_ali_5451_peek(struct snd_ali *codec, + unsigned int port) +{ + return (unsigned int)inl(ALI_REG(codec, port)); +} + +static inline void snd_ali_5451_poke(struct snd_ali *codec, + unsigned int port, + unsigned int val) +{ + outl((unsigned int)val, ALI_REG(codec, port)); +} + +static int snd_ali_codec_ready(struct snd_ali *codec, + unsigned int port) +{ + unsigned long end_time; + unsigned int res; + + end_time = jiffies + msecs_to_jiffies(250); + + for (;;) { + res = snd_ali_5451_peek(codec,port); + if (!(res & 0x8000)) + return 0; + if (!time_after_eq(end_time, jiffies)) + break; + schedule_timeout_uninterruptible(1); + } + + snd_ali_5451_poke(codec, port, res & ~0x8000); + dev_dbg(codec->card->dev, "ali_codec_ready: codec is not ready.\n"); + return -EIO; +} + +static int snd_ali_stimer_ready(struct snd_ali *codec) +{ + unsigned long end_time; + unsigned long dwChk1,dwChk2; + + dwChk1 = snd_ali_5451_peek(codec, ALI_STIMER); + end_time = jiffies + msecs_to_jiffies(250); + + for (;;) { + dwChk2 = snd_ali_5451_peek(codec, ALI_STIMER); + if (dwChk2 != dwChk1) + return 0; + if (!time_after_eq(end_time, jiffies)) + break; + schedule_timeout_uninterruptible(1); + } + + dev_err(codec->card->dev, "ali_stimer_read: stimer is not ready.\n"); + return -EIO; +} + +static void snd_ali_codec_poke(struct snd_ali *codec,int secondary, + unsigned short reg, + unsigned short val) +{ + unsigned int dwVal; + unsigned int port; + + if (reg >= 0x80) { + dev_err(codec->card->dev, + "ali_codec_poke: reg(%xh) invalid.\n", reg); + return; + } + + port = codec->chregs.regs.ac97write; + + if (snd_ali_codec_ready(codec, port) < 0) + return; + if (snd_ali_stimer_ready(codec) < 0) + return; + + dwVal = (unsigned int) (reg & 0xff); + dwVal |= 0x8000 | (val << 16); + if (secondary) + dwVal |= 0x0080; + if (codec->revision == ALI_5451_V02) + dwVal |= 0x0100; + + snd_ali_5451_poke(codec, port, dwVal); + + return ; +} + +static unsigned short snd_ali_codec_peek(struct snd_ali *codec, + int secondary, + unsigned short reg) +{ + unsigned int dwVal; + unsigned int port; + + if (reg >= 0x80) { + dev_err(codec->card->dev, + "ali_codec_peek: reg(%xh) invalid.\n", reg); + return ~0; + } + + port = codec->chregs.regs.ac97read; + + if (snd_ali_codec_ready(codec, port) < 0) + return ~0; + if (snd_ali_stimer_ready(codec) < 0) + return ~0; + + dwVal = (unsigned int) (reg & 0xff); + dwVal |= 0x8000; /* bit 15*/ + if (secondary) + dwVal |= 0x0080; + + snd_ali_5451_poke(codec, port, dwVal); + + if (snd_ali_stimer_ready(codec) < 0) + return ~0; + if (snd_ali_codec_ready(codec, port) < 0) + return ~0; + + return (snd_ali_5451_peek(codec, port) & 0xffff0000) >> 16; +} + +static void snd_ali_codec_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val ) +{ + struct snd_ali *codec = ac97->private_data; + + dev_dbg(codec->card->dev, "codec_write: reg=%xh data=%xh.\n", reg, val); + if (reg == AC97_GPIO_STATUS) { + outl((val << ALI_AC97_GPIO_DATA_SHIFT) | ALI_AC97_GPIO_ENABLE, + ALI_REG(codec, ALI_AC97_GPIO)); + return; + } + snd_ali_codec_poke(codec, ac97->num, reg, val); + return ; +} + + +static unsigned short snd_ali_codec_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct snd_ali *codec = ac97->private_data; + + dev_dbg(codec->card->dev, "codec_read reg=%xh.\n", reg); + return snd_ali_codec_peek(codec, ac97->num, reg); +} + +/* + * AC97 Reset + */ + +static int snd_ali_reset_5451(struct snd_ali *codec) +{ + struct pci_dev *pci_dev; + unsigned short wCount, wReg; + unsigned int dwVal; + + pci_dev = codec->pci_m1533; + if (pci_dev) { + pci_read_config_dword(pci_dev, 0x7c, &dwVal); + pci_write_config_dword(pci_dev, 0x7c, dwVal | 0x08000000); + mdelay(5); + pci_read_config_dword(pci_dev, 0x7c, &dwVal); + pci_write_config_dword(pci_dev, 0x7c, dwVal & 0xf7ffffff); + mdelay(5); + } + + pci_dev = codec->pci; + pci_read_config_dword(pci_dev, 0x44, &dwVal); + pci_write_config_dword(pci_dev, 0x44, dwVal | 0x000c0000); + udelay(500); + pci_read_config_dword(pci_dev, 0x44, &dwVal); + pci_write_config_dword(pci_dev, 0x44, dwVal & 0xfffbffff); + mdelay(5); + + wCount = 200; + while(wCount--) { + wReg = snd_ali_codec_peek(codec, 0, AC97_POWERDOWN); + if ((wReg & 0x000f) == 0x000f) + return 0; + mdelay(5); + } + + /* non-fatal if you have a non PM capable codec */ + /* dev_warn(codec->card->dev, "ali5451: reset time out\n"); */ + return 0; +} + +/* + * ALI 5451 Controller + */ + +static void snd_ali_enable_special_channel(struct snd_ali *codec, + unsigned int channel) +{ + unsigned long dwVal; + + dwVal = inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)); + dwVal |= 1 << (channel & 0x0000001f); + outl(dwVal, ALI_REG(codec, ALI_GLOBAL_CONTROL)); +} + +static void snd_ali_disable_special_channel(struct snd_ali *codec, + unsigned int channel) +{ + unsigned long dwVal; + + dwVal = inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)); + dwVal &= ~(1 << (channel & 0x0000001f)); + outl(dwVal, ALI_REG(codec, ALI_GLOBAL_CONTROL)); +} + +static void snd_ali_enable_address_interrupt(struct snd_ali *codec) +{ + unsigned int gc; + + gc = inl(ALI_REG(codec, ALI_GC_CIR)); + gc |= ENDLP_IE; + gc |= MIDLP_IE; + outl( gc, ALI_REG(codec, ALI_GC_CIR)); +} + +static void snd_ali_disable_address_interrupt(struct snd_ali *codec) +{ + unsigned int gc; + + gc = inl(ALI_REG(codec, ALI_GC_CIR)); + gc &= ~ENDLP_IE; + gc &= ~MIDLP_IE; + outl(gc, ALI_REG(codec, ALI_GC_CIR)); +} + +static void snd_ali_disable_voice_irq(struct snd_ali *codec, + unsigned int channel) +{ + unsigned int mask; + struct snd_ali_channel_control *pchregs = &(codec->chregs); + + dev_dbg(codec->card->dev, "disable_voice_irq channel=%d\n", channel); + + mask = 1 << (channel & 0x1f); + pchregs->data.ainten = inl(ALI_REG(codec, pchregs->regs.ainten)); + pchregs->data.ainten &= ~mask; + outl(pchregs->data.ainten, ALI_REG(codec, pchregs->regs.ainten)); +} + +static int snd_ali_alloc_pcm_channel(struct snd_ali *codec, int channel) +{ + unsigned int idx = channel & 0x1f; + + if (codec->synth.chcnt >= ALI_CHANNELS){ + dev_err(codec->card->dev, + "ali_alloc_pcm_channel: no free channels.\n"); + return -1; + } + + if (!(codec->synth.chmap & (1 << idx))) { + codec->synth.chmap |= 1 << idx; + codec->synth.chcnt++; + dev_dbg(codec->card->dev, "alloc_pcm_channel no. %d.\n", idx); + return idx; + } + return -1; +} + +static int snd_ali_find_free_channel(struct snd_ali * codec, int rec) +{ + int idx; + int result = -1; + + dev_dbg(codec->card->dev, + "find_free_channel: for %s\n", rec ? "rec" : "pcm"); + + /* recording */ + if (rec) { + if (codec->spdif_support && + (inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)) & + ALI_SPDIF_IN_SUPPORT)) + idx = ALI_SPDIF_IN_CHANNEL; + else + idx = ALI_PCM_IN_CHANNEL; + + result = snd_ali_alloc_pcm_channel(codec, idx); + if (result >= 0) + return result; + else { + dev_err(codec->card->dev, + "ali_find_free_channel: record channel is busy now.\n"); + return -1; + } + } + + /* playback... */ + if (codec->spdif_support && + (inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)) & + ALI_SPDIF_OUT_CH_ENABLE)) { + idx = ALI_SPDIF_OUT_CHANNEL; + result = snd_ali_alloc_pcm_channel(codec, idx); + if (result >= 0) + return result; + else + dev_err(codec->card->dev, + "ali_find_free_channel: S/PDIF out channel is in busy now.\n"); + } + + for (idx = 0; idx < ALI_CHANNELS; idx++) { + result = snd_ali_alloc_pcm_channel(codec, idx); + if (result >= 0) + return result; + } + dev_err(codec->card->dev, "ali_find_free_channel: no free channels.\n"); + return -1; +} + +static void snd_ali_free_channel_pcm(struct snd_ali *codec, int channel) +{ + unsigned int idx = channel & 0x0000001f; + + dev_dbg(codec->card->dev, "free_channel_pcm channel=%d\n", channel); + + if (channel < 0 || channel >= ALI_CHANNELS) + return; + + if (!(codec->synth.chmap & (1 << idx))) { + dev_err(codec->card->dev, + "ali_free_channel_pcm: channel %d is not in use.\n", + channel); + return; + } else { + codec->synth.chmap &= ~(1 << idx); + codec->synth.chcnt--; + } +} + +static void snd_ali_stop_voice(struct snd_ali *codec, unsigned int channel) +{ + unsigned int mask = 1 << (channel & 0x1f); + + dev_dbg(codec->card->dev, "stop_voice: channel=%d\n", channel); + outl(mask, ALI_REG(codec, codec->chregs.regs.stop)); +} + +/* + * S/PDIF Part + */ + +static void snd_ali_delay(struct snd_ali *codec,int interval) +{ + unsigned long begintimer,currenttimer; + + begintimer = inl(ALI_REG(codec, ALI_STIMER)); + currenttimer = inl(ALI_REG(codec, ALI_STIMER)); + + while (currenttimer < begintimer + interval) { + if (snd_ali_stimer_ready(codec) < 0) + break; + currenttimer = inl(ALI_REG(codec, ALI_STIMER)); + cpu_relax(); + } +} + +static void snd_ali_detect_spdif_rate(struct snd_ali *codec) +{ + u16 wval; + u16 count = 0; + u8 bval, R1 = 0, R2; + + bval = inb(ALI_REG(codec, ALI_SPDIF_CTRL + 1)); + bval |= 0x1F; + outb(bval, ALI_REG(codec, ALI_SPDIF_CTRL + 1)); + + while ((R1 < 0x0b || R1 > 0x0e) && R1 != 0x12 && count <= 50000) { + count ++; + snd_ali_delay(codec, 6); + bval = inb(ALI_REG(codec, ALI_SPDIF_CTRL + 1)); + R1 = bval & 0x1F; + } + + if (count > 50000) { + dev_err(codec->card->dev, "ali_detect_spdif_rate: timeout!\n"); + return; + } + + for (count = 0; count <= 50000; count++) { + snd_ali_delay(codec, 6); + bval = inb(ALI_REG(codec,ALI_SPDIF_CTRL + 1)); + R2 = bval & 0x1F; + if (R2 != R1) + R1 = R2; + else + break; + } + + if (count > 50000) { + dev_err(codec->card->dev, "ali_detect_spdif_rate: timeout!\n"); + return; + } + + if (R2 >= 0x0b && R2 <= 0x0e) { + wval = inw(ALI_REG(codec, ALI_SPDIF_CTRL + 2)); + wval &= 0xe0f0; + wval |= (0x09 << 8) | 0x05; + outw(wval, ALI_REG(codec, ALI_SPDIF_CTRL + 2)); + + bval = inb(ALI_REG(codec, ALI_SPDIF_CS + 3)) & 0xf0; + outb(bval | 0x02, ALI_REG(codec, ALI_SPDIF_CS + 3)); + } else if (R2 == 0x12) { + wval = inw(ALI_REG(codec, ALI_SPDIF_CTRL + 2)); + wval &= 0xe0f0; + wval |= (0x0e << 8) | 0x08; + outw(wval, ALI_REG(codec, ALI_SPDIF_CTRL + 2)); + + bval = inb(ALI_REG(codec,ALI_SPDIF_CS + 3)) & 0xf0; + outb(bval | 0x03, ALI_REG(codec, ALI_SPDIF_CS + 3)); + } +} + +static unsigned int snd_ali_get_spdif_in_rate(struct snd_ali *codec) +{ + u32 dwRate; + u8 bval; + + bval = inb(ALI_REG(codec, ALI_SPDIF_CTRL)); + bval &= 0x7f; + bval |= 0x40; + outb(bval, ALI_REG(codec, ALI_SPDIF_CTRL)); + + snd_ali_detect_spdif_rate(codec); + + bval = inb(ALI_REG(codec, ALI_SPDIF_CS + 3)); + bval &= 0x0f; + + switch (bval) { + case 0: dwRate = 44100; break; + case 1: dwRate = 48000; break; + case 2: dwRate = 32000; break; + default: dwRate = 0; break; + } + + return dwRate; +} + +static void snd_ali_enable_spdif_in(struct snd_ali *codec) +{ + unsigned int dwVal; + + dwVal = inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)); + dwVal |= ALI_SPDIF_IN_SUPPORT; + outl(dwVal, ALI_REG(codec, ALI_GLOBAL_CONTROL)); + + dwVal = inb(ALI_REG(codec, ALI_SPDIF_CTRL)); + dwVal |= 0x02; + outb(dwVal, ALI_REG(codec, ALI_SPDIF_CTRL)); + + snd_ali_enable_special_channel(codec, ALI_SPDIF_IN_CHANNEL); +} + +static void snd_ali_disable_spdif_in(struct snd_ali *codec) +{ + unsigned int dwVal; + + dwVal = inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)); + dwVal &= ~ALI_SPDIF_IN_SUPPORT; + outl(dwVal, ALI_REG(codec, ALI_GLOBAL_CONTROL)); + + snd_ali_disable_special_channel(codec, ALI_SPDIF_IN_CHANNEL); +} + + +static void snd_ali_set_spdif_out_rate(struct snd_ali *codec, unsigned int rate) +{ + unsigned char bVal; + unsigned int dwRate; + + switch (rate) { + case 32000: dwRate = 0x300; break; + case 48000: dwRate = 0x200; break; + default: dwRate = 0; break; + } + + bVal = inb(ALI_REG(codec, ALI_SPDIF_CTRL)); + bVal &= (unsigned char)(~(1<<6)); + + bVal |= 0x80; /* select right */ + outb(bVal, ALI_REG(codec, ALI_SPDIF_CTRL)); + outb(dwRate | 0x20, ALI_REG(codec, ALI_SPDIF_CS + 2)); + + bVal &= ~0x80; /* select left */ + outb(bVal, ALI_REG(codec, ALI_SPDIF_CTRL)); + outw(rate | 0x10, ALI_REG(codec, ALI_SPDIF_CS + 2)); +} + +static void snd_ali_enable_spdif_out(struct snd_ali *codec) +{ + unsigned short wVal; + unsigned char bVal; + struct pci_dev *pci_dev; + + pci_dev = codec->pci_m1533; + if (pci_dev == NULL) + return; + pci_read_config_byte(pci_dev, 0x61, &bVal); + bVal |= 0x40; + pci_write_config_byte(pci_dev, 0x61, bVal); + pci_read_config_byte(pci_dev, 0x7d, &bVal); + bVal |= 0x01; + pci_write_config_byte(pci_dev, 0x7d, bVal); + + pci_read_config_byte(pci_dev, 0x7e, &bVal); + bVal &= (~0x20); + bVal |= 0x10; + pci_write_config_byte(pci_dev, 0x7e, bVal); + + bVal = inb(ALI_REG(codec, ALI_SCTRL)); + outb(bVal | ALI_SPDIF_OUT_ENABLE, ALI_REG(codec, ALI_SCTRL)); + + bVal = inb(ALI_REG(codec, ALI_SPDIF_CTRL)); + outb(bVal & ALI_SPDIF_OUT_CH_STATUS, ALI_REG(codec, ALI_SPDIF_CTRL)); + + wVal = inw(ALI_REG(codec, ALI_GLOBAL_CONTROL)); + wVal |= ALI_SPDIF_OUT_SEL_PCM; + outw(wVal, ALI_REG(codec, ALI_GLOBAL_CONTROL)); + snd_ali_disable_special_channel(codec, ALI_SPDIF_OUT_CHANNEL); +} + +static void snd_ali_enable_spdif_chnout(struct snd_ali *codec) +{ + unsigned short wVal; + + wVal = inw(ALI_REG(codec, ALI_GLOBAL_CONTROL)); + wVal &= ~ALI_SPDIF_OUT_SEL_PCM; + outw(wVal, ALI_REG(codec, ALI_GLOBAL_CONTROL)); +/* + wVal = inw(ALI_REG(codec, ALI_SPDIF_CS)); + if (flag & ALI_SPDIF_OUT_NON_PCM) + wVal |= 0x0002; + else + wVal &= (~0x0002); + outw(wVal, ALI_REG(codec, ALI_SPDIF_CS)); +*/ + snd_ali_enable_special_channel(codec, ALI_SPDIF_OUT_CHANNEL); +} + +static void snd_ali_disable_spdif_chnout(struct snd_ali *codec) +{ + unsigned short wVal; + + wVal = inw(ALI_REG(codec, ALI_GLOBAL_CONTROL)); + wVal |= ALI_SPDIF_OUT_SEL_PCM; + outw(wVal, ALI_REG(codec, ALI_GLOBAL_CONTROL)); + + snd_ali_enable_special_channel(codec, ALI_SPDIF_OUT_CHANNEL); +} + +static void snd_ali_disable_spdif_out(struct snd_ali *codec) +{ + unsigned char bVal; + + bVal = inb(ALI_REG(codec, ALI_SCTRL)); + outb(bVal & ~ALI_SPDIF_OUT_ENABLE, ALI_REG(codec, ALI_SCTRL)); + + snd_ali_disable_spdif_chnout(codec); +} + +static void snd_ali_update_ptr(struct snd_ali *codec, int channel) +{ + struct snd_ali_voice *pvoice; + struct snd_ali_channel_control *pchregs; + unsigned int old, mask; + + pchregs = &(codec->chregs); + + /* check if interrupt occurred for channel */ + old = pchregs->data.aint; + mask = 1U << (channel & 0x1f); + + if (!(old & mask)) + return; + + pvoice = &codec->synth.voices[channel]; + + udelay(100); + spin_lock(&codec->reg_lock); + + if (pvoice->pcm && pvoice->substream) { + /* pcm interrupt */ + if (pvoice->running) { + dev_dbg(codec->card->dev, + "update_ptr: cso=%4.4x cspf=%d.\n", + inw(ALI_REG(codec, ALI_CSO_ALPHA_FMS + 2)), + (inl(ALI_REG(codec, ALI_CSPF)) & mask) == mask); + spin_unlock(&codec->reg_lock); + snd_pcm_period_elapsed(pvoice->substream); + spin_lock(&codec->reg_lock); + } else { + snd_ali_stop_voice(codec, channel); + snd_ali_disable_voice_irq(codec, channel); + } + } else if (codec->synth.voices[channel].synth) { + /* synth interrupt */ + } else if (codec->synth.voices[channel].midi) { + /* midi interrupt */ + } else { + /* unknown interrupt */ + snd_ali_stop_voice(codec, channel); + snd_ali_disable_voice_irq(codec, channel); + } + spin_unlock(&codec->reg_lock); + outl(mask,ALI_REG(codec,pchregs->regs.aint)); + pchregs->data.aint = old & (~mask); +} + +static irqreturn_t snd_ali_card_interrupt(int irq, void *dev_id) +{ + struct snd_ali *codec = dev_id; + int channel; + unsigned int audio_int; + struct snd_ali_channel_control *pchregs; + + if (codec == NULL || !codec->hw_initialized) + return IRQ_NONE; + + audio_int = inl(ALI_REG(codec, ALI_MISCINT)); + if (!audio_int) + return IRQ_NONE; + + pchregs = &(codec->chregs); + if (audio_int & ADDRESS_IRQ) { + /* get interrupt status for all channels */ + pchregs->data.aint = inl(ALI_REG(codec, pchregs->regs.aint)); + for (channel = 0; channel < ALI_CHANNELS; channel++) + snd_ali_update_ptr(codec, channel); + } + outl((TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), + ALI_REG(codec, ALI_MISCINT)); + + return IRQ_HANDLED; +} + + +static struct snd_ali_voice *snd_ali_alloc_voice(struct snd_ali * codec, + int type, int rec, int channel) +{ + struct snd_ali_voice *pvoice; + int idx; + + dev_dbg(codec->card->dev, "alloc_voice: type=%d rec=%d\n", type, rec); + + guard(spinlock_irq)(&codec->voice_alloc); + if (type == SNDRV_ALI_VOICE_TYPE_PCM) { + idx = channel > 0 ? snd_ali_alloc_pcm_channel(codec, channel) : + snd_ali_find_free_channel(codec,rec); + if (idx < 0) { + dev_err(codec->card->dev, "ali_alloc_voice: err.\n"); + return NULL; + } + pvoice = &(codec->synth.voices[idx]); + pvoice->codec = codec; + pvoice->use = 1; + pvoice->pcm = 1; + pvoice->mode = rec; + return pvoice; + } + return NULL; +} + + +static void snd_ali_free_voice(struct snd_ali * codec, + struct snd_ali_voice *pvoice) +{ + void (*private_free)(void *); + void *private_data; + + dev_dbg(codec->card->dev, "free_voice: channel=%d\n", pvoice->number); + if (!pvoice->use) + return; + snd_ali_clear_voices(codec, pvoice->number, pvoice->number); + scoped_guard(spinlock_irq, &codec->voice_alloc) { + private_free = pvoice->private_free; + private_data = pvoice->private_data; + pvoice->private_free = NULL; + pvoice->private_data = NULL; + if (pvoice->pcm) + snd_ali_free_channel_pcm(codec, pvoice->number); + pvoice->use = pvoice->pcm = pvoice->synth = 0; + pvoice->substream = NULL; + } + if (private_free) + private_free(private_data); +} + + +static void snd_ali_clear_voices(struct snd_ali *codec, + unsigned int v_min, + unsigned int v_max) +{ + unsigned int i; + + for (i = v_min; i <= v_max; i++) { + snd_ali_stop_voice(codec, i); + snd_ali_disable_voice_irq(codec, i); + } +} + +static void snd_ali_write_voice_regs(struct snd_ali *codec, + unsigned int Channel, + unsigned int LBA, + unsigned int CSO, + unsigned int ESO, + unsigned int DELTA, + unsigned int ALPHA_FMS, + unsigned int GVSEL, + unsigned int PAN, + unsigned int VOL, + unsigned int CTRL, + unsigned int EC) +{ + unsigned int ctlcmds[4]; + + outb((unsigned char)(Channel & 0x001f), ALI_REG(codec, ALI_GC_CIR)); + + ctlcmds[0] = (CSO << 16) | (ALPHA_FMS & 0x0000ffff); + ctlcmds[1] = LBA; + ctlcmds[2] = (ESO << 16) | (DELTA & 0x0ffff); + ctlcmds[3] = (GVSEL << 31) | + ((PAN & 0x0000007f) << 24) | + ((VOL & 0x000000ff) << 16) | + ((CTRL & 0x0000000f) << 12) | + (EC & 0x00000fff); + + outb(Channel, ALI_REG(codec, ALI_GC_CIR)); + + outl(ctlcmds[0], ALI_REG(codec, ALI_CSO_ALPHA_FMS)); + outl(ctlcmds[1], ALI_REG(codec, ALI_LBA)); + outl(ctlcmds[2], ALI_REG(codec, ALI_ESO_DELTA)); + outl(ctlcmds[3], ALI_REG(codec, ALI_GVSEL_PAN_VOC_CTRL_EC)); + + outl(0x30000000, ALI_REG(codec, ALI_EBUF1)); /* Still Mode */ + outl(0x30000000, ALI_REG(codec, ALI_EBUF2)); /* Still Mode */ +} + +static unsigned int snd_ali_convert_rate(unsigned int rate, int rec) +{ + unsigned int delta; + + if (rate < 4000) + rate = 4000; + if (rate > 48000) + rate = 48000; + + if (rec) { + if (rate == 44100) + delta = 0x116a; + else if (rate == 8000) + delta = 0x6000; + else if (rate == 48000) + delta = 0x1000; + else + delta = ((48000 << 12) / rate) & 0x0000ffff; + } else { + if (rate == 44100) + delta = 0xeb3; + else if (rate == 8000) + delta = 0x2ab; + else if (rate == 48000) + delta = 0x1000; + else + delta = (((rate << 12) + rate) / 48000) & 0x0000ffff; + } + + return delta; +} + +static unsigned int snd_ali_control_mode(struct snd_pcm_substream *substream) +{ + unsigned int CTRL; + struct snd_pcm_runtime *runtime = substream->runtime; + + /* set ctrl mode + CTRL default: 8-bit (unsigned) mono, loop mode enabled + */ + CTRL = 0x00000001; + if (snd_pcm_format_width(runtime->format) == 16) + CTRL |= 0x00000008; /* 16-bit data */ + if (!snd_pcm_format_unsigned(runtime->format)) + CTRL |= 0x00000002; /* signed data */ + if (runtime->channels > 1) + CTRL |= 0x00000004; /* stereo data */ + return CTRL; +} + +/* + * PCM part + */ + +static int snd_ali_trigger(struct snd_pcm_substream *substream, + int cmd) + +{ + struct snd_ali *codec = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + unsigned int what, whati; + struct snd_ali_voice *pvoice, *evoice; + unsigned int val; + 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 = whati = 0; + snd_pcm_group_for_each_entry(s, substream) { + if ((struct snd_ali *) snd_pcm_substream_chip(s) == codec) { + pvoice = s->runtime->private_data; + evoice = pvoice->extra; + what |= 1 << (pvoice->number & 0x1f); + if (evoice == NULL) + whati |= 1 << (pvoice->number & 0x1f); + else { + whati |= 1 << (evoice->number & 0x1f); + what |= 1 << (evoice->number & 0x1f); + } + if (do_start) { + pvoice->running = 1; + if (evoice != NULL) + evoice->running = 1; + } else { + pvoice->running = 0; + if (evoice != NULL) + evoice->running = 0; + } + snd_pcm_trigger_done(s, substream); + } + } + guard(spinlock)(&codec->reg_lock); + if (!do_start) + outl(what, ALI_REG(codec, ALI_STOP)); + val = inl(ALI_REG(codec, ALI_AINTEN)); + if (do_start) + val |= whati; + else + val &= ~whati; + outl(val, ALI_REG(codec, ALI_AINTEN)); + if (do_start) + outl(what, ALI_REG(codec, ALI_START)); + dev_dbg(codec->card->dev, "trigger: what=%xh whati=%xh\n", what, whati); + + return 0; +} + +static int snd_ali_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ali *codec = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ali_voice *pvoice = runtime->private_data; + struct snd_ali_voice *evoice = pvoice->extra; + + /* voice management */ + + if (params_buffer_size(hw_params) / 2 != + params_period_size(hw_params)) { + if (!evoice) { + evoice = snd_ali_alloc_voice(codec, + SNDRV_ALI_VOICE_TYPE_PCM, + 0, -1); + if (!evoice) + return -ENOMEM; + pvoice->extra = evoice; + evoice->substream = substream; + } + } else { + if (evoice) { + snd_ali_free_voice(codec, evoice); + pvoice->extra = evoice = NULL; + } + } + + return 0; +} + +static int snd_ali_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ali *codec = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ali_voice *pvoice = runtime->private_data; + struct snd_ali_voice *evoice = pvoice ? pvoice->extra : NULL; + + if (evoice) { + snd_ali_free_voice(codec, evoice); + pvoice->extra = NULL; + } + return 0; +} + +static int snd_ali_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ali *codec = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ali_voice *pvoice = runtime->private_data; + struct snd_ali_voice *evoice = pvoice->extra; + + unsigned int LBA; + unsigned int Delta; + unsigned int ESO; + unsigned int CTRL; + unsigned int GVSEL; + unsigned int PAN; + unsigned int VOL; + unsigned int EC; + + dev_dbg(codec->card->dev, "playback_prepare ...\n"); + + guard(spinlock_irq)(&codec->reg_lock); + + /* set Delta (rate) value */ + Delta = snd_ali_convert_rate(runtime->rate, 0); + + if (pvoice->number == ALI_SPDIF_IN_CHANNEL || + pvoice->number == ALI_PCM_IN_CHANNEL) + snd_ali_disable_special_channel(codec, pvoice->number); + else if (codec->spdif_support && + (inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)) & + ALI_SPDIF_OUT_CH_ENABLE) + && pvoice->number == ALI_SPDIF_OUT_CHANNEL) { + snd_ali_set_spdif_out_rate(codec, runtime->rate); + Delta = 0x1000; + } + + /* set Loop Back Address */ + LBA = runtime->dma_addr; + + /* set interrupt count size */ + pvoice->count = runtime->period_size; + + /* set target ESO for channel */ + pvoice->eso = runtime->buffer_size; + + dev_dbg(codec->card->dev, "playback_prepare: eso=%xh count=%xh\n", + pvoice->eso, pvoice->count); + + /* set ESO to capture first MIDLP interrupt */ + ESO = pvoice->eso -1; + /* set ctrl mode */ + CTRL = snd_ali_control_mode(substream); + + GVSEL = 1; + PAN = 0; + VOL = 0; + EC = 0; + dev_dbg(codec->card->dev, "playback_prepare:\n"); + dev_dbg(codec->card->dev, + "ch=%d, Rate=%d Delta=%xh,GVSEL=%xh,PAN=%xh,CTRL=%xh\n", + pvoice->number,runtime->rate,Delta,GVSEL,PAN,CTRL); + snd_ali_write_voice_regs(codec, + pvoice->number, + LBA, + 0, /* cso */ + ESO, + Delta, + 0, /* alpha */ + GVSEL, + PAN, + VOL, + CTRL, + EC); + if (evoice) { + evoice->count = pvoice->count; + evoice->eso = pvoice->count << 1; + ESO = evoice->eso - 1; + snd_ali_write_voice_regs(codec, + evoice->number, + LBA, + 0, /* cso */ + ESO, + Delta, + 0, /* alpha */ + GVSEL, + 0x7f, + 0x3ff, + CTRL, + EC); + } + return 0; +} + + +static int snd_ali_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ali *codec = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ali_voice *pvoice = runtime->private_data; + unsigned int LBA; + unsigned int Delta; + unsigned int ESO; + unsigned int CTRL; + unsigned int GVSEL; + unsigned int PAN; + unsigned int VOL; + unsigned int EC; + u8 bValue; + + spin_lock_irq(&codec->reg_lock); + + dev_dbg(codec->card->dev, "ali_prepare...\n"); + + snd_ali_enable_special_channel(codec,pvoice->number); + + Delta = (pvoice->number == ALI_MODEM_IN_CHANNEL || + pvoice->number == ALI_MODEM_OUT_CHANNEL) ? + 0x1000 : snd_ali_convert_rate(runtime->rate, pvoice->mode); + + /* Prepare capture intr channel */ + if (pvoice->number == ALI_SPDIF_IN_CHANNEL) { + + unsigned int rate; + + spin_unlock_irq(&codec->reg_lock); + if (codec->revision != ALI_5451_V02) + return -1; + + rate = snd_ali_get_spdif_in_rate(codec); + if (rate == 0) { + dev_warn(codec->card->dev, + "ali_capture_prepare: spdif rate detect err!\n"); + rate = 48000; + } + spin_lock_irq(&codec->reg_lock); + bValue = inb(ALI_REG(codec,ALI_SPDIF_CTRL)); + if (bValue & 0x10) { + outb(bValue,ALI_REG(codec,ALI_SPDIF_CTRL)); + dev_warn(codec->card->dev, + "clear SPDIF parity error flag.\n"); + } + + if (rate != 48000) + Delta = ((rate << 12) / runtime->rate) & 0x00ffff; + } + + /* set target ESO for channel */ + pvoice->eso = runtime->buffer_size; + + /* set interrupt count size */ + pvoice->count = runtime->period_size; + + /* set Loop Back Address */ + LBA = runtime->dma_addr; + + /* set ESO to capture first MIDLP interrupt */ + ESO = pvoice->eso - 1; + CTRL = snd_ali_control_mode(substream); + GVSEL = 0; + PAN = 0x00; + VOL = 0x00; + EC = 0; + + snd_ali_write_voice_regs( codec, + pvoice->number, + LBA, + 0, /* cso */ + ESO, + Delta, + 0, /* alpha */ + GVSEL, + PAN, + VOL, + CTRL, + EC); + + spin_unlock_irq(&codec->reg_lock); + + return 0; +} + + +static snd_pcm_uframes_t +snd_ali_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ali *codec = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ali_voice *pvoice = runtime->private_data; + unsigned int cso; + + guard(spinlock)(&codec->reg_lock); + if (!pvoice->running) + return 0; + outb(pvoice->number, ALI_REG(codec, ALI_GC_CIR)); + cso = inw(ALI_REG(codec, ALI_CSO_ALPHA_FMS + 2)); + dev_dbg(codec->card->dev, "playback pointer returned cso=%xh.\n", cso); + + cso %= runtime->buffer_size; + return cso; +} + + +static snd_pcm_uframes_t snd_ali_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ali *codec = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ali_voice *pvoice = runtime->private_data; + unsigned int cso; + + guard(spinlock)(&codec->reg_lock); + if (!pvoice->running) + return 0; + outb(pvoice->number, ALI_REG(codec, ALI_GC_CIR)); + cso = inw(ALI_REG(codec, ALI_CSO_ALPHA_FMS + 2)); + + cso %= runtime->buffer_size; + return cso; +} + +static const struct snd_pcm_hardware snd_ali_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S8 | 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 = (256*1024), + .period_bytes_min = 64, + .period_bytes_max = (256*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + * Capture support device description + */ + +static const struct snd_pcm_hardware snd_ali_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S8 | 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 = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static void snd_ali_pcm_free_substream(struct snd_pcm_runtime *runtime) +{ + struct snd_ali_voice *pvoice = runtime->private_data; + + if (pvoice) + snd_ali_free_voice(pvoice->codec, pvoice); +} + +static int snd_ali_open(struct snd_pcm_substream *substream, int rec, + int channel, const struct snd_pcm_hardware *phw) +{ + struct snd_ali *codec = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ali_voice *pvoice; + + pvoice = snd_ali_alloc_voice(codec, SNDRV_ALI_VOICE_TYPE_PCM, rec, + channel); + if (!pvoice) + return -EAGAIN; + + pvoice->substream = substream; + runtime->private_data = pvoice; + runtime->private_free = snd_ali_pcm_free_substream; + + runtime->hw = *phw; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + 0, 64*1024); + return 0; +} + +static int snd_ali_playback_open(struct snd_pcm_substream *substream) +{ + return snd_ali_open(substream, 0, -1, &snd_ali_playback); +} + +static int snd_ali_capture_open(struct snd_pcm_substream *substream) +{ + return snd_ali_open(substream, 1, -1, &snd_ali_capture); +} + +static int snd_ali_playback_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int snd_ali_close(struct snd_pcm_substream *substream) +{ + struct snd_ali *codec = snd_pcm_substream_chip(substream); + struct snd_ali_voice *pvoice = substream->runtime->private_data; + + snd_ali_disable_special_channel(codec,pvoice->number); + + return 0; +} + +static const struct snd_pcm_ops snd_ali_playback_ops = { + .open = snd_ali_playback_open, + .close = snd_ali_playback_close, + .hw_params = snd_ali_playback_hw_params, + .hw_free = snd_ali_playback_hw_free, + .prepare = snd_ali_playback_prepare, + .trigger = snd_ali_trigger, + .pointer = snd_ali_playback_pointer, +}; + +static const struct snd_pcm_ops snd_ali_capture_ops = { + .open = snd_ali_capture_open, + .close = snd_ali_close, + .prepare = snd_ali_prepare, + .trigger = snd_ali_trigger, + .pointer = snd_ali_pointer, +}; + +/* + * Modem PCM + */ + +static int snd_ali_modem_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ali *chip = snd_pcm_substream_chip(substream); + unsigned int modem_num = chip->num_of_codecs - 1; + snd_ac97_write(chip->ac97[modem_num], AC97_LINE1_RATE, + params_rate(hw_params)); + snd_ac97_write(chip->ac97[modem_num], AC97_LINE1_LEVEL, 0); + return 0; +} + +static const struct snd_pcm_hardware snd_ali_modem = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000), + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = (256*1024), + .period_bytes_min = 64, + .period_bytes_max = (256*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_ali_modem_open(struct snd_pcm_substream *substream, int rec, + int channel) +{ + static const unsigned int rates[] = {8000, 9600, 12000, 16000}; + static const struct snd_pcm_hw_constraint_list hw_constraint_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + int err = snd_ali_open(substream, rec, channel, &snd_ali_modem); + + if (err) + return err; + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &hw_constraint_rates); +} + +static int snd_ali_modem_playback_open(struct snd_pcm_substream *substream) +{ + return snd_ali_modem_open(substream, 0, ALI_MODEM_OUT_CHANNEL); +} + +static int snd_ali_modem_capture_open(struct snd_pcm_substream *substream) +{ + return snd_ali_modem_open(substream, 1, ALI_MODEM_IN_CHANNEL); +} + +static const struct snd_pcm_ops snd_ali_modem_playback_ops = { + .open = snd_ali_modem_playback_open, + .close = snd_ali_close, + .hw_params = snd_ali_modem_hw_params, + .prepare = snd_ali_prepare, + .trigger = snd_ali_trigger, + .pointer = snd_ali_pointer, +}; + +static const struct snd_pcm_ops snd_ali_modem_capture_ops = { + .open = snd_ali_modem_capture_open, + .close = snd_ali_close, + .hw_params = snd_ali_modem_hw_params, + .prepare = snd_ali_prepare, + .trigger = snd_ali_trigger, + .pointer = snd_ali_pointer, +}; + + +struct ali_pcm_description { + char *name; + unsigned int playback_num; + unsigned int capture_num; + const struct snd_pcm_ops *playback_ops; + const struct snd_pcm_ops *capture_ops; + unsigned short class; +}; + + +static void snd_ali_pcm_free(struct snd_pcm *pcm) +{ + struct snd_ali *codec = pcm->private_data; + codec->pcm[pcm->device] = NULL; +} + + +static int snd_ali_pcm(struct snd_ali *codec, int device, + struct ali_pcm_description *desc) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(codec->card, desc->name, device, + desc->playback_num, desc->capture_num, &pcm); + if (err < 0) { + dev_err(codec->card->dev, + "snd_ali_pcm: err called snd_pcm_new.\n"); + return err; + } + pcm->private_data = codec; + pcm->private_free = snd_ali_pcm_free; + if (desc->playback_ops) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + desc->playback_ops); + if (desc->capture_ops) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + desc->capture_ops); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &codec->pci->dev, 64*1024, 128*1024); + + pcm->info_flags = 0; + pcm->dev_class = desc->class; + pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + strscpy(pcm->name, desc->name); + codec->pcm[0] = pcm; + return 0; +} + +static struct ali_pcm_description ali_pcms[] = { + { .name = "ALI 5451", + .playback_num = ALI_CHANNELS, + .capture_num = 1, + .playback_ops = &snd_ali_playback_ops, + .capture_ops = &snd_ali_capture_ops + }, + { .name = "ALI 5451 modem", + .playback_num = 1, + .capture_num = 1, + .playback_ops = &snd_ali_modem_playback_ops, + .capture_ops = &snd_ali_modem_capture_ops, + .class = SNDRV_PCM_CLASS_MODEM + } +}; + +static int snd_ali_build_pcms(struct snd_ali *codec) +{ + int i, err; + for (i = 0; i < codec->num_of_codecs && i < ARRAY_SIZE(ali_pcms); i++) { + err = snd_ali_pcm(codec, i, &ali_pcms[i]); + if (err < 0) + return err; + } + return 0; +} + + +#define ALI5451_SPDIF(xname, xindex, value) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ +.info = snd_ali5451_spdif_info, .get = snd_ali5451_spdif_get, \ +.put = snd_ali5451_spdif_put, .private_value = value} + +#define snd_ali5451_spdif_info snd_ctl_boolean_mono_info + +static int snd_ali5451_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ali *codec = snd_kcontrol_chip(kcontrol); + unsigned int spdif_enable; + + spdif_enable = ucontrol->value.integer.value[0] ? 1 : 0; + + guard(spinlock_irq)(&codec->reg_lock); + switch (kcontrol->private_value) { + case 0: + spdif_enable = (codec->spdif_mask & 0x02) ? 1 : 0; + break; + case 1: + spdif_enable = ((codec->spdif_mask & 0x02) && + (codec->spdif_mask & 0x04)) ? 1 : 0; + break; + case 2: + spdif_enable = (codec->spdif_mask & 0x01) ? 1 : 0; + break; + default: + break; + } + ucontrol->value.integer.value[0] = spdif_enable; + return 0; +} + +static int snd_ali5451_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ali *codec = snd_kcontrol_chip(kcontrol); + unsigned int change = 0, spdif_enable = 0; + + spdif_enable = ucontrol->value.integer.value[0] ? 1 : 0; + + guard(spinlock_irq)(&codec->reg_lock); + switch (kcontrol->private_value) { + case 0: + change = (codec->spdif_mask & 0x02) ? 1 : 0; + change = change ^ spdif_enable; + if (change) { + if (spdif_enable) { + codec->spdif_mask |= 0x02; + snd_ali_enable_spdif_out(codec); + } else { + codec->spdif_mask &= ~(0x02); + codec->spdif_mask &= ~(0x04); + snd_ali_disable_spdif_out(codec); + } + } + break; + case 1: + change = (codec->spdif_mask & 0x04) ? 1 : 0; + change = change ^ spdif_enable; + if (change && (codec->spdif_mask & 0x02)) { + if (spdif_enable) { + codec->spdif_mask |= 0x04; + snd_ali_enable_spdif_chnout(codec); + } else { + codec->spdif_mask &= ~(0x04); + snd_ali_disable_spdif_chnout(codec); + } + } + break; + case 2: + change = (codec->spdif_mask & 0x01) ? 1 : 0; + change = change ^ spdif_enable; + if (change) { + if (spdif_enable) { + codec->spdif_mask |= 0x01; + snd_ali_enable_spdif_in(codec); + } else { + codec->spdif_mask &= ~(0x01); + snd_ali_disable_spdif_in(codec); + } + } + break; + default: + break; + } + + return change; +} + +static const struct snd_kcontrol_new snd_ali5451_mixer_spdif[] = { + /* spdif aplayback switch */ + /* FIXME: "IEC958 Playback Switch" may conflict with one on ac97_codec */ + ALI5451_SPDIF(SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH), 0, 0), + /* spdif out to spdif channel */ + ALI5451_SPDIF(SNDRV_CTL_NAME_IEC958("Channel Output ",NONE,SWITCH), 0, 1), + /* spdif in from spdif channel */ + ALI5451_SPDIF(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), 0, 2) +}; + +static int snd_ali_mixer(struct snd_ali *codec) +{ + struct snd_ac97_template ac97; + unsigned int idx; + int i, err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_ali_codec_write, + .read = snd_ali_codec_read, + }; + + err = snd_ac97_bus(codec->card, 0, &ops, codec, &codec->ac97_bus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = codec; + + for (i = 0; i < codec->num_of_codecs; i++) { + ac97.num = i; + err = snd_ac97_mixer(codec->ac97_bus, &ac97, &codec->ac97[i]); + if (err < 0) { + dev_err(codec->card->dev, + "ali mixer %d creating error.\n", i); + if (i == 0) + return err; + codec->num_of_codecs = 1; + break; + } + } + + if (codec->spdif_support) { + for (idx = 0; idx < ARRAY_SIZE(snd_ali5451_mixer_spdif); idx++) { + err = snd_ctl_add(codec->card, + snd_ctl_new1(&snd_ali5451_mixer_spdif[idx], codec)); + if (err < 0) + return err; + } + } + return 0; +} + +static int ali_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ali *chip = card->private_data; + struct snd_ali_image *im = &chip->image; + int i, j; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + for (i = 0; i < chip->num_of_codecs; i++) + snd_ac97_suspend(chip->ac97[i]); + + guard(spinlock_irq)(&chip->reg_lock); + + im->regs[ALI_MISCINT >> 2] = inl(ALI_REG(chip, ALI_MISCINT)); + /* im->regs[ALI_START >> 2] = inl(ALI_REG(chip, ALI_START)); */ + im->regs[ALI_STOP >> 2] = inl(ALI_REG(chip, ALI_STOP)); + + /* disable all IRQ bits */ + outl(0, ALI_REG(chip, ALI_MISCINT)); + + for (i = 0; i < ALI_GLOBAL_REGS; i++) { + if ((i*4 == ALI_MISCINT) || (i*4 == ALI_STOP)) + continue; + im->regs[i] = inl(ALI_REG(chip, i*4)); + } + + for (i = 0; i < ALI_CHANNELS; i++) { + outb(i, ALI_REG(chip, ALI_GC_CIR)); + for (j = 0; j < ALI_CHANNEL_REGS; j++) + im->channel_regs[i][j] = inl(ALI_REG(chip, j*4 + 0xe0)); + } + + /* stop all HW channel */ + outl(0xffffffff, ALI_REG(chip, ALI_STOP)); + + return 0; +} + +static int ali_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ali *chip = card->private_data; + struct snd_ali_image *im = &chip->image; + int i, j; + + scoped_guard(spinlock_irq, &chip->reg_lock) { + for (i = 0; i < ALI_CHANNELS; i++) { + outb(i, ALI_REG(chip, ALI_GC_CIR)); + for (j = 0; j < ALI_CHANNEL_REGS; j++) + outl(im->channel_regs[i][j], ALI_REG(chip, j*4 + 0xe0)); + } + + for (i = 0; i < ALI_GLOBAL_REGS; i++) { + if ((i*4 == ALI_MISCINT) || (i*4 == ALI_STOP) || + (i*4 == ALI_START)) + continue; + outl(im->regs[i], ALI_REG(chip, i*4)); + } + + /* start HW channel */ + outl(im->regs[ALI_START >> 2], ALI_REG(chip, ALI_START)); + /* restore IRQ enable bits */ + outl(im->regs[ALI_MISCINT >> 2], ALI_REG(chip, ALI_MISCINT)); + } + + for (i = 0 ; i < chip->num_of_codecs; i++) + snd_ac97_resume(chip->ac97[i]); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ali_pm, ali_suspend, ali_resume); + +static void snd_ali_free(struct snd_card *card) +{ + struct snd_ali *codec = card->private_data; + + if (codec->hw_initialized) + snd_ali_disable_address_interrupt(codec); + pci_dev_put(codec->pci_m1533); + pci_dev_put(codec->pci_m7101); +} + +static int snd_ali_chip_init(struct snd_ali *codec) +{ + unsigned int legacy; + unsigned char temp; + struct pci_dev *pci_dev; + + dev_dbg(codec->card->dev, "chip initializing ...\n"); + + if (snd_ali_reset_5451(codec)) { + dev_err(codec->card->dev, "ali_chip_init: reset 5451 error.\n"); + return -1; + } + + if (codec->revision == ALI_5451_V02) { + pci_dev = codec->pci_m1533; + pci_read_config_byte(pci_dev, 0x59, &temp); + temp |= 0x80; + pci_write_config_byte(pci_dev, 0x59, temp); + + pci_dev = codec->pci_m7101; + pci_read_config_byte(pci_dev, 0xb8, &temp); + temp |= 0x20; + pci_write_config_byte(pci_dev, 0xB8, temp); + } + + pci_read_config_dword(codec->pci, 0x44, &legacy); + legacy &= 0xff00ff00; + legacy |= 0x000800aa; + pci_write_config_dword(codec->pci, 0x44, legacy); + + outl(0x80000001, ALI_REG(codec, ALI_GLOBAL_CONTROL)); + outl(0x00000000, ALI_REG(codec, ALI_AINTEN)); + outl(0xffffffff, ALI_REG(codec, ALI_AINT)); + outl(0x00000000, ALI_REG(codec, ALI_VOLUME)); + outb(0x10, ALI_REG(codec, ALI_MPUR2)); + + codec->ac97_ext_id = snd_ali_codec_peek(codec, 0, AC97_EXTENDED_ID); + codec->ac97_ext_status = snd_ali_codec_peek(codec, 0, + AC97_EXTENDED_STATUS); + if (codec->spdif_support) { + snd_ali_enable_spdif_out(codec); + codec->spdif_mask = 0x00000002; + } + + codec->num_of_codecs = 1; + + /* secondary codec - modem */ + if (inl(ALI_REG(codec, ALI_SCTRL)) & ALI_SCTRL_CODEC2_READY) { + codec->num_of_codecs++; + outl(inl(ALI_REG(codec, ALI_SCTRL)) | + (ALI_SCTRL_LINE_IN2 | ALI_SCTRL_GPIO_IN2 | + ALI_SCTRL_LINE_OUT_EN), + ALI_REG(codec, ALI_SCTRL)); + } + + dev_dbg(codec->card->dev, "chip initialize succeed.\n"); + return 0; + +} + +/* proc for register dump */ +static void snd_ali_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buf) +{ + struct snd_ali *codec = entry->private_data; + int i; + for (i = 0; i < 256 ; i+= 4) + snd_iprintf(buf, "%02x: %08x\n", i, inl(ALI_REG(codec, i))); +} + +static void snd_ali_proc_init(struct snd_ali *codec) +{ + snd_card_ro_proc_new(codec->card, "ali5451", codec, snd_ali_proc_read); +} + +static int snd_ali_resources(struct snd_ali *codec) +{ + int err; + + dev_dbg(codec->card->dev, "resources allocation ...\n"); + err = pcim_request_all_regions(codec->pci, "ALI 5451"); + if (err < 0) + return err; + codec->port = pci_resource_start(codec->pci, 0); + + if (devm_request_irq(&codec->pci->dev, codec->pci->irq, + snd_ali_card_interrupt, + IRQF_SHARED, KBUILD_MODNAME, codec)) { + dev_err(codec->card->dev, "Unable to request irq.\n"); + return -EBUSY; + } + codec->irq = codec->pci->irq; + codec->card->sync_irq = codec->irq; + dev_dbg(codec->card->dev, "resources allocated.\n"); + return 0; +} + +static int snd_ali_create(struct snd_card *card, + struct pci_dev *pci, + int pcm_streams, + int spdif_support) +{ + struct snd_ali *codec = card->private_data; + int i, err; + unsigned short cmdw; + + dev_dbg(card->dev, "creating ...\n"); + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + /* check, if we can restrict PCI DMA transfers to 31 bits */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(31))) { + dev_err(card->dev, + "architecture does not support 31bit PCI busmaster DMA\n"); + return -ENXIO; + } + + spin_lock_init(&codec->reg_lock); + spin_lock_init(&codec->voice_alloc); + + codec->card = card; + codec->pci = pci; + codec->irq = -1; + codec->revision = pci->revision; + codec->spdif_support = spdif_support; + + if (pcm_streams < 1) + pcm_streams = 1; + if (pcm_streams > 32) + pcm_streams = 32; + + pci_set_master(pci); + pci_read_config_word(pci, PCI_COMMAND, &cmdw); + if ((cmdw & PCI_COMMAND_IO) != PCI_COMMAND_IO) { + cmdw |= PCI_COMMAND_IO; + pci_write_config_word(pci, PCI_COMMAND, cmdw); + } + + if (snd_ali_resources(codec)) + return -EBUSY; + card->private_free = snd_ali_free; + + codec->synth.chmap = 0; + codec->synth.chcnt = 0; + codec->spdif_mask = 0; + codec->synth.synthcount = 0; + + if (codec->revision == ALI_5451_V02) + codec->chregs.regs.ac97read = ALI_AC97_WRITE; + else + codec->chregs.regs.ac97read = ALI_AC97_READ; + codec->chregs.regs.ac97write = ALI_AC97_WRITE; + + codec->chregs.regs.start = ALI_START; + codec->chregs.regs.stop = ALI_STOP; + codec->chregs.regs.aint = ALI_AINT; + codec->chregs.regs.ainten = ALI_AINTEN; + + codec->chregs.data.start = 0x00; + codec->chregs.data.stop = 0x00; + codec->chregs.data.aint = 0x00; + codec->chregs.data.ainten = 0x00; + + /* M1533: southbridge */ + codec->pci_m1533 = pci_get_device(0x10b9, 0x1533, NULL); + if (!codec->pci_m1533) { + dev_err(card->dev, "cannot find ALi 1533 chip.\n"); + return -ENODEV; + } + /* M7101: power management */ + codec->pci_m7101 = pci_get_device(0x10b9, 0x7101, NULL); + if (!codec->pci_m7101 && codec->revision == ALI_5451_V02) { + dev_err(card->dev, "cannot find ALi 7101 chip.\n"); + return -ENODEV; + } + + /* initialise synth voices*/ + for (i = 0; i < ALI_CHANNELS; i++) + codec->synth.voices[i].number = i; + + err = snd_ali_chip_init(codec); + if (err < 0) { + dev_err(card->dev, "ali create: chip init error.\n"); + return err; + } + + snd_ali_enable_address_interrupt(codec); + codec->hw_initialized = 1; + return 0; +} + +static int __snd_ali_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct snd_ali *codec; + int err; + + dev_dbg(&pci->dev, "probe ...\n"); + + err = snd_devm_card_new(&pci->dev, index, id, THIS_MODULE, + sizeof(*codec), &card); + if (err < 0) + return err; + codec = card->private_data; + + err = snd_ali_create(card, pci, pcm_channels, spdif); + if (err < 0) + return err; + + dev_dbg(&pci->dev, "mixer building ...\n"); + err = snd_ali_mixer(codec); + if (err < 0) + return err; + + dev_dbg(&pci->dev, "pcm building ...\n"); + err = snd_ali_build_pcms(codec); + if (err < 0) + return err; + + snd_ali_proc_init(codec); + + strscpy(card->driver, "ALI5451"); + strscpy(card->shortname, "ALI 5451"); + + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, codec->port, codec->irq); + + dev_dbg(&pci->dev, "register card.\n"); + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + return 0; +} + +static int snd_ali_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_ali_probe(pci, pci_id)); +} + +static struct pci_driver ali5451_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_ali_ids, + .probe = snd_ali_probe, + .driver = { + .pm = &ali_pm, + }, +}; + +module_pci_driver(ali5451_driver); diff --git a/sound/pci/als300.c b/sound/pci/als300.c new file mode 100644 index 000000000..00f072016 --- /dev/null +++ b/sound/pci/als300.c @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * als300.c - driver for Avance Logic ALS300/ALS300+ soundcards. + * Copyright (C) 2005 by Ash Willis + * + * TODO + * 4 channel playback for ALS300+ + * gameport + * mpu401 + * opl3 + * + * NOTES + * The BLOCK_COUNTER registers for the ALS300(+) return a figure related to + * the position in the current period, NOT the whole buffer. It is important + * to know which period we are in so we can calculate the correct pointer. + * This is why we always use 2 periods. We can then use a flip-flop variable + * to keep track of what period we are in. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* snd_als300_set_irq_flag */ +#define IRQ_DISABLE 0 +#define IRQ_ENABLE 1 + +/* I/O port layout */ +#define AC97_ACCESS 0x00 +#define AC97_READ 0x04 +#define AC97_STATUS 0x06 +#define AC97_DATA_AVAIL (1<<6) +#define AC97_BUSY (1<<7) +#define ALS300_IRQ_STATUS 0x07 /* ALS300 Only */ +#define IRQ_PLAYBACK (1<<3) +#define IRQ_CAPTURE (1<<2) +#define GCR_DATA 0x08 +#define GCR_INDEX 0x0C +#define ALS300P_DRAM_IRQ_STATUS 0x0D /* ALS300+ Only */ +#define MPU_IRQ_STATUS 0x0E /* ALS300 Rev. E+, ALS300+ */ +#define ALS300P_IRQ_STATUS 0x0F /* ALS300+ Only */ + +/* General Control Registers */ +#define PLAYBACK_START 0x80 +#define PLAYBACK_END 0x81 +#define PLAYBACK_CONTROL 0x82 +#define TRANSFER_START (1<<16) +#define FIFO_PAUSE (1<<17) +#define RECORD_START 0x83 +#define RECORD_END 0x84 +#define RECORD_CONTROL 0x85 +#define DRAM_WRITE_CONTROL 0x8B +#define WRITE_TRANS_START (1<<16) +#define DRAM_MODE_2 (1<<17) +#define MISC_CONTROL 0x8C +#define IRQ_SET_BIT (1<<15) +#define VMUTE_NORMAL (1<<20) +#define MMUTE_NORMAL (1<<21) +#define MUS_VOC_VOL 0x8E +#define PLAYBACK_BLOCK_COUNTER 0x9A +#define RECORD_BLOCK_COUNTER 0x9B + +#define DEBUG_PLAY_REC 0 + +#if DEBUG_PLAY_REC +#define snd_als300_dbgplay(format, args...) printk(KERN_ERR format, ##args) +#else +#define snd_als300_dbgplay(format, args...) +#endif + +enum {DEVICE_ALS300, DEVICE_ALS300_PLUS}; + +MODULE_AUTHOR("Ash Willis "); +MODULE_DESCRIPTION("Avance Logic ALS300"); +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_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ALS300 sound card."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ALS300 sound card."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ALS300 sound card."); + +struct snd_als300 { + unsigned long port; + spinlock_t reg_lock; + struct snd_card *card; + struct pci_dev *pci; + + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct snd_ac97 *ac97; + struct snd_opl3 *opl3; + + struct resource *res_port; + + int irq; + + int chip_type; /* ALS300 or ALS300+ */ + + char revision; +}; + +struct snd_als300_substream_data { + int period_flipflop; + int control_register; + int block_counter_register; +}; + +static const struct pci_device_id snd_als300_ids[] = { + { PCI_DEVICE(0x4005, 0x0300), .driver_data = DEVICE_ALS300 }, + { PCI_DEVICE(0x4005, 0x0308), .driver_data = DEVICE_ALS300_PLUS }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_als300_ids); + +static inline u32 snd_als300_gcr_read(unsigned long port, unsigned short reg) +{ + outb(reg, port+GCR_INDEX); + return inl(port+GCR_DATA); +} + +static inline void snd_als300_gcr_write(unsigned long port, + unsigned short reg, u32 val) +{ + outb(reg, port+GCR_INDEX); + outl(val, port+GCR_DATA); +} + +/* Enable/Disable Interrupts */ +static void snd_als300_set_irq_flag(struct snd_als300 *chip, int cmd) +{ + u32 tmp = snd_als300_gcr_read(chip->port, MISC_CONTROL); + + /* boolean XOR check, since old vs. new hardware have + directly reversed bit setting for ENABLE and DISABLE. + ALS300+ acts like newer versions of ALS300 */ + if (((chip->revision > 5 || chip->chip_type == DEVICE_ALS300_PLUS) ^ + (cmd == IRQ_ENABLE)) == 0) + tmp |= IRQ_SET_BIT; + else + tmp &= ~IRQ_SET_BIT; + snd_als300_gcr_write(chip->port, MISC_CONTROL, tmp); +} + +static void snd_als300_free(struct snd_card *card) +{ + struct snd_als300 *chip = card->private_data; + + snd_als300_set_irq_flag(chip, IRQ_DISABLE); +} + +static irqreturn_t snd_als300_interrupt(int irq, void *dev_id) +{ + u8 status; + struct snd_als300 *chip = dev_id; + struct snd_als300_substream_data *data; + + status = inb(chip->port+ALS300_IRQ_STATUS); + if (!status) /* shared IRQ, for different device?? Exit ASAP! */ + return IRQ_NONE; + + /* ACK everything ASAP */ + outb(status, chip->port+ALS300_IRQ_STATUS); + if (status & IRQ_PLAYBACK) { + if (chip->pcm && chip->playback_substream) { + data = chip->playback_substream->runtime->private_data; + data->period_flipflop ^= 1; + snd_pcm_period_elapsed(chip->playback_substream); + snd_als300_dbgplay("IRQ_PLAYBACK\n"); + } + } + if (status & IRQ_CAPTURE) { + if (chip->pcm && chip->capture_substream) { + data = chip->capture_substream->runtime->private_data; + data->period_flipflop ^= 1; + snd_pcm_period_elapsed(chip->capture_substream); + snd_als300_dbgplay("IRQ_CAPTURE\n"); + } + } + return IRQ_HANDLED; +} + +static irqreturn_t snd_als300plus_interrupt(int irq, void *dev_id) +{ + u8 general, mpu, dram; + struct snd_als300 *chip = dev_id; + struct snd_als300_substream_data *data; + + general = inb(chip->port+ALS300P_IRQ_STATUS); + mpu = inb(chip->port+MPU_IRQ_STATUS); + dram = inb(chip->port+ALS300P_DRAM_IRQ_STATUS); + + /* shared IRQ, for different device?? Exit ASAP! */ + if ((general == 0) && ((mpu & 0x80) == 0) && ((dram & 0x01) == 0)) + return IRQ_NONE; + + if (general & IRQ_PLAYBACK) { + if (chip->pcm && chip->playback_substream) { + outb(IRQ_PLAYBACK, chip->port+ALS300P_IRQ_STATUS); + data = chip->playback_substream->runtime->private_data; + data->period_flipflop ^= 1; + snd_pcm_period_elapsed(chip->playback_substream); + snd_als300_dbgplay("IRQ_PLAYBACK\n"); + } + } + if (general & IRQ_CAPTURE) { + if (chip->pcm && chip->capture_substream) { + outb(IRQ_CAPTURE, chip->port+ALS300P_IRQ_STATUS); + data = chip->capture_substream->runtime->private_data; + data->period_flipflop ^= 1; + snd_pcm_period_elapsed(chip->capture_substream); + snd_als300_dbgplay("IRQ_CAPTURE\n"); + } + } + /* FIXME: Ack other interrupt types. Not important right now as + * those other devices aren't enabled. */ + return IRQ_HANDLED; +} + +static unsigned short snd_als300_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int i; + struct snd_als300 *chip = ac97->private_data; + + for (i = 0; i < 1000; i++) { + if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0) + break; + udelay(10); + } + outl((reg << 24) | (1 << 31), chip->port+AC97_ACCESS); + + for (i = 0; i < 1000; i++) { + if ((inb(chip->port+AC97_STATUS) & (AC97_DATA_AVAIL)) != 0) + break; + udelay(10); + } + return inw(chip->port+AC97_READ); +} + +static void snd_als300_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + int i; + struct snd_als300 *chip = ac97->private_data; + + for (i = 0; i < 1000; i++) { + if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0) + break; + udelay(10); + } + outl((reg << 24) | val, chip->port+AC97_ACCESS); +} + +static int snd_als300_ac97(struct snd_als300 *chip) +{ + struct snd_ac97_bus *bus; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_als300_ac97_write, + .read = snd_als300_ac97_read, + }; + + err = snd_ac97_bus(chip->card, 0, &ops, NULL, &bus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + + return snd_ac97_mixer(bus, &ac97, &chip->ac97); +} + +/* hardware definition + * + * In AC97 mode, we always use 48k/16bit/stereo. + * Any request to change data type is ignored by + * the card when it is running outside of legacy + * mode. + */ +static const struct snd_pcm_hardware snd_als300_playback_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 2, +}; + +static const struct snd_pcm_hardware snd_als300_capture_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 2, +}; + +static int snd_als300_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_als300_substream_data *data = kzalloc_obj(*data); + + if (!data) + return -ENOMEM; + chip->playback_substream = substream; + runtime->hw = snd_als300_playback_hw; + runtime->private_data = data; + data->control_register = PLAYBACK_CONTROL; + data->block_counter_register = PLAYBACK_BLOCK_COUNTER; + return 0; +} + +static int snd_als300_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_als300_substream_data *data; + + data = substream->runtime->private_data; + kfree(data); + chip->playback_substream = NULL; + return 0; +} + +static int snd_als300_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_als300_substream_data *data = kzalloc_obj(*data); + + if (!data) + return -ENOMEM; + chip->capture_substream = substream; + runtime->hw = snd_als300_capture_hw; + runtime->private_data = data; + data->control_register = RECORD_CONTROL; + data->block_counter_register = RECORD_BLOCK_COUNTER; + return 0; +} + +static int snd_als300_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_als300_substream_data *data; + + data = substream->runtime->private_data; + kfree(data); + chip->capture_substream = NULL; + return 0; +} + +static int snd_als300_playback_prepare(struct snd_pcm_substream *substream) +{ + u32 tmp; + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short period_bytes = snd_pcm_lib_period_bytes(substream); + unsigned short buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + + guard(spinlock_irq)(&chip->reg_lock); + tmp = snd_als300_gcr_read(chip->port, PLAYBACK_CONTROL); + tmp &= ~TRANSFER_START; + + snd_als300_dbgplay("Period bytes: %d Buffer bytes %d\n", + period_bytes, buffer_bytes); + + /* set block size */ + tmp &= 0xffff0000; + tmp |= period_bytes - 1; + snd_als300_gcr_write(chip->port, PLAYBACK_CONTROL, tmp); + + /* set dma area */ + snd_als300_gcr_write(chip->port, PLAYBACK_START, + runtime->dma_addr); + snd_als300_gcr_write(chip->port, PLAYBACK_END, + runtime->dma_addr + buffer_bytes - 1); + return 0; +} + +static int snd_als300_capture_prepare(struct snd_pcm_substream *substream) +{ + u32 tmp; + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short period_bytes = snd_pcm_lib_period_bytes(substream); + unsigned short buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + + guard(spinlock_irq)(&chip->reg_lock); + tmp = snd_als300_gcr_read(chip->port, RECORD_CONTROL); + tmp &= ~TRANSFER_START; + + snd_als300_dbgplay("Period bytes: %d Buffer bytes %d\n", period_bytes, + buffer_bytes); + + /* set block size */ + tmp &= 0xffff0000; + tmp |= period_bytes - 1; + + /* set dma area */ + snd_als300_gcr_write(chip->port, RECORD_CONTROL, tmp); + snd_als300_gcr_write(chip->port, RECORD_START, + runtime->dma_addr); + snd_als300_gcr_write(chip->port, RECORD_END, + runtime->dma_addr + buffer_bytes - 1); + return 0; +} + +static int snd_als300_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + u32 tmp; + struct snd_als300_substream_data *data; + unsigned short reg; + int ret = 0; + + data = substream->runtime->private_data; + reg = data->control_register; + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + tmp = snd_als300_gcr_read(chip->port, reg); + data->period_flipflop = 1; + snd_als300_gcr_write(chip->port, reg, tmp | TRANSFER_START); + snd_als300_dbgplay("TRIGGER START\n"); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + tmp = snd_als300_gcr_read(chip->port, reg); + snd_als300_gcr_write(chip->port, reg, tmp & ~TRANSFER_START); + snd_als300_dbgplay("TRIGGER STOP\n"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + tmp = snd_als300_gcr_read(chip->port, reg); + snd_als300_gcr_write(chip->port, reg, tmp | FIFO_PAUSE); + snd_als300_dbgplay("TRIGGER PAUSE\n"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + tmp = snd_als300_gcr_read(chip->port, reg); + snd_als300_gcr_write(chip->port, reg, tmp & ~FIFO_PAUSE); + snd_als300_dbgplay("TRIGGER RELEASE\n"); + break; + default: + snd_als300_dbgplay("TRIGGER INVALID\n"); + ret = -EINVAL; + } + return ret; +} + +static snd_pcm_uframes_t snd_als300_pointer(struct snd_pcm_substream *substream) +{ + u16 current_ptr; + struct snd_als300 *chip = snd_pcm_substream_chip(substream); + struct snd_als300_substream_data *data; + unsigned short period_bytes; + + data = substream->runtime->private_data; + period_bytes = snd_pcm_lib_period_bytes(substream); + + scoped_guard(spinlock, &chip->reg_lock) { + current_ptr = (u16) snd_als300_gcr_read(chip->port, + data->block_counter_register) + 4; + } + if (current_ptr > period_bytes) + current_ptr = 0; + else + current_ptr = period_bytes - current_ptr; + + if (data->period_flipflop == 0) + current_ptr += period_bytes; + snd_als300_dbgplay("Pointer (bytes): %d\n", current_ptr); + return bytes_to_frames(substream->runtime, current_ptr); +} + +static const struct snd_pcm_ops snd_als300_playback_ops = { + .open = snd_als300_playback_open, + .close = snd_als300_playback_close, + .prepare = snd_als300_playback_prepare, + .trigger = snd_als300_trigger, + .pointer = snd_als300_pointer, +}; + +static const struct snd_pcm_ops snd_als300_capture_ops = { + .open = snd_als300_capture_open, + .close = snd_als300_capture_close, + .prepare = snd_als300_capture_prepare, + .trigger = snd_als300_trigger, + .pointer = snd_als300_pointer, +}; + +static int snd_als300_new_pcm(struct snd_als300 *chip) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "ALS300", 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + strscpy(pcm->name, "ALS300"); + chip->pcm = pcm; + + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_als300_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_als300_capture_ops); + + /* pre-allocation of buffers */ + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + 64*1024, 64*1024); + return 0; +} + +static void snd_als300_init(struct snd_als300 *chip) +{ + u32 tmp; + + guard(spinlock_irqsave)(&chip->reg_lock); + chip->revision = (snd_als300_gcr_read(chip->port, MISC_CONTROL) >> 16) + & 0x0000000F; + /* Setup DRAM */ + tmp = snd_als300_gcr_read(chip->port, DRAM_WRITE_CONTROL); + snd_als300_gcr_write(chip->port, DRAM_WRITE_CONTROL, + (tmp | DRAM_MODE_2) + & ~WRITE_TRANS_START); + + /* Enable IRQ output */ + snd_als300_set_irq_flag(chip, IRQ_ENABLE); + + /* Unmute hardware devices so their outputs get routed to + * the onboard mixer */ + tmp = snd_als300_gcr_read(chip->port, MISC_CONTROL); + snd_als300_gcr_write(chip->port, MISC_CONTROL, + tmp | VMUTE_NORMAL | MMUTE_NORMAL); + + /* Reset volumes */ + snd_als300_gcr_write(chip->port, MUS_VOC_VOL, 0); + + /* Make sure playback transfer is stopped */ + tmp = snd_als300_gcr_read(chip->port, PLAYBACK_CONTROL); + snd_als300_gcr_write(chip->port, PLAYBACK_CONTROL, + tmp & ~TRANSFER_START); +} + +static int snd_als300_create(struct snd_card *card, + struct pci_dev *pci, int chip_type) +{ + struct snd_als300 *chip = card->private_data; + void *irq_handler; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(28))) { + dev_err(card->dev, "error setting 28bit DMA mask\n"); + return -ENXIO; + } + pci_set_master(pci); + + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->chip_type = chip_type; + spin_lock_init(&chip->reg_lock); + + err = pcim_request_all_regions(pci, "ALS300"); + if (err < 0) + return err; + + chip->port = pci_resource_start(pci, 0); + + if (chip->chip_type == DEVICE_ALS300_PLUS) + irq_handler = snd_als300plus_interrupt; + else + irq_handler = snd_als300_interrupt; + + if (devm_request_irq(&pci->dev, pci->irq, irq_handler, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_als300_free; + + snd_als300_init(chip); + + err = snd_als300_ac97(chip); + if (err < 0) { + dev_err(card->dev, "Could not create ac97\n"); + return err; + } + + err = snd_als300_new_pcm(chip); + if (err < 0) { + dev_err(card->dev, "Could not create PCM\n"); + return err; + } + + return 0; +} + +static int snd_als300_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_als300 *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(chip->ac97); + return 0; +} + +static int snd_als300_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_als300 *chip = card->private_data; + + snd_als300_init(chip); + snd_ac97_resume(chip->ac97); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_als300_pm, snd_als300_suspend, snd_als300_resume); + +static int snd_als300_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_als300 *chip; + int err, chip_type; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + chip_type = pci_id->driver_data; + + err = snd_als300_create(card, pci, chip_type); + if (err < 0) + goto error; + + strscpy(card->driver, "ALS300"); + if (chip->chip_type == DEVICE_ALS300_PLUS) + /* don't know much about ALS300+ yet + * print revision number for now */ + sprintf(card->shortname, "ALS300+ (Rev. %d)", chip->revision); + else + sprintf(card->shortname, "ALS300 (Rev. %c)", 'A' + + chip->revision - 1); + sprintf(card->longname, "%s at 0x%lx irq %i", + card->shortname, chip->port, chip->irq); + + err = snd_card_register(card); + if (err < 0) + goto error; + + pci_set_drvdata(pci, card); + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +static struct pci_driver als300_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_als300_ids, + .probe = snd_als300_probe, + .driver = { + .pm = &snd_als300_pm, + }, +}; + +module_pci_driver(als300_driver); diff --git a/sound/pci/als4000.c b/sound/pci/als4000.c new file mode 100644 index 000000000..6396aa6c3 --- /dev/null +++ b/sound/pci/als4000.c @@ -0,0 +1,973 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * card-als4000.c - driver for Avance Logic ALS4000 based soundcards. + * Copyright (C) 2000 by Bart Hartgers , + * Jaroslav Kysela + * Copyright (C) 2002, 2008 by Andreas Mohr + * + * Framework borrowed from Massimo Piccioni's card-als100.c. + * + * NOTES + * + * Since Avance does not provide any meaningful documentation, and I + * bought an ALS4000 based soundcard, I was forced to base this driver + * on reverse engineering. + * + * Note: this is no longer true (thank you!): + * pretty verbose chip docu (ALS4000a.PDF) can be found on the ALSA web site. + * Page numbers stated anywhere below with the "SPECS_PAGE:" tag + * refer to: ALS4000a.PDF specs Ver 1.0, May 28th, 1998. + * + * The ALS4000 seems to be the PCI-cousin of the ALS100. It contains an + * ALS100-like SB DSP/mixer, an OPL3 synth, a MPU401 and a gameport + * interface. These subsystems can be mapped into ISA io-port space, + * using the PCI-interface. In addition, the PCI-bit provides DMA and IRQ + * services to the subsystems. + * + * While ALS4000 is very similar to a SoundBlaster, the differences in + * DMA and capturing require more changes to the SoundBlaster than + * desirable, so I made this separate driver. + * + * The ALS4000 can do real full duplex playback/capture. + * + * FMDAC: + * - 0x4f -> port 0x14 + * - port 0x15 |= 1 + * + * Enable/disable 3D sound: + * - 0x50 -> port 0x14 + * - change bit 6 (0x40) of port 0x15 + * + * Set QSound: + * - 0xdb -> port 0x14 + * - set port 0x15: + * 0x3e (mode 3), 0x3c (mode 2), 0x3a (mode 1), 0x38 (mode 0) + * + * Set KSound: + * - value -> some port 0x0c0d + * + * ToDo: + * - by default, don't enable legacy game and use PCI game I/O + * - power management? (card can do voice wakeup according to datasheet!!) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Bart Hartgers , Andreas Mohr"); +MODULE_DESCRIPTION("Avance Logic ALS4000"); +MODULE_LICENSE("GPL"); + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK 1 +#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_PNP; /* Enable this card */ +#ifdef SUPPORT_JOYSTICK +static int joystick_port[SNDRV_CARDS]; +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ALS4000 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ALS4000 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ALS4000 soundcard."); +#ifdef SUPPORT_JOYSTICK +module_param_hw_array(joystick_port, int, ioport, NULL, 0444); +MODULE_PARM_DESC(joystick_port, "Joystick port address for ALS4000 soundcard. (0 = disabled)"); +#endif + +struct snd_card_als4000 { + /* most frequent access first */ + unsigned long iobase; + struct pci_dev *pci; + struct snd_sb *chip; +#ifdef SUPPORT_JOYSTICK + struct gameport *gameport; +#endif +}; + +static const struct pci_device_id snd_als4000_ids[] = { + { PCI_DEVICE(0x4005, 0x4000) }, /* ALS4000 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_als4000_ids); + +enum als4k_iobase_t { + /* IOx: B == Byte, W = Word, D = DWord; SPECS_PAGE: 37 */ + ALS4K_IOD_00_AC97_ACCESS = 0x00, + ALS4K_IOW_04_AC97_READ = 0x04, + ALS4K_IOB_06_AC97_STATUS = 0x06, + ALS4K_IOB_07_IRQSTATUS = 0x07, + ALS4K_IOD_08_GCR_DATA = 0x08, + ALS4K_IOB_0C_GCR_INDEX = 0x0c, + ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU = 0x0e, + ALS4K_IOB_10_ADLIB_ADDR0 = 0x10, + ALS4K_IOB_11_ADLIB_ADDR1 = 0x11, + ALS4K_IOB_12_ADLIB_ADDR2 = 0x12, + ALS4K_IOB_13_ADLIB_ADDR3 = 0x13, + ALS4K_IOB_14_MIXER_INDEX = 0x14, + ALS4K_IOB_15_MIXER_DATA = 0x15, + ALS4K_IOB_16_ESP_RESET = 0x16, + ALS4K_IOB_16_ACK_FOR_CR1E = 0x16, /* 2nd function */ + ALS4K_IOB_18_OPL_ADDR0 = 0x18, + ALS4K_IOB_19_OPL_ADDR1 = 0x19, + ALS4K_IOB_1A_ESP_RD_DATA = 0x1a, + ALS4K_IOB_1C_ESP_CMD_DATA = 0x1c, + ALS4K_IOB_1C_ESP_WR_STATUS = 0x1c, /* 2nd function */ + ALS4K_IOB_1E_ESP_RD_STATUS8 = 0x1e, + ALS4K_IOB_1F_ESP_RD_STATUS16 = 0x1f, + ALS4K_IOB_20_ESP_GAMEPORT_200 = 0x20, + ALS4K_IOB_21_ESP_GAMEPORT_201 = 0x21, + ALS4K_IOB_30_MIDI_DATA = 0x30, + ALS4K_IOB_31_MIDI_STATUS = 0x31, + ALS4K_IOB_31_MIDI_COMMAND = 0x31, /* 2nd function */ +}; + +enum als4k_iobase_0e_t { + ALS4K_IOB_0E_MPU_IRQ = 0x10, + ALS4K_IOB_0E_CR1E_IRQ = 0x40, + ALS4K_IOB_0E_SB_DMA_IRQ = 0x80, +}; + +enum als4k_gcr_t { /* all registers 32bit wide; SPECS_PAGE: 38 to 42 */ + ALS4K_GCR8C_MISC_CTRL = 0x8c, + ALS4K_GCR90_TEST_MODE_REG = 0x90, + ALS4K_GCR91_DMA0_ADDR = 0x91, + ALS4K_GCR92_DMA0_MODE_COUNT = 0x92, + ALS4K_GCR93_DMA1_ADDR = 0x93, + ALS4K_GCR94_DMA1_MODE_COUNT = 0x94, + ALS4K_GCR95_DMA3_ADDR = 0x95, + ALS4K_GCR96_DMA3_MODE_COUNT = 0x96, + ALS4K_GCR99_DMA_EMULATION_CTRL = 0x99, + ALS4K_GCRA0_FIFO1_CURRENT_ADDR = 0xa0, + ALS4K_GCRA1_FIFO1_STATUS_BYTECOUNT = 0xa1, + ALS4K_GCRA2_FIFO2_PCIADDR = 0xa2, + ALS4K_GCRA3_FIFO2_COUNT = 0xa3, + ALS4K_GCRA4_FIFO2_CURRENT_ADDR = 0xa4, + ALS4K_GCRA5_FIFO1_STATUS_BYTECOUNT = 0xa5, + ALS4K_GCRA6_PM_CTRL = 0xa6, + ALS4K_GCRA7_PCI_ACCESS_STORAGE = 0xa7, + ALS4K_GCRA8_LEGACY_CFG1 = 0xa8, + ALS4K_GCRA9_LEGACY_CFG2 = 0xa9, + ALS4K_GCRFF_DUMMY_SCRATCH = 0xff, +}; + +enum als4k_gcr8c_t { + ALS4K_GCR8C_IRQ_MASK_CTRL_ENABLE = 0x8000, + ALS4K_GCR8C_CHIP_REV_MASK = 0xf0000 +}; + +static inline void snd_als4k_iobase_writeb(unsigned long iobase, + enum als4k_iobase_t reg, + u8 val) +{ + outb(val, iobase + reg); +} + +static inline void snd_als4k_iobase_writel(unsigned long iobase, + enum als4k_iobase_t reg, + u32 val) +{ + outl(val, iobase + reg); +} + +static inline u8 snd_als4k_iobase_readb(unsigned long iobase, + enum als4k_iobase_t reg) +{ + return inb(iobase + reg); +} + +static inline u32 snd_als4k_iobase_readl(unsigned long iobase, + enum als4k_iobase_t reg) +{ + return inl(iobase + reg); +} + +static inline void snd_als4k_gcr_write_addr(unsigned long iobase, + enum als4k_gcr_t reg, + u32 val) +{ + snd_als4k_iobase_writeb(iobase, ALS4K_IOB_0C_GCR_INDEX, reg); + snd_als4k_iobase_writel(iobase, ALS4K_IOD_08_GCR_DATA, val); +} + +static inline void snd_als4k_gcr_write(struct snd_sb *sb, + enum als4k_gcr_t reg, + u32 val) +{ + snd_als4k_gcr_write_addr(sb->alt_port, reg, val); +} + +static inline u32 snd_als4k_gcr_read_addr(unsigned long iobase, + enum als4k_gcr_t reg) +{ + /* SPECS_PAGE: 37/38 */ + snd_als4k_iobase_writeb(iobase, ALS4K_IOB_0C_GCR_INDEX, reg); + return snd_als4k_iobase_readl(iobase, ALS4K_IOD_08_GCR_DATA); +} + +static inline u32 snd_als4k_gcr_read(struct snd_sb *sb, enum als4k_gcr_t reg) +{ + return snd_als4k_gcr_read_addr(sb->alt_port, reg); +} + +enum als4k_cr_t { /* all registers 8bit wide; SPECS_PAGE: 20 to 23 */ + ALS4K_CR0_SB_CONFIG = 0x00, + ALS4K_CR2_MISC_CONTROL = 0x02, + ALS4K_CR3_CONFIGURATION = 0x03, + ALS4K_CR17_FIFO_STATUS = 0x17, + ALS4K_CR18_ESP_MAJOR_VERSION = 0x18, + ALS4K_CR19_ESP_MINOR_VERSION = 0x19, + ALS4K_CR1A_MPU401_UART_MODE_CONTROL = 0x1a, + ALS4K_CR1C_FIFO2_BLOCK_LENGTH_LO = 0x1c, + ALS4K_CR1D_FIFO2_BLOCK_LENGTH_HI = 0x1d, + ALS4K_CR1E_FIFO2_CONTROL = 0x1e, /* secondary PCM FIFO (recording) */ + ALS4K_CR3A_MISC_CONTROL = 0x3a, + ALS4K_CR3B_CRC32_BYTE0 = 0x3b, /* for testing, activate via CR3A */ + ALS4K_CR3C_CRC32_BYTE1 = 0x3c, + ALS4K_CR3D_CRC32_BYTE2 = 0x3d, + ALS4K_CR3E_CRC32_BYTE3 = 0x3e, +}; + +enum als4k_cr0_t { + ALS4K_CR0_DMA_CONTIN_MODE_CTRL = 0x02, /* IRQ/FIFO controlled for 0/1 */ + ALS4K_CR0_DMA_90H_MODE_CTRL = 0x04, /* IRQ/FIFO controlled for 0/1 */ + ALS4K_CR0_MX80_81_REG_WRITE_ENABLE = 0x80, +}; + +static inline void snd_als4_cr_write(struct snd_sb *chip, + enum als4k_cr_t reg, + u8 data) +{ + /* Control Register is reg | 0xc0 (bit 7, 6 set) on sbmixer_index + * NOTE: assumes chip->mixer_lock to be locked externally already! + * SPECS_PAGE: 6 */ + snd_sbmixer_write(chip, reg | 0xc0, data); +} + +static inline u8 snd_als4_cr_read(struct snd_sb *chip, + enum als4k_cr_t reg) +{ + /* NOTE: assumes chip->mixer_lock to be locked externally already! */ + return snd_sbmixer_read(chip, reg | 0xc0); +} + + + +static void snd_als4000_set_rate(struct snd_sb *chip, unsigned int rate) +{ + if (!(chip->mode & SB_RATE_LOCK)) { + snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT); + snd_sbdsp_command(chip, rate>>8); + snd_sbdsp_command(chip, rate); + } +} + +static inline void snd_als4000_set_capture_dma(struct snd_sb *chip, + dma_addr_t addr, unsigned size) +{ + /* SPECS_PAGE: 40 */ + snd_als4k_gcr_write(chip, ALS4K_GCRA2_FIFO2_PCIADDR, addr); + snd_als4k_gcr_write(chip, ALS4K_GCRA3_FIFO2_COUNT, (size-1)); +} + +static inline void snd_als4000_set_playback_dma(struct snd_sb *chip, + dma_addr_t addr, + unsigned size) +{ + /* SPECS_PAGE: 38 */ + snd_als4k_gcr_write(chip, ALS4K_GCR91_DMA0_ADDR, addr); + snd_als4k_gcr_write(chip, ALS4K_GCR92_DMA0_MODE_COUNT, + (size-1)|0x180000); +} + +#define ALS4000_FORMAT_SIGNED (1<<0) +#define ALS4000_FORMAT_16BIT (1<<1) +#define ALS4000_FORMAT_STEREO (1<<2) + +static int snd_als4000_get_format(struct snd_pcm_runtime *runtime) +{ + int result; + + result = 0; + if (snd_pcm_format_signed(runtime->format)) + result |= ALS4000_FORMAT_SIGNED; + if (snd_pcm_format_physical_width(runtime->format) == 16) + result |= ALS4000_FORMAT_16BIT; + if (runtime->channels > 1) + result |= ALS4000_FORMAT_STEREO; + return result; +} + +/* structure for setting up playback */ +static const struct { + unsigned char dsp_cmd, dma_on, dma_off, format; +} playback_cmd_vals[]={ +/* ALS4000_FORMAT_U8_MONO */ +{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_UNS_MONO }, +/* ALS4000_FORMAT_S8_MONO */ +{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_SIGN_MONO }, +/* ALS4000_FORMAT_U16L_MONO */ +{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_UNS_MONO }, +/* ALS4000_FORMAT_S16L_MONO */ +{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_SIGN_MONO }, +/* ALS4000_FORMAT_U8_STEREO */ +{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_UNS_STEREO }, +/* ALS4000_FORMAT_S8_STEREO */ +{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_SIGN_STEREO }, +/* ALS4000_FORMAT_U16L_STEREO */ +{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_UNS_STEREO }, +/* ALS4000_FORMAT_S16L_STEREO */ +{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_SIGN_STEREO }, +}; +#define playback_cmd(chip) (playback_cmd_vals[(chip)->playback_format]) + +/* structure for setting up capture */ +enum { CMD_WIDTH8=0x04, CMD_SIGNED=0x10, CMD_MONO=0x80, CMD_STEREO=0xA0 }; +static const unsigned char capture_cmd_vals[]= +{ +CMD_WIDTH8|CMD_MONO, /* ALS4000_FORMAT_U8_MONO */ +CMD_WIDTH8|CMD_SIGNED|CMD_MONO, /* ALS4000_FORMAT_S8_MONO */ +CMD_MONO, /* ALS4000_FORMAT_U16L_MONO */ +CMD_SIGNED|CMD_MONO, /* ALS4000_FORMAT_S16L_MONO */ +CMD_WIDTH8|CMD_STEREO, /* ALS4000_FORMAT_U8_STEREO */ +CMD_WIDTH8|CMD_SIGNED|CMD_STEREO, /* ALS4000_FORMAT_S8_STEREO */ +CMD_STEREO, /* ALS4000_FORMAT_U16L_STEREO */ +CMD_SIGNED|CMD_STEREO, /* ALS4000_FORMAT_S16L_STEREO */ +}; +#define capture_cmd(chip) (capture_cmd_vals[(chip)->capture_format]) + +static int snd_als4000_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long size; + unsigned count; + + chip->capture_format = snd_als4000_get_format(runtime); + + size = snd_pcm_lib_buffer_bytes(substream); + count = snd_pcm_lib_period_bytes(substream); + + if (chip->capture_format & ALS4000_FORMAT_16BIT) + count >>= 1; + count--; + + scoped_guard(spinlock_irq, &chip->reg_lock) { + snd_als4000_set_rate(chip, runtime->rate); + snd_als4000_set_capture_dma(chip, runtime->dma_addr, size); + } + scoped_guard(spinlock_irq, &chip->mixer_lock) { + snd_als4_cr_write(chip, ALS4K_CR1C_FIFO2_BLOCK_LENGTH_LO, count & 0xff); + snd_als4_cr_write(chip, ALS4K_CR1D_FIFO2_BLOCK_LENGTH_HI, count >> 8); + } + return 0; +} + +static int snd_als4000_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long size; + unsigned count; + + chip->playback_format = snd_als4000_get_format(runtime); + + size = snd_pcm_lib_buffer_bytes(substream); + count = snd_pcm_lib_period_bytes(substream); + + if (chip->playback_format & ALS4000_FORMAT_16BIT) + count >>= 1; + count--; + + /* FIXME: from second playback on, there's a lot more clicks and pops + * involved here than on first playback. Fiddling with + * tons of different settings didn't help (DMA, speaker on/off, + * reordering, ...). Something seems to get enabled on playback + * that I haven't found out how to disable again, which then causes + * the switching pops to reach the speakers the next time here. */ + guard(spinlock_irq)(&chip->reg_lock); + snd_als4000_set_rate(chip, runtime->rate); + snd_als4000_set_playback_dma(chip, runtime->dma_addr, size); + + /* SPEAKER_ON not needed, since dma_on seems to also enable speaker */ + /* snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON); */ + snd_sbdsp_command(chip, playback_cmd(chip).dsp_cmd); + snd_sbdsp_command(chip, playback_cmd(chip).format); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + snd_sbdsp_command(chip, playback_cmd(chip).dma_off); + + return 0; +} + +static int snd_als4000_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + int result = 0; + + guard(spinlock)(&chip->reg_lock); + scoped_guard(spinlock, &chip->mixer_lock) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + chip->mode |= SB_RATE_LOCK_CAPTURE; + snd_als4_cr_write(chip, ALS4K_CR1E_FIFO2_CONTROL, + capture_cmd(chip)); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + chip->mode &= ~SB_RATE_LOCK_CAPTURE; + snd_als4_cr_write(chip, ALS4K_CR1E_FIFO2_CONTROL, + capture_cmd(chip)); + break; + default: + result = -EINVAL; + break; + } + } + return result; +} + +static int snd_als4000_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + int result = 0; + + guard(spinlock)(&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, playback_cmd(chip).dma_on); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + snd_sbdsp_command(chip, playback_cmd(chip).dma_off); + chip->mode &= ~SB_RATE_LOCK_PLAYBACK; + break; + default: + result = -EINVAL; + break; + } + return result; +} + +static snd_pcm_uframes_t snd_als4000_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + unsigned int result; + + scoped_guard(spinlock, &chip->reg_lock) { + result = snd_als4k_gcr_read(chip, ALS4K_GCRA4_FIFO2_CURRENT_ADDR); + } + result &= 0xffff; + return bytes_to_frames( substream->runtime, result ); +} + +static snd_pcm_uframes_t snd_als4000_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + unsigned result; + + scoped_guard(spinlock, &chip->reg_lock) { + result = snd_als4k_gcr_read(chip, ALS4K_GCRA0_FIFO1_CURRENT_ADDR); + } + result &= 0xffff; + return bytes_to_frames( substream->runtime, result ); +} + +/* FIXME: this IRQ routine doesn't really support IRQ sharing (we always + * return IRQ_HANDLED no matter whether we actually had an IRQ flag or not). + * ALS4000a.PDF writes that while ACKing IRQ in PCI block will *not* ACK + * the IRQ in the SB core, ACKing IRQ in SB block *will* ACK the PCI IRQ + * register (alt_port + ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU). Probably something + * could be optimized here to query/write one register only... + * And even if both registers need to be queried, then there's still the + * question of whether it's actually correct to ACK PCI IRQ before reading + * SB IRQ like we do now, since ALS4000a.PDF mentions that PCI IRQ will *clear* + * SB IRQ status. + * (hmm, SPECS_PAGE: 38 mentions it the other way around!) + * And do we *really* need the lock here for *reading* SB_DSP4_IRQSTATUS?? + * */ +static irqreturn_t snd_als4000_interrupt(int irq, void *dev_id) +{ + struct snd_sb *chip = dev_id; + unsigned pci_irqstatus; + unsigned sb_irqstatus; + + /* find out which bit of the ALS4000 PCI block produced the interrupt, + SPECS_PAGE: 38, 5 */ + pci_irqstatus = snd_als4k_iobase_readb(chip->alt_port, + ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU); + if ((pci_irqstatus & ALS4K_IOB_0E_SB_DMA_IRQ) + && (chip->playback_substream)) /* playback */ + snd_pcm_period_elapsed(chip->playback_substream); + if ((pci_irqstatus & ALS4K_IOB_0E_CR1E_IRQ) + && (chip->capture_substream)) /* capturing */ + snd_pcm_period_elapsed(chip->capture_substream); + if ((pci_irqstatus & ALS4K_IOB_0E_MPU_IRQ) + && (chip->rmidi)) /* MPU401 interrupt */ + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); + /* ACK the PCI block IRQ */ + snd_als4k_iobase_writeb(chip->alt_port, + ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU, pci_irqstatus); + + scoped_guard(spinlock, &chip->mixer_lock) { + /* SPECS_PAGE: 20 */ + sb_irqstatus = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS); + } + + if (sb_irqstatus & SB_IRQTYPE_8BIT) + snd_sb_ack_8bit(chip); + if (sb_irqstatus & SB_IRQTYPE_16BIT) + snd_sb_ack_16bit(chip); + if (sb_irqstatus & SB_IRQTYPE_MPUIN) + inb(chip->mpu_port); + if (sb_irqstatus & ALS4K_IRQTYPE_CR1E_DMA) + snd_als4k_iobase_readb(chip->alt_port, + ALS4K_IOB_16_ACK_FOR_CR1E); + + /* dev_dbg(chip->card->dev, "als4000: irq 0x%04x 0x%04x\n", + pci_irqstatus, sb_irqstatus); */ + + /* only ack the things we actually handled above */ + return IRQ_RETVAL( + (pci_irqstatus & (ALS4K_IOB_0E_SB_DMA_IRQ|ALS4K_IOB_0E_CR1E_IRQ| + ALS4K_IOB_0E_MPU_IRQ)) + || (sb_irqstatus & (SB_IRQTYPE_8BIT|SB_IRQTYPE_16BIT| + SB_IRQTYPE_MPUIN|ALS4K_IRQTYPE_CR1E_DMA)) + ); +} + +/*****************************************************************/ + +static const struct snd_pcm_hardware snd_als4000_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE, /* formats */ + .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_als4000_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE, /* formats */ + .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_als4000_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + chip->playback_substream = substream; + runtime->hw = snd_als4000_playback; + return 0; +} + +static int snd_als4000_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + return 0; +} + +static int snd_als4000_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + chip->capture_substream = substream; + runtime->hw = snd_als4000_capture; + return 0; +} + +static int snd_als4000_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + return 0; +} + +/******************************************************************/ + +static const struct snd_pcm_ops snd_als4000_playback_ops = { + .open = snd_als4000_playback_open, + .close = snd_als4000_playback_close, + .prepare = snd_als4000_playback_prepare, + .trigger = snd_als4000_playback_trigger, + .pointer = snd_als4000_playback_pointer +}; + +static const struct snd_pcm_ops snd_als4000_capture_ops = { + .open = snd_als4000_capture_open, + .close = snd_als4000_capture_close, + .prepare = snd_als4000_capture_prepare, + .trigger = snd_als4000_capture_trigger, + .pointer = snd_als4000_capture_pointer +}; + +static int snd_als4000_pcm(struct snd_sb *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "ALS4000 DSP", device, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_als4000_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_als4000_capture_ops); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 64*1024); + + chip->pcm = pcm; + + return 0; +} + +/******************************************************************/ + +static void snd_als4000_set_addr(unsigned long iobase, + unsigned int sb_io, + unsigned int mpu_io, + unsigned int opl_io, + unsigned int game_io) +{ + u32 cfg1 = 0; + u32 cfg2 = 0; + + if (mpu_io > 0) + cfg2 |= (mpu_io | 1) << 16; + if (sb_io > 0) + cfg2 |= (sb_io | 1); + if (game_io > 0) + cfg1 |= (game_io | 1) << 16; + if (opl_io > 0) + cfg1 |= (opl_io | 1); + snd_als4k_gcr_write_addr(iobase, ALS4K_GCRA8_LEGACY_CFG1, cfg1); + snd_als4k_gcr_write_addr(iobase, ALS4K_GCRA9_LEGACY_CFG2, cfg2); +} + +static void snd_als4000_configure(struct snd_sb *chip) +{ + u8 tmp; + int i; + + /* do some more configuration */ + scoped_guard(spinlock_irq, &chip->mixer_lock) { + tmp = snd_als4_cr_read(chip, ALS4K_CR0_SB_CONFIG); + snd_als4_cr_write(chip, ALS4K_CR0_SB_CONFIG, + tmp|ALS4K_CR0_MX80_81_REG_WRITE_ENABLE); + /* always select DMA channel 0, since we do not actually use DMA + * SPECS_PAGE: 19/20 */ + snd_sbmixer_write(chip, SB_DSP4_DMASETUP, SB_DMASETUP_DMA0); + snd_als4_cr_write(chip, ALS4K_CR0_SB_CONFIG, + tmp & ~ALS4K_CR0_MX80_81_REG_WRITE_ENABLE); + } + + guard(spinlock_irq)(&chip->reg_lock); + /* enable interrupts */ + snd_als4k_gcr_write(chip, ALS4K_GCR8C_MISC_CTRL, + ALS4K_GCR8C_IRQ_MASK_CTRL_ENABLE); + + /* SPECS_PAGE: 39 */ + for (i = ALS4K_GCR91_DMA0_ADDR; i <= ALS4K_GCR96_DMA3_MODE_COUNT; ++i) + snd_als4k_gcr_write(chip, i, 0); + /* enable burst mode to prevent dropouts during high PCI bus usage */ + snd_als4k_gcr_write(chip, ALS4K_GCR99_DMA_EMULATION_CTRL, + (snd_als4k_gcr_read(chip, ALS4K_GCR99_DMA_EMULATION_CTRL) & ~0x07) | 0x04); +} + +#ifdef SUPPORT_JOYSTICK +static int snd_als4000_create_gameport(struct snd_card_als4000 *acard, int dev) +{ + struct gameport *gp; + struct resource *r; + int io_port; + + if (joystick_port[dev] == 0) + return -ENODEV; + + if (joystick_port[dev] == 1) { /* auto-detect */ + for (io_port = 0x200; io_port <= 0x218; io_port += 8) { + r = devm_request_region(&acard->pci->dev, io_port, 8, + "ALS4000 gameport"); + if (r) + break; + } + } else { + io_port = joystick_port[dev]; + r = devm_request_region(&acard->pci->dev, io_port, 8, + "ALS4000 gameport"); + } + + if (!r) { + dev_warn(&acard->pci->dev, "cannot reserve joystick ports\n"); + return -EBUSY; + } + + acard->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(&acard->pci->dev, "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "ALS4000 Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(acard->pci)); + gameport_set_dev_parent(gp, &acard->pci->dev); + gp->io = io_port; + + /* Enable legacy joystick port */ + snd_als4000_set_addr(acard->iobase, 0, 0, 0, 1); + + gameport_register_port(acard->gameport); + + return 0; +} + +static void snd_als4000_free_gameport(struct snd_card_als4000 *acard) +{ + if (acard->gameport) { + gameport_unregister_port(acard->gameport); + acard->gameport = NULL; + + /* disable joystick */ + snd_als4000_set_addr(acard->iobase, 0, 0, 0, 0); + } +} +#else +static inline int snd_als4000_create_gameport(struct snd_card_als4000 *acard, int dev) { return -ENOSYS; } +static inline void snd_als4000_free_gameport(struct snd_card_als4000 *acard) { } +#endif + +static void snd_card_als4000_free( struct snd_card *card ) +{ + struct snd_card_als4000 *acard = card->private_data; + + /* make sure that interrupts are disabled */ + snd_als4k_gcr_write_addr(acard->iobase, ALS4K_GCR8C_MISC_CTRL, 0); + /* free resources */ + snd_als4000_free_gameport(acard); +} + +static int __snd_card_als4000_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_card_als4000 *acard; + unsigned long iobase; + struct snd_sb *chip; + struct snd_opl3 *opl3; + unsigned short word; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + + /* check, if we can restrict PCI DMA transfers to 24 bits */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(24))) { + dev_err(&pci->dev, "architecture does not support 24bit PCI busmaster DMA\n"); + return -ENXIO; + } + + err = pcim_request_all_regions(pci, "ALS4000"); + if (err < 0) + return err; + iobase = pci_resource_start(pci, 0); + + pci_read_config_word(pci, PCI_COMMAND, &word); + pci_write_config_word(pci, PCI_COMMAND, word | PCI_COMMAND_IO); + pci_set_master(pci); + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*acard) /* private_data: acard */, + &card); + if (err < 0) + return err; + + acard = card->private_data; + acard->pci = pci; + acard->iobase = iobase; + card->private_free = snd_card_als4000_free; + + /* disable all legacy ISA stuff */ + snd_als4000_set_addr(acard->iobase, 0, 0, 0, 0); + + err = snd_sbdsp_create(card, + iobase + ALS4K_IOB_10_ADLIB_ADDR0, + pci->irq, + /* internally registered as IRQF_SHARED in case of ALS4000 SB */ + snd_als4000_interrupt, + -1, + -1, + SB_HW_ALS4000, + &chip); + if (err < 0) + return err; + acard->chip = chip; + + chip->pci = pci; + chip->alt_port = iobase; + + snd_als4000_configure(chip); + + strscpy(card->driver, "ALS4000"); + strscpy(card->shortname, "Avance Logic ALS4000"); + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, chip->alt_port, chip->irq); + + err = snd_mpu401_uart_new(card, 0, MPU401_HW_ALS4000, + iobase + ALS4K_IOB_30_MIDI_DATA, + MPU401_INFO_INTEGRATED | + MPU401_INFO_IRQ_HOOK, + -1, &chip->rmidi); + if (err < 0) { + dev_err(&pci->dev, "no MPU-401 device at 0x%lx?\n", + iobase + ALS4K_IOB_30_MIDI_DATA); + return err; + } + /* FIXME: ALS4000 has interesting MPU401 configuration features + * at ALS4K_CR1A_MPU401_UART_MODE_CONTROL + * (pass-thru / UART switching, fast MIDI clock, etc.), + * however there doesn't seem to be an ALSA API for this... + * SPECS_PAGE: 21 */ + + err = snd_als4000_pcm(chip, 0); + if (err < 0) + return err; + + err = snd_sbmixer_new(chip); + if (err < 0) + return err; + + if (snd_opl3_create(card, + iobase + ALS4K_IOB_10_ADLIB_ADDR0, + iobase + ALS4K_IOB_12_ADLIB_ADDR2, + OPL3_HW_AUTO, 1, &opl3) < 0) { + dev_err(&pci->dev, "no OPL device at 0x%lx-0x%lx?\n", + iobase + ALS4K_IOB_10_ADLIB_ADDR0, + iobase + ALS4K_IOB_12_ADLIB_ADDR2); + } else { + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + return err; + } + + snd_als4000_create_gameport(acard, dev); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_card_als4000_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_card_als4000_probe(pci, pci_id)); +} + +static int snd_als4000_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_card_als4000 *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_als4000_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_card_als4000 *acard = card->private_data; + struct snd_sb *chip = acard->chip; + + snd_als4000_configure(chip); + snd_sbdsp_reset(chip); + snd_sbmixer_resume(chip); + +#ifdef SUPPORT_JOYSTICK + if (acard->gameport) + snd_als4000_set_addr(acard->iobase, 0, 0, 0, 1); +#endif + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_als4000_pm, snd_als4000_suspend, snd_als4000_resume); + +static struct pci_driver als4000_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_als4000_ids, + .probe = snd_card_als4000_probe, + .driver = { + .pm = &snd_als4000_pm, + }, +}; + +module_pci_driver(als4000_driver); diff --git a/sound/pci/asihpi/Makefile b/sound/pci/asihpi/Makefile new file mode 100644 index 000000000..d558a974f --- /dev/null +++ b/sound/pci/asihpi/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-asihpi-y := asihpi.o hpioctl.o hpimsginit.o\ + hpicmn.o hpifunc.o hpidebug.o hpidspcd.o\ + hpios.o hpi6000.o hpi6205.o hpimsgx.o + +obj-$(CONFIG_SND_ASIHPI) += snd-asihpi.o diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c new file mode 100644 index 000000000..b78e96cae --- /dev/null +++ b/sound/pci/asihpi/asihpi.c @@ -0,0 +1,2971 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Asihpi soundcard + * Copyright (c) by AudioScience Inc + * + * The following is not a condition of use, merely a request: + * If you modify this program, particularly if you fix errors, AudioScience Inc + * would appreciate it if you grant us the right to use those modifications + * for any purpose including commercial applications. + */ + +#include "hpi_internal.h" +#include "hpi_version.h" +#include "hpimsginit.h" +#include "hpioctl.h" +#include "hpicmn.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("AudioScience inc. "); +MODULE_DESCRIPTION("AudioScience ALSA ASI5xxx ASI6xxx ASI87xx ASI89xx " + HPI_VER_STRING); + +#ifdef ASIHPI_VERBOSE_DEBUG +#define asihpi_dbg(format, args...) pr_debug(format, ##args) +#else +#define asihpi_dbg(format, args...) do { } while (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 */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static bool enable_hpi_hwdep = 1; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "ALSA index value for AudioScience soundcard."); + +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ALSA ID string for AudioScience soundcard."); + +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "ALSA enable AudioScience soundcard."); + +module_param(enable_hpi_hwdep, bool, 0644); +MODULE_PARM_DESC(enable_hpi_hwdep, + "ALSA enable HPI hwdep for AudioScience soundcard "); + +/* identify driver */ +#ifdef KERNEL_ALSA_BUILD +static char *build_info = "Built using headers from kernel source"; +module_param(build_info, charp, 0444); +MODULE_PARM_DESC(build_info, "Built using headers from kernel source"); +#else +static char *build_info = "Built within ALSA source"; +module_param(build_info, charp, 0444); +MODULE_PARM_DESC(build_info, "Built within ALSA source"); +#endif + +/* set to 1 to dump every control from adapter to log */ +static const int mixer_dump; + +#define DEFAULT_SAMPLERATE 44100 +static int adapter_fs = DEFAULT_SAMPLERATE; + +/* defaults */ +#define PERIODS_MIN 2 +#define PERIOD_BYTES_MIN 2048 +#define BUFFER_BYTES_MAX (512 * 1024) + +#define MAX_CLOCKSOURCES (HPI_SAMPLECLOCK_SOURCE_LAST + 1 + 7) + +struct clk_source { + int source; + int index; + const char *name; +}; + +struct clk_cache { + int count; + int has_local; + struct clk_source s[MAX_CLOCKSOURCES]; +}; + +/* Per card data */ +struct snd_card_asihpi { + struct snd_card *card; + struct pci_dev *pci; + struct hpi_adapter *hpi; + + /* In low latency mode there is only one stream, a pointer to its + * private data is stored here on trigger and cleared on stop. + * The interrupt handler uses it as a parameter when calling + * snd_card_asihpi_timer_function(). + */ + struct snd_card_asihpi_pcm *llmode_streampriv; + void (*pcm_start)(struct snd_pcm_substream *substream); + void (*pcm_stop)(struct snd_pcm_substream *substream); + + u32 h_mixer; + struct clk_cache cc; + + u16 can_dma; + u16 support_grouping; + u16 support_mrx; + u16 update_interval_frames; + u16 in_max_chans; + u16 out_max_chans; + u16 in_min_chans; + u16 out_min_chans; +}; + +/* Per stream data */ +struct snd_card_asihpi_pcm { + struct timer_list timer; + unsigned int respawn_timer; + unsigned int hpi_buffer_attached; + unsigned int buffer_bytes; + unsigned int period_bytes; + unsigned int bytes_per_sec; + unsigned int pcm_buf_host_rw_ofs; /* Host R/W pos */ + unsigned int pcm_buf_dma_ofs; /* DMA R/W offset in buffer */ + unsigned int pcm_buf_elapsed_dma_ofs; /* DMA R/W offset in buffer */ + unsigned int drained_count; + struct snd_pcm_substream *substream; + u32 h_stream; + struct hpi_format format; +}; + +/* universal stream verbs work with out or in stream handles */ + +/* Functions to allow driver to give a buffer to HPI for busmastering */ + +static u16 hpi_stream_host_buffer_attach( + u32 h_stream, /* handle to outstream. */ + u32 size_in_bytes, /* size in bytes of bus mastering buffer */ + u32 pci_address +) +{ + struct hpi_message hm; + struct hpi_response hr; + unsigned int obj = hpi_handle_object(h_stream); + + if (!h_stream) + return HPI_ERROR_INVALID_OBJ; + hpi_init_message_response(&hm, &hr, obj, + obj == HPI_OBJ_OSTREAM ? + HPI_OSTREAM_HOSTBUFFER_ALLOC : + HPI_ISTREAM_HOSTBUFFER_ALLOC); + + hpi_handle_to_indexes(h_stream, &hm.adapter_index, + &hm.obj_index); + + hm.u.d.u.buffer.buffer_size = size_in_bytes; + hm.u.d.u.buffer.pci_address = pci_address; + hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +static u16 hpi_stream_host_buffer_detach(u32 h_stream) +{ + struct hpi_message hm; + struct hpi_response hr; + unsigned int obj = hpi_handle_object(h_stream); + + if (!h_stream) + return HPI_ERROR_INVALID_OBJ; + + hpi_init_message_response(&hm, &hr, obj, + obj == HPI_OBJ_OSTREAM ? + HPI_OSTREAM_HOSTBUFFER_FREE : + HPI_ISTREAM_HOSTBUFFER_FREE); + + hpi_handle_to_indexes(h_stream, &hm.adapter_index, + &hm.obj_index); + hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +static inline u16 hpi_stream_start(u32 h_stream) +{ + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + return hpi_outstream_start(h_stream); + else + return hpi_instream_start(h_stream); +} + +static inline u16 hpi_stream_stop(u32 h_stream) +{ + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + return hpi_outstream_stop(h_stream); + else + return hpi_instream_stop(h_stream); +} + +static inline u16 hpi_stream_get_info_ex( + u32 h_stream, + u16 *pw_state, + u32 *pbuffer_size, + u32 *pdata_in_buffer, + u32 *psample_count, + u32 *pauxiliary_data +) +{ + u16 e; + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + e = hpi_outstream_get_info_ex(h_stream, pw_state, + pbuffer_size, pdata_in_buffer, + psample_count, pauxiliary_data); + else + e = hpi_instream_get_info_ex(h_stream, pw_state, + pbuffer_size, pdata_in_buffer, + psample_count, pauxiliary_data); + return e; +} + +static inline u16 hpi_stream_group_add( + u32 h_master, + u32 h_stream) +{ + if (hpi_handle_object(h_master) == HPI_OBJ_OSTREAM) + return hpi_outstream_group_add(h_master, h_stream); + else + return hpi_instream_group_add(h_master, h_stream); +} + +static inline u16 hpi_stream_group_reset(u32 h_stream) +{ + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + return hpi_outstream_group_reset(h_stream); + else + return hpi_instream_group_reset(h_stream); +} + +static u16 handle_error(u16 err, int line, char *filename) +{ + if (err) + pr_warn("in file %s, line %d: HPI error %d\n", + filename, line, err); + return err; +} + +#define hpi_handle_error(x) handle_error(x, __LINE__, __FILE__) + +/***************************** GENERAL PCM ****************/ + +static void print_hwparams(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *p) +{ + struct device *dev = substream->pcm->card->dev; + char name[16]; + + snd_pcm_debug_name(substream, name, sizeof(name)); + dev_dbg(dev, "%s HWPARAMS\n", name); + dev_dbg(dev, " samplerate=%dHz channels=%d format=%d subformat=%d\n", + params_rate(p), params_channels(p), + params_format(p), params_subformat(p)); + dev_dbg(dev, " buffer=%dB period=%dB period_size=%dB periods=%d\n", + params_buffer_bytes(p), params_period_bytes(p), + params_period_size(p), params_periods(p)); + dev_dbg(dev, " buffer_size=%d access=%d data_rate=%dB/s\n", + params_buffer_size(p), params_access(p), + params_rate(p) * params_channels(p) * + snd_pcm_format_width(params_format(p)) / 8); +} + +#define INVALID_FORMAT -1 + +static const snd_pcm_format_t hpi_to_alsa_formats[] = { + INVALID_FORMAT, /* INVALID */ + SNDRV_PCM_FORMAT_U8, /* HPI_FORMAT_PCM8_UNSIGNED 1 */ + SNDRV_PCM_FORMAT_S16, /* HPI_FORMAT_PCM16_SIGNED 2 */ + INVALID_FORMAT, /* HPI_FORMAT_MPEG_L1 3 */ + SNDRV_PCM_FORMAT_MPEG, /* HPI_FORMAT_MPEG_L2 4 */ + SNDRV_PCM_FORMAT_MPEG, /* HPI_FORMAT_MPEG_L3 5 */ + INVALID_FORMAT, /* HPI_FORMAT_DOLBY_AC2 6 */ + INVALID_FORMAT, /* HPI_FORMAT_DOLBY_AC3 7 */ + SNDRV_PCM_FORMAT_S16_BE,/* HPI_FORMAT_PCM16_BIGENDIAN 8 */ + INVALID_FORMAT, /* HPI_FORMAT_AA_TAGIT1_HITS 9 */ + INVALID_FORMAT, /* HPI_FORMAT_AA_TAGIT1_INSERTS 10 */ + SNDRV_PCM_FORMAT_S32, /* HPI_FORMAT_PCM32_SIGNED 11 */ + INVALID_FORMAT, /* HPI_FORMAT_RAW_BITSTREAM 12 */ + INVALID_FORMAT, /* HPI_FORMAT_AA_TAGIT1_HITS_EX1 13 */ + SNDRV_PCM_FORMAT_FLOAT, /* HPI_FORMAT_PCM32_FLOAT 14 */ +#if 1 + /* ALSA can't handle 3 byte sample size together with power-of-2 + * constraint on buffer_bytes, so disable this format + */ + INVALID_FORMAT +#else + /* SNDRV_PCM_FORMAT_S24_3LE */ /* HPI_FORMAT_PCM24_SIGNED 15 */ +#endif +}; + + +static int snd_card_asihpi_format_alsa2hpi(struct snd_card_asihpi *asihpi, + snd_pcm_format_t alsa_format, + u16 *hpi_format) +{ + u16 format; + + for (format = HPI_FORMAT_PCM8_UNSIGNED; + format <= HPI_FORMAT_PCM24_SIGNED; format++) { + if (hpi_to_alsa_formats[format] == alsa_format) { + *hpi_format = format; + return 0; + } + } + + dev_dbg(asihpi->card->dev, "failed match for alsa format %d\n", + alsa_format); + *hpi_format = 0; + return -EINVAL; +} + +static void snd_card_asihpi_pcm_samplerates(struct snd_card_asihpi *asihpi, + struct snd_pcm_hardware *pcmhw) +{ + u16 err; + u32 h_control; + u32 sample_rate; + int idx; + unsigned int rate_min = 200000; + unsigned int rate_max = 0; + unsigned int rates = 0; + + if (asihpi->support_mrx) { + rates |= SNDRV_PCM_RATE_CONTINUOUS; + rates |= SNDRV_PCM_RATE_8000_96000; + rate_min = 8000; + rate_max = 100000; + } else { + /* on cards without SRC, + valid rates are determined by sampleclock */ + err = hpi_mixer_get_control(asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + if (err) { + dev_err(&asihpi->pci->dev, + "No local sampleclock, err %d\n", err); + } + + for (idx = -1; idx < 100; idx++) { + if (idx == -1) { + if (hpi_sample_clock_get_sample_rate(h_control, + &sample_rate)) + continue; + } else if (hpi_sample_clock_query_local_rate(h_control, + idx, &sample_rate)) { + break; + } + + rate_min = min(rate_min, sample_rate); + rate_max = max(rate_max, sample_rate); + + switch (sample_rate) { + case 5512: + rates |= SNDRV_PCM_RATE_5512; + break; + case 8000: + rates |= SNDRV_PCM_RATE_8000; + break; + case 11025: + rates |= SNDRV_PCM_RATE_11025; + break; + case 16000: + rates |= SNDRV_PCM_RATE_16000; + break; + case 22050: + rates |= SNDRV_PCM_RATE_22050; + break; + case 32000: + rates |= SNDRV_PCM_RATE_32000; + break; + case 44100: + rates |= SNDRV_PCM_RATE_44100; + break; + case 48000: + rates |= SNDRV_PCM_RATE_48000; + break; + case 64000: + rates |= SNDRV_PCM_RATE_64000; + break; + case 88200: + rates |= SNDRV_PCM_RATE_88200; + break; + case 96000: + rates |= SNDRV_PCM_RATE_96000; + break; + case 176400: + rates |= SNDRV_PCM_RATE_176400; + break; + case 192000: + rates |= SNDRV_PCM_RATE_192000; + break; + default: /* some other rate */ + rates |= SNDRV_PCM_RATE_KNOT; + } + } + } + + pcmhw->rates = rates; + pcmhw->rate_min = rate_min; + pcmhw->rate_max = rate_max; +} + +static int snd_card_asihpi_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); + int err; + u16 format; + int width; + unsigned int bytes_per_sec; + + print_hwparams(substream, params); + err = snd_card_asihpi_format_alsa2hpi(card, params_format(params), &format); + if (err) + return err; + + hpi_handle_error(hpi_format_create(&dpcm->format, + params_channels(params), + format, params_rate(params), 0, 0)); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (hpi_instream_reset(dpcm->h_stream) != 0) + return -EINVAL; + + if (hpi_instream_set_format( + dpcm->h_stream, &dpcm->format) != 0) + return -EINVAL; + } + + dpcm->hpi_buffer_attached = 0; + if (card->can_dma) { + err = hpi_stream_host_buffer_attach(dpcm->h_stream, + params_buffer_bytes(params), runtime->dma_addr); + if (err == 0) { + dev_dbg(card->card->dev, + "stream_host_buffer_attach success %u %lu\n", + params_buffer_bytes(params), + (unsigned long)runtime->dma_addr); + } else { + dev_dbg(card->card->dev, + "stream_host_buffer_attach error %d\n", err); + return -ENOMEM; + } + + hpi_stream_get_info_ex(dpcm->h_stream, NULL, + &dpcm->hpi_buffer_attached, NULL, NULL, NULL); + } + bytes_per_sec = params_rate(params) * params_channels(params); + width = snd_pcm_format_width(params_format(params)); + bytes_per_sec *= width; + bytes_per_sec /= 8; + if (width < 0 || bytes_per_sec == 0) + return -EINVAL; + + dpcm->bytes_per_sec = bytes_per_sec; + dpcm->buffer_bytes = params_buffer_bytes(params); + dpcm->period_bytes = params_period_bytes(params); + + return 0; +} + +static int +snd_card_asihpi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + if (dpcm->hpi_buffer_attached) + hpi_stream_host_buffer_detach(dpcm->h_stream); + + return 0; +} + +static void snd_card_asihpi_runtime_free(struct snd_pcm_runtime *runtime) +{ + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + kfree(dpcm); +} + +static void snd_card_asihpi_pcm_timer_start(struct snd_pcm_substream * + substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + int expiry; + + expiry = HZ / 200; + + expiry = max(expiry, 1); /* don't let it be zero! */ + mod_timer(&dpcm->timer, jiffies + expiry); + dpcm->respawn_timer = 1; +} + +static void snd_card_asihpi_pcm_timer_stop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + dpcm->respawn_timer = 0; + timer_delete(&dpcm->timer); +} + +static void snd_card_asihpi_pcm_int_start(struct snd_pcm_substream *substream) +{ + struct snd_card_asihpi_pcm *dpcm; + struct snd_card_asihpi *card; + + dpcm = (struct snd_card_asihpi_pcm *)substream->runtime->private_data; + card = snd_pcm_substream_chip(substream); + + WARN_ON(in_interrupt()); + card->llmode_streampriv = dpcm; + + hpi_handle_error(hpi_adapter_set_property(card->hpi->adapter->index, + HPI_ADAPTER_PROPERTY_IRQ_RATE, + card->update_interval_frames, 0)); +} + +static void snd_card_asihpi_pcm_int_stop(struct snd_pcm_substream *substream) +{ + struct snd_card_asihpi *card; + + card = snd_pcm_substream_chip(substream); + + hpi_handle_error(hpi_adapter_set_property(card->hpi->adapter->index, + HPI_ADAPTER_PROPERTY_IRQ_RATE, 0, 0)); + + card->llmode_streampriv = NULL; +} + +static int snd_card_asihpi_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data; + struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + u16 e; + char name[16]; + + snd_pcm_debug_name(substream, name, sizeof(name)); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev_dbg(card->card->dev, "%s trigger start\n", name); + snd_pcm_group_for_each_entry(s, substream) { + struct snd_pcm_runtime *runtime = s->runtime; + struct snd_card_asihpi_pcm *ds = runtime->private_data; + + if (snd_pcm_substream_chip(s) != card) + continue; + + /* don't link Cap and Play */ + if (substream->stream != s->stream) + continue; + + ds->drained_count = 0; + if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* How do I know how much valid data is present + * in buffer? Must be at least one period! + * Guessing 2 periods, but if + * buffer is bigger it may contain even more + * data?? + */ + unsigned int preload = ds->period_bytes * 1; + asihpi_dbg("%d preload %d\n", s->number, preload); + hpi_handle_error(hpi_outstream_write_buf( + ds->h_stream, + &runtime->dma_area[0], + preload, + &ds->format)); + ds->pcm_buf_host_rw_ofs = preload; + } + + if (card->support_grouping) { + dev_dbg(card->card->dev, "%d group\n", s->number); + e = hpi_stream_group_add( + dpcm->h_stream, + ds->h_stream); + if (!e) { + snd_pcm_trigger_done(s, substream); + } else { + hpi_handle_error(e); + break; + } + } else + break; + } + /* start the master stream */ + card->pcm_start(substream); + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) || + !card->can_dma) + hpi_handle_error(hpi_stream_start(dpcm->h_stream)); + break; + + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(card->card->dev, "%s trigger stop\n", name); + card->pcm_stop(substream); + snd_pcm_group_for_each_entry(s, substream) { + if (snd_pcm_substream_chip(s) != card) + continue; + /* don't link Cap and Play */ + if (substream->stream != s->stream) + continue; + + /*? workaround linked streams don't + transition to SETUP 20070706*/ + __snd_pcm_set_state(s->runtime, SNDRV_PCM_STATE_SETUP); + + if (card->support_grouping) { + dev_dbg(card->card->dev, "%d group\n", s->number); + snd_pcm_trigger_done(s, substream); + } else + break; + } + + /* _prepare and _hwparams reset the stream */ + hpi_handle_error(hpi_stream_stop(dpcm->h_stream)); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + hpi_handle_error( + hpi_outstream_reset(dpcm->h_stream)); + + if (card->support_grouping) + hpi_handle_error(hpi_stream_group_reset(dpcm->h_stream)); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev_dbg(card->card->dev, "%s trigger pause release\n", name); + card->pcm_start(substream); + hpi_handle_error(hpi_stream_start(dpcm->h_stream)); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev_dbg(card->card->dev, "%s trigger pause push\n", name); + card->pcm_stop(substream); + hpi_handle_error(hpi_stream_stop(dpcm->h_stream)); + break; + default: + dev_dbg(card->card->dev, "\tINVALID\n"); + return -EINVAL; + } + + return 0; +} + +/*algorithm outline + Without linking degenerates to getting single stream pos etc + Without mmap 2nd loop degenerates to snd_pcm_period_elapsed +*/ +/* +pcm_buf_dma_ofs=get_buf_pos(s); +for_each_linked_stream(s) { + pcm_buf_dma_ofs=get_buf_pos(s); + min_buf_pos = modulo_min(min_buf_pos, pcm_buf_dma_ofs, buffer_bytes) + new_data = min(new_data, calc_new_data(pcm_buf_dma_ofs,irq_pos) +} +timer.expires = jiffies + predict_next_period_ready(min_buf_pos); +for_each_linked_stream(s) { + s->pcm_buf_dma_ofs = min_buf_pos; + if (new_data > period_bytes) { + if (mmap) { + irq_pos = (irq_pos + period_bytes) % buffer_bytes; + if (playback) { + write(period_bytes); + } else { + read(period_bytes); + } + } + snd_pcm_period_elapsed(s); + } +} +*/ + +/** Minimum of 2 modulo values. Works correctly when the difference between +* the values is less than half the modulus +*/ +static inline unsigned int modulo_min(unsigned int a, unsigned int b, + unsigned long int modulus) +{ + unsigned int result; + if (((a-b) % modulus) < (modulus/2)) + result = b; + else + result = a; + + return result; +} + +/** Timer function, equivalent to interrupt service routine for cards +*/ +static void snd_card_asihpi_timer_function(struct timer_list *t) +{ + struct snd_card_asihpi_pcm *dpcm = timer_container_of(dpcm, t, timer); + struct snd_pcm_substream *substream = dpcm->substream; + struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime; + struct snd_pcm_substream *s; + unsigned int newdata = 0; + unsigned int pcm_buf_dma_ofs, min_buf_pos = 0; + unsigned int remdata, xfercount, next_jiffies; + int first = 1; + u16 state; + u32 buffer_size, bytes_avail, samples_played, on_card_bytes; + char name[16]; + + + snd_pcm_debug_name(substream, name, sizeof(name)); + + /* find minimum newdata and buffer pos in group */ + snd_pcm_group_for_each_entry(s, substream) { + struct snd_card_asihpi_pcm *ds = s->runtime->private_data; + runtime = s->runtime; + + if (snd_pcm_substream_chip(s) != card) + continue; + + /* don't link Cap and Play */ + if (substream->stream != s->stream) + continue; + + hpi_handle_error(hpi_stream_get_info_ex( + ds->h_stream, &state, + &buffer_size, &bytes_avail, + &samples_played, &on_card_bytes)); + + /* number of bytes in on-card buffer */ + runtime->delay = on_card_bytes; + + if (!card->can_dma) + on_card_bytes = bytes_avail; + + if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pcm_buf_dma_ofs = ds->pcm_buf_host_rw_ofs - bytes_avail; + if (state == HPI_STATE_STOPPED) { + if (bytes_avail == 0) { + hpi_handle_error(hpi_stream_start(ds->h_stream)); + dev_dbg(card->card->dev, + "P%d start\n", s->number); + ds->drained_count = 0; + } + } else if (state == HPI_STATE_DRAINED) { + dev_dbg(card->card->dev, + "P%d drained\n", s->number); + ds->drained_count++; + if (ds->drained_count > 20) { + snd_pcm_stop_xrun(s); + continue; + } + } else { + ds->drained_count = 0; + } + } else + pcm_buf_dma_ofs = bytes_avail + ds->pcm_buf_host_rw_ofs; + + if (first) { + /* can't statically init min when wrap is involved */ + min_buf_pos = pcm_buf_dma_ofs; + newdata = (pcm_buf_dma_ofs - ds->pcm_buf_elapsed_dma_ofs) % ds->buffer_bytes; + first = 0; + } else { + min_buf_pos = + modulo_min(min_buf_pos, pcm_buf_dma_ofs, UINT_MAX+1L); + newdata = min( + (pcm_buf_dma_ofs - ds->pcm_buf_elapsed_dma_ofs) % ds->buffer_bytes, + newdata); + } + + asihpi_dbg( + "timer1, %s, %d, S=%d, elap=%d, rw=%d, dsp=%d, left=%d, aux=%d, space=%d, hw_ptr=%ld, appl_ptr=%ld\n", + name, s->number, state, + ds->pcm_buf_elapsed_dma_ofs, + ds->pcm_buf_host_rw_ofs, + pcm_buf_dma_ofs, + (int)bytes_avail, + + (int)on_card_bytes, + buffer_size-bytes_avail, + (unsigned long)frames_to_bytes(runtime, + runtime->status->hw_ptr), + (unsigned long)frames_to_bytes(runtime, + runtime->control->appl_ptr) + ); + } + pcm_buf_dma_ofs = min_buf_pos; + + remdata = newdata % dpcm->period_bytes; + xfercount = newdata - remdata; /* a multiple of period_bytes */ + /* come back when on_card_bytes has decreased enough to allow + write to happen, or when data has been consumed to make another + period + */ + if (xfercount && (on_card_bytes > dpcm->period_bytes)) + next_jiffies = ((on_card_bytes - dpcm->period_bytes) * HZ / dpcm->bytes_per_sec); + else + next_jiffies = ((dpcm->period_bytes - remdata) * HZ / dpcm->bytes_per_sec); + + next_jiffies = max(next_jiffies, 1U); + dpcm->timer.expires = jiffies + next_jiffies; + asihpi_dbg("timer2, jif=%d, buf_pos=%d, newdata=%d, xfer=%d\n", + next_jiffies, pcm_buf_dma_ofs, newdata, xfercount); + + snd_pcm_group_for_each_entry(s, substream) { + struct snd_card_asihpi_pcm *ds = s->runtime->private_data; + + /* don't link Cap and Play */ + if (substream->stream != s->stream) + continue; + + /* Store dma offset for use by pointer callback */ + ds->pcm_buf_dma_ofs = pcm_buf_dma_ofs; + + if (xfercount && + /* Limit use of on card fifo for playback */ + ((on_card_bytes <= ds->period_bytes) || + (s->stream == SNDRV_PCM_STREAM_CAPTURE))) + + { + + unsigned int buf_ofs = ds->pcm_buf_host_rw_ofs % ds->buffer_bytes; + unsigned int xfer1, xfer2; + char *pd = &s->runtime->dma_area[buf_ofs]; + + if (card->can_dma) { /* buffer wrap is handled at lower level */ + xfer1 = xfercount; + xfer2 = 0; + } else { + xfer1 = min(xfercount, ds->buffer_bytes - buf_ofs); + xfer2 = xfercount - xfer1; + } + + if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { + asihpi_dbg("write1, P=%d, xfer=%d, buf_ofs=%d\n", + s->number, xfer1, buf_ofs); + hpi_handle_error( + hpi_outstream_write_buf( + ds->h_stream, pd, xfer1, + &ds->format)); + + if (xfer2) { + pd = s->runtime->dma_area; + + asihpi_dbg("write2, P=%d, xfer=%d, buf_ofs=%d\n", + s->number, + xfercount - xfer1, buf_ofs); + hpi_handle_error( + hpi_outstream_write_buf( + ds->h_stream, pd, + xfercount - xfer1, + &ds->format)); + } + } else { + asihpi_dbg("read1, C=%d, xfer=%d\n", + s->number, xfer1); + hpi_handle_error( + hpi_instream_read_buf( + ds->h_stream, + pd, xfer1)); + if (xfer2) { + pd = s->runtime->dma_area; + asihpi_dbg("read2, C=%d, xfer=%d\n", + s->number, xfer2); + hpi_handle_error( + hpi_instream_read_buf( + ds->h_stream, + pd, xfer2)); + } + } + /* ? host_rw_ofs always ahead of elapsed_dma_ofs by preload size? */ + ds->pcm_buf_host_rw_ofs += xfercount; + ds->pcm_buf_elapsed_dma_ofs += xfercount; + snd_pcm_period_elapsed(s); + } + } + + if (!card->hpi->interrupt_mode && dpcm->respawn_timer) + add_timer(&dpcm->timer); +} + +static void snd_card_asihpi_isr(struct hpi_adapter *a) +{ + struct snd_card_asihpi *asihpi; + + WARN_ON(!a || !a->snd_card || !a->snd_card->private_data); + asihpi = (struct snd_card_asihpi *)a->snd_card->private_data; + if (asihpi->llmode_streampriv) + snd_card_asihpi_timer_function( + &asihpi->llmode_streampriv->timer); +} + +/***************************** PLAYBACK OPS ****************/ +static int snd_card_asihpi_playback_prepare(struct snd_pcm_substream * + substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + hpi_handle_error(hpi_outstream_reset(dpcm->h_stream)); + dpcm->pcm_buf_host_rw_ofs = 0; + dpcm->pcm_buf_dma_ofs = 0; + dpcm->pcm_buf_elapsed_dma_ofs = 0; + return 0; +} + +static snd_pcm_uframes_t +snd_card_asihpi_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + snd_pcm_uframes_t ptr; + char name[16]; + snd_pcm_debug_name(substream, name, sizeof(name)); + + ptr = bytes_to_frames(runtime, dpcm->pcm_buf_dma_ofs % dpcm->buffer_bytes); + asihpi_dbg("%s, pointer=%ld\n", name, (unsigned long)ptr); + return ptr; +} + +static u64 snd_card_asihpi_playback_formats(struct snd_card_asihpi *asihpi, + u32 h_stream) +{ + struct hpi_format hpi_format; + u16 format; + u16 err; + u32 h_control; + u32 sample_rate = 48000; + u64 formats = 0; + + /* on cards without SRC, must query at valid rate, + * maybe set by external sync + */ + err = hpi_mixer_get_control(asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + + if (!err) + err = hpi_sample_clock_get_sample_rate(h_control, + &sample_rate); + + for (format = HPI_FORMAT_PCM8_UNSIGNED; + format <= HPI_FORMAT_PCM24_SIGNED; format++) { + err = hpi_format_create(&hpi_format, asihpi->out_max_chans, + format, sample_rate, 128000, 0); + if (!err) + err = hpi_outstream_query_format(h_stream, &hpi_format); + if (!err && (hpi_to_alsa_formats[format] != INVALID_FORMAT)) + formats |= pcm_format_to_bits(hpi_to_alsa_formats[format]); + } + return formats; +} + +static int snd_card_asihpi_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm; + struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); + struct snd_pcm_hardware snd_card_asihpi_playback; + int err; + + dpcm = kzalloc_obj(*dpcm); + if (dpcm == NULL) + return -ENOMEM; + + err = hpi_outstream_open(card->hpi->adapter->index, + substream->number, &dpcm->h_stream); + hpi_handle_error(err); + if (err) { + kfree(dpcm); + if (err == HPI_ERROR_OBJ_ALREADY_OPEN) + return -EBUSY; + return -EIO; + } + + /*? also check ASI5000 samplerate source + If external, only support external rate. + If internal and other stream playing, can't switch + */ + + timer_setup(&dpcm->timer, snd_card_asihpi_timer_function, 0); + dpcm->substream = substream; + runtime->private_data = dpcm; + runtime->private_free = snd_card_asihpi_runtime_free; + + memset(&snd_card_asihpi_playback, 0, sizeof(snd_card_asihpi_playback)); + if (!card->hpi->interrupt_mode) { + snd_card_asihpi_playback.buffer_bytes_max = BUFFER_BYTES_MAX; + snd_card_asihpi_playback.period_bytes_min = PERIOD_BYTES_MIN; + snd_card_asihpi_playback.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN; + snd_card_asihpi_playback.periods_min = PERIODS_MIN; + snd_card_asihpi_playback.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN; + } else { + size_t pbmin = card->update_interval_frames * + card->out_max_chans; + snd_card_asihpi_playback.buffer_bytes_max = BUFFER_BYTES_MAX; + snd_card_asihpi_playback.period_bytes_min = pbmin; + snd_card_asihpi_playback.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN; + snd_card_asihpi_playback.periods_min = PERIODS_MIN; + snd_card_asihpi_playback.periods_max = BUFFER_BYTES_MAX / pbmin; + } + + /* snd_card_asihpi_playback.fifo_size = 0; */ + snd_card_asihpi_playback.channels_max = card->out_max_chans; + snd_card_asihpi_playback.channels_min = card->out_min_chans; + snd_card_asihpi_playback.formats = + snd_card_asihpi_playback_formats(card, dpcm->h_stream); + + snd_card_asihpi_pcm_samplerates(card, &snd_card_asihpi_playback); + + snd_card_asihpi_playback.info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_DOUBLE | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (card->support_grouping) { + snd_card_asihpi_playback.info |= SNDRV_PCM_INFO_SYNC_START; + snd_pcm_set_sync(substream); + } + + /* struct is copied, so can create initializer dynamically */ + runtime->hw = snd_card_asihpi_playback; + + if (card->can_dma) + err = snd_pcm_hw_constraint_pow2(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES); + if (err < 0) + return err; + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + card->update_interval_frames); + + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + card->update_interval_frames, UINT_MAX); + + return 0; +} + +static int snd_card_asihpi_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + hpi_handle_error(hpi_outstream_close(dpcm->h_stream)); + return 0; +} + +static const struct snd_pcm_ops snd_card_asihpi_playback_mmap_ops = { + .open = snd_card_asihpi_playback_open, + .close = snd_card_asihpi_playback_close, + .hw_params = snd_card_asihpi_pcm_hw_params, + .hw_free = snd_card_asihpi_hw_free, + .prepare = snd_card_asihpi_playback_prepare, + .trigger = snd_card_asihpi_trigger, + .pointer = snd_card_asihpi_playback_pointer, +}; + +/***************************** CAPTURE OPS ****************/ +static snd_pcm_uframes_t +snd_card_asihpi_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + char name[16]; + snd_pcm_debug_name(substream, name, sizeof(name)); + + asihpi_dbg("%s, pointer=%d\n", name, dpcm->pcm_buf_dma_ofs); + /* NOTE Unlike playback can't use actual samples_played + for the capture position, because those samples aren't yet in + the local buffer available for reading. + */ + return bytes_to_frames(runtime, dpcm->pcm_buf_dma_ofs % dpcm->buffer_bytes); +} + +static int snd_card_asihpi_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + hpi_handle_error(hpi_instream_reset(dpcm->h_stream)); + dpcm->pcm_buf_host_rw_ofs = 0; + dpcm->pcm_buf_dma_ofs = 0; + dpcm->pcm_buf_elapsed_dma_ofs = 0; + + return 0; +} + +static u64 snd_card_asihpi_capture_formats(struct snd_card_asihpi *asihpi, + u32 h_stream) +{ + struct hpi_format hpi_format; + u16 format; + u16 err; + u32 h_control; + u32 sample_rate = 48000; + u64 formats = 0; + + /* on cards without SRC, must query at valid rate, + maybe set by external sync */ + err = hpi_mixer_get_control(asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + + if (!err) + err = hpi_sample_clock_get_sample_rate(h_control, + &sample_rate); + + for (format = HPI_FORMAT_PCM8_UNSIGNED; + format <= HPI_FORMAT_PCM24_SIGNED; format++) { + + err = hpi_format_create(&hpi_format, asihpi->in_max_chans, + format, sample_rate, 128000, 0); + if (!err) + err = hpi_instream_query_format(h_stream, &hpi_format); + if (!err && (hpi_to_alsa_formats[format] != INVALID_FORMAT)) + formats |= pcm_format_to_bits(hpi_to_alsa_formats[format]); + } + return formats; +} + +static int snd_card_asihpi_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); + struct snd_card_asihpi_pcm *dpcm; + struct snd_pcm_hardware snd_card_asihpi_capture; + int err; + + dpcm = kzalloc_obj(*dpcm); + if (dpcm == NULL) + return -ENOMEM; + + + dev_dbg(card->card->dev, "capture open adapter %d stream %d\n", + card->hpi->adapter->index, substream->number); + + err = hpi_handle_error( + hpi_instream_open(card->hpi->adapter->index, + substream->number, &dpcm->h_stream)); + if (err) { + kfree(dpcm); + if (err == HPI_ERROR_OBJ_ALREADY_OPEN) + return -EBUSY; + return -EIO; + } + + timer_setup(&dpcm->timer, snd_card_asihpi_timer_function, 0); + dpcm->substream = substream; + runtime->private_data = dpcm; + runtime->private_free = snd_card_asihpi_runtime_free; + + memset(&snd_card_asihpi_capture, 0, sizeof(snd_card_asihpi_capture)); + if (!card->hpi->interrupt_mode) { + snd_card_asihpi_capture.buffer_bytes_max = BUFFER_BYTES_MAX; + snd_card_asihpi_capture.period_bytes_min = PERIOD_BYTES_MIN; + snd_card_asihpi_capture.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN; + snd_card_asihpi_capture.periods_min = PERIODS_MIN; + snd_card_asihpi_capture.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN; + } else { + size_t pbmin = card->update_interval_frames * + card->out_max_chans; + snd_card_asihpi_capture.buffer_bytes_max = BUFFER_BYTES_MAX; + snd_card_asihpi_capture.period_bytes_min = pbmin; + snd_card_asihpi_capture.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN; + snd_card_asihpi_capture.periods_min = PERIODS_MIN; + snd_card_asihpi_capture.periods_max = BUFFER_BYTES_MAX / pbmin; + } + /* snd_card_asihpi_capture.fifo_size = 0; */ + snd_card_asihpi_capture.channels_max = card->in_max_chans; + snd_card_asihpi_capture.channels_min = card->in_min_chans; + snd_card_asihpi_capture.formats = + snd_card_asihpi_capture_formats(card, dpcm->h_stream); + snd_card_asihpi_pcm_samplerates(card, &snd_card_asihpi_capture); + snd_card_asihpi_capture.info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (card->support_grouping) + snd_card_asihpi_capture.info |= SNDRV_PCM_INFO_SYNC_START; + + runtime->hw = snd_card_asihpi_capture; + + if (card->can_dma) + err = snd_pcm_hw_constraint_pow2(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES); + if (err < 0) + return err; + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + card->update_interval_frames); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + card->update_interval_frames, UINT_MAX); + + snd_pcm_set_sync(substream); + + return 0; +} + +static int snd_card_asihpi_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data; + + hpi_handle_error(hpi_instream_close(dpcm->h_stream)); + return 0; +} + +static const struct snd_pcm_ops snd_card_asihpi_capture_mmap_ops = { + .open = snd_card_asihpi_capture_open, + .close = snd_card_asihpi_capture_close, + .hw_params = snd_card_asihpi_pcm_hw_params, + .hw_free = snd_card_asihpi_hw_free, + .prepare = snd_card_asihpi_capture_prepare, + .trigger = snd_card_asihpi_trigger, + .pointer = snd_card_asihpi_capture_pointer, +}; + +static int snd_card_asihpi_pcm_new(struct snd_card_asihpi *asihpi, int device) +{ + struct snd_pcm *pcm; + int err; + u16 num_instreams, num_outstreams, x16; + u32 x32; + + err = hpi_adapter_get_info(asihpi->hpi->adapter->index, + &num_outstreams, &num_instreams, + &x16, &x32, &x16); + + err = snd_pcm_new(asihpi->card, "Asihpi PCM", device, + num_outstreams, num_instreams, &pcm); + if (err < 0) + return err; + + /* pointer to ops struct is stored, dont change ops afterwards! */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_asihpi_playback_mmap_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_card_asihpi_capture_mmap_ops); + + pcm->private_data = asihpi; + pcm->info_flags = 0; + strscpy(pcm->name, "Asihpi PCM"); + + /*? do we want to emulate MMAP for non-BBM cards? + Jack doesn't work with ALSAs MMAP emulation - WHY NOT? */ + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &asihpi->pci->dev, + 64*1024, BUFFER_BYTES_MAX); + + return 0; +} + +/***************************** MIXER CONTROLS ****************/ +struct hpi_control { + u32 h_control; + u16 control_type; + u16 src_node_type; + u16 src_node_index; + u16 dst_node_type; + u16 dst_node_index; + u16 band; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; /* copied to snd_ctl_elem_id.name[44]; */ +}; + +static const char * const asihpi_tuner_band_names[] = { + "invalid", + "AM", + "FM mono", + "TV NTSC-M", + "FM stereo", + "AUX", + "TV PAL BG", + "TV PAL I", + "TV PAL DK", + "TV SECAM", + "TV DAB", +}; +/* Number of strings must match the enumerations for HPI_TUNER_BAND in hpi.h */ +compile_time_assert( + (ARRAY_SIZE(asihpi_tuner_band_names) == + (HPI_TUNER_BAND_LAST+1)), + assert_tuner_band_names_size); + +static const char * const asihpi_src_names[] = { + "no source", + "PCM", + "Line", + "Digital", + "Tuner", + "RF", + "Clock", + "Bitstream", + "Mic", + "Net", + "Analog", + "Adapter", + "RTP", + "Internal", + "AVB", + "BLU-Link" +}; +/* Number of strings must match the enumerations for HPI_SOURCENODES in hpi.h */ +compile_time_assert( + (ARRAY_SIZE(asihpi_src_names) == + (HPI_SOURCENODE_LAST_INDEX-HPI_SOURCENODE_NONE+1)), + assert_src_names_size); + +static const char * const asihpi_dst_names[] = { + "no destination", + "PCM", + "Line", + "Digital", + "RF", + "Speaker", + "Net", + "Analog", + "RTP", + "AVB", + "Internal", + "BLU-Link" +}; +/* Number of strings must match the enumerations for HPI_DESTNODES in hpi.h */ +compile_time_assert( + (ARRAY_SIZE(asihpi_dst_names) == + (HPI_DESTNODE_LAST_INDEX-HPI_DESTNODE_NONE+1)), + assert_dst_names_size); + +static inline int ctl_add(struct snd_card *card, struct snd_kcontrol_new *ctl, + struct snd_card_asihpi *asihpi) +{ + int err; + + err = snd_ctl_add(card, snd_ctl_new1(ctl, asihpi)); + if (err < 0) + return err; + else if (mixer_dump) + dev_info(&asihpi->pci->dev, "added %s(%d)\n", ctl->name, ctl->index); + + return 0; +} + +/* Convert HPI control name and location into ALSA control name */ +static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control, + struct hpi_control *hpi_ctl, + char *name) +{ + int len; + char *dir; + memset(snd_control, 0, sizeof(*snd_control)); + snd_control->name = hpi_ctl->name; + snd_control->private_value = hpi_ctl->h_control; + snd_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + snd_control->index = 0; + + if (hpi_ctl->src_node_type + HPI_SOURCENODE_NONE == HPI_SOURCENODE_CLOCK_SOURCE) + dir = ""; /* clock is neither capture nor playback */ + else if (hpi_ctl->dst_node_type + HPI_DESTNODE_NONE == HPI_DESTNODE_ISTREAM) + dir = "Capture "; /* On or towards a PCM capture destination*/ + else if ((hpi_ctl->src_node_type + HPI_SOURCENODE_NONE != HPI_SOURCENODE_OSTREAM) && + (!hpi_ctl->dst_node_type)) + dir = "Capture "; /* On a source node that is not PCM playback */ + else if (hpi_ctl->src_node_type && + (hpi_ctl->src_node_type + HPI_SOURCENODE_NONE != HPI_SOURCENODE_OSTREAM) && + (hpi_ctl->dst_node_type)) + dir = "Monitor Playback "; /* Between an input and an output */ + else + dir = "Playback "; /* PCM Playback source, or output node */ + + if (hpi_ctl->src_node_type && hpi_ctl->dst_node_type) + len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name), + "%s %d %s %d %s%s", + asihpi_src_names[hpi_ctl->src_node_type], + hpi_ctl->src_node_index, + asihpi_dst_names[hpi_ctl->dst_node_type], + hpi_ctl->dst_node_index, + dir, name); + else if (hpi_ctl->dst_node_type) { + len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name), + "%s %d %s%s", + asihpi_dst_names[hpi_ctl->dst_node_type], + hpi_ctl->dst_node_index, + dir, name); + } else { + len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name), + "%s %d %s%s", + asihpi_src_names[hpi_ctl->src_node_type], + hpi_ctl->src_node_index, + dir, name); + } + + if (len >= sizeof(hpi_ctl->name)) + pr_err("asihpi: truncated control name: %s\n", + hpi_ctl->name); +} + +/*------------------------------------------------------------ + Volume controls + ------------------------------------------------------------*/ +#define VOL_STEP_mB 1 +static int snd_asihpi_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 h_control = kcontrol->private_value; + u32 count; + u16 err; + /* native gains are in millibels */ + short min_gain_mB; + short max_gain_mB; + short step_gain_mB; + + err = hpi_volume_query_range(h_control, + &min_gain_mB, &max_gain_mB, &step_gain_mB); + if (err) { + max_gain_mB = 0; + min_gain_mB = -10000; + step_gain_mB = VOL_STEP_mB; + } + + err = hpi_meter_query_channels(h_control, &count); + if (err) + count = HPI_MAX_CHANNELS; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = count; + uinfo->value.integer.min = min_gain_mB / VOL_STEP_mB; + uinfo->value.integer.max = max_gain_mB / VOL_STEP_mB; + uinfo->value.integer.step = step_gain_mB / VOL_STEP_mB; + return 0; +} + +static int snd_asihpi_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS]; + + hpi_handle_error(hpi_volume_get_gain(h_control, an_gain_mB)); + ucontrol->value.integer.value[0] = an_gain_mB[0] / VOL_STEP_mB; + ucontrol->value.integer.value[1] = an_gain_mB[1] / VOL_STEP_mB; + + return 0; +} + +static int snd_asihpi_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS]; + + an_gain_mB[0] = + (ucontrol->value.integer.value[0]) * VOL_STEP_mB; + an_gain_mB[1] = + (ucontrol->value.integer.value[1]) * VOL_STEP_mB; + /* change = asihpi->mixer_volume[addr][0] != left || + asihpi->mixer_volume[addr][1] != right; + */ + hpi_handle_error(hpi_volume_set_gain(h_control, an_gain_mB)); + return 1; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_100, -10000, VOL_STEP_mB, 0); + +#define snd_asihpi_volume_mute_info snd_ctl_boolean_mono_info + +static int snd_asihpi_volume_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u32 mute; + + hpi_handle_error(hpi_volume_get_mute(h_control, &mute)); + ucontrol->value.integer.value[0] = mute ? 0 : 1; + + return 0; +} + +static int snd_asihpi_volume_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + /* HPI currently only supports all or none muting of multichannel volume + ALSA Switch element has opposite sense to HPI mute: on==unmuted, off=muted + */ + int mute = ucontrol->value.integer.value[0] ? 0 : HPI_BITMASK_ALL_CHANNELS; + hpi_handle_error(hpi_volume_set_mute(h_control, mute)); + return 1; +} + +static int snd_asihpi_volume_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + int err; + u32 mute; + + asihpi_ctl_init(&snd_control, hpi_ctl, "Volume"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + snd_control.info = snd_asihpi_volume_info; + snd_control.get = snd_asihpi_volume_get; + snd_control.put = snd_asihpi_volume_put; + snd_control.tlv.p = db_scale_100; + + err = ctl_add(card, &snd_control, asihpi); + if (err) + return err; + + if (hpi_volume_get_mute(hpi_ctl->h_control, &mute) == 0) { + asihpi_ctl_init(&snd_control, hpi_ctl, "Switch"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_control.info = snd_asihpi_volume_mute_info; + snd_control.get = snd_asihpi_volume_mute_get; + snd_control.put = snd_asihpi_volume_mute_put; + err = ctl_add(card, &snd_control, asihpi); + } + return err; +} + +/*------------------------------------------------------------ + Level controls + ------------------------------------------------------------*/ +static int snd_asihpi_level_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 h_control = kcontrol->private_value; + u16 err; + short min_gain_mB; + short max_gain_mB; + short step_gain_mB; + + err = + hpi_level_query_range(h_control, &min_gain_mB, + &max_gain_mB, &step_gain_mB); + if (err) { + max_gain_mB = 2400; + min_gain_mB = -1000; + step_gain_mB = 100; + } + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = min_gain_mB / HPI_UNITS_PER_dB; + uinfo->value.integer.max = max_gain_mB / HPI_UNITS_PER_dB; + uinfo->value.integer.step = step_gain_mB / HPI_UNITS_PER_dB; + return 0; +} + +static int snd_asihpi_level_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS]; + + hpi_handle_error(hpi_level_get_gain(h_control, an_gain_mB)); + ucontrol->value.integer.value[0] = + an_gain_mB[0] / HPI_UNITS_PER_dB; + ucontrol->value.integer.value[1] = + an_gain_mB[1] / HPI_UNITS_PER_dB; + + return 0; +} + +static int snd_asihpi_level_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS]; + + an_gain_mB[0] = + (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB; + an_gain_mB[1] = + (ucontrol->value.integer.value[1]) * HPI_UNITS_PER_dB; + /* change = asihpi->mixer_level[addr][0] != left || + asihpi->mixer_level[addr][1] != right; + */ + change = 1; + hpi_handle_error(hpi_level_set_gain(h_control, an_gain_mB)); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_level, -1000, 100, 0); + +static int snd_asihpi_level_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + /* can't use 'volume' cos some nodes have volume as well */ + asihpi_ctl_init(&snd_control, hpi_ctl, "Level"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + snd_control.info = snd_asihpi_level_info; + snd_control.get = snd_asihpi_level_get; + snd_control.put = snd_asihpi_level_put; + snd_control.tlv.p = db_scale_level; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + AESEBU controls + ------------------------------------------------------------*/ + +/* AESEBU format */ +static const char * const asihpi_aesebu_format_names[] = { + "N/A", "S/PDIF", "AES/EBU" }; + +static int snd_asihpi_aesebu_format_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 3, asihpi_aesebu_format_names); +} + +static int snd_asihpi_aesebu_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + u16 (*func)(u32, u16 *)) +{ + u32 h_control = kcontrol->private_value; + u16 source, err; + + err = func(h_control, &source); + + /* default to N/A */ + ucontrol->value.enumerated.item[0] = 0; + /* return success but set the control to N/A */ + if (err) + return 0; + if (source == HPI_AESEBU_FORMAT_SPDIF) + ucontrol->value.enumerated.item[0] = 1; + if (source == HPI_AESEBU_FORMAT_AESEBU) + ucontrol->value.enumerated.item[0] = 2; + + return 0; +} + +static int snd_asihpi_aesebu_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + u16 (*func)(u32, u16)) +{ + u32 h_control = kcontrol->private_value; + + /* default to S/PDIF */ + u16 source = HPI_AESEBU_FORMAT_SPDIF; + + if (ucontrol->value.enumerated.item[0] == 1) + source = HPI_AESEBU_FORMAT_SPDIF; + if (ucontrol->value.enumerated.item[0] == 2) + source = HPI_AESEBU_FORMAT_AESEBU; + + if (func(h_control, source) != 0) + return -EINVAL; + + return 1; +} + +static int snd_asihpi_aesebu_rx_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + return snd_asihpi_aesebu_format_get(kcontrol, ucontrol, + hpi_aesebu_receiver_get_format); +} + +static int snd_asihpi_aesebu_rx_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + return snd_asihpi_aesebu_format_put(kcontrol, ucontrol, + hpi_aesebu_receiver_set_format); +} + +static int snd_asihpi_aesebu_rxstatus_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 = 0X1F; + uinfo->value.integer.step = 1; + + return 0; +} + +static int snd_asihpi_aesebu_rxstatus_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + + u32 h_control = kcontrol->private_value; + u16 status; + + hpi_handle_error(hpi_aesebu_receiver_get_error_status( + h_control, &status)); + ucontrol->value.integer.value[0] = status; + return 0; +} + +static int snd_asihpi_aesebu_rx_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "Format"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_control.info = snd_asihpi_aesebu_format_info; + snd_control.get = snd_asihpi_aesebu_rx_format_get; + snd_control.put = snd_asihpi_aesebu_rx_format_put; + + + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + + asihpi_ctl_init(&snd_control, hpi_ctl, "Status"); + snd_control.access = + SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ; + snd_control.info = snd_asihpi_aesebu_rxstatus_info; + snd_control.get = snd_asihpi_aesebu_rxstatus_get; + + return ctl_add(card, &snd_control, asihpi); +} + +static int snd_asihpi_aesebu_tx_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + return snd_asihpi_aesebu_format_get(kcontrol, ucontrol, + hpi_aesebu_transmitter_get_format); +} + +static int snd_asihpi_aesebu_tx_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + return snd_asihpi_aesebu_format_put(kcontrol, ucontrol, + hpi_aesebu_transmitter_set_format); +} + + +static int snd_asihpi_aesebu_tx_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "Format"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_control.info = snd_asihpi_aesebu_format_info; + snd_control.get = snd_asihpi_aesebu_tx_format_get; + snd_control.put = snd_asihpi_aesebu_tx_format_put; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + Tuner controls + ------------------------------------------------------------*/ + +/* Gain */ + +static int snd_asihpi_tuner_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 h_control = kcontrol->private_value; + u16 err; + short idx; + u16 gain_range[3]; + + for (idx = 0; idx < 3; idx++) { + err = hpi_tuner_query_gain(h_control, + idx, &gain_range[idx]); + if (err != 0) + return err; + } + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = ((int)gain_range[0]) / HPI_UNITS_PER_dB; + uinfo->value.integer.max = ((int)gain_range[1]) / HPI_UNITS_PER_dB; + uinfo->value.integer.step = ((int) gain_range[2]) / HPI_UNITS_PER_dB; + return 0; +} + +static int snd_asihpi_tuner_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + */ + u32 h_control = kcontrol->private_value; + short gain; + + hpi_handle_error(hpi_tuner_get_gain(h_control, &gain)); + ucontrol->value.integer.value[0] = gain / HPI_UNITS_PER_dB; + + return 0; +} + +static int snd_asihpi_tuner_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + */ + u32 h_control = kcontrol->private_value; + short gain; + + gain = (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB; + hpi_handle_error(hpi_tuner_set_gain(h_control, gain)); + + return 1; +} + +/* Band */ + +static int asihpi_tuner_band_query(struct snd_kcontrol *kcontrol, + u16 *band_list, u32 len) { + u32 h_control = kcontrol->private_value; + u16 err = 0; + u32 i; + + for (i = 0; i < len; i++) { + err = hpi_tuner_query_band( + h_control, i, &band_list[i]); + if (err != 0) + break; + } + + if (err && (err != HPI_ERROR_INVALID_OBJ_INDEX)) + return -EIO; + + return i; +} + +static int snd_asihpi_tuner_band_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u16 tuner_bands[HPI_TUNER_BAND_LAST]; + int num_bands = 0; + + num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, + HPI_TUNER_BAND_LAST); + + if (num_bands < 0) + return num_bands; + + return snd_ctl_enum_info(uinfo, 1, num_bands, asihpi_tuner_band_names); +} + +static int snd_asihpi_tuner_band_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + /* + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + */ + u16 band, idx; + u16 tuner_bands[HPI_TUNER_BAND_LAST]; + __always_unused u32 num_bands; + + num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, + HPI_TUNER_BAND_LAST); + + hpi_handle_error(hpi_tuner_get_band(h_control, &band)); + + ucontrol->value.enumerated.item[0] = -1; + for (idx = 0; idx < HPI_TUNER_BAND_LAST; idx++) + if (tuner_bands[idx] == band) { + ucontrol->value.enumerated.item[0] = idx; + break; + } + + return 0; +} + +static int snd_asihpi_tuner_band_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + */ + u32 h_control = kcontrol->private_value; + unsigned int idx; + u16 band; + u16 tuner_bands[HPI_TUNER_BAND_LAST]; + __always_unused u32 num_bands; + + num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, + HPI_TUNER_BAND_LAST); + + idx = ucontrol->value.enumerated.item[0]; + if (idx >= ARRAY_SIZE(tuner_bands)) + idx = ARRAY_SIZE(tuner_bands) - 1; + band = tuner_bands[idx]; + hpi_handle_error(hpi_tuner_set_band(h_control, band)); + + return 1; +} + +/* Freq */ + +static int snd_asihpi_tuner_freq_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 h_control = kcontrol->private_value; + u16 err; + u16 tuner_bands[HPI_TUNER_BAND_LAST]; + u16 num_bands = 0, band_iter, idx; + u32 freq_range[3], temp_freq_range[3]; + + num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, + HPI_TUNER_BAND_LAST); + + freq_range[0] = INT_MAX; + freq_range[1] = 0; + freq_range[2] = INT_MAX; + + for (band_iter = 0; band_iter < num_bands; band_iter++) { + for (idx = 0; idx < 3; idx++) { + err = hpi_tuner_query_frequency(h_control, + idx, tuner_bands[band_iter], + &temp_freq_range[idx]); + if (err != 0) + return err; + } + + /* skip band with bogus stepping */ + if (temp_freq_range[2] <= 0) + continue; + + if (temp_freq_range[0] < freq_range[0]) + freq_range[0] = temp_freq_range[0]; + if (temp_freq_range[1] > freq_range[1]) + freq_range[1] = temp_freq_range[1]; + if (temp_freq_range[2] < freq_range[2]) + freq_range[2] = temp_freq_range[2]; + } + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = ((int)freq_range[0]); + uinfo->value.integer.max = ((int)freq_range[1]); + uinfo->value.integer.step = ((int)freq_range[2]); + return 0; +} + +static int snd_asihpi_tuner_freq_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u32 freq; + + hpi_handle_error(hpi_tuner_get_frequency(h_control, &freq)); + ucontrol->value.integer.value[0] = freq; + + return 0; +} + +static int snd_asihpi_tuner_freq_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u32 freq; + + freq = ucontrol->value.integer.value[0]; + hpi_handle_error(hpi_tuner_set_frequency(h_control, freq)); + + return 1; +} + +/* Tuner control group initializer */ +static int snd_asihpi_tuner_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + snd_control.private_value = hpi_ctl->h_control; + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + + if (!hpi_tuner_get_gain(hpi_ctl->h_control, NULL)) { + asihpi_ctl_init(&snd_control, hpi_ctl, "Gain"); + snd_control.info = snd_asihpi_tuner_gain_info; + snd_control.get = snd_asihpi_tuner_gain_get; + snd_control.put = snd_asihpi_tuner_gain_put; + + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + } + + asihpi_ctl_init(&snd_control, hpi_ctl, "Band"); + snd_control.info = snd_asihpi_tuner_band_info; + snd_control.get = snd_asihpi_tuner_band_get; + snd_control.put = snd_asihpi_tuner_band_put; + + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + + asihpi_ctl_init(&snd_control, hpi_ctl, "Freq"); + snd_control.info = snd_asihpi_tuner_freq_info; + snd_control.get = snd_asihpi_tuner_freq_get; + snd_control.put = snd_asihpi_tuner_freq_put; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + Meter controls + ------------------------------------------------------------*/ +static int snd_asihpi_meter_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 h_control = kcontrol->private_value; + u32 count; + u16 err; + err = hpi_meter_query_channels(h_control, &count); + if (err) + count = HPI_MAX_CHANNELS; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = count; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0x7FFFFFFF; + return 0; +} + +/* linear values for 10dB steps */ +static const int log2lin[] = { + 0x7FFFFFFF, /* 0dB */ + 679093956, + 214748365, + 67909396, + 21474837, + 6790940, + 2147484, /* -60dB */ + 679094, + 214748, /* -80 */ + 67909, + 21475, /* -100 */ + 6791, + 2147, + 679, + 214, + 68, + 21, + 7, + 2 +}; + +static int snd_asihpi_meter_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS], i; + u16 err; + + err = hpi_meter_get_peak(h_control, an_gain_mB); + + for (i = 0; i < HPI_MAX_CHANNELS; i++) { + if (err) { + ucontrol->value.integer.value[i] = 0; + } else if (an_gain_mB[i] >= 0) { + ucontrol->value.integer.value[i] = + an_gain_mB[i] << 16; + } else { + /* -ve is log value in millibels < -60dB, + * convert to (roughly!) linear, + */ + ucontrol->value.integer.value[i] = + log2lin[an_gain_mB[i] / -1000]; + } + } + return 0; +} + +static int snd_asihpi_meter_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl, int subidx) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "Meter"); + snd_control.access = + SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ; + snd_control.info = snd_asihpi_meter_info; + snd_control.get = snd_asihpi_meter_get; + + snd_control.index = subidx; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + Multiplexer controls + ------------------------------------------------------------*/ +static int snd_card_asihpi_mux_count_sources(struct snd_kcontrol *snd_control) +{ + u32 h_control = snd_control->private_value; + struct hpi_control hpi_ctl; + int s, err; + for (s = 0; s < 32; s++) { + err = hpi_multiplexer_query_source(h_control, s, + &hpi_ctl. + src_node_type, + &hpi_ctl. + src_node_index); + if (err) + break; + } + return s; +} + +static int snd_asihpi_mux_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u16 src_node_type, src_node_index; + u32 h_control = kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = + snd_card_asihpi_mux_count_sources(kcontrol); + + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + + hpi_multiplexer_query_source(h_control, + uinfo->value.enumerated.item, + &src_node_type, &src_node_index); + + sprintf(uinfo->value.enumerated.name, "%s %d", + asihpi_src_names[src_node_type - HPI_SOURCENODE_NONE], + src_node_index); + return 0; +} + +static int snd_asihpi_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u16 source_type, source_index; + u16 src_node_type, src_node_index; + int s; + + hpi_handle_error(hpi_multiplexer_get_source(h_control, + &source_type, &source_index)); + /* Should cache this search result! */ + for (s = 0; s < 256; s++) { + if (hpi_multiplexer_query_source(h_control, s, + &src_node_type, &src_node_index)) + break; + + if ((source_type == src_node_type) + && (source_index == src_node_index)) { + ucontrol->value.enumerated.item[0] = s; + return 0; + } + } + pr_warn("%s: Control %x failed to match mux source %hu %hu\n", + __func__, h_control, source_type, source_index); + ucontrol->value.enumerated.item[0] = 0; + return 0; +} + +static int snd_asihpi_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + u32 h_control = kcontrol->private_value; + u16 source_type, source_index; + u16 e; + + change = 1; + + e = hpi_multiplexer_query_source(h_control, + ucontrol->value.enumerated.item[0], + &source_type, &source_index); + if (!e) + hpi_handle_error( + hpi_multiplexer_set_source(h_control, + source_type, source_index)); + return change; +} + + +static int snd_asihpi_mux_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "Route"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_control.info = snd_asihpi_mux_info; + snd_control.get = snd_asihpi_mux_get; + snd_control.put = snd_asihpi_mux_put; + + return ctl_add(card, &snd_control, asihpi); + +} + +/*------------------------------------------------------------ + Channel mode controls + ------------------------------------------------------------*/ +static int snd_asihpi_cmode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const mode_names[HPI_CHANNEL_MODE_LAST + 1] = { + "invalid", + "Normal", "Swap", + "From Left", "From Right", + "To Left", "To Right" + }; + + u32 h_control = kcontrol->private_value; + u16 mode; + int i; + const char *mapped_names[6]; + int valid_modes = 0; + + /* HPI channel mode values can be from 1 to 6 + Some adapters only support a contiguous subset + */ + for (i = 0; i < HPI_CHANNEL_MODE_LAST; i++) + if (!hpi_channel_mode_query_mode( + h_control, i, &mode)) { + mapped_names[valid_modes] = mode_names[mode]; + valid_modes++; + } + + if (!valid_modes) + return -EINVAL; + + return snd_ctl_enum_info(uinfo, 1, valid_modes, mapped_names); +} + +static int snd_asihpi_cmode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u16 mode; + + if (hpi_channel_mode_get(h_control, &mode)) + mode = 1; + + ucontrol->value.enumerated.item[0] = mode - 1; + + return 0; +} + +static int snd_asihpi_cmode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + u32 h_control = kcontrol->private_value; + + change = 1; + + hpi_handle_error(hpi_channel_mode_set(h_control, + ucontrol->value.enumerated.item[0] + 1)); + return change; +} + + +static int snd_asihpi_cmode_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "Mode"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_control.info = snd_asihpi_cmode_info; + snd_control.get = snd_asihpi_cmode_get; + snd_control.put = snd_asihpi_cmode_put; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + Sampleclock source controls + ------------------------------------------------------------*/ +static const char * const sampleclock_sources[] = { + "N/A", "Local PLL", "Digital Sync", "Word External", "Word Header", + "SMPTE", "Digital1", "Auto", "Network", "Invalid", + "Prev Module", "BLU-Link", + "Digital2", "Digital3", "Digital4", "Digital5", + "Digital6", "Digital7", "Digital8"}; + + /* Number of strings must match expected enumerated values */ + compile_time_assert( + (ARRAY_SIZE(sampleclock_sources) == MAX_CLOCKSOURCES), + assert_sampleclock_sources_size); + +static int snd_asihpi_clksrc_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + struct clk_cache *clkcache = &asihpi->cc; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = clkcache->count; + + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + + strscpy(uinfo->value.enumerated.name, + clkcache->s[uinfo->value.enumerated.item].name); + return 0; +} + +static int snd_asihpi_clksrc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + struct clk_cache *clkcache = &asihpi->cc; + u32 h_control = kcontrol->private_value; + u16 source, srcindex = 0; + int i; + + ucontrol->value.enumerated.item[0] = 0; + if (hpi_sample_clock_get_source(h_control, &source)) + source = 0; + + if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT) + if (hpi_sample_clock_get_source_index(h_control, &srcindex)) + srcindex = 0; + + for (i = 0; i < clkcache->count; i++) + if ((clkcache->s[i].source == source) && + (clkcache->s[i].index == srcindex)) + break; + + ucontrol->value.enumerated.item[0] = i; + + return 0; +} + +static int snd_asihpi_clksrc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + struct clk_cache *clkcache = &asihpi->cc; + unsigned int item; + int change; + u32 h_control = kcontrol->private_value; + + change = 1; + item = ucontrol->value.enumerated.item[0]; + if (item >= clkcache->count) + item = clkcache->count-1; + + hpi_handle_error(hpi_sample_clock_set_source( + h_control, clkcache->s[item].source)); + + if (clkcache->s[item].source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT) + hpi_handle_error(hpi_sample_clock_set_source_index( + h_control, clkcache->s[item].index)); + return change; +} + +/*------------------------------------------------------------ + Clkrate controls + ------------------------------------------------------------*/ +/* Need to change this to enumerated control with list of rates */ +static int snd_asihpi_clklocal_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 = 8000; + uinfo->value.integer.max = 192000; + uinfo->value.integer.step = 100; + + return 0; +} + +static int snd_asihpi_clklocal_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u32 rate; + u16 e; + + e = hpi_sample_clock_get_local_rate(h_control, &rate); + if (!e) + ucontrol->value.integer.value[0] = rate; + else + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int snd_asihpi_clklocal_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + u32 h_control = kcontrol->private_value; + + /* change = asihpi->mixer_clkrate[addr][0] != left || + asihpi->mixer_clkrate[addr][1] != right; + */ + change = 1; + hpi_handle_error(hpi_sample_clock_set_local_rate(h_control, + ucontrol->value.integer.value[0])); + return change; +} + +static int snd_asihpi_clkrate_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 = 8000; + uinfo->value.integer.max = 192000; + uinfo->value.integer.step = 100; + + return 0; +} + +static int snd_asihpi_clkrate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u32 rate; + u16 e; + + e = hpi_sample_clock_get_sample_rate(h_control, &rate); + if (!e) + ucontrol->value.integer.value[0] = rate; + else + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int snd_asihpi_sampleclock_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card; + struct snd_kcontrol_new snd_control; + + struct clk_cache *clkcache; + u32 hSC = hpi_ctl->h_control; + int has_aes_in = 0; + int i, j; + u16 source; + + if (snd_BUG_ON(!asihpi)) + return -EINVAL; + card = asihpi->card; + clkcache = &asihpi->cc; + snd_control.private_value = hpi_ctl->h_control; + + clkcache->has_local = 0; + + for (i = 0; i <= HPI_SAMPLECLOCK_SOURCE_LAST; i++) { + if (hpi_sample_clock_query_source(hSC, + i, &source)) + break; + clkcache->s[i].source = source; + clkcache->s[i].index = 0; + clkcache->s[i].name = sampleclock_sources[source]; + if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT) + has_aes_in = 1; + if (source == HPI_SAMPLECLOCK_SOURCE_LOCAL) + clkcache->has_local = 1; + } + if (has_aes_in) + /* already will have picked up index 0 above */ + for (j = 1; j < 8; j++) { + if (hpi_sample_clock_query_source_index(hSC, + j, HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT, + &source)) + break; + clkcache->s[i].source = + HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT; + clkcache->s[i].index = j; + clkcache->s[i].name = sampleclock_sources[ + j+HPI_SAMPLECLOCK_SOURCE_LAST]; + i++; + } + clkcache->count = i; + + asihpi_ctl_init(&snd_control, hpi_ctl, "Source"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ; + snd_control.info = snd_asihpi_clksrc_info; + snd_control.get = snd_asihpi_clksrc_get; + snd_control.put = snd_asihpi_clksrc_put; + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + + + if (clkcache->has_local) { + asihpi_ctl_init(&snd_control, hpi_ctl, "Localrate"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ; + snd_control.info = snd_asihpi_clklocal_info; + snd_control.get = snd_asihpi_clklocal_get; + snd_control.put = snd_asihpi_clklocal_put; + + + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + } + + asihpi_ctl_init(&snd_control, hpi_ctl, "Rate"); + snd_control.access = + SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ; + snd_control.info = snd_asihpi_clkrate_info; + snd_control.get = snd_asihpi_clkrate_get; + + return ctl_add(card, &snd_control, asihpi); +} +/*------------------------------------------------------------ + Mixer + ------------------------------------------------------------*/ + +static int snd_card_asihpi_mixer_new(struct snd_card_asihpi *asihpi) +{ + struct snd_card *card; + unsigned int idx = 0; + unsigned int subindex = 0; + int err; + struct hpi_control hpi_ctl, prev_ctl; + + if (snd_BUG_ON(!asihpi)) + return -EINVAL; + card = asihpi->card; + strscpy(card->mixername, "Asihpi Mixer"); + + err = + hpi_mixer_open(asihpi->hpi->adapter->index, + &asihpi->h_mixer); + hpi_handle_error(err); + if (err) + return -err; + + memset(&prev_ctl, 0, sizeof(prev_ctl)); + prev_ctl.control_type = -1; + + for (idx = 0; idx < 2000; idx++) { + err = hpi_mixer_get_control_by_index( + asihpi->h_mixer, + idx, + &hpi_ctl.src_node_type, + &hpi_ctl.src_node_index, + &hpi_ctl.dst_node_type, + &hpi_ctl.dst_node_index, + &hpi_ctl.control_type, + &hpi_ctl.h_control); + if (err) { + if (err == HPI_ERROR_CONTROL_DISABLED) { + if (mixer_dump) + dev_info(&asihpi->pci->dev, + "Disabled HPI Control(%d)\n", + idx); + continue; + } else + break; + + } + + hpi_ctl.src_node_type -= HPI_SOURCENODE_NONE; + hpi_ctl.dst_node_type -= HPI_DESTNODE_NONE; + + /* ASI50xx in SSX mode has multiple meters on the same node. + Use subindex to create distinct ALSA controls + for any duplicated controls. + */ + if ((hpi_ctl.control_type == prev_ctl.control_type) && + (hpi_ctl.src_node_type == prev_ctl.src_node_type) && + (hpi_ctl.src_node_index == prev_ctl.src_node_index) && + (hpi_ctl.dst_node_type == prev_ctl.dst_node_type) && + (hpi_ctl.dst_node_index == prev_ctl.dst_node_index)) + subindex++; + else + subindex = 0; + + prev_ctl = hpi_ctl; + + switch (hpi_ctl.control_type) { + case HPI_CONTROL_VOLUME: + err = snd_asihpi_volume_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_LEVEL: + err = snd_asihpi_level_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_MULTIPLEXER: + err = snd_asihpi_mux_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_CHANNEL_MODE: + err = snd_asihpi_cmode_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_METER: + err = snd_asihpi_meter_add(asihpi, &hpi_ctl, subindex); + break; + case HPI_CONTROL_SAMPLECLOCK: + err = snd_asihpi_sampleclock_add( + asihpi, &hpi_ctl); + break; + case HPI_CONTROL_CONNECTION: /* ignore these */ + continue; + case HPI_CONTROL_TUNER: + err = snd_asihpi_tuner_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_AESEBU_TRANSMITTER: + err = snd_asihpi_aesebu_tx_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_AESEBU_RECEIVER: + err = snd_asihpi_aesebu_rx_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_VOX: + case HPI_CONTROL_BITSTREAM: + case HPI_CONTROL_MICROPHONE: + case HPI_CONTROL_PARAMETRIC_EQ: + case HPI_CONTROL_COMPANDER: + default: + if (mixer_dump) + dev_info(&asihpi->pci->dev, + "Untranslated HPI Control (%d) %d %d %d %d %d\n", + idx, + hpi_ctl.control_type, + hpi_ctl.src_node_type, + hpi_ctl.src_node_index, + hpi_ctl.dst_node_type, + hpi_ctl.dst_node_index); + continue; + } + if (err < 0) + return err; + } + if (HPI_ERROR_INVALID_OBJ_INDEX != err) + hpi_handle_error(err); + + dev_info(&asihpi->pci->dev, "%d mixer controls found\n", idx); + + return 0; +} + +/*------------------------------------------------------------ + /proc interface + ------------------------------------------------------------*/ + +static void +snd_asihpi_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_card_asihpi *asihpi = entry->private_data; + u32 h_control; + u32 rate = 0; + u16 source = 0; + + u16 num_outstreams; + u16 num_instreams; + u16 version; + u32 serial_number; + u16 type; + + int err; + + snd_iprintf(buffer, "ASIHPI driver proc file\n"); + + hpi_handle_error(hpi_adapter_get_info(asihpi->hpi->adapter->index, + &num_outstreams, &num_instreams, + &version, &serial_number, &type)); + + snd_iprintf(buffer, + "Adapter type ASI%4X\nHardware Index %d\n" + "%d outstreams\n%d instreams\n", + type, asihpi->hpi->adapter->index, + num_outstreams, num_instreams); + + snd_iprintf(buffer, + "Serial#%d\nHardware version %c%d\nDSP code version %03d\n", + serial_number, ((version >> 3) & 0xf) + 'A', version & 0x7, + ((version >> 13) * 100) + ((version >> 7) & 0x3f)); + + err = hpi_mixer_get_control(asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + + if (!err) { + err = hpi_sample_clock_get_sample_rate(h_control, &rate); + err += hpi_sample_clock_get_source(h_control, &source); + + if (!err) + snd_iprintf(buffer, "Sample Clock %dHz, source %s\n", + rate, sampleclock_sources[source]); + } +} + +static void snd_asihpi_proc_init(struct snd_card_asihpi *asihpi) +{ + snd_card_ro_proc_new(asihpi->card, "info", asihpi, + snd_asihpi_proc_read); +} + +/*------------------------------------------------------------ + HWDEP + ------------------------------------------------------------*/ + +static int snd_asihpi_hpi_open(struct snd_hwdep *hw, struct file *file) +{ + if (enable_hpi_hwdep) + return 0; + else + return -ENODEV; + +} + +static int snd_asihpi_hpi_release(struct snd_hwdep *hw, struct file *file) +{ + if (enable_hpi_hwdep) + return asihpi_hpi_release(file); + else + return -ENODEV; +} + +static int snd_asihpi_hpi_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + if (enable_hpi_hwdep) + return asihpi_hpi_ioctl(file, cmd, arg); + else + return -ENODEV; +} + + +/* results in /dev/snd/hwC#D0 file for each card with index # + also /proc/asound/hwdep will contain '#-00: asihpi (HPI) for each card' +*/ +static int snd_asihpi_hpi_new(struct snd_card_asihpi *asihpi, int device) +{ + struct snd_hwdep *hw; + int err; + + err = snd_hwdep_new(asihpi->card, "HPI", device, &hw); + if (err < 0) + return err; + strscpy(hw->name, "asihpi (HPI)"); + hw->iface = SNDRV_HWDEP_IFACE_LAST; + hw->ops.open = snd_asihpi_hpi_open; + hw->ops.ioctl = snd_asihpi_hpi_ioctl; + hw->ops.release = snd_asihpi_hpi_release; + hw->private_data = asihpi; + return 0; +} + +/*------------------------------------------------------------ + CARD + ------------------------------------------------------------*/ +static int snd_asihpi_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + int err; + struct hpi_adapter *hpi; + struct snd_card *card; + struct snd_card_asihpi *asihpi; + + u32 h_control; + u32 h_stream; + u32 adapter_index; + + static int dev; + if (dev >= SNDRV_CARDS) + return -ENODEV; + + /* Should this be enable[hpi->index] ? */ + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + /* Initialise low-level HPI driver */ + err = asihpi_adapter_probe(pci_dev, pci_id); + if (err < 0) + return err; + + hpi = pci_get_drvdata(pci_dev); + adapter_index = hpi->adapter->index; + /* first try to give the card the same index as its hardware index */ + err = snd_card_new(&pci_dev->dev, adapter_index, id[adapter_index], + THIS_MODULE, sizeof(struct snd_card_asihpi), &card); + if (err < 0) { + /* if that fails, try the default index==next available */ + err = snd_card_new(&pci_dev->dev, index[dev], id[dev], + THIS_MODULE, sizeof(struct snd_card_asihpi), + &card); + if (err < 0) + return err; + dev_warn(&pci_dev->dev, "Adapter index %d->ALSA index %d\n", + adapter_index, card->number); + } + + asihpi = card->private_data; + asihpi->card = card; + asihpi->pci = pci_dev; + asihpi->hpi = hpi; + hpi->snd_card = card; + + err = hpi_adapter_get_property(adapter_index, + HPI_ADAPTER_PROPERTY_CAPS1, + NULL, &asihpi->support_grouping); + if (err) + asihpi->support_grouping = 0; + + err = hpi_adapter_get_property(adapter_index, + HPI_ADAPTER_PROPERTY_CAPS2, + &asihpi->support_mrx, NULL); + if (err) + asihpi->support_mrx = 0; + + err = hpi_adapter_get_property(adapter_index, + HPI_ADAPTER_PROPERTY_INTERVAL, + NULL, &asihpi->update_interval_frames); + if (err) + asihpi->update_interval_frames = 512; + + if (hpi->interrupt_mode) { + asihpi->pcm_start = snd_card_asihpi_pcm_int_start; + asihpi->pcm_stop = snd_card_asihpi_pcm_int_stop; + hpi->interrupt_callback = snd_card_asihpi_isr; + } else { + asihpi->pcm_start = snd_card_asihpi_pcm_timer_start; + asihpi->pcm_stop = snd_card_asihpi_pcm_timer_stop; + } + + hpi_handle_error(hpi_instream_open(adapter_index, + 0, &h_stream)); + + err = hpi_instream_host_buffer_free(h_stream); + asihpi->can_dma = (!err); + + hpi_handle_error(hpi_instream_close(h_stream)); + + if (!asihpi->can_dma) + asihpi->update_interval_frames *= 2; + + err = hpi_adapter_get_property(adapter_index, + HPI_ADAPTER_PROPERTY_CURCHANNELS, + &asihpi->in_max_chans, &asihpi->out_max_chans); + if (err) { + asihpi->in_max_chans = 2; + asihpi->out_max_chans = 2; + } + + if (asihpi->out_max_chans > 2) { /* assume LL mode */ + asihpi->out_min_chans = asihpi->out_max_chans; + asihpi->in_min_chans = asihpi->in_max_chans; + asihpi->support_grouping = 0; + } else { + asihpi->out_min_chans = 1; + asihpi->in_min_chans = 1; + } + + dev_info(&pci_dev->dev, "Has dma:%d, grouping:%d, mrx:%d, uif:%d\n", + asihpi->can_dma, + asihpi->support_grouping, + asihpi->support_mrx, + asihpi->update_interval_frames + ); + + err = snd_card_asihpi_pcm_new(asihpi, 0); + if (err < 0) { + dev_err(&pci_dev->dev, "pcm_new failed\n"); + goto __nodev; + } + err = snd_card_asihpi_mixer_new(asihpi); + if (err < 0) { + dev_err(&pci_dev->dev, "mixer_new failed\n"); + goto __nodev; + } + + err = hpi_mixer_get_control(asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + + if (!err) + err = hpi_sample_clock_set_local_rate( + h_control, adapter_fs); + + snd_asihpi_proc_init(asihpi); + + /* always create, can be enabled or disabled dynamically + by enable_hwdep module param*/ + snd_asihpi_hpi_new(asihpi, 0); + + strscpy(card->driver, "ASIHPI"); + + sprintf(card->shortname, "AudioScience ASI%4X", + asihpi->hpi->adapter->type); + sprintf(card->longname, "%s %i", + card->shortname, adapter_index); + err = snd_card_register(card); + + if (!err) { + dev++; + return 0; + } +__nodev: + snd_card_free(card); + dev_err(&pci_dev->dev, "snd_asihpi_probe error %d\n", err); + return err; + +} + +static void snd_asihpi_remove(struct pci_dev *pci_dev) +{ + struct hpi_adapter *hpi = pci_get_drvdata(pci_dev); + + /* Stop interrupts */ + if (hpi->interrupt_mode) { + hpi->interrupt_callback = NULL; + hpi_handle_error(hpi_adapter_set_property(hpi->adapter->index, + HPI_ADAPTER_PROPERTY_IRQ_RATE, 0, 0)); + } + + snd_card_free(hpi->snd_card); + hpi->snd_card = NULL; + asihpi_adapter_remove(pci_dev); +} + +static const struct pci_device_id asihpi_pci_tbl[] = { + { + PCI_DEVICE_SUB(HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205, + HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID), + .driver_data = (kernel_ulong_t)HPI_6205, + }, { + PCI_DEVICE_SUB(HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040, + HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID), + .driver_data = (kernel_ulong_t)HPI_6000, + }, + {} +}; +MODULE_DEVICE_TABLE(pci, asihpi_pci_tbl); + +static struct pci_driver driver = { + .name = KBUILD_MODNAME, + .id_table = asihpi_pci_tbl, + .probe = snd_asihpi_probe, + .remove = snd_asihpi_remove, +}; + +static int __init snd_asihpi_init(void) +{ + asihpi_init(); + return pci_register_driver(&driver); +} + +static void __exit snd_asihpi_exit(void) +{ + + pci_unregister_driver(&driver); + asihpi_exit(); +} + +module_init(snd_asihpi_init) +module_exit(snd_asihpi_exit) + diff --git a/sound/pci/asihpi/hpi.h b/sound/pci/asihpi/hpi.h new file mode 100644 index 000000000..04a5cf657 --- /dev/null +++ b/sound/pci/asihpi/hpi.h @@ -0,0 +1,1708 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + +*/ +/** \file hpi.h + + AudioScience Hardware Programming Interface (HPI) + public API definition. + + The HPI is a low-level hardware abstraction layer to all + AudioScience digital audio adapters + +(C) Copyright AudioScience Inc. 1998-2010 +*/ + +#ifndef _HPI_H_ +#define _HPI_H_ + +#include +#define HPI_BUILD_KERNEL_MODE + +/******************************************************************************/ +/******** HPI API DEFINITIONS *****/ +/******************************************************************************/ + +/*******************************************/ +/** Audio format types +\ingroup stream +*/ +enum HPI_FORMATS { +/** Used internally on adapter. */ + HPI_FORMAT_MIXER_NATIVE = 0, +/** 8-bit unsigned PCM. Windows equivalent is WAVE_FORMAT_PCM. */ + HPI_FORMAT_PCM8_UNSIGNED = 1, +/** 16-bit signed PCM. Windows equivalent is WAVE_FORMAT_PCM. */ + HPI_FORMAT_PCM16_SIGNED = 2, +/** MPEG-1 Layer-1. */ + HPI_FORMAT_MPEG_L1 = 3, +/** MPEG-1 Layer-2. + +Windows equivalent is WAVE_FORMAT_MPEG. + +The following table shows what combinations of mode and bitrate are possible: + + + +

Bitrate (kbs)

+

Mono

+

Stereo,
Joint Stereo or
Dual Channel

+ +
32X_ +
40__ +
48X_ +
56X_ +
64XX +
80X_ +
96XX +
112XX +
128XX +
160XX +
192XX +
224_X +
256-X +
320-X +
384_X +
+*/ + HPI_FORMAT_MPEG_L2 = 4, +/** MPEG-1 Layer-3. +Windows equivalent is WAVE_FORMAT_MPEG. + +The following table shows what combinations of mode and bitrate are possible: + + + +

Bitrate (kbs)

+

Mono
Stereo @ 8,
11.025 and
12kHz*

+

Mono
Stereo @ 16,
22.050 and
24kHz*

+

Mono
Stereo @ 32,
44.1 and
48kHz

+ +
16XX_ +
24XX_ +
32XXX +
40XXX +
48XXX +
56XXX +
64XXX +
80_XX +
96_XX +
112_XX +
128_XX +
144_X_ +
160_XX +
192__X +
224__X +
256-_X +
320-_X +
+\b * Available on the ASI6000 series only +*/ + HPI_FORMAT_MPEG_L3 = 5, +/** Dolby AC-2. */ + HPI_FORMAT_DOLBY_AC2 = 6, +/** Dolbt AC-3. */ + HPI_FORMAT_DOLBY_AC3 = 7, +/** 16-bit PCM big-endian. */ + HPI_FORMAT_PCM16_BIGENDIAN = 8, +/** TAGIT-1 algorithm - hits. */ + HPI_FORMAT_AA_TAGIT1_HITS = 9, +/** TAGIT-1 algorithm - inserts. */ + HPI_FORMAT_AA_TAGIT1_INSERTS = 10, +/** 32-bit signed PCM. Windows equivalent is WAVE_FORMAT_PCM. +Each sample is a 32bit word. The most significant 24 bits contain a 24-bit +sample and the least significant 8 bits are set to 0. +*/ + HPI_FORMAT_PCM32_SIGNED = 11, +/** Raw bitstream - unknown format. */ + HPI_FORMAT_RAW_BITSTREAM = 12, +/** TAGIT-1 algorithm hits - extended. */ + HPI_FORMAT_AA_TAGIT1_HITS_EX1 = 13, +/** 32-bit PCM as an IEEE float. Windows equivalent is WAVE_FORMAT_IEEE_FLOAT. +Each sample is a 32bit word in IEEE754 floating point format. +The range is +1.0 to -1.0, which corresponds to digital fullscale. +*/ + HPI_FORMAT_PCM32_FLOAT = 14, +/** 24-bit PCM signed. Windows equivalent is WAVE_FORMAT_PCM. */ + HPI_FORMAT_PCM24_SIGNED = 15, +/** OEM format 1 - private. */ + HPI_FORMAT_OEM1 = 16, +/** OEM format 2 - private. */ + HPI_FORMAT_OEM2 = 17, +/** Undefined format. */ + HPI_FORMAT_UNDEFINED = 0xffff +}; + +/*******************************************/ +/** Stream States +\ingroup stream +*/ +enum HPI_STREAM_STATES { + /** State stopped - stream is stopped. */ + HPI_STATE_STOPPED = 1, + /** State playing - stream is playing audio. */ + HPI_STATE_PLAYING = 2, + /** State recording - stream is recording. */ + HPI_STATE_RECORDING = 3, + /** State drained - playing stream ran out of data to play. */ + HPI_STATE_DRAINED = 4, + /** State generate sine - to be implemented. */ + HPI_STATE_SINEGEN = 5, + /** State wait - used for inter-card sync to mean waiting for all + cards to be ready. */ + HPI_STATE_WAIT = 6 +}; +/*******************************************/ +/** Source node types +\ingroup mixer +*/ +enum HPI_SOURCENODES { + /** This define can be used instead of 0 to indicate + that there is no valid source node. A control that + exists on a destination node can be searched for using a source + node value of either 0, or HPI_SOURCENODE_NONE */ + HPI_SOURCENODE_NONE = 100, + /** Out Stream (Play) node. */ + HPI_SOURCENODE_OSTREAM = 101, + /** Line in node - could be analog, AES/EBU or network. */ + HPI_SOURCENODE_LINEIN = 102, + HPI_SOURCENODE_AESEBU_IN = 103, /**< AES/EBU input node. */ + HPI_SOURCENODE_TUNER = 104, /**< tuner node. */ + HPI_SOURCENODE_RF = 105, /**< RF input node. */ + HPI_SOURCENODE_CLOCK_SOURCE = 106, /**< clock source node. */ + HPI_SOURCENODE_RAW_BITSTREAM = 107, /**< raw bitstream node. */ + HPI_SOURCENODE_MICROPHONE = 108, /**< microphone node. */ + /** Cobranet input node - + Audio samples come from the Cobranet network and into the device. */ + HPI_SOURCENODE_COBRANET = 109, + HPI_SOURCENODE_ANALOG = 110, /**< analog input node. */ + HPI_SOURCENODE_ADAPTER = 111, /**< adapter node. */ + /** RTP stream input node - This node is a destination for + packets of RTP audio samples from other devices. */ + HPI_SOURCENODE_RTP_DESTINATION = 112, + HPI_SOURCENODE_INTERNAL = 113, /**< node internal to the device. */ + HPI_SOURCENODE_AVB = 114, /**< AVB input stream */ + HPI_SOURCENODE_BLULINK = 115, /**< BLU-link input channel */ + /* !!!Update this AND hpidebug.h if you add a new sourcenode type!!! */ + HPI_SOURCENODE_LAST_INDEX = 115 /**< largest ID */ + /* AX6 max sourcenode types = 15 */ +}; + +/*******************************************/ +/** Destination node types +\ingroup mixer +*/ +enum HPI_DESTNODES { + /** This define can be used instead of 0 to indicate + that there is no valid destination node. A control that + exists on a source node can be searched for using a destination + node value of either 0, or HPI_DESTNODE_NONE */ + HPI_DESTNODE_NONE = 200, + /** In Stream (Record) node. */ + HPI_DESTNODE_ISTREAM = 201, + HPI_DESTNODE_LINEOUT = 202, /**< line out node. */ + HPI_DESTNODE_AESEBU_OUT = 203, /**< AES/EBU output node. */ + HPI_DESTNODE_RF = 204, /**< RF output node. */ + HPI_DESTNODE_SPEAKER = 205, /**< speaker output node. */ + /** Cobranet output node - + Audio samples from the device are sent out on the Cobranet network.*/ + HPI_DESTNODE_COBRANET = 206, + HPI_DESTNODE_ANALOG = 207, /**< analog output node. */ + /** RTP stream output node - This node is a source for + packets of RTP audio samples that are sent to other devices. */ + HPI_DESTNODE_RTP_SOURCE = 208, + HPI_DESTNODE_AVB = 209, /**< AVB output stream */ + HPI_DESTNODE_INTERNAL = 210, /**< node internal to the device. */ + HPI_DESTNODE_BLULINK = 211, /**< BLU-link output channel. */ + /* !!!Update this AND hpidebug.h if you add a new destnode type!!! */ + HPI_DESTNODE_LAST_INDEX = 211 /**< largest ID */ + /* AX6 max destnode types = 15 */ +}; + +/*******************************************/ +/** Mixer control types +\ingroup mixer +*/ +enum HPI_CONTROLS { + HPI_CONTROL_GENERIC = 0, /**< generic control. */ + HPI_CONTROL_CONNECTION = 1, /**< A connection between nodes. */ + HPI_CONTROL_VOLUME = 2, /**< volume control - works in dB_fs. */ + HPI_CONTROL_METER = 3, /**< peak meter control. */ + HPI_CONTROL_MUTE = 4, /*mute control - not used at present. */ + HPI_CONTROL_MULTIPLEXER = 5, /**< multiplexer control. */ + + HPI_CONTROL_AESEBU_TRANSMITTER = 6, /**< AES/EBU transmitter control */ + HPI_CONTROL_AESEBUTX = 6, /* HPI_CONTROL_AESEBU_TRANSMITTER */ + + HPI_CONTROL_AESEBU_RECEIVER = 7, /**< AES/EBU receiver control. */ + HPI_CONTROL_AESEBURX = 7, /* HPI_CONTROL_AESEBU_RECEIVER */ + + HPI_CONTROL_LEVEL = 8, /**< level/trim control - works in d_bu. */ + HPI_CONTROL_TUNER = 9, /**< tuner control. */ +/* HPI_CONTROL_ONOFFSWITCH = 10 */ + HPI_CONTROL_VOX = 11, /**< vox control. */ +/* HPI_CONTROL_AES18_TRANSMITTER = 12 */ +/* HPI_CONTROL_AES18_RECEIVER = 13 */ +/* HPI_CONTROL_AES18_BLOCKGENERATOR = 14 */ + HPI_CONTROL_CHANNEL_MODE = 15, /**< channel mode control. */ + + HPI_CONTROL_BITSTREAM = 16, /**< bitstream control. */ + HPI_CONTROL_SAMPLECLOCK = 17, /**< sample clock control. */ + HPI_CONTROL_MICROPHONE = 18, /**< microphone control. */ + HPI_CONTROL_PARAMETRIC_EQ = 19, /**< parametric EQ control. */ + HPI_CONTROL_EQUALIZER = 19, /*HPI_CONTROL_PARAMETRIC_EQ */ + + HPI_CONTROL_COMPANDER = 20, /**< compander control. */ + HPI_CONTROL_COBRANET = 21, /**< cobranet control. */ + HPI_CONTROL_TONEDETECTOR = 22, /**< tone detector control. */ + HPI_CONTROL_SILENCEDETECTOR = 23, /**< silence detector control. */ + HPI_CONTROL_PAD = 24, /**< tuner PAD control. */ + HPI_CONTROL_SRC = 25, /**< samplerate converter control. */ + HPI_CONTROL_UNIVERSAL = 26, /**< universal control. */ + +/* !!! Update this AND hpidebug.h if you add a new control type!!!*/ + HPI_CONTROL_LAST_INDEX = 26 /** 4 line outs = 1 to 8 channel streams), + 4 lineins -> 1 instream (1 to 8 channel streams) at 0-48kHz. + For more info see the SSX Specification. +*/ + HPI_ADAPTER_MODE_MULTICHANNEL = 9, + +/** 12 outstream mode. +- ASI6514, ASI6614: 2 instreams +- ASI6540,ASI6544: 8 instreams +- ASI6640,ASI6644: 8 instreams +*/ + HPI_ADAPTER_MODE_12OSTREAM = 10, + +/** 9 outstream mode. +- ASI6044: 8 instreams +*/ + HPI_ADAPTER_MODE_9OSTREAM = 11, + +/** mono mode. +- ASI6416: 16 outstreams/instreams +- ASI5402: 2 outstreams/instreams +*/ + HPI_ADAPTER_MODE_MONO = 12, + +/** Low latency mode. +- ASI6416/ASI6316: 1 16 channel outstream and instream +*/ + HPI_ADAPTER_MODE_LOW_LATENCY = 13 +}; + +/* Note, adapters can have more than one capability - +encoding as bitfield is recommended. */ +#define HPI_CAPABILITY_NONE (0) +#define HPI_CAPABILITY_MPEG_LAYER3 (1) + +/* Set this equal to maximum capability index, +Must not be greater than 32 - see axnvdef.h */ +#define HPI_CAPABILITY_MAX 1 +/* #define HPI_CAPABILITY_AAC 2 */ + +/******************************************* STREAM ATTRIBUTES ****/ + +/** MPEG Ancillary Data modes + +The mode for the ancillary data insertion or extraction to operate in. +\ingroup stream +*/ +enum HPI_MPEG_ANC_MODES { + /** the MPEG frames have energy information stored in them (5 bytes per stereo frame, 3 per mono) */ + HPI_MPEG_ANC_HASENERGY = 0, + /** the entire ancillary data field is taken up by data from the Anc data buffer + On encode, the encoder will insert the energy bytes before filling the remainder + of the ancillary data space with data from the ancillary data buffer. + */ + HPI_MPEG_ANC_RAW = 1 +}; + +/** Ancillary Data Alignment +\ingroup instream +*/ +enum HPI_ISTREAM_MPEG_ANC_ALIGNS { + /** data is packed against the end of data, then padded to the end of frame */ + HPI_MPEG_ANC_ALIGN_LEFT = 0, + /** data is packed against the end of the frame */ + HPI_MPEG_ANC_ALIGN_RIGHT = 1 +}; + +/** MPEG modes +MPEG modes - can be used optionally for HPI_FormatCreate() +parameter dwAttributes. + +Using any mode setting other than HPI_MPEG_MODE_DEFAULT +with single channel format will return an error. +\ingroup stream +*/ +enum HPI_MPEG_MODES { +/** Causes the MPEG-1 Layer II bitstream to be recorded +in single_channel mode when the number of channels is 1 and in stereo when the +number of channels is 2. */ + HPI_MPEG_MODE_DEFAULT = 0, + /** Standard stereo without joint-stereo compression */ + HPI_MPEG_MODE_STEREO = 1, + /** Joint stereo */ + HPI_MPEG_MODE_JOINTSTEREO = 2, + /** Left and Right channels are completely independent */ + HPI_MPEG_MODE_DUALCHANNEL = 3 +}; +/******************************************* MIXER ATTRIBUTES ****/ + +/* \defgroup mixer_flags Mixer flags for HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES +{ +*/ +#define HPI_MIXER_GET_CONTROL_MULTIPLE_CHANGED (0) +#define HPI_MIXER_GET_CONTROL_MULTIPLE_RESET (1) +/*}*/ + +/** Commands used by HPI_MixerStore() +\ingroup mixer +*/ +enum HPI_MIXER_STORE_COMMAND { +/** Save all mixer control settings. */ + HPI_MIXER_STORE_SAVE = 1, +/** Restore all controls from saved. */ + HPI_MIXER_STORE_RESTORE = 2, +/** Delete saved control settings. */ + HPI_MIXER_STORE_DELETE = 3, +/** Enable auto storage of some control settings. */ + HPI_MIXER_STORE_ENABLE = 4, +/** Disable auto storage of some control settings. */ + HPI_MIXER_STORE_DISABLE = 5, +/** Unimplemented - save the attributes of a single control. */ + HPI_MIXER_STORE_SAVE_SINGLE = 6 +}; + +/****************************/ +/* CONTROL ATTRIBUTE VALUES */ +/****************************/ + +/** Used by mixer plugin enable functions + +E.g. HPI_ParametricEq_SetState() +\ingroup mixer +*/ +enum HPI_SWITCH_STATES { + HPI_SWITCH_OFF = 0, /**< turn the mixer plugin on. */ + HPI_SWITCH_ON = 1 /**< turn the mixer plugin off. */ +}; + +/* Volume control special gain values */ + +/** volumes units are 100ths of a dB +\ingroup volume +*/ +#define HPI_UNITS_PER_dB 100 +/** turns volume control OFF or MUTE +\ingroup volume +*/ +#define HPI_GAIN_OFF (-100 * HPI_UNITS_PER_dB) + +/** channel mask specifying all channels +\ingroup volume +*/ +#define HPI_BITMASK_ALL_CHANNELS (0xFFFFFFFF) + +/** value returned for no signal +\ingroup meter +*/ +#define HPI_METER_MINIMUM (-150 * HPI_UNITS_PER_dB) + +/** autofade profiles +\ingroup volume +*/ +enum HPI_VOLUME_AUTOFADES { +/** log fade - dB attenuation changes linearly over time */ + HPI_VOLUME_AUTOFADE_LOG = 2, +/** linear fade - amplitude changes linearly */ + HPI_VOLUME_AUTOFADE_LINEAR = 3 +}; + +/** The physical encoding format of the AESEBU I/O. + +Used in HPI_Aesebu_Transmitter_SetFormat(), HPI_Aesebu_Receiver_SetFormat() +along with related Get and Query functions +\ingroup aestx +*/ +enum HPI_AESEBU_FORMATS { +/** AES/EBU physical format - AES/EBU balanced "professional" */ + HPI_AESEBU_FORMAT_AESEBU = 1, +/** AES/EBU physical format - S/PDIF unbalanced "consumer" */ + HPI_AESEBU_FORMAT_SPDIF = 2 +}; + +/** AES/EBU error status bits + +Returned by HPI_Aesebu_Receiver_GetErrorStatus() +\ingroup aesrx +*/ +enum HPI_AESEBU_ERRORS { +/** bit0: 1 when PLL is not locked */ + HPI_AESEBU_ERROR_NOT_LOCKED = 0x01, +/** bit1: 1 when signal quality is poor */ + HPI_AESEBU_ERROR_POOR_QUALITY = 0x02, +/** bit2: 1 when there is a parity error */ + HPI_AESEBU_ERROR_PARITY_ERROR = 0x04, +/** bit3: 1 when there is a bi-phase coding violation */ + HPI_AESEBU_ERROR_BIPHASE_VIOLATION = 0x08, +/** bit4: 1 when the validity bit is high */ + HPI_AESEBU_ERROR_VALIDITY = 0x10, +/** bit5: 1 when the CRC error bit is high */ + HPI_AESEBU_ERROR_CRC = 0x20 +}; + +/** \addtogroup pad +\{ +*/ +/** The text string containing the station/channel combination. */ +#define HPI_PAD_CHANNEL_NAME_LEN 16 +/** The text string containing the artist. */ +#define HPI_PAD_ARTIST_LEN 64 +/** The text string containing the title. */ +#define HPI_PAD_TITLE_LEN 64 +/** The text string containing the comment. */ +#define HPI_PAD_COMMENT_LEN 256 +/** The PTY when the tuner has not received any PTY. */ +#define HPI_PAD_PROGRAM_TYPE_INVALID 0xffff +/** \} */ + +/** Data types for PTY string translation. +\ingroup rds +*/ +enum eHPI_RDS_type { + HPI_RDS_DATATYPE_RDS = 0, /**< RDS bitstream.*/ + HPI_RDS_DATATYPE_RBDS = 1 /**< RBDS bitstream.*/ +}; + +/** Tuner bands + +Used for HPI_Tuner_SetBand(),HPI_Tuner_GetBand() +\ingroup tuner +*/ +enum HPI_TUNER_BAND { + HPI_TUNER_BAND_AM = 1, /**< AM band */ + HPI_TUNER_BAND_FM = 2, /**< FM band (mono) */ + HPI_TUNER_BAND_TV_NTSC_M = 3, /**< NTSC-M TV band*/ + HPI_TUNER_BAND_TV = 3, /* use TV_NTSC_M */ + HPI_TUNER_BAND_FM_STEREO = 4, /**< FM band (stereo) */ + HPI_TUNER_BAND_AUX = 5, /**< auxiliary input */ + HPI_TUNER_BAND_TV_PAL_BG = 6, /**< PAL-B/G TV band*/ + HPI_TUNER_BAND_TV_PAL_I = 7, /**< PAL-I TV band*/ + HPI_TUNER_BAND_TV_PAL_DK = 8, /**< PAL-D/K TV band*/ + HPI_TUNER_BAND_TV_SECAM_L = 9, /**< SECAM-L TV band*/ + HPI_TUNER_BAND_DAB = 10, + HPI_TUNER_BAND_LAST = 10 /**< the index of the last tuner band. */ +}; + +/** Tuner mode attributes + +Used by HPI_Tuner_SetMode(), HPI_Tuner_GetMode() +\ingroup tuner + +*/ +enum HPI_TUNER_MODES { + HPI_TUNER_MODE_RSS = 1, /**< control RSS */ + HPI_TUNER_MODE_RDS = 2 /**< control RBDS/RDS */ +}; + +/** Tuner mode attribute values + +Used by HPI_Tuner_SetMode(), HPI_Tuner_GetMode() +\ingroup tuner +*/ +enum HPI_TUNER_MODE_VALUES { +/* RSS attribute values */ + HPI_TUNER_MODE_RSS_DISABLE = 0, /**< RSS disable */ + HPI_TUNER_MODE_RSS_ENABLE = 1, /**< RSS enable */ + +/* RDS mode attributes */ + HPI_TUNER_MODE_RDS_DISABLE = 0, /**< RDS - disabled */ + HPI_TUNER_MODE_RDS_RDS = 1, /**< RDS - RDS mode */ + HPI_TUNER_MODE_RDS_RBDS = 2 /**< RDS - RBDS mode */ +}; + +/** Tuner Status Bits + +These bitfield values are returned by a call to HPI_Tuner_GetStatus(). +Multiple fields are returned from a single call. +\ingroup tuner +*/ +enum HPI_TUNER_STATUS_BITS { + HPI_TUNER_VIDEO_COLOR_PRESENT = 0x0001, /**< video color is present. */ + HPI_TUNER_VIDEO_IS_60HZ = 0x0020, /**< 60 hz video detected. */ + HPI_TUNER_VIDEO_HORZ_SYNC_MISSING = 0x0040, /**< video HSYNC is missing. */ + HPI_TUNER_VIDEO_STATUS_VALID = 0x0100, /**< video status is valid. */ + HPI_TUNER_DIGITAL = 0x0200, /**< tuner reports digital programming. */ + HPI_TUNER_MULTIPROGRAM = 0x0400, /**< tuner reports multiple programs. */ + HPI_TUNER_PLL_LOCKED = 0x1000, /**< the tuner's PLL is locked. */ + HPI_TUNER_FM_STEREO = 0x2000 /**< tuner reports back FM stereo. */ +}; + +/** Channel Modes +Used for HPI_ChannelModeSet/Get() +\ingroup channelmode +*/ +enum HPI_CHANNEL_MODES { +/** Left channel out = left channel in, Right channel out = right channel in. */ + HPI_CHANNEL_MODE_NORMAL = 1, +/** Left channel out = right channel in, Right channel out = left channel in. */ + HPI_CHANNEL_MODE_SWAP = 2, +/** Left channel out = left channel in, Right channel out = left channel in. */ + HPI_CHANNEL_MODE_LEFT_TO_STEREO = 3, +/** Left channel out = right channel in, Right channel out = right channel in.*/ + HPI_CHANNEL_MODE_RIGHT_TO_STEREO = 4, +/** Left channel out = (left channel in + right channel in)/2, + Right channel out = mute. */ + HPI_CHANNEL_MODE_STEREO_TO_LEFT = 5, +/** Left channel out = mute, + Right channel out = (right channel in + left channel in)/2. */ + HPI_CHANNEL_MODE_STEREO_TO_RIGHT = 6, + HPI_CHANNEL_MODE_LAST = 6 +}; + +/** SampleClock source values +\ingroup sampleclock +*/ +enum HPI_SAMPLECLOCK_SOURCES { +/** The sampleclock output is derived from its local samplerate generator. + The local samplerate may be set using HPI_SampleClock_SetLocalRate(). */ + HPI_SAMPLECLOCK_SOURCE_LOCAL = 1, +/** The adapter is clocked from a dedicated AES/EBU SampleClock input.*/ + HPI_SAMPLECLOCK_SOURCE_AESEBU_SYNC = 2, +/** From external wordclock connector */ + HPI_SAMPLECLOCK_SOURCE_WORD = 3, +/** Board-to-board header */ + HPI_SAMPLECLOCK_SOURCE_WORD_HEADER = 4, +/** FUTURE - SMPTE clock. */ + HPI_SAMPLECLOCK_SOURCE_SMPTE = 5, +/** One of the aesebu inputs */ + HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT = 6, +/** From a network interface e.g. Cobranet or Livewire at either 48 or 96kHz */ + HPI_SAMPLECLOCK_SOURCE_NETWORK = 8, +/** From previous adjacent module (ASI2416 only)*/ + HPI_SAMPLECLOCK_SOURCE_PREV_MODULE = 10, +/** Blu link sample clock*/ + HPI_SAMPLECLOCK_SOURCE_BLULINK = 11, +/*! Update this if you add a new clock source.*/ + HPI_SAMPLECLOCK_SOURCE_LAST = 11 +}; + +/** Equalizer filter types. Used by HPI_ParametricEq_SetBand() +\ingroup parmeq +*/ +enum HPI_FILTER_TYPE { + HPI_FILTER_TYPE_BYPASS = 0, /**< filter is turned off */ + + HPI_FILTER_TYPE_LOWSHELF = 1, /**< EQ low shelf */ + HPI_FILTER_TYPE_HIGHSHELF = 2, /**< EQ high shelf */ + HPI_FILTER_TYPE_EQ_BAND = 3, /**< EQ gain */ + + HPI_FILTER_TYPE_LOWPASS = 4, /**< standard low pass */ + HPI_FILTER_TYPE_HIGHPASS = 5, /**< standard high pass */ + HPI_FILTER_TYPE_BANDPASS = 6, /**< standard band pass */ + HPI_FILTER_TYPE_BANDSTOP = 7 /**< standard band stop/notch */ +}; + +/** Async Event sources +\ingroup async +*/ +enum ASYNC_EVENT_SOURCES { + HPI_ASYNC_EVENT_GPIO = 1, /**< GPIO event. */ + HPI_ASYNC_EVENT_SILENCE = 2, /**< silence event detected. */ + HPI_ASYNC_EVENT_TONE = 3 /**< tone event detected. */ +}; +/*******************************************/ +/** HPI Error codes + +Almost all HPI functions return an error code +A return value of zero means there was no error. +Otherwise one of these error codes is returned. +Error codes can be converted to a descriptive string using HPI_GetErrorText() + +\note When a new error code is added HPI_GetErrorText() MUST be updated. +\note Codes 1-100 are reserved for driver use +\ingroup utility +*/ +enum HPI_ERROR_CODES { + /** Message type does not exist. */ + HPI_ERROR_INVALID_TYPE = 100, + /** Object type does not exist. */ + HPI_ERROR_INVALID_OBJ = 101, + /** Function does not exist. */ + HPI_ERROR_INVALID_FUNC = 102, + /** The specified object does not exist. */ + HPI_ERROR_INVALID_OBJ_INDEX = 103, + /** Trying to access an object that has not been opened yet. */ + HPI_ERROR_OBJ_NOT_OPEN = 104, + /** Trying to open an already open object. */ + HPI_ERROR_OBJ_ALREADY_OPEN = 105, + /** PCI, ISA resource not valid. */ + HPI_ERROR_INVALID_RESOURCE = 106, + /* HPI_ERROR_SUBSYSFINDADAPTERS_GETINFO= 107 */ + /** Default response was never updated with actual error code. */ + HPI_ERROR_INVALID_RESPONSE = 108, + /** wSize field of response was not updated, + indicating that the message was not processed. */ + HPI_ERROR_PROCESSING_MESSAGE = 109, + /** The network did not respond in a timely manner. */ + HPI_ERROR_NETWORK_TIMEOUT = 110, + /* An HPI handle is invalid (uninitialised?). */ + HPI_ERROR_INVALID_HANDLE = 111, + /** A function or attribute has not been implemented yet. */ + HPI_ERROR_UNIMPLEMENTED = 112, + /** There are too many clients attempting + to access a network resource. */ + HPI_ERROR_NETWORK_TOO_MANY_CLIENTS = 113, + /** Response buffer passed to HPI_Message + was smaller than returned response. + wSpecificError field of hpi response contains the required size. + */ + HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL = 114, + /** The returned response did not match the sent message */ + HPI_ERROR_RESPONSE_MISMATCH = 115, + /** A control setting that should have been cached was not. */ + HPI_ERROR_CONTROL_CACHING = 116, + /** A message buffer in the path to the adapter was smaller + than the message size. + wSpecificError field of hpi response contains the actual size. + */ + HPI_ERROR_MESSAGE_BUFFER_TOO_SMALL = 117, + + /* HPI_ERROR_TOO_MANY_ADAPTERS= 200 */ + /** Bad adpater. */ + HPI_ERROR_BAD_ADAPTER = 201, + /** Adapter number out of range or not set properly. */ + HPI_ERROR_BAD_ADAPTER_NUMBER = 202, + /** 2 adapters with the same adapter number. */ + HPI_ERROR_DUPLICATE_ADAPTER_NUMBER = 203, + /** DSP code failed to bootload. Usually a DSP memory test failure. */ + HPI_ERROR_DSP_BOOTLOAD = 204, + /** Couldn't find or open the DSP code file. */ + HPI_ERROR_DSP_FILE_NOT_FOUND = 206, + /** Internal DSP hardware error. */ + HPI_ERROR_DSP_HARDWARE = 207, + /** Could not allocate memory */ + HPI_ERROR_MEMORY_ALLOC = 208, + /** Failed to correctly load/config PLD. (unused) */ + HPI_ERROR_PLD_LOAD = 209, + /** Unexpected end of file, block length too big etc. */ + HPI_ERROR_DSP_FILE_FORMAT = 210, + + /** Found but could not open DSP code file. */ + HPI_ERROR_DSP_FILE_ACCESS_DENIED = 211, + /** First DSP code section header not found in DSP file. */ + HPI_ERROR_DSP_FILE_NO_HEADER = 212, + /* HPI_ERROR_DSP_FILE_READ_ERROR= 213, */ + /** DSP code for adapter family not found. */ + HPI_ERROR_DSP_SECTION_NOT_FOUND = 214, + /** Other OS specific error opening DSP file. */ + HPI_ERROR_DSP_FILE_OTHER_ERROR = 215, + /** Sharing violation opening DSP code file. */ + HPI_ERROR_DSP_FILE_SHARING_VIOLATION = 216, + /** DSP code section header had size == 0. */ + HPI_ERROR_DSP_FILE_NULL_HEADER = 217, + + /* HPI_ERROR_FLASH = 220, */ + + /** Flash has bad checksum */ + HPI_ERROR_BAD_CHECKSUM = 221, + HPI_ERROR_BAD_SEQUENCE = 222, + HPI_ERROR_FLASH_ERASE = 223, + HPI_ERROR_FLASH_PROGRAM = 224, + HPI_ERROR_FLASH_VERIFY = 225, + HPI_ERROR_FLASH_TYPE = 226, + HPI_ERROR_FLASH_START = 227, + HPI_ERROR_FLASH_READ = 228, + HPI_ERROR_FLASH_READ_NO_FILE = 229, + HPI_ERROR_FLASH_SIZE = 230, + + /** Reserved for OEMs. */ + HPI_ERROR_RESERVED_1 = 290, + + /* HPI_ERROR_INVALID_STREAM = 300 use HPI_ERROR_INVALID_OBJ_INDEX */ + /** Invalid compression format. */ + HPI_ERROR_INVALID_FORMAT = 301, + /** Invalid format samplerate */ + HPI_ERROR_INVALID_SAMPLERATE = 302, + /** Invalid format number of channels. */ + HPI_ERROR_INVALID_CHANNELS = 303, + /** Invalid format bitrate. */ + HPI_ERROR_INVALID_BITRATE = 304, + /** Invalid datasize used for stream read/write. */ + HPI_ERROR_INVALID_DATASIZE = 305, + /* HPI_ERROR_BUFFER_FULL = 306 use HPI_ERROR_INVALID_DATASIZE */ + /* HPI_ERROR_BUFFER_EMPTY = 307 use HPI_ERROR_INVALID_DATASIZE */ + /** Null data pointer used for stream read/write. */ + HPI_ERROR_INVALID_DATA_POINTER = 308, + /** Packet ordering error for stream read/write. */ + HPI_ERROR_INVALID_PACKET_ORDER = 309, + + /** Object can't do requested operation in its current + state, eg set format, change rec mux state while recording.*/ + HPI_ERROR_INVALID_OPERATION = 310, + + /** Where a SRG is shared amongst streams, an incompatible samplerate + is one that is different to any currently active stream. */ + HPI_ERROR_INCOMPATIBLE_SAMPLERATE = 311, + /** Adapter mode is illegal.*/ + HPI_ERROR_BAD_ADAPTER_MODE = 312, + + /** There have been too many attempts to set the adapter's + capabilities (using bad keys), the card should be returned + to ASI if further capabilities updates are required */ + HPI_ERROR_TOO_MANY_CAPABILITY_CHANGE_ATTEMPTS = 313, + /** Streams on different adapters cannot be grouped. */ + HPI_ERROR_NO_INTERADAPTER_GROUPS = 314, + /** Streams on different DSPs cannot be grouped. */ + HPI_ERROR_NO_INTERDSP_GROUPS = 315, + /** Stream wait cancelled before threshold reached. */ + HPI_ERROR_WAIT_CANCELLED = 316, + /** A character string is invalid. */ + HPI_ERROR_INVALID_STRING = 317, + + /** Invalid mixer node for this adapter. */ + HPI_ERROR_INVALID_NODE = 400, + /** Invalid control. */ + HPI_ERROR_INVALID_CONTROL = 401, + /** Invalid control value was passed. */ + HPI_ERROR_INVALID_CONTROL_VALUE = 402, + /** Control attribute not supported by this control. */ + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE = 403, + /** Control is disabled. */ + HPI_ERROR_CONTROL_DISABLED = 404, + /** I2C transaction failed due to a missing ACK. */ + HPI_ERROR_CONTROL_I2C_MISSING_ACK = 405, + HPI_ERROR_I2C_MISSING_ACK = 405, + /** Control is busy, or coming out of + reset and cannot be accessed at this time. */ + HPI_ERROR_CONTROL_NOT_READY = 407, + + /** Non volatile memory */ + HPI_ERROR_NVMEM_BUSY = 450, + HPI_ERROR_NVMEM_FULL = 451, + HPI_ERROR_NVMEM_FAIL = 452, + + /** I2C */ + HPI_ERROR_I2C_BAD_ADR = 460, + + /** Entity type did not match requested type */ + HPI_ERROR_ENTITY_TYPE_MISMATCH = 470, + /** Entity item count did not match requested count */ + HPI_ERROR_ENTITY_ITEM_COUNT = 471, + /** Entity type is not one of the valid types */ + HPI_ERROR_ENTITY_TYPE_INVALID = 472, + /** Entity role is not one of the valid roles */ + HPI_ERROR_ENTITY_ROLE_INVALID = 473, + /** Entity size doesn't match target size */ + HPI_ERROR_ENTITY_SIZE_MISMATCH = 474, + + /* AES18 specific errors were 500..507 */ + + /** custom error to use for debugging */ + HPI_ERROR_CUSTOM = 600, + + /** hpioct32.c can't obtain mutex */ + HPI_ERROR_MUTEX_TIMEOUT = 700, + + /** Backend errors used to be greater than this. + \deprecated Now, all backends return only errors defined here in hpi.h + */ + HPI_ERROR_BACKEND_BASE = 900, + + /** Communication with DSP failed */ + HPI_ERROR_DSP_COMMUNICATION = 900 + /* Note that the dsp communication error is set to this value so that + it remains compatible with any software that expects such errors + to be backend errors i.e. >= 900. + Do not define any new error codes with values > 900. + */ +}; + +/** \defgroup maximums HPI maximum values +\{ +*/ +/** Maximum number of PCI HPI adapters */ +#define HPI_MAX_ADAPTERS 20 +/** Maximum number of in or out streams per adapter */ +#define HPI_MAX_STREAMS 16 +#define HPI_MAX_CHANNELS 2 /* per stream */ +#define HPI_MAX_NODES 8 /* per mixer ? */ +#define HPI_MAX_CONTROLS 4 /* per node ? */ +/** maximum number of ancillary bytes per MPEG frame */ +#define HPI_MAX_ANC_BYTES_PER_FRAME (64) +#define HPI_STRING_LEN 16 + +/** Networked adapters have index >= 100 */ +#define HPI_MIN_NETWORK_ADAPTER_IDX 100 + +/** Velocity units */ +#define HPI_OSTREAM_VELOCITY_UNITS 4096 +/** OutStream timescale units */ +#define HPI_OSTREAM_TIMESCALE_UNITS 10000 +/** OutStream timescale passthrough - turns timescaling on in passthough mode */ +#define HPI_OSTREAM_TIMESCALE_PASSTHROUGH 99999 + +/**\}*/ + +/**************/ +/* STRUCTURES */ +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(push, 1) +#endif + +/** Structure containing sample format information. + See also HPI_FormatCreate(). + */ +struct hpi_format { + u32 sample_rate; + /**< 11025, 32000, 44100 ... */ + u32 bit_rate; /**< for MPEG */ + u32 attributes; + /**< Stereo/JointStereo/Mono */ + u16 mode_legacy; + /**< Legacy ancillary mode or idle bit */ + u16 unused; /**< Unused */ + u16 channels; /**< 1,2..., (or ancillary mode or idle bit */ + u16 format; /**< HPI_FORMAT_PCM16, _MPEG etc. see #HPI_FORMATS. */ +}; + +struct hpi_anc_frame { + u32 valid_bits_in_this_frame; + u8 b_data[HPI_MAX_ANC_BYTES_PER_FRAME]; +}; + +/** An object for containing a single async event. +*/ +struct hpi_async_event { + u16 event_type; /**< type of event. \sa async_event */ + u16 sequence; /**< Sequence number, allows lost event detection */ + u32 state; /**< New state */ + u32 h_object; /**< handle to the object returning the event. */ + union { + struct { + u16 index; /**< GPIO bit index. */ + } gpio; + struct { + u16 node_index; /**< what node is the control on ? */ + u16 node_type; /**< what type of node is the control on ? */ + } control; + } u; +}; + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(pop) +#endif + +/*****************/ +/* HPI FUNCTIONS */ +/*****************/ + +/* Stream */ +u16 hpi_stream_estimate_buffer_size(struct hpi_format *pF, + u32 host_polling_rate_in_milli_seconds, u32 *recommended_buffer_size); + +/*************/ +/* SubSystem */ +/*************/ + +u16 hpi_subsys_get_version_ex(u32 *pversion_ex); + +u16 hpi_subsys_get_num_adapters(int *pn_num_adapters); + +u16 hpi_subsys_get_adapter(int iterator, u32 *padapter_index, + u16 *pw_adapter_type); + +/***********/ +/* Adapter */ +/***********/ + +u16 hpi_adapter_open(u16 adapter_index); + +u16 hpi_adapter_close(u16 adapter_index); + +u16 hpi_adapter_get_info(u16 adapter_index, u16 *pw_num_outstreams, + u16 *pw_num_instreams, u16 *pw_version, u32 *pserial_number, + u16 *pw_adapter_type); + +u16 hpi_adapter_get_module_by_index(u16 adapter_index, u16 module_index, + u16 *pw_num_outputs, u16 *pw_num_inputs, u16 *pw_version, + u32 *pserial_number, u16 *pw_module_type, u32 *ph_module); + +u16 hpi_adapter_set_mode(u16 adapter_index, u32 adapter_mode); + +u16 hpi_adapter_set_mode_ex(u16 adapter_index, u32 adapter_mode, + u16 query_or_set); + +u16 hpi_adapter_get_mode(u16 adapter_index, u32 *padapter_mode); + +u16 hpi_adapter_set_property(u16 adapter_index, u16 property, u16 paramter1, + u16 paramter2); + +u16 hpi_adapter_get_property(u16 adapter_index, u16 property, + u16 *pw_paramter1, u16 *pw_paramter2); + +u16 hpi_adapter_enumerate_property(u16 adapter_index, u16 index, + u16 what_to_enumerate, u16 property_index, u32 *psetting); +/*************/ +/* OutStream */ +/*************/ +u16 hpi_outstream_open(u16 adapter_index, u16 outstream_index, + u32 *ph_outstream); + +u16 hpi_outstream_close(u32 h_outstream); + +u16 hpi_outstream_get_info_ex(u32 h_outstream, u16 *pw_state, + u32 *pbuffer_size, u32 *pdata_to_play, u32 *psamples_played, + u32 *pauxiliary_data_to_play); + +u16 hpi_outstream_write_buf(u32 h_outstream, const u8 *pb_write_buf, + u32 bytes_to_write, const struct hpi_format *p_format); + +u16 hpi_outstream_start(u32 h_outstream); + +u16 hpi_outstream_wait_start(u32 h_outstream); + +u16 hpi_outstream_stop(u32 h_outstream); + +u16 hpi_outstream_sinegen(u32 h_outstream); + +u16 hpi_outstream_reset(u32 h_outstream); + +u16 hpi_outstream_query_format(u32 h_outstream, struct hpi_format *p_format); + +u16 hpi_outstream_set_format(u32 h_outstream, struct hpi_format *p_format); + +u16 hpi_outstream_set_punch_in_out(u32 h_outstream, u32 punch_in_sample, + u32 punch_out_sample); + +u16 hpi_outstream_set_velocity(u32 h_outstream, short velocity); + +u16 hpi_outstream_ancillary_reset(u32 h_outstream, u16 mode); + +u16 hpi_outstream_ancillary_get_info(u32 h_outstream, u32 *pframes_available); + +u16 hpi_outstream_ancillary_read(u32 h_outstream, + struct hpi_anc_frame *p_anc_frame_buffer, + u32 anc_frame_buffer_size_in_bytes, + u32 number_of_ancillary_frames_to_read); + +u16 hpi_outstream_set_time_scale(u32 h_outstream, u32 time_scaleX10000); + +u16 hpi_outstream_host_buffer_allocate(u32 h_outstream, u32 size_in_bytes); + +u16 hpi_outstream_host_buffer_free(u32 h_outstream); + +u16 hpi_outstream_group_add(u32 h_outstream, u32 h_stream); + +u16 hpi_outstream_group_get_map(u32 h_outstream, u32 *poutstream_map, + u32 *pinstream_map); + +u16 hpi_outstream_group_reset(u32 h_outstream); + +/************/ +/* InStream */ +/************/ +u16 hpi_instream_open(u16 adapter_index, u16 instream_index, + u32 *ph_instream); + +u16 hpi_instream_close(u32 h_instream); + +u16 hpi_instream_query_format(u32 h_instream, + const struct hpi_format *p_format); + +u16 hpi_instream_set_format(u32 h_instream, + const struct hpi_format *p_format); + +u16 hpi_instream_read_buf(u32 h_instream, u8 *pb_read_buf, u32 bytes_to_read); + +u16 hpi_instream_start(u32 h_instream); + +u16 hpi_instream_wait_start(u32 h_instream); + +u16 hpi_instream_stop(u32 h_instream); + +u16 hpi_instream_reset(u32 h_instream); + +u16 hpi_instream_get_info_ex(u32 h_instream, u16 *pw_state, u32 *pbuffer_size, + u32 *pdata_recorded, u32 *psamples_recorded, + u32 *pauxiliary_data_recorded); + +u16 hpi_instream_ancillary_reset(u32 h_instream, u16 bytes_per_frame, + u16 mode, u16 alignment, u16 idle_bit); + +u16 hpi_instream_ancillary_get_info(u32 h_instream, u32 *pframe_space); + +u16 hpi_instream_ancillary_write(u32 h_instream, + const struct hpi_anc_frame *p_anc_frame_buffer, + u32 anc_frame_buffer_size_in_bytes, + u32 number_of_ancillary_frames_to_write); + +u16 hpi_instream_host_buffer_allocate(u32 h_instream, u32 size_in_bytes); + +u16 hpi_instream_host_buffer_free(u32 h_instream); + +u16 hpi_instream_group_add(u32 h_instream, u32 h_stream); + +u16 hpi_instream_group_get_map(u32 h_instream, u32 *poutstream_map, + u32 *pinstream_map); + +u16 hpi_instream_group_reset(u32 h_instream); + +/*********/ +/* Mixer */ +/*********/ +u16 hpi_mixer_open(u16 adapter_index, u32 *ph_mixer); + +u16 hpi_mixer_close(u32 h_mixer); + +u16 hpi_mixer_get_control(u32 h_mixer, u16 src_node_type, + u16 src_node_type_index, u16 dst_node_type, u16 dst_node_type_index, + u16 control_type, u32 *ph_control); + +u16 hpi_mixer_get_control_by_index(u32 h_mixer, u16 control_index, + u16 *pw_src_node_type, u16 *pw_src_node_index, u16 *pw_dst_node_type, + u16 *pw_dst_node_index, u16 *pw_control_type, u32 *ph_control); + +u16 hpi_mixer_store(u32 h_mixer, enum HPI_MIXER_STORE_COMMAND command, + u16 index); +/************/ +/* Controls */ +/************/ +/******************/ +/* Volume control */ +/******************/ +u16 hpi_volume_set_gain(u32 h_control, short an_gain0_01dB[HPI_MAX_CHANNELS] + ); + +u16 hpi_volume_get_gain(u32 h_control, + short an_gain0_01dB_out[HPI_MAX_CHANNELS] + ); + +u16 hpi_volume_set_mute(u32 h_control, u32 mute); + +u16 hpi_volume_get_mute(u32 h_control, u32 *mute); + +#define hpi_volume_get_range hpi_volume_query_range +u16 hpi_volume_query_range(u32 h_control, short *min_gain_01dB, + short *max_gain_01dB, short *step_gain_01dB); + +u16 hpi_volume_query_channels(const u32 h_control, u32 *p_channels); + +u16 hpi_volume_auto_fade(u32 h_control, + short an_stop_gain0_01dB[HPI_MAX_CHANNELS], u32 duration_ms); + +u16 hpi_volume_auto_fade_profile(u32 h_control, + short an_stop_gain0_01dB[HPI_MAX_CHANNELS], u32 duration_ms, + u16 profile); + +u16 hpi_volume_query_auto_fade_profile(const u32 h_control, const u32 i, + u16 *profile); + +/*****************/ +/* Level control */ +/*****************/ +u16 hpi_level_query_range(u32 h_control, short *min_gain_01dB, + short *max_gain_01dB, short *step_gain_01dB); + +u16 hpi_level_set_gain(u32 h_control, short an_gain0_01dB[HPI_MAX_CHANNELS] + ); + +u16 hpi_level_get_gain(u32 h_control, + short an_gain0_01dB_out[HPI_MAX_CHANNELS] + ); + +/*****************/ +/* Meter control */ +/*****************/ +u16 hpi_meter_query_channels(const u32 h_meter, u32 *p_channels); + +u16 hpi_meter_get_peak(u32 h_control, + short an_peak0_01dB_out[HPI_MAX_CHANNELS] + ); + +u16 hpi_meter_get_rms(u32 h_control, short an_peak0_01dB_out[HPI_MAX_CHANNELS] + ); + +u16 hpi_meter_set_peak_ballistics(u32 h_control, u16 attack, u16 decay); + +u16 hpi_meter_set_rms_ballistics(u32 h_control, u16 attack, u16 decay); + +u16 hpi_meter_get_peak_ballistics(u32 h_control, u16 *attack, u16 *decay); + +u16 hpi_meter_get_rms_ballistics(u32 h_control, u16 *attack, u16 *decay); + +/************************/ +/* ChannelMode control */ +/************************/ +u16 hpi_channel_mode_query_mode(const u32 h_mode, const u32 index, + u16 *pw_mode); + +u16 hpi_channel_mode_set(u32 h_control, u16 mode); + +u16 hpi_channel_mode_get(u32 h_control, u16 *mode); + +/*****************/ +/* Tuner control */ +/*****************/ +u16 hpi_tuner_query_band(const u32 h_tuner, const u32 index, u16 *pw_band); + +u16 hpi_tuner_set_band(u32 h_control, u16 band); + +u16 hpi_tuner_get_band(u32 h_control, u16 *pw_band); + +u16 hpi_tuner_query_frequency(const u32 h_tuner, const u32 index, + const u16 band, u32 *pfreq); + +u16 hpi_tuner_set_frequency(u32 h_control, u32 freq_ink_hz); + +u16 hpi_tuner_get_frequency(u32 h_control, u32 *pw_freq_ink_hz); + +u16 hpi_tuner_get_rf_level(u32 h_control, short *pw_level); + +u16 hpi_tuner_get_raw_rf_level(u32 h_control, short *pw_level); + +u16 hpi_tuner_query_gain(const u32 h_tuner, const u32 index, u16 *pw_gain); + +u16 hpi_tuner_set_gain(u32 h_control, short gain); + +u16 hpi_tuner_get_gain(u32 h_control, short *pn_gain); + +u16 hpi_tuner_get_status(u32 h_control, u16 *pw_status_mask, u16 *pw_status); + +u16 hpi_tuner_set_mode(u32 h_control, u32 mode, u32 value); + +u16 hpi_tuner_get_mode(u32 h_control, u32 mode, u32 *pn_value); + +u16 hpi_tuner_get_rds(u32 h_control, char *p_rds_data); + +u16 hpi_tuner_query_deemphasis(const u32 h_tuner, const u32 index, + const u16 band, u32 *pdeemphasis); + +u16 hpi_tuner_set_deemphasis(u32 h_control, u32 deemphasis); +u16 hpi_tuner_get_deemphasis(u32 h_control, u32 *pdeemphasis); + +u16 hpi_tuner_query_program(const u32 h_tuner, u32 *pbitmap_program); + +u16 hpi_tuner_set_program(u32 h_control, u32 program); + +u16 hpi_tuner_get_program(u32 h_control, u32 *pprogram); + +u16 hpi_tuner_get_hd_radio_dsp_version(u32 h_control, char *psz_dsp_version, + const u32 string_size); + +u16 hpi_tuner_get_hd_radio_sdk_version(u32 h_control, char *psz_sdk_version, + const u32 string_size); + +u16 hpi_tuner_get_hd_radio_signal_quality(u32 h_control, u32 *pquality); + +u16 hpi_tuner_get_hd_radio_signal_blend(u32 h_control, u32 *pblend); + +u16 hpi_tuner_set_hd_radio_signal_blend(u32 h_control, const u32 blend); + +/***************/ +/* PAD control */ +/***************/ + +u16 hpi_pad_get_channel_name(u32 h_control, char *psz_string, + const u32 string_length); + +u16 hpi_pad_get_artist(u32 h_control, char *psz_string, + const u32 string_length); + +u16 hpi_pad_get_title(u32 h_control, char *psz_string, + const u32 string_length); + +u16 hpi_pad_get_comment(u32 h_control, char *psz_string, + const u32 string_length); + +u16 hpi_pad_get_program_type(u32 h_control, u32 *ppTY); + +u16 hpi_pad_get_rdsPI(u32 h_control, u32 *ppI); + +/****************************/ +/* AES/EBU Receiver control */ +/****************************/ +u16 hpi_aesebu_receiver_query_format(const u32 h_aes_rx, const u32 index, + u16 *pw_format); + +u16 hpi_aesebu_receiver_set_format(u32 h_control, u16 source); + +u16 hpi_aesebu_receiver_get_format(u32 h_control, u16 *pw_source); + +u16 hpi_aesebu_receiver_get_sample_rate(u32 h_control, u32 *psample_rate); + +u16 hpi_aesebu_receiver_get_user_data(u32 h_control, u16 index, u16 *pw_data); + +u16 hpi_aesebu_receiver_get_channel_status(u32 h_control, u16 index, + u16 *pw_data); + +u16 hpi_aesebu_receiver_get_error_status(u32 h_control, u16 *pw_error_data); + +/*******************************/ +/* AES/EBU Transmitter control */ +/*******************************/ +u16 hpi_aesebu_transmitter_set_sample_rate(u32 h_control, u32 sample_rate); + +u16 hpi_aesebu_transmitter_set_user_data(u32 h_control, u16 index, u16 data); + +u16 hpi_aesebu_transmitter_set_channel_status(u32 h_control, u16 index, + u16 data); + +u16 hpi_aesebu_transmitter_get_channel_status(u32 h_control, u16 index, + u16 *pw_data); + +u16 hpi_aesebu_transmitter_query_format(const u32 h_aes_tx, const u32 index, + u16 *pw_format); + +u16 hpi_aesebu_transmitter_set_format(u32 h_control, u16 output_format); + +u16 hpi_aesebu_transmitter_get_format(u32 h_control, u16 *pw_output_format); + +/***********************/ +/* Multiplexer control */ +/***********************/ +u16 hpi_multiplexer_set_source(u32 h_control, u16 source_node_type, + u16 source_node_index); + +u16 hpi_multiplexer_get_source(u32 h_control, u16 *source_node_type, + u16 *source_node_index); + +u16 hpi_multiplexer_query_source(u32 h_control, u16 index, + u16 *source_node_type, u16 *source_node_index); + +/***************/ +/* Vox control */ +/***************/ +u16 hpi_vox_set_threshold(u32 h_control, short an_gain0_01dB); + +u16 hpi_vox_get_threshold(u32 h_control, short *an_gain0_01dB); + +/*********************/ +/* Bitstream control */ +/*********************/ +u16 hpi_bitstream_set_clock_edge(u32 h_control, u16 edge_type); + +u16 hpi_bitstream_set_data_polarity(u32 h_control, u16 polarity); + +u16 hpi_bitstream_get_activity(u32 h_control, u16 *pw_clk_activity, + u16 *pw_data_activity); + +/***********************/ +/* SampleClock control */ +/***********************/ + +u16 hpi_sample_clock_query_source(const u32 h_clock, const u32 index, + u16 *pw_source); + +u16 hpi_sample_clock_set_source(u32 h_control, u16 source); + +u16 hpi_sample_clock_get_source(u32 h_control, u16 *pw_source); + +u16 hpi_sample_clock_query_source_index(const u32 h_clock, const u32 index, + const u32 source, u16 *pw_source_index); + +u16 hpi_sample_clock_set_source_index(u32 h_control, u16 source_index); + +u16 hpi_sample_clock_get_source_index(u32 h_control, u16 *pw_source_index); + +u16 hpi_sample_clock_get_sample_rate(u32 h_control, u32 *psample_rate); + +u16 hpi_sample_clock_query_local_rate(const u32 h_clock, const u32 index, + u32 *psource); + +u16 hpi_sample_clock_set_local_rate(u32 h_control, u32 sample_rate); + +u16 hpi_sample_clock_get_local_rate(u32 h_control, u32 *psample_rate); + +u16 hpi_sample_clock_set_auto(u32 h_control, u32 enable); + +u16 hpi_sample_clock_get_auto(u32 h_control, u32 *penable); + +u16 hpi_sample_clock_set_local_rate_lock(u32 h_control, u32 lock); + +u16 hpi_sample_clock_get_local_rate_lock(u32 h_control, u32 *plock); + +/***********************/ +/* Microphone control */ +/***********************/ +u16 hpi_microphone_set_phantom_power(u32 h_control, u16 on_off); + +u16 hpi_microphone_get_phantom_power(u32 h_control, u16 *pw_on_off); + +/********************************/ +/* Parametric Equalizer control */ +/********************************/ +u16 hpi_parametric_eq_get_info(u32 h_control, u16 *pw_number_of_bands, + u16 *pw_enabled); + +u16 hpi_parametric_eq_set_state(u32 h_control, u16 on_off); + +u16 hpi_parametric_eq_set_band(u32 h_control, u16 index, u16 type, + u32 frequency_hz, short q100, short gain0_01dB); + +u16 hpi_parametric_eq_get_band(u32 h_control, u16 index, u16 *pn_type, + u32 *pfrequency_hz, short *pnQ100, short *pn_gain0_01dB); + +u16 hpi_parametric_eq_get_coeffs(u32 h_control, u16 index, short coeffs[5] + ); + +/*******************************/ +/* Compressor Expander control */ +/*******************************/ + +u16 hpi_compander_set_enable(u32 h_control, u32 on); + +u16 hpi_compander_get_enable(u32 h_control, u32 *pon); + +u16 hpi_compander_set_makeup_gain(u32 h_control, short makeup_gain0_01dB); + +u16 hpi_compander_get_makeup_gain(u32 h_control, short *pn_makeup_gain0_01dB); + +u16 hpi_compander_set_attack_time_constant(u32 h_control, u32 index, + u32 attack); + +u16 hpi_compander_get_attack_time_constant(u32 h_control, u32 index, + u32 *pw_attack); + +u16 hpi_compander_set_decay_time_constant(u32 h_control, u32 index, + u32 decay); + +u16 hpi_compander_get_decay_time_constant(u32 h_control, u32 index, + u32 *pw_decay); + +u16 hpi_compander_set_threshold(u32 h_control, u32 index, + short threshold0_01dB); + +u16 hpi_compander_get_threshold(u32 h_control, u32 index, + short *pn_threshold0_01dB); + +u16 hpi_compander_set_ratio(u32 h_control, u32 index, u32 ratio100); + +u16 hpi_compander_get_ratio(u32 h_control, u32 index, u32 *pw_ratio100); + +/********************/ +/* Cobranet control */ +/********************/ +u16 hpi_cobranet_hmi_write(u32 h_control, u32 hmi_address, u32 byte_count, + u8 *pb_data); + +u16 hpi_cobranet_hmi_read(u32 h_control, u32 hmi_address, u32 max_byte_count, + u32 *pbyte_count, u8 *pb_data); + +u16 hpi_cobranet_hmi_get_status(u32 h_control, u32 *pstatus, + u32 *preadable_size, u32 *pwriteable_size); + +u16 hpi_cobranet_get_ip_address(u32 h_control, u32 *pdw_ip_address); + +u16 hpi_cobranet_set_ip_address(u32 h_control, u32 dw_ip_address); + +u16 hpi_cobranet_get_static_ip_address(u32 h_control, u32 *pdw_ip_address); + +u16 hpi_cobranet_set_static_ip_address(u32 h_control, u32 dw_ip_address); + +u16 hpi_cobranet_get_macaddress(u32 h_control, u32 *p_mac_msbs, + u32 *p_mac_lsbs); + +/*************************/ +/* Tone Detector control */ +/*************************/ +u16 hpi_tone_detector_get_state(u32 hC, u32 *state); + +u16 hpi_tone_detector_set_enable(u32 hC, u32 enable); + +u16 hpi_tone_detector_get_enable(u32 hC, u32 *enable); + +u16 hpi_tone_detector_set_event_enable(u32 hC, u32 event_enable); + +u16 hpi_tone_detector_get_event_enable(u32 hC, u32 *event_enable); + +u16 hpi_tone_detector_set_threshold(u32 hC, int threshold); + +u16 hpi_tone_detector_get_threshold(u32 hC, int *threshold); + +u16 hpi_tone_detector_get_frequency(u32 hC, u32 index, u32 *frequency); + +/****************************/ +/* Silence Detector control */ +/****************************/ +u16 hpi_silence_detector_get_state(u32 hC, u32 *state); + +u16 hpi_silence_detector_set_enable(u32 hC, u32 enable); + +u16 hpi_silence_detector_get_enable(u32 hC, u32 *enable); + +u16 hpi_silence_detector_set_event_enable(u32 hC, u32 event_enable); + +u16 hpi_silence_detector_get_event_enable(u32 hC, u32 *event_enable); + +u16 hpi_silence_detector_set_delay(u32 hC, u32 delay); + +u16 hpi_silence_detector_get_delay(u32 hC, u32 *delay); + +u16 hpi_silence_detector_set_threshold(u32 hC, int threshold); + +u16 hpi_silence_detector_get_threshold(u32 hC, int *threshold); +/*********************/ +/* Utility functions */ +/*********************/ + +u16 hpi_format_create(struct hpi_format *p_format, u16 channels, u16 format, + u32 sample_rate, u32 bit_rate, u32 attributes); + +#endif /*_HPI_H_ */ diff --git a/sound/pci/asihpi/hpi6000.c b/sound/pci/asihpi/hpi6000.c new file mode 100644 index 000000000..fd7fe9dba --- /dev/null +++ b/sound/pci/asihpi/hpi6000.c @@ -0,0 +1,1804 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + + Hardware Programming Interface (HPI) for AudioScience ASI6200 series adapters. + These PCI bus adapters are based on the TI C6711 DSP. + + Exported functions: + void HPI_6000(struct hpi_message *phm, struct hpi_response *phr) + + #defines + HIDE_PCI_ASSERTS to show the PCI asserts + PROFILE_DSP2 get profile data from DSP2 if present (instead of DSP 1) + +(C) Copyright AudioScience Inc. 1998-2003 +*******************************************************************************/ +#define SOURCEFILE_NAME "hpi6000.c" + +#include "hpi_internal.h" +#include "hpimsginit.h" +#include "hpidebug.h" +#include "hpi6000.h" +#include "hpidspcd.h" +#include "hpicmn.h" + +#define HPI_HIF_BASE (0x00000200) /* start of C67xx internal RAM */ +#define HPI_HIF_ADDR(member) \ + (HPI_HIF_BASE + offsetof(struct hpi_hif_6000, member)) +#define HPI_HIF_ERROR_MASK 0x4000 + +/* HPI6000 specific error codes */ +#define HPI6000_ERROR_BASE 900 /* not actually used anywhere */ + +/* operational/messaging errors */ +#define HPI6000_ERROR_MSG_RESP_IDLE_TIMEOUT 901 +#define HPI6000_ERROR_RESP_GET_LEN 902 +#define HPI6000_ERROR_MSG_RESP_GET_RESP_ACK 903 +#define HPI6000_ERROR_MSG_GET_ADR 904 +#define HPI6000_ERROR_RESP_GET_ADR 905 +#define HPI6000_ERROR_MSG_RESP_BLOCKWRITE32 906 +#define HPI6000_ERROR_MSG_RESP_BLOCKREAD32 907 + +#define HPI6000_ERROR_CONTROL_CACHE_PARAMS 909 + +#define HPI6000_ERROR_SEND_DATA_IDLE_TIMEOUT 911 +#define HPI6000_ERROR_SEND_DATA_ACK 912 +#define HPI6000_ERROR_SEND_DATA_ADR 913 +#define HPI6000_ERROR_SEND_DATA_TIMEOUT 914 +#define HPI6000_ERROR_SEND_DATA_CMD 915 +#define HPI6000_ERROR_SEND_DATA_WRITE 916 +#define HPI6000_ERROR_SEND_DATA_IDLECMD 917 + +#define HPI6000_ERROR_GET_DATA_IDLE_TIMEOUT 921 +#define HPI6000_ERROR_GET_DATA_ACK 922 +#define HPI6000_ERROR_GET_DATA_CMD 923 +#define HPI6000_ERROR_GET_DATA_READ 924 +#define HPI6000_ERROR_GET_DATA_IDLECMD 925 + +#define HPI6000_ERROR_CONTROL_CACHE_ADDRLEN 951 +#define HPI6000_ERROR_CONTROL_CACHE_READ 952 +#define HPI6000_ERROR_CONTROL_CACHE_FLUSH 953 + +#define HPI6000_ERROR_MSG_RESP_GETRESPCMD 961 +#define HPI6000_ERROR_MSG_RESP_IDLECMD 962 + +/* Initialisation/bootload errors */ +#define HPI6000_ERROR_UNHANDLED_SUBSYS_ID 930 + +/* can't access PCI2040 */ +#define HPI6000_ERROR_INIT_PCI2040 931 +/* can't access DSP HPI i/f */ +#define HPI6000_ERROR_INIT_DSPHPI 932 +/* can't access internal DSP memory */ +#define HPI6000_ERROR_INIT_DSPINTMEM 933 +/* can't access SDRAM - test#1 */ +#define HPI6000_ERROR_INIT_SDRAM1 934 +/* can't access SDRAM - test#2 */ +#define HPI6000_ERROR_INIT_SDRAM2 935 + +#define HPI6000_ERROR_INIT_VERIFY 938 + +#define HPI6000_ERROR_INIT_NOACK 939 + +#define HPI6000_ERROR_INIT_PLDTEST1 941 +#define HPI6000_ERROR_INIT_PLDTEST2 942 + +/* local defines */ + +#define HIDE_PCI_ASSERTS +#define PROFILE_DSP2 + +/* for PCI2040 i/f chip */ +/* HPI CSR registers */ +/* word offsets from CSR base */ +/* use when io addresses defined as u32 * */ + +#define INTERRUPT_EVENT_SET 0 +#define INTERRUPT_EVENT_CLEAR 1 +#define INTERRUPT_MASK_SET 2 +#define INTERRUPT_MASK_CLEAR 3 +#define HPI_ERROR_REPORT 4 +#define HPI_RESET 5 +#define HPI_DATA_WIDTH 6 + +#define MAX_DSPS 2 +/* HPI registers, spaced 8K bytes = 2K words apart */ +#define DSP_SPACING 0x800 + +#define CONTROL 0x0000 +#define ADDRESS 0x0200 +#define DATA_AUTOINC 0x0400 +#define DATA 0x0600 + +#define TIMEOUT 500000 + +struct dsp_obj { + __iomem u32 *prHPI_control; + __iomem u32 *prHPI_address; + __iomem u32 *prHPI_data; + __iomem u32 *prHPI_data_auto_inc; + char c_dsp_rev; /*A, B */ + u32 control_cache_address_on_dsp; + u32 control_cache_length_on_dsp; + struct hpi_adapter_obj *pa_parent_adapter; +}; + +struct hpi_hw_obj { + __iomem u32 *dw2040_HPICSR; + __iomem u32 *dw2040_HPIDSP; + + u16 num_dsp; + struct dsp_obj ado[MAX_DSPS]; + + u32 message_buffer_address_on_dsp; + u32 response_buffer_address_on_dsp; + u32 pCI2040HPI_error_count; + + struct hpi_control_cache_single control_cache[HPI_NMIXER_CONTROLS]; + struct hpi_control_cache *p_cache; +}; + +static u16 hpi6000_dsp_block_write32(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 hpi_address, u32 *source, u32 count); +static u16 hpi6000_dsp_block_read32(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 hpi_address, u32 *dest, u32 count); + +static short hpi6000_adapter_boot_load_dsp(struct hpi_adapter_obj *pao, + u32 *pos_error_code); +static short hpi6000_check_PCI2040_error_flag(struct hpi_adapter_obj *pao, + u16 read_or_write); +#define H6READ 1 +#define H6WRITE 0 + +static short hpi6000_update_control_cache(struct hpi_adapter_obj *pao, + struct hpi_message *phm); +static short hpi6000_message_response_sequence(struct hpi_adapter_obj *pao, + u16 dsp_index, struct hpi_message *phm, struct hpi_response *phr); + +static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr); + +static short hpi6000_wait_dsp_ack(struct hpi_adapter_obj *pao, u16 dsp_index, + u32 ack_value); + +static short hpi6000_send_host_command(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 host_cmd); + +static void hpi6000_send_dsp_interrupt(struct dsp_obj *pdo); + +static short hpi6000_send_data(struct hpi_adapter_obj *pao, u16 dsp_index, + struct hpi_message *phm, struct hpi_response *phr); + +static short hpi6000_get_data(struct hpi_adapter_obj *pao, u16 dsp_index, + struct hpi_message *phm, struct hpi_response *phr); + +static void hpi_write_word(struct dsp_obj *pdo, u32 address, u32 data); + +static u32 hpi_read_word(struct dsp_obj *pdo, u32 address); + +static void hpi_write_block(struct dsp_obj *pdo, u32 address, u32 *pdata, + u32 length); + +static void hpi_read_block(struct dsp_obj *pdo, u32 address, u32 *pdata, + u32 length); + +static void subsys_create_adapter(struct hpi_message *phm, + struct hpi_response *phr); + +static void adapter_delete(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void adapter_get_asserts(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static short create_adapter_obj(struct hpi_adapter_obj *pao, + u32 *pos_error_code); + +static void delete_adapter_obj(struct hpi_adapter_obj *pao); + +/* local globals */ + +static u16 gw_pci_read_asserts; /* used to count PCI2040 errors */ +static u16 gw_pci_write_asserts; /* used to count PCI2040 errors */ + +static void subsys_message(struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + case HPI_SUBSYS_CREATE_ADAPTER: + subsys_create_adapter(phm, phr); + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +static void control_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + + switch (phm->function) { + case HPI_CONTROL_GET_STATE: + if (pao->has_control_cache) { + u16 err; + err = hpi6000_update_control_cache(pao, phm); + + if (err) { + if (err >= HPI_ERROR_BACKEND_BASE) { + phr->error = + HPI_ERROR_CONTROL_CACHING; + phr->specific_error = err; + } else { + phr->error = err; + } + break; + } + + if (hpi_check_control_cache(phw->p_cache, phm, phr)) + break; + } + hw_message(pao, phm, phr); + break; + case HPI_CONTROL_SET_STATE: + hw_message(pao, phm, phr); + hpi_cmn_control_cache_sync_to_msg(phw->p_cache, phm, phr); + break; + + case HPI_CONTROL_GET_INFO: + default: + hw_message(pao, phm, phr); + break; + } +} + +static void adapter_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + case HPI_ADAPTER_GET_ASSERT: + adapter_get_asserts(pao, phm, phr); + break; + + case HPI_ADAPTER_DELETE: + adapter_delete(pao, phm, phr); + break; + + default: + hw_message(pao, phm, phr); + break; + } +} + +static void outstream_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + case HPI_OSTREAM_HOSTBUFFER_ALLOC: + case HPI_OSTREAM_HOSTBUFFER_FREE: + /* Don't let these messages go to the HW function because + * they're called without locking the spinlock. + * For the HPI6000 adapters the HW would return + * HPI_ERROR_INVALID_FUNC anyway. + */ + phr->error = HPI_ERROR_INVALID_FUNC; + break; + default: + hw_message(pao, phm, phr); + return; + } +} + +static void instream_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + + switch (phm->function) { + case HPI_ISTREAM_HOSTBUFFER_ALLOC: + case HPI_ISTREAM_HOSTBUFFER_FREE: + /* Don't let these messages go to the HW function because + * they're called without locking the spinlock. + * For the HPI6000 adapters the HW would return + * HPI_ERROR_INVALID_FUNC anyway. + */ + phr->error = HPI_ERROR_INVALID_FUNC; + break; + default: + hw_message(pao, phm, phr); + return; + } +} + +/************************************************************************/ +/** HPI_6000() + * Entry point from HPIMAN + * All calls to the HPI start here + */ +void HPI_6000(struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_adapter_obj *pao = NULL; + + if (phm->object != HPI_OBJ_SUBSYSTEM) { + pao = hpi_find_adapter(phm->adapter_index); + if (!pao) { + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_BAD_ADAPTER_NUMBER); + HPI_DEBUG_LOG(DEBUG, "invalid adapter index: %d \n", + phm->adapter_index); + return; + } + + /* Don't even try to communicate with crashed DSP */ + if (pao->dsp_crashed >= 10) { + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_DSP_HARDWARE); + HPI_DEBUG_LOG(DEBUG, "adapter %d dsp crashed\n", + phm->adapter_index); + return; + } + } + /* Init default response including the size field */ + if (phm->function != HPI_SUBSYS_CREATE_ADAPTER) + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_PROCESSING_MESSAGE); + + switch (phm->type) { + case HPI_TYPE_REQUEST: + switch (phm->object) { + case HPI_OBJ_SUBSYSTEM: + subsys_message(phm, phr); + break; + + case HPI_OBJ_ADAPTER: + phr->size = + sizeof(struct hpi_response_header) + + sizeof(struct hpi_adapter_res); + adapter_message(pao, phm, phr); + break; + + case HPI_OBJ_CONTROL: + control_message(pao, phm, phr); + break; + + case HPI_OBJ_OSTREAM: + outstream_message(pao, phm, phr); + break; + + case HPI_OBJ_ISTREAM: + instream_message(pao, phm, phr); + break; + + default: + hw_message(pao, phm, phr); + break; + } + break; + + default: + phr->error = HPI_ERROR_INVALID_TYPE; + break; + } +} + +/************************************************************************/ +/* SUBSYSTEM */ + +/* create an adapter object and initialise it based on resource information + * passed in the message + * NOTE - you cannot use this function AND the FindAdapters function at the + * same time, the application must use only one of them to get the adapters + */ +static void subsys_create_adapter(struct hpi_message *phm, + struct hpi_response *phr) +{ + /* create temp adapter obj, because we don't know what index yet */ + struct hpi_adapter_obj ao; + struct hpi_adapter_obj *pao; + u32 os_error_code; + u16 err = 0; + u32 dsp_index = 0; + + HPI_DEBUG_LOG(VERBOSE, "subsys_create_adapter\n"); + + memset(&ao, 0, sizeof(ao)); + + ao.priv = kzalloc_obj(struct hpi_hw_obj); + if (!ao.priv) { + HPI_DEBUG_LOG(ERROR, "can't get mem for adapter object\n"); + phr->error = HPI_ERROR_MEMORY_ALLOC; + return; + } + + /* create the adapter object based on the resource information */ + ao.pci = *phm->u.s.resource.r.pci; + + err = create_adapter_obj(&ao, &os_error_code); + if (err) { + delete_adapter_obj(&ao); + if (err >= HPI_ERROR_BACKEND_BASE) { + phr->error = HPI_ERROR_DSP_BOOTLOAD; + phr->specific_error = err; + } else { + phr->error = err; + } + + phr->u.s.data = os_error_code; + return; + } + /* need to update paParentAdapter */ + pao = hpi_find_adapter(ao.index); + if (!pao) { + /* We just added this adapter, why can't we find it!? */ + HPI_DEBUG_LOG(ERROR, "lost adapter after boot\n"); + phr->error = HPI_ERROR_BAD_ADAPTER; + return; + } + + for (dsp_index = 0; dsp_index < MAX_DSPS; dsp_index++) { + struct hpi_hw_obj *phw = pao->priv; + phw->ado[dsp_index].pa_parent_adapter = pao; + } + + phr->u.s.adapter_type = ao.type; + phr->u.s.adapter_index = ao.index; + phr->error = 0; +} + +static void adapter_delete(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + delete_adapter_obj(pao); + hpi_delete_adapter(pao); + phr->error = 0; +} + +/* this routine is called from SubSysFindAdapter and SubSysCreateAdapter */ +static short create_adapter_obj(struct hpi_adapter_obj *pao, + u32 *pos_error_code) +{ + short boot_error = 0; + u32 dsp_index = 0; + u32 control_cache_size = 0; + u32 control_cache_count = 0; + struct hpi_hw_obj *phw = pao->priv; + + /* The PCI2040 has the following address map */ + /* BAR0 - 4K = HPI control and status registers on PCI2040 (HPI CSR) */ + /* BAR1 - 32K = HPI registers on DSP */ + phw->dw2040_HPICSR = pao->pci.ap_mem_base[0]; + phw->dw2040_HPIDSP = pao->pci.ap_mem_base[1]; + HPI_DEBUG_LOG(VERBOSE, "csr %p, dsp %p\n", phw->dw2040_HPICSR, + phw->dw2040_HPIDSP); + + /* set addresses for the possible DSP HPI interfaces */ + for (dsp_index = 0; dsp_index < MAX_DSPS; dsp_index++) { + phw->ado[dsp_index].prHPI_control = + phw->dw2040_HPIDSP + (CONTROL + + DSP_SPACING * dsp_index); + + phw->ado[dsp_index].prHPI_address = + phw->dw2040_HPIDSP + (ADDRESS + + DSP_SPACING * dsp_index); + phw->ado[dsp_index].prHPI_data = + phw->dw2040_HPIDSP + (DATA + DSP_SPACING * dsp_index); + + phw->ado[dsp_index].prHPI_data_auto_inc = + phw->dw2040_HPIDSP + (DATA_AUTOINC + + DSP_SPACING * dsp_index); + + HPI_DEBUG_LOG(VERBOSE, "ctl %p, adr %p, dat %p, dat++ %p\n", + phw->ado[dsp_index].prHPI_control, + phw->ado[dsp_index].prHPI_address, + phw->ado[dsp_index].prHPI_data, + phw->ado[dsp_index].prHPI_data_auto_inc); + + phw->ado[dsp_index].pa_parent_adapter = pao; + } + + phw->pCI2040HPI_error_count = 0; + pao->has_control_cache = 0; + + /* Set the default number of DSPs on this card */ + /* This is (conditionally) adjusted after bootloading */ + /* of the first DSP in the bootload section. */ + phw->num_dsp = 1; + + boot_error = hpi6000_adapter_boot_load_dsp(pao, pos_error_code); + if (boot_error) + return boot_error; + + HPI_DEBUG_LOG(INFO, "bootload DSP OK\n"); + + phw->message_buffer_address_on_dsp = 0L; + phw->response_buffer_address_on_dsp = 0L; + + /* get info about the adapter by asking the adapter */ + /* send a HPI_ADAPTER_GET_INFO message */ + { + struct hpi_message hm; + struct hpi_response hr0; /* response from DSP 0 */ + struct hpi_response hr1; /* response from DSP 1 */ + u16 error = 0; + + HPI_DEBUG_LOG(VERBOSE, "send ADAPTER_GET_INFO\n"); + memset(&hm, 0, sizeof(hm)); + hm.type = HPI_TYPE_REQUEST; + hm.size = sizeof(struct hpi_message); + hm.object = HPI_OBJ_ADAPTER; + hm.function = HPI_ADAPTER_GET_INFO; + hm.adapter_index = 0; + memset(&hr0, 0, sizeof(hr0)); + memset(&hr1, 0, sizeof(hr1)); + hr0.size = sizeof(hr0); + hr1.size = sizeof(hr1); + + error = hpi6000_message_response_sequence(pao, 0, &hm, &hr0); + if (error) { + HPI_DEBUG_LOG(ERROR, "message transport error %d\n", + error); + return error; + } + if (hr0.error) { + HPI_DEBUG_LOG(DEBUG, "message error %d\n", hr0.error); + return hr0.error; + } + if (phw->num_dsp == 2) { + error = hpi6000_message_response_sequence(pao, 1, &hm, + &hr1); + if (error) + return error; + } + pao->type = hr0.u.ax.info.adapter_type; + pao->index = hr0.u.ax.info.adapter_index; + } + + memset(&phw->control_cache[0], 0, + sizeof(struct hpi_control_cache_single) * + HPI_NMIXER_CONTROLS); + /* Read the control cache length to figure out if it is turned on */ + control_cache_size = + hpi_read_word(&phw->ado[0], + HPI_HIF_ADDR(control_cache_size_in_bytes)); + if (control_cache_size) { + control_cache_count = + hpi_read_word(&phw->ado[0], + HPI_HIF_ADDR(control_cache_count)); + + phw->p_cache = + hpi_alloc_control_cache(control_cache_count, + control_cache_size, (unsigned char *) + &phw->control_cache[0] + ); + if (phw->p_cache) + pao->has_control_cache = 1; + } + + HPI_DEBUG_LOG(DEBUG, "get adapter info ASI%04X index %d\n", pao->type, + pao->index); + + if (phw->p_cache) + phw->p_cache->adap_idx = pao->index; + + return hpi_add_adapter(pao); +} + +static void delete_adapter_obj(struct hpi_adapter_obj *pao) +{ + struct hpi_hw_obj *phw = pao->priv; + + if (pao->has_control_cache) + hpi_free_control_cache(phw->p_cache); + + /* reset DSPs on adapter */ + iowrite32(0x0003000F, phw->dw2040_HPICSR + HPI_RESET); + + kfree(phw); +} + +/************************************************************************/ +/* ADAPTER */ + +static void adapter_get_asserts(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ +#ifndef HIDE_PCI_ASSERTS + /* if we have PCI2040 asserts then collect them */ + if ((gw_pci_read_asserts > 0) || (gw_pci_write_asserts > 0)) { + phr->u.ax.assert.p1 = + gw_pci_read_asserts * 100 + gw_pci_write_asserts; + phr->u.ax.assert.p2 = 0; + phr->u.ax.assert.count = 1; /* assert count */ + phr->u.ax.assert.dsp_index = -1; /* "dsp index" */ + strscpy(phr->u.ax.assert.sz_message, "PCI2040 error"); + phr->u.ax.assert.dsp_msg_addr = 0; + gw_pci_read_asserts = 0; + gw_pci_write_asserts = 0; + phr->error = 0; + } else +#endif + hw_message(pao, phm, phr); /*get DSP asserts */ + + return; +} + +/************************************************************************/ +/* LOW-LEVEL */ + +static short hpi6000_adapter_boot_load_dsp(struct hpi_adapter_obj *pao, + u32 *pos_error_code) +{ + struct hpi_hw_obj *phw = pao->priv; + short error; + u32 timeout; + u32 read = 0; + u32 i = 0; + u32 data = 0; + u32 j = 0; + u32 test_addr = 0x80000000; + u32 test_data = 0x00000001; + u32 dw2040_reset = 0; + u32 dsp_index = 0; + u32 endian = 0; + u32 adapter_info = 0; + u32 delay = 0; + + struct dsp_code dsp_code; + u16 boot_load_family = 0; + + /* NOTE don't use wAdapterType in this routine. It is not setup yet */ + + switch (pao->pci.pci_dev->subsystem_device) { + case 0x5100: + case 0x5110: /* ASI5100 revB or higher with C6711D */ + case 0x5200: /* ASI5200 PCIe version of ASI5100 */ + case 0x6100: + case 0x6200: + boot_load_family = HPI_ADAPTER_FAMILY_ASI(0x6200); + break; + default: + return HPI6000_ERROR_UNHANDLED_SUBSYS_ID; + } + + /* reset all DSPs, indicate two DSPs are present + * set RST3-=1 to disconnect HAD8 to set DSP in little endian mode + */ + endian = 0; + dw2040_reset = 0x0003000F; + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + + /* read back register to make sure PCI2040 chip is functioning + * note that bits 4..15 are read-only and so should always return zero, + * even though we wrote 1 to them + */ + hpios_delay_micro_seconds(1000); + delay = ioread32(phw->dw2040_HPICSR + HPI_RESET); + + if (delay != dw2040_reset) { + HPI_DEBUG_LOG(ERROR, "INIT_PCI2040 %x %x\n", dw2040_reset, + delay); + return HPI6000_ERROR_INIT_PCI2040; + } + + /* Indicate that DSP#0,1 is a C6X */ + iowrite32(0x00000003, phw->dw2040_HPICSR + HPI_DATA_WIDTH); + /* set Bit30 and 29 - which will prevent Target aborts from being + * issued upon HPI or GP error + */ + iowrite32(0x60000000, phw->dw2040_HPICSR + INTERRUPT_MASK_SET); + + /* isolate DSP HAD8 line from PCI2040 so that + * Little endian can be set by pullup + */ + dw2040_reset = dw2040_reset & (~(endian << 3)); + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + + phw->ado[0].c_dsp_rev = 'B'; /* revB */ + phw->ado[1].c_dsp_rev = 'B'; /* revB */ + + /*Take both DSPs out of reset, setting HAD8 to the correct Endian */ + dw2040_reset = dw2040_reset & (~0x00000001); /* start DSP 0 */ + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + dw2040_reset = dw2040_reset & (~0x00000002); /* start DSP 1 */ + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + + /* set HAD8 back to PCI2040, now that DSP set to little endian mode */ + dw2040_reset = dw2040_reset & (~0x00000008); + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + /*delay to allow DSP to get going */ + hpios_delay_micro_seconds(100); + + /* loop through all DSPs, downloading DSP code */ + for (dsp_index = 0; dsp_index < phw->num_dsp; dsp_index++) { + struct dsp_obj *pdo = &phw->ado[dsp_index]; + + /* configure DSP so that we download code into the SRAM */ + /* set control reg for little endian, HWOB=1 */ + iowrite32(0x00010001, pdo->prHPI_control); + + /* test access to the HPI address register (HPIA) */ + test_data = 0x00000001; + for (j = 0; j < 32; j++) { + iowrite32(test_data, pdo->prHPI_address); + data = ioread32(pdo->prHPI_address); + if (data != test_data) { + HPI_DEBUG_LOG(ERROR, "INIT_DSPHPI %x %x %x\n", + test_data, data, dsp_index); + return HPI6000_ERROR_INIT_DSPHPI; + } + test_data = test_data << 1; + } + +/* if C6713 the setup PLL to generate 225MHz from 25MHz. +* Since the PLLDIV1 read is sometimes wrong, even on a C6713, +* we're going to do this unconditionally +*/ +/* PLLDIV1 should have a value of 8000 after reset */ +/* + if (HpiReadWord(pdo,0x01B7C118) == 0x8000) +*/ + { + /* C6713 datasheet says we cannot program PLL from HPI, + * and indeed if we try to set the PLL multiply from the + * HPI, the PLL does not seem to lock, + * so we enable the PLL and use the default of x 7 + */ + /* bypass PLL */ + hpi_write_word(pdo, 0x01B7C100, 0x0000); + hpios_delay_micro_seconds(100); + + /* ** use default of PLL x7 ** */ + /* EMIF = 225/3=75MHz */ + hpi_write_word(pdo, 0x01B7C120, 0x8002); + hpios_delay_micro_seconds(100); + + /* peri = 225/2 */ + hpi_write_word(pdo, 0x01B7C11C, 0x8001); + hpios_delay_micro_seconds(100); + + /* cpu = 225/1 */ + hpi_write_word(pdo, 0x01B7C118, 0x8000); + + /* ~2ms delay */ + hpios_delay_micro_seconds(2000); + + /* PLL not bypassed */ + hpi_write_word(pdo, 0x01B7C100, 0x0001); + /* ~2ms delay */ + hpios_delay_micro_seconds(2000); + } + + /* test r/w to internal DSP memory + * C6711 has L2 cache mapped to 0x0 when reset + * + * revB - because of bug 3.0.1 last HPI read + * (before HPI address issued) must be non-autoinc + */ + /* test each bit in the 32bit word */ + for (i = 0; i < 100; i++) { + test_addr = 0x00000000; + test_data = 0x00000001; + for (j = 0; j < 32; j++) { + hpi_write_word(pdo, test_addr + i, test_data); + data = hpi_read_word(pdo, test_addr + i); + if (data != test_data) { + HPI_DEBUG_LOG(ERROR, + "DSP mem %x %x %x %x\n", + test_addr + i, test_data, + data, dsp_index); + + return HPI6000_ERROR_INIT_DSPINTMEM; + } + test_data = test_data << 1; + } + } + + /* memory map of ASI6200 + 00000000-0000FFFF 16Kx32 internal program + 01800000-019FFFFF Internal peripheral + 80000000-807FFFFF CE0 2Mx32 SDRAM running @ 100MHz + 90000000-9000FFFF CE1 Async peripherals: + + EMIF config + ------------ + Global EMIF control + 0 - + 1 - + 2 - + 3 CLK2EN = 1 CLKOUT2 enabled + 4 CLK1EN = 0 CLKOUT1 disabled + 5 EKEN = 1 <--!! C6713 specific, enables ECLKOUT + 6 - + 7 NOHOLD = 1 external HOLD disabled + 8 HOLDA = 0 HOLDA output is low + 9 HOLD = 0 HOLD input is low + 10 ARDY = 1 ARDY input is high + 11 BUSREQ = 0 BUSREQ output is low + 12,13 Reserved = 1 + */ + hpi_write_word(pdo, 0x01800000, 0x34A8); + + /* EMIF CE0 setup - 2Mx32 Sync DRAM + 31..28 Wr setup + 27..22 Wr strobe + 21..20 Wr hold + 19..16 Rd setup + 15..14 - + 13..8 Rd strobe + 7..4 MTYPE 0011 Sync DRAM 32bits + 3 Wr hold MSB + 2..0 Rd hold + */ + hpi_write_word(pdo, 0x01800008, 0x00000030); + + /* EMIF SDRAM Extension + 31-21 0 + 20 WR2RD = 0 + 19-18 WR2DEAC = 1 + 17 WR2WR = 0 + 16-15 R2WDQM = 2 + 14-12 RD2WR = 4 + 11-10 RD2DEAC = 1 + 9 RD2RD = 1 + 8-7 THZP = 10b + 6-5 TWR = 2-1 = 01b (tWR = 10ns) + 4 TRRD = 0b = 2 ECLK (tRRD = 14ns) + 3-1 TRAS = 5-1 = 100b (Tras=42ns = 5 ECLK) + 1 CAS latency = 3 ECLK + (for Micron 2M32-7 operating at 100Mhz) + */ + + /* need to use this else DSP code crashes */ + hpi_write_word(pdo, 0x01800020, 0x001BDF29); + + /* EMIF SDRAM control - set up for a 2Mx32 SDRAM (512x32x4 bank) + 31 - - + 30 SDBSZ 1 4 bank + 29..28 SDRSZ 00 11 row address pins + 27..26 SDCSZ 01 8 column address pins + 25 RFEN 1 refersh enabled + 24 INIT 1 init SDRAM + 23..20 TRCD 0001 + 19..16 TRP 0001 + 15..12 TRC 0110 + 11..0 - - + */ + /* need to use this else DSP code crashes */ + hpi_write_word(pdo, 0x01800018, 0x47117000); + + /* EMIF SDRAM Refresh Timing */ + hpi_write_word(pdo, 0x0180001C, 0x00000410); + + /*MIF CE1 setup - Async peripherals + @100MHz bus speed, each cycle is 10ns, + 31..28 Wr setup = 1 + 27..22 Wr strobe = 3 30ns + 21..20 Wr hold = 1 + 19..16 Rd setup =1 + 15..14 Ta = 2 + 13..8 Rd strobe = 3 30ns + 7..4 MTYPE 0010 Async 32bits + 3 Wr hold MSB =0 + 2..0 Rd hold = 1 + */ + { + u32 cE1 = + (1L << 28) | (3L << 22) | (1L << 20) | (1L << + 16) | (2L << 14) | (3L << 8) | (2L << 4) | 1L; + hpi_write_word(pdo, 0x01800004, cE1); + } + + /* delay a little to allow SDRAM and DSP to "get going" */ + hpios_delay_micro_seconds(1000); + + /* test access to SDRAM */ + { + test_addr = 0x80000000; + test_data = 0x00000001; + /* test each bit in the 32bit word */ + for (j = 0; j < 32; j++) { + hpi_write_word(pdo, test_addr, test_data); + data = hpi_read_word(pdo, test_addr); + if (data != test_data) { + HPI_DEBUG_LOG(ERROR, + "DSP dram %x %x %x %x\n", + test_addr, test_data, data, + dsp_index); + + return HPI6000_ERROR_INIT_SDRAM1; + } + test_data = test_data << 1; + } + /* test every Nth address in the DRAM */ +#define DRAM_SIZE_WORDS 0x200000 /*2_mx32 */ +#define DRAM_INC 1024 + test_addr = 0x80000000; + test_data = 0x0; + for (i = 0; i < DRAM_SIZE_WORDS; i = i + DRAM_INC) { + hpi_write_word(pdo, test_addr + i, test_data); + test_data++; + } + test_addr = 0x80000000; + test_data = 0x0; + for (i = 0; i < DRAM_SIZE_WORDS; i = i + DRAM_INC) { + data = hpi_read_word(pdo, test_addr + i); + if (data != test_data) { + HPI_DEBUG_LOG(ERROR, + "DSP dram %x %x %x %x\n", + test_addr + i, test_data, + data, dsp_index); + return HPI6000_ERROR_INIT_SDRAM2; + } + test_data++; + } + + } + + /* write the DSP code down into the DSPs memory */ + error = hpi_dsp_code_open(boot_load_family, pao->pci.pci_dev, + &dsp_code, pos_error_code); + + if (error) + return error; + + while (1) { + u32 length; + u32 address; + u32 type; + u32 *pcode; + + error = hpi_dsp_code_read_word(&dsp_code, &length); + if (error) + break; + if (length == 0xFFFFFFFF) + break; /* end of code */ + + error = hpi_dsp_code_read_word(&dsp_code, &address); + if (error) + break; + error = hpi_dsp_code_read_word(&dsp_code, &type); + if (error) + break; + error = hpi_dsp_code_read_block(length, &dsp_code, + &pcode); + if (error) + break; + error = hpi6000_dsp_block_write32(pao, (u16)dsp_index, + address, pcode, length); + if (error) + break; + } + + if (error) { + hpi_dsp_code_close(&dsp_code); + return error; + } + /* verify that code was written correctly */ + /* this time through, assume no errors in DSP code file/array */ + hpi_dsp_code_rewind(&dsp_code); + while (1) { + u32 length; + u32 address; + u32 type; + u32 *pcode; + + hpi_dsp_code_read_word(&dsp_code, &length); + if (length == 0xFFFFFFFF) + break; /* end of code */ + + hpi_dsp_code_read_word(&dsp_code, &address); + hpi_dsp_code_read_word(&dsp_code, &type); + hpi_dsp_code_read_block(length, &dsp_code, &pcode); + + for (i = 0; i < length; i++) { + data = hpi_read_word(pdo, address); + if (data != *pcode) { + error = HPI6000_ERROR_INIT_VERIFY; + HPI_DEBUG_LOG(ERROR, + "DSP verify %x %x %x %x\n", + address, *pcode, data, + dsp_index); + break; + } + pcode++; + address += 4; + } + if (error) + break; + } + hpi_dsp_code_close(&dsp_code); + if (error) + return error; + + /* zero out the hostmailbox */ + { + u32 address = HPI_HIF_ADDR(host_cmd); + for (i = 0; i < 4; i++) { + hpi_write_word(pdo, address, 0); + address += 4; + } + } + /* write the DSP number into the hostmailbox */ + /* structure before starting the DSP */ + hpi_write_word(pdo, HPI_HIF_ADDR(dsp_number), dsp_index); + + /* write the DSP adapter Info into the */ + /* hostmailbox before starting the DSP */ + if (dsp_index > 0) + hpi_write_word(pdo, HPI_HIF_ADDR(adapter_info), + adapter_info); + + /* step 3. Start code by sending interrupt */ + iowrite32(0x00030003, pdo->prHPI_control); + hpios_delay_micro_seconds(10000); + + /* wait for a non-zero value in hostcmd - + * indicating initialization is complete + * + * Init could take a while if DSP checks SDRAM memory + * Was 200000. Increased to 2000000 for ASI8801 so we + * don't get 938 errors. + */ + timeout = 2000000; + while (timeout) { + do { + read = hpi_read_word(pdo, + HPI_HIF_ADDR(host_cmd)); + } while (--timeout + && hpi6000_check_PCI2040_error_flag(pao, + H6READ)); + + if (read) + break; + /* The following is a workaround for bug #94: + * Bluescreen on install and subsequent boots on a + * DELL PowerEdge 600SC PC with 1.8GHz P4 and + * ServerWorks chipset. Without this delay the system + * locks up with a bluescreen (NOT GPF or pagefault). + */ + else + hpios_delay_micro_seconds(10000); + } + if (timeout == 0) + return HPI6000_ERROR_INIT_NOACK; + + /* read the DSP adapter Info from the */ + /* hostmailbox structure after starting the DSP */ + if (dsp_index == 0) { + /*u32 dwTestData=0; */ + u32 mask = 0; + + adapter_info = + hpi_read_word(pdo, + HPI_HIF_ADDR(adapter_info)); + if (HPI_ADAPTER_FAMILY_ASI + (HPI_HIF_ADAPTER_INFO_EXTRACT_ADAPTER + (adapter_info)) == + HPI_ADAPTER_FAMILY_ASI(0x6200)) + /* all 6200 cards have this many DSPs */ + phw->num_dsp = 2; + + /* test that the PLD is programmed */ + /* and we can read/write 24bits */ +#define PLD_BASE_ADDRESS 0x90000000L /*for ASI6100/6200/8800 */ + + switch (boot_load_family) { + case HPI_ADAPTER_FAMILY_ASI(0x6200): + /* ASI6100/6200 has 24bit path to FPGA */ + mask = 0xFFFFFF00L; + /* ASI5100 uses AX6 code, */ + /* but has no PLD r/w register to test */ + if (HPI_ADAPTER_FAMILY_ASI(pao->pci.pci_dev-> + subsystem_device) == + HPI_ADAPTER_FAMILY_ASI(0x5100)) + mask = 0x00000000L; + /* ASI5200 uses AX6 code, */ + /* but has no PLD r/w register to test */ + if (HPI_ADAPTER_FAMILY_ASI(pao->pci.pci_dev-> + subsystem_device) == + HPI_ADAPTER_FAMILY_ASI(0x5200)) + mask = 0x00000000L; + break; + case HPI_ADAPTER_FAMILY_ASI(0x8800): + /* ASI8800 has 16bit path to FPGA */ + mask = 0xFFFF0000L; + break; + } + test_data = 0xAAAAAA00L & mask; + /* write to 24 bit Debug register (D31-D8) */ + hpi_write_word(pdo, PLD_BASE_ADDRESS + 4L, test_data); + read = hpi_read_word(pdo, + PLD_BASE_ADDRESS + 4L) & mask; + if (read != test_data) { + HPI_DEBUG_LOG(ERROR, "PLD %x %x\n", test_data, + read); + return HPI6000_ERROR_INIT_PLDTEST1; + } + test_data = 0x55555500L & mask; + hpi_write_word(pdo, PLD_BASE_ADDRESS + 4L, test_data); + read = hpi_read_word(pdo, + PLD_BASE_ADDRESS + 4L) & mask; + if (read != test_data) { + HPI_DEBUG_LOG(ERROR, "PLD %x %x\n", test_data, + read); + return HPI6000_ERROR_INIT_PLDTEST2; + } + } + } /* for numDSP */ + return 0; +} + +#define PCI_TIMEOUT 100 + +static int hpi_set_address(struct dsp_obj *pdo, u32 address) +{ + u32 timeout = PCI_TIMEOUT; + + do { + iowrite32(address, pdo->prHPI_address); + } while (hpi6000_check_PCI2040_error_flag(pdo->pa_parent_adapter, + H6WRITE) + && --timeout); + + if (timeout) + return 0; + + return 1; +} + +/* write one word to the HPI port */ +static void hpi_write_word(struct dsp_obj *pdo, u32 address, u32 data) +{ + if (hpi_set_address(pdo, address)) + return; + iowrite32(data, pdo->prHPI_data); +} + +/* read one word from the HPI port */ +static u32 hpi_read_word(struct dsp_obj *pdo, u32 address) +{ + u32 data = 0; + + if (hpi_set_address(pdo, address)) + return 0; /*? No way to return error */ + + /* take care of errata in revB DSP (2.0.1) */ + data = ioread32(pdo->prHPI_data); + return data; +} + +/* write a block of 32bit words to the DSP HPI port using auto-inc mode */ +static void hpi_write_block(struct dsp_obj *pdo, u32 address, u32 *pdata, + u32 length) +{ + u16 length16 = length - 1; + + if (length == 0) + return; + + if (hpi_set_address(pdo, address)) + return; + + iowrite32_rep(pdo->prHPI_data_auto_inc, pdata, length16); + + /* take care of errata in revB DSP (2.0.1) */ + /* must end with non auto-inc */ + iowrite32(*(pdata + length - 1), pdo->prHPI_data); +} + +/** read a block of 32bit words from the DSP HPI port using auto-inc mode + */ +static void hpi_read_block(struct dsp_obj *pdo, u32 address, u32 *pdata, + u32 length) +{ + u16 length16 = length - 1; + + if (length == 0) + return; + + if (hpi_set_address(pdo, address)) + return; + + ioread32_rep(pdo->prHPI_data_auto_inc, pdata, length16); + + /* take care of errata in revB DSP (2.0.1) */ + /* must end with non auto-inc */ + *(pdata + length - 1) = ioread32(pdo->prHPI_data); +} + +static u16 hpi6000_dsp_block_write32(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 hpi_address, u32 *source, u32 count) +{ + struct hpi_hw_obj *phw = pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 time_out = PCI_TIMEOUT; + int c6711_burst_size = 128; + u32 local_hpi_address = hpi_address; + int local_count = count; + int xfer_size; + u32 *pdata = source; + + while (local_count) { + if (local_count > c6711_burst_size) + xfer_size = c6711_burst_size; + else + xfer_size = local_count; + + time_out = PCI_TIMEOUT; + do { + hpi_write_block(pdo, local_hpi_address, pdata, + xfer_size); + } while (hpi6000_check_PCI2040_error_flag(pao, H6WRITE) + && --time_out); + + if (!time_out) + break; + pdata += xfer_size; + local_hpi_address += sizeof(u32) * xfer_size; + local_count -= xfer_size; + } + + if (time_out) + return 0; + else + return 1; +} + +static u16 hpi6000_dsp_block_read32(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 hpi_address, u32 *dest, u32 count) +{ + struct hpi_hw_obj *phw = pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 time_out = PCI_TIMEOUT; + int c6711_burst_size = 16; + u32 local_hpi_address = hpi_address; + int local_count = count; + int xfer_size; + u32 *pdata = dest; + + while (local_count) { + if (local_count > c6711_burst_size) + xfer_size = c6711_burst_size; + else + xfer_size = local_count; + + time_out = PCI_TIMEOUT; + do { + hpi_read_block(pdo, local_hpi_address, pdata, + xfer_size); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) + && --time_out); + if (!time_out) + break; + + pdata += xfer_size; + local_hpi_address += sizeof(u32) * xfer_size; + local_count -= xfer_size; + } + + if (time_out) + return 0; + else + return 1; +} + +static short hpi6000_message_response_sequence(struct hpi_adapter_obj *pao, + u16 dsp_index, struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 timeout; + u16 ack; + u32 address; + u32 length; + u32 *p_data; + u16 error = 0; + + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_IDLE); + if (ack & HPI_HIF_ERROR_MASK) { + pao->dsp_crashed++; + return HPI6000_ERROR_MSG_RESP_IDLE_TIMEOUT; + } + pao->dsp_crashed = 0; + + /* get the message address and size */ + if (phw->message_buffer_address_on_dsp == 0) { + timeout = TIMEOUT; + do { + address = + hpi_read_word(pdo, + HPI_HIF_ADDR(message_buffer_address)); + phw->message_buffer_address_on_dsp = address; + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) + && --timeout); + if (!timeout) + return HPI6000_ERROR_MSG_GET_ADR; + } else + address = phw->message_buffer_address_on_dsp; + + length = phm->size; + + /* send the message */ + p_data = (u32 *)phm; + if (hpi6000_dsp_block_write32(pao, dsp_index, address, p_data, + (u16)length / 4)) + return HPI6000_ERROR_MSG_RESP_BLOCKWRITE32; + + if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_GET_RESP)) + return HPI6000_ERROR_MSG_RESP_GETRESPCMD; + hpi6000_send_dsp_interrupt(pdo); + + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_GET_RESP); + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_MSG_RESP_GET_RESP_ACK; + + /* get the response address */ + if (phw->response_buffer_address_on_dsp == 0) { + timeout = TIMEOUT; + do { + address = + hpi_read_word(pdo, + HPI_HIF_ADDR(response_buffer_address)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) + && --timeout); + phw->response_buffer_address_on_dsp = address; + + if (!timeout) + return HPI6000_ERROR_RESP_GET_ADR; + } else + address = phw->response_buffer_address_on_dsp; + + /* read the length of the response back from the DSP */ + timeout = TIMEOUT; + do { + length = hpi_read_word(pdo, HPI_HIF_ADDR(length)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) && --timeout); + if (!timeout) + return HPI6000_ERROR_RESP_GET_LEN; + + if (length > phr->size) + return HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL; + + /* get the response */ + p_data = (u32 *)phr; + if (hpi6000_dsp_block_read32(pao, dsp_index, address, p_data, + (u16)length / 4)) + return HPI6000_ERROR_MSG_RESP_BLOCKREAD32; + + /* set i/f back to idle */ + if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_IDLE)) + return HPI6000_ERROR_MSG_RESP_IDLECMD; + hpi6000_send_dsp_interrupt(pdo); + + error = hpi_validate_response(phm, phr); + return error; +} + +/* have to set up the below defines to match stuff in the MAP file */ + +#define MSG_ADDRESS (HPI_HIF_BASE+0x18) +#define MSG_LENGTH 11 +#define RESP_ADDRESS (HPI_HIF_BASE+0x44) +#define RESP_LENGTH 16 +#define QUEUE_START (HPI_HIF_BASE+0x88) +#define QUEUE_SIZE 0x8000 + +static short hpi6000_send_data_check_adr(u32 address, u32 length_in_dwords) +{ +/*#define CHECKING // comment this line in to enable checking */ +#ifdef CHECKING + if (address < (u32)MSG_ADDRESS) + return 0; + if (address > (u32)(QUEUE_START + QUEUE_SIZE)) + return 0; + if ((address + (length_in_dwords << 2)) > + (u32)(QUEUE_START + QUEUE_SIZE)) + return 0; +#else + (void)address; + (void)length_in_dwords; + return 1; +#endif +} + +static short hpi6000_send_data(struct hpi_adapter_obj *pao, u16 dsp_index, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 data_sent = 0; + u16 ack; + u32 length, address; + u32 *p_data = (u32 *)phm->u.d.u.data.pb_data; + u16 time_out = 8; + + (void)phr; + + /* round dwDataSize down to nearest 4 bytes */ + while ((data_sent < (phm->u.d.u.data.data_size & ~3L)) + && --time_out) { + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_IDLE); + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_SEND_DATA_IDLE_TIMEOUT; + + if (hpi6000_send_host_command(pao, dsp_index, + HPI_HIF_SEND_DATA)) + return HPI6000_ERROR_SEND_DATA_CMD; + + hpi6000_send_dsp_interrupt(pdo); + + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_SEND_DATA); + + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_SEND_DATA_ACK; + + do { + /* get the address and size */ + address = hpi_read_word(pdo, HPI_HIF_ADDR(address)); + /* DSP returns number of DWORDS */ + length = hpi_read_word(pdo, HPI_HIF_ADDR(length)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ)); + + if (!hpi6000_send_data_check_adr(address, length)) + return HPI6000_ERROR_SEND_DATA_ADR; + + /* send the data. break data into 512 DWORD blocks (2K bytes) + * and send using block write. 2Kbytes is the max as this is the + * memory window given to the HPI data register by the PCI2040 + */ + + { + u32 len = length; + u32 blk_len = 512; + while (len) { + if (len < blk_len) + blk_len = len; + if (hpi6000_dsp_block_write32(pao, dsp_index, + address, p_data, blk_len)) + return HPI6000_ERROR_SEND_DATA_WRITE; + address += blk_len * 4; + p_data += blk_len; + len -= blk_len; + } + } + + if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_IDLE)) + return HPI6000_ERROR_SEND_DATA_IDLECMD; + + hpi6000_send_dsp_interrupt(pdo); + + data_sent += length * 4; + } + if (!time_out) + return HPI6000_ERROR_SEND_DATA_TIMEOUT; + return 0; +} + +static short hpi6000_get_data(struct hpi_adapter_obj *pao, u16 dsp_index, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 data_got = 0; + u16 ack; + u32 length, address; + u32 *p_data = (u32 *)phm->u.d.u.data.pb_data; + + (void)phr; /* this parameter not used! */ + + /* round dwDataSize down to nearest 4 bytes */ + while (data_got < (phm->u.d.u.data.data_size & ~3L)) { + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_IDLE); + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_GET_DATA_IDLE_TIMEOUT; + + if (hpi6000_send_host_command(pao, dsp_index, + HPI_HIF_GET_DATA)) + return HPI6000_ERROR_GET_DATA_CMD; + hpi6000_send_dsp_interrupt(pdo); + + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_GET_DATA); + + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_GET_DATA_ACK; + + /* get the address and size */ + do { + address = hpi_read_word(pdo, HPI_HIF_ADDR(address)); + length = hpi_read_word(pdo, HPI_HIF_ADDR(length)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ)); + + /* read the data */ + { + u32 len = length; + u32 blk_len = 512; + while (len) { + if (len < blk_len) + blk_len = len; + if (hpi6000_dsp_block_read32(pao, dsp_index, + address, p_data, blk_len)) + return HPI6000_ERROR_GET_DATA_READ; + address += blk_len * 4; + p_data += blk_len; + len -= blk_len; + } + } + + if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_IDLE)) + return HPI6000_ERROR_GET_DATA_IDLECMD; + hpi6000_send_dsp_interrupt(pdo); + + data_got += length * 4; + } + return 0; +} + +static void hpi6000_send_dsp_interrupt(struct dsp_obj *pdo) +{ + iowrite32(0x00030003, pdo->prHPI_control); /* DSPINT */ +} + +static short hpi6000_send_host_command(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 host_cmd) +{ + struct hpi_hw_obj *phw = pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 timeout = TIMEOUT; + + /* set command */ + do { + hpi_write_word(pdo, HPI_HIF_ADDR(host_cmd), host_cmd); + /* flush the FIFO */ + hpi_set_address(pdo, HPI_HIF_ADDR(host_cmd)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6WRITE) && --timeout); + + /* reset the interrupt bit */ + iowrite32(0x00040004, pdo->prHPI_control); + + if (timeout) + return 0; + else + return 1; +} + +/* if the PCI2040 has recorded an HPI timeout, reset the error and return 1 */ +static short hpi6000_check_PCI2040_error_flag(struct hpi_adapter_obj *pao, + u16 read_or_write) +{ + u32 hPI_error; + + struct hpi_hw_obj *phw = pao->priv; + + /* read the error bits from the PCI2040 */ + hPI_error = ioread32(phw->dw2040_HPICSR + HPI_ERROR_REPORT); + if (hPI_error) { + /* reset the error flag */ + iowrite32(0L, phw->dw2040_HPICSR + HPI_ERROR_REPORT); + phw->pCI2040HPI_error_count++; + if (read_or_write == 1) + gw_pci_read_asserts++; /************* inc global */ + else + gw_pci_write_asserts++; + return 1; + } else + return 0; +} + +static short hpi6000_wait_dsp_ack(struct hpi_adapter_obj *pao, u16 dsp_index, + u32 ack_value) +{ + struct hpi_hw_obj *phw = pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 ack = 0L; + u32 timeout; + u32 hPIC = 0L; + + /* wait for host interrupt to signal ack is ready */ + timeout = TIMEOUT; + while (--timeout) { + hPIC = ioread32(pdo->prHPI_control); + if (hPIC & 0x04) /* 0x04 = HINT from DSP */ + break; + } + if (timeout == 0) + return HPI_HIF_ERROR_MASK; + + /* wait for dwAckValue */ + timeout = TIMEOUT; + while (--timeout) { + /* read the ack mailbox */ + ack = hpi_read_word(pdo, HPI_HIF_ADDR(dsp_ack)); + if (ack == ack_value) + break; + if ((ack & HPI_HIF_ERROR_MASK) + && !hpi6000_check_PCI2040_error_flag(pao, H6READ)) + break; + /*for (i=0;i<1000;i++) */ + /* dwPause=i+1; */ + } + if (ack & HPI_HIF_ERROR_MASK) + /* indicates bad read from DSP - + typically 0xffffff is read for some reason */ + ack = HPI_HIF_ERROR_MASK; + + if (timeout == 0) + ack = HPI_HIF_ERROR_MASK; + return (short)ack; +} + +static short hpi6000_update_control_cache(struct hpi_adapter_obj *pao, + struct hpi_message *phm) +{ + const u16 dsp_index = 0; + struct hpi_hw_obj *phw = pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 timeout; + u32 cache_dirty_flag; + u16 err; + + hpios_dsplock_lock(pao); + + timeout = TIMEOUT; + do { + cache_dirty_flag = + hpi_read_word((struct dsp_obj *)pdo, + HPI_HIF_ADDR(control_cache_is_dirty)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) && --timeout); + if (!timeout) { + err = HPI6000_ERROR_CONTROL_CACHE_PARAMS; + goto unlock; + } + + if (cache_dirty_flag) { + /* read the cached controls */ + u32 address; + u32 length; + + timeout = TIMEOUT; + if (pdo->control_cache_address_on_dsp == 0) { + do { + address = + hpi_read_word((struct dsp_obj *)pdo, + HPI_HIF_ADDR(control_cache_address)); + + length = hpi_read_word((struct dsp_obj *)pdo, + HPI_HIF_ADDR + (control_cache_size_in_bytes)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) + && --timeout); + if (!timeout) { + err = HPI6000_ERROR_CONTROL_CACHE_ADDRLEN; + goto unlock; + } + pdo->control_cache_address_on_dsp = address; + pdo->control_cache_length_on_dsp = length; + } else { + address = pdo->control_cache_address_on_dsp; + length = pdo->control_cache_length_on_dsp; + } + + if (hpi6000_dsp_block_read32(pao, dsp_index, address, + (u32 *)&phw->control_cache[0], + length / sizeof(u32))) { + err = HPI6000_ERROR_CONTROL_CACHE_READ; + goto unlock; + } + do { + hpi_write_word((struct dsp_obj *)pdo, + HPI_HIF_ADDR(control_cache_is_dirty), 0); + /* flush the FIFO */ + hpi_set_address(pdo, HPI_HIF_ADDR(host_cmd)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6WRITE) + && --timeout); + if (!timeout) { + err = HPI6000_ERROR_CONTROL_CACHE_FLUSH; + goto unlock; + } + + } + err = 0; + +unlock: + hpios_dsplock_unlock(pao); + return err; +} + +/** Get dsp index for multi DSP adapters only */ +static u16 get_dsp_index(struct hpi_adapter_obj *pao, struct hpi_message *phm) +{ + u16 ret = 0; + switch (phm->object) { + case HPI_OBJ_ISTREAM: + if (phm->obj_index < 2) + ret = 1; + break; + case HPI_OBJ_PROFILE: + ret = phm->obj_index; + break; + default: + break; + } + return ret; +} + +/** Complete transaction with DSP + +Send message, get response, send or get stream data if any. +*/ +static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr) +{ + u16 error = 0; + u16 dsp_index = 0; + struct hpi_hw_obj *phw = pao->priv; + u16 num_dsp = phw->num_dsp; + + if (num_dsp < 2) + dsp_index = 0; + else { + dsp_index = get_dsp_index(pao, phm); + + /* is this checked on the DSP anyway? */ + if ((phm->function == HPI_ISTREAM_GROUP_ADD) + || (phm->function == HPI_OSTREAM_GROUP_ADD)) { + struct hpi_message hm; + u16 add_index; + hm.obj_index = phm->u.d.u.stream.stream_index; + hm.object = phm->u.d.u.stream.object_type; + add_index = get_dsp_index(pao, &hm); + if (add_index != dsp_index) { + phr->error = HPI_ERROR_NO_INTERDSP_GROUPS; + return; + } + } + } + + hpios_dsplock_lock(pao); + error = hpi6000_message_response_sequence(pao, dsp_index, phm, phr); + + if (error) /* something failed in the HPI/DSP interface */ + goto err; + + if (phr->error) /* something failed in the DSP */ + goto out; + + switch (phm->function) { + case HPI_OSTREAM_WRITE: + case HPI_ISTREAM_ANC_WRITE: + error = hpi6000_send_data(pao, dsp_index, phm, phr); + break; + case HPI_ISTREAM_READ: + case HPI_OSTREAM_ANC_READ: + error = hpi6000_get_data(pao, dsp_index, phm, phr); + break; + case HPI_ADAPTER_GET_ASSERT: + phr->u.ax.assert.dsp_index = 0; /* dsp 0 default */ + if (num_dsp == 2) { + if (!phr->u.ax.assert.count) { + /* no assert from dsp 0, check dsp 1 */ + error = hpi6000_message_response_sequence(pao, + 1, phm, phr); + phr->u.ax.assert.dsp_index = 1; + } + } + } + +err: + if (error) { + if (error >= HPI_ERROR_BACKEND_BASE) { + phr->error = HPI_ERROR_DSP_COMMUNICATION; + phr->specific_error = error; + } else { + phr->error = error; + } + + /* just the header of the response is valid */ + phr->size = sizeof(struct hpi_response_header); + } +out: + hpios_dsplock_unlock(pao); + return; +} diff --git a/sound/pci/asihpi/hpi6000.h b/sound/pci/asihpi/hpi6000.h new file mode 100644 index 000000000..4a5256a6e --- /dev/null +++ b/sound/pci/asihpi/hpi6000.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/***************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + +Public declarations for DSP Proramming Interface to TI C6701 + +Shared between hpi6000.c and DSP code + +(C) Copyright AudioScience Inc. 1998-2003 +******************************************************************************/ + +#ifndef _HPI6000_H_ +#define _HPI6000_H_ + +#define HPI_NMIXER_CONTROLS 200 + +/* + * Control caching is always supported in the HPI code. + * The DSP should make sure that dwControlCacheSizeInBytes is initialized to 0 + * during boot to make it in-active. + */ +struct hpi_hif_6000 { + u32 host_cmd; + u32 dsp_ack; + u32 address; + u32 length; + u32 message_buffer_address; + u32 response_buffer_address; + u32 dsp_number; + u32 adapter_info; + u32 control_cache_is_dirty; + u32 control_cache_address; + u32 control_cache_size_in_bytes; + u32 control_cache_count; +}; + +#define HPI_HIF_PACK_ADAPTER_INFO(adapter, version_major, version_minor) \ + ((adapter << 16) | (version_major << 8) | version_minor) +#define HPI_HIF_ADAPTER_INFO_EXTRACT_ADAPTER(adapterinfo) \ + ((adapterinfo >> 16) & 0xffff) +#define HPI_HIF_ADAPTER_INFO_EXTRACT_HWVERSION_MAJOR(adapterinfo) \ + ((adapterinfo >> 8) & 0xff) +#define HPI_HIF_ADAPTER_INFO_EXTRACT_HWVERSION_MINOR(adapterinfo) \ + (adapterinfo & 0xff) + +/* Command/status exchanged between host and DSP */ +#define HPI_HIF_IDLE 0 +#define HPI_HIF_SEND_MSG 1 +#define HPI_HIF_GET_RESP 2 +#define HPI_HIF_DATA_MASK 0x10 +#define HPI_HIF_SEND_DATA 0x13 +#define HPI_HIF_GET_DATA 0x14 +#define HPI_HIF_SEND_DONE 5 +#define HPI_HIF_RESET 9 + +#endif /* _HPI6000_H_ */ diff --git a/sound/pci/asihpi/hpi6205.c b/sound/pci/asihpi/hpi6205.c new file mode 100644 index 000000000..98e7f7fa7 --- /dev/null +++ b/sound/pci/asihpi/hpi6205.c @@ -0,0 +1,2219 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + + Hardware Programming Interface (HPI) for AudioScience + ASI50xx, AS51xx, ASI6xxx, ASI87xx ASI89xx series adapters. + These PCI and PCIe bus adapters are based on a + TMS320C6205 PCI bus mastering DSP, + and (except ASI50xx) TI TMS320C6xxx floating point DSP + + Exported function: + void HPI_6205(struct hpi_message *phm, struct hpi_response *phr) + +(C) Copyright AudioScience Inc. 1998-2010 +*******************************************************************************/ +#define SOURCEFILE_NAME "hpi6205.c" + +#include "hpi_internal.h" +#include "hpimsginit.h" +#include "hpidebug.h" +#include "hpi6205.h" +#include "hpidspcd.h" +#include "hpicmn.h" + +/*****************************************************************************/ +/* HPI6205 specific error codes */ +#define HPI6205_ERROR_BASE 1000 /* not actually used anywhere */ + +/* operational/messaging errors */ +#define HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT 1015 +#define HPI6205_ERROR_MSG_RESP_TIMEOUT 1016 + +/* initialization/bootload errors */ +#define HPI6205_ERROR_6205_NO_IRQ 1002 +#define HPI6205_ERROR_6205_INIT_FAILED 1003 +#define HPI6205_ERROR_6205_REG 1006 +#define HPI6205_ERROR_6205_DSPPAGE 1007 +#define HPI6205_ERROR_C6713_HPIC 1009 +#define HPI6205_ERROR_C6713_HPIA 1010 +#define HPI6205_ERROR_C6713_PLL 1011 +#define HPI6205_ERROR_DSP_INTMEM 1012 +#define HPI6205_ERROR_DSP_EXTMEM 1013 +#define HPI6205_ERROR_DSP_PLD 1014 +#define HPI6205_ERROR_6205_EEPROM 1017 +#define HPI6205_ERROR_DSP_EMIF1 1018 +#define HPI6205_ERROR_DSP_EMIF2 1019 +#define HPI6205_ERROR_DSP_EMIF3 1020 +#define HPI6205_ERROR_DSP_EMIF4 1021 + +/*****************************************************************************/ +/* for C6205 PCI i/f */ +/* Host Status Register (HSR) bitfields */ +#define C6205_HSR_INTSRC 0x01 +#define C6205_HSR_INTAVAL 0x02 +#define C6205_HSR_INTAM 0x04 +#define C6205_HSR_CFGERR 0x08 +#define C6205_HSR_EEREAD 0x10 +/* Host-to-DSP Control Register (HDCR) bitfields */ +#define C6205_HDCR_WARMRESET 0x01 +#define C6205_HDCR_DSPINT 0x02 +#define C6205_HDCR_PCIBOOT 0x04 +/* DSP Page Register (DSPP) bitfields, */ +/* defines 4 Mbyte page that BAR0 points to */ +#define C6205_DSPP_MAP1 0x400 + +/* BAR0 maps to prefetchable 4 Mbyte memory block set by DSPP. + * BAR1 maps to non-prefetchable 8 Mbyte memory block + * of DSP memory mapped registers (starting at 0x01800000). + * 0x01800000 is hardcoded in the PCI i/f, so that only the offset from this + * needs to be added to the BAR1 base address set in the PCI config reg + */ +#define C6205_BAR1_PCI_IO_OFFSET (0x027FFF0L) +#define C6205_BAR1_HSR (C6205_BAR1_PCI_IO_OFFSET) +#define C6205_BAR1_HDCR (C6205_BAR1_PCI_IO_OFFSET+4) +#define C6205_BAR1_DSPP (C6205_BAR1_PCI_IO_OFFSET+8) + +/* used to control LED (revA) and reset C6713 (revB) */ +#define C6205_BAR0_TIMER1_CTL (0x01980000L) + +/* For first 6713 in CE1 space, using DA17,16,2 */ +#define HPICL_ADDR 0x01400000L +#define HPICH_ADDR 0x01400004L +#define HPIAL_ADDR 0x01410000L +#define HPIAH_ADDR 0x01410004L +#define HPIDIL_ADDR 0x01420000L +#define HPIDIH_ADDR 0x01420004L +#define HPIDL_ADDR 0x01430000L +#define HPIDH_ADDR 0x01430004L + +#define C6713_EMIF_GCTL 0x01800000 +#define C6713_EMIF_CE1 0x01800004 +#define C6713_EMIF_CE0 0x01800008 +#define C6713_EMIF_CE2 0x01800010 +#define C6713_EMIF_CE3 0x01800014 +#define C6713_EMIF_SDRAMCTL 0x01800018 +#define C6713_EMIF_SDRAMTIMING 0x0180001C +#define C6713_EMIF_SDRAMEXT 0x01800020 + +struct hpi_hw_obj { + /* PCI registers */ + __iomem u32 *prHSR; + __iomem u32 *prHDCR; + __iomem u32 *prDSPP; + + u32 dsp_page; + + struct consistent_dma_area h_locked_mem; + struct bus_master_interface *p_interface_buffer; + + u16 flag_outstream_just_reset[HPI_MAX_STREAMS]; + /* a non-NULL handle means there is an HPI allocated buffer */ + struct consistent_dma_area instream_host_buffers[HPI_MAX_STREAMS]; + struct consistent_dma_area outstream_host_buffers[HPI_MAX_STREAMS]; + /* non-zero size means a buffer exists, may be external */ + u32 instream_host_buffer_size[HPI_MAX_STREAMS]; + u32 outstream_host_buffer_size[HPI_MAX_STREAMS]; + + struct consistent_dma_area h_control_cache; + struct hpi_control_cache *p_cache; +}; + +/*****************************************************************************/ +/* local prototypes */ + +#define check_before_bbm_copy(status, p_bbm_data, l_first_write, l_second_write) + +static int wait_dsp_ack(struct hpi_hw_obj *phw, int state, int timeout_us); + +static void send_dsp_command(struct hpi_hw_obj *phw, int cmd); + +static u16 adapter_boot_load_dsp(struct hpi_adapter_obj *pao, + u32 *pos_error_code); + +static u16 message_response_sequence(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr); + +#define HPI6205_TIMEOUT 1000000 + +static void subsys_create_adapter(struct hpi_message *phm, + struct hpi_response *phr); +static void adapter_delete(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static u16 create_adapter_obj(struct hpi_adapter_obj *pao, + u32 *pos_error_code); + +static void delete_adapter_obj(struct hpi_adapter_obj *pao); + +static int adapter_irq_query_and_clear(struct hpi_adapter_obj *pao, + u32 message); + +static void outstream_host_buffer_allocate(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_host_buffer_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_host_buffer_free(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); +static void outstream_write(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_start(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_open(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_reset(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_host_buffer_allocate(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_host_buffer_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_host_buffer_free(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_read(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_start(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static u32 boot_loader_read_mem32(struct hpi_adapter_obj *pao, int dsp_index, + u32 address); + +static void boot_loader_write_mem32(struct hpi_adapter_obj *pao, + int dsp_index, u32 address, u32 data); + +static u16 boot_loader_config_emif(struct hpi_adapter_obj *pao, + int dsp_index); + +static u16 boot_loader_test_memory(struct hpi_adapter_obj *pao, int dsp_index, + u32 address, u32 length); + +static u16 boot_loader_test_internal_memory(struct hpi_adapter_obj *pao, + int dsp_index); + +static u16 boot_loader_test_external_memory(struct hpi_adapter_obj *pao, + int dsp_index); + +static u16 boot_loader_test_pld(struct hpi_adapter_obj *pao, int dsp_index); + +/*****************************************************************************/ + +static void subsys_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + case HPI_SUBSYS_CREATE_ADAPTER: + subsys_create_adapter(phm, phr); + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +static void control_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + + struct hpi_hw_obj *phw = pao->priv; + u16 pending_cache_error = 0; + + switch (phm->function) { + case HPI_CONTROL_GET_STATE: + if (pao->has_control_cache) { + rmb(); /* make sure we see updates DMAed from DSP */ + if (hpi_check_control_cache(phw->p_cache, phm, phr)) { + break; + } else if (phm->u.c.attribute == HPI_METER_PEAK) { + pending_cache_error = + HPI_ERROR_CONTROL_CACHING; + } + } + hw_message(pao, phm, phr); + if (pending_cache_error && !phr->error) + phr->error = pending_cache_error; + break; + case HPI_CONTROL_GET_INFO: + hw_message(pao, phm, phr); + break; + case HPI_CONTROL_SET_STATE: + hw_message(pao, phm, phr); + if (pao->has_control_cache) + hpi_cmn_control_cache_sync_to_msg(phw->p_cache, phm, + phr); + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +static void adapter_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + case HPI_ADAPTER_DELETE: + adapter_delete(pao, phm, phr); + break; + default: + hw_message(pao, phm, phr); + break; + } +} + +static void outstream_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + + if (phm->obj_index >= HPI_MAX_STREAMS) { + phr->error = HPI_ERROR_INVALID_OBJ_INDEX; + HPI_DEBUG_LOG(WARNING, + "Message referencing invalid stream %d " + "on adapter index %d\n", phm->obj_index, + phm->adapter_index); + return; + } + + switch (phm->function) { + case HPI_OSTREAM_WRITE: + outstream_write(pao, phm, phr); + break; + case HPI_OSTREAM_GET_INFO: + outstream_get_info(pao, phm, phr); + break; + case HPI_OSTREAM_HOSTBUFFER_ALLOC: + outstream_host_buffer_allocate(pao, phm, phr); + break; + case HPI_OSTREAM_HOSTBUFFER_GET_INFO: + outstream_host_buffer_get_info(pao, phm, phr); + break; + case HPI_OSTREAM_HOSTBUFFER_FREE: + outstream_host_buffer_free(pao, phm, phr); + break; + case HPI_OSTREAM_START: + outstream_start(pao, phm, phr); + break; + case HPI_OSTREAM_OPEN: + outstream_open(pao, phm, phr); + break; + case HPI_OSTREAM_RESET: + outstream_reset(pao, phm, phr); + break; + default: + hw_message(pao, phm, phr); + break; + } +} + +static void instream_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + + if (phm->obj_index >= HPI_MAX_STREAMS) { + phr->error = HPI_ERROR_INVALID_OBJ_INDEX; + HPI_DEBUG_LOG(WARNING, + "Message referencing invalid stream %d " + "on adapter index %d\n", phm->obj_index, + phm->adapter_index); + return; + } + + switch (phm->function) { + case HPI_ISTREAM_READ: + instream_read(pao, phm, phr); + break; + case HPI_ISTREAM_GET_INFO: + instream_get_info(pao, phm, phr); + break; + case HPI_ISTREAM_HOSTBUFFER_ALLOC: + instream_host_buffer_allocate(pao, phm, phr); + break; + case HPI_ISTREAM_HOSTBUFFER_GET_INFO: + instream_host_buffer_get_info(pao, phm, phr); + break; + case HPI_ISTREAM_HOSTBUFFER_FREE: + instream_host_buffer_free(pao, phm, phr); + break; + case HPI_ISTREAM_START: + instream_start(pao, phm, phr); + break; + default: + hw_message(pao, phm, phr); + break; + } +} + +/*****************************************************************************/ +/** Entry point to this HPI backend + * All calls to the HPI start here + */ +static +void _HPI_6205(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr) +{ + if (pao && (pao->dsp_crashed >= 10) + && (phm->function != HPI_ADAPTER_DEBUG_READ)) { + /* allow last resort debug read even after crash */ + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_DSP_HARDWARE); + HPI_DEBUG_LOG(WARNING, " %d,%d dsp crashed.\n", phm->object, + phm->function); + return; + } + + /* Init default response */ + if (phm->function != HPI_SUBSYS_CREATE_ADAPTER) + phr->error = HPI_ERROR_PROCESSING_MESSAGE; + + HPI_DEBUG_LOG(VERBOSE, "start of switch\n"); + switch (phm->type) { + case HPI_TYPE_REQUEST: + switch (phm->object) { + case HPI_OBJ_SUBSYSTEM: + subsys_message(pao, phm, phr); + break; + + case HPI_OBJ_ADAPTER: + adapter_message(pao, phm, phr); + break; + + case HPI_OBJ_CONTROL: + control_message(pao, phm, phr); + break; + + case HPI_OBJ_OSTREAM: + outstream_message(pao, phm, phr); + break; + + case HPI_OBJ_ISTREAM: + instream_message(pao, phm, phr); + break; + + default: + hw_message(pao, phm, phr); + break; + } + break; + + default: + phr->error = HPI_ERROR_INVALID_TYPE; + break; + } +} + +void HPI_6205(struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_adapter_obj *pao = NULL; + + if (phm->object != HPI_OBJ_SUBSYSTEM) { + /* normal messages must have valid adapter index */ + pao = hpi_find_adapter(phm->adapter_index); + } else { + /* subsys messages don't address an adapter */ + phr->error = HPI_ERROR_INVALID_OBJ_INDEX; + return; + } + + if (pao) + _HPI_6205(pao, phm, phr); + else + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_BAD_ADAPTER_NUMBER); +} + +/*****************************************************************************/ +/* SUBSYSTEM */ + +/** Create an adapter object and initialise it based on resource information + * passed in the message + * *** NOTE - you cannot use this function AND the FindAdapters function at the + * same time, the application must use only one of them to get the adapters *** + */ +static void subsys_create_adapter(struct hpi_message *phm, + struct hpi_response *phr) +{ + /* create temp adapter obj, because we don't know what index yet */ + struct hpi_adapter_obj ao; + u32 os_error_code; + u16 err; + + HPI_DEBUG_LOG(DEBUG, " subsys_create_adapter\n"); + + memset(&ao, 0, sizeof(ao)); + + ao.priv = kzalloc_obj(struct hpi_hw_obj); + if (!ao.priv) { + HPI_DEBUG_LOG(ERROR, "can't get mem for adapter object\n"); + phr->error = HPI_ERROR_MEMORY_ALLOC; + return; + } + + ao.pci = *phm->u.s.resource.r.pci; + err = create_adapter_obj(&ao, &os_error_code); + if (err) { + delete_adapter_obj(&ao); + if (err >= HPI_ERROR_BACKEND_BASE) { + phr->error = HPI_ERROR_DSP_BOOTLOAD; + phr->specific_error = err; + } else { + phr->error = err; + } + phr->u.s.data = os_error_code; + return; + } + + phr->u.s.adapter_type = ao.type; + phr->u.s.adapter_index = ao.index; + phr->error = 0; +} + +/** delete an adapter - required by WDM driver */ +static void adapter_delete(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw; + + if (!pao) { + phr->error = HPI_ERROR_INVALID_OBJ_INDEX; + return; + } + phw = pao->priv; + /* reset adapter h/w */ + /* Reset C6713 #1 */ + boot_loader_write_mem32(pao, 0, C6205_BAR0_TIMER1_CTL, 0); + /* reset C6205 */ + iowrite32(C6205_HDCR_WARMRESET, phw->prHDCR); + + delete_adapter_obj(pao); + hpi_delete_adapter(pao); + phr->error = 0; +} + +/** Create adapter object + allocate buffers, bootload DSPs, initialise control cache +*/ +static u16 create_adapter_obj(struct hpi_adapter_obj *pao, + u32 *pos_error_code) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface; + u32 phys_addr; + int i; + u16 err; + + /* init error reporting */ + pao->dsp_crashed = 0; + + for (i = 0; i < HPI_MAX_STREAMS; i++) + phw->flag_outstream_just_reset[i] = 1; + + /* The C6205 memory area 1 is 8Mbyte window into DSP registers */ + phw->prHSR = + pao->pci.ap_mem_base[1] + + C6205_BAR1_HSR / sizeof(*pao->pci.ap_mem_base[1]); + phw->prHDCR = + pao->pci.ap_mem_base[1] + + C6205_BAR1_HDCR / sizeof(*pao->pci.ap_mem_base[1]); + phw->prDSPP = + pao->pci.ap_mem_base[1] + + C6205_BAR1_DSPP / sizeof(*pao->pci.ap_mem_base[1]); + + pao->has_control_cache = 0; + + if (hpios_locked_mem_alloc(&phw->h_locked_mem, + sizeof(struct bus_master_interface), + pao->pci.pci_dev)) + phw->p_interface_buffer = NULL; + else if (hpios_locked_mem_get_virt_addr(&phw->h_locked_mem, + (void *)&phw->p_interface_buffer)) + phw->p_interface_buffer = NULL; + + HPI_DEBUG_LOG(DEBUG, "interface buffer address %p\n", + phw->p_interface_buffer); + + if (phw->p_interface_buffer) { + memset((void *)phw->p_interface_buffer, 0, + sizeof(struct bus_master_interface)); + phw->p_interface_buffer->dsp_ack = H620_HIF_UNKNOWN; + } + + err = adapter_boot_load_dsp(pao, pos_error_code); + if (err) { + HPI_DEBUG_LOG(ERROR, "DSP code load failed\n"); + /* no need to clean up as SubSysCreateAdapter */ + /* calls DeleteAdapter on error. */ + return err; + } + HPI_DEBUG_LOG(INFO, "load DSP code OK\n"); + + /* allow boot load even if mem alloc wont work */ + if (!phw->p_interface_buffer) + return HPI_ERROR_MEMORY_ALLOC; + + interface = phw->p_interface_buffer; + + /* make sure the DSP has started ok */ + if (!wait_dsp_ack(phw, H620_HIF_RESET, HPI6205_TIMEOUT * 10)) { + HPI_DEBUG_LOG(ERROR, "timed out waiting reset state \n"); + return HPI6205_ERROR_6205_INIT_FAILED; + } + /* Note that *pao, *phw are zeroed after allocation, + * so pointers and flags are NULL by default. + * Allocate bus mastering control cache buffer and tell the DSP about it + */ + if (interface->control_cache.number_of_controls) { + u8 *p_control_cache_virtual; + + err = hpios_locked_mem_alloc(&phw->h_control_cache, + interface->control_cache.size_in_bytes, + pao->pci.pci_dev); + if (!err) + err = hpios_locked_mem_get_virt_addr(&phw-> + h_control_cache, + (void *)&p_control_cache_virtual); + if (!err) { + memset(p_control_cache_virtual, 0, + interface->control_cache.size_in_bytes); + + phw->p_cache = + hpi_alloc_control_cache(interface-> + control_cache.number_of_controls, + interface->control_cache.size_in_bytes, + p_control_cache_virtual); + + if (!phw->p_cache) + err = HPI_ERROR_MEMORY_ALLOC; + } + if (!err) { + err = hpios_locked_mem_get_phys_addr(&phw-> + h_control_cache, &phys_addr); + interface->control_cache.physical_address32 = + phys_addr; + } + + if (!err) + pao->has_control_cache = 1; + else { + if (hpios_locked_mem_valid(&phw->h_control_cache)) + hpios_locked_mem_free(&phw->h_control_cache); + pao->has_control_cache = 0; + } + } + send_dsp_command(phw, H620_HIF_IDLE); + + { + struct hpi_message hm; + struct hpi_response hr; + + HPI_DEBUG_LOG(VERBOSE, "init ADAPTER_GET_INFO\n"); + memset(&hm, 0, sizeof(hm)); + /* wAdapterIndex == version == 0 */ + hm.type = HPI_TYPE_REQUEST; + hm.size = sizeof(hm); + hm.object = HPI_OBJ_ADAPTER; + hm.function = HPI_ADAPTER_GET_INFO; + + memset(&hr, 0, sizeof(hr)); + hr.size = sizeof(hr); + + err = message_response_sequence(pao, &hm, &hr); + if (err) { + HPI_DEBUG_LOG(ERROR, "message transport error %d\n", + err); + return err; + } + if (hr.error) + return hr.error; + + pao->type = hr.u.ax.info.adapter_type; + pao->index = hr.u.ax.info.adapter_index; + + HPI_DEBUG_LOG(VERBOSE, + "got adapter info type %x index %d serial %d\n", + hr.u.ax.info.adapter_type, hr.u.ax.info.adapter_index, + hr.u.ax.info.serial_number); + } + + if (phw->p_cache) + phw->p_cache->adap_idx = pao->index; + + HPI_DEBUG_LOG(INFO, "bootload DSP OK\n"); + + pao->irq_query_and_clear = adapter_irq_query_and_clear; + pao->instream_host_buffer_status = + phw->p_interface_buffer->instream_host_buffer_status; + pao->outstream_host_buffer_status = + phw->p_interface_buffer->outstream_host_buffer_status; + + return hpi_add_adapter(pao); +} + +/** Free memory areas allocated by adapter + * this routine is called from AdapterDelete, + * and SubSysCreateAdapter if duplicate index +*/ +static void delete_adapter_obj(struct hpi_adapter_obj *pao) +{ + struct hpi_hw_obj *phw = pao->priv; + int i; + + if (hpios_locked_mem_valid(&phw->h_control_cache)) { + hpios_locked_mem_free(&phw->h_control_cache); + hpi_free_control_cache(phw->p_cache); + } + + if (hpios_locked_mem_valid(&phw->h_locked_mem)) { + hpios_locked_mem_free(&phw->h_locked_mem); + phw->p_interface_buffer = NULL; + } + + for (i = 0; i < HPI_MAX_STREAMS; i++) + if (hpios_locked_mem_valid(&phw->instream_host_buffers[i])) { + hpios_locked_mem_free(&phw->instream_host_buffers[i]); + /*?phw->InStreamHostBuffers[i] = NULL; */ + phw->instream_host_buffer_size[i] = 0; + } + + for (i = 0; i < HPI_MAX_STREAMS; i++) + if (hpios_locked_mem_valid(&phw->outstream_host_buffers[i])) { + hpios_locked_mem_free(&phw->outstream_host_buffers + [i]); + phw->outstream_host_buffer_size[i] = 0; + } + kfree(phw); +} + +/*****************************************************************************/ +/* Adapter functions */ +static int adapter_irq_query_and_clear(struct hpi_adapter_obj *pao, + u32 message) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 hsr = 0; + + hsr = ioread32(phw->prHSR); + if (hsr & C6205_HSR_INTSRC) { + /* reset the interrupt from the DSP */ + iowrite32(C6205_HSR_INTSRC, phw->prHSR); + return HPI_IRQ_MIXER; + } + + return HPI_IRQ_NONE; +} + +/*****************************************************************************/ +/* OutStream Host buffer functions */ + +/** Allocate or attach buffer for busmastering +*/ +static void outstream_host_buffer_allocate(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + u16 err = 0; + u32 command = phm->u.d.u.buffer.command; + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + + hpi_init_response(phr, phm->object, phm->function, 0); + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_ALLOC) { + /* ALLOC phase, allocate a buffer with power of 2 size, + get its bus address for PCI bus mastering + */ + phm->u.d.u.buffer.buffer_size = + roundup_pow_of_two(phm->u.d.u.buffer.buffer_size); + /* return old size and allocated size, + so caller can detect change */ + phr->u.d.u.stream_info.data_available = + phw->outstream_host_buffer_size[phm->obj_index]; + phr->u.d.u.stream_info.buffer_size = + phm->u.d.u.buffer.buffer_size; + + if (phw->outstream_host_buffer_size[phm->obj_index] == + phm->u.d.u.buffer.buffer_size) { + /* Same size, no action required */ + return; + } + + if (hpios_locked_mem_valid(&phw->outstream_host_buffers[phm-> + obj_index])) + hpios_locked_mem_free(&phw->outstream_host_buffers + [phm->obj_index]); + + err = hpios_locked_mem_alloc(&phw->outstream_host_buffers + [phm->obj_index], phm->u.d.u.buffer.buffer_size, + pao->pci.pci_dev); + + if (err) { + phr->error = HPI_ERROR_INVALID_DATASIZE; + phw->outstream_host_buffer_size[phm->obj_index] = 0; + return; + } + + err = hpios_locked_mem_get_phys_addr + (&phw->outstream_host_buffers[phm->obj_index], + &phm->u.d.u.buffer.pci_address); + /* get the phys addr into msg for single call alloc caller + * needs to do this for split alloc (or use the same message) + * return the phy address for split alloc in the respose too + */ + phr->u.d.u.stream_info.auxiliary_data_available = + phm->u.d.u.buffer.pci_address; + + if (err) { + hpios_locked_mem_free(&phw->outstream_host_buffers + [phm->obj_index]); + phw->outstream_host_buffer_size[phm->obj_index] = 0; + phr->error = HPI_ERROR_MEMORY_ALLOC; + return; + } + } + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER) { + /* GRANT phase. Set up the BBM status, tell the DSP about + the buffer so it can start using BBM. + */ + struct hpi_hostbuffer_status *status; + + if (phm->u.d.u.buffer.buffer_size & (phm->u.d.u.buffer. + buffer_size - 1)) { + HPI_DEBUG_LOG(ERROR, + "Buffer size must be 2^N not %d\n", + phm->u.d.u.buffer.buffer_size); + phr->error = HPI_ERROR_INVALID_DATASIZE; + return; + } + phw->outstream_host_buffer_size[phm->obj_index] = + phm->u.d.u.buffer.buffer_size; + status = &interface->outstream_host_buffer_status[phm-> + obj_index]; + status->samples_processed = 0; + status->stream_state = HPI_STATE_STOPPED; + status->dsp_index = 0; + status->host_index = status->dsp_index; + status->size_in_bytes = phm->u.d.u.buffer.buffer_size; + status->auxiliary_data_available = 0; + + hw_message(pao, phm, phr); + + if (phr->error + && hpios_locked_mem_valid(&phw-> + outstream_host_buffers[phm->obj_index])) { + hpios_locked_mem_free(&phw->outstream_host_buffers + [phm->obj_index]); + phw->outstream_host_buffer_size[phm->obj_index] = 0; + } + } +} + +static void outstream_host_buffer_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + u8 *p_bbm_data; + + if (hpios_locked_mem_valid(&phw->outstream_host_buffers[phm-> + obj_index])) { + if (hpios_locked_mem_get_virt_addr(&phw-> + outstream_host_buffers[phm->obj_index], + (void *)&p_bbm_data)) { + phr->error = HPI_ERROR_INVALID_OPERATION; + return; + } + status = &interface->outstream_host_buffer_status[phm-> + obj_index]; + hpi_init_response(phr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_GET_INFO, 0); + phr->u.d.u.hostbuffer_info.p_buffer = p_bbm_data; + phr->u.d.u.hostbuffer_info.p_status = status; + } else { + hpi_init_response(phr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_GET_INFO, + HPI_ERROR_INVALID_OPERATION); + } +} + +static void outstream_host_buffer_free(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 command = phm->u.d.u.buffer.command; + + if (phw->outstream_host_buffer_size[phm->obj_index]) { + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER) { + phw->outstream_host_buffer_size[phm->obj_index] = 0; + hw_message(pao, phm, phr); + /* Tell adapter to stop using the host buffer. */ + } + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_FREE) + hpios_locked_mem_free(&phw->outstream_host_buffers + [phm->obj_index]); + } + /* Should HPI_ERROR_INVALID_OPERATION be returned + if no host buffer is allocated? */ + else + hpi_init_response(phr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_FREE, 0); + +} + +static u32 outstream_get_space_available(struct hpi_hostbuffer_status *status) +{ + return status->size_in_bytes - (status->host_index - + status->dsp_index); +} + +static void outstream_write(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + u32 space_available; + + if (!phw->outstream_host_buffer_size[phm->obj_index]) { + /* there is no BBM buffer, write via message */ + hw_message(pao, phm, phr); + return; + } + + hpi_init_response(phr, phm->object, phm->function, 0); + status = &interface->outstream_host_buffer_status[phm->obj_index]; + + space_available = outstream_get_space_available(status); + if (space_available < phm->u.d.u.data.data_size) { + phr->error = HPI_ERROR_INVALID_DATASIZE; + return; + } + + /* HostBuffers is used to indicate host buffer is internally allocated. + otherwise, assumed external, data written externally */ + if (phm->u.d.u.data.pb_data + && hpios_locked_mem_valid(&phw->outstream_host_buffers[phm-> + obj_index])) { + u8 *p_bbm_data; + u32 l_first_write; + u8 *p_app_data = (u8 *)phm->u.d.u.data.pb_data; + + if (hpios_locked_mem_get_virt_addr(&phw-> + outstream_host_buffers[phm->obj_index], + (void *)&p_bbm_data)) { + phr->error = HPI_ERROR_INVALID_OPERATION; + return; + } + + /* either all data, + or enough to fit from current to end of BBM buffer */ + l_first_write = + min(phm->u.d.u.data.data_size, + status->size_in_bytes - + (status->host_index & (status->size_in_bytes - 1))); + + memcpy(p_bbm_data + + (status->host_index & (status->size_in_bytes - 1)), + p_app_data, l_first_write); + /* remaining data if any */ + memcpy(p_bbm_data, p_app_data + l_first_write, + phm->u.d.u.data.data_size - l_first_write); + } + + /* + * This version relies on the DSP code triggering an OStream buffer + * update immediately following a SET_FORMAT call. The host has + * already written data into the BBM buffer, but the DSP won't know + * about it until dwHostIndex is adjusted. + */ + if (phw->flag_outstream_just_reset[phm->obj_index]) { + /* Format can only change after reset. Must tell DSP. */ + u16 function = phm->function; + phw->flag_outstream_just_reset[phm->obj_index] = 0; + phm->function = HPI_OSTREAM_SET_FORMAT; + hw_message(pao, phm, phr); /* send the format to the DSP */ + phm->function = function; + if (phr->error) + return; + } + + status->host_index += phm->u.d.u.data.data_size; +} + +static void outstream_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + + if (!phw->outstream_host_buffer_size[phm->obj_index]) { + hw_message(pao, phm, phr); + return; + } + + hpi_init_response(phr, phm->object, phm->function, 0); + + status = &interface->outstream_host_buffer_status[phm->obj_index]; + + phr->u.d.u.stream_info.state = (u16)status->stream_state; + phr->u.d.u.stream_info.samples_transferred = + status->samples_processed; + phr->u.d.u.stream_info.buffer_size = status->size_in_bytes; + phr->u.d.u.stream_info.data_available = + status->size_in_bytes - outstream_get_space_available(status); + phr->u.d.u.stream_info.auxiliary_data_available = + status->auxiliary_data_available; +} + +static void outstream_start(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + hw_message(pao, phm, phr); +} + +static void outstream_reset(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + phw->flag_outstream_just_reset[phm->obj_index] = 1; + hw_message(pao, phm, phr); +} + +static void outstream_open(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + outstream_reset(pao, phm, phr); +} + +/*****************************************************************************/ +/* InStream Host buffer functions */ + +static void instream_host_buffer_allocate(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + u16 err = 0; + u32 command = phm->u.d.u.buffer.command; + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + + hpi_init_response(phr, phm->object, phm->function, 0); + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_ALLOC) { + + phm->u.d.u.buffer.buffer_size = + roundup_pow_of_two(phm->u.d.u.buffer.buffer_size); + phr->u.d.u.stream_info.data_available = + phw->instream_host_buffer_size[phm->obj_index]; + phr->u.d.u.stream_info.buffer_size = + phm->u.d.u.buffer.buffer_size; + + if (phw->instream_host_buffer_size[phm->obj_index] == + phm->u.d.u.buffer.buffer_size) { + /* Same size, no action required */ + return; + } + + if (hpios_locked_mem_valid(&phw->instream_host_buffers[phm-> + obj_index])) + hpios_locked_mem_free(&phw->instream_host_buffers + [phm->obj_index]); + + err = hpios_locked_mem_alloc(&phw->instream_host_buffers[phm-> + obj_index], phm->u.d.u.buffer.buffer_size, + pao->pci.pci_dev); + + if (err) { + phr->error = HPI_ERROR_INVALID_DATASIZE; + phw->instream_host_buffer_size[phm->obj_index] = 0; + return; + } + + err = hpios_locked_mem_get_phys_addr + (&phw->instream_host_buffers[phm->obj_index], + &phm->u.d.u.buffer.pci_address); + /* get the phys addr into msg for single call alloc. Caller + needs to do this for split alloc so return the phy address */ + phr->u.d.u.stream_info.auxiliary_data_available = + phm->u.d.u.buffer.pci_address; + if (err) { + hpios_locked_mem_free(&phw->instream_host_buffers + [phm->obj_index]); + phw->instream_host_buffer_size[phm->obj_index] = 0; + phr->error = HPI_ERROR_MEMORY_ALLOC; + return; + } + } + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER) { + struct hpi_hostbuffer_status *status; + + if (phm->u.d.u.buffer.buffer_size & (phm->u.d.u.buffer. + buffer_size - 1)) { + HPI_DEBUG_LOG(ERROR, + "Buffer size must be 2^N not %d\n", + phm->u.d.u.buffer.buffer_size); + phr->error = HPI_ERROR_INVALID_DATASIZE; + return; + } + + phw->instream_host_buffer_size[phm->obj_index] = + phm->u.d.u.buffer.buffer_size; + status = &interface->instream_host_buffer_status[phm-> + obj_index]; + status->samples_processed = 0; + status->stream_state = HPI_STATE_STOPPED; + status->dsp_index = 0; + status->host_index = status->dsp_index; + status->size_in_bytes = phm->u.d.u.buffer.buffer_size; + status->auxiliary_data_available = 0; + + hw_message(pao, phm, phr); + + if (phr->error + && hpios_locked_mem_valid(&phw-> + instream_host_buffers[phm->obj_index])) { + hpios_locked_mem_free(&phw->instream_host_buffers + [phm->obj_index]); + phw->instream_host_buffer_size[phm->obj_index] = 0; + } + } +} + +static void instream_host_buffer_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + u8 *p_bbm_data; + + if (hpios_locked_mem_valid(&phw->instream_host_buffers[phm-> + obj_index])) { + if (hpios_locked_mem_get_virt_addr(&phw-> + instream_host_buffers[phm->obj_index], + (void *)&p_bbm_data)) { + phr->error = HPI_ERROR_INVALID_OPERATION; + return; + } + status = &interface->instream_host_buffer_status[phm-> + obj_index]; + hpi_init_response(phr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_GET_INFO, 0); + phr->u.d.u.hostbuffer_info.p_buffer = p_bbm_data; + phr->u.d.u.hostbuffer_info.p_status = status; + } else { + hpi_init_response(phr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_GET_INFO, + HPI_ERROR_INVALID_OPERATION); + } +} + +static void instream_host_buffer_free(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 command = phm->u.d.u.buffer.command; + + if (phw->instream_host_buffer_size[phm->obj_index]) { + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER) { + phw->instream_host_buffer_size[phm->obj_index] = 0; + hw_message(pao, phm, phr); + } + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_FREE) + hpios_locked_mem_free(&phw->instream_host_buffers + [phm->obj_index]); + + } else { + /* Should HPI_ERROR_INVALID_OPERATION be returned + if no host buffer is allocated? */ + hpi_init_response(phr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_FREE, 0); + + } + +} + +static void instream_start(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + hw_message(pao, phm, phr); +} + +static u32 instream_get_bytes_available(struct hpi_hostbuffer_status *status) +{ + return status->dsp_index - status->host_index; +} + +static void instream_read(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + u32 data_available; + u8 *p_bbm_data; + u32 l_first_read; + u8 *p_app_data = (u8 *)phm->u.d.u.data.pb_data; + + if (!phw->instream_host_buffer_size[phm->obj_index]) { + hw_message(pao, phm, phr); + return; + } + hpi_init_response(phr, phm->object, phm->function, 0); + + status = &interface->instream_host_buffer_status[phm->obj_index]; + data_available = instream_get_bytes_available(status); + if (data_available < phm->u.d.u.data.data_size) { + phr->error = HPI_ERROR_INVALID_DATASIZE; + return; + } + + if (hpios_locked_mem_valid(&phw->instream_host_buffers[phm-> + obj_index])) { + if (hpios_locked_mem_get_virt_addr(&phw-> + instream_host_buffers[phm->obj_index], + (void *)&p_bbm_data)) { + phr->error = HPI_ERROR_INVALID_OPERATION; + return; + } + + /* either all data, + or enough to fit from current to end of BBM buffer */ + l_first_read = + min(phm->u.d.u.data.data_size, + status->size_in_bytes - + (status->host_index & (status->size_in_bytes - 1))); + + memcpy(p_app_data, + p_bbm_data + + (status->host_index & (status->size_in_bytes - 1)), + l_first_read); + /* remaining data if any */ + memcpy(p_app_data + l_first_read, p_bbm_data, + phm->u.d.u.data.data_size - l_first_read); + } + status->host_index += phm->u.d.u.data.data_size; +} + +static void instream_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + if (!phw->instream_host_buffer_size[phm->obj_index]) { + hw_message(pao, phm, phr); + return; + } + + status = &interface->instream_host_buffer_status[phm->obj_index]; + + hpi_init_response(phr, phm->object, phm->function, 0); + + phr->u.d.u.stream_info.state = (u16)status->stream_state; + phr->u.d.u.stream_info.samples_transferred = + status->samples_processed; + phr->u.d.u.stream_info.buffer_size = status->size_in_bytes; + phr->u.d.u.stream_info.data_available = + instream_get_bytes_available(status); + phr->u.d.u.stream_info.auxiliary_data_available = + status->auxiliary_data_available; +} + +/*****************************************************************************/ +/* LOW-LEVEL */ +#define HPI6205_MAX_FILES_TO_LOAD 2 + +static u16 adapter_boot_load_dsp(struct hpi_adapter_obj *pao, + u32 *pos_error_code) +{ + struct hpi_hw_obj *phw = pao->priv; + struct dsp_code dsp_code; + u16 boot_code_id[HPI6205_MAX_FILES_TO_LOAD]; + u32 temp; + int dsp = 0, i = 0; + u16 err = 0; + + boot_code_id[0] = HPI_ADAPTER_ASI(0x6205); + + boot_code_id[1] = pao->pci.pci_dev->subsystem_device; + boot_code_id[1] = HPI_ADAPTER_FAMILY_ASI(boot_code_id[1]); + + /* fix up cases where bootcode id[1] != subsys id */ + switch (boot_code_id[1]) { + case HPI_ADAPTER_FAMILY_ASI(0x5000): + boot_code_id[0] = boot_code_id[1]; + boot_code_id[1] = 0; + break; + case HPI_ADAPTER_FAMILY_ASI(0x5300): + case HPI_ADAPTER_FAMILY_ASI(0x5400): + case HPI_ADAPTER_FAMILY_ASI(0x6300): + boot_code_id[1] = HPI_ADAPTER_FAMILY_ASI(0x6400); + break; + case HPI_ADAPTER_FAMILY_ASI(0x5500): + case HPI_ADAPTER_FAMILY_ASI(0x5600): + case HPI_ADAPTER_FAMILY_ASI(0x6500): + boot_code_id[1] = HPI_ADAPTER_FAMILY_ASI(0x6600); + break; + case HPI_ADAPTER_FAMILY_ASI(0x8800): + boot_code_id[1] = HPI_ADAPTER_FAMILY_ASI(0x8900); + break; + default: + break; + } + + /* reset DSP by writing a 1 to the WARMRESET bit */ + temp = C6205_HDCR_WARMRESET; + iowrite32(temp, phw->prHDCR); + hpios_delay_micro_seconds(1000); + + /* check that PCI i/f was configured by EEPROM */ + temp = ioread32(phw->prHSR); + if ((temp & (C6205_HSR_CFGERR | C6205_HSR_EEREAD)) != + C6205_HSR_EEREAD) + return HPI6205_ERROR_6205_EEPROM; + temp |= 0x04; + /* disable PINTA interrupt */ + iowrite32(temp, phw->prHSR); + + /* check control register reports PCI boot mode */ + temp = ioread32(phw->prHDCR); + if (!(temp & C6205_HDCR_PCIBOOT)) + return HPI6205_ERROR_6205_REG; + + /* try writing a few numbers to the DSP page register */ + /* and reading them back. */ + temp = 3; + iowrite32(temp, phw->prDSPP); + if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP)) + return HPI6205_ERROR_6205_DSPPAGE; + temp = 2; + iowrite32(temp, phw->prDSPP); + if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP)) + return HPI6205_ERROR_6205_DSPPAGE; + temp = 1; + iowrite32(temp, phw->prDSPP); + if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP)) + return HPI6205_ERROR_6205_DSPPAGE; + /* reset DSP page to the correct number */ + temp = 0; + iowrite32(temp, phw->prDSPP); + if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP)) + return HPI6205_ERROR_6205_DSPPAGE; + phw->dsp_page = 0; + + /* release 6713 from reset before 6205 is bootloaded. + This ensures that the EMIF is inactive, + and the 6713 HPI gets the correct bootmode etc + */ + if (boot_code_id[1] != 0) { + /* DSP 1 is a C6713 */ + /* CLKX0 <- '1' release the C6205 bootmode pulldowns */ + boot_loader_write_mem32(pao, 0, 0x018C0024, 0x00002202); + hpios_delay_micro_seconds(100); + /* Reset the 6713 #1 - revB */ + boot_loader_write_mem32(pao, 0, C6205_BAR0_TIMER1_CTL, 0); + /* value of bit 3 is unknown after DSP reset, other bits shoudl be 0 */ + if (0 != (boot_loader_read_mem32(pao, 0, + (C6205_BAR0_TIMER1_CTL)) & ~8)) + return HPI6205_ERROR_6205_REG; + hpios_delay_micro_seconds(100); + + /* Release C6713 from reset - revB */ + boot_loader_write_mem32(pao, 0, C6205_BAR0_TIMER1_CTL, 4); + if (4 != (boot_loader_read_mem32(pao, 0, + (C6205_BAR0_TIMER1_CTL)) & ~8)) + return HPI6205_ERROR_6205_REG; + hpios_delay_micro_seconds(100); + } + + for (dsp = 0; dsp < HPI6205_MAX_FILES_TO_LOAD; dsp++) { + /* is there a DSP to load? */ + if (boot_code_id[dsp] == 0) + continue; + + err = boot_loader_config_emif(pao, dsp); + if (err) + return err; + + err = boot_loader_test_internal_memory(pao, dsp); + if (err) + return err; + + err = boot_loader_test_external_memory(pao, dsp); + if (err) + return err; + + err = boot_loader_test_pld(pao, dsp); + if (err) + return err; + + /* write the DSP code down into the DSPs memory */ + err = hpi_dsp_code_open(boot_code_id[dsp], pao->pci.pci_dev, + &dsp_code, pos_error_code); + if (err) + return err; + + while (1) { + u32 length; + u32 address; + u32 type; + u32 *pcode; + + err = hpi_dsp_code_read_word(&dsp_code, &length); + if (err) + break; + if (length == 0xFFFFFFFF) + break; /* end of code */ + + err = hpi_dsp_code_read_word(&dsp_code, &address); + if (err) + break; + err = hpi_dsp_code_read_word(&dsp_code, &type); + if (err) + break; + err = hpi_dsp_code_read_block(length, &dsp_code, + &pcode); + if (err) + break; + for (i = 0; i < (int)length; i++) { + boot_loader_write_mem32(pao, dsp, address, + *pcode); + /* dummy read every 4 words */ + /* for 6205 advisory 1.4.4 */ + if (i % 4 == 0) + boot_loader_read_mem32(pao, dsp, + address); + pcode++; + address += 4; + } + + } + if (err) { + hpi_dsp_code_close(&dsp_code); + return err; + } + + /* verify code */ + hpi_dsp_code_rewind(&dsp_code); + while (1) { + u32 length = 0; + u32 address = 0; + u32 type = 0; + u32 *pcode = NULL; + u32 data = 0; + + hpi_dsp_code_read_word(&dsp_code, &length); + if (length == 0xFFFFFFFF) + break; /* end of code */ + + hpi_dsp_code_read_word(&dsp_code, &address); + hpi_dsp_code_read_word(&dsp_code, &type); + hpi_dsp_code_read_block(length, &dsp_code, &pcode); + + for (i = 0; i < (int)length; i++) { + data = boot_loader_read_mem32(pao, dsp, + address); + if (data != *pcode) { + err = 0; + break; + } + pcode++; + address += 4; + } + if (err) + break; + } + hpi_dsp_code_close(&dsp_code); + if (err) + return err; + } + + /* After bootloading all DSPs, start DSP0 running + * The DSP0 code will handle starting and synchronizing with its slaves + */ + if (phw->p_interface_buffer) { + /* we need to tell the card the physical PCI address */ + u32 physicalPC_iaddress; + struct bus_master_interface *interface = + phw->p_interface_buffer; + u32 host_mailbox_address_on_dsp; + u32 physicalPC_iaddress_verify = 0; + int time_out = 10; + /* set ack so we know when DSP is ready to go */ + /* (dwDspAck will be changed to HIF_RESET) */ + interface->dsp_ack = H620_HIF_UNKNOWN; + wmb(); /* ensure ack is written before dsp writes back */ + + err = hpios_locked_mem_get_phys_addr(&phw->h_locked_mem, + &physicalPC_iaddress); + + /* locate the host mailbox on the DSP. */ + host_mailbox_address_on_dsp = 0x80000000; + while ((physicalPC_iaddress != physicalPC_iaddress_verify) + && time_out--) { + boot_loader_write_mem32(pao, 0, + host_mailbox_address_on_dsp, + physicalPC_iaddress); + physicalPC_iaddress_verify = + boot_loader_read_mem32(pao, 0, + host_mailbox_address_on_dsp); + } + } + HPI_DEBUG_LOG(DEBUG, "starting DS_ps running\n"); + /* enable interrupts */ + temp = ioread32(phw->prHSR); + temp &= ~(u32)C6205_HSR_INTAM; + iowrite32(temp, phw->prHSR); + + /* start code running... */ + temp = ioread32(phw->prHDCR); + temp |= (u32)C6205_HDCR_DSPINT; + iowrite32(temp, phw->prHDCR); + + /* give the DSP 10ms to start up */ + hpios_delay_micro_seconds(10000); + return err; + +} + +/*****************************************************************************/ +/* Bootloader utility functions */ + +static u32 boot_loader_read_mem32(struct hpi_adapter_obj *pao, int dsp_index, + u32 address) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 data = 0; + __iomem u32 *p_data; + + if (dsp_index == 0) { + /* DSP 0 is always C6205 */ + if ((address >= 0x01800000) & (address < 0x02000000)) { + /* BAR1 register access */ + p_data = pao->pci.ap_mem_base[1] + + (address & 0x007fffff) / + sizeof(*pao->pci.ap_mem_base[1]); + /* HPI_DEBUG_LOG(WARNING, + "BAR1 access %08x\n", dwAddress); */ + } else { + u32 dw4M_page = address >> 22L; + if (dw4M_page != phw->dsp_page) { + phw->dsp_page = dw4M_page; + /* *INDENT OFF* */ + iowrite32(phw->dsp_page, phw->prDSPP); + /* *INDENT-ON* */ + } + address &= 0x3fffff; /* address within 4M page */ + /* BAR0 memory access */ + p_data = pao->pci.ap_mem_base[0] + + address / sizeof(u32); + } + data = ioread32(p_data); + } else if (dsp_index == 1) { + /* DSP 1 is a C6713 */ + u32 lsb; + boot_loader_write_mem32(pao, 0, HPIAL_ADDR, address); + boot_loader_write_mem32(pao, 0, HPIAH_ADDR, address >> 16); + lsb = boot_loader_read_mem32(pao, 0, HPIDL_ADDR); + data = boot_loader_read_mem32(pao, 0, HPIDH_ADDR); + data = (data << 16) | (lsb & 0xFFFF); + } + return data; +} + +static void boot_loader_write_mem32(struct hpi_adapter_obj *pao, + int dsp_index, u32 address, u32 data) +{ + struct hpi_hw_obj *phw = pao->priv; + __iomem u32 *p_data; + /* u32 dwVerifyData=0; */ + + if (dsp_index == 0) { + /* DSP 0 is always C6205 */ + if ((address >= 0x01800000) & (address < 0x02000000)) { + /* BAR1 - DSP register access using */ + /* Non-prefetchable PCI access */ + p_data = pao->pci.ap_mem_base[1] + + (address & 0x007fffff) / + sizeof(*pao->pci.ap_mem_base[1]); + } else { + /* BAR0 access - all of DSP memory using */ + /* pre-fetchable PCI access */ + u32 dw4M_page = address >> 22L; + if (dw4M_page != phw->dsp_page) { + phw->dsp_page = dw4M_page; + /* *INDENT-OFF* */ + iowrite32(phw->dsp_page, phw->prDSPP); + /* *INDENT-ON* */ + } + address &= 0x3fffff; /* address within 4M page */ + p_data = pao->pci.ap_mem_base[0] + + address / sizeof(u32); + } + iowrite32(data, p_data); + } else if (dsp_index == 1) { + /* DSP 1 is a C6713 */ + boot_loader_write_mem32(pao, 0, HPIAL_ADDR, address); + boot_loader_write_mem32(pao, 0, HPIAH_ADDR, address >> 16); + + /* dummy read every 4 words for 6205 advisory 1.4.4 */ + boot_loader_read_mem32(pao, 0, 0); + + boot_loader_write_mem32(pao, 0, HPIDL_ADDR, data); + boot_loader_write_mem32(pao, 0, HPIDH_ADDR, data >> 16); + + /* dummy read every 4 words for 6205 advisory 1.4.4 */ + boot_loader_read_mem32(pao, 0, 0); + } +} + +static u16 boot_loader_config_emif(struct hpi_adapter_obj *pao, int dsp_index) +{ + if (dsp_index == 0) { + u32 setting; + + /* DSP 0 is always C6205 */ + + /* Set the EMIF */ + /* memory map of C6205 */ + /* 00000000-0000FFFF 16Kx32 internal program */ + /* 00400000-00BFFFFF CE0 2Mx32 SDRAM running @ 100MHz */ + + /* EMIF config */ + /*------------ */ + /* Global EMIF control */ + boot_loader_write_mem32(pao, dsp_index, 0x01800000, 0x3779); +#define WS_OFS 28 +#define WST_OFS 22 +#define WH_OFS 20 +#define RS_OFS 16 +#define RST_OFS 8 +#define MTYPE_OFS 4 +#define RH_OFS 0 + + /* EMIF CE0 setup - 2Mx32 Sync DRAM on ASI5000 cards only */ + setting = 0x00000030; + boot_loader_write_mem32(pao, dsp_index, 0x01800008, setting); + if (setting != boot_loader_read_mem32(pao, dsp_index, + 0x01800008)) + return HPI6205_ERROR_DSP_EMIF1; + + /* EMIF CE1 setup - 32 bit async. This is 6713 #1 HPI, */ + /* which occupies D15..0. 6713 starts at 27MHz, so need */ + /* plenty of wait states. See dsn8701.rtf, and 6713 errata. */ + /* WST should be 71, but 63 is max possible */ + setting = + (1L << WS_OFS) | (63L << WST_OFS) | (1L << WH_OFS) | + (1L << RS_OFS) | (63L << RST_OFS) | (1L << RH_OFS) | + (2L << MTYPE_OFS); + boot_loader_write_mem32(pao, dsp_index, 0x01800004, setting); + if (setting != boot_loader_read_mem32(pao, dsp_index, + 0x01800004)) + return HPI6205_ERROR_DSP_EMIF2; + + /* EMIF CE2 setup - 32 bit async. This is 6713 #2 HPI, */ + /* which occupies D15..0. 6713 starts at 27MHz, so need */ + /* plenty of wait states */ + setting = + (1L << WS_OFS) | (28L << WST_OFS) | (1L << WH_OFS) | + (1L << RS_OFS) | (63L << RST_OFS) | (1L << RH_OFS) | + (2L << MTYPE_OFS); + boot_loader_write_mem32(pao, dsp_index, 0x01800010, setting); + if (setting != boot_loader_read_mem32(pao, dsp_index, + 0x01800010)) + return HPI6205_ERROR_DSP_EMIF3; + + /* EMIF CE3 setup - 32 bit async. */ + /* This is the PLD on the ASI5000 cards only */ + setting = + (1L << WS_OFS) | (10L << WST_OFS) | (1L << WH_OFS) | + (1L << RS_OFS) | (10L << RST_OFS) | (1L << RH_OFS) | + (2L << MTYPE_OFS); + boot_loader_write_mem32(pao, dsp_index, 0x01800014, setting); + if (setting != boot_loader_read_mem32(pao, dsp_index, + 0x01800014)) + return HPI6205_ERROR_DSP_EMIF4; + + /* set EMIF SDRAM control for 2Mx32 SDRAM (512x32x4 bank) */ + /* need to use this else DSP code crashes? */ + boot_loader_write_mem32(pao, dsp_index, 0x01800018, + 0x07117000); + + /* EMIF SDRAM Refresh Timing */ + /* EMIF SDRAM timing (orig = 0x410, emulator = 0x61a) */ + boot_loader_write_mem32(pao, dsp_index, 0x0180001C, + 0x00000410); + + } else if (dsp_index == 1) { + /* test access to the C6713s HPI registers */ + u32 write_data = 0, read_data = 0, i = 0; + + /* Set up HPIC for little endian, by setiing HPIC:HWOB=1 */ + write_data = 1; + boot_loader_write_mem32(pao, 0, HPICL_ADDR, write_data); + boot_loader_write_mem32(pao, 0, HPICH_ADDR, write_data); + /* C67 HPI is on lower 16bits of 32bit EMIF */ + read_data = + 0xFFF7 & boot_loader_read_mem32(pao, 0, HPICL_ADDR); + if (write_data != read_data) { + HPI_DEBUG_LOG(ERROR, "HPICL %x %x\n", write_data, + read_data); + return HPI6205_ERROR_C6713_HPIC; + } + /* HPIA - walking ones test */ + write_data = 1; + for (i = 0; i < 32; i++) { + boot_loader_write_mem32(pao, 0, HPIAL_ADDR, + write_data); + boot_loader_write_mem32(pao, 0, HPIAH_ADDR, + (write_data >> 16)); + read_data = + 0xFFFF & boot_loader_read_mem32(pao, 0, + HPIAL_ADDR); + read_data = + read_data | ((0xFFFF & + boot_loader_read_mem32(pao, 0, + HPIAH_ADDR)) + << 16); + if (read_data != write_data) { + HPI_DEBUG_LOG(ERROR, "HPIA %x %x\n", + write_data, read_data); + return HPI6205_ERROR_C6713_HPIA; + } + write_data = write_data << 1; + } + + /* setup C67x PLL + * ** C6713 datasheet says we cannot program PLL from HPI, + * and indeed if we try to set the PLL multiply from the HPI, + * the PLL does not seem to lock, so we enable the PLL and + * use the default multiply of x 7, which for a 27MHz clock + * gives a DSP speed of 189MHz + */ + /* bypass PLL */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C100, 0x0000); + hpios_delay_micro_seconds(1000); + /* EMIF = 189/3=63MHz */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C120, 0x8002); + /* peri = 189/2 */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C11C, 0x8001); + /* cpu = 189/1 */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C118, 0x8000); + hpios_delay_micro_seconds(1000); + /* ** SGT test to take GPO3 high when we start the PLL */ + /* and low when the delay is completed */ + /* FSX0 <- '1' (GPO3) */ + boot_loader_write_mem32(pao, 0, (0x018C0024L), 0x00002A0A); + /* PLL not bypassed */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C100, 0x0001); + hpios_delay_micro_seconds(1000); + /* FSX0 <- '0' (GPO3) */ + boot_loader_write_mem32(pao, 0, (0x018C0024L), 0x00002A02); + + /* 6205 EMIF CE1 resetup - 32 bit async. */ + /* Now 6713 #1 is running at 189MHz can reduce waitstates */ + boot_loader_write_mem32(pao, 0, 0x01800004, /* CE1 */ + (1L << WS_OFS) | (8L << WST_OFS) | (1L << WH_OFS) | + (1L << RS_OFS) | (12L << RST_OFS) | (1L << RH_OFS) | + (2L << MTYPE_OFS)); + + hpios_delay_micro_seconds(1000); + + /* check that we can read one of the PLL registers */ + /* PLL should not be bypassed! */ + if ((boot_loader_read_mem32(pao, dsp_index, 0x01B7C100) & 0xF) + != 0x0001) { + return HPI6205_ERROR_C6713_PLL; + } + /* setup C67x EMIF (note this is the only use of + BAR1 via BootLoader_WriteMem32) */ + boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_GCTL, + 0x000034A8); + + /* EMIF CE0 setup - 2Mx32 Sync DRAM + 31..28 Wr setup + 27..22 Wr strobe + 21..20 Wr hold + 19..16 Rd setup + 15..14 - + 13..8 Rd strobe + 7..4 MTYPE 0011 Sync DRAM 32bits + 3 Wr hold MSB + 2..0 Rd hold + */ + boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_CE0, + 0x00000030); + + /* EMIF SDRAM Extension + 0x00 + 31-21 0000b 0000b 000b + 20 WR2RD = 2cycles-1 = 1b + + 19-18 WR2DEAC = 3cycle-1 = 10b + 17 WR2WR = 2cycle-1 = 1b + 16-15 R2WDQM = 4cycle-1 = 11b + 14-12 RD2WR = 6cycles-1 = 101b + + 11-10 RD2DEAC = 4cycle-1 = 11b + 9 RD2RD = 2cycle-1 = 1b + 8-7 THZP = 3cycle-1 = 10b + 6-5 TWR = 2cycle-1 = 01b (tWR = 17ns) + 4 TRRD = 2cycle = 0b (tRRD = 14ns) + 3-1 TRAS = 5cycle-1 = 100b (Tras=42ns) + 1 CAS latency = 3cyc = 1b + (for Micron 2M32-7 operating at 100MHz) + */ + boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_SDRAMEXT, + 0x001BDF29); + + /* EMIF SDRAM control - set up for a 2Mx32 SDRAM (512x32x4 bank) + 31 - 0b - + 30 SDBSZ 1b 4 bank + 29..28 SDRSZ 00b 11 row address pins + + 27..26 SDCSZ 01b 8 column address pins + 25 RFEN 1b refersh enabled + 24 INIT 1b init SDRAM! + + 23..20 TRCD 0001b (Trcd/Tcyc)-1 = (20/10)-1 = 1 + + 19..16 TRP 0001b (Trp/Tcyc)-1 = (20/10)-1 = 1 + + 15..12 TRC 0110b (Trc/Tcyc)-1 = (70/10)-1 = 6 + + 11..0 - 0000b 0000b 0000b + */ + boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_SDRAMCTL, + 0x47116000); + + /* SDRAM refresh timing + Need 4,096 refresh cycles every 64ms = 15.625us = 1562cycles of 100MHz = 0x61A + */ + boot_loader_write_mem32(pao, dsp_index, + C6713_EMIF_SDRAMTIMING, 0x00000410); + + hpios_delay_micro_seconds(1000); + } else if (dsp_index == 2) { + /* DSP 2 is a C6713 */ + } + + return 0; +} + +static u16 boot_loader_test_memory(struct hpi_adapter_obj *pao, int dsp_index, + u32 start_address, u32 length) +{ + u32 i = 0, j = 0; + u32 test_addr = 0; + u32 test_data = 0, data = 0; + + length = 1000; + + /* for 1st word, test each bit in the 32bit word, */ + /* dwLength specifies number of 32bit words to test */ + /*for(i=0; ipci.pci_dev->subsystem_device == 0x5000) { + /* DSP 0 is always C6205 */ + dRAM_start_address = 0x00400000; + dRAM_size = 0x200000; + /*dwDRAMinc=1024; */ + } else + return 0; + } else if (dsp_index == 1) { + /* DSP 1 is a C6713 */ + dRAM_start_address = 0x80000000; + dRAM_size = 0x200000; + /*dwDRAMinc=1024; */ + } + + if (boot_loader_test_memory(pao, dsp_index, dRAM_start_address, + dRAM_size)) + return HPI6205_ERROR_DSP_EXTMEM; + return 0; +} + +static u16 boot_loader_test_pld(struct hpi_adapter_obj *pao, int dsp_index) +{ + u32 data = 0; + if (dsp_index == 0) { + /* only test for DSP0 PLD on ASI5000 card */ + if (pao->pci.pci_dev->subsystem_device == 0x5000) { + /* PLD is located at CE3=0x03000000 */ + data = boot_loader_read_mem32(pao, dsp_index, + 0x03000008); + if ((data & 0xF) != 0x5) + return HPI6205_ERROR_DSP_PLD; + data = boot_loader_read_mem32(pao, dsp_index, + 0x0300000C); + if ((data & 0xF) != 0xA) + return HPI6205_ERROR_DSP_PLD; + } + } else if (dsp_index == 1) { + /* DSP 1 is a C6713 */ + if (pao->pci.pci_dev->subsystem_device == 0x8700) { + /* PLD is located at CE1=0x90000000 */ + data = boot_loader_read_mem32(pao, dsp_index, + 0x90000010); + if ((data & 0xFF) != 0xAA) + return HPI6205_ERROR_DSP_PLD; + /* 8713 - LED on */ + boot_loader_write_mem32(pao, dsp_index, 0x90000000, + 0x02); + } + } + return 0; +} + +/** Transfer data to or from DSP + nOperation = H620_H620_HIF_SEND_DATA or H620_HIF_GET_DATA +*/ +static short hpi6205_transfer_data(struct hpi_adapter_obj *pao, u8 *p_data, + u32 data_size, int operation) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 data_transferred = 0; + u16 err = 0; + u32 temp2; + struct bus_master_interface *interface = phw->p_interface_buffer; + + if (!p_data) + return HPI_ERROR_INVALID_DATA_POINTER; + + data_size &= ~3L; /* round data_size down to nearest 4 bytes */ + + /* make sure state is IDLE */ + if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) + return HPI_ERROR_DSP_HARDWARE; + + while (data_transferred < data_size) { + u32 this_copy = data_size - data_transferred; + + if (this_copy > HPI6205_SIZEOF_DATA) + this_copy = HPI6205_SIZEOF_DATA; + + if (operation == H620_HIF_SEND_DATA) + memcpy((void *)&interface->u.b_data[0], + &p_data[data_transferred], this_copy); + + interface->transfer_size_in_bytes = this_copy; + + /* DSP must change this back to nOperation */ + interface->dsp_ack = H620_HIF_IDLE; + send_dsp_command(phw, operation); + + temp2 = wait_dsp_ack(phw, operation, HPI6205_TIMEOUT); + HPI_DEBUG_LOG(DEBUG, "spun %d times for data xfer of %d\n", + HPI6205_TIMEOUT - temp2, this_copy); + + if (!temp2) { + /* timed out */ + HPI_DEBUG_LOG(ERROR, + "Timed out waiting for " "state %d got %d\n", + operation, interface->dsp_ack); + + break; + } + if (operation == H620_HIF_GET_DATA) + memcpy(&p_data[data_transferred], + (void *)&interface->u.b_data[0], this_copy); + + data_transferred += this_copy; + } + if (interface->dsp_ack != operation) + HPI_DEBUG_LOG(DEBUG, "interface->dsp_ack=%d, expected %d\n", + interface->dsp_ack, operation); + /* err=HPI_ERROR_DSP_HARDWARE; */ + + send_dsp_command(phw, H620_HIF_IDLE); + + return err; +} + +/* wait for up to timeout_us microseconds for the DSP + to signal state by DMA into dwDspAck +*/ +static int wait_dsp_ack(struct hpi_hw_obj *phw, int state, int timeout_us) +{ + struct bus_master_interface *interface = phw->p_interface_buffer; + int t = timeout_us / 4; + + rmb(); /* ensure interface->dsp_ack is up to date */ + while ((interface->dsp_ack != state) && --t) { + hpios_delay_micro_seconds(4); + rmb(); /* DSP changes dsp_ack by DMA */ + } + + /*HPI_DEBUG_LOG(VERBOSE, "Spun %d for %d\n", timeout_us/4-t, state); */ + return t * 4; +} + +/* set the busmaster interface to cmd, then interrupt the DSP */ +static void send_dsp_command(struct hpi_hw_obj *phw, int cmd) +{ + struct bus_master_interface *interface = phw->p_interface_buffer; + u32 r; + + interface->host_cmd = cmd; + wmb(); /* DSP gets state by DMA, make sure it is written to memory */ + /* before we interrupt the DSP */ + r = ioread32(phw->prHDCR); + r |= (u32)C6205_HDCR_DSPINT; + iowrite32(r, phw->prHDCR); + r &= ~(u32)C6205_HDCR_DSPINT; + iowrite32(r, phw->prHDCR); +} + +static unsigned int message_count; + +static u16 message_response_sequence(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + u32 time_out, time_out2; + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + u16 err = 0; + + message_count++; + if (phm->size > sizeof(interface->u.message_buffer)) { + phr->error = HPI_ERROR_MESSAGE_BUFFER_TOO_SMALL; + phr->specific_error = sizeof(interface->u.message_buffer); + phr->size = sizeof(struct hpi_response_header); + HPI_DEBUG_LOG(ERROR, + "message len %d too big for buffer %zd \n", phm->size, + sizeof(interface->u.message_buffer)); + return 0; + } + + /* Assume buffer of type struct bus_master_interface_62 + is allocated "noncacheable" */ + + if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) { + HPI_DEBUG_LOG(DEBUG, "timeout waiting for idle\n"); + return HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT; + } + + memcpy(&interface->u.message_buffer, phm, phm->size); + /* signal we want a response */ + send_dsp_command(phw, H620_HIF_GET_RESP); + + time_out2 = wait_dsp_ack(phw, H620_HIF_GET_RESP, HPI6205_TIMEOUT); + + if (!time_out2) { + HPI_DEBUG_LOG(ERROR, + "(%u) Timed out waiting for " "GET_RESP state [%x]\n", + message_count, interface->dsp_ack); + } else { + HPI_DEBUG_LOG(VERBOSE, + "(%u) transition to GET_RESP after %u\n", + message_count, HPI6205_TIMEOUT - time_out2); + } + /* spin waiting on HIF interrupt flag (end of msg process) */ + time_out = HPI6205_TIMEOUT; + + /* read the result */ + if (time_out) { + if (interface->u.response_buffer.response.size <= phr->size) + memcpy(phr, &interface->u.response_buffer, + interface->u.response_buffer.response.size); + else { + HPI_DEBUG_LOG(ERROR, + "response len %d too big for buffer %d\n", + interface->u.response_buffer.response.size, + phr->size); + memcpy(phr, &interface->u.response_buffer, + sizeof(struct hpi_response_header)); + phr->error = HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL; + phr->specific_error = + interface->u.response_buffer.response.size; + phr->size = sizeof(struct hpi_response_header); + } + } + /* set interface back to idle */ + send_dsp_command(phw, H620_HIF_IDLE); + + if (!time_out || !time_out2) { + HPI_DEBUG_LOG(DEBUG, "something timed out!\n"); + return HPI6205_ERROR_MSG_RESP_TIMEOUT; + } + /* special case for adapter close - */ + /* wait for the DSP to indicate it is idle */ + if (phm->function == HPI_ADAPTER_CLOSE) { + if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) { + HPI_DEBUG_LOG(DEBUG, + "Timeout waiting for idle " + "(on adapter_close)\n"); + return HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT; + } + } + err = hpi_validate_response(phm, phr); + return err; +} + +static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr) +{ + + u16 err = 0; + + hpios_dsplock_lock(pao); + + err = message_response_sequence(pao, phm, phr); + + /* maybe an error response */ + if (err) { + /* something failed in the HPI/DSP interface */ + if (err >= HPI_ERROR_BACKEND_BASE) { + phr->error = HPI_ERROR_DSP_COMMUNICATION; + phr->specific_error = err; + } else { + phr->error = err; + } + + pao->dsp_crashed++; + + /* just the header of the response is valid */ + phr->size = sizeof(struct hpi_response_header); + goto err; + } else + pao->dsp_crashed = 0; + + if (phr->error != 0) /* something failed in the DSP */ + goto err; + + switch (phm->function) { + case HPI_OSTREAM_WRITE: + case HPI_ISTREAM_ANC_WRITE: + err = hpi6205_transfer_data(pao, phm->u.d.u.data.pb_data, + phm->u.d.u.data.data_size, H620_HIF_SEND_DATA); + break; + + case HPI_ISTREAM_READ: + case HPI_OSTREAM_ANC_READ: + err = hpi6205_transfer_data(pao, phm->u.d.u.data.pb_data, + phm->u.d.u.data.data_size, H620_HIF_GET_DATA); + break; + + } + phr->error = err; + +err: + hpios_dsplock_unlock(pao); + + return; +} diff --git a/sound/pci/asihpi/hpi6205.h b/sound/pci/asihpi/hpi6205.h new file mode 100644 index 000000000..cc70e99a4 --- /dev/null +++ b/sound/pci/asihpi/hpi6205.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/***************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + +Host Interface module for an ASI6205 based +bus mastering PCI adapter. + +Copyright AudioScience, Inc., 2003 +******************************************************************************/ + +#ifndef _HPI6205_H_ +#define _HPI6205_H_ + +#include "hpi_internal.h" + +/*********************************************************** + Defines used for basic messaging +************************************************************/ +#define H620_HIF_RESET 0 +#define H620_HIF_IDLE 1 +#define H620_HIF_GET_RESP 2 +#define H620_HIF_DATA_DONE 3 +#define H620_HIF_DATA_MASK 0x10 +#define H620_HIF_SEND_DATA 0x14 +#define H620_HIF_GET_DATA 0x15 +#define H620_HIF_UNKNOWN 0x0000ffff + +/*********************************************************** + Types used for mixer control caching +************************************************************/ + +#define H620_MAX_ISTREAMS 32 +#define H620_MAX_OSTREAMS 32 +#define HPI_NMIXER_CONTROLS 2048 + +/********************************************************************* +This is used for dynamic control cache allocation +**********************************************************************/ +struct controlcache_6205 { + u32 number_of_controls; + u32 physical_address32; + u32 size_in_bytes; +}; + +/********************************************************************* +This is used for dynamic allocation of async event array +**********************************************************************/ +struct async_event_buffer_6205 { + u32 physical_address32; + u32 spare; + struct hpi_fifo_buffer b; +}; + +/*********************************************************** +The Host located memory buffer that the 6205 will bus master +in and out of. +************************************************************/ +#define HPI6205_SIZEOF_DATA (16*1024) + +struct message_buffer_6205 { + struct hpi_message message; + char data[256]; +}; + +struct response_buffer_6205 { + struct hpi_response response; + char data[256]; +}; + +union buffer_6205 { + struct message_buffer_6205 message_buffer; + struct response_buffer_6205 response_buffer; + u8 b_data[HPI6205_SIZEOF_DATA]; +}; + +struct bus_master_interface { + u32 host_cmd; + u32 dsp_ack; + u32 transfer_size_in_bytes; + union buffer_6205 u; + struct controlcache_6205 control_cache; + struct async_event_buffer_6205 async_buffer; + struct hpi_hostbuffer_status + instream_host_buffer_status[H620_MAX_ISTREAMS]; + struct hpi_hostbuffer_status + outstream_host_buffer_status[H620_MAX_OSTREAMS]; +}; + +#endif diff --git a/sound/pci/asihpi/hpi_internal.h b/sound/pci/asihpi/hpi_internal.h new file mode 100644 index 000000000..e569e3b33 --- /dev/null +++ b/sound/pci/asihpi/hpi_internal.h @@ -0,0 +1,1419 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2012 AudioScience Inc. + + +HPI internal definitions + +(C) Copyright AudioScience Inc. 1996-2009 +******************************************************************************/ + +#ifndef _HPI_INTERNAL_H_ +#define _HPI_INTERNAL_H_ + +#include "hpi.h" + +/** maximum number of memory regions mapped to an adapter */ +#define HPI_MAX_ADAPTER_MEM_SPACES (2) + +/* Each OS needs its own hpios.h */ +#include "hpios.h" + +/* physical memory allocation */ + +/** Allocate and map an area of locked memory for bus master DMA operations. + +On success, *pLockedMemeHandle is a valid handle, and 0 is returned +On error *pLockedMemHandle marked invalid, non-zero returned. + +If this function succeeds, then HpiOs_LockedMem_GetVirtAddr() and +HpiOs_LockedMem_GetPyhsAddr() will always succed on the returned handle. +*/ +u16 hpios_locked_mem_alloc(struct consistent_dma_area *p_locked_mem_handle, + /**< memory handle */ + u32 size, /**< Size in bytes to allocate */ + struct pci_dev *p_os_reference + /**< OS specific data required for memory allocation */ + ); + +/** Free mapping and memory represented by LockedMemHandle + +Frees any resources, then invalidates the handle. +Returns 0 on success, 1 if handle is invalid. + +*/ +u16 hpios_locked_mem_free(struct consistent_dma_area *locked_mem_handle); + +/** Get the physical PCI address of memory represented by LockedMemHandle. + +If handle is invalid *pPhysicalAddr is set to zero and return 1 +*/ +u16 hpios_locked_mem_get_phys_addr(struct consistent_dma_area + *locked_mem_handle, u32 *p_physical_addr); + +/** Get the CPU address of memory represented by LockedMemHandle. + +If handle is NULL *ppvVirtualAddr is set to NULL and return 1 +*/ +u16 hpios_locked_mem_get_virt_addr(struct consistent_dma_area + *locked_mem_handle, void **ppv_virtual_addr); + +/** Check that handle is valid +i.e it represents a valid memory area +*/ +u16 hpios_locked_mem_valid(struct consistent_dma_area *locked_mem_handle); + +/* timing/delay */ +void hpios_delay_micro_seconds(u32 num_micro_sec); + +struct hpi_message; +struct hpi_response; + +typedef void hpi_handler_func(struct hpi_message *, struct hpi_response *); + +/* If the assert fails, compiler complains + something like size of array `msg' is negative. + Unlike linux BUILD_BUG_ON, this works outside function scope. +*/ +#define compile_time_assert(cond, msg) \ + typedef char ASSERT_##msg[(cond) ? 1 : -1] + +/******************************************* bus types */ +enum HPI_BUSES { + HPI_BUS_ISAPNP = 1, + HPI_BUS_PCI = 2, + HPI_BUS_USB = 3, + HPI_BUS_NET = 4 +}; + +enum HPI_SUBSYS_OPTIONS { + /* 0, 256 are invalid, 1..255 reserved for global options */ + HPI_SUBSYS_OPT_NET_ENABLE = 257, + HPI_SUBSYS_OPT_NET_BROADCAST = 258, + HPI_SUBSYS_OPT_NET_UNICAST = 259, + HPI_SUBSYS_OPT_NET_ADDR = 260, + HPI_SUBSYS_OPT_NET_MASK = 261, + HPI_SUBSYS_OPT_NET_ADAPTER_ADDRESS_ADD = 262 +}; + +/** Volume flags +*/ +enum HPI_VOLUME_FLAGS { + /** Set if the volume control is muted */ + HPI_VOLUME_FLAG_MUTED = (1 << 0), + /** Set if the volume control has a mute function */ + HPI_VOLUME_FLAG_HAS_MUTE = (1 << 1), + /** Set if volume control can do autofading */ + HPI_VOLUME_FLAG_HAS_AUTOFADE = (1 << 2) + /* Note Flags >= (1<<8) are for DSP internal use only */ +}; + +/******************************************* CONTROL ATTRIBUTES ****/ +/* (in order of control type ID */ + +/* This allows for 255 control types, 256 unique attributes each */ +#define HPI_CTL_ATTR(ctl, ai) ((HPI_CONTROL_##ctl << 8) + ai) + +/* Get the sub-index of the attribute for a control type */ +#define HPI_CTL_ATTR_INDEX(i) (i & 0xff) + +/* Extract the control from the control attribute */ +#define HPI_CTL_ATTR_CONTROL(i) (i >> 8) + +/** Enable event generation for a control. +0=disable, 1=enable +\note generic to all controls that can generate events +*/ + +/** Unique identifiers for every control attribute +*/ +enum HPI_CONTROL_ATTRIBUTES { + HPI_GENERIC_ENABLE = HPI_CTL_ATTR(GENERIC, 1), + HPI_GENERIC_EVENT_ENABLE = HPI_CTL_ATTR(GENERIC, 2), + + HPI_VOLUME_GAIN = HPI_CTL_ATTR(VOLUME, 1), + HPI_VOLUME_AUTOFADE = HPI_CTL_ATTR(VOLUME, 2), + HPI_VOLUME_MUTE = HPI_CTL_ATTR(VOLUME, 3), + HPI_VOLUME_GAIN_AND_FLAGS = HPI_CTL_ATTR(VOLUME, 4), + HPI_VOLUME_NUM_CHANNELS = HPI_CTL_ATTR(VOLUME, 6), + HPI_VOLUME_RANGE = HPI_CTL_ATTR(VOLUME, 10), + + HPI_METER_RMS = HPI_CTL_ATTR(METER, 1), + HPI_METER_PEAK = HPI_CTL_ATTR(METER, 2), + HPI_METER_RMS_BALLISTICS = HPI_CTL_ATTR(METER, 3), + HPI_METER_PEAK_BALLISTICS = HPI_CTL_ATTR(METER, 4), + HPI_METER_NUM_CHANNELS = HPI_CTL_ATTR(METER, 5), + + HPI_MULTIPLEXER_SOURCE = HPI_CTL_ATTR(MULTIPLEXER, 1), + HPI_MULTIPLEXER_QUERYSOURCE = HPI_CTL_ATTR(MULTIPLEXER, 2), + + HPI_AESEBUTX_FORMAT = HPI_CTL_ATTR(AESEBUTX, 1), + HPI_AESEBUTX_SAMPLERATE = HPI_CTL_ATTR(AESEBUTX, 3), + HPI_AESEBUTX_CHANNELSTATUS = HPI_CTL_ATTR(AESEBUTX, 4), + HPI_AESEBUTX_USERDATA = HPI_CTL_ATTR(AESEBUTX, 5), + + HPI_AESEBURX_FORMAT = HPI_CTL_ATTR(AESEBURX, 1), + HPI_AESEBURX_ERRORSTATUS = HPI_CTL_ATTR(AESEBURX, 2), + HPI_AESEBURX_SAMPLERATE = HPI_CTL_ATTR(AESEBURX, 3), + HPI_AESEBURX_CHANNELSTATUS = HPI_CTL_ATTR(AESEBURX, 4), + HPI_AESEBURX_USERDATA = HPI_CTL_ATTR(AESEBURX, 5), + + HPI_LEVEL_GAIN = HPI_CTL_ATTR(LEVEL, 1), + HPI_LEVEL_RANGE = HPI_CTL_ATTR(LEVEL, 10), + + HPI_TUNER_BAND = HPI_CTL_ATTR(TUNER, 1), + HPI_TUNER_FREQ = HPI_CTL_ATTR(TUNER, 2), + HPI_TUNER_LEVEL_AVG = HPI_CTL_ATTR(TUNER, 3), + HPI_TUNER_LEVEL_RAW = HPI_CTL_ATTR(TUNER, 4), + HPI_TUNER_SNR = HPI_CTL_ATTR(TUNER, 5), + HPI_TUNER_GAIN = HPI_CTL_ATTR(TUNER, 6), + HPI_TUNER_STATUS = HPI_CTL_ATTR(TUNER, 7), + HPI_TUNER_MODE = HPI_CTL_ATTR(TUNER, 8), + HPI_TUNER_RDS = HPI_CTL_ATTR(TUNER, 9), + HPI_TUNER_DEEMPHASIS = HPI_CTL_ATTR(TUNER, 10), + HPI_TUNER_PROGRAM = HPI_CTL_ATTR(TUNER, 11), + HPI_TUNER_HDRADIO_SIGNAL_QUALITY = HPI_CTL_ATTR(TUNER, 12), + HPI_TUNER_HDRADIO_SDK_VERSION = HPI_CTL_ATTR(TUNER, 13), + HPI_TUNER_HDRADIO_DSP_VERSION = HPI_CTL_ATTR(TUNER, 14), + HPI_TUNER_HDRADIO_BLEND = HPI_CTL_ATTR(TUNER, 15), + + HPI_VOX_THRESHOLD = HPI_CTL_ATTR(VOX, 1), + + HPI_CHANNEL_MODE_MODE = HPI_CTL_ATTR(CHANNEL_MODE, 1), + + HPI_BITSTREAM_DATA_POLARITY = HPI_CTL_ATTR(BITSTREAM, 1), + HPI_BITSTREAM_CLOCK_EDGE = HPI_CTL_ATTR(BITSTREAM, 2), + HPI_BITSTREAM_CLOCK_SOURCE = HPI_CTL_ATTR(BITSTREAM, 3), + HPI_BITSTREAM_ACTIVITY = HPI_CTL_ATTR(BITSTREAM, 4), + + HPI_SAMPLECLOCK_SOURCE = HPI_CTL_ATTR(SAMPLECLOCK, 1), + HPI_SAMPLECLOCK_SAMPLERATE = HPI_CTL_ATTR(SAMPLECLOCK, 2), + HPI_SAMPLECLOCK_SOURCE_INDEX = HPI_CTL_ATTR(SAMPLECLOCK, 3), + HPI_SAMPLECLOCK_LOCAL_SAMPLERATE = HPI_CTL_ATTR(SAMPLECLOCK, 4), + HPI_SAMPLECLOCK_AUTO = HPI_CTL_ATTR(SAMPLECLOCK, 5), + HPI_SAMPLECLOCK_LOCAL_LOCK = HPI_CTL_ATTR(SAMPLECLOCK, 6), + + HPI_MICROPHONE_PHANTOM_POWER = HPI_CTL_ATTR(MICROPHONE, 1), + + HPI_EQUALIZER_NUM_FILTERS = HPI_CTL_ATTR(EQUALIZER, 1), + HPI_EQUALIZER_FILTER = HPI_CTL_ATTR(EQUALIZER, 2), + HPI_EQUALIZER_COEFFICIENTS = HPI_CTL_ATTR(EQUALIZER, 3), + + HPI_COMPANDER_PARAMS = HPI_CTL_ATTR(COMPANDER, 1), + HPI_COMPANDER_MAKEUPGAIN = HPI_CTL_ATTR(COMPANDER, 2), + HPI_COMPANDER_THRESHOLD = HPI_CTL_ATTR(COMPANDER, 3), + HPI_COMPANDER_RATIO = HPI_CTL_ATTR(COMPANDER, 4), + HPI_COMPANDER_ATTACK = HPI_CTL_ATTR(COMPANDER, 5), + HPI_COMPANDER_DECAY = HPI_CTL_ATTR(COMPANDER, 6), + + HPI_COBRANET_SET = HPI_CTL_ATTR(COBRANET, 1), + HPI_COBRANET_GET = HPI_CTL_ATTR(COBRANET, 2), + HPI_COBRANET_GET_STATUS = HPI_CTL_ATTR(COBRANET, 5), + HPI_COBRANET_SEND_PACKET = HPI_CTL_ATTR(COBRANET, 6), + HPI_COBRANET_GET_PACKET = HPI_CTL_ATTR(COBRANET, 7), + + HPI_TONEDETECTOR_THRESHOLD = HPI_CTL_ATTR(TONEDETECTOR, 1), + HPI_TONEDETECTOR_STATE = HPI_CTL_ATTR(TONEDETECTOR, 2), + HPI_TONEDETECTOR_FREQUENCY = HPI_CTL_ATTR(TONEDETECTOR, 3), + + HPI_SILENCEDETECTOR_THRESHOLD = HPI_CTL_ATTR(SILENCEDETECTOR, 1), + HPI_SILENCEDETECTOR_STATE = HPI_CTL_ATTR(SILENCEDETECTOR, 2), + HPI_SILENCEDETECTOR_DELAY = HPI_CTL_ATTR(SILENCEDETECTOR, 3), + + HPI_PAD_CHANNEL_NAME = HPI_CTL_ATTR(PAD, 1), + HPI_PAD_ARTIST = HPI_CTL_ATTR(PAD, 2), + HPI_PAD_TITLE = HPI_CTL_ATTR(PAD, 3), + HPI_PAD_COMMENT = HPI_CTL_ATTR(PAD, 4), + HPI_PAD_PROGRAM_TYPE = HPI_CTL_ATTR(PAD, 5), + HPI_PAD_PROGRAM_ID = HPI_CTL_ATTR(PAD, 6), + HPI_PAD_TA_SUPPORT = HPI_CTL_ATTR(PAD, 7), + HPI_PAD_TA_ACTIVE = HPI_CTL_ATTR(PAD, 8), + + HPI_UNIVERSAL_ENTITY = HPI_CTL_ATTR(UNIVERSAL, 1) +}; + +#define HPI_POLARITY_POSITIVE 0 +#define HPI_POLARITY_NEGATIVE 1 + +/*------------------------------------------------------------ + Cobranet Chip Bridge - copied from HMI.H +------------------------------------------------------------*/ +#define HPI_COBRANET_HMI_cobra_bridge 0x20000 +#define HPI_COBRANET_HMI_cobra_bridge_tx_pkt_buf \ + (HPI_COBRANET_HMI_cobra_bridge + 0x1000) +#define HPI_COBRANET_HMI_cobra_bridge_rx_pkt_buf \ + (HPI_COBRANET_HMI_cobra_bridge + 0x2000) +#define HPI_COBRANET_HMI_cobra_if_table1 0x110000 +#define HPI_COBRANET_HMI_cobra_if_phy_address \ + (HPI_COBRANET_HMI_cobra_if_table1 + 0xd) +#define HPI_COBRANET_HMI_cobra_protocolIP 0x72000 +#define HPI_COBRANET_HMI_cobra_ip_mon_currentIP \ + (HPI_COBRANET_HMI_cobra_protocolIP + 0x0) +#define HPI_COBRANET_HMI_cobra_ip_mon_staticIP \ + (HPI_COBRANET_HMI_cobra_protocolIP + 0x2) +#define HPI_COBRANET_HMI_cobra_sys 0x100000 +#define HPI_COBRANET_HMI_cobra_sys_desc \ + (HPI_COBRANET_HMI_cobra_sys + 0x0) +#define HPI_COBRANET_HMI_cobra_sys_objectID \ + (HPI_COBRANET_HMI_cobra_sys + 0x100) +#define HPI_COBRANET_HMI_cobra_sys_contact \ + (HPI_COBRANET_HMI_cobra_sys + 0x200) +#define HPI_COBRANET_HMI_cobra_sys_name \ + (HPI_COBRANET_HMI_cobra_sys + 0x300) +#define HPI_COBRANET_HMI_cobra_sys_location \ + (HPI_COBRANET_HMI_cobra_sys + 0x400) + +/*------------------------------------------------------------ + Cobranet Chip Status bits +------------------------------------------------------------*/ +#define HPI_COBRANET_HMI_STATUS_RXPACKET 2 +#define HPI_COBRANET_HMI_STATUS_TXPACKET 3 + +/*------------------------------------------------------------ + Ethernet header size +------------------------------------------------------------*/ +#define HPI_ETHERNET_HEADER_SIZE (16) + +/* These defines are used to fill in protocol information for an Ethernet packet + sent using HMI on CS18102 */ +/** ID supplied by Cirrus for ASI packets. */ +#define HPI_ETHERNET_PACKET_ID 0x85 +/** Simple packet - no special routing required */ +#define HPI_ETHERNET_PACKET_V1 0x01 +/** This packet must make its way to the host across the HPI interface */ +#define HPI_ETHERNET_PACKET_HOSTED_VIA_HMI 0x20 +/** This packet must make its way to the host across the HPI interface */ +#define HPI_ETHERNET_PACKET_HOSTED_VIA_HMI_V1 0x21 +/** This packet must make its way to the host across the HPI interface */ +#define HPI_ETHERNET_PACKET_HOSTED_VIA_HPI 0x40 +/** This packet must make its way to the host across the HPI interface */ +#define HPI_ETHERNET_PACKET_HOSTED_VIA_HPI_V1 0x41 + +#define HPI_ETHERNET_UDP_PORT 44600 /**< HPI UDP service */ + +/** Default network timeout in milli-seconds. */ +#define HPI_ETHERNET_TIMEOUT_MS 500 + +/** Locked memory buffer alloc/free phases */ +enum HPI_BUFFER_CMDS { + /** use one message to allocate or free physical memory */ + HPI_BUFFER_CMD_EXTERNAL = 0, + /** alloc physical memory */ + HPI_BUFFER_CMD_INTERNAL_ALLOC = 1, + /** send physical memory address to adapter */ + HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER = 2, + /** notify adapter to stop using physical buffer */ + HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER = 3, + /** free physical buffer */ + HPI_BUFFER_CMD_INTERNAL_FREE = 4 +}; + +/*****************************************************************************/ +/*****************************************************************************/ +/******** HPI LOW LEVEL MESSAGES *******/ +/*****************************************************************************/ +/*****************************************************************************/ +/** Pnp ids */ +/** "ASI" - actual is "ASX" - need to change */ +#define HPI_ID_ISAPNP_AUDIOSCIENCE 0x0669 +/** PCI vendor ID that AudioScience uses */ +#define HPI_PCI_VENDOR_ID_AUDIOSCIENCE 0x175C +/** PCI vendor ID that the DSP56301 has */ +#define HPI_PCI_VENDOR_ID_MOTOROLA 0x1057 +/** PCI vendor ID that TI uses */ +#define HPI_PCI_VENDOR_ID_TI 0x104C + +#define HPI_PCI_DEV_ID_PCI2040 0xAC60 +/** TI's C6205 PCI interface has this ID */ +#define HPI_PCI_DEV_ID_DSP6205 0xA106 + +#define HPI_USB_VENDOR_ID_AUDIOSCIENCE 0x1257 +#define HPI_USB_W2K_TAG 0x57495341 /* "ASIW" */ +#define HPI_USB_LINUX_TAG 0x4C495341 /* "ASIL" */ + +/** Invalid Adapter index +Used in HPI messages that are not addressed to a specific adapter +Used in DLL to indicate device not present +*/ +#define HPI_ADAPTER_INDEX_INVALID 0xFFFF + +/** First 2 hex digits define the adapter family */ +#define HPI_ADAPTER_FAMILY_MASK 0xff00 +#define HPI_MODULE_FAMILY_MASK 0xfff0 + +#define HPI_ADAPTER_FAMILY_ASI(f) (f & HPI_ADAPTER_FAMILY_MASK) +#define HPI_MODULE_FAMILY_ASI(f) (f & HPI_MODULE_FAMILY_MASK) +#define HPI_ADAPTER_ASI(f) (f) + +enum HPI_MESSAGE_TYPES { + HPI_TYPE_REQUEST = 1, + HPI_TYPE_RESPONSE = 2, + HPI_TYPE_DATA = 3, + HPI_TYPE_SSX2BYPASS_MESSAGE = 4, + HPI_TYPE_COMMAND = 5, + HPI_TYPE_NOTIFICATION = 6 +}; + +enum HPI_OBJECT_TYPES { + HPI_OBJ_SUBSYSTEM = 1, + HPI_OBJ_ADAPTER = 2, + HPI_OBJ_OSTREAM = 3, + HPI_OBJ_ISTREAM = 4, + HPI_OBJ_MIXER = 5, + HPI_OBJ_NODE = 6, + HPI_OBJ_CONTROL = 7, + HPI_OBJ_NVMEMORY = 8, + HPI_OBJ_GPIO = 9, + HPI_OBJ_WATCHDOG = 10, + HPI_OBJ_CLOCK = 11, + HPI_OBJ_PROFILE = 12, + /* HPI_ OBJ_ CONTROLEX = 13, */ + HPI_OBJ_ASYNCEVENT = 14 +#define HPI_OBJ_MAXINDEX 14 +}; + +#define HPI_OBJ_FUNCTION_SPACING 0x100 +#define HPI_FUNC_ID(obj, i) (HPI_OBJ_##obj * HPI_OBJ_FUNCTION_SPACING + i) + +#define HPI_EXTRACT_INDEX(fn) (fn & 0xff) + +enum HPI_FUNCTION_IDS { + HPI_SUBSYS_OPEN = HPI_FUNC_ID(SUBSYSTEM, 1), + HPI_SUBSYS_GET_VERSION = HPI_FUNC_ID(SUBSYSTEM, 2), + HPI_SUBSYS_GET_INFO = HPI_FUNC_ID(SUBSYSTEM, 3), + HPI_SUBSYS_CREATE_ADAPTER = HPI_FUNC_ID(SUBSYSTEM, 5), + HPI_SUBSYS_CLOSE = HPI_FUNC_ID(SUBSYSTEM, 6), + HPI_SUBSYS_DRIVER_LOAD = HPI_FUNC_ID(SUBSYSTEM, 8), + HPI_SUBSYS_DRIVER_UNLOAD = HPI_FUNC_ID(SUBSYSTEM, 9), + HPI_SUBSYS_GET_NUM_ADAPTERS = HPI_FUNC_ID(SUBSYSTEM, 12), + HPI_SUBSYS_GET_ADAPTER = HPI_FUNC_ID(SUBSYSTEM, 13), + HPI_SUBSYS_SET_NETWORK_INTERFACE = HPI_FUNC_ID(SUBSYSTEM, 14), + HPI_SUBSYS_OPTION_INFO = HPI_FUNC_ID(SUBSYSTEM, 15), + HPI_SUBSYS_OPTION_GET = HPI_FUNC_ID(SUBSYSTEM, 16), + HPI_SUBSYS_OPTION_SET = HPI_FUNC_ID(SUBSYSTEM, 17), +#define HPI_SUBSYS_FUNCTION_COUNT 17 + + HPI_ADAPTER_OPEN = HPI_FUNC_ID(ADAPTER, 1), + HPI_ADAPTER_CLOSE = HPI_FUNC_ID(ADAPTER, 2), + HPI_ADAPTER_GET_INFO = HPI_FUNC_ID(ADAPTER, 3), + HPI_ADAPTER_GET_ASSERT = HPI_FUNC_ID(ADAPTER, 4), + HPI_ADAPTER_TEST_ASSERT = HPI_FUNC_ID(ADAPTER, 5), + HPI_ADAPTER_SET_MODE = HPI_FUNC_ID(ADAPTER, 6), + HPI_ADAPTER_GET_MODE = HPI_FUNC_ID(ADAPTER, 7), + HPI_ADAPTER_ENABLE_CAPABILITY = HPI_FUNC_ID(ADAPTER, 8), + HPI_ADAPTER_SELFTEST = HPI_FUNC_ID(ADAPTER, 9), + HPI_ADAPTER_FIND_OBJECT = HPI_FUNC_ID(ADAPTER, 10), + HPI_ADAPTER_QUERY_FLASH = HPI_FUNC_ID(ADAPTER, 11), + HPI_ADAPTER_START_FLASH = HPI_FUNC_ID(ADAPTER, 12), + HPI_ADAPTER_PROGRAM_FLASH = HPI_FUNC_ID(ADAPTER, 13), + HPI_ADAPTER_SET_PROPERTY = HPI_FUNC_ID(ADAPTER, 14), + HPI_ADAPTER_GET_PROPERTY = HPI_FUNC_ID(ADAPTER, 15), + HPI_ADAPTER_ENUM_PROPERTY = HPI_FUNC_ID(ADAPTER, 16), + HPI_ADAPTER_MODULE_INFO = HPI_FUNC_ID(ADAPTER, 17), + HPI_ADAPTER_DEBUG_READ = HPI_FUNC_ID(ADAPTER, 18), + HPI_ADAPTER_IRQ_QUERY_AND_CLEAR = HPI_FUNC_ID(ADAPTER, 19), + HPI_ADAPTER_IRQ_CALLBACK = HPI_FUNC_ID(ADAPTER, 20), + HPI_ADAPTER_DELETE = HPI_FUNC_ID(ADAPTER, 21), + HPI_ADAPTER_READ_FLASH = HPI_FUNC_ID(ADAPTER, 22), + HPI_ADAPTER_END_FLASH = HPI_FUNC_ID(ADAPTER, 23), + HPI_ADAPTER_FILESTORE_DELETE_ALL = HPI_FUNC_ID(ADAPTER, 24), +#define HPI_ADAPTER_FUNCTION_COUNT 24 + + HPI_OSTREAM_OPEN = HPI_FUNC_ID(OSTREAM, 1), + HPI_OSTREAM_CLOSE = HPI_FUNC_ID(OSTREAM, 2), + HPI_OSTREAM_WRITE = HPI_FUNC_ID(OSTREAM, 3), + HPI_OSTREAM_START = HPI_FUNC_ID(OSTREAM, 4), + HPI_OSTREAM_STOP = HPI_FUNC_ID(OSTREAM, 5), + HPI_OSTREAM_RESET = HPI_FUNC_ID(OSTREAM, 6), + HPI_OSTREAM_GET_INFO = HPI_FUNC_ID(OSTREAM, 7), + HPI_OSTREAM_QUERY_FORMAT = HPI_FUNC_ID(OSTREAM, 8), + HPI_OSTREAM_DATA = HPI_FUNC_ID(OSTREAM, 9), + HPI_OSTREAM_SET_VELOCITY = HPI_FUNC_ID(OSTREAM, 10), + HPI_OSTREAM_SET_PUNCHINOUT = HPI_FUNC_ID(OSTREAM, 11), + HPI_OSTREAM_SINEGEN = HPI_FUNC_ID(OSTREAM, 12), + HPI_OSTREAM_ANC_RESET = HPI_FUNC_ID(OSTREAM, 13), + HPI_OSTREAM_ANC_GET_INFO = HPI_FUNC_ID(OSTREAM, 14), + HPI_OSTREAM_ANC_READ = HPI_FUNC_ID(OSTREAM, 15), + HPI_OSTREAM_SET_TIMESCALE = HPI_FUNC_ID(OSTREAM, 16), + HPI_OSTREAM_SET_FORMAT = HPI_FUNC_ID(OSTREAM, 17), + HPI_OSTREAM_HOSTBUFFER_ALLOC = HPI_FUNC_ID(OSTREAM, 18), + HPI_OSTREAM_HOSTBUFFER_FREE = HPI_FUNC_ID(OSTREAM, 19), + HPI_OSTREAM_GROUP_ADD = HPI_FUNC_ID(OSTREAM, 20), + HPI_OSTREAM_GROUP_GETMAP = HPI_FUNC_ID(OSTREAM, 21), + HPI_OSTREAM_GROUP_RESET = HPI_FUNC_ID(OSTREAM, 22), + HPI_OSTREAM_HOSTBUFFER_GET_INFO = HPI_FUNC_ID(OSTREAM, 23), + HPI_OSTREAM_WAIT_START = HPI_FUNC_ID(OSTREAM, 24), + HPI_OSTREAM_WAIT = HPI_FUNC_ID(OSTREAM, 25), +#define HPI_OSTREAM_FUNCTION_COUNT 25 + + HPI_ISTREAM_OPEN = HPI_FUNC_ID(ISTREAM, 1), + HPI_ISTREAM_CLOSE = HPI_FUNC_ID(ISTREAM, 2), + HPI_ISTREAM_SET_FORMAT = HPI_FUNC_ID(ISTREAM, 3), + HPI_ISTREAM_READ = HPI_FUNC_ID(ISTREAM, 4), + HPI_ISTREAM_START = HPI_FUNC_ID(ISTREAM, 5), + HPI_ISTREAM_STOP = HPI_FUNC_ID(ISTREAM, 6), + HPI_ISTREAM_RESET = HPI_FUNC_ID(ISTREAM, 7), + HPI_ISTREAM_GET_INFO = HPI_FUNC_ID(ISTREAM, 8), + HPI_ISTREAM_QUERY_FORMAT = HPI_FUNC_ID(ISTREAM, 9), + HPI_ISTREAM_ANC_RESET = HPI_FUNC_ID(ISTREAM, 10), + HPI_ISTREAM_ANC_GET_INFO = HPI_FUNC_ID(ISTREAM, 11), + HPI_ISTREAM_ANC_WRITE = HPI_FUNC_ID(ISTREAM, 12), + HPI_ISTREAM_HOSTBUFFER_ALLOC = HPI_FUNC_ID(ISTREAM, 13), + HPI_ISTREAM_HOSTBUFFER_FREE = HPI_FUNC_ID(ISTREAM, 14), + HPI_ISTREAM_GROUP_ADD = HPI_FUNC_ID(ISTREAM, 15), + HPI_ISTREAM_GROUP_GETMAP = HPI_FUNC_ID(ISTREAM, 16), + HPI_ISTREAM_GROUP_RESET = HPI_FUNC_ID(ISTREAM, 17), + HPI_ISTREAM_HOSTBUFFER_GET_INFO = HPI_FUNC_ID(ISTREAM, 18), + HPI_ISTREAM_WAIT_START = HPI_FUNC_ID(ISTREAM, 19), + HPI_ISTREAM_WAIT = HPI_FUNC_ID(ISTREAM, 20), +#define HPI_ISTREAM_FUNCTION_COUNT 20 + +/* NOTE: + GET_NODE_INFO, SET_CONNECTION, GET_CONNECTIONS are not currently used */ + HPI_MIXER_OPEN = HPI_FUNC_ID(MIXER, 1), + HPI_MIXER_CLOSE = HPI_FUNC_ID(MIXER, 2), + HPI_MIXER_GET_INFO = HPI_FUNC_ID(MIXER, 3), + HPI_MIXER_GET_NODE_INFO = HPI_FUNC_ID(MIXER, 4), + HPI_MIXER_GET_CONTROL = HPI_FUNC_ID(MIXER, 5), + HPI_MIXER_SET_CONNECTION = HPI_FUNC_ID(MIXER, 6), + HPI_MIXER_GET_CONNECTIONS = HPI_FUNC_ID(MIXER, 7), + HPI_MIXER_GET_CONTROL_BY_INDEX = HPI_FUNC_ID(MIXER, 8), + HPI_MIXER_GET_CONTROL_ARRAY_BY_INDEX = HPI_FUNC_ID(MIXER, 9), + HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES = HPI_FUNC_ID(MIXER, 10), + HPI_MIXER_STORE = HPI_FUNC_ID(MIXER, 11), + HPI_MIXER_GET_CACHE_INFO = HPI_FUNC_ID(MIXER, 12), + HPI_MIXER_GET_BLOCK_HANDLE = HPI_FUNC_ID(MIXER, 13), + HPI_MIXER_GET_PARAMETER_HANDLE = HPI_FUNC_ID(MIXER, 14), +#define HPI_MIXER_FUNCTION_COUNT 14 + + HPI_CONTROL_GET_INFO = HPI_FUNC_ID(CONTROL, 1), + HPI_CONTROL_GET_STATE = HPI_FUNC_ID(CONTROL, 2), + HPI_CONTROL_SET_STATE = HPI_FUNC_ID(CONTROL, 3), +#define HPI_CONTROL_FUNCTION_COUNT 3 + + HPI_NVMEMORY_OPEN = HPI_FUNC_ID(NVMEMORY, 1), + HPI_NVMEMORY_READ_BYTE = HPI_FUNC_ID(NVMEMORY, 2), + HPI_NVMEMORY_WRITE_BYTE = HPI_FUNC_ID(NVMEMORY, 3), +#define HPI_NVMEMORY_FUNCTION_COUNT 3 + + HPI_GPIO_OPEN = HPI_FUNC_ID(GPIO, 1), + HPI_GPIO_READ_BIT = HPI_FUNC_ID(GPIO, 2), + HPI_GPIO_WRITE_BIT = HPI_FUNC_ID(GPIO, 3), + HPI_GPIO_READ_ALL = HPI_FUNC_ID(GPIO, 4), + HPI_GPIO_WRITE_STATUS = HPI_FUNC_ID(GPIO, 5), +#define HPI_GPIO_FUNCTION_COUNT 5 + + HPI_ASYNCEVENT_OPEN = HPI_FUNC_ID(ASYNCEVENT, 1), + HPI_ASYNCEVENT_CLOSE = HPI_FUNC_ID(ASYNCEVENT, 2), + HPI_ASYNCEVENT_WAIT = HPI_FUNC_ID(ASYNCEVENT, 3), + HPI_ASYNCEVENT_GETCOUNT = HPI_FUNC_ID(ASYNCEVENT, 4), + HPI_ASYNCEVENT_GET = HPI_FUNC_ID(ASYNCEVENT, 5), + HPI_ASYNCEVENT_SENDEVENTS = HPI_FUNC_ID(ASYNCEVENT, 6), +#define HPI_ASYNCEVENT_FUNCTION_COUNT 6 + + HPI_WATCHDOG_OPEN = HPI_FUNC_ID(WATCHDOG, 1), + HPI_WATCHDOG_SET_TIME = HPI_FUNC_ID(WATCHDOG, 2), + HPI_WATCHDOG_PING = HPI_FUNC_ID(WATCHDOG, 3), + + HPI_CLOCK_OPEN = HPI_FUNC_ID(CLOCK, 1), + HPI_CLOCK_SET_TIME = HPI_FUNC_ID(CLOCK, 2), + HPI_CLOCK_GET_TIME = HPI_FUNC_ID(CLOCK, 3), + + HPI_PROFILE_OPEN_ALL = HPI_FUNC_ID(PROFILE, 1), + HPI_PROFILE_START_ALL = HPI_FUNC_ID(PROFILE, 2), + HPI_PROFILE_STOP_ALL = HPI_FUNC_ID(PROFILE, 3), + HPI_PROFILE_GET = HPI_FUNC_ID(PROFILE, 4), + HPI_PROFILE_GET_IDLECOUNT = HPI_FUNC_ID(PROFILE, 5), + HPI_PROFILE_GET_NAME = HPI_FUNC_ID(PROFILE, 6), + HPI_PROFILE_GET_UTILIZATION = HPI_FUNC_ID(PROFILE, 7) +#define HPI_PROFILE_FUNCTION_COUNT 7 +}; + +/* ////////////////////////////////////////////////////////////////////// */ +/* STRUCTURES */ +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(push, 1) +#endif + +/** PCI bus resource */ +struct hpi_pci { + u32 __iomem *ap_mem_base[HPI_MAX_ADAPTER_MEM_SPACES]; + struct pci_dev *pci_dev; +}; + +/** Adapter specification resource */ +struct hpi_adapter_specification { + u32 type; + u8 modules[4]; +}; + +struct hpi_resource { + union { + const struct hpi_pci *pci; + const char *net_if; + struct hpi_adapter_specification adapter_spec; + const void *sw_if; + } r; + u16 bus_type; /* HPI_BUS_PNPISA, _PCI, _USB etc */ + u16 padding; +}; + +/** Format info used inside struct hpi_message + Not the same as public API struct hpi_format */ +struct hpi_msg_format { + u32 sample_rate; /**< 11025, 32000, 44100 etc. */ + u32 bit_rate; /**< for MPEG */ + u32 attributes; /**< stereo/joint_stereo/mono */ + u16 channels; /**< 1,2..., (or ancillary mode or idle bit */ + u16 format; /**< HPI_FORMAT_PCM16, _MPEG etc. see \ref HPI_FORMATS. */ +}; + +/** Buffer+format structure. + Must be kept 7 * 32 bits to match public struct hpi_datastruct */ +struct hpi_msg_data { + struct hpi_msg_format format; + u8 *pb_data; +#ifndef CONFIG_64BIT + u32 padding; +#endif + u32 data_size; +}; + +/** struct hpi_datastructure used up to 3.04 driver */ +struct hpi_data_legacy32 { + struct hpi_format format; + u32 pb_data; + u32 data_size; +}; + +#ifdef CONFIG_64BIT +/* Compatibility version of struct hpi_data*/ +struct hpi_data_compat32 { + struct hpi_msg_format format; + u32 pb_data; + u32 padding; + u32 data_size; +}; +#endif + +struct hpi_buffer { + /** placeholder for backward compatibility (see dwBufferSize) */ + struct hpi_msg_format reserved; + u32 command; /**< HPI_BUFFER_CMD_xxx*/ + u32 pci_address; /**< PCI physical address of buffer for DSP DMA */ + u32 buffer_size; /**< must line up with data_size of HPI_DATA*/ +}; + +/*/////////////////////////////////////////////////////////////////////////// */ +/* This is used for background buffer bus mastering stream buffers. */ +struct hpi_hostbuffer_status { + u32 samples_processed; + u32 auxiliary_data_available; + u32 stream_state; + /* DSP index in to the host bus master buffer. */ + u32 dsp_index; + /* Host index in to the host bus master buffer. */ + u32 host_index; + u32 size_in_bytes; +}; + +struct hpi_streamid { + u16 object_type; + /**< Type of object, HPI_OBJ_OSTREAM or HPI_OBJ_ISTREAM. */ + u16 stream_index; /**< outstream or instream index. */ +}; + +struct hpi_punchinout { + u32 punch_in_sample; + u32 punch_out_sample; +}; + +struct hpi_subsys_msg { + struct hpi_resource resource; +}; + +struct hpi_subsys_res { + u32 version; + u32 data; /* extended version */ + u16 num_adapters; + u16 adapter_index; + u16 adapter_type; + u16 pad16; +}; + +union hpi_adapterx_msg { + struct { + u32 dsp_address; + u32 count_bytes; + } debug_read; + struct { + u32 adapter_mode; + u16 query_or_set; + } mode; + struct { + u16 index; + } module_info; + struct { + u16 index; + u16 what; + u16 property_index; + } property_enum; + struct { + u16 property; + u16 parameter1; + u16 parameter2; + } property_set; + struct { + u32 pad32; + u16 key1; + u16 key2; + } restart; + struct { + u32 pad32; + u16 value; + } test_assert; + struct { + u32 message; + } irq; + u32 pad[3]; +}; + +struct hpi_adapter_res { + u32 serial_number; + u16 adapter_type; + u16 adapter_index; + u16 num_instreams; + u16 num_outstreams; + u16 num_mixers; + u16 version; + u8 sz_adapter_assert[HPI_STRING_LEN]; +}; + +union hpi_adapterx_res { + struct hpi_adapter_res info; + struct { + u32 p1; + u16 count; + u16 dsp_index; + u32 p2; + u32 dsp_msg_addr; + char sz_message[HPI_STRING_LEN]; + } assert; + struct { + u32 adapter_mode; + } mode; + struct { + u16 parameter1; + u16 parameter2; + } property_get; + struct { + u32 yes; + } irq_query; +}; + +struct hpi_stream_msg { + union { + struct hpi_msg_data data; + struct hpi_data_legacy32 data32; + u16 velocity; + struct hpi_punchinout pio; + u32 time_scale; + struct hpi_buffer buffer; + struct hpi_streamid stream; + u32 threshold_bytes; + } u; +}; + +struct hpi_stream_res { + union { + struct { + /* size of hardware buffer */ + u32 buffer_size; + /* OutStream - data to play, + InStream - data recorded */ + u32 data_available; + /* OutStream - samples played, + InStream - samples recorded */ + u32 samples_transferred; + /* Adapter - OutStream - data to play, + InStream - data recorded */ + u32 auxiliary_data_available; + u16 state; /* HPI_STATE_PLAYING, _STATE_STOPPED */ + u16 padding; + } stream_info; + struct { + u32 buffer_size; + u32 data_available; + u32 samples_transfered; + u16 state; + u16 outstream_index; + u16 instream_index; + u16 padding; + u32 auxiliary_data_available; + } legacy_stream_info; + struct { + /* bitmap of grouped OutStreams */ + u32 outstream_group_map; + /* bitmap of grouped InStreams */ + u32 instream_group_map; + } group_info; + struct { + /* pointer to the buffer */ + u8 *p_buffer; + /* pointer to the hostbuffer status */ + struct hpi_hostbuffer_status *p_status; + } hostbuffer_info; + } u; +}; + +struct hpi_mixer_msg { + u16 control_index; + u16 control_type; /* = HPI_CONTROL_METER _VOLUME etc */ + u16 padding1; /* Maintain alignment of subsequent fields */ + u16 node_type1; /* = HPI_SOURCENODE_LINEIN etc */ + u16 node_index1; /* = 0..N */ + u16 node_type2; + u16 node_index2; + u16 padding2; /* round to 4 bytes */ +}; + +struct hpi_mixer_res { + u16 src_node_type; /* = HPI_SOURCENODE_LINEIN etc */ + u16 src_node_index; /* = 0..N */ + u16 dst_node_type; + u16 dst_node_index; + /* Also controlType for MixerGetControlByIndex */ + u16 control_index; + /* may indicate which DSP the control is located on */ + u16 dsp_index; +}; + +union hpi_mixerx_msg { + struct { + u16 starting_index; + u16 flags; + u32 length_in_bytes; /* length in bytes of p_data */ + u32 p_data; /* pointer to a data array */ + } gcabi; + struct { + u16 command; + u16 index; + } store; /* for HPI_MIXER_STORE message */ +}; + +union hpi_mixerx_res { + struct { + u32 bytes_returned; /* size of items returned */ + u32 p_data; /* pointer to data array */ + u16 more_to_do; /* indicates if there is more to do */ + } gcabi; + struct { + u32 total_controls; /* count of controls in the mixer */ + u32 cache_controls; /* count of controls in the cac */ + u32 cache_bytes; /* size of cache */ + } cache_info; +}; + +struct hpi_control_msg { + u16 attribute; /* control attribute or property */ + u16 saved_index; + u32 param1; /* generic parameter 1 */ + u32 param2; /* generic parameter 2 */ + short an_log_value[HPI_MAX_CHANNELS]; +}; + +struct hpi_control_union_msg { + u16 attribute; /* control attribute or property */ + u16 saved_index; /* only used in ctrl save/restore */ + union { + struct { + u32 param1; /* generic parameter 1 */ + u32 param2; /* generic parameter 2 */ + short an_log_value[HPI_MAX_CHANNELS]; + } old; + union { + u32 frequency; + u32 gain; + u32 band; + u32 deemphasis; + u32 program; + struct { + u32 mode; + u32 value; + } mode; + u32 blend; + } tuner; + } u; +}; + +struct hpi_control_res { + /* Could make union. dwParam, anLogValue never used in same response */ + u32 param1; + u32 param2; + short an_log_value[HPI_MAX_CHANNELS]; +}; + +union hpi_control_union_res { + struct { + u32 param1; + u32 param2; + short an_log_value[HPI_MAX_CHANNELS]; + } old; + union { + u32 band; + u32 frequency; + u32 gain; + u32 deemphasis; + struct { + u32 data[2]; + u32 bLER; + } rds; + short s_level; + struct { + u16 value; + u16 mask; + } status; + } tuner; + struct { + char sz_data[8]; + u32 remaining_chars; + } chars8; + char c_data12[12]; + union { + struct { + u32 status; + u32 readable_size; + u32 writeable_size; + } status; + } cobranet; +}; + +struct hpi_nvmemory_msg { + u16 address; + u16 data; +}; + +struct hpi_nvmemory_res { + u16 size_in_bytes; + u16 data; +}; + +struct hpi_gpio_msg { + u16 bit_index; + u16 bit_data; +}; + +struct hpi_gpio_res { + u16 number_input_bits; + u16 number_output_bits; + u16 bit_data[4]; +}; + +struct hpi_async_msg { + u32 events; + u16 maximum_events; + u16 padding; +}; + +struct hpi_async_res { + union { + struct { + u16 count; + } count; + struct { + u32 events; + u16 number_returned; + u16 padding; + } get; + struct hpi_async_event event; + } u; +}; + +struct hpi_watchdog_msg { + u32 time_ms; +}; + +struct hpi_watchdog_res { + u32 time_ms; +}; + +struct hpi_clock_msg { + u16 hours; + u16 minutes; + u16 seconds; + u16 milli_seconds; +}; + +struct hpi_clock_res { + u16 size_in_bytes; + u16 hours; + u16 minutes; + u16 seconds; + u16 milli_seconds; + u16 padding; +}; + +struct hpi_profile_msg { + u16 bin_index; + u16 padding; +}; + +struct hpi_profile_res_open { + u16 max_profiles; +}; + +struct hpi_profile_res_time { + u32 total_tick_count; + u32 call_count; + u32 max_tick_count; + u32 ticks_per_millisecond; + u16 profile_interval; +}; + +struct hpi_profile_res_name { + u8 sz_name[32]; +}; + +struct hpi_profile_res { + union { + struct hpi_profile_res_open o; + struct hpi_profile_res_time t; + struct hpi_profile_res_name n; + } u; +}; + +struct hpi_message_header { + u16 size; /* total size in bytes */ + u8 type; /* HPI_TYPE_MESSAGE */ + u8 version; /* message version */ + u16 object; /* HPI_OBJ_* */ + u16 function; /* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */ + u16 adapter_index; /* the adapter index */ + u16 obj_index; /* */ +}; + +struct hpi_message { + /* following fields must match HPI_MESSAGE_HEADER */ + u16 size; /* total size in bytes */ + u8 type; /* HPI_TYPE_MESSAGE */ + u8 version; /* message version */ + u16 object; /* HPI_OBJ_* */ + u16 function; /* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */ + u16 adapter_index; /* the adapter index */ + u16 obj_index; /* */ + union { + struct hpi_subsys_msg s; + union hpi_adapterx_msg ax; + struct hpi_stream_msg d; + struct hpi_mixer_msg m; + union hpi_mixerx_msg mx; /* extended mixer; */ + struct hpi_control_msg c; /* mixer control; */ + /* identical to struct hpi_control_msg, + but field naming is improved */ + struct hpi_control_union_msg cu; + struct hpi_nvmemory_msg n; + struct hpi_gpio_msg l; /* digital i/o */ + struct hpi_watchdog_msg w; + struct hpi_clock_msg t; /* dsp time */ + struct hpi_profile_msg p; + struct hpi_async_msg as; + char fixed_size[32]; + } u; +}; + +#define HPI_MESSAGE_SIZE_BY_OBJECT { \ + sizeof(struct hpi_message_header) , /* Default, no object type 0 */ \ + sizeof(struct hpi_message_header) + sizeof(struct hpi_subsys_msg),\ + sizeof(struct hpi_message_header) + sizeof(union hpi_adapterx_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_stream_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_stream_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_mixer_msg),\ + sizeof(struct hpi_message_header) , /* no node message */ \ + sizeof(struct hpi_message_header) + sizeof(struct hpi_control_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_nvmemory_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_gpio_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_watchdog_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_clock_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_profile_msg),\ + sizeof(struct hpi_message_header), /* controlx obj removed */ \ + sizeof(struct hpi_message_header) + sizeof(struct hpi_async_msg) \ +} + +/* +Note that the wSpecificError error field should be inspected and potentially +reported whenever HPI_ERROR_DSP_COMMUNICATION or HPI_ERROR_DSP_BOOTLOAD is +returned in wError. +*/ +struct hpi_response_header { + u16 size; + u8 type; /* HPI_TYPE_RESPONSE */ + u8 version; /* response version */ + u16 object; /* HPI_OBJ_* */ + u16 function; /* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */ + u16 error; /* HPI_ERROR_xxx */ + u16 specific_error; /* adapter specific error */ +}; + +struct hpi_response { +/* following fields must match HPI_RESPONSE_HEADER */ + u16 size; + u8 type; /* HPI_TYPE_RESPONSE */ + u8 version; /* response version */ + u16 object; /* HPI_OBJ_* */ + u16 function; /* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */ + u16 error; /* HPI_ERROR_xxx */ + u16 specific_error; /* adapter specific error */ + union { + struct hpi_subsys_res s; + union hpi_adapterx_res ax; + struct hpi_stream_res d; + struct hpi_mixer_res m; + union hpi_mixerx_res mx; /* extended mixer; */ + struct hpi_control_res c; /* mixer control; */ + /* identical to hpi_control_res, but field naming is improved */ + union hpi_control_union_res cu; + struct hpi_nvmemory_res n; + struct hpi_gpio_res l; /* digital i/o */ + struct hpi_watchdog_res w; + struct hpi_clock_res t; /* dsp time */ + struct hpi_profile_res p; + struct hpi_async_res as; + u8 bytes[52]; + } u; +}; + +#define HPI_RESPONSE_SIZE_BY_OBJECT { \ + sizeof(struct hpi_response_header) ,/* Default, no object type 0 */ \ + sizeof(struct hpi_response_header) + sizeof(struct hpi_subsys_res),\ + sizeof(struct hpi_response_header) + sizeof(union hpi_adapterx_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_stream_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_stream_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_mixer_res),\ + sizeof(struct hpi_response_header) , /* no node response */ \ + sizeof(struct hpi_response_header) + sizeof(struct hpi_control_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_nvmemory_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_gpio_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_watchdog_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_clock_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_profile_res),\ + sizeof(struct hpi_response_header), /* controlx obj removed */ \ + sizeof(struct hpi_response_header) + sizeof(struct hpi_async_res) \ +} + +/*********************** version 1 message/response **************************/ +#define HPINET_ETHERNET_DATA_SIZE (1500) +#define HPINET_IP_HDR_SIZE (20) +#define HPINET_IP_DATA_SIZE (HPINET_ETHERNET_DATA_SIZE - HPINET_IP_HDR_SIZE) +#define HPINET_UDP_HDR_SIZE (8) +#define HPINET_UDP_DATA_SIZE (HPINET_IP_DATA_SIZE - HPINET_UDP_HDR_SIZE) +#define HPINET_ASI_HDR_SIZE (2) +#define HPINET_ASI_DATA_SIZE (HPINET_UDP_DATA_SIZE - HPINET_ASI_HDR_SIZE) + +#define HPI_MAX_PAYLOAD_SIZE (HPINET_ASI_DATA_SIZE - 2) + +/* New style message/response, but still V0 compatible */ +struct hpi_msg_adapter_get_info { + struct hpi_message_header h; +}; + +struct hpi_res_adapter_get_info { + struct hpi_response_header h; /*v0 */ + struct hpi_adapter_res p; +}; + +struct hpi_res_adapter_debug_read { + struct hpi_response_header h; + u8 bytes[1024]; +}; + +struct hpi_msg_cobranet_hmi { + u16 attribute; + u16 padding; + u32 hmi_address; + u32 byte_count; +}; + +struct hpi_msg_cobranet_hmiwrite { + struct hpi_message_header h; + struct hpi_msg_cobranet_hmi p; + u8 bytes[256]; +}; + +struct hpi_msg_cobranet_hmiread { + struct hpi_message_header h; + struct hpi_msg_cobranet_hmi p; +}; + +struct hpi_res_cobranet_hmiread { + struct hpi_response_header h; + u32 byte_count; + u8 bytes[256]; +}; + +#if 1 +#define hpi_message_header_v1 hpi_message_header +#define hpi_response_header_v1 hpi_response_header +#else +/* V1 headers in Addition to v0 headers */ +struct hpi_message_header_v1 { + struct hpi_message_header h0; +/* struct { +} h1; */ +}; + +struct hpi_response_header_v1 { + struct hpi_response_header h0; + struct { + u16 adapter_index; /* the adapter index */ + u16 obj_index; /* object index */ + } h1; +}; +#endif + +struct hpi_msg_payload_v0 { + struct hpi_message_header h; + union { + struct hpi_subsys_msg s; + union hpi_adapterx_msg ax; + struct hpi_stream_msg d; + struct hpi_mixer_msg m; + union hpi_mixerx_msg mx; + struct hpi_control_msg c; + struct hpi_control_union_msg cu; + struct hpi_nvmemory_msg n; + struct hpi_gpio_msg l; + struct hpi_watchdog_msg w; + struct hpi_clock_msg t; + struct hpi_profile_msg p; + struct hpi_async_msg as; + } u; +}; + +struct hpi_res_payload_v0 { + struct hpi_response_header h; + union { + struct hpi_subsys_res s; + union hpi_adapterx_res ax; + struct hpi_stream_res d; + struct hpi_mixer_res m; + union hpi_mixerx_res mx; + struct hpi_control_res c; + union hpi_control_union_res cu; + struct hpi_nvmemory_res n; + struct hpi_gpio_res l; + struct hpi_watchdog_res w; + struct hpi_clock_res t; + struct hpi_profile_res p; + struct hpi_async_res as; + } u; +}; + +union hpi_message_buffer_v1 { + struct hpi_message m0; /* version 0 */ + struct hpi_message_header_v1 h; + u8 buf[HPI_MAX_PAYLOAD_SIZE]; +}; + +union hpi_response_buffer_v1 { + struct hpi_response r0; /* version 0 */ + struct hpi_response_header_v1 h; + u8 buf[HPI_MAX_PAYLOAD_SIZE]; +}; + +compile_time_assert((sizeof(union hpi_message_buffer_v1) <= + HPI_MAX_PAYLOAD_SIZE), message_buffer_ok); +compile_time_assert((sizeof(union hpi_response_buffer_v1) <= + HPI_MAX_PAYLOAD_SIZE), response_buffer_ok); + +/*////////////////////////////////////////////////////////////////////////// */ +/* declarations for compact control calls */ +struct hpi_control_defn { + u8 type; + u8 channels; + u8 src_node_type; + u8 src_node_index; + u8 dest_node_type; + u8 dest_node_index; +}; + +/*////////////////////////////////////////////////////////////////////////// */ +/* declarations for control caching (internal to HPI<->DSP interaction) */ + +/** indicates a cached u16 value is invalid. */ +#define HPI_CACHE_INVALID_UINT16 0xFFFF +/** indicates a cached short value is invalid. */ +#define HPI_CACHE_INVALID_SHORT -32768 + +/** A compact representation of (part of) a controls state. +Used for efficient transfer of the control state +between DSP and host or across a network +*/ +struct hpi_control_cache_info { + /** one of HPI_CONTROL_* */ + u8 control_type; + /** The total size of cached information in 32-bit words. */ + u8 size_in32bit_words; + /** The original index of the control on the DSP */ + u16 control_index; +}; + +struct hpi_control_cache_vol { + struct hpi_control_cache_info i; + short an_log[2]; + unsigned short flags; + char padding[2]; +}; + +struct hpi_control_cache_meter { + struct hpi_control_cache_info i; + short an_log_peak[2]; + short an_logRMS[2]; +}; + +struct hpi_control_cache_channelmode { + struct hpi_control_cache_info i; + u16 mode; + char temp_padding[6]; +}; + +struct hpi_control_cache_mux { + struct hpi_control_cache_info i; + u16 source_node_type; + u16 source_node_index; + char temp_padding[4]; +}; + +struct hpi_control_cache_level { + struct hpi_control_cache_info i; + short an_log[2]; + char temp_padding[4]; +}; + +struct hpi_control_cache_tuner { + struct hpi_control_cache_info i; + u32 freq_ink_hz; + u16 band; + short s_level_avg; +}; + +struct hpi_control_cache_aes3rx { + struct hpi_control_cache_info i; + u32 error_status; + u32 format; +}; + +struct hpi_control_cache_aes3tx { + struct hpi_control_cache_info i; + u32 format; + char temp_padding[4]; +}; + +struct hpi_control_cache_tonedetector { + struct hpi_control_cache_info i; + u16 state; + char temp_padding[6]; +}; + +struct hpi_control_cache_silencedetector { + struct hpi_control_cache_info i; + u32 state; + char temp_padding[4]; +}; + +struct hpi_control_cache_sampleclock { + struct hpi_control_cache_info i; + u16 source; + u16 source_index; + u32 sample_rate; +}; + +struct hpi_control_cache_microphone { + struct hpi_control_cache_info i; + u16 phantom_state; + char temp_padding[6]; +}; + +struct hpi_control_cache_single { + union { + struct hpi_control_cache_info i; + struct hpi_control_cache_vol vol; + struct hpi_control_cache_meter meter; + struct hpi_control_cache_channelmode mode; + struct hpi_control_cache_mux mux; + struct hpi_control_cache_level level; + struct hpi_control_cache_tuner tuner; + struct hpi_control_cache_aes3rx aes3rx; + struct hpi_control_cache_aes3tx aes3tx; + struct hpi_control_cache_tonedetector tone; + struct hpi_control_cache_silencedetector silence; + struct hpi_control_cache_sampleclock clk; + struct hpi_control_cache_microphone microphone; + } u; +}; + +struct hpi_control_cache_pad { + struct hpi_control_cache_info i; + u32 field_valid_flags; + u8 c_channel[40]; + u8 c_artist[100]; + u8 c_title[100]; + u8 c_comment[200]; + u32 pTY; + u32 pI; + u32 traffic_supported; + u32 traffic_anouncement; +}; + +/* 2^N sized FIFO buffer (internal to HPI<->DSP interaction) */ +struct hpi_fifo_buffer { + u32 size; + u32 dsp_index; + u32 host_index; +}; + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(pop) +#endif + +/* skip host side function declarations for DSP + compile and documentation extraction */ + +char hpi_handle_object(const u32 handle); + +void hpi_handle_to_indexes(const u32 handle, u16 *pw_adapter_index, + u16 *pw_object_index); + +u32 hpi_indexes_to_handle(const char c_object, const u16 adapter_index, + const u16 object_index); + +/*////////////////////////////////////////////////////////////////////////// */ + +/* main HPI entry point */ +void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr); + +/* used in PnP OS/driver */ +u16 hpi_outstream_host_buffer_get_info(u32 h_outstream, u8 **pp_buffer, + struct hpi_hostbuffer_status **pp_status); + +u16 hpi_instream_host_buffer_get_info(u32 h_instream, u8 **pp_buffer, + struct hpi_hostbuffer_status **pp_status); + +/* +The following 3 functions were last declared in header files for +driver 3.10. HPI_ControlQuery() used to be the recommended way +of getting a volume range. Declared here for binary asihpi32.dll +compatibility. +*/ + +void hpi_format_to_msg(struct hpi_msg_format *pMF, + const struct hpi_format *pF); +void hpi_stream_response_to_legacy(struct hpi_stream_res *pSR); + +/*////////////////////////////////////////////////////////////////////////// */ +/* declarations for individual HPI entry points */ +hpi_handler_func HPI_6000; +hpi_handler_func HPI_6205; + +#endif /* _HPI_INTERNAL_H_ */ diff --git a/sound/pci/asihpi/hpi_version.h b/sound/pci/asihpi/hpi_version.h new file mode 100644 index 000000000..016bc5545 --- /dev/null +++ b/sound/pci/asihpi/hpi_version.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/** HPI Version Definitions +Development releases have odd minor version. +Production releases have even minor version. + +\file hpi_version.h +*/ + +#ifndef _HPI_VERSION_H +#define _HPI_VERSION_H + +/* Use single digits for versions less that 10 to avoid octal. */ +/* *** HPI_VER is the only edit required to update version *** */ +/** HPI version */ +#define HPI_VER HPI_VERSION_CONSTRUCTOR(4, 14, 3) + +/** HPI version string in dotted decimal format */ +#define HPI_VER_STRING "4.14.03" + +/** Library version as documented in hpi-api-versions.txt */ +#define HPI_LIB_VER HPI_VERSION_CONSTRUCTOR(10, 4, 0) + +/** Construct hpi version number from major, minor, release numbers */ +#define HPI_VERSION_CONSTRUCTOR(maj, min, r) ((maj << 16) + (min << 8) + r) + +/** Extract major version from hpi version number */ +#define HPI_VER_MAJOR(v) ((int)(v >> 16)) +/** Extract minor version from hpi version number */ +#define HPI_VER_MINOR(v) ((int)((v >> 8) & 0xFF)) +/** Extract release from hpi version number */ +#define HPI_VER_RELEASE(v) ((int)(v & 0xFF)) + +#endif diff --git a/sound/pci/asihpi/hpicmn.c b/sound/pci/asihpi/hpicmn.c new file mode 100644 index 000000000..5f69c04c6 --- /dev/null +++ b/sound/pci/asihpi/hpicmn.c @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + +\file hpicmn.c + + Common functions used by hpixxxx.c modules + +(C) Copyright AudioScience Inc. 1998-2003 +*******************************************************************************/ +#define SOURCEFILE_NAME "hpicmn.c" + +#include "hpi_internal.h" +#include "hpidebug.h" +#include "hpimsginit.h" + +#include "hpicmn.h" + +struct hpi_adapters_list { + struct hpios_spinlock list_lock; + struct hpi_adapter_obj adapter[HPI_MAX_ADAPTERS]; + u16 gw_num_adapters; +}; + +static struct hpi_adapters_list adapters; + +/** + * hpi_validate_response - Given an HPI Message that was sent out and + * a response that was received, validate that the response has the + * correct fields filled in, i.e ObjectType, Function etc + * @phm: message + * @phr: response + */ +u16 hpi_validate_response(struct hpi_message *phm, struct hpi_response *phr) +{ + if (phr->type != HPI_TYPE_RESPONSE) { + HPI_DEBUG_LOG(ERROR, "header type %d invalid\n", phr->type); + return HPI_ERROR_INVALID_RESPONSE; + } + + if (phr->object != phm->object) { + HPI_DEBUG_LOG(ERROR, "header object %d invalid\n", + phr->object); + return HPI_ERROR_INVALID_RESPONSE; + } + + if (phr->function != phm->function) { + HPI_DEBUG_LOG(ERROR, "header function %d invalid\n", + phr->function); + return HPI_ERROR_INVALID_RESPONSE; + } + + return 0; +} + +u16 hpi_add_adapter(struct hpi_adapter_obj *pao) +{ + u16 retval = 0; + /*HPI_ASSERT(pao->type); */ + + hpios_alistlock_lock(&adapters); + + if (pao->index >= HPI_MAX_ADAPTERS) { + retval = HPI_ERROR_BAD_ADAPTER_NUMBER; + goto unlock; + } + + if (adapters.adapter[pao->index].type) { + int a; + for (a = HPI_MAX_ADAPTERS - 1; a >= 0; a--) { + if (!adapters.adapter[a].type) { + HPI_DEBUG_LOG(WARNING, + "ASI%X duplicate index %d moved to %d\n", + pao->type, pao->index, a); + pao->index = a; + break; + } + } + if (a < 0) { + retval = HPI_ERROR_DUPLICATE_ADAPTER_NUMBER; + goto unlock; + } + } + adapters.adapter[pao->index] = *pao; + hpios_dsplock_init(&adapters.adapter[pao->index]); + adapters.gw_num_adapters++; + +unlock: + hpios_alistlock_unlock(&adapters); + return retval; +} + +void hpi_delete_adapter(struct hpi_adapter_obj *pao) +{ + if (!pao->type) { + HPI_DEBUG_LOG(ERROR, "removing null adapter?\n"); + return; + } + + hpios_alistlock_lock(&adapters); + if (adapters.adapter[pao->index].type) + adapters.gw_num_adapters--; + memset(&adapters.adapter[pao->index], 0, sizeof(adapters.adapter[0])); + hpios_alistlock_unlock(&adapters); +} + +/** + * hpi_find_adapter - FindAdapter returns a pointer to the struct + * hpi_adapter_obj with index wAdapterIndex in an HPI_ADAPTERS_LIST + * structure. + * @adapter_index: value in [0, HPI_MAX_ADAPTERS[ + */ +struct hpi_adapter_obj *hpi_find_adapter(u16 adapter_index) +{ + struct hpi_adapter_obj *pao = NULL; + + if (adapter_index >= HPI_MAX_ADAPTERS) { + HPI_DEBUG_LOG(VERBOSE, "find_adapter invalid index %d\n", + adapter_index); + return NULL; + } + + pao = &adapters.adapter[adapter_index]; + if (pao->type != 0) { + /* + HPI_DEBUG_LOG(VERBOSE, "Found adapter index %d\n", + wAdapterIndex); + */ + return pao; + } else { + /* + HPI_DEBUG_LOG(VERBOSE, "No adapter index %d\n", + wAdapterIndex); + */ + return NULL; + } +} + +/** + * wipe_adapter_list - wipe an HPI_ADAPTERS_LIST structure. + * + */ +static void wipe_adapter_list(void) +{ + memset(&adapters, 0, sizeof(adapters)); +} + +static void subsys_get_adapter(struct hpi_message *phm, + struct hpi_response *phr) +{ + int count = phm->obj_index; + u16 index = 0; + + /* find the nCount'th nonzero adapter in array */ + for (index = 0; index < HPI_MAX_ADAPTERS; index++) { + if (adapters.adapter[index].type) { + if (!count) + break; + count--; + } + } + + if (index < HPI_MAX_ADAPTERS) { + phr->u.s.adapter_index = adapters.adapter[index].index; + phr->u.s.adapter_type = adapters.adapter[index].type; + } else { + phr->u.s.adapter_index = 0; + phr->u.s.adapter_type = 0; + phr->error = HPI_ERROR_INVALID_OBJ_INDEX; + } +} + +static unsigned int control_cache_alloc_check(struct hpi_control_cache *pC) +{ + unsigned int i; + int cached = 0; + if (!pC) + return 0; + + if (pC->init) + return pC->init; + + if (!pC->p_cache) + return 0; + + if (pC->control_count && pC->cache_size_in_bytes) { + char *p_master_cache; + unsigned int byte_count = 0; + + p_master_cache = (char *)pC->p_cache; + HPI_DEBUG_LOG(DEBUG, "check %d controls\n", + pC->control_count); + for (i = 0; i < pC->control_count; i++) { + struct hpi_control_cache_info *info = + (struct hpi_control_cache_info *) + &p_master_cache[byte_count]; + u16 control_index = info->control_index; + + if (control_index >= pC->control_count) { + HPI_DEBUG_LOG(INFO, + "adap %d control index %d out of range, cache not ready?\n", + pC->adap_idx, control_index); + return 0; + } + + if (!info->size_in32bit_words) { + if (!i) { + HPI_DEBUG_LOG(INFO, + "adap %d cache not ready?\n", + pC->adap_idx); + return 0; + } + /* The cache is invalid. + * Minimum valid entry size is + * sizeof(struct hpi_control_cache_info) + */ + HPI_DEBUG_LOG(ERROR, + "adap %d zero size cache entry %d\n", + pC->adap_idx, i); + break; + } + + if (info->control_type) { + pC->p_info[control_index] = info; + cached++; + } else { /* dummy cache entry */ + pC->p_info[control_index] = NULL; + } + + byte_count += info->size_in32bit_words * 4; + + HPI_DEBUG_LOG(VERBOSE, + "cached %d, pinfo %p index %d type %d size %d\n", + cached, pC->p_info[info->control_index], + info->control_index, info->control_type, + info->size_in32bit_words); + + /* quit loop early if whole cache has been scanned. + * dwControlCount is the maximum possible entries + * but some may be absent from the cache + */ + if (byte_count >= pC->cache_size_in_bytes) + break; + /* have seen last control index */ + if (info->control_index == pC->control_count - 1) + break; + } + + if (byte_count != pC->cache_size_in_bytes) + HPI_DEBUG_LOG(WARNING, + "adap %d bytecount %d != cache size %d\n", + pC->adap_idx, byte_count, + pC->cache_size_in_bytes); + else + HPI_DEBUG_LOG(DEBUG, + "adap %d cache good, bytecount == cache size = %d\n", + pC->adap_idx, byte_count); + + pC->init = (u16)cached; + } + return pC->init; +} + +/** Find a control. +*/ +static short find_control(u16 control_index, + struct hpi_control_cache *p_cache, struct hpi_control_cache_info **pI) +{ + if (!control_cache_alloc_check(p_cache)) { + HPI_DEBUG_LOG(VERBOSE, + "control_cache_alloc_check() failed %d\n", + control_index); + return 0; + } + + if (control_index >= p_cache->control_count) { + HPI_DEBUG_LOG(VERBOSE, "control_index out of bounce %d\n", + control_index); + return 0; + } + + *pI = p_cache->p_info[control_index]; + if (!*pI) { + HPI_DEBUG_LOG(VERBOSE, "Uncached Control %d\n", + control_index); + return 0; + } else { + HPI_DEBUG_LOG(VERBOSE, "find_control() type %d\n", + (*pI)->control_type); + } + return 1; +} + +/* allow unified treatment of several string fields within struct */ +#define HPICMN_PAD_OFS_AND_SIZE(m) {\ + offsetof(struct hpi_control_cache_pad, m), \ + sizeof(((struct hpi_control_cache_pad *)(NULL))->m) } + +struct pad_ofs_size { + unsigned int offset; + unsigned int field_size; +}; + +static const struct pad_ofs_size pad_desc[] = { + HPICMN_PAD_OFS_AND_SIZE(c_channel), /* HPI_PAD_CHANNEL_NAME */ + HPICMN_PAD_OFS_AND_SIZE(c_artist), /* HPI_PAD_ARTIST */ + HPICMN_PAD_OFS_AND_SIZE(c_title), /* HPI_PAD_TITLE */ + HPICMN_PAD_OFS_AND_SIZE(c_comment), /* HPI_PAD_COMMENT */ +}; + +/** CheckControlCache checks the cache and fills the struct hpi_response + * accordingly. It returns one if a cache hit occurred, zero otherwise. + */ +short hpi_check_control_cache_single(struct hpi_control_cache_single *pC, + struct hpi_message *phm, struct hpi_response *phr) +{ + size_t response_size; + short found = 1; + + /* set the default response size */ + response_size = + sizeof(struct hpi_response_header) + + sizeof(struct hpi_control_res); + + switch (pC->u.i.control_type) { + + case HPI_CONTROL_METER: + if (phm->u.c.attribute == HPI_METER_PEAK) { + phr->u.c.an_log_value[0] = pC->u.meter.an_log_peak[0]; + phr->u.c.an_log_value[1] = pC->u.meter.an_log_peak[1]; + } else if (phm->u.c.attribute == HPI_METER_RMS) { + if (pC->u.meter.an_logRMS[0] == + HPI_CACHE_INVALID_SHORT) { + phr->error = + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE; + phr->u.c.an_log_value[0] = HPI_METER_MINIMUM; + phr->u.c.an_log_value[1] = HPI_METER_MINIMUM; + } else { + phr->u.c.an_log_value[0] = + pC->u.meter.an_logRMS[0]; + phr->u.c.an_log_value[1] = + pC->u.meter.an_logRMS[1]; + } + } else + found = 0; + break; + case HPI_CONTROL_VOLUME: + if (phm->u.c.attribute == HPI_VOLUME_GAIN) { + phr->u.c.an_log_value[0] = pC->u.vol.an_log[0]; + phr->u.c.an_log_value[1] = pC->u.vol.an_log[1]; + } else if (phm->u.c.attribute == HPI_VOLUME_MUTE) { + if (pC->u.vol.flags & HPI_VOLUME_FLAG_HAS_MUTE) { + if (pC->u.vol.flags & HPI_VOLUME_FLAG_MUTED) + phr->u.c.param1 = + HPI_BITMASK_ALL_CHANNELS; + else + phr->u.c.param1 = 0; + } else { + phr->error = + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE; + phr->u.c.param1 = 0; + } + } else { + found = 0; + } + break; + case HPI_CONTROL_MULTIPLEXER: + if (phm->u.c.attribute == HPI_MULTIPLEXER_SOURCE) { + phr->u.c.param1 = pC->u.mux.source_node_type; + phr->u.c.param2 = pC->u.mux.source_node_index; + } else { + found = 0; + } + break; + case HPI_CONTROL_CHANNEL_MODE: + if (phm->u.c.attribute == HPI_CHANNEL_MODE_MODE) + phr->u.c.param1 = pC->u.mode.mode; + else + found = 0; + break; + case HPI_CONTROL_LEVEL: + if (phm->u.c.attribute == HPI_LEVEL_GAIN) { + phr->u.c.an_log_value[0] = pC->u.level.an_log[0]; + phr->u.c.an_log_value[1] = pC->u.level.an_log[1]; + } else + found = 0; + break; + case HPI_CONTROL_TUNER: + if (phm->u.c.attribute == HPI_TUNER_FREQ) + phr->u.c.param1 = pC->u.tuner.freq_ink_hz; + else if (phm->u.c.attribute == HPI_TUNER_BAND) + phr->u.c.param1 = pC->u.tuner.band; + else if (phm->u.c.attribute == HPI_TUNER_LEVEL_AVG) + if (pC->u.tuner.s_level_avg == + HPI_CACHE_INVALID_SHORT) { + phr->u.cu.tuner.s_level = 0; + phr->error = + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE; + } else + phr->u.cu.tuner.s_level = + pC->u.tuner.s_level_avg; + else + found = 0; + break; + case HPI_CONTROL_AESEBU_RECEIVER: + if (phm->u.c.attribute == HPI_AESEBURX_ERRORSTATUS) + phr->u.c.param1 = pC->u.aes3rx.error_status; + else if (phm->u.c.attribute == HPI_AESEBURX_FORMAT) + phr->u.c.param1 = pC->u.aes3rx.format; + else + found = 0; + break; + case HPI_CONTROL_AESEBU_TRANSMITTER: + if (phm->u.c.attribute == HPI_AESEBUTX_FORMAT) + phr->u.c.param1 = pC->u.aes3tx.format; + else + found = 0; + break; + case HPI_CONTROL_TONEDETECTOR: + if (phm->u.c.attribute == HPI_TONEDETECTOR_STATE) + phr->u.c.param1 = pC->u.tone.state; + else + found = 0; + break; + case HPI_CONTROL_SILENCEDETECTOR: + if (phm->u.c.attribute == HPI_SILENCEDETECTOR_STATE) { + phr->u.c.param1 = pC->u.silence.state; + } else + found = 0; + break; + case HPI_CONTROL_MICROPHONE: + if (phm->u.c.attribute == HPI_MICROPHONE_PHANTOM_POWER) + phr->u.c.param1 = pC->u.microphone.phantom_state; + else + found = 0; + break; + case HPI_CONTROL_SAMPLECLOCK: + if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE) + phr->u.c.param1 = pC->u.clk.source; + else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE_INDEX) { + if (pC->u.clk.source_index == + HPI_CACHE_INVALID_UINT16) { + phr->u.c.param1 = 0; + phr->error = + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE; + } else + phr->u.c.param1 = pC->u.clk.source_index; + } else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SAMPLERATE) + phr->u.c.param1 = pC->u.clk.sample_rate; + else + found = 0; + break; + case HPI_CONTROL_PAD:{ + struct hpi_control_cache_pad *p_pad; + p_pad = (struct hpi_control_cache_pad *)pC; + + if (!(p_pad->field_valid_flags & (1 << + HPI_CTL_ATTR_INDEX(phm->u.c. + attribute)))) { + phr->error = + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE; + break; + } + + if (phm->u.c.attribute == HPI_PAD_PROGRAM_ID) + phr->u.c.param1 = p_pad->pI; + else if (phm->u.c.attribute == HPI_PAD_PROGRAM_TYPE) + phr->u.c.param1 = p_pad->pTY; + else { + unsigned int index = + HPI_CTL_ATTR_INDEX(phm->u.c. + attribute) - 1; + unsigned int offset = phm->u.c.param1; + unsigned int pad_string_len, field_size; + char *pad_string; + unsigned int tocopy; + + if (index > ARRAY_SIZE(pad_desc) - 1) { + phr->error = + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE; + break; + } + + pad_string = + ((char *)p_pad) + + pad_desc[index].offset; + field_size = pad_desc[index].field_size; + /* Ensure null terminator */ + pad_string[field_size - 1] = 0; + + pad_string_len = strlen(pad_string) + 1; + + if (offset > pad_string_len) { + phr->error = + HPI_ERROR_INVALID_CONTROL_VALUE; + break; + } + + tocopy = pad_string_len - offset; + if (tocopy > sizeof(phr->u.cu.chars8.sz_data)) + tocopy = sizeof(phr->u.cu.chars8. + sz_data); + + memcpy(phr->u.cu.chars8.sz_data, + &pad_string[offset], tocopy); + + phr->u.cu.chars8.remaining_chars = + pad_string_len - offset - tocopy; + } + } + break; + default: + found = 0; + break; + } + + HPI_DEBUG_LOG(VERBOSE, "%s Adap %d, Ctl %d, Type %d, Attr %d\n", + found ? "Cached" : "Uncached", phm->adapter_index, + pC->u.i.control_index, pC->u.i.control_type, + phm->u.c.attribute); + + if (found) { + phr->size = (u16)response_size; + phr->type = HPI_TYPE_RESPONSE; + phr->object = phm->object; + phr->function = phm->function; + } + + return found; +} + +short hpi_check_control_cache(struct hpi_control_cache *p_cache, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_control_cache_info *pI; + + if (!find_control(phm->obj_index, p_cache, &pI)) { + HPI_DEBUG_LOG(VERBOSE, + "HPICMN find_control() failed for adap %d\n", + phm->adapter_index); + return 0; + } + + phr->error = 0; + phr->specific_error = 0; + phr->version = 0; + + return hpi_check_control_cache_single((struct hpi_control_cache_single + *)pI, phm, phr); +} + +/** Updates the cache with Set values. + +Only update if no error. +Volume and Level return the limited values in the response, so use these +Multiplexer does so use sent values +*/ +void hpi_cmn_control_cache_sync_to_msg_single(struct hpi_control_cache_single + *pC, struct hpi_message *phm, struct hpi_response *phr) +{ + switch (pC->u.i.control_type) { + case HPI_CONTROL_VOLUME: + if (phm->u.c.attribute == HPI_VOLUME_GAIN) { + pC->u.vol.an_log[0] = phr->u.c.an_log_value[0]; + pC->u.vol.an_log[1] = phr->u.c.an_log_value[1]; + } else if (phm->u.c.attribute == HPI_VOLUME_MUTE) { + if (phm->u.c.param1) + pC->u.vol.flags |= HPI_VOLUME_FLAG_MUTED; + else + pC->u.vol.flags &= ~HPI_VOLUME_FLAG_MUTED; + } + break; + case HPI_CONTROL_MULTIPLEXER: + /* mux does not return its setting on Set command. */ + if (phm->u.c.attribute == HPI_MULTIPLEXER_SOURCE) { + pC->u.mux.source_node_type = (u16)phm->u.c.param1; + pC->u.mux.source_node_index = (u16)phm->u.c.param2; + } + break; + case HPI_CONTROL_CHANNEL_MODE: + /* mode does not return its setting on Set command. */ + if (phm->u.c.attribute == HPI_CHANNEL_MODE_MODE) + pC->u.mode.mode = (u16)phm->u.c.param1; + break; + case HPI_CONTROL_LEVEL: + if (phm->u.c.attribute == HPI_LEVEL_GAIN) { + pC->u.vol.an_log[0] = phr->u.c.an_log_value[0]; + pC->u.vol.an_log[1] = phr->u.c.an_log_value[1]; + } + break; + case HPI_CONTROL_MICROPHONE: + if (phm->u.c.attribute == HPI_MICROPHONE_PHANTOM_POWER) + pC->u.microphone.phantom_state = (u16)phm->u.c.param1; + break; + case HPI_CONTROL_AESEBU_TRANSMITTER: + if (phm->u.c.attribute == HPI_AESEBUTX_FORMAT) + pC->u.aes3tx.format = phm->u.c.param1; + break; + case HPI_CONTROL_AESEBU_RECEIVER: + if (phm->u.c.attribute == HPI_AESEBURX_FORMAT) + pC->u.aes3rx.format = phm->u.c.param1; + break; + case HPI_CONTROL_SAMPLECLOCK: + if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE) + pC->u.clk.source = (u16)phm->u.c.param1; + else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE_INDEX) + pC->u.clk.source_index = (u16)phm->u.c.param1; + else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SAMPLERATE) + pC->u.clk.sample_rate = phm->u.c.param1; + break; + default: + break; + } +} + +void hpi_cmn_control_cache_sync_to_msg(struct hpi_control_cache *p_cache, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_control_cache_single *pC; + struct hpi_control_cache_info *pI; + + if (phr->error) + return; + + if (!find_control(phm->obj_index, p_cache, &pI)) { + HPI_DEBUG_LOG(VERBOSE, + "HPICMN find_control() failed for adap %d\n", + phm->adapter_index); + return; + } + + /* pC is the default cached control strucure. + May be cast to something else in the following switch statement. + */ + pC = (struct hpi_control_cache_single *)pI; + + hpi_cmn_control_cache_sync_to_msg_single(pC, phm, phr); +} + +/** Allocate control cache. + +\return Cache pointer, or NULL if allocation fails. +*/ +struct hpi_control_cache *hpi_alloc_control_cache(const u32 control_count, + const u32 size_in_bytes, u8 *p_dsp_control_buffer) +{ + struct hpi_control_cache *p_cache; + + p_cache = kzalloc_flex(*p_cache, p_info, control_count); + if (!p_cache) + return NULL; + + p_cache->cache_size_in_bytes = size_in_bytes; + p_cache->control_count = control_count; + p_cache->p_cache = p_dsp_control_buffer; + p_cache->init = 0; + return p_cache; +} + +void hpi_free_control_cache(struct hpi_control_cache *p_cache) +{ + kfree(p_cache); +} + +static void subsys_message(struct hpi_message *phm, struct hpi_response *phr) +{ + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, phm->function, 0); + + switch (phm->function) { + case HPI_SUBSYS_OPEN: + case HPI_SUBSYS_CLOSE: + case HPI_SUBSYS_DRIVER_UNLOAD: + break; + case HPI_SUBSYS_DRIVER_LOAD: + wipe_adapter_list(); + hpios_alistlock_init(&adapters); + break; + case HPI_SUBSYS_GET_ADAPTER: + subsys_get_adapter(phm, phr); + break; + case HPI_SUBSYS_GET_NUM_ADAPTERS: + phr->u.s.num_adapters = adapters.gw_num_adapters; + break; + case HPI_SUBSYS_CREATE_ADAPTER: + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +void HPI_COMMON(struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->type) { + case HPI_TYPE_REQUEST: + switch (phm->object) { + case HPI_OBJ_SUBSYSTEM: + subsys_message(phm, phr); + break; + } + break; + + default: + phr->error = HPI_ERROR_INVALID_TYPE; + break; + } +} diff --git a/sound/pci/asihpi/hpicmn.h b/sound/pci/asihpi/hpicmn.h new file mode 100644 index 000000000..40af73295 --- /dev/null +++ b/sound/pci/asihpi/hpicmn.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + +*/ + +struct hpi_adapter_obj; + +/* a function that takes an adapter obj and returns an int */ +typedef int adapter_int_func(struct hpi_adapter_obj *pao, u32 message); + +#define HPI_IRQ_NONE (0) +#define HPI_IRQ_MESSAGE (1) +#define HPI_IRQ_MIXER (2) + +struct hpi_adapter_obj { + struct hpi_pci pci; /* PCI info - bus#,dev#,address etc */ + u16 type; /* 0x6644 == ASI6644 etc */ + u16 index; + + struct hpios_spinlock dsp_lock; + + u16 dsp_crashed; + u16 has_control_cache; + void *priv; + adapter_int_func *irq_query_and_clear; + struct hpi_hostbuffer_status *instream_host_buffer_status; + struct hpi_hostbuffer_status *outstream_host_buffer_status; +}; + +struct hpi_control_cache { + /** indicates whether the structures are initialized */ + u16 init; + u16 adap_idx; + u32 control_count; + u32 cache_size_in_bytes; + /** pointer to DSP's control cache. */ + u8 *p_cache; + /** pointer to allocated memory of lookup pointers. */ + struct hpi_control_cache_info *p_info[] __counted_by(control_count); +}; + +struct hpi_adapter_obj *hpi_find_adapter(u16 adapter_index); + +u16 hpi_add_adapter(struct hpi_adapter_obj *pao); + +void hpi_delete_adapter(struct hpi_adapter_obj *pao); + +short hpi_check_control_cache(struct hpi_control_cache *pC, + struct hpi_message *phm, struct hpi_response *phr); + +short hpi_check_control_cache_single(struct hpi_control_cache_single *pC, + struct hpi_message *phm, struct hpi_response *phr); + +struct hpi_control_cache *hpi_alloc_control_cache(const u32 + number_of_controls, const u32 size_in_bytes, u8 *pDSP_control_buffer); + +void hpi_free_control_cache(struct hpi_control_cache *p_cache); + +void hpi_cmn_control_cache_sync_to_msg(struct hpi_control_cache *pC, + struct hpi_message *phm, struct hpi_response *phr); + +void hpi_cmn_control_cache_sync_to_msg_single(struct hpi_control_cache_single + *pC, struct hpi_message *phm, struct hpi_response *phr); + +u16 hpi_validate_response(struct hpi_message *phm, struct hpi_response *phr); + +hpi_handler_func HPI_COMMON; diff --git a/sound/pci/asihpi/hpidebug.c b/sound/pci/asihpi/hpidebug.c new file mode 100644 index 000000000..9570d9a44 --- /dev/null +++ b/sound/pci/asihpi/hpidebug.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/************************************************************************ + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + +Debug macro translation. + +************************************************************************/ + +#include "hpi_internal.h" +#include "hpidebug.h" + +/* Debug level; 0 quiet; 1 informative, 2 debug, 3 verbose debug. */ +int hpi_debug_level = HPI_DEBUG_LEVEL_DEFAULT; + +void hpi_debug_init(void) +{ + printk(KERN_INFO "debug start\n"); +} + +int hpi_debug_level_set(int level) +{ + int old_level; + + old_level = hpi_debug_level; + hpi_debug_level = level; + return old_level; +} + +int hpi_debug_level_get(void) +{ + return hpi_debug_level; +} + +void hpi_debug_message(struct hpi_message *phm, char *sz_fileline) +{ + if (phm) { + printk(KERN_DEBUG "HPI_MSG%d,%d,%d,%d,%d\n", phm->version, + phm->adapter_index, phm->obj_index, phm->function, + phm->u.c.attribute); + } + +} + +void hpi_debug_data(u16 *pdata, u32 len) +{ + u32 i; + int j; + int k; + int lines; + int cols = 8; + + lines = DIV_ROUND_UP(len, cols); + if (lines > 8) + lines = 8; + + for (i = 0, j = 0; j < lines; j++) { + printk(KERN_DEBUG "%p:", (pdata + i)); + + for (k = 0; k < cols && i < len; i++, k++) + printk(KERN_CONT "%s%04x", k == 0 ? "" : " ", pdata[i]); + + printk(KERN_CONT "\n"); + } +} diff --git a/sound/pci/asihpi/hpidebug.h b/sound/pci/asihpi/hpidebug.h new file mode 100644 index 000000000..c6dfc2292 --- /dev/null +++ b/sound/pci/asihpi/hpidebug.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/***************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + +Debug macros. + +*****************************************************************************/ + +#ifndef _HPIDEBUG_H +#define _HPIDEBUG_H + +#include "hpi_internal.h" + +/* Define debugging levels. */ +enum { HPI_DEBUG_LEVEL_ERROR = 0, /* always log errors */ + HPI_DEBUG_LEVEL_WARNING = 1, + HPI_DEBUG_LEVEL_NOTICE = 2, + HPI_DEBUG_LEVEL_INFO = 3, + HPI_DEBUG_LEVEL_DEBUG = 4, + HPI_DEBUG_LEVEL_VERBOSE = 5 /* same printk level as DEBUG */ +}; + +#define HPI_DEBUG_LEVEL_DEFAULT HPI_DEBUG_LEVEL_NOTICE + +/* an OS can define an extra flag string that is appended to + the start of each message, eg see linux kernel hpios.h */ + +#ifdef SOURCEFILE_NAME +#undef FILE_LINE +#define FILE_LINE SOURCEFILE_NAME ":" __stringify(__LINE__) " " +#endif + +#define HPI_DEBUG_ASSERT(expression) \ + do { \ + if (!(expression)) { \ + printk(KERN_ERR FILE_LINE \ + " ASSERT " __stringify(expression)); \ + } \ + } while (0) + +#define HPI_DEBUG_LOG(level, ...) \ + do { \ + if (hpi_debug_level >= HPI_DEBUG_LEVEL_##level) { \ + printk(HPI_DEBUG_FLAG_##level \ + FILE_LINE " " __VA_ARGS__); \ + } \ + } while (0) + +void hpi_debug_init(void); +int hpi_debug_level_set(int level); +int hpi_debug_level_get(void); +/* needed by Linux driver for dynamic debug level changes */ +extern int hpi_debug_level; + +void hpi_debug_message(struct hpi_message *phm, char *sz_fileline); + +void hpi_debug_data(u16 *pdata, u32 len); + +#define HPI_DEBUG_DATA(pdata, len) \ + do { \ + if (hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE) \ + hpi_debug_data(pdata, len); \ + } while (0) + +#define HPI_DEBUG_MESSAGE(level, phm) \ + do { \ + if (hpi_debug_level >= HPI_DEBUG_LEVEL_##level) { \ + hpi_debug_message(phm, HPI_DEBUG_FLAG_##level \ + FILE_LINE " " __stringify(level)); \ + } \ + } while (0) + +#define HPI_DEBUG_RESPONSE(phr) \ + do { \ + if (((hpi_debug_level >= HPI_DEBUG_LEVEL_DEBUG) && \ + (phr->error)) ||\ + (hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE)) \ + printk(KERN_DEBUG "HPI_RES%d,%d,%d\n", \ + phr->version, phr->error, phr->specific_error); \ + } while (0) + +#ifndef compile_time_assert +#define compile_time_assert(cond, msg) \ + typedef char msg[(cond) ? 1 : -1] +#endif + +#endif /* _HPIDEBUG_H_ */ diff --git a/sound/pci/asihpi/hpidspcd.c b/sound/pci/asihpi/hpidspcd.c new file mode 100644 index 000000000..6d2c27e47 --- /dev/null +++ b/sound/pci/asihpi/hpidspcd.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*********************************************************************** + + AudioScience HPI driver + Functions for reading DSP code using hotplug firmware loader + + Copyright (C) 1997-2014 AudioScience Inc. + + +***********************************************************************/ +#define SOURCEFILE_NAME "hpidspcd.c" +#include "hpidspcd.h" +#include "hpidebug.h" +#include "hpi_version.h" + +struct dsp_code_private { + /** Firmware descriptor */ + const struct firmware *firmware; + struct pci_dev *dev; +}; + +/*-------------------------------------------------------------------*/ +short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + u32 *os_error_code) +{ + const struct firmware *firmware __free(firmware) = NULL; + struct pci_dev *dev = os_data; + struct code_header header; + char fw_name[20]; + short err_ret = HPI_ERROR_DSP_FILE_NOT_FOUND; + int err; + + sprintf(fw_name, "asihpi/dsp%04x.bin", adapter); + + err = request_firmware(&firmware, fw_name, &dev->dev); + + if (err || !firmware) { + dev_err(&dev->dev, "%d, request_firmware failed for %s\n", + err, fw_name); + goto error; + } + if (firmware->size < sizeof(header)) { + dev_err(&dev->dev, "Header size too small %s\n", fw_name); + goto error; + } + memcpy(&header, firmware->data, sizeof(header)); + + if ((header.type != 0x45444F43) || /* "CODE" */ + (header.adapter != adapter) + || (header.size != firmware->size)) { + dev_err(&dev->dev, + "Invalid firmware header size %d != file %zd\n", + header.size, firmware->size); + goto error; + } + + if (HPI_VER_MAJOR(header.version) != HPI_VER_MAJOR(HPI_VER)) { + /* Major version change probably means Host-DSP protocol change */ + dev_err(&dev->dev, + "Incompatible firmware version DSP image %X != Driver %X\n", + header.version, HPI_VER); + goto error; + } + + if (header.version != HPI_VER) { + dev_warn(&dev->dev, + "Firmware version mismatch: DSP image %X != Driver %X\n", + header.version, HPI_VER); + } + + HPI_DEBUG_LOG(DEBUG, "dsp code %s opened\n", fw_name); + dsp_code->pvt = kmalloc_obj(*dsp_code->pvt); + if (!dsp_code->pvt) { + err_ret = HPI_ERROR_MEMORY_ALLOC; + goto error; + } + + dsp_code->pvt->dev = dev; + dsp_code->pvt->firmware = no_free_ptr(firmware); + dsp_code->header = header; + dsp_code->block_length = header.size / sizeof(u32); + dsp_code->word_count = sizeof(header) / sizeof(u32); + return 0; + +error: + dsp_code->block_length = 0; + return err_ret; +} + +/*-------------------------------------------------------------------*/ +void hpi_dsp_code_close(struct dsp_code *dsp_code) +{ + HPI_DEBUG_LOG(DEBUG, "dsp code closed\n"); + release_firmware(dsp_code->pvt->firmware); + kfree(dsp_code->pvt); +} + +/*-------------------------------------------------------------------*/ +void hpi_dsp_code_rewind(struct dsp_code *dsp_code) +{ + /* Go back to start of data, after header */ + dsp_code->word_count = sizeof(struct code_header) / sizeof(u32); +} + +/*-------------------------------------------------------------------*/ +short hpi_dsp_code_read_word(struct dsp_code *dsp_code, u32 *pword) +{ + if (dsp_code->word_count + 1 > dsp_code->block_length) + return HPI_ERROR_DSP_FILE_FORMAT; + + *pword = ((u32 *)(dsp_code->pvt->firmware->data))[dsp_code-> + word_count]; + dsp_code->word_count++; + return 0; +} + +/*-------------------------------------------------------------------*/ +short hpi_dsp_code_read_block(size_t words_requested, + struct dsp_code *dsp_code, u32 **ppblock) +{ + if (dsp_code->word_count + words_requested > dsp_code->block_length) + return HPI_ERROR_DSP_FILE_FORMAT; + + *ppblock = + ((u32 *)(dsp_code->pvt->firmware->data)) + + dsp_code->word_count; + dsp_code->word_count += words_requested; + return 0; +} diff --git a/sound/pci/asihpi/hpidspcd.h b/sound/pci/asihpi/hpidspcd.h new file mode 100644 index 000000000..9f1468ed7 --- /dev/null +++ b/sound/pci/asihpi/hpidspcd.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/***********************************************************************/ +/* + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + +\file +Functions for reading DSP code to load into DSP + +*/ +/***********************************************************************/ +#ifndef _HPIDSPCD_H_ +#define _HPIDSPCD_H_ + +#include "hpi_internal.h" + +/** Header structure for dsp firmware file + This structure must match that used in s2bin.c for generation of asidsp.bin + */ +/*#ifndef DISABLE_PRAGMA_PACK1 */ +/*#pragma pack(push, 1) */ +/*#endif */ +struct code_header { + /** Size in bytes including header */ + u32 size; + /** File type tag "CODE" == 0x45444F43 */ + u32 type; + /** Adapter model number */ + u32 adapter; + /** Firmware version*/ + u32 version; + /** Data checksum */ + u32 checksum; +}; +/*#ifndef DISABLE_PRAGMA_PACK1 */ +/*#pragma pack(pop) */ +/*#endif */ + +/*? Don't need the pragmas? */ +compile_time_assert((sizeof(struct code_header) == 20), code_header_size); + +/** Descriptor for dspcode from firmware loader */ +struct dsp_code { + /** copy of file header */ + struct code_header header; + /** Expected number of words in the whole dsp code,INCL header */ + u32 block_length; + /** Number of words read so far */ + u32 word_count; + + /** internal state of DSP code reader */ + struct dsp_code_private *pvt; +}; + +/** Prepare *psDspCode to refer to the requested adapter's firmware. +Code file name is obtained from HpiOs_GetDspCodePath + +\return 0 for success, or error code if requested code is not available +*/ +short hpi_dsp_code_open( + /** Code identifier, usually adapter family */ + u32 adapter, void *pci_dev, + /** Pointer to DSP code control structure */ + struct dsp_code *ps_dsp_code, + /** Pointer to dword to receive OS specific error code */ + u32 *pos_error_code); + +/** Close the DSP code file */ +void hpi_dsp_code_close(struct dsp_code *ps_dsp_code); + +/** Rewind to the beginning of the DSP code file (for verify) */ +void hpi_dsp_code_rewind(struct dsp_code *ps_dsp_code); + +/** Read one word from the dsp code file + \return 0 for success, or error code if eof, or block length exceeded +*/ +short hpi_dsp_code_read_word(struct dsp_code *ps_dsp_code, + /**< DSP code descriptor */ + u32 *pword /**< Where to store the read word */ + ); + +/** Get a block of dsp code into an internal buffer, and provide a pointer to +that buffer. (If dsp code is already an array in memory, it is referenced, +not copied.) + +\return Error if requested number of words are not available +*/ +short hpi_dsp_code_read_block(size_t words_requested, + struct dsp_code *ps_dsp_code, + /* Pointer to store (Pointer to code buffer) */ + u32 **ppblock); + +#endif diff --git a/sound/pci/asihpi/hpifunc.c b/sound/pci/asihpi/hpifunc.c new file mode 100644 index 000000000..24047fafe --- /dev/null +++ b/sound/pci/asihpi/hpifunc.c @@ -0,0 +1,2868 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "hpi_internal.h" +#include "hpimsginit.h" + +#include "hpidebug.h" + +struct hpi_handle { + unsigned int obj_index:12; + unsigned int obj_type:4; + unsigned int adapter_index:14; + unsigned int spare:1; + unsigned int read_only:1; +}; + +union handle_word { + struct hpi_handle h; + u32 w; +}; + +u32 hpi_indexes_to_handle(const char c_object, const u16 adapter_index, + const u16 object_index) +{ + union handle_word handle; + + handle.h.adapter_index = adapter_index; + handle.h.spare = 0; + handle.h.read_only = 0; + handle.h.obj_type = c_object; + handle.h.obj_index = object_index; + return handle.w; +} + +static u16 hpi_handle_indexes(const u32 h, u16 *p1, u16 *p2) +{ + union handle_word uhandle; + if (!h) + return HPI_ERROR_INVALID_HANDLE; + + uhandle.w = h; + + *p1 = (u16)uhandle.h.adapter_index; + if (p2) + *p2 = (u16)uhandle.h.obj_index; + + return 0; +} + +void hpi_handle_to_indexes(const u32 handle, u16 *pw_adapter_index, + u16 *pw_object_index) +{ + hpi_handle_indexes(handle, pw_adapter_index, pw_object_index); +} + +char hpi_handle_object(const u32 handle) +{ + union handle_word uhandle; + uhandle.w = handle; + return (char)uhandle.h.obj_type; +} + +void hpi_format_to_msg(struct hpi_msg_format *pMF, + const struct hpi_format *pF) +{ + pMF->sample_rate = pF->sample_rate; + pMF->bit_rate = pF->bit_rate; + pMF->attributes = pF->attributes; + pMF->channels = pF->channels; + pMF->format = pF->format; +} + +static void hpi_msg_to_format(struct hpi_format *pF, + struct hpi_msg_format *pMF) +{ + pF->sample_rate = pMF->sample_rate; + pF->bit_rate = pMF->bit_rate; + pF->attributes = pMF->attributes; + pF->channels = pMF->channels; + pF->format = pMF->format; + pF->mode_legacy = 0; + pF->unused = 0; +} + +void hpi_stream_response_to_legacy(struct hpi_stream_res *pSR) +{ + pSR->u.legacy_stream_info.auxiliary_data_available = + pSR->u.stream_info.auxiliary_data_available; + pSR->u.legacy_stream_info.state = pSR->u.stream_info.state; +} + +static inline void hpi_send_recvV1(struct hpi_message_header *m, + struct hpi_response_header *r) +{ + hpi_send_recv((struct hpi_message *)m, (struct hpi_response *)r); +} + +u16 hpi_subsys_get_version_ex(u32 *pversion_ex) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_VERSION); + hpi_send_recv(&hm, &hr); + *pversion_ex = hr.u.s.data; + return hr.error; +} + +u16 hpi_subsys_get_num_adapters(int *pn_num_adapters) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_NUM_ADAPTERS); + hpi_send_recv(&hm, &hr); + *pn_num_adapters = (int)hr.u.s.num_adapters; + return hr.error; +} + +u16 hpi_subsys_get_adapter(int iterator, u32 *padapter_index, + u16 *pw_adapter_type) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_ADAPTER); + hm.obj_index = (u16)iterator; + hpi_send_recv(&hm, &hr); + *padapter_index = (int)hr.u.s.adapter_index; + *pw_adapter_type = hr.u.s.adapter_type; + + return hr.error; +} + +u16 hpi_adapter_open(u16 adapter_index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_OPEN); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + return hr.error; + +} + +u16 hpi_adapter_close(u16 adapter_index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_CLOSE); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_adapter_set_mode(u16 adapter_index, u32 adapter_mode) +{ + return hpi_adapter_set_mode_ex(adapter_index, adapter_mode, + HPI_ADAPTER_MODE_SET); +} + +u16 hpi_adapter_set_mode_ex(u16 adapter_index, u32 adapter_mode, + u16 query_or_set) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_SET_MODE); + hm.adapter_index = adapter_index; + hm.u.ax.mode.adapter_mode = adapter_mode; + hm.u.ax.mode.query_or_set = query_or_set; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_adapter_get_mode(u16 adapter_index, u32 *padapter_mode) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_MODE); + hm.adapter_index = adapter_index; + hpi_send_recv(&hm, &hr); + if (padapter_mode) + *padapter_mode = hr.u.ax.mode.adapter_mode; + return hr.error; +} + +u16 hpi_adapter_get_info(u16 adapter_index, u16 *pw_num_outstreams, + u16 *pw_num_instreams, u16 *pw_version, u32 *pserial_number, + u16 *pw_adapter_type) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_INFO); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + *pw_adapter_type = hr.u.ax.info.adapter_type; + *pw_num_outstreams = hr.u.ax.info.num_outstreams; + *pw_num_instreams = hr.u.ax.info.num_instreams; + *pw_version = hr.u.ax.info.version; + *pserial_number = hr.u.ax.info.serial_number; + return hr.error; +} + +u16 hpi_adapter_get_module_by_index(u16 adapter_index, u16 module_index, + u16 *pw_num_outputs, u16 *pw_num_inputs, u16 *pw_version, + u32 *pserial_number, u16 *pw_module_type, u32 *ph_module) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_MODULE_INFO); + hm.adapter_index = adapter_index; + hm.u.ax.module_info.index = module_index; + + hpi_send_recv(&hm, &hr); + + *pw_module_type = hr.u.ax.info.adapter_type; + *pw_num_outputs = hr.u.ax.info.num_outstreams; + *pw_num_inputs = hr.u.ax.info.num_instreams; + *pw_version = hr.u.ax.info.version; + *pserial_number = hr.u.ax.info.serial_number; + *ph_module = 0; + + return hr.error; +} + +u16 hpi_adapter_set_property(u16 adapter_index, u16 property, u16 parameter1, + u16 parameter2) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_SET_PROPERTY); + hm.adapter_index = adapter_index; + hm.u.ax.property_set.property = property; + hm.u.ax.property_set.parameter1 = parameter1; + hm.u.ax.property_set.parameter2 = parameter2; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_adapter_get_property(u16 adapter_index, u16 property, + u16 *pw_parameter1, u16 *pw_parameter2) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_PROPERTY); + hm.adapter_index = adapter_index; + hm.u.ax.property_set.property = property; + + hpi_send_recv(&hm, &hr); + if (!hr.error) { + if (pw_parameter1) + *pw_parameter1 = hr.u.ax.property_get.parameter1; + if (pw_parameter2) + *pw_parameter2 = hr.u.ax.property_get.parameter2; + } + + return hr.error; +} + +u16 hpi_adapter_enumerate_property(u16 adapter_index, u16 index, + u16 what_to_enumerate, u16 property_index, u32 *psetting) +{ + return 0; +} + +u16 hpi_format_create(struct hpi_format *p_format, u16 channels, u16 format, + u32 sample_rate, u32 bit_rate, u32 attributes) +{ + u16 err = 0; + struct hpi_msg_format fmt; + + switch (channels) { + case 1: + case 2: + case 4: + case 6: + case 8: + case 16: + break; + default: + err = HPI_ERROR_INVALID_CHANNELS; + return err; + } + fmt.channels = channels; + + switch (format) { + case HPI_FORMAT_PCM16_SIGNED: + case HPI_FORMAT_PCM24_SIGNED: + case HPI_FORMAT_PCM32_SIGNED: + case HPI_FORMAT_PCM32_FLOAT: + case HPI_FORMAT_PCM16_BIGENDIAN: + case HPI_FORMAT_PCM8_UNSIGNED: + case HPI_FORMAT_MPEG_L1: + case HPI_FORMAT_MPEG_L2: + case HPI_FORMAT_MPEG_L3: + case HPI_FORMAT_DOLBY_AC2: + case HPI_FORMAT_AA_TAGIT1_HITS: + case HPI_FORMAT_AA_TAGIT1_INSERTS: + case HPI_FORMAT_RAW_BITSTREAM: + case HPI_FORMAT_AA_TAGIT1_HITS_EX1: + case HPI_FORMAT_OEM1: + case HPI_FORMAT_OEM2: + break; + default: + err = HPI_ERROR_INVALID_FORMAT; + return err; + } + fmt.format = format; + + if (sample_rate < 8000L) { + err = HPI_ERROR_INCOMPATIBLE_SAMPLERATE; + sample_rate = 8000L; + } + if (sample_rate > 200000L) { + err = HPI_ERROR_INCOMPATIBLE_SAMPLERATE; + sample_rate = 200000L; + } + fmt.sample_rate = sample_rate; + + switch (format) { + case HPI_FORMAT_MPEG_L1: + case HPI_FORMAT_MPEG_L2: + case HPI_FORMAT_MPEG_L3: + fmt.bit_rate = bit_rate; + break; + case HPI_FORMAT_PCM16_SIGNED: + case HPI_FORMAT_PCM16_BIGENDIAN: + fmt.bit_rate = channels * sample_rate * 2; + break; + case HPI_FORMAT_PCM32_SIGNED: + case HPI_FORMAT_PCM32_FLOAT: + fmt.bit_rate = channels * sample_rate * 4; + break; + case HPI_FORMAT_PCM8_UNSIGNED: + fmt.bit_rate = channels * sample_rate; + break; + default: + fmt.bit_rate = 0; + } + + switch (format) { + case HPI_FORMAT_MPEG_L2: + if ((channels == 1) + && (attributes != HPI_MPEG_MODE_DEFAULT)) { + attributes = HPI_MPEG_MODE_DEFAULT; + err = HPI_ERROR_INVALID_FORMAT; + } else if (attributes > HPI_MPEG_MODE_DUALCHANNEL) { + attributes = HPI_MPEG_MODE_DEFAULT; + err = HPI_ERROR_INVALID_FORMAT; + } + fmt.attributes = attributes; + break; + default: + fmt.attributes = attributes; + } + + hpi_msg_to_format(p_format, &fmt); + return err; +} + +u16 hpi_stream_estimate_buffer_size(struct hpi_format *p_format, + u32 host_polling_rate_in_milli_seconds, u32 *recommended_buffer_size) +{ + + u32 bytes_per_second; + u32 size; + u16 channels; + struct hpi_format *pF = p_format; + + channels = pF->channels; + + switch (pF->format) { + case HPI_FORMAT_PCM16_BIGENDIAN: + case HPI_FORMAT_PCM16_SIGNED: + bytes_per_second = pF->sample_rate * 2L * channels; + break; + case HPI_FORMAT_PCM24_SIGNED: + bytes_per_second = pF->sample_rate * 3L * channels; + break; + case HPI_FORMAT_PCM32_SIGNED: + case HPI_FORMAT_PCM32_FLOAT: + bytes_per_second = pF->sample_rate * 4L * channels; + break; + case HPI_FORMAT_PCM8_UNSIGNED: + bytes_per_second = pF->sample_rate * 1L * channels; + break; + case HPI_FORMAT_MPEG_L1: + case HPI_FORMAT_MPEG_L2: + case HPI_FORMAT_MPEG_L3: + bytes_per_second = pF->bit_rate / 8L; + break; + case HPI_FORMAT_DOLBY_AC2: + + bytes_per_second = 256000L / 8L; + break; + default: + return HPI_ERROR_INVALID_FORMAT; + } + size = (bytes_per_second * host_polling_rate_in_milli_seconds * 2) / + 1000L; + + *recommended_buffer_size = + roundup_pow_of_two(((size + 4095L) & ~4095L)); + return 0; +} + +u16 hpi_outstream_open(u16 adapter_index, u16 outstream_index, + u32 *ph_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_OPEN); + hm.adapter_index = adapter_index; + hm.obj_index = outstream_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + *ph_outstream = + hpi_indexes_to_handle(HPI_OBJ_OSTREAM, adapter_index, + outstream_index); + else + *ph_outstream = 0; + return hr.error; +} + +u16 hpi_outstream_close(u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_FREE); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GROUP_RESET); + hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_CLOSE); + hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_get_info_ex(u32 h_outstream, u16 *pw_state, + u32 *pbuffer_size, u32 *pdata_to_play, u32 *psamples_played, + u32 *pauxiliary_data_to_play) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GET_INFO); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + if (pw_state) + *pw_state = hr.u.d.u.stream_info.state; + if (pbuffer_size) + *pbuffer_size = hr.u.d.u.stream_info.buffer_size; + if (pdata_to_play) + *pdata_to_play = hr.u.d.u.stream_info.data_available; + if (psamples_played) + *psamples_played = hr.u.d.u.stream_info.samples_transferred; + if (pauxiliary_data_to_play) + *pauxiliary_data_to_play = + hr.u.d.u.stream_info.auxiliary_data_available; + return hr.error; +} + +u16 hpi_outstream_write_buf(u32 h_outstream, const u8 *pb_data, + u32 bytes_to_write, const struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_WRITE); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.d.u.data.pb_data = (u8 *)pb_data; + hm.u.d.u.data.data_size = bytes_to_write; + + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_start(u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_START); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_wait_start(u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_WAIT_START); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_stop(u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_STOP); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_sinegen(u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SINEGEN); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_reset(u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_RESET); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_query_format(u32 h_outstream, struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_QUERY_FORMAT); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_set_format(u32 h_outstream, struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SET_FORMAT); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_set_velocity(u32 h_outstream, short velocity) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SET_VELOCITY); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.d.u.velocity = velocity; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_set_punch_in_out(u32 h_outstream, u32 punch_in_sample, + u32 punch_out_sample) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SET_PUNCHINOUT); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hm.u.d.u.pio.punch_in_sample = punch_in_sample; + hm.u.d.u.pio.punch_out_sample = punch_out_sample; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_ancillary_reset(u32 h_outstream, u16 mode) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_ANC_RESET); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.d.u.data.format.channels = mode; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_outstream_ancillary_get_info(u32 h_outstream, u32 *pframes_available) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_ANC_GET_INFO); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + if (hr.error == 0) { + if (pframes_available) + *pframes_available = + hr.u.d.u.stream_info.data_available / + sizeof(struct hpi_anc_frame); + } + return hr.error; +} + +u16 hpi_outstream_ancillary_read(u32 h_outstream, + struct hpi_anc_frame *p_anc_frame_buffer, + u32 anc_frame_buffer_size_in_bytes, + u32 number_of_ancillary_frames_to_read) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_ANC_READ); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.d.u.data.pb_data = (u8 *)p_anc_frame_buffer; + hm.u.d.u.data.data_size = + number_of_ancillary_frames_to_read * + sizeof(struct hpi_anc_frame); + if (hm.u.d.u.data.data_size <= anc_frame_buffer_size_in_bytes) + hpi_send_recv(&hm, &hr); + else + hr.error = HPI_ERROR_INVALID_DATASIZE; + return hr.error; +} + +u16 hpi_outstream_set_time_scale(u32 h_outstream, u32 time_scale) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SET_TIMESCALE); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hm.u.d.u.time_scale = time_scale; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_host_buffer_allocate(u32 h_outstream, u32 size_in_bytes) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_ALLOC); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.d.u.data.data_size = size_in_bytes; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_outstream_host_buffer_get_info(u32 h_outstream, u8 **pp_buffer, + struct hpi_hostbuffer_status **pp_status) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_GET_INFO); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) { + if (pp_buffer) + *pp_buffer = hr.u.d.u.hostbuffer_info.p_buffer; + if (pp_status) + *pp_status = hr.u.d.u.hostbuffer_info.p_status; + } + return hr.error; +} + +u16 hpi_outstream_host_buffer_free(u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_FREE); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_outstream_group_add(u32 h_outstream, u32 h_stream) +{ + struct hpi_message hm; + struct hpi_response hr; + u16 adapter; + char c_obj_type; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GROUP_ADD); + + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + if (hpi_handle_indexes(h_stream, &adapter, + &hm.u.d.u.stream.stream_index)) + return HPI_ERROR_INVALID_HANDLE; + + c_obj_type = hpi_handle_object(h_stream); + switch (c_obj_type) { + case HPI_OBJ_OSTREAM: + case HPI_OBJ_ISTREAM: + hm.u.d.u.stream.object_type = c_obj_type; + break; + default: + return HPI_ERROR_INVALID_OBJ; + } + if (adapter != hm.adapter_index) + return HPI_ERROR_NO_INTERADAPTER_GROUPS; + + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_outstream_group_get_map(u32 h_outstream, u32 *poutstream_map, + u32 *pinstream_map) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GROUP_GETMAP); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + + if (poutstream_map) + *poutstream_map = hr.u.d.u.group_info.outstream_group_map; + if (pinstream_map) + *pinstream_map = hr.u.d.u.group_info.instream_group_map; + + return hr.error; +} + +u16 hpi_outstream_group_reset(u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GROUP_RESET); + if (hpi_handle_indexes(h_outstream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_open(u16 adapter_index, u16 instream_index, u32 *ph_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_OPEN); + hm.adapter_index = adapter_index; + hm.obj_index = instream_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + *ph_instream = + hpi_indexes_to_handle(HPI_OBJ_ISTREAM, adapter_index, + instream_index); + else + *ph_instream = 0; + + return hr.error; +} + +u16 hpi_instream_close(u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_FREE); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_GROUP_RESET); + hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_CLOSE); + hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_query_format(u32 h_instream, + const struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_QUERY_FORMAT); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_set_format(u32 h_instream, const struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_SET_FORMAT); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_read_buf(u32 h_instream, u8 *pb_data, u32 bytes_to_read) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_READ); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.d.u.data.data_size = bytes_to_read; + hm.u.d.u.data.pb_data = pb_data; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_start(u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_START); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_wait_start(u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_WAIT_START); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_stop(u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_STOP); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_reset(u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_RESET); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_get_info_ex(u32 h_instream, u16 *pw_state, u32 *pbuffer_size, + u32 *pdata_recorded, u32 *psamples_recorded, + u32 *pauxiliary_data_recorded) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_GET_INFO); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + + if (pw_state) + *pw_state = hr.u.d.u.stream_info.state; + if (pbuffer_size) + *pbuffer_size = hr.u.d.u.stream_info.buffer_size; + if (pdata_recorded) + *pdata_recorded = hr.u.d.u.stream_info.data_available; + if (psamples_recorded) + *psamples_recorded = hr.u.d.u.stream_info.samples_transferred; + if (pauxiliary_data_recorded) + *pauxiliary_data_recorded = + hr.u.d.u.stream_info.auxiliary_data_available; + return hr.error; +} + +u16 hpi_instream_ancillary_reset(u32 h_instream, u16 bytes_per_frame, + u16 mode, u16 alignment, u16 idle_bit) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_ANC_RESET); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.d.u.data.format.attributes = bytes_per_frame; + hm.u.d.u.data.format.format = (mode << 8) | (alignment & 0xff); + hm.u.d.u.data.format.channels = idle_bit; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_ancillary_get_info(u32 h_instream, u32 *pframe_space) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_ANC_GET_INFO); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + if (pframe_space) + *pframe_space = + (hr.u.d.u.stream_info.buffer_size - + hr.u.d.u.stream_info.data_available) / + sizeof(struct hpi_anc_frame); + return hr.error; +} + +u16 hpi_instream_ancillary_write(u32 h_instream, + const struct hpi_anc_frame *p_anc_frame_buffer, + u32 anc_frame_buffer_size_in_bytes, + u32 number_of_ancillary_frames_to_write) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_ANC_WRITE); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.d.u.data.pb_data = (u8 *)p_anc_frame_buffer; + hm.u.d.u.data.data_size = + number_of_ancillary_frames_to_write * + sizeof(struct hpi_anc_frame); + if (hm.u.d.u.data.data_size <= anc_frame_buffer_size_in_bytes) + hpi_send_recv(&hm, &hr); + else + hr.error = HPI_ERROR_INVALID_DATASIZE; + return hr.error; +} + +u16 hpi_instream_host_buffer_allocate(u32 h_instream, u32 size_in_bytes) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_ALLOC); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.d.u.data.data_size = size_in_bytes; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_host_buffer_get_info(u32 h_instream, u8 **pp_buffer, + struct hpi_hostbuffer_status **pp_status) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_GET_INFO); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) { + if (pp_buffer) + *pp_buffer = hr.u.d.u.hostbuffer_info.p_buffer; + if (pp_status) + *pp_status = hr.u.d.u.hostbuffer_info.p_status; + } + return hr.error; +} + +u16 hpi_instream_host_buffer_free(u32 h_instream) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_FREE); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_group_add(u32 h_instream, u32 h_stream) +{ + struct hpi_message hm; + struct hpi_response hr; + u16 adapter; + char c_obj_type; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_GROUP_ADD); + hr.error = 0; + + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + if (hpi_handle_indexes(h_stream, &adapter, + &hm.u.d.u.stream.stream_index)) + return HPI_ERROR_INVALID_HANDLE; + + c_obj_type = hpi_handle_object(h_stream); + + switch (c_obj_type) { + case HPI_OBJ_OSTREAM: + case HPI_OBJ_ISTREAM: + hm.u.d.u.stream.object_type = c_obj_type; + break; + default: + return HPI_ERROR_INVALID_OBJ; + } + + if (adapter != hm.adapter_index) + return HPI_ERROR_NO_INTERADAPTER_GROUPS; + + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_group_get_map(u32 h_instream, u32 *poutstream_map, + u32 *pinstream_map) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_FREE); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + + if (poutstream_map) + *poutstream_map = hr.u.d.u.group_info.outstream_group_map; + if (pinstream_map) + *pinstream_map = hr.u.d.u.group_info.instream_group_map; + + return hr.error; +} + +u16 hpi_instream_group_reset(u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_GROUP_RESET); + if (hpi_handle_indexes(h_instream, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_mixer_open(u16 adapter_index, u32 *ph_mixer) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + *ph_mixer = + hpi_indexes_to_handle(HPI_OBJ_MIXER, adapter_index, + 0); + else + *ph_mixer = 0; + return hr.error; +} + +u16 hpi_mixer_close(u32 h_mixer) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_CLOSE); + if (hpi_handle_indexes(h_mixer, &hm.adapter_index, NULL)) + return HPI_ERROR_INVALID_HANDLE; + + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_mixer_get_control(u32 h_mixer, u16 src_node_type, + u16 src_node_type_index, u16 dst_node_type, u16 dst_node_type_index, + u16 control_type, u32 *ph_control) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, + HPI_MIXER_GET_CONTROL); + if (hpi_handle_indexes(h_mixer, &hm.adapter_index, NULL)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.m.node_type1 = src_node_type; + hm.u.m.node_index1 = src_node_type_index; + hm.u.m.node_type2 = dst_node_type; + hm.u.m.node_index2 = dst_node_type_index; + hm.u.m.control_type = control_type; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + *ph_control = + hpi_indexes_to_handle(HPI_OBJ_CONTROL, + hm.adapter_index, hr.u.m.control_index); + else + *ph_control = 0; + return hr.error; +} + +u16 hpi_mixer_get_control_by_index(u32 h_mixer, u16 control_index, + u16 *pw_src_node_type, u16 *pw_src_node_index, u16 *pw_dst_node_type, + u16 *pw_dst_node_index, u16 *pw_control_type, u32 *ph_control) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, + HPI_MIXER_GET_CONTROL_BY_INDEX); + if (hpi_handle_indexes(h_mixer, &hm.adapter_index, NULL)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.m.control_index = control_index; + hpi_send_recv(&hm, &hr); + + if (pw_src_node_type) { + *pw_src_node_type = + hr.u.m.src_node_type + HPI_SOURCENODE_NONE; + *pw_src_node_index = hr.u.m.src_node_index; + *pw_dst_node_type = hr.u.m.dst_node_type + HPI_DESTNODE_NONE; + *pw_dst_node_index = hr.u.m.dst_node_index; + } + if (pw_control_type) + *pw_control_type = hr.u.m.control_index; + + if (ph_control) { + if (hr.error == 0) + *ph_control = + hpi_indexes_to_handle(HPI_OBJ_CONTROL, + hm.adapter_index, control_index); + else + *ph_control = 0; + } + return hr.error; +} + +u16 hpi_mixer_store(u32 h_mixer, enum HPI_MIXER_STORE_COMMAND command, + u16 index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_STORE); + if (hpi_handle_indexes(h_mixer, &hm.adapter_index, NULL)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.mx.store.command = command; + hm.u.mx.store.index = index; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +static +u16 hpi_control_param_set(const u32 h_control, const u16 attrib, + const u32 param1, const u32 param2) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = attrib; + hm.u.c.param1 = param1; + hm.u.c.param2 = param2; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +static u16 hpi_control_log_set2(u32 h_control, u16 attrib, short sv0, + short sv1) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = attrib; + hm.u.c.an_log_value[0] = sv0; + hm.u.c.an_log_value[1] = sv1; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +static +u16 hpi_control_param_get(const u32 h_control, const u16 attrib, u32 param1, + u32 param2, u32 *pparam1, u32 *pparam2) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = attrib; + hm.u.c.param1 = param1; + hm.u.c.param2 = param2; + hpi_send_recv(&hm, &hr); + + *pparam1 = hr.u.c.param1; + if (pparam2) + *pparam2 = hr.u.c.param2; + + return hr.error; +} + +#define hpi_control_param1_get(h, a, p1) \ + hpi_control_param_get(h, a, 0, 0, p1, NULL) +#define hpi_control_param2_get(h, a, p1, p2) \ + hpi_control_param_get(h, a, 0, 0, p1, p2) + +static u16 hpi_control_log_get2(u32 h_control, u16 attrib, short *sv0, + short *sv1) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = attrib; + + hpi_send_recv(&hm, &hr); + *sv0 = hr.u.c.an_log_value[0]; + if (sv1) + *sv1 = hr.u.c.an_log_value[1]; + return hr.error; +} + +static +u16 hpi_control_query(const u32 h_control, const u16 attrib, const u32 index, + const u32 param, u32 *psetting) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_INFO); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hm.u.c.attribute = attrib; + hm.u.c.param1 = index; + hm.u.c.param2 = param; + + hpi_send_recv(&hm, &hr); + *psetting = hr.u.c.param1; + + return hr.error; +} + +static u16 hpi_control_get_string(const u32 h_control, const u16 attribute, + char *psz_string, const u32 string_length) +{ + unsigned int sub_string_index = 0, j = 0; + char c = 0; + unsigned int n = 0; + u16 err = 0; + + if ((string_length < 1) || (string_length > 256)) + return HPI_ERROR_INVALID_CONTROL_VALUE; + for (sub_string_index = 0; sub_string_index < string_length; + sub_string_index += 8) { + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, + &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = attribute; + hm.u.c.param1 = sub_string_index; + hm.u.c.param2 = 0; + hpi_send_recv(&hm, &hr); + + if (sub_string_index == 0 + && (hr.u.cu.chars8.remaining_chars + 8) > + string_length) + return HPI_ERROR_INVALID_CONTROL_VALUE; + + if (hr.error) { + err = hr.error; + break; + } + for (j = 0; j < 8; j++) { + c = hr.u.cu.chars8.sz_data[j]; + psz_string[sub_string_index + j] = c; + n++; + if (n >= string_length) { + psz_string[string_length - 1] = 0; + err = HPI_ERROR_INVALID_CONTROL_VALUE; + break; + } + if (c == 0) + break; + } + + if ((hr.u.cu.chars8.remaining_chars == 0) + && ((sub_string_index + j) < string_length) + && (c != 0)) { + c = 0; + psz_string[sub_string_index + j] = c; + } + if (c == 0) + break; + } + return err; +} + +u16 hpi_aesebu_receiver_query_format(const u32 h_aes_rx, const u32 index, + u16 *pw_format) +{ + u32 qr; + u16 err; + + err = hpi_control_query(h_aes_rx, HPI_AESEBURX_FORMAT, index, 0, &qr); + *pw_format = (u16)qr; + return err; +} + +u16 hpi_aesebu_receiver_set_format(u32 h_control, u16 format) +{ + return hpi_control_param_set(h_control, HPI_AESEBURX_FORMAT, format, + 0); +} + +u16 hpi_aesebu_receiver_get_format(u32 h_control, u16 *pw_format) +{ + u16 err; + u32 param; + + err = hpi_control_param1_get(h_control, HPI_AESEBURX_FORMAT, ¶m); + if (!err && pw_format) + *pw_format = (u16)param; + + return err; +} + +u16 hpi_aesebu_receiver_get_sample_rate(u32 h_control, u32 *psample_rate) +{ + return hpi_control_param1_get(h_control, HPI_AESEBURX_SAMPLERATE, + psample_rate); +} + +u16 hpi_aesebu_receiver_get_user_data(u32 h_control, u16 index, u16 *pw_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_AESEBURX_USERDATA; + hm.u.c.param1 = index; + + hpi_send_recv(&hm, &hr); + + if (pw_data) + *pw_data = (u16)hr.u.c.param2; + return hr.error; +} + +u16 hpi_aesebu_receiver_get_channel_status(u32 h_control, u16 index, + u16 *pw_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_AESEBURX_CHANNELSTATUS; + hm.u.c.param1 = index; + + hpi_send_recv(&hm, &hr); + + if (pw_data) + *pw_data = (u16)hr.u.c.param2; + return hr.error; +} + +u16 hpi_aesebu_receiver_get_error_status(u32 h_control, u16 *pw_error_data) +{ + u32 error_data = 0; + u16 err = 0; + + err = hpi_control_param1_get(h_control, HPI_AESEBURX_ERRORSTATUS, + &error_data); + if (pw_error_data) + *pw_error_data = (u16)error_data; + return err; +} + +u16 hpi_aesebu_transmitter_set_sample_rate(u32 h_control, u32 sample_rate) +{ + return hpi_control_param_set(h_control, HPI_AESEBUTX_SAMPLERATE, + sample_rate, 0); +} + +u16 hpi_aesebu_transmitter_set_user_data(u32 h_control, u16 index, u16 data) +{ + return hpi_control_param_set(h_control, HPI_AESEBUTX_USERDATA, index, + data); +} + +u16 hpi_aesebu_transmitter_set_channel_status(u32 h_control, u16 index, + u16 data) +{ + return hpi_control_param_set(h_control, HPI_AESEBUTX_CHANNELSTATUS, + index, data); +} + +u16 hpi_aesebu_transmitter_get_channel_status(u32 h_control, u16 index, + u16 *pw_data) +{ + return HPI_ERROR_INVALID_OPERATION; +} + +u16 hpi_aesebu_transmitter_query_format(const u32 h_aes_tx, const u32 index, + u16 *pw_format) +{ + u32 qr; + u16 err; + + err = hpi_control_query(h_aes_tx, HPI_AESEBUTX_FORMAT, index, 0, &qr); + *pw_format = (u16)qr; + return err; +} + +u16 hpi_aesebu_transmitter_set_format(u32 h_control, u16 output_format) +{ + return hpi_control_param_set(h_control, HPI_AESEBUTX_FORMAT, + output_format, 0); +} + +u16 hpi_aesebu_transmitter_get_format(u32 h_control, u16 *pw_output_format) +{ + u16 err; + u32 param; + + err = hpi_control_param1_get(h_control, HPI_AESEBUTX_FORMAT, ¶m); + if (!err && pw_output_format) + *pw_output_format = (u16)param; + + return err; +} + +u16 hpi_bitstream_set_clock_edge(u32 h_control, u16 edge_type) +{ + return hpi_control_param_set(h_control, HPI_BITSTREAM_CLOCK_EDGE, + edge_type, 0); +} + +u16 hpi_bitstream_set_data_polarity(u32 h_control, u16 polarity) +{ + return hpi_control_param_set(h_control, HPI_BITSTREAM_DATA_POLARITY, + polarity, 0); +} + +u16 hpi_bitstream_get_activity(u32 h_control, u16 *pw_clk_activity, + u16 *pw_data_activity) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_BITSTREAM_ACTIVITY; + hpi_send_recv(&hm, &hr); + if (pw_clk_activity) + *pw_clk_activity = (u16)hr.u.c.param1; + if (pw_data_activity) + *pw_data_activity = (u16)hr.u.c.param2; + return hr.error; +} + +u16 hpi_channel_mode_query_mode(const u32 h_mode, const u32 index, + u16 *pw_mode) +{ + u32 qr; + u16 err; + + err = hpi_control_query(h_mode, HPI_CHANNEL_MODE_MODE, index, 0, &qr); + *pw_mode = (u16)qr; + return err; +} + +u16 hpi_channel_mode_set(u32 h_control, u16 mode) +{ + return hpi_control_param_set(h_control, HPI_CHANNEL_MODE_MODE, mode, + 0); +} + +u16 hpi_channel_mode_get(u32 h_control, u16 *mode) +{ + u32 mode32 = 0; + u16 err = hpi_control_param1_get(h_control, + HPI_CHANNEL_MODE_MODE, &mode32); + if (mode) + *mode = (u16)mode32; + return err; +} + +u16 hpi_cobranet_hmi_write(u32 h_control, u32 hmi_address, u32 byte_count, + u8 *pb_data) +{ + struct hpi_msg_cobranet_hmiwrite hm; + struct hpi_response_header hr; + + hpi_init_message_responseV1(&hm.h, sizeof(hm), &hr, sizeof(hr), + HPI_OBJ_CONTROL, HPI_CONTROL_SET_STATE); + + if (hpi_handle_indexes(h_control, &hm.h.adapter_index, + &hm.h.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + if (byte_count > sizeof(hm.bytes)) + return HPI_ERROR_MESSAGE_BUFFER_TOO_SMALL; + + hm.p.attribute = HPI_COBRANET_SET; + hm.p.byte_count = byte_count; + hm.p.hmi_address = hmi_address; + memcpy(hm.bytes, pb_data, byte_count); + hm.h.size = (u16)(sizeof(hm.h) + sizeof(hm.p) + byte_count); + + hpi_send_recvV1(&hm.h, &hr); + return hr.error; +} + +u16 hpi_cobranet_hmi_read(u32 h_control, u32 hmi_address, u32 max_byte_count, + u32 *pbyte_count, u8 *pb_data) +{ + struct hpi_msg_cobranet_hmiread hm; + struct hpi_res_cobranet_hmiread hr; + + hpi_init_message_responseV1(&hm.h, sizeof(hm), &hr.h, sizeof(hr), + HPI_OBJ_CONTROL, HPI_CONTROL_GET_STATE); + + if (hpi_handle_indexes(h_control, &hm.h.adapter_index, + &hm.h.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + if (max_byte_count > sizeof(hr.bytes)) + return HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL; + + hm.p.attribute = HPI_COBRANET_GET; + hm.p.byte_count = max_byte_count; + hm.p.hmi_address = hmi_address; + + hpi_send_recvV1(&hm.h, &hr.h); + + if (!hr.h.error && pb_data) { + if (hr.byte_count > sizeof(hr.bytes)) + + return HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL; + + *pbyte_count = hr.byte_count; + + if (hr.byte_count < max_byte_count) + max_byte_count = *pbyte_count; + + memcpy(pb_data, hr.bytes, max_byte_count); + } + return hr.h.error; +} + +u16 hpi_cobranet_hmi_get_status(u32 h_control, u32 *pstatus, + u32 *preadable_size, u32 *pwriteable_size) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hm.u.c.attribute = HPI_COBRANET_GET_STATUS; + + hpi_send_recv(&hm, &hr); + if (!hr.error) { + if (pstatus) + *pstatus = hr.u.cu.cobranet.status.status; + if (preadable_size) + *preadable_size = + hr.u.cu.cobranet.status.readable_size; + if (pwriteable_size) + *pwriteable_size = + hr.u.cu.cobranet.status.writeable_size; + } + return hr.error; +} + +u16 hpi_cobranet_get_ip_address(u32 h_control, u32 *pdw_ip_address) +{ + u32 byte_count; + u32 iP; + u16 err; + + err = hpi_cobranet_hmi_read(h_control, + HPI_COBRANET_HMI_cobra_ip_mon_currentIP, 4, &byte_count, + (u8 *)&iP); + + *pdw_ip_address = + ((iP & 0xff000000) >> 8) | ((iP & 0x00ff0000) << 8) | ((iP & + 0x0000ff00) >> 8) | ((iP & 0x000000ff) << 8); + + if (err) + *pdw_ip_address = 0; + + return err; + +} + +u16 hpi_cobranet_set_ip_address(u32 h_control, u32 dw_ip_address) +{ + u32 iP; + u16 err; + + iP = ((dw_ip_address & 0xff000000) >> 8) | ((dw_ip_address & + 0x00ff0000) << 8) | ((dw_ip_address & 0x0000ff00) >> + 8) | ((dw_ip_address & 0x000000ff) << 8); + + err = hpi_cobranet_hmi_write(h_control, + HPI_COBRANET_HMI_cobra_ip_mon_currentIP, 4, (u8 *)&iP); + + return err; + +} + +u16 hpi_cobranet_get_static_ip_address(u32 h_control, u32 *pdw_ip_address) +{ + u32 byte_count; + u32 iP; + u16 err; + err = hpi_cobranet_hmi_read(h_control, + HPI_COBRANET_HMI_cobra_ip_mon_staticIP, 4, &byte_count, + (u8 *)&iP); + + *pdw_ip_address = + ((iP & 0xff000000) >> 8) | ((iP & 0x00ff0000) << 8) | ((iP & + 0x0000ff00) >> 8) | ((iP & 0x000000ff) << 8); + + if (err) + *pdw_ip_address = 0; + + return err; + +} + +u16 hpi_cobranet_set_static_ip_address(u32 h_control, u32 dw_ip_address) +{ + u32 iP; + u16 err; + + iP = ((dw_ip_address & 0xff000000) >> 8) | ((dw_ip_address & + 0x00ff0000) << 8) | ((dw_ip_address & 0x0000ff00) >> + 8) | ((dw_ip_address & 0x000000ff) << 8); + + err = hpi_cobranet_hmi_write(h_control, + HPI_COBRANET_HMI_cobra_ip_mon_staticIP, 4, (u8 *)&iP); + + return err; + +} + +u16 hpi_cobranet_get_macaddress(u32 h_control, u32 *p_mac_msbs, + u32 *p_mac_lsbs) +{ + u32 byte_count; + u16 err; + u32 mac; + + err = hpi_cobranet_hmi_read(h_control, + HPI_COBRANET_HMI_cobra_if_phy_address, 4, &byte_count, + (u8 *)&mac); + + if (!err) { + *p_mac_msbs = + ((mac & 0xff000000) >> 8) | ((mac & 0x00ff0000) << 8) + | ((mac & 0x0000ff00) >> 8) | ((mac & 0x000000ff) << + 8); + + err = hpi_cobranet_hmi_read(h_control, + HPI_COBRANET_HMI_cobra_if_phy_address + 1, 4, + &byte_count, (u8 *)&mac); + } + + if (!err) { + *p_mac_lsbs = + ((mac & 0xff000000) >> 8) | ((mac & 0x00ff0000) << 8) + | ((mac & 0x0000ff00) >> 8) | ((mac & 0x000000ff) << + 8); + } else { + *p_mac_msbs = 0; + *p_mac_lsbs = 0; + } + + return err; +} + +u16 hpi_compander_set_enable(u32 h_control, u32 enable) +{ + return hpi_control_param_set(h_control, HPI_GENERIC_ENABLE, enable, + 0); +} + +u16 hpi_compander_get_enable(u32 h_control, u32 *enable) +{ + return hpi_control_param1_get(h_control, HPI_GENERIC_ENABLE, enable); +} + +u16 hpi_compander_set_makeup_gain(u32 h_control, short makeup_gain0_01dB) +{ + return hpi_control_log_set2(h_control, HPI_COMPANDER_MAKEUPGAIN, + makeup_gain0_01dB, 0); +} + +u16 hpi_compander_get_makeup_gain(u32 h_control, short *makeup_gain0_01dB) +{ + return hpi_control_log_get2(h_control, HPI_COMPANDER_MAKEUPGAIN, + makeup_gain0_01dB, NULL); +} + +u16 hpi_compander_set_attack_time_constant(u32 h_control, unsigned int index, + u32 attack) +{ + return hpi_control_param_set(h_control, HPI_COMPANDER_ATTACK, attack, + index); +} + +u16 hpi_compander_get_attack_time_constant(u32 h_control, unsigned int index, + u32 *attack) +{ + return hpi_control_param_get(h_control, HPI_COMPANDER_ATTACK, 0, + index, attack, NULL); +} + +u16 hpi_compander_set_decay_time_constant(u32 h_control, unsigned int index, + u32 decay) +{ + return hpi_control_param_set(h_control, HPI_COMPANDER_DECAY, decay, + index); +} + +u16 hpi_compander_get_decay_time_constant(u32 h_control, unsigned int index, + u32 *decay) +{ + return hpi_control_param_get(h_control, HPI_COMPANDER_DECAY, 0, index, + decay, NULL); + +} + +u16 hpi_compander_set_threshold(u32 h_control, unsigned int index, + short threshold0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_COMPANDER_THRESHOLD; + hm.u.c.param2 = index; + hm.u.c.an_log_value[0] = threshold0_01dB; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_compander_get_threshold(u32 h_control, unsigned int index, + short *threshold0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_COMPANDER_THRESHOLD; + hm.u.c.param2 = index; + + hpi_send_recv(&hm, &hr); + *threshold0_01dB = hr.u.c.an_log_value[0]; + + return hr.error; +} + +u16 hpi_compander_set_ratio(u32 h_control, u32 index, u32 ratio100) +{ + return hpi_control_param_set(h_control, HPI_COMPANDER_RATIO, ratio100, + index); +} + +u16 hpi_compander_get_ratio(u32 h_control, u32 index, u32 *ratio100) +{ + return hpi_control_param_get(h_control, HPI_COMPANDER_RATIO, 0, index, + ratio100, NULL); +} + +u16 hpi_level_query_range(u32 h_control, short *min_gain_01dB, + short *max_gain_01dB, short *step_gain_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_LEVEL_RANGE; + + hpi_send_recv(&hm, &hr); + if (hr.error) { + hr.u.c.an_log_value[0] = 0; + hr.u.c.an_log_value[1] = 0; + hr.u.c.param1 = 0; + } + if (min_gain_01dB) + *min_gain_01dB = hr.u.c.an_log_value[0]; + if (max_gain_01dB) + *max_gain_01dB = hr.u.c.an_log_value[1]; + if (step_gain_01dB) + *step_gain_01dB = (short)hr.u.c.param1; + return hr.error; +} + +u16 hpi_level_set_gain(u32 h_control, short an_gain0_01dB[HPI_MAX_CHANNELS] + ) +{ + return hpi_control_log_set2(h_control, HPI_LEVEL_GAIN, + an_gain0_01dB[0], an_gain0_01dB[1]); +} + +u16 hpi_level_get_gain(u32 h_control, short an_gain0_01dB[HPI_MAX_CHANNELS] + ) +{ + return hpi_control_log_get2(h_control, HPI_LEVEL_GAIN, + &an_gain0_01dB[0], &an_gain0_01dB[1]); +} + +u16 hpi_meter_query_channels(const u32 h_meter, u32 *p_channels) +{ + return hpi_control_query(h_meter, HPI_METER_NUM_CHANNELS, 0, 0, + p_channels); +} + +u16 hpi_meter_get_peak(u32 h_control, short an_peakdB[HPI_MAX_CHANNELS] + ) +{ + short i = 0; + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_METER_PEAK; + + hpi_send_recv(&hm, &hr); + + if (!hr.error) + memcpy(an_peakdB, hr.u.c.an_log_value, + sizeof(short) * HPI_MAX_CHANNELS); + else + for (i = 0; i < HPI_MAX_CHANNELS; i++) + an_peakdB[i] = HPI_METER_MINIMUM; + return hr.error; +} + +u16 hpi_meter_get_rms(u32 h_control, short an_rmsdB[HPI_MAX_CHANNELS] + ) +{ + short i = 0; + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_METER_RMS; + + hpi_send_recv(&hm, &hr); + + if (!hr.error) + memcpy(an_rmsdB, hr.u.c.an_log_value, + sizeof(short) * HPI_MAX_CHANNELS); + else + for (i = 0; i < HPI_MAX_CHANNELS; i++) + an_rmsdB[i] = HPI_METER_MINIMUM; + + return hr.error; +} + +u16 hpi_meter_set_rms_ballistics(u32 h_control, u16 attack, u16 decay) +{ + return hpi_control_param_set(h_control, HPI_METER_RMS_BALLISTICS, + attack, decay); +} + +u16 hpi_meter_get_rms_ballistics(u32 h_control, u16 *pn_attack, u16 *pn_decay) +{ + u32 attack; + u32 decay; + u16 error; + + error = hpi_control_param2_get(h_control, HPI_METER_RMS_BALLISTICS, + &attack, &decay); + + if (pn_attack) + *pn_attack = (unsigned short)attack; + if (pn_decay) + *pn_decay = (unsigned short)decay; + + return error; +} + +u16 hpi_meter_set_peak_ballistics(u32 h_control, u16 attack, u16 decay) +{ + return hpi_control_param_set(h_control, HPI_METER_PEAK_BALLISTICS, + attack, decay); +} + +u16 hpi_meter_get_peak_ballistics(u32 h_control, u16 *pn_attack, + u16 *pn_decay) +{ + u32 attack; + u32 decay; + u16 error; + + error = hpi_control_param2_get(h_control, HPI_METER_PEAK_BALLISTICS, + &attack, &decay); + + if (pn_attack) + *pn_attack = (short)attack; + if (pn_decay) + *pn_decay = (short)decay; + + return error; +} + +u16 hpi_microphone_set_phantom_power(u32 h_control, u16 on_off) +{ + return hpi_control_param_set(h_control, HPI_MICROPHONE_PHANTOM_POWER, + (u32)on_off, 0); +} + +u16 hpi_microphone_get_phantom_power(u32 h_control, u16 *pw_on_off) +{ + u16 error = 0; + u32 on_off = 0; + error = hpi_control_param1_get(h_control, + HPI_MICROPHONE_PHANTOM_POWER, &on_off); + if (pw_on_off) + *pw_on_off = (u16)on_off; + return error; +} + +u16 hpi_multiplexer_set_source(u32 h_control, u16 source_node_type, + u16 source_node_index) +{ + return hpi_control_param_set(h_control, HPI_MULTIPLEXER_SOURCE, + source_node_type, source_node_index); +} + +u16 hpi_multiplexer_get_source(u32 h_control, u16 *source_node_type, + u16 *source_node_index) +{ + u32 node, index; + u16 err = hpi_control_param2_get(h_control, + HPI_MULTIPLEXER_SOURCE, &node, + &index); + if (source_node_type) + *source_node_type = (u16)node; + if (source_node_index) + *source_node_index = (u16)index; + return err; +} + +u16 hpi_multiplexer_query_source(u32 h_control, u16 index, + u16 *source_node_type, u16 *source_node_index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_MULTIPLEXER_QUERYSOURCE; + hm.u.c.param1 = index; + + hpi_send_recv(&hm, &hr); + + if (source_node_type) + *source_node_type = (u16)hr.u.c.param1; + if (source_node_index) + *source_node_index = (u16)hr.u.c.param2; + return hr.error; +} + +u16 hpi_parametric_eq_get_info(u32 h_control, u16 *pw_number_of_bands, + u16 *pw_on_off) +{ + u32 oB = 0; + u32 oO = 0; + u16 error = 0; + + error = hpi_control_param2_get(h_control, HPI_EQUALIZER_NUM_FILTERS, + &oO, &oB); + if (pw_number_of_bands) + *pw_number_of_bands = (u16)oB; + if (pw_on_off) + *pw_on_off = (u16)oO; + return error; +} + +u16 hpi_parametric_eq_set_state(u32 h_control, u16 on_off) +{ + return hpi_control_param_set(h_control, HPI_EQUALIZER_NUM_FILTERS, + on_off, 0); +} + +u16 hpi_parametric_eq_get_band(u32 h_control, u16 index, u16 *pn_type, + u32 *pfrequency_hz, short *pnQ100, short *pn_gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_EQUALIZER_FILTER; + hm.u.c.param2 = index; + + hpi_send_recv(&hm, &hr); + + if (pfrequency_hz) + *pfrequency_hz = hr.u.c.param1; + if (pn_type) + *pn_type = (u16)(hr.u.c.param2 >> 16); + if (pnQ100) + *pnQ100 = hr.u.c.an_log_value[1]; + if (pn_gain0_01dB) + *pn_gain0_01dB = hr.u.c.an_log_value[0]; + + return hr.error; +} + +u16 hpi_parametric_eq_set_band(u32 h_control, u16 index, u16 type, + u32 frequency_hz, short q100, short gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hm.u.c.param1 = frequency_hz; + hm.u.c.param2 = (index & 0xFFFFL) + ((u32)type << 16); + hm.u.c.an_log_value[0] = gain0_01dB; + hm.u.c.an_log_value[1] = q100; + hm.u.c.attribute = HPI_EQUALIZER_FILTER; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_parametric_eq_get_coeffs(u32 h_control, u16 index, short coeffs[5] + ) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_EQUALIZER_COEFFICIENTS; + hm.u.c.param2 = index; + + hpi_send_recv(&hm, &hr); + + coeffs[0] = (short)hr.u.c.an_log_value[0]; + coeffs[1] = (short)hr.u.c.an_log_value[1]; + coeffs[2] = (short)hr.u.c.param1; + coeffs[3] = (short)(hr.u.c.param1 >> 16); + coeffs[4] = (short)hr.u.c.param2; + + return hr.error; +} + +u16 hpi_sample_clock_query_source(const u32 h_clock, const u32 index, + u16 *pw_source) +{ + u32 qr; + u16 err; + + err = hpi_control_query(h_clock, HPI_SAMPLECLOCK_SOURCE, index, 0, + &qr); + *pw_source = (u16)qr; + return err; +} + +u16 hpi_sample_clock_set_source(u32 h_control, u16 source) +{ + return hpi_control_param_set(h_control, HPI_SAMPLECLOCK_SOURCE, + source, 0); +} + +u16 hpi_sample_clock_get_source(u32 h_control, u16 *pw_source) +{ + u16 err = 0; + u32 source = 0; + err = hpi_control_param1_get(h_control, HPI_SAMPLECLOCK_SOURCE, + &source); + if (!err) + if (pw_source) + *pw_source = (u16)source; + return err; +} + +u16 hpi_sample_clock_query_source_index(const u32 h_clock, const u32 index, + const u32 source, u16 *pw_source_index) +{ + u32 qr; + u16 err; + + err = hpi_control_query(h_clock, HPI_SAMPLECLOCK_SOURCE_INDEX, index, + source, &qr); + *pw_source_index = (u16)qr; + return err; +} + +u16 hpi_sample_clock_set_source_index(u32 h_control, u16 source_index) +{ + return hpi_control_param_set(h_control, HPI_SAMPLECLOCK_SOURCE_INDEX, + source_index, 0); +} + +u16 hpi_sample_clock_get_source_index(u32 h_control, u16 *pw_source_index) +{ + u16 err = 0; + u32 source_index = 0; + err = hpi_control_param1_get(h_control, HPI_SAMPLECLOCK_SOURCE_INDEX, + &source_index); + if (!err) + if (pw_source_index) + *pw_source_index = (u16)source_index; + return err; +} + +u16 hpi_sample_clock_query_local_rate(const u32 h_clock, const u32 index, + u32 *prate) +{ + return hpi_control_query(h_clock, HPI_SAMPLECLOCK_LOCAL_SAMPLERATE, + index, 0, prate); +} + +u16 hpi_sample_clock_set_local_rate(u32 h_control, u32 sample_rate) +{ + return hpi_control_param_set(h_control, + HPI_SAMPLECLOCK_LOCAL_SAMPLERATE, sample_rate, 0); +} + +u16 hpi_sample_clock_get_local_rate(u32 h_control, u32 *psample_rate) +{ + u16 err = 0; + u32 sample_rate = 0; + err = hpi_control_param1_get(h_control, + HPI_SAMPLECLOCK_LOCAL_SAMPLERATE, &sample_rate); + if (!err) + if (psample_rate) + *psample_rate = sample_rate; + return err; +} + +u16 hpi_sample_clock_get_sample_rate(u32 h_control, u32 *psample_rate) +{ + u16 err = 0; + u32 sample_rate = 0; + err = hpi_control_param1_get(h_control, HPI_SAMPLECLOCK_SAMPLERATE, + &sample_rate); + if (!err) + if (psample_rate) + *psample_rate = sample_rate; + return err; +} + +u16 hpi_sample_clock_set_auto(u32 h_control, u32 enable) +{ + return hpi_control_param_set(h_control, HPI_SAMPLECLOCK_AUTO, enable, + 0); +} + +u16 hpi_sample_clock_get_auto(u32 h_control, u32 *penable) +{ + return hpi_control_param1_get(h_control, HPI_SAMPLECLOCK_AUTO, + penable); +} + +u16 hpi_sample_clock_set_local_rate_lock(u32 h_control, u32 lock) +{ + return hpi_control_param_set(h_control, HPI_SAMPLECLOCK_LOCAL_LOCK, + lock, 0); +} + +u16 hpi_sample_clock_get_local_rate_lock(u32 h_control, u32 *plock) +{ + return hpi_control_param1_get(h_control, HPI_SAMPLECLOCK_LOCAL_LOCK, + plock); +} + +u16 hpi_tone_detector_get_frequency(u32 h_control, u32 index, u32 *frequency) +{ + return hpi_control_param_get(h_control, HPI_TONEDETECTOR_FREQUENCY, + index, 0, frequency, NULL); +} + +u16 hpi_tone_detector_get_state(u32 h_control, u32 *state) +{ + return hpi_control_param1_get(h_control, HPI_TONEDETECTOR_STATE, + state); +} + +u16 hpi_tone_detector_set_enable(u32 h_control, u32 enable) +{ + return hpi_control_param_set(h_control, HPI_GENERIC_ENABLE, enable, + 0); +} + +u16 hpi_tone_detector_get_enable(u32 h_control, u32 *enable) +{ + return hpi_control_param1_get(h_control, HPI_GENERIC_ENABLE, enable); +} + +u16 hpi_tone_detector_set_event_enable(u32 h_control, u32 event_enable) +{ + return hpi_control_param_set(h_control, HPI_GENERIC_EVENT_ENABLE, + (u32)event_enable, 0); +} + +u16 hpi_tone_detector_get_event_enable(u32 h_control, u32 *event_enable) +{ + return hpi_control_param1_get(h_control, HPI_GENERIC_EVENT_ENABLE, + event_enable); +} + +u16 hpi_tone_detector_set_threshold(u32 h_control, int threshold) +{ + return hpi_control_param_set(h_control, HPI_TONEDETECTOR_THRESHOLD, + (u32)threshold, 0); +} + +u16 hpi_tone_detector_get_threshold(u32 h_control, int *threshold) +{ + return hpi_control_param1_get(h_control, HPI_TONEDETECTOR_THRESHOLD, + (u32 *)threshold); +} + +u16 hpi_silence_detector_get_state(u32 h_control, u32 *state) +{ + return hpi_control_param1_get(h_control, HPI_SILENCEDETECTOR_STATE, + state); +} + +u16 hpi_silence_detector_set_enable(u32 h_control, u32 enable) +{ + return hpi_control_param_set(h_control, HPI_GENERIC_ENABLE, enable, + 0); +} + +u16 hpi_silence_detector_get_enable(u32 h_control, u32 *enable) +{ + return hpi_control_param1_get(h_control, HPI_GENERIC_ENABLE, enable); +} + +u16 hpi_silence_detector_set_event_enable(u32 h_control, u32 event_enable) +{ + return hpi_control_param_set(h_control, HPI_GENERIC_EVENT_ENABLE, + event_enable, 0); +} + +u16 hpi_silence_detector_get_event_enable(u32 h_control, u32 *event_enable) +{ + return hpi_control_param1_get(h_control, HPI_GENERIC_EVENT_ENABLE, + event_enable); +} + +u16 hpi_silence_detector_set_delay(u32 h_control, u32 delay) +{ + return hpi_control_param_set(h_control, HPI_SILENCEDETECTOR_DELAY, + delay, 0); +} + +u16 hpi_silence_detector_get_delay(u32 h_control, u32 *delay) +{ + return hpi_control_param1_get(h_control, HPI_SILENCEDETECTOR_DELAY, + delay); +} + +u16 hpi_silence_detector_set_threshold(u32 h_control, int threshold) +{ + return hpi_control_param_set(h_control, HPI_SILENCEDETECTOR_THRESHOLD, + threshold, 0); +} + +u16 hpi_silence_detector_get_threshold(u32 h_control, int *threshold) +{ + return hpi_control_param1_get(h_control, + HPI_SILENCEDETECTOR_THRESHOLD, (u32 *)threshold); +} + +u16 hpi_tuner_query_band(const u32 h_tuner, const u32 index, u16 *pw_band) +{ + u32 qr; + u16 err; + + err = hpi_control_query(h_tuner, HPI_TUNER_BAND, index, 0, &qr); + *pw_band = (u16)qr; + return err; +} + +u16 hpi_tuner_set_band(u32 h_control, u16 band) +{ + return hpi_control_param_set(h_control, HPI_TUNER_BAND, band, 0); +} + +u16 hpi_tuner_get_band(u32 h_control, u16 *pw_band) +{ + u32 band = 0; + u16 error = 0; + + error = hpi_control_param1_get(h_control, HPI_TUNER_BAND, &band); + if (pw_band) + *pw_band = (u16)band; + return error; +} + +u16 hpi_tuner_query_frequency(const u32 h_tuner, const u32 index, + const u16 band, u32 *pfreq) +{ + return hpi_control_query(h_tuner, HPI_TUNER_FREQ, index, band, pfreq); +} + +u16 hpi_tuner_set_frequency(u32 h_control, u32 freq_ink_hz) +{ + return hpi_control_param_set(h_control, HPI_TUNER_FREQ, freq_ink_hz, + 0); +} + +u16 hpi_tuner_get_frequency(u32 h_control, u32 *pw_freq_ink_hz) +{ + return hpi_control_param1_get(h_control, HPI_TUNER_FREQ, + pw_freq_ink_hz); +} + +u16 hpi_tuner_query_gain(const u32 h_tuner, const u32 index, u16 *pw_gain) +{ + u32 qr; + u16 err; + + err = hpi_control_query(h_tuner, HPI_TUNER_BAND, index, 0, &qr); + *pw_gain = (u16)qr; + return err; +} + +u16 hpi_tuner_set_gain(u32 h_control, short gain) +{ + return hpi_control_param_set(h_control, HPI_TUNER_GAIN, gain, 0); +} + +u16 hpi_tuner_get_gain(u32 h_control, short *pn_gain) +{ + u32 gain = 0; + u16 error = 0; + + error = hpi_control_param1_get(h_control, HPI_TUNER_GAIN, &gain); + if (pn_gain) + *pn_gain = (u16)gain; + return error; +} + +u16 hpi_tuner_get_rf_level(u32 h_control, short *pw_level) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.cu.attribute = HPI_TUNER_LEVEL_AVG; + hpi_send_recv(&hm, &hr); + if (pw_level) + *pw_level = hr.u.cu.tuner.s_level; + return hr.error; +} + +u16 hpi_tuner_get_raw_rf_level(u32 h_control, short *pw_level) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.cu.attribute = HPI_TUNER_LEVEL_RAW; + hpi_send_recv(&hm, &hr); + if (pw_level) + *pw_level = hr.u.cu.tuner.s_level; + return hr.error; +} + +u16 hpi_tuner_query_deemphasis(const u32 h_tuner, const u32 index, + const u16 band, u32 *pdeemphasis) +{ + return hpi_control_query(h_tuner, HPI_TUNER_DEEMPHASIS, index, band, + pdeemphasis); +} + +u16 hpi_tuner_set_deemphasis(u32 h_control, u32 deemphasis) +{ + return hpi_control_param_set(h_control, HPI_TUNER_DEEMPHASIS, + deemphasis, 0); +} + +u16 hpi_tuner_get_deemphasis(u32 h_control, u32 *pdeemphasis) +{ + return hpi_control_param1_get(h_control, HPI_TUNER_DEEMPHASIS, + pdeemphasis); +} + +u16 hpi_tuner_query_program(const u32 h_tuner, u32 *pbitmap_program) +{ + return hpi_control_query(h_tuner, HPI_TUNER_PROGRAM, 0, 0, + pbitmap_program); +} + +u16 hpi_tuner_set_program(u32 h_control, u32 program) +{ + return hpi_control_param_set(h_control, HPI_TUNER_PROGRAM, program, + 0); +} + +u16 hpi_tuner_get_program(u32 h_control, u32 *pprogram) +{ + return hpi_control_param1_get(h_control, HPI_TUNER_PROGRAM, pprogram); +} + +u16 hpi_tuner_get_hd_radio_dsp_version(u32 h_control, char *psz_dsp_version, + const u32 string_size) +{ + return hpi_control_get_string(h_control, + HPI_TUNER_HDRADIO_DSP_VERSION, psz_dsp_version, string_size); +} + +u16 hpi_tuner_get_hd_radio_sdk_version(u32 h_control, char *psz_sdk_version, + const u32 string_size) +{ + return hpi_control_get_string(h_control, + HPI_TUNER_HDRADIO_SDK_VERSION, psz_sdk_version, string_size); +} + +u16 hpi_tuner_get_status(u32 h_control, u16 *pw_status_mask, u16 *pw_status) +{ + u32 status = 0; + u16 error = 0; + + error = hpi_control_param1_get(h_control, HPI_TUNER_STATUS, &status); + if (pw_status) { + if (!error) { + *pw_status_mask = (u16)(status >> 16); + *pw_status = (u16)(status & 0xFFFF); + } else { + *pw_status_mask = 0; + *pw_status = 0; + } + } + return error; +} + +u16 hpi_tuner_set_mode(u32 h_control, u32 mode, u32 value) +{ + return hpi_control_param_set(h_control, HPI_TUNER_MODE, mode, value); +} + +u16 hpi_tuner_get_mode(u32 h_control, u32 mode, u32 *pn_value) +{ + return hpi_control_param_get(h_control, HPI_TUNER_MODE, mode, 0, + pn_value, NULL); +} + +u16 hpi_tuner_get_hd_radio_signal_quality(u32 h_control, u32 *pquality) +{ + return hpi_control_param1_get(h_control, + HPI_TUNER_HDRADIO_SIGNAL_QUALITY, pquality); +} + +u16 hpi_tuner_get_hd_radio_signal_blend(u32 h_control, u32 *pblend) +{ + return hpi_control_param1_get(h_control, HPI_TUNER_HDRADIO_BLEND, + pblend); +} + +u16 hpi_tuner_set_hd_radio_signal_blend(u32 h_control, const u32 blend) +{ + return hpi_control_param_set(h_control, HPI_TUNER_HDRADIO_BLEND, + blend, 0); +} + +u16 hpi_tuner_get_rds(u32 h_control, char *p_data) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_TUNER_RDS; + hpi_send_recv(&hm, &hr); + if (p_data) { + *(u32 *)&p_data[0] = hr.u.cu.tuner.rds.data[0]; + *(u32 *)&p_data[4] = hr.u.cu.tuner.rds.data[1]; + *(u32 *)&p_data[8] = hr.u.cu.tuner.rds.bLER; + } + return hr.error; +} + +u16 hpi_pad_get_channel_name(u32 h_control, char *psz_string, + const u32 data_length) +{ + return hpi_control_get_string(h_control, HPI_PAD_CHANNEL_NAME, + psz_string, data_length); +} + +u16 hpi_pad_get_artist(u32 h_control, char *psz_string, const u32 data_length) +{ + return hpi_control_get_string(h_control, HPI_PAD_ARTIST, psz_string, + data_length); +} + +u16 hpi_pad_get_title(u32 h_control, char *psz_string, const u32 data_length) +{ + return hpi_control_get_string(h_control, HPI_PAD_TITLE, psz_string, + data_length); +} + +u16 hpi_pad_get_comment(u32 h_control, char *psz_string, + const u32 data_length) +{ + return hpi_control_get_string(h_control, HPI_PAD_COMMENT, psz_string, + data_length); +} + +u16 hpi_pad_get_program_type(u32 h_control, u32 *ppTY) +{ + return hpi_control_param1_get(h_control, HPI_PAD_PROGRAM_TYPE, ppTY); +} + +u16 hpi_pad_get_rdsPI(u32 h_control, u32 *ppI) +{ + return hpi_control_param1_get(h_control, HPI_PAD_PROGRAM_ID, ppI); +} + +u16 hpi_volume_query_channels(const u32 h_volume, u32 *p_channels) +{ + return hpi_control_query(h_volume, HPI_VOLUME_NUM_CHANNELS, 0, 0, + p_channels); +} + +u16 hpi_volume_set_gain(u32 h_control, short an_log_gain[HPI_MAX_CHANNELS] + ) +{ + return hpi_control_log_set2(h_control, HPI_VOLUME_GAIN, + an_log_gain[0], an_log_gain[1]); +} + +u16 hpi_volume_get_gain(u32 h_control, short an_log_gain[HPI_MAX_CHANNELS] + ) +{ + return hpi_control_log_get2(h_control, HPI_VOLUME_GAIN, + &an_log_gain[0], &an_log_gain[1]); +} + +u16 hpi_volume_set_mute(u32 h_control, u32 mute) +{ + return hpi_control_param_set(h_control, HPI_VOLUME_MUTE, mute, 0); +} + +u16 hpi_volume_get_mute(u32 h_control, u32 *mute) +{ + return hpi_control_param1_get(h_control, HPI_VOLUME_MUTE, mute); +} + +u16 hpi_volume_query_range(u32 h_control, short *min_gain_01dB, + short *max_gain_01dB, short *step_gain_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_VOLUME_RANGE; + + hpi_send_recv(&hm, &hr); + if (hr.error) { + hr.u.c.an_log_value[0] = 0; + hr.u.c.an_log_value[1] = 0; + hr.u.c.param1 = 0; + } + if (min_gain_01dB) + *min_gain_01dB = hr.u.c.an_log_value[0]; + if (max_gain_01dB) + *max_gain_01dB = hr.u.c.an_log_value[1]; + if (step_gain_01dB) + *step_gain_01dB = (short)hr.u.c.param1; + return hr.error; +} + +u16 hpi_volume_auto_fade_profile(u32 h_control, + short an_stop_gain0_01dB[HPI_MAX_CHANNELS], u32 duration_ms, + u16 profile) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + memcpy(hm.u.c.an_log_value, an_stop_gain0_01dB, + sizeof(short) * HPI_MAX_CHANNELS); + + hm.u.c.attribute = HPI_VOLUME_AUTOFADE; + hm.u.c.param1 = duration_ms; + hm.u.c.param2 = profile; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_volume_auto_fade(u32 h_control, + short an_stop_gain0_01dB[HPI_MAX_CHANNELS], u32 duration_ms) +{ + return hpi_volume_auto_fade_profile(h_control, an_stop_gain0_01dB, + duration_ms, HPI_VOLUME_AUTOFADE_LOG); +} + +u16 hpi_volume_query_auto_fade_profile(const u32 h_volume, const u32 i, + u16 *profile) +{ + u16 e; + u32 u; + e = hpi_control_query(h_volume, HPI_VOLUME_AUTOFADE, i, 0, &u); + *profile = (u16)u; + return e; +} + +u16 hpi_vox_set_threshold(u32 h_control, short an_gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_VOX_THRESHOLD; + + hm.u.c.an_log_value[0] = an_gain0_01dB; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_vox_get_threshold(u32 h_control, short *an_gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_VOX_THRESHOLD; + + hpi_send_recv(&hm, &hr); + + *an_gain0_01dB = hr.u.c.an_log_value[0]; + + return hr.error; +} diff --git a/sound/pci/asihpi/hpimsginit.c b/sound/pci/asihpi/hpimsginit.c new file mode 100644 index 000000000..a3fdcbf49 --- /dev/null +++ b/sound/pci/asihpi/hpimsginit.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + + Hardware Programming Interface (HPI) Utility functions. + + (C) Copyright AudioScience Inc. 2007 +*******************************************************************************/ + +#include "hpi_internal.h" +#include "hpimsginit.h" +#include + +/* The actual message size for each object type */ +static u16 msg_size[HPI_OBJ_MAXINDEX + 1] = HPI_MESSAGE_SIZE_BY_OBJECT; +/* The actual response size for each object type */ +static u16 res_size[HPI_OBJ_MAXINDEX + 1] = HPI_RESPONSE_SIZE_BY_OBJECT; +/* Flag to enable alternate message type for SSX2 bypass. */ +static u16 gwSSX2_bypass; + +/** \internal + * initialize the HPI message structure + */ +static void hpi_init_message(struct hpi_message *phm, u16 object, + u16 function) +{ + u16 size; + + if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) { + object = array_index_nospec(object, HPI_OBJ_MAXINDEX + 1); + size = msg_size[object]; + } else { + size = sizeof(*phm); + } + + memset(phm, 0, size); + phm->size = size; + + if (gwSSX2_bypass) + phm->type = HPI_TYPE_SSX2BYPASS_MESSAGE; + else + phm->type = HPI_TYPE_REQUEST; + phm->object = object; + phm->function = function; + phm->version = 0; + phm->adapter_index = HPI_ADAPTER_INDEX_INVALID; + /* Expect actual adapter index to be set by caller */ +} + +/** \internal + * initialize the HPI response structure + */ +void hpi_init_response(struct hpi_response *phr, u16 object, u16 function, + u16 error) +{ + u16 size; + + if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) { + object = array_index_nospec(object, HPI_OBJ_MAXINDEX + 1); + size = res_size[object]; + } else { + size = sizeof(*phr); + } + + memset(phr, 0, sizeof(*phr)); + phr->size = size; + phr->type = HPI_TYPE_RESPONSE; + phr->object = object; + phr->function = function; + phr->error = error; + phr->specific_error = 0; + phr->version = 0; +} + +void hpi_init_message_response(struct hpi_message *phm, + struct hpi_response *phr, u16 object, u16 function) +{ + hpi_init_message(phm, object, function); + /* default error return if the response is + not filled in by the callee */ + hpi_init_response(phr, object, function, + HPI_ERROR_PROCESSING_MESSAGE); +} + +static void hpi_init_messageV1(struct hpi_message_header *phm, u16 size, + u16 object, u16 function) +{ + memset(phm, 0, size); + if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) { + phm->size = size; + phm->type = HPI_TYPE_REQUEST; + phm->object = object; + phm->function = function; + phm->version = 1; + /* Expect adapter index to be set by caller */ + } +} + +void hpi_init_responseV1(struct hpi_response_header *phr, u16 size, + u16 object, u16 function) +{ + (void)object; + (void)function; + memset(phr, 0, size); + phr->size = size; + phr->version = 1; + phr->type = HPI_TYPE_RESPONSE; + phr->error = HPI_ERROR_PROCESSING_MESSAGE; +} + +void hpi_init_message_responseV1(struct hpi_message_header *phm, u16 msg_size, + struct hpi_response_header *phr, u16 res_size, u16 object, + u16 function) +{ + hpi_init_messageV1(phm, msg_size, object, function); + hpi_init_responseV1(phr, res_size, object, function); +} diff --git a/sound/pci/asihpi/hpimsginit.h b/sound/pci/asihpi/hpimsginit.h new file mode 100644 index 000000000..c64126b30 --- /dev/null +++ b/sound/pci/asihpi/hpimsginit.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + + Hardware Programming Interface (HPI) Utility functions + + (C) Copyright AudioScience Inc. 2007 +*******************************************************************************/ +/* Initialise response headers, or msg/response pairs. +Note that it is valid to just init a response e.g. when a lower level is +preparing a response to a message. +However, when sending a message, a matching response buffer must always be +prepared. +*/ + +#ifndef _HPIMSGINIT_H_ +#define _HPIMSGINIT_H_ + +void hpi_init_response(struct hpi_response *phr, u16 object, u16 function, + u16 error); + +void hpi_init_message_response(struct hpi_message *phm, + struct hpi_response *phr, u16 object, u16 function); + +void hpi_init_responseV1(struct hpi_response_header *phr, u16 size, + u16 object, u16 function); + +void hpi_init_message_responseV1(struct hpi_message_header *phm, u16 msg_size, + struct hpi_response_header *phr, u16 res_size, u16 object, + u16 function); + +#endif /* _HPIMSGINIT_H_ */ diff --git a/sound/pci/asihpi/hpimsgx.c b/sound/pci/asihpi/hpimsgx.c new file mode 100644 index 000000000..ed1c7b774 --- /dev/null +++ b/sound/pci/asihpi/hpimsgx.c @@ -0,0 +1,795 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + +Extended Message Function With Response Caching + +(C) Copyright AudioScience Inc. 2002 +*****************************************************************************/ +#define SOURCEFILE_NAME "hpimsgx.c" +#include "hpi_internal.h" +#include "hpi_version.h" +#include "hpimsginit.h" +#include "hpicmn.h" +#include "hpimsgx.h" +#include "hpidebug.h" + +static const struct pci_device_id asihpi_pci_tbl[] = { +#include "hpipcida.h" +}; + +static struct hpios_spinlock msgx_lock; + +static hpi_handler_func *hpi_entry_points[HPI_MAX_ADAPTERS]; +static int logging_enabled = 1; + +static hpi_handler_func *hpi_lookup_entry_point_function(const struct hpi_pci + *pci_info) +{ + + int i; + + for (i = 0; asihpi_pci_tbl[i].vendor != 0; i++) { + if (asihpi_pci_tbl[i].vendor != PCI_ANY_ID + && asihpi_pci_tbl[i].vendor != + pci_info->pci_dev->vendor) + continue; + if (asihpi_pci_tbl[i].device != PCI_ANY_ID + && asihpi_pci_tbl[i].device != + pci_info->pci_dev->device) + continue; + if (asihpi_pci_tbl[i].subvendor != PCI_ANY_ID + && asihpi_pci_tbl[i].subvendor != + pci_info->pci_dev->subsystem_vendor) + continue; + if (asihpi_pci_tbl[i].subdevice != PCI_ANY_ID + && asihpi_pci_tbl[i].subdevice != + pci_info->pci_dev->subsystem_device) + continue; + + /* HPI_DEBUG_LOG(DEBUG, " %x,%lx\n", i, + asihpi_pci_tbl[i].driver_data); */ + return (hpi_handler_func *) asihpi_pci_tbl[i].driver_data; + } + + return NULL; +} + +static inline void hw_entry_point(struct hpi_message *phm, + struct hpi_response *phr) +{ + if ((phm->adapter_index < HPI_MAX_ADAPTERS) + && hpi_entry_points[phm->adapter_index]) + hpi_entry_points[phm->adapter_index] (phm, phr); + else + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_PROCESSING_MESSAGE); +} + +static void adapter_open(struct hpi_message *phm, struct hpi_response *phr); +static void adapter_close(struct hpi_message *phm, struct hpi_response *phr); + +static void mixer_open(struct hpi_message *phm, struct hpi_response *phr); +static void mixer_close(struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_open(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); +static void outstream_close(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); +static void instream_open(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); +static void instream_close(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); + +static void HPIMSGX__reset(u16 adapter_index); + +static u16 HPIMSGX__init(struct hpi_message *phm, struct hpi_response *phr); +static void HPIMSGX__cleanup(u16 adapter_index, void *h_owner); + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(push, 1) +#endif + +struct hpi_adapter_response { + struct hpi_response_header h; + struct hpi_adapter_res a; +}; + +struct hpi_mixer_response { + struct hpi_response_header h; + struct hpi_mixer_res m; +}; + +struct hpi_stream_response { + struct hpi_response_header h; + struct hpi_stream_res d; +}; + +struct adapter_info { + u16 type; + u16 num_instreams; + u16 num_outstreams; +}; + +struct asi_open_state { + int open_flag; + void *h_owner; +}; + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(pop) +#endif + +/* Globals */ +static struct hpi_adapter_response rESP_HPI_ADAPTER_OPEN[HPI_MAX_ADAPTERS]; + +static struct hpi_stream_response + rESP_HPI_OSTREAM_OPEN[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS]; + +static struct hpi_stream_response + rESP_HPI_ISTREAM_OPEN[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS]; + +static struct hpi_mixer_response rESP_HPI_MIXER_OPEN[HPI_MAX_ADAPTERS]; + +static struct adapter_info aDAPTER_INFO[HPI_MAX_ADAPTERS]; + +/* use these to keep track of opens from user mode apps/DLLs */ +static struct asi_open_state + outstream_user_open[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS]; + +static struct asi_open_state + instream_user_open[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS]; + +static void subsys_message(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + if (phm->adapter_index != HPI_ADAPTER_INDEX_INVALID) + HPI_DEBUG_LOG(WARNING, + "suspicious adapter index %d in subsys message 0x%x.\n", + phm->adapter_index, phm->function); + + switch (phm->function) { + case HPI_SUBSYS_GET_VERSION: + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_VERSION, 0); + phr->u.s.version = HPI_VER >> 8; /* return major.minor */ + phr->u.s.data = HPI_VER; /* return major.minor.release */ + break; + case HPI_SUBSYS_OPEN: + /*do not propagate the message down the chain */ + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_OPEN, 0); + break; + case HPI_SUBSYS_CLOSE: + /*do not propagate the message down the chain */ + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_CLOSE, + 0); + HPIMSGX__cleanup(HPIMSGX_ALLADAPTERS, h_owner); + break; + case HPI_SUBSYS_DRIVER_LOAD: + /* Initialize this module's internal state */ + hpios_msgxlock_init(&msgx_lock); + memset(&hpi_entry_points, 0, sizeof(hpi_entry_points)); + /* Init subsys_findadapters response to no-adapters */ + HPIMSGX__reset(HPIMSGX_ALLADAPTERS); + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DRIVER_LOAD, 0); + /* individual HPIs dont implement driver load */ + HPI_COMMON(phm, phr); + break; + case HPI_SUBSYS_DRIVER_UNLOAD: + HPI_COMMON(phm, phr); + HPIMSGX__cleanup(HPIMSGX_ALLADAPTERS, h_owner); + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DRIVER_UNLOAD, 0); + return; + + case HPI_SUBSYS_GET_NUM_ADAPTERS: + case HPI_SUBSYS_GET_ADAPTER: + HPI_COMMON(phm, phr); + break; + + case HPI_SUBSYS_CREATE_ADAPTER: + HPIMSGX__init(phm, phr); + break; + + default: + /* Must explicitly handle every subsys message in this switch */ + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, phm->function, + HPI_ERROR_INVALID_FUNC); + break; + } +} + +static void adapter_message(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + switch (phm->function) { + case HPI_ADAPTER_OPEN: + adapter_open(phm, phr); + break; + case HPI_ADAPTER_CLOSE: + adapter_close(phm, phr); + break; + case HPI_ADAPTER_DELETE: + HPIMSGX__cleanup(phm->adapter_index, h_owner); + { + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_CLOSE); + hm.adapter_index = phm->adapter_index; + hw_entry_point(&hm, &hr); + } + hw_entry_point(phm, phr); + break; + + default: + hw_entry_point(phm, phr); + break; + } +} + +static void mixer_message(struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + case HPI_MIXER_OPEN: + mixer_open(phm, phr); + break; + case HPI_MIXER_CLOSE: + mixer_close(phm, phr); + break; + default: + hw_entry_point(phm, phr); + break; + } +} + +static void outstream_message(struct hpi_message *phm, + struct hpi_response *phr, void *h_owner) +{ + if (phm->obj_index >= aDAPTER_INFO[phm->adapter_index].num_outstreams) { + hpi_init_response(phr, HPI_OBJ_OSTREAM, phm->function, + HPI_ERROR_INVALID_OBJ_INDEX); + return; + } + + switch (phm->function) { + case HPI_OSTREAM_OPEN: + outstream_open(phm, phr, h_owner); + break; + case HPI_OSTREAM_CLOSE: + outstream_close(phm, phr, h_owner); + break; + default: + hw_entry_point(phm, phr); + break; + } +} + +static void instream_message(struct hpi_message *phm, + struct hpi_response *phr, void *h_owner) +{ + if (phm->obj_index >= aDAPTER_INFO[phm->adapter_index].num_instreams) { + hpi_init_response(phr, HPI_OBJ_ISTREAM, phm->function, + HPI_ERROR_INVALID_OBJ_INDEX); + return; + } + + switch (phm->function) { + case HPI_ISTREAM_OPEN: + instream_open(phm, phr, h_owner); + break; + case HPI_ISTREAM_CLOSE: + instream_close(phm, phr, h_owner); + break; + default: + hw_entry_point(phm, phr); + break; + } +} + +/* NOTE: HPI_Message() must be defined in the driver as a wrapper for + * HPI_MessageEx so that functions in hpifunc.c compile. + */ +void hpi_send_recv_ex(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + + if (logging_enabled) + HPI_DEBUG_MESSAGE(DEBUG, phm); + + if (phm->type != HPI_TYPE_REQUEST) { + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_INVALID_TYPE); + return; + } + + if (phm->adapter_index >= HPI_MAX_ADAPTERS + && phm->adapter_index != HPIMSGX_ALLADAPTERS) { + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_BAD_ADAPTER_NUMBER); + return; + } + + switch (phm->object) { + case HPI_OBJ_SUBSYSTEM: + subsys_message(phm, phr, h_owner); + break; + + case HPI_OBJ_ADAPTER: + adapter_message(phm, phr, h_owner); + break; + + case HPI_OBJ_MIXER: + mixer_message(phm, phr); + break; + + case HPI_OBJ_OSTREAM: + outstream_message(phm, phr, h_owner); + break; + + case HPI_OBJ_ISTREAM: + instream_message(phm, phr, h_owner); + break; + + default: + hw_entry_point(phm, phr); + break; + } + + if (logging_enabled) + HPI_DEBUG_RESPONSE(phr); + + if (phr->error >= HPI_ERROR_DSP_COMMUNICATION) { + hpi_debug_level_set(HPI_DEBUG_LEVEL_ERROR); + logging_enabled = 0; + } +} + +static void adapter_open(struct hpi_message *phm, struct hpi_response *phr) +{ + HPI_DEBUG_LOG(VERBOSE, "adapter_open\n"); + memcpy(phr, &rESP_HPI_ADAPTER_OPEN[phm->adapter_index], + sizeof(rESP_HPI_ADAPTER_OPEN[0])); +} + +static void adapter_close(struct hpi_message *phm, struct hpi_response *phr) +{ + HPI_DEBUG_LOG(VERBOSE, "adapter_close\n"); + hpi_init_response(phr, HPI_OBJ_ADAPTER, HPI_ADAPTER_CLOSE, 0); +} + +static void mixer_open(struct hpi_message *phm, struct hpi_response *phr) +{ + memcpy(phr, &rESP_HPI_MIXER_OPEN[phm->adapter_index], + sizeof(rESP_HPI_MIXER_OPEN[0])); +} + +static void mixer_close(struct hpi_message *phm, struct hpi_response *phr) +{ + hpi_init_response(phr, HPI_OBJ_MIXER, HPI_MIXER_CLOSE, 0); +} + +static void instream_open(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_response(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_OPEN, 0); + + hpios_msgxlock_lock(&msgx_lock); + + if (instream_user_open[phm->adapter_index][phm->obj_index].open_flag) + phr->error = HPI_ERROR_OBJ_ALREADY_OPEN; + else if (rESP_HPI_ISTREAM_OPEN[phm->adapter_index] + [phm->obj_index].h.error) + memcpy(phr, + &rESP_HPI_ISTREAM_OPEN[phm->adapter_index][phm-> + obj_index], + sizeof(rESP_HPI_ISTREAM_OPEN[0][0])); + else { + instream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 1; + hpios_msgxlock_unlock(&msgx_lock); + + /* issue a reset */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_RESET); + hm.adapter_index = phm->adapter_index; + hm.obj_index = phm->obj_index; + hw_entry_point(&hm, &hr); + + hpios_msgxlock_lock(&msgx_lock); + if (hr.error) { + instream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 0; + phr->error = hr.error; + } else { + instream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 1; + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = h_owner; + memcpy(phr, + &rESP_HPI_ISTREAM_OPEN[phm->adapter_index] + [phm->obj_index], + sizeof(rESP_HPI_ISTREAM_OPEN[0][0])); + } + } + hpios_msgxlock_unlock(&msgx_lock); +} + +static void instream_close(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_response(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_CLOSE, 0); + + hpios_msgxlock_lock(&msgx_lock); + if (h_owner == + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner) { + /* HPI_DEBUG_LOG(INFO,"closing adapter %d " + "instream %d owned by %p\n", + phm->wAdapterIndex, phm->wObjIndex, hOwner); */ + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = NULL; + hpios_msgxlock_unlock(&msgx_lock); + /* issue a reset */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_RESET); + hm.adapter_index = phm->adapter_index; + hm.obj_index = phm->obj_index; + hw_entry_point(&hm, &hr); + hpios_msgxlock_lock(&msgx_lock); + if (hr.error) { + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = h_owner; + phr->error = hr.error; + } else { + instream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 0; + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = NULL; + } + } else { + HPI_DEBUG_LOG(WARNING, + "%p trying to close %d instream %d owned by %p\n", + h_owner, phm->adapter_index, phm->obj_index, + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner); + phr->error = HPI_ERROR_OBJ_NOT_OPEN; + } + hpios_msgxlock_unlock(&msgx_lock); +} + +static void outstream_open(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_response(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_OPEN, 0); + + hpios_msgxlock_lock(&msgx_lock); + + if (outstream_user_open[phm->adapter_index][phm->obj_index].open_flag) + phr->error = HPI_ERROR_OBJ_ALREADY_OPEN; + else if (rESP_HPI_OSTREAM_OPEN[phm->adapter_index] + [phm->obj_index].h.error) + memcpy(phr, + &rESP_HPI_OSTREAM_OPEN[phm->adapter_index][phm-> + obj_index], + sizeof(rESP_HPI_OSTREAM_OPEN[0][0])); + else { + outstream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 1; + hpios_msgxlock_unlock(&msgx_lock); + + /* issue a reset */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_RESET); + hm.adapter_index = phm->adapter_index; + hm.obj_index = phm->obj_index; + hw_entry_point(&hm, &hr); + + hpios_msgxlock_lock(&msgx_lock); + if (hr.error) { + outstream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 0; + phr->error = hr.error; + } else { + outstream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 1; + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = h_owner; + memcpy(phr, + &rESP_HPI_OSTREAM_OPEN[phm->adapter_index] + [phm->obj_index], + sizeof(rESP_HPI_OSTREAM_OPEN[0][0])); + } + } + hpios_msgxlock_unlock(&msgx_lock); +} + +static void outstream_close(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_response(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_CLOSE, 0); + + hpios_msgxlock_lock(&msgx_lock); + + if (h_owner == + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner) { + /* HPI_DEBUG_LOG(INFO,"closing adapter %d " + "outstream %d owned by %p\n", + phm->wAdapterIndex, phm->wObjIndex, hOwner); */ + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = NULL; + hpios_msgxlock_unlock(&msgx_lock); + /* issue a reset */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_RESET); + hm.adapter_index = phm->adapter_index; + hm.obj_index = phm->obj_index; + hw_entry_point(&hm, &hr); + hpios_msgxlock_lock(&msgx_lock); + if (hr.error) { + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = h_owner; + phr->error = hr.error; + } else { + outstream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 0; + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = NULL; + } + } else { + HPI_DEBUG_LOG(WARNING, + "%p trying to close %d outstream %d owned by %p\n", + h_owner, phm->adapter_index, phm->obj_index, + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner); + phr->error = HPI_ERROR_OBJ_NOT_OPEN; + } + hpios_msgxlock_unlock(&msgx_lock); +} + +static u16 adapter_prepare(u16 adapter) +{ + struct hpi_message hm; + struct hpi_response hr; + + /* Open the adapter and streams */ + u16 i; + + /* call to HPI_ADAPTER_OPEN */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_OPEN); + hm.adapter_index = adapter; + hw_entry_point(&hm, &hr); + memcpy(&rESP_HPI_ADAPTER_OPEN[adapter].h, &hr, + sizeof(rESP_HPI_ADAPTER_OPEN[adapter].h)); + memcpy(&rESP_HPI_ADAPTER_OPEN[adapter].a, &hr.u.ax.info, + sizeof(rESP_HPI_ADAPTER_OPEN[adapter].a)); + if (hr.error) + return hr.error; + + /* call to HPI_ADAPTER_GET_INFO */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_INFO); + hm.adapter_index = adapter; + hw_entry_point(&hm, &hr); + if (hr.error) + return hr.error; + + aDAPTER_INFO[adapter].num_outstreams = hr.u.ax.info.num_outstreams; + aDAPTER_INFO[adapter].num_instreams = hr.u.ax.info.num_instreams; + aDAPTER_INFO[adapter].type = hr.u.ax.info.adapter_type; + + /* call to HPI_OSTREAM_OPEN */ + for (i = 0; i < aDAPTER_INFO[adapter].num_outstreams; i++) { + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_OPEN); + hm.adapter_index = adapter; + hm.obj_index = i; + hw_entry_point(&hm, &hr); + memcpy(&rESP_HPI_OSTREAM_OPEN[adapter][i], &hr, + sizeof(rESP_HPI_OSTREAM_OPEN[0][0])); + outstream_user_open[adapter][i].open_flag = 0; + outstream_user_open[adapter][i].h_owner = NULL; + } + + /* call to HPI_ISTREAM_OPEN */ + for (i = 0; i < aDAPTER_INFO[adapter].num_instreams; i++) { + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_OPEN); + hm.adapter_index = adapter; + hm.obj_index = i; + hw_entry_point(&hm, &hr); + memcpy(&rESP_HPI_ISTREAM_OPEN[adapter][i], &hr, + sizeof(rESP_HPI_ISTREAM_OPEN[0][0])); + instream_user_open[adapter][i].open_flag = 0; + instream_user_open[adapter][i].h_owner = NULL; + } + + /* call to HPI_MIXER_OPEN */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN); + hm.adapter_index = adapter; + hw_entry_point(&hm, &hr); + memcpy(&rESP_HPI_MIXER_OPEN[adapter], &hr, + sizeof(rESP_HPI_MIXER_OPEN[0])); + + return 0; +} + +static void HPIMSGX__reset(u16 adapter_index) +{ + int i; + u16 adapter; + struct hpi_response hr; + + if (adapter_index == HPIMSGX_ALLADAPTERS) { + for (adapter = 0; adapter < HPI_MAX_ADAPTERS; adapter++) { + + hpi_init_response(&hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_OPEN, HPI_ERROR_BAD_ADAPTER); + memcpy(&rESP_HPI_ADAPTER_OPEN[adapter], &hr, + sizeof(rESP_HPI_ADAPTER_OPEN[adapter])); + + hpi_init_response(&hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN, + HPI_ERROR_INVALID_OBJ); + memcpy(&rESP_HPI_MIXER_OPEN[adapter], &hr, + sizeof(rESP_HPI_MIXER_OPEN[adapter])); + + for (i = 0; i < HPI_MAX_STREAMS; i++) { + hpi_init_response(&hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_OPEN, + HPI_ERROR_INVALID_OBJ); + memcpy(&rESP_HPI_OSTREAM_OPEN[adapter][i], + &hr, + sizeof(rESP_HPI_OSTREAM_OPEN[adapter] + [i])); + hpi_init_response(&hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_OPEN, + HPI_ERROR_INVALID_OBJ); + memcpy(&rESP_HPI_ISTREAM_OPEN[adapter][i], + &hr, + sizeof(rESP_HPI_ISTREAM_OPEN[adapter] + [i])); + } + } + } else if (adapter_index < HPI_MAX_ADAPTERS) { + rESP_HPI_ADAPTER_OPEN[adapter_index].h.error = + HPI_ERROR_BAD_ADAPTER; + rESP_HPI_MIXER_OPEN[adapter_index].h.error = + HPI_ERROR_INVALID_OBJ; + for (i = 0; i < HPI_MAX_STREAMS; i++) { + rESP_HPI_OSTREAM_OPEN[adapter_index][i].h.error = + HPI_ERROR_INVALID_OBJ; + rESP_HPI_ISTREAM_OPEN[adapter_index][i].h.error = + HPI_ERROR_INVALID_OBJ; + } + } +} + +static u16 HPIMSGX__init(struct hpi_message *phm, + /* HPI_SUBSYS_CREATE_ADAPTER structure with */ + /* resource list or NULL=find all */ + struct hpi_response *phr + /* response from HPI_ADAPTER_GET_INFO */ + ) +{ + hpi_handler_func *entry_point_func; + struct hpi_response hr; + + /* Init response here so we can pass in previous adapter list */ + hpi_init_response(&hr, phm->object, phm->function, + HPI_ERROR_INVALID_OBJ); + + entry_point_func = + hpi_lookup_entry_point_function(phm->u.s.resource.r.pci); + + if (entry_point_func) { + HPI_DEBUG_MESSAGE(DEBUG, phm); + entry_point_func(phm, &hr); + } else { + phr->error = HPI_ERROR_PROCESSING_MESSAGE; + return phr->error; + } + if (hr.error == 0 && hr.u.s.adapter_index < HPI_MAX_ADAPTERS) { + /* the adapter was created successfully + save the mapping for future use */ + hpi_entry_points[hr.u.s.adapter_index] = entry_point_func; + /* prepare adapter (pre-open streams etc.) */ + HPI_DEBUG_LOG(DEBUG, + "HPI_SUBSYS_CREATE_ADAPTER successful," + " preparing adapter\n"); + adapter_prepare(hr.u.s.adapter_index); + } + memcpy(phr, &hr, hr.size); + return phr->error; +} + +static void HPIMSGX__cleanup(u16 adapter_index, void *h_owner) +{ + int i, adapter, adapter_limit; + + if (!h_owner) + return; + + if (adapter_index == HPIMSGX_ALLADAPTERS) { + adapter = 0; + adapter_limit = HPI_MAX_ADAPTERS; + } else { + adapter = adapter_index; + adapter_limit = adapter + 1; + } + + for (; adapter < adapter_limit; adapter++) { + /* printk(KERN_INFO "Cleanup adapter #%d\n",wAdapter); */ + for (i = 0; i < HPI_MAX_STREAMS; i++) { + if (h_owner == + outstream_user_open[adapter][i].h_owner) { + struct hpi_message hm; + struct hpi_response hr; + + HPI_DEBUG_LOG(DEBUG, + "Close adapter %d ostream %d\n", + adapter, i); + + hpi_init_message_response(&hm, &hr, + HPI_OBJ_OSTREAM, HPI_OSTREAM_RESET); + hm.adapter_index = (u16)adapter; + hm.obj_index = (u16)i; + hw_entry_point(&hm, &hr); + + hm.function = HPI_OSTREAM_HOSTBUFFER_FREE; + hw_entry_point(&hm, &hr); + + hm.function = HPI_OSTREAM_GROUP_RESET; + hw_entry_point(&hm, &hr); + + outstream_user_open[adapter][i].open_flag = 0; + outstream_user_open[adapter][i].h_owner = + NULL; + } + if (h_owner == instream_user_open[adapter][i].h_owner) { + struct hpi_message hm; + struct hpi_response hr; + + HPI_DEBUG_LOG(DEBUG, + "Close adapter %d istream %d\n", + adapter, i); + + hpi_init_message_response(&hm, &hr, + HPI_OBJ_ISTREAM, HPI_ISTREAM_RESET); + hm.adapter_index = (u16)adapter; + hm.obj_index = (u16)i; + hw_entry_point(&hm, &hr); + + hm.function = HPI_ISTREAM_HOSTBUFFER_FREE; + hw_entry_point(&hm, &hr); + + hm.function = HPI_ISTREAM_GROUP_RESET; + hw_entry_point(&hm, &hr); + + instream_user_open[adapter][i].open_flag = 0; + instream_user_open[adapter][i].h_owner = NULL; + } + } + } +} diff --git a/sound/pci/asihpi/hpimsgx.h b/sound/pci/asihpi/hpimsgx.h new file mode 100644 index 000000000..14c01cc91 --- /dev/null +++ b/sound/pci/asihpi/hpimsgx.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + + HPI Extended Message Handler Functions + +(C) Copyright AudioScience Inc. 1997-2003 +******************************************************************************/ + +#ifndef _HPIMSGX_H_ +#define _HPIMSGX_H_ + +#include "hpi_internal.h" + +#define HPIMSGX_ALLADAPTERS (0xFFFF) + +void hpi_send_recv_ex(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); + +#define HPI_MESSAGE_LOWER_LAYER hpi_send_recv_ex + +#endif /* _HPIMSGX_H_ */ diff --git a/sound/pci/asihpi/hpioctl.c b/sound/pci/asihpi/hpioctl.c new file mode 100644 index 000000000..ec2da792e --- /dev/null +++ b/sound/pci/asihpi/hpioctl.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0-only +/******************************************************************************* + AudioScience HPI driver + Common Linux HPI ioctl and module probe/remove functions + + Copyright (C) 1997-2014 AudioScience Inc. + + +*******************************************************************************/ +#define SOURCEFILE_NAME "hpioctl.c" + +#include "hpi_internal.h" +#include "hpi_version.h" +#include "hpimsginit.h" +#include "hpidebug.h" +#include "hpimsgx.h" +#include "hpioctl.h" +#include "hpicmn.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef MODULE_FIRMWARE +MODULE_FIRMWARE("asihpi/dsp5000.bin"); +MODULE_FIRMWARE("asihpi/dsp6200.bin"); +MODULE_FIRMWARE("asihpi/dsp6205.bin"); +MODULE_FIRMWARE("asihpi/dsp6400.bin"); +MODULE_FIRMWARE("asihpi/dsp6600.bin"); +MODULE_FIRMWARE("asihpi/dsp8700.bin"); +MODULE_FIRMWARE("asihpi/dsp8900.bin"); +#endif + +static int prealloc_stream_buf; +module_param(prealloc_stream_buf, int, 0444); +MODULE_PARM_DESC(prealloc_stream_buf, + "Preallocate size for per-adapter stream buffer"); + +/* Allow the debug level to be changed after module load. + E.g. echo 2 > /sys/module/asihpi/parameters/hpiDebugLevel +*/ +module_param(hpi_debug_level, int, 0644); +MODULE_PARM_DESC(hpi_debug_level, "debug verbosity 0..5"); + +/* List of adapters found */ +static struct hpi_adapter adapters[HPI_MAX_ADAPTERS]; + +/* Wrapper function to HPI_Message to enable dumping of the + message and response types. +*/ +static void hpi_send_recv_f(struct hpi_message *phm, struct hpi_response *phr, + struct file *file) +{ + if ((phm->adapter_index >= HPI_MAX_ADAPTERS) + && (phm->object != HPI_OBJ_SUBSYSTEM)) + phr->error = HPI_ERROR_INVALID_OBJ_INDEX; + else + hpi_send_recv_ex(phm, phr, file); +} + +/* This is called from hpifunc.c functions, called by ALSA + * (or other kernel process) In this case there is no file descriptor + * available for the message cache code + */ +void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr) +{ + hpi_send_recv_f(phm, phr, HOWNER_KERNEL); +} + +EXPORT_SYMBOL(hpi_send_recv); +/* for radio-asihpi */ + +int asihpi_hpi_release(struct file *file) +{ + struct hpi_message hm; + struct hpi_response hr; + +/* HPI_DEBUG_LOG(INFO,"hpi_release file %p, pid %d\n", file, current->pid); */ + /* close the subsystem just in case the application forgot to. */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_CLOSE); + hpi_send_recv_ex(&hm, &hr, file); + return 0; +} + +long asihpi_hpi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct hpi_ioctl_linux __user *phpi_ioctl_data; + void __user *puhm; + void __user *puhr; + union hpi_message_buffer_v1 *hm; + union hpi_response_buffer_v1 *hr; + u16 msg_size; + u16 res_max_size; + u32 uncopied_bytes; + int err = 0; + + if (cmd != HPI_IOCTL_LINUX) + return -EINVAL; + + hm = kmalloc_obj(*hm); + hr = kzalloc_obj(*hr); + if (!hm || !hr) { + err = -ENOMEM; + goto out; + } + + phpi_ioctl_data = (struct hpi_ioctl_linux __user *)arg; + + /* Read the message and response pointers from user space. */ + if (get_user(puhm, &phpi_ioctl_data->phm) + || get_user(puhr, &phpi_ioctl_data->phr)) { + err = -EFAULT; + goto out; + } + + /* Now read the message size and data from user space. */ + if (get_user(msg_size, (u16 __user *)puhm)) { + err = -EFAULT; + goto out; + } + if (msg_size > sizeof(*hm)) + msg_size = sizeof(*hm); + + /* printk(KERN_INFO "message size %d\n", hm->h.wSize); */ + + uncopied_bytes = copy_from_user(hm, puhm, msg_size); + if (uncopied_bytes) { + HPI_DEBUG_LOG(ERROR, "uncopied bytes %d\n", uncopied_bytes); + err = -EFAULT; + goto out; + } + + /* Override h.size in case it is changed between two userspace fetches */ + hm->h.size = msg_size; + + if (get_user(res_max_size, (u16 __user *)puhr)) { + err = -EFAULT; + goto out; + } + /* printk(KERN_INFO "user response size %d\n", res_max_size); */ + if (res_max_size < sizeof(struct hpi_response_header)) { + HPI_DEBUG_LOG(WARNING, "small res size %d\n", res_max_size); + err = -EFAULT; + goto out; + } + + res_max_size = min_t(size_t, res_max_size, sizeof(*hr)); + + switch (hm->h.function) { + case HPI_SUBSYS_CREATE_ADAPTER: + case HPI_ADAPTER_DELETE: + /* Application must not use these functions! */ + hr->h.size = sizeof(hr->h); + hr->h.error = HPI_ERROR_INVALID_OPERATION; + hr->h.function = hm->h.function; + uncopied_bytes = copy_to_user(puhr, hr, hr->h.size); + if (uncopied_bytes) + err = -EFAULT; + else + err = 0; + goto out; + } + + hr->h.size = res_max_size; + if (hm->h.object == HPI_OBJ_SUBSYSTEM) { + hpi_send_recv_f(&hm->m0, &hr->r0, file); + } else { + u16 __user *ptr = NULL; + u32 size = 0; + /* -1=no data 0=read from user mem, 1=write to user mem */ + int wrflag = -1; + struct hpi_adapter *pa = NULL; + + if (hm->h.adapter_index < ARRAY_SIZE(adapters)) + pa = &adapters[array_index_nospec(hm->h.adapter_index, + ARRAY_SIZE(adapters))]; + + if (!pa || !pa->adapter || !pa->adapter->type) { + hpi_init_response(&hr->r0, hm->h.object, + hm->h.function, HPI_ERROR_BAD_ADAPTER_NUMBER); + + uncopied_bytes = + copy_to_user(puhr, hr, sizeof(hr->h)); + if (uncopied_bytes) + err = -EFAULT; + else + err = 0; + goto out; + } + + if (mutex_lock_interruptible(&pa->mutex)) { + err = -EINTR; + goto out; + } + + /* Dig out any pointers embedded in the message. */ + switch (hm->h.function) { + case HPI_OSTREAM_WRITE: + case HPI_ISTREAM_READ:{ + /* Yes, sparse, this is correct. */ + ptr = (u16 __user *)hm->m0.u.d.u.data.pb_data; + size = hm->m0.u.d.u.data.data_size; + + /* Allocate buffer according to application request. + ?Is it better to alloc/free for the duration + of the transaction? + */ + if (pa->buffer_size < size) { + HPI_DEBUG_LOG(DEBUG, + "Realloc adapter %d stream " + "buffer from %zd to %d\n", + hm->h.adapter_index, + pa->buffer_size, size); + if (pa->p_buffer) { + pa->buffer_size = 0; + vfree(pa->p_buffer); + } + pa->p_buffer = vmalloc(size); + if (pa->p_buffer) + pa->buffer_size = size; + else { + HPI_DEBUG_LOG(ERROR, + "HPI could not allocate " + "stream buffer size %d\n", + size); + + mutex_unlock(&pa->mutex); + err = -EINVAL; + goto out; + } + } + + hm->m0.u.d.u.data.pb_data = pa->p_buffer; + if (hm->h.function == HPI_ISTREAM_READ) + /* from card, WRITE to user mem */ + wrflag = 1; + else + wrflag = 0; + break; + } + + default: + size = 0; + break; + } + + if (size && (wrflag == 0)) { + uncopied_bytes = + copy_from_user(pa->p_buffer, ptr, size); + if (uncopied_bytes) + HPI_DEBUG_LOG(WARNING, + "Missed %d of %d " + "bytes from user\n", uncopied_bytes, + size); + } + + hpi_send_recv_f(&hm->m0, &hr->r0, file); + + if (size && (wrflag == 1)) { + uncopied_bytes = + copy_to_user(ptr, pa->p_buffer, size); + if (uncopied_bytes) + HPI_DEBUG_LOG(WARNING, + "Missed %d of %d " "bytes to user\n", + uncopied_bytes, size); + } + + mutex_unlock(&pa->mutex); + } + + /* on return response size must be set */ + /*printk(KERN_INFO "response size %d\n", hr->h.wSize); */ + + if (!hr->h.size) { + HPI_DEBUG_LOG(ERROR, "response zero size\n"); + err = -EFAULT; + goto out; + } + + if (hr->h.size > res_max_size) { + HPI_DEBUG_LOG(ERROR, "response too big %d %d\n", hr->h.size, + res_max_size); + hr->h.error = HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL; + hr->h.specific_error = hr->h.size; + hr->h.size = sizeof(hr->h); + } + + uncopied_bytes = copy_to_user(puhr, hr, hr->h.size); + if (uncopied_bytes) { + HPI_DEBUG_LOG(ERROR, "uncopied bytes %d\n", uncopied_bytes); + err = -EFAULT; + goto out; + } + +out: + kfree(hm); + kfree(hr); + return err; +} + +static int asihpi_irq_count; + +static irqreturn_t asihpi_isr(int irq, void *dev_id) +{ + struct hpi_adapter *a = dev_id; + int handled; + + if (!a->adapter->irq_query_and_clear) { + pr_err("asihpi_isr ASI%04X:%d no handler\n", a->adapter->type, + a->adapter->index); + return IRQ_NONE; + } + + handled = a->adapter->irq_query_and_clear(a->adapter, 0); + + if (!handled) + return IRQ_NONE; + + asihpi_irq_count++; + /* printk(KERN_INFO "asihpi_isr %d ASI%04X:%d irq handled\n", + asihpi_irq_count, a->adapter->type, a->adapter->index); */ + + if (a->interrupt_callback) + return IRQ_WAKE_THREAD; + + return IRQ_HANDLED; +} + +static irqreturn_t asihpi_isr_thread(int irq, void *dev_id) +{ + struct hpi_adapter *a = dev_id; + + if (a->interrupt_callback) + a->interrupt_callback(a); + return IRQ_HANDLED; +} + +int asihpi_adapter_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + int idx, nm, low_latency_mode = 0, irq_supported = 0; + int adapter_index; + unsigned int memlen; + struct hpi_message hm; + struct hpi_response hr; + struct hpi_adapter adapter; + struct hpi_pci pci = { 0 }; + + memset(&adapter, 0, sizeof(adapter)); + + dev_dbg(&pci_dev->dev, + "probe %04x:%04x,%04x:%04x,%04x\n", pci_dev->vendor, + pci_dev->device, pci_dev->subsystem_vendor, + pci_dev->subsystem_device, pci_dev->devfn); + + if (pcim_enable_device(pci_dev) < 0) { + dev_err(&pci_dev->dev, + "pci_enable_device failed, disabling device\n"); + return -EIO; + } + + pci_set_master(pci_dev); /* also sets latency timer if < 16 */ + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_CREATE_ADAPTER); + hpi_init_response(&hr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_CREATE_ADAPTER, + HPI_ERROR_PROCESSING_MESSAGE); + + hm.adapter_index = HPI_ADAPTER_INDEX_INVALID; + + nm = HPI_MAX_ADAPTER_MEM_SPACES; + + for (idx = 0; idx < nm; idx++) { + HPI_DEBUG_LOG(INFO, "resource %d %pR\n", idx, + &pci_dev->resource[idx]); + + if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) { + memlen = pci_resource_len(pci_dev, idx); + pci.ap_mem_base[idx] = + pcim_iomap(pci_dev, idx, memlen); + if (!pci.ap_mem_base[idx]) { + HPI_DEBUG_LOG(ERROR, + "ioremap failed, aborting\n"); + /* unmap previously mapped pci mem space */ + goto err; + } + } + } + + pci.pci_dev = pci_dev; + hm.u.s.resource.bus_type = HPI_BUS_PCI; + hm.u.s.resource.r.pci = &pci; + + /* call CreateAdapterObject on the relevant hpi module */ + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + if (hr.error) + goto err; + + adapter_index = hr.u.s.adapter_index; + adapter.adapter = hpi_find_adapter(adapter_index); + + if (prealloc_stream_buf) { + adapter.p_buffer = vmalloc(prealloc_stream_buf); + if (!adapter.p_buffer) { + HPI_DEBUG_LOG(ERROR, + "HPI could not allocate " + "kernel buffer size %d\n", + prealloc_stream_buf); + goto err; + } + } + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_OPEN); + hm.adapter_index = adapter.adapter->index; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + + if (hr.error) { + HPI_DEBUG_LOG(ERROR, "HPI_ADAPTER_OPEN failed, aborting\n"); + goto err; + } + + /* Check if current mode == Low Latency mode */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_MODE); + hm.adapter_index = adapter.adapter->index; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + + if (!hr.error + && hr.u.ax.mode.adapter_mode == HPI_ADAPTER_MODE_LOW_LATENCY) + low_latency_mode = 1; + else + dev_info(&pci_dev->dev, + "Adapter at index %d is not in low latency mode\n", + adapter.adapter->index); + + /* Check if IRQs are supported */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_PROPERTY); + hm.adapter_index = adapter.adapter->index; + hm.u.ax.property_set.property = HPI_ADAPTER_PROPERTY_SUPPORTS_IRQ; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + if (hr.error || !hr.u.ax.property_get.parameter1) { + dev_info(&pci_dev->dev, + "IRQs not supported by adapter at index %d\n", + adapter.adapter->index); + } else { + irq_supported = 1; + } + + /* WARNING can't init mutex in 'adapter' + * and then copy it to adapters[] ?!?! + */ + adapters[adapter_index] = adapter; + mutex_init(&adapters[adapter_index].mutex); + pci_set_drvdata(pci_dev, &adapters[adapter_index]); + + if (low_latency_mode && irq_supported) { + if (!adapter.adapter->irq_query_and_clear) { + dev_err(&pci_dev->dev, + "no IRQ handler for adapter %d, aborting\n", + adapter.adapter->index); + goto err; + } + + /* Disable IRQ generation on DSP side by setting the rate to 0 */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_SET_PROPERTY); + hm.adapter_index = adapter.adapter->index; + hm.u.ax.property_set.property = HPI_ADAPTER_PROPERTY_IRQ_RATE; + hm.u.ax.property_set.parameter1 = 0; + hm.u.ax.property_set.parameter2 = 0; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + if (hr.error) { + HPI_DEBUG_LOG(ERROR, + "HPI_ADAPTER_GET_MODE failed, aborting\n"); + goto err; + } + + /* Note: request_irq calls asihpi_isr here */ + if (request_threaded_irq(pci_dev->irq, asihpi_isr, + asihpi_isr_thread, IRQF_SHARED, + "asihpi", &adapters[adapter_index])) { + dev_err(&pci_dev->dev, "request_irq(%d) failed\n", + pci_dev->irq); + goto err; + } + + adapters[adapter_index].interrupt_mode = 1; + + dev_info(&pci_dev->dev, "using irq %d\n", pci_dev->irq); + adapters[adapter_index].irq = pci_dev->irq; + } else { + dev_info(&pci_dev->dev, "using polled mode\n"); + } + + dev_info(&pci_dev->dev, "probe succeeded for ASI%04X HPI index %d\n", + adapter.adapter->type, adapter_index); + + return 0; + +err: + if (adapter.p_buffer) { + adapter.buffer_size = 0; + vfree(adapter.p_buffer); + } + + HPI_DEBUG_LOG(ERROR, "adapter_probe failed\n"); + return -ENODEV; +} + +void asihpi_adapter_remove(struct pci_dev *pci_dev) +{ + struct hpi_message hm; + struct hpi_response hr; + struct hpi_adapter *pa; + + pa = pci_get_drvdata(pci_dev); + + /* Disable IRQ generation on DSP side */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_SET_PROPERTY); + hm.adapter_index = pa->adapter->index; + hm.u.ax.property_set.property = HPI_ADAPTER_PROPERTY_IRQ_RATE; + hm.u.ax.property_set.parameter1 = 0; + hm.u.ax.property_set.parameter2 = 0; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_DELETE); + hm.adapter_index = pa->adapter->index; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + + if (pa->irq) + free_irq(pa->irq, pa); + + vfree(pa->p_buffer); + + if (1) + dev_info(&pci_dev->dev, + "remove %04x:%04x,%04x:%04x,%04x, HPI index %d\n", + pci_dev->vendor, pci_dev->device, + pci_dev->subsystem_vendor, pci_dev->subsystem_device, + pci_dev->devfn, pa->adapter->index); + + memset(pa, 0, sizeof(*pa)); +} + +void __init asihpi_init(void) +{ + struct hpi_message hm; + struct hpi_response hr; + + memset(adapters, 0, sizeof(adapters)); + + printk(KERN_INFO "ASIHPI driver " HPI_VER_STRING "\n"); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DRIVER_LOAD); + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); +} + +void asihpi_exit(void) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DRIVER_UNLOAD); + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); +} diff --git a/sound/pci/asihpi/hpioctl.h b/sound/pci/asihpi/hpioctl.h new file mode 100644 index 000000000..f2ee172da --- /dev/null +++ b/sound/pci/asihpi/hpioctl.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/******************************************************************************* + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + +Linux HPI ioctl, and shared module init functions +*******************************************************************************/ + +int asihpi_adapter_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id); +void asihpi_adapter_remove(struct pci_dev *pci_dev); +void __init asihpi_init(void); +void __exit asihpi_exit(void); + +int asihpi_hpi_release(struct file *file); + +long asihpi_hpi_ioctl(struct file *file, unsigned int cmd, unsigned long arg); + +/* This is called from hpifunc.c functions, called by ALSA + * (or other kernel process) In this case there is no file descriptor + * available for the message cache code + */ +void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr); + +#define HOWNER_KERNEL ((void *)-1) diff --git a/sound/pci/asihpi/hpios.c b/sound/pci/asihpi/hpios.c new file mode 100644 index 000000000..6fe60d13e --- /dev/null +++ b/sound/pci/asihpi/hpios.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2012 AudioScience Inc. + + +HPI Operating System function implementation for Linux + +(C) Copyright AudioScience Inc. 1997-2003 +******************************************************************************/ +#define SOURCEFILE_NAME "hpios.c" +#include "hpi_internal.h" +#include "hpidebug.h" +#include +#include + +void hpios_delay_micro_seconds(u32 num_micro_sec) +{ + if ((usecs_to_jiffies(num_micro_sec) > 1) && !in_interrupt()) { + /* MUST NOT SCHEDULE IN INTERRUPT CONTEXT! */ + schedule_timeout_uninterruptible(usecs_to_jiffies + (num_micro_sec)); + } else if (num_micro_sec <= 2000) + udelay(num_micro_sec); + else + mdelay(num_micro_sec / 1000); + +} + +/** Allocate an area of locked memory for bus master DMA operations. + +If allocation fails, return 1, and *pMemArea.size = 0 +*/ +u16 hpios_locked_mem_alloc(struct consistent_dma_area *p_mem_area, u32 size, + struct pci_dev *pdev) +{ + /*?? any benefit in using managed dmam_alloc_coherent? */ + p_mem_area->vaddr = + dma_alloc_coherent(&pdev->dev, size, &p_mem_area->dma_handle, + GFP_KERNEL); + + if (p_mem_area->vaddr) { + HPI_DEBUG_LOG(DEBUG, "allocated %d bytes, dma 0x%x vma %p\n", + size, (unsigned int)p_mem_area->dma_handle, + p_mem_area->vaddr); + p_mem_area->pdev = &pdev->dev; + p_mem_area->size = size; + return 0; + } else { + HPI_DEBUG_LOG(WARNING, + "failed to allocate %d bytes locked memory\n", size); + p_mem_area->size = 0; + return 1; + } +} + +u16 hpios_locked_mem_free(struct consistent_dma_area *p_mem_area) +{ + if (p_mem_area->size) { + dma_free_coherent(p_mem_area->pdev, p_mem_area->size, + p_mem_area->vaddr, p_mem_area->dma_handle); + HPI_DEBUG_LOG(DEBUG, "freed %lu bytes, dma 0x%x vma %p\n", + (unsigned long)p_mem_area->size, + (unsigned int)p_mem_area->dma_handle, + p_mem_area->vaddr); + p_mem_area->size = 0; + return 0; + } else { + return 1; + } +} diff --git a/sound/pci/asihpi/hpios.h b/sound/pci/asihpi/hpios.h new file mode 100644 index 000000000..9e551bc46 --- /dev/null +++ b/sound/pci/asihpi/hpios.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + +HPI Operating System Specific macros for Linux Kernel driver + +(C) Copyright AudioScience Inc. 1997-2003 +******************************************************************************/ +#ifndef _HPIOS_H_ +#define _HPIOS_H_ + +#undef HPI_OS_LINUX_KERNEL +#define HPI_OS_LINUX_KERNEL + +#define HPI_OS_DEFINED +#define HPI_BUILD_KERNEL_MODE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HPI_NO_OS_FILE_OPS + +/** Details of a memory area allocated with pci_alloc_consistent +Need all info for parameters to pci_free_consistent +*/ +struct consistent_dma_area { + struct device *pdev; + /* looks like dma-mapping dma_devres ?! */ + size_t size; + void *vaddr; + dma_addr_t dma_handle; +}; + +static inline u16 hpios_locked_mem_get_phys_addr(struct consistent_dma_area + *locked_mem_handle, u32 *p_physical_addr) +{ + *p_physical_addr = locked_mem_handle->dma_handle; + return 0; +} + +static inline u16 hpios_locked_mem_get_virt_addr(struct consistent_dma_area + *locked_mem_handle, void **pp_virtual_addr) +{ + *pp_virtual_addr = locked_mem_handle->vaddr; + return 0; +} + +static inline u16 hpios_locked_mem_valid(struct consistent_dma_area + *locked_mem_handle) +{ + return locked_mem_handle->size != 0; +} + +struct hpi_ioctl_linux { + void __user *phm; + void __user *phr; +}; + +/* Conflict?: H is already used by a number of drivers hid, bluetooth hci, + and some sound drivers sb16, hdsp, emu10k. AFAIK 0xFC is unused command +*/ +#define HPI_IOCTL_LINUX _IOWR('H', 0xFC, struct hpi_ioctl_linux) + +#define HPI_DEBUG_FLAG_ERROR KERN_ERR +#define HPI_DEBUG_FLAG_WARNING KERN_WARNING +#define HPI_DEBUG_FLAG_NOTICE KERN_NOTICE +#define HPI_DEBUG_FLAG_INFO KERN_INFO +#define HPI_DEBUG_FLAG_DEBUG KERN_DEBUG +#define HPI_DEBUG_FLAG_VERBOSE KERN_DEBUG /* kernel has no verbose */ + +#include + +#define HPI_LOCKING + +struct hpios_spinlock { + spinlock_t lock; /* SEE hpios_spinlock */ + int lock_context; +}; + +/* The reason for all this evilness is that ALSA calls some of a drivers + * operators in atomic context, and some not. But all our functions channel + * through the HPI_Message conduit, so we can't handle the different context + * per function + */ +#define IN_LOCK_BH 1 +#define IN_LOCK_IRQ 0 +static inline void cond_lock(struct hpios_spinlock *l) +{ + if (irqs_disabled()) { + /* NO bh or isr can execute on this processor, + so ordinary lock will do + */ + spin_lock(&((l)->lock)); + l->lock_context = IN_LOCK_IRQ; + } else { + spin_lock_bh(&((l)->lock)); + l->lock_context = IN_LOCK_BH; + } +} + +static inline void cond_unlock(struct hpios_spinlock *l) +{ + if (l->lock_context == IN_LOCK_BH) + spin_unlock_bh(&((l)->lock)); + else + spin_unlock(&((l)->lock)); +} + +#define hpios_msgxlock_init(obj) spin_lock_init(&(obj)->lock) +#define hpios_msgxlock_lock(obj) cond_lock(obj) +#define hpios_msgxlock_unlock(obj) cond_unlock(obj) + +#define hpios_dsplock_init(obj) spin_lock_init(&(obj)->dsp_lock.lock) +#define hpios_dsplock_lock(obj) cond_lock(&(obj)->dsp_lock) +#define hpios_dsplock_unlock(obj) cond_unlock(&(obj)->dsp_lock) + +#ifdef CONFIG_SND_DEBUG +#define HPI_BUILD_DEBUG +#endif + +#define HPI_ALIST_LOCKING +#define hpios_alistlock_init(obj) spin_lock_init(&((obj)->list_lock.lock)) +#define hpios_alistlock_lock(obj) spin_lock(&((obj)->list_lock.lock)) +#define hpios_alistlock_unlock(obj) spin_unlock(&((obj)->list_lock.lock)) + +struct snd_card; + +/** pci drvdata points to an instance of this struct */ +struct hpi_adapter { + struct hpi_adapter_obj *adapter; + struct snd_card *snd_card; + + int irq; + int interrupt_mode; + void (*interrupt_callback) (struct hpi_adapter *); + + /* mutex prevents contention for one card + between multiple user programs (via ioctl) */ + struct mutex mutex; + char *p_buffer; + size_t buffer_size; +}; + +#endif diff --git a/sound/pci/asihpi/hpipcida.h b/sound/pci/asihpi/hpipcida.h new file mode 100644 index 000000000..6dceff2b4 --- /dev/null +++ b/sound/pci/asihpi/hpipcida.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + + Array initializer for PCI card IDs + +(C) Copyright AudioScience Inc. 1998-2003 +*******************************************************************************/ + +/*NOTE: when adding new lines to this header file + they MUST be grouped by HPI entry point. +*/ + +{ + PCI_DEVICE_SUB(HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205, + HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID), + .driver_data = (kernel_ulong_t) HPI_6205, +}, { + PCI_DEVICE_SUB(HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040, + HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID), + .driver_data = (kernel_ulong_t) HPI_6000, +}, +{ } diff --git a/sound/pci/atiixp.c b/sound/pci/atiixp.c new file mode 100644 index 000000000..b738295b4 --- /dev/null +++ b/sound/pci/atiixp.c @@ -0,0 +1,1619 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ATI IXP 150/200/250/300 AC97 controllers + * + * Copyright (c) 2004 Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("ATI IXP AC97 controller"); +MODULE_LICENSE("GPL"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static int ac97_clock = 48000; +static char *ac97_quirk; +static bool spdif_aclink = 1; +static int ac97_codec = -1; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for ATI IXP controller."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for ATI IXP controller."); +module_param(ac97_clock, int, 0444); +MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz)."); +module_param(ac97_quirk, charp, 0444); +MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware."); +module_param(ac97_codec, int, 0444); +MODULE_PARM_DESC(ac97_codec, "Specify codec instead of probing."); +module_param(spdif_aclink, bool, 0444); +MODULE_PARM_DESC(spdif_aclink, "S/PDIF over AC-link."); + +/* just for backward compatibility */ +static bool enable; +module_param(enable, bool, 0444); + + +/* + */ + +#define ATI_REG_ISR 0x00 /* interrupt source */ +#define ATI_REG_ISR_IN_XRUN (1U<<0) +#define ATI_REG_ISR_IN_STATUS (1U<<1) +#define ATI_REG_ISR_OUT_XRUN (1U<<2) +#define ATI_REG_ISR_OUT_STATUS (1U<<3) +#define ATI_REG_ISR_SPDF_XRUN (1U<<4) +#define ATI_REG_ISR_SPDF_STATUS (1U<<5) +#define ATI_REG_ISR_PHYS_INTR (1U<<8) +#define ATI_REG_ISR_PHYS_MISMATCH (1U<<9) +#define ATI_REG_ISR_CODEC0_NOT_READY (1U<<10) +#define ATI_REG_ISR_CODEC1_NOT_READY (1U<<11) +#define ATI_REG_ISR_CODEC2_NOT_READY (1U<<12) +#define ATI_REG_ISR_NEW_FRAME (1U<<13) + +#define ATI_REG_IER 0x04 /* interrupt enable */ +#define ATI_REG_IER_IN_XRUN_EN (1U<<0) +#define ATI_REG_IER_IO_STATUS_EN (1U<<1) +#define ATI_REG_IER_OUT_XRUN_EN (1U<<2) +#define ATI_REG_IER_OUT_XRUN_COND (1U<<3) +#define ATI_REG_IER_SPDF_XRUN_EN (1U<<4) +#define ATI_REG_IER_SPDF_STATUS_EN (1U<<5) +#define ATI_REG_IER_PHYS_INTR_EN (1U<<8) +#define ATI_REG_IER_PHYS_MISMATCH_EN (1U<<9) +#define ATI_REG_IER_CODEC0_INTR_EN (1U<<10) +#define ATI_REG_IER_CODEC1_INTR_EN (1U<<11) +#define ATI_REG_IER_CODEC2_INTR_EN (1U<<12) +#define ATI_REG_IER_NEW_FRAME_EN (1U<<13) /* (RO */ +#define ATI_REG_IER_SET_BUS_BUSY (1U<<14) /* (WO) audio is running */ + +#define ATI_REG_CMD 0x08 /* command */ +#define ATI_REG_CMD_POWERDOWN (1U<<0) +#define ATI_REG_CMD_RECEIVE_EN (1U<<1) +#define ATI_REG_CMD_SEND_EN (1U<<2) +#define ATI_REG_CMD_STATUS_MEM (1U<<3) +#define ATI_REG_CMD_SPDF_OUT_EN (1U<<4) +#define ATI_REG_CMD_SPDF_STATUS_MEM (1U<<5) +#define ATI_REG_CMD_SPDF_THRESHOLD (3U<<6) +#define ATI_REG_CMD_SPDF_THRESHOLD_SHIFT 6 +#define ATI_REG_CMD_IN_DMA_EN (1U<<8) +#define ATI_REG_CMD_OUT_DMA_EN (1U<<9) +#define ATI_REG_CMD_SPDF_DMA_EN (1U<<10) +#define ATI_REG_CMD_SPDF_OUT_STOPPED (1U<<11) +#define ATI_REG_CMD_SPDF_CONFIG_MASK (7U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_34 (1U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_78 (2U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_69 (3U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_01 (4U<<12) +#define ATI_REG_CMD_INTERLEAVE_SPDF (1U<<16) +#define ATI_REG_CMD_AUDIO_PRESENT (1U<<20) +#define ATI_REG_CMD_INTERLEAVE_IN (1U<<21) +#define ATI_REG_CMD_INTERLEAVE_OUT (1U<<22) +#define ATI_REG_CMD_LOOPBACK_EN (1U<<23) +#define ATI_REG_CMD_PACKED_DIS (1U<<24) +#define ATI_REG_CMD_BURST_EN (1U<<25) +#define ATI_REG_CMD_PANIC_EN (1U<<26) +#define ATI_REG_CMD_MODEM_PRESENT (1U<<27) +#define ATI_REG_CMD_ACLINK_ACTIVE (1U<<28) +#define ATI_REG_CMD_AC_SOFT_RESET (1U<<29) +#define ATI_REG_CMD_AC_SYNC (1U<<30) +#define ATI_REG_CMD_AC_RESET (1U<<31) + +#define ATI_REG_PHYS_OUT_ADDR 0x0c +#define ATI_REG_PHYS_OUT_CODEC_MASK (3U<<0) +#define ATI_REG_PHYS_OUT_RW (1U<<2) +#define ATI_REG_PHYS_OUT_ADDR_EN (1U<<8) +#define ATI_REG_PHYS_OUT_ADDR_SHIFT 9 +#define ATI_REG_PHYS_OUT_DATA_SHIFT 16 + +#define ATI_REG_PHYS_IN_ADDR 0x10 +#define ATI_REG_PHYS_IN_READ_FLAG (1U<<8) +#define ATI_REG_PHYS_IN_ADDR_SHIFT 9 +#define ATI_REG_PHYS_IN_DATA_SHIFT 16 + +#define ATI_REG_SLOTREQ 0x14 + +#define ATI_REG_COUNTER 0x18 +#define ATI_REG_COUNTER_SLOT (3U<<0) /* slot # */ +#define ATI_REG_COUNTER_BITCLOCK (31U<<8) + +#define ATI_REG_IN_FIFO_THRESHOLD 0x1c + +#define ATI_REG_IN_DMA_LINKPTR 0x20 +#define ATI_REG_IN_DMA_DT_START 0x24 /* RO */ +#define ATI_REG_IN_DMA_DT_NEXT 0x28 /* RO */ +#define ATI_REG_IN_DMA_DT_CUR 0x2c /* RO */ +#define ATI_REG_IN_DMA_DT_SIZE 0x30 + +#define ATI_REG_OUT_DMA_SLOT 0x34 +#define ATI_REG_OUT_DMA_SLOT_BIT(x) (1U << ((x) - 3)) +#define ATI_REG_OUT_DMA_SLOT_MASK 0x1ff +#define ATI_REG_OUT_DMA_THRESHOLD_MASK 0xf800 +#define ATI_REG_OUT_DMA_THRESHOLD_SHIFT 11 + +#define ATI_REG_OUT_DMA_LINKPTR 0x38 +#define ATI_REG_OUT_DMA_DT_START 0x3c /* RO */ +#define ATI_REG_OUT_DMA_DT_NEXT 0x40 /* RO */ +#define ATI_REG_OUT_DMA_DT_CUR 0x44 /* RO */ +#define ATI_REG_OUT_DMA_DT_SIZE 0x48 + +#define ATI_REG_SPDF_CMD 0x4c +#define ATI_REG_SPDF_CMD_LFSR (1U<<4) +#define ATI_REG_SPDF_CMD_SINGLE_CH (1U<<5) +#define ATI_REG_SPDF_CMD_LFSR_ACC (0xff<<8) /* RO */ + +#define ATI_REG_SPDF_DMA_LINKPTR 0x50 +#define ATI_REG_SPDF_DMA_DT_START 0x54 /* RO */ +#define ATI_REG_SPDF_DMA_DT_NEXT 0x58 /* RO */ +#define ATI_REG_SPDF_DMA_DT_CUR 0x5c /* RO */ +#define ATI_REG_SPDF_DMA_DT_SIZE 0x60 + +#define ATI_REG_MODEM_MIRROR 0x7c +#define ATI_REG_AUDIO_MIRROR 0x80 + +#define ATI_REG_6CH_REORDER 0x84 /* reorder slots for 6ch */ +#define ATI_REG_6CH_REORDER_EN (1U<<0) /* 3,4,7,8,6,9 -> 3,4,6,9,7,8 */ + +#define ATI_REG_FIFO_FLUSH 0x88 +#define ATI_REG_FIFO_OUT_FLUSH (1U<<0) +#define ATI_REG_FIFO_IN_FLUSH (1U<<1) + +/* LINKPTR */ +#define ATI_REG_LINKPTR_EN (1U<<0) + +/* [INT|OUT|SPDIF]_DMA_DT_SIZE */ +#define ATI_REG_DMA_DT_SIZE (0xffffU<<0) +#define ATI_REG_DMA_FIFO_USED (0x1fU<<16) +#define ATI_REG_DMA_FIFO_FREE (0x1fU<<21) +#define ATI_REG_DMA_STATE (7U<<26) + + +#define ATI_MAX_DESCRIPTORS 256 /* max number of descriptor packets */ + + +struct atiixp; + +/* + * DMA packate descriptor + */ + +struct atiixp_dma_desc { + __le32 addr; /* DMA buffer address */ + u16 status; /* status bits */ + u16 size; /* size of the packet in dwords */ + __le32 next; /* address of the next packet descriptor */ +}; + +/* + * stream enum + */ +enum { ATI_DMA_PLAYBACK, ATI_DMA_CAPTURE, ATI_DMA_SPDIF, NUM_ATI_DMAS }; /* DMAs */ +enum { ATI_PCM_OUT, ATI_PCM_IN, ATI_PCM_SPDIF, NUM_ATI_PCMS }; /* AC97 pcm slots */ +enum { ATI_PCMDEV_ANALOG, ATI_PCMDEV_DIGITAL, NUM_ATI_PCMDEVS }; /* pcm devices */ + +#define NUM_ATI_CODECS 3 + + +/* + * constants and callbacks for each DMA type + */ +struct atiixp_dma_ops { + int type; /* ATI_DMA_XXX */ + unsigned int llp_offset; /* LINKPTR offset */ + unsigned int dt_cur; /* DT_CUR offset */ + /* called from open callback */ + void (*enable_dma)(struct atiixp *chip, int on); + /* called from trigger (START/STOP) */ + void (*enable_transfer)(struct atiixp *chip, int on); + /* called from trigger (STOP only) */ + void (*flush_dma)(struct atiixp *chip); +}; + +/* + * DMA stream + */ +struct atiixp_dma { + const struct atiixp_dma_ops *ops; + struct snd_dma_buffer desc_buf; + struct snd_pcm_substream *substream; /* assigned PCM substream */ + unsigned int buf_addr, buf_bytes; /* DMA buffer address, bytes */ + unsigned int period_bytes, periods; + int opened; + int running; + int suspended; + int pcm_open_flag; + int ac97_pcm_type; /* index # of ac97_pcm to access, -1 = not used */ + unsigned int saved_curptr; +}; + +/* + * ATI IXP chip + */ +struct atiixp { + struct snd_card *card; + struct pci_dev *pci; + + unsigned long addr; + void __iomem *remap_addr; + int irq; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97[NUM_ATI_CODECS]; + + spinlock_t reg_lock; + + struct atiixp_dma dmas[NUM_ATI_DMAS]; + struct ac97_pcm *pcms[NUM_ATI_PCMS]; + struct snd_pcm *pcmdevs[NUM_ATI_PCMDEVS]; + + int max_channels; /* max. channels for PCM out */ + + unsigned int codec_not_ready_bits; /* for codec detection */ + + int spdif_over_aclink; /* passed from the module option */ + struct mutex open_mutex; /* playback open mutex */ +}; + + +/* + */ +static const struct pci_device_id snd_atiixp_ids[] = { + { PCI_VDEVICE(ATI, 0x4341) }, /* SB200 */ + { PCI_VDEVICE(ATI, 0x4361) }, /* SB300 */ + { PCI_VDEVICE(ATI, 0x4370) }, /* SB400 */ + { PCI_VDEVICE(ATI, 0x4382) }, /* SB600 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_atiixp_ids); + +static const struct snd_pci_quirk atiixp_quirks[] = { + SND_PCI_QUIRK(0x105b, 0x0c81, "Foxconn RC4107MA-RS2", 0), + SND_PCI_QUIRK(0x15bd, 0x3100, "DFI RS482", 0), + { } /* terminator */ +}; + +/* + * lowlevel functions + */ + +/* + * update the bits of the given register. + * return 1 if the bits changed. + */ +static int snd_atiixp_update_bits(struct atiixp *chip, unsigned int reg, + unsigned int mask, unsigned int value) +{ + void __iomem *addr = chip->remap_addr + reg; + unsigned int data, old_data; + old_data = data = readl(addr); + data &= ~mask; + data |= value; + if (old_data == data) + return 0; + writel(data, addr); + return 1; +} + +/* + * macros for easy use + */ +#define atiixp_write(chip,reg,value) \ + writel(value, chip->remap_addr + ATI_REG_##reg) +#define atiixp_read(chip,reg) \ + readl(chip->remap_addr + ATI_REG_##reg) +#define atiixp_update(chip,reg,mask,val) \ + snd_atiixp_update_bits(chip, ATI_REG_##reg, mask, val) + +/* + * handling DMA packets + * + * we allocate a linear buffer for the DMA, and split it to each packet. + * in a future version, a scatter-gather buffer should be implemented. + */ + +#define ATI_DESC_LIST_SIZE \ + PAGE_ALIGN(ATI_MAX_DESCRIPTORS * sizeof(struct atiixp_dma_desc)) + +/* + * build packets ring for the given buffer size. + * + * IXP handles the buffer descriptors, which are connected as a linked + * list. although we can change the list dynamically, in this version, + * a static RING of buffer descriptors is used. + * + * the ring is built in this function, and is set up to the hardware. + */ +static int atiixp_build_dma_packets(struct atiixp *chip, struct atiixp_dma *dma, + struct snd_pcm_substream *substream, + unsigned int periods, + unsigned int period_bytes) +{ + unsigned int i; + u32 addr, desc_addr; + + if (periods > ATI_MAX_DESCRIPTORS) + return -ENOMEM; + + if (dma->desc_buf.area == NULL) { + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + ATI_DESC_LIST_SIZE, + &dma->desc_buf) < 0) + return -ENOMEM; + dma->period_bytes = dma->periods = 0; /* clear */ + } + + if (dma->periods == periods && dma->period_bytes == period_bytes) + return 0; + + /* reset DMA before changing the descriptor table */ + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + writel(0, chip->remap_addr + dma->ops->llp_offset); + dma->ops->enable_dma(chip, 0); + dma->ops->enable_dma(chip, 1); + } + + /* fill the entries */ + addr = (u32)substream->runtime->dma_addr; + desc_addr = (u32)dma->desc_buf.addr; + for (i = 0; i < periods; i++) { + struct atiixp_dma_desc *desc; + desc = &((struct atiixp_dma_desc *)dma->desc_buf.area)[i]; + desc->addr = cpu_to_le32(addr); + desc->status = 0; + desc->size = period_bytes >> 2; /* in dwords */ + desc_addr += sizeof(struct atiixp_dma_desc); + if (i == periods - 1) + desc->next = cpu_to_le32((u32)dma->desc_buf.addr); + else + desc->next = cpu_to_le32(desc_addr); + addr += period_bytes; + } + + writel((u32)dma->desc_buf.addr | ATI_REG_LINKPTR_EN, + chip->remap_addr + dma->ops->llp_offset); + + dma->period_bytes = period_bytes; + dma->periods = periods; + + return 0; +} + +/* + * remove the ring buffer and release it if assigned + */ +static void atiixp_clear_dma_packets(struct atiixp *chip, struct atiixp_dma *dma, + struct snd_pcm_substream *substream) +{ + if (dma->desc_buf.area) { + writel(0, chip->remap_addr + dma->ops->llp_offset); + snd_dma_free_pages(&dma->desc_buf); + dma->desc_buf.area = NULL; + } +} + +/* + * AC97 interface + */ +static int snd_atiixp_acquire_codec(struct atiixp *chip) +{ + int timeout = 1000; + + while (atiixp_read(chip, PHYS_OUT_ADDR) & ATI_REG_PHYS_OUT_ADDR_EN) { + if (! timeout--) { + dev_warn(chip->card->dev, "codec acquire timeout\n"); + return -EBUSY; + } + udelay(1); + } + return 0; +} + +static unsigned short snd_atiixp_codec_read(struct atiixp *chip, unsigned short codec, unsigned short reg) +{ + unsigned int data; + int timeout; + + if (snd_atiixp_acquire_codec(chip) < 0) + return 0xffff; + data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | + ATI_REG_PHYS_OUT_ADDR_EN | + ATI_REG_PHYS_OUT_RW | + codec; + atiixp_write(chip, PHYS_OUT_ADDR, data); + if (snd_atiixp_acquire_codec(chip) < 0) + return 0xffff; + timeout = 1000; + do { + data = atiixp_read(chip, PHYS_IN_ADDR); + if (data & ATI_REG_PHYS_IN_READ_FLAG) + return data >> ATI_REG_PHYS_IN_DATA_SHIFT; + udelay(1); + } while (--timeout); + /* time out may happen during reset */ + if (reg < 0x7c) + dev_warn(chip->card->dev, "codec read timeout (reg %x)\n", reg); + return 0xffff; +} + + +static void snd_atiixp_codec_write(struct atiixp *chip, unsigned short codec, + unsigned short reg, unsigned short val) +{ + unsigned int data; + + if (snd_atiixp_acquire_codec(chip) < 0) + return; + data = ((unsigned int)val << ATI_REG_PHYS_OUT_DATA_SHIFT) | + ((unsigned int)reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | + ATI_REG_PHYS_OUT_ADDR_EN | codec; + atiixp_write(chip, PHYS_OUT_ADDR, data); +} + + +static unsigned short snd_atiixp_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct atiixp *chip = ac97->private_data; + return snd_atiixp_codec_read(chip, ac97->num, reg); + +} + +static void snd_atiixp_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct atiixp *chip = ac97->private_data; + snd_atiixp_codec_write(chip, ac97->num, reg, val); +} + +/* + * reset AC link + */ +static int snd_atiixp_aclink_reset(struct atiixp *chip) +{ + int timeout; + + /* reset powerdoewn */ + if (atiixp_update(chip, CMD, ATI_REG_CMD_POWERDOWN, 0)) + udelay(10); + + /* perform a software reset */ + atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, ATI_REG_CMD_AC_SOFT_RESET); + atiixp_read(chip, CMD); + udelay(10); + atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, 0); + + timeout = 10; + while (! (atiixp_read(chip, CMD) & ATI_REG_CMD_ACLINK_ACTIVE)) { + /* do a hard reset */ + atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET, + ATI_REG_CMD_AC_SYNC); + atiixp_read(chip, CMD); + mdelay(1); + atiixp_update(chip, CMD, ATI_REG_CMD_AC_RESET, ATI_REG_CMD_AC_RESET); + if (!--timeout) { + dev_err(chip->card->dev, "codec reset timeout\n"); + break; + } + } + + /* deassert RESET and assert SYNC to make sure */ + atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET, + ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET); + + return 0; +} + +static int snd_atiixp_aclink_down(struct atiixp *chip) +{ + // if (atiixp_read(chip, MODEM_MIRROR) & 0x1) /* modem running, too? */ + // return -EBUSY; + atiixp_update(chip, CMD, + ATI_REG_CMD_POWERDOWN | ATI_REG_CMD_AC_RESET, + ATI_REG_CMD_POWERDOWN); + return 0; +} + +/* + * auto-detection of codecs + * + * the IXP chip can generate interrupts for the non-existing codecs. + * NEW_FRAME interrupt is used to make sure that the interrupt is generated + * even if all three codecs are connected. + */ + +#define ALL_CODEC_NOT_READY \ + (ATI_REG_ISR_CODEC0_NOT_READY |\ + ATI_REG_ISR_CODEC1_NOT_READY |\ + ATI_REG_ISR_CODEC2_NOT_READY) +#define CODEC_CHECK_BITS (ALL_CODEC_NOT_READY|ATI_REG_ISR_NEW_FRAME) + +static int ac97_probing_bugs(struct pci_dev *pci) +{ + const struct snd_pci_quirk *q; + + q = snd_pci_quirk_lookup(pci, atiixp_quirks); + if (q) { + dev_dbg(&pci->dev, "atiixp quirk for %s. Forcing codec %d\n", + snd_pci_quirk_name(q), q->value); + return q->value; + } + /* this hardware doesn't need workarounds. Probe for codec */ + return -1; +} + +static int snd_atiixp_codec_detect(struct atiixp *chip) +{ + int timeout; + + chip->codec_not_ready_bits = 0; + if (ac97_codec == -1) + ac97_codec = ac97_probing_bugs(chip->pci); + if (ac97_codec >= 0) { + chip->codec_not_ready_bits |= + CODEC_CHECK_BITS ^ (1 << (ac97_codec + 10)); + return 0; + } + + atiixp_write(chip, IER, CODEC_CHECK_BITS); + /* wait for the interrupts */ + timeout = 50; + while (timeout-- > 0) { + mdelay(1); + if (chip->codec_not_ready_bits) + break; + } + atiixp_write(chip, IER, 0); /* disable irqs */ + + if ((chip->codec_not_ready_bits & ALL_CODEC_NOT_READY) == ALL_CODEC_NOT_READY) { + dev_err(chip->card->dev, "no codec detected!\n"); + return -ENXIO; + } + return 0; +} + + +/* + * enable DMA and irqs + */ +static int snd_atiixp_chip_start(struct atiixp *chip) +{ + unsigned int reg; + + /* set up spdif, enable burst mode */ + reg = atiixp_read(chip, CMD); + reg |= 0x02 << ATI_REG_CMD_SPDF_THRESHOLD_SHIFT; + reg |= ATI_REG_CMD_BURST_EN; + atiixp_write(chip, CMD, reg); + + reg = atiixp_read(chip, SPDF_CMD); + reg &= ~(ATI_REG_SPDF_CMD_LFSR|ATI_REG_SPDF_CMD_SINGLE_CH); + atiixp_write(chip, SPDF_CMD, reg); + + /* clear all interrupt source */ + atiixp_write(chip, ISR, 0xffffffff); + /* enable irqs */ + atiixp_write(chip, IER, + ATI_REG_IER_IO_STATUS_EN | + ATI_REG_IER_IN_XRUN_EN | + ATI_REG_IER_OUT_XRUN_EN | + ATI_REG_IER_SPDF_XRUN_EN | + ATI_REG_IER_SPDF_STATUS_EN); + return 0; +} + + +/* + * disable DMA and IRQs + */ +static int snd_atiixp_chip_stop(struct atiixp *chip) +{ + /* clear interrupt source */ + atiixp_write(chip, ISR, atiixp_read(chip, ISR)); + /* disable irqs */ + atiixp_write(chip, IER, 0); + return 0; +} + + +/* + * PCM section + */ + +/* + * pointer callback simplly reads XXX_DMA_DT_CUR register as the current + * position. when SG-buffer is implemented, the offset must be calculated + * correctly... + */ +static snd_pcm_uframes_t snd_atiixp_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct atiixp_dma *dma = runtime->private_data; + unsigned int curptr; + int timeout = 1000; + + while (timeout--) { + curptr = readl(chip->remap_addr + dma->ops->dt_cur); + if (curptr < dma->buf_addr) + continue; + curptr -= dma->buf_addr; + if (curptr >= dma->buf_bytes) + continue; + return bytes_to_frames(runtime, curptr); + } + dev_dbg(chip->card->dev, "invalid DMA pointer read 0x%x (buf=%x)\n", + readl(chip->remap_addr + dma->ops->dt_cur), dma->buf_addr); + return 0; +} + +/* + * XRUN detected, and stop the PCM substream + */ +static void snd_atiixp_xrun_dma(struct atiixp *chip, struct atiixp_dma *dma) +{ + if (! dma->substream || ! dma->running) + return; + dev_dbg(chip->card->dev, "XRUN detected (DMA %d)\n", dma->ops->type); + snd_pcm_stop_xrun(dma->substream); +} + +/* + * the period ack. update the substream. + */ +static void snd_atiixp_update_dma(struct atiixp *chip, struct atiixp_dma *dma) +{ + if (! dma->substream || ! dma->running) + return; + snd_pcm_period_elapsed(dma->substream); +} + +/* set BUS_BUSY interrupt bit if any DMA is running */ +/* call with spinlock held */ +static void snd_atiixp_check_bus_busy(struct atiixp *chip) +{ + unsigned int bus_busy; + if (atiixp_read(chip, CMD) & (ATI_REG_CMD_SEND_EN | + ATI_REG_CMD_RECEIVE_EN | + ATI_REG_CMD_SPDF_OUT_EN)) + bus_busy = ATI_REG_IER_SET_BUS_BUSY; + else + bus_busy = 0; + atiixp_update(chip, IER, ATI_REG_IER_SET_BUS_BUSY, bus_busy); +} + +/* common trigger callback + * calling the lowlevel callbacks in it + */ +static int snd_atiixp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + struct atiixp_dma *dma = substream->runtime->private_data; + int err = 0; + + if (snd_BUG_ON(!dma->ops->enable_transfer || + !dma->ops->flush_dma)) + return -EINVAL; + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (dma->running && dma->suspended && + cmd == SNDRV_PCM_TRIGGER_RESUME) + writel(dma->saved_curptr, chip->remap_addr + + dma->ops->dt_cur); + dma->ops->enable_transfer(chip, 1); + dma->running = 1; + dma->suspended = 0; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + dma->suspended = cmd == SNDRV_PCM_TRIGGER_SUSPEND; + if (dma->running && dma->suspended) + dma->saved_curptr = readl(chip->remap_addr + + dma->ops->dt_cur); + dma->ops->enable_transfer(chip, 0); + dma->running = 0; + break; + default: + err = -EINVAL; + break; + } + if (! err) { + snd_atiixp_check_bus_busy(chip); + if (cmd == SNDRV_PCM_TRIGGER_STOP) { + dma->ops->flush_dma(chip); + snd_atiixp_check_bus_busy(chip); + } + } + return err; +} + + +/* + * lowlevel callbacks for each DMA type + * + * every callback is supposed to be called in chip->reg_lock spinlock + */ + +/* flush FIFO of analog OUT DMA */ +static void atiixp_out_flush_dma(struct atiixp *chip) +{ + atiixp_write(chip, FIFO_FLUSH, ATI_REG_FIFO_OUT_FLUSH); +} + +/* enable/disable analog OUT DMA */ +static void atiixp_out_enable_dma(struct atiixp *chip, int on) +{ + unsigned int data; + data = atiixp_read(chip, CMD); + if (on) { + if (data & ATI_REG_CMD_OUT_DMA_EN) + return; + atiixp_out_flush_dma(chip); + data |= ATI_REG_CMD_OUT_DMA_EN; + } else + data &= ~ATI_REG_CMD_OUT_DMA_EN; + atiixp_write(chip, CMD, data); +} + +/* start/stop transfer over OUT DMA */ +static void atiixp_out_enable_transfer(struct atiixp *chip, int on) +{ + atiixp_update(chip, CMD, ATI_REG_CMD_SEND_EN, + on ? ATI_REG_CMD_SEND_EN : 0); +} + +/* enable/disable analog IN DMA */ +static void atiixp_in_enable_dma(struct atiixp *chip, int on) +{ + atiixp_update(chip, CMD, ATI_REG_CMD_IN_DMA_EN, + on ? ATI_REG_CMD_IN_DMA_EN : 0); +} + +/* start/stop analog IN DMA */ +static void atiixp_in_enable_transfer(struct atiixp *chip, int on) +{ + if (on) { + unsigned int data = atiixp_read(chip, CMD); + if (! (data & ATI_REG_CMD_RECEIVE_EN)) { + data |= ATI_REG_CMD_RECEIVE_EN; +#if 0 /* FIXME: this causes the endless loop */ + /* wait until slot 3/4 are finished */ + while ((atiixp_read(chip, COUNTER) & + ATI_REG_COUNTER_SLOT) != 5) + ; +#endif + atiixp_write(chip, CMD, data); + } + } else + atiixp_update(chip, CMD, ATI_REG_CMD_RECEIVE_EN, 0); +} + +/* flush FIFO of analog IN DMA */ +static void atiixp_in_flush_dma(struct atiixp *chip) +{ + atiixp_write(chip, FIFO_FLUSH, ATI_REG_FIFO_IN_FLUSH); +} + +/* enable/disable SPDIF OUT DMA */ +static void atiixp_spdif_enable_dma(struct atiixp *chip, int on) +{ + atiixp_update(chip, CMD, ATI_REG_CMD_SPDF_DMA_EN, + on ? ATI_REG_CMD_SPDF_DMA_EN : 0); +} + +/* start/stop SPDIF OUT DMA */ +static void atiixp_spdif_enable_transfer(struct atiixp *chip, int on) +{ + unsigned int data; + data = atiixp_read(chip, CMD); + if (on) + data |= ATI_REG_CMD_SPDF_OUT_EN; + else + data &= ~ATI_REG_CMD_SPDF_OUT_EN; + atiixp_write(chip, CMD, data); +} + +/* flush FIFO of SPDIF OUT DMA */ +static void atiixp_spdif_flush_dma(struct atiixp *chip) +{ + int timeout; + + /* DMA off, transfer on */ + atiixp_spdif_enable_dma(chip, 0); + atiixp_spdif_enable_transfer(chip, 1); + + timeout = 100; + do { + if (! (atiixp_read(chip, SPDF_DMA_DT_SIZE) & ATI_REG_DMA_FIFO_USED)) + break; + udelay(1); + } while (timeout-- > 0); + + atiixp_spdif_enable_transfer(chip, 0); +} + +/* set up slots and formats for SPDIF OUT */ +static int snd_atiixp_spdif_prepare(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&chip->reg_lock); + if (chip->spdif_over_aclink) { + unsigned int data; + /* enable slots 10/11 */ + atiixp_update(chip, CMD, ATI_REG_CMD_SPDF_CONFIG_MASK, + ATI_REG_CMD_SPDF_CONFIG_01); + data = atiixp_read(chip, OUT_DMA_SLOT) & ~ATI_REG_OUT_DMA_SLOT_MASK; + data |= ATI_REG_OUT_DMA_SLOT_BIT(10) | + ATI_REG_OUT_DMA_SLOT_BIT(11); + data |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT; + atiixp_write(chip, OUT_DMA_SLOT, data); + atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_OUT, + substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ? + ATI_REG_CMD_INTERLEAVE_OUT : 0); + } else { + atiixp_update(chip, CMD, ATI_REG_CMD_SPDF_CONFIG_MASK, 0); + atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_SPDF, 0); + } + return 0; +} + +/* set up slots and formats for analog OUT */ +static int snd_atiixp_playback_prepare(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + unsigned int data; + + guard(spinlock_irq)(&chip->reg_lock); + data = atiixp_read(chip, OUT_DMA_SLOT) & ~ATI_REG_OUT_DMA_SLOT_MASK; + switch (substream->runtime->channels) { + case 8: + data |= ATI_REG_OUT_DMA_SLOT_BIT(10) | + ATI_REG_OUT_DMA_SLOT_BIT(11); + fallthrough; + case 6: + data |= ATI_REG_OUT_DMA_SLOT_BIT(7) | + ATI_REG_OUT_DMA_SLOT_BIT(8); + fallthrough; + case 4: + data |= ATI_REG_OUT_DMA_SLOT_BIT(6) | + ATI_REG_OUT_DMA_SLOT_BIT(9); + fallthrough; + default: + data |= ATI_REG_OUT_DMA_SLOT_BIT(3) | + ATI_REG_OUT_DMA_SLOT_BIT(4); + break; + } + + /* set output threshold */ + data |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT; + atiixp_write(chip, OUT_DMA_SLOT, data); + + atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_OUT, + substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ? + ATI_REG_CMD_INTERLEAVE_OUT : 0); + + /* + * enable 6 channel re-ordering bit if needed + */ + atiixp_update(chip, 6CH_REORDER, ATI_REG_6CH_REORDER_EN, + substream->runtime->channels >= 6 ? ATI_REG_6CH_REORDER_EN: 0); + + return 0; +} + +/* set up slots and formats for analog IN */ +static int snd_atiixp_capture_prepare(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&chip->reg_lock); + atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_IN, + substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ? + ATI_REG_CMD_INTERLEAVE_IN : 0); + return 0; +} + +/* + * hw_params - allocate the buffer and set up buffer descriptors + */ +static int snd_atiixp_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + struct atiixp_dma *dma = substream->runtime->private_data; + int err; + + dma->buf_addr = substream->runtime->dma_addr; + dma->buf_bytes = params_buffer_bytes(hw_params); + + err = atiixp_build_dma_packets(chip, dma, substream, + params_periods(hw_params), + params_period_bytes(hw_params)); + if (err < 0) + return err; + + if (dma->ac97_pcm_type >= 0) { + struct ac97_pcm *pcm = chip->pcms[dma->ac97_pcm_type]; + /* PCM is bound to AC97 codec(s) + * set up the AC97 codecs + */ + if (dma->pcm_open_flag) { + snd_ac97_pcm_close(pcm); + dma->pcm_open_flag = 0; + } + err = snd_ac97_pcm_open(pcm, params_rate(hw_params), + params_channels(hw_params), + pcm->r[0].slots); + if (err >= 0) + dma->pcm_open_flag = 1; + } + + return err; +} + +static int snd_atiixp_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + struct atiixp_dma *dma = substream->runtime->private_data; + + if (dma->pcm_open_flag) { + struct ac97_pcm *pcm = chip->pcms[dma->ac97_pcm_type]; + snd_ac97_pcm_close(pcm); + dma->pcm_open_flag = 0; + } + atiixp_clear_dma_packets(chip, dma, substream); + return 0; +} + + +/* + * pcm hardware definition, identical for all DMA types + */ +static const struct snd_pcm_hardware snd_atiixp_pcm_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 256 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = ATI_MAX_DESCRIPTORS, +}; + +static int snd_atiixp_pcm_open(struct snd_pcm_substream *substream, + struct atiixp_dma *dma, int pcm_type) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + if (snd_BUG_ON(!dma->ops || !dma->ops->enable_dma)) + return -EINVAL; + + if (dma->opened) + return -EBUSY; + dma->substream = substream; + runtime->hw = snd_atiixp_pcm_hw; + dma->ac97_pcm_type = pcm_type; + if (pcm_type >= 0) { + runtime->hw.rates = chip->pcms[pcm_type]->rates; + snd_pcm_limit_hw_rates(runtime); + } else { + /* direct SPDIF */ + runtime->hw.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE; + } + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + runtime->private_data = dma; + + /* enable DMA bits */ + scoped_guard(spinlock_irq, &chip->reg_lock) { + dma->ops->enable_dma(chip, 1); + } + dma->opened = 1; + + return 0; +} + +static int snd_atiixp_pcm_close(struct snd_pcm_substream *substream, + struct atiixp_dma *dma) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + /* disable DMA bits */ + if (snd_BUG_ON(!dma->ops || !dma->ops->enable_dma)) + return -EINVAL; + scoped_guard(spinlock_irq, &chip->reg_lock) { + dma->ops->enable_dma(chip, 0); + } + dma->substream = NULL; + dma->opened = 0; + return 0; +} + +/* + */ +static int snd_atiixp_playback_open(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + int err; + + guard(mutex)(&chip->open_mutex); + err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 0); + if (err < 0) + return err; + substream->runtime->hw.channels_max = chip->max_channels; + if (chip->max_channels > 2) + /* channels must be even */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + return 0; +} + +static int snd_atiixp_playback_close(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + + guard(mutex)(&chip->open_mutex); + return snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]); +} + +static int snd_atiixp_capture_open(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + return snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_CAPTURE], 1); +} + +static int snd_atiixp_capture_close(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + return snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_CAPTURE]); +} + +static int snd_atiixp_spdif_open(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + + guard(mutex)(&chip->open_mutex); + if (chip->spdif_over_aclink) /* share DMA_PLAYBACK */ + return snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 2); + else + return snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_SPDIF], -1); +} + +static int snd_atiixp_spdif_close(struct snd_pcm_substream *substream) +{ + struct atiixp *chip = snd_pcm_substream_chip(substream); + + guard(mutex)(&chip->open_mutex); + if (chip->spdif_over_aclink) + return snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]); + else + return snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_SPDIF]); +} + +/* AC97 playback */ +static const struct snd_pcm_ops snd_atiixp_playback_ops = { + .open = snd_atiixp_playback_open, + .close = snd_atiixp_playback_close, + .hw_params = snd_atiixp_pcm_hw_params, + .hw_free = snd_atiixp_pcm_hw_free, + .prepare = snd_atiixp_playback_prepare, + .trigger = snd_atiixp_pcm_trigger, + .pointer = snd_atiixp_pcm_pointer, +}; + +/* AC97 capture */ +static const struct snd_pcm_ops snd_atiixp_capture_ops = { + .open = snd_atiixp_capture_open, + .close = snd_atiixp_capture_close, + .hw_params = snd_atiixp_pcm_hw_params, + .hw_free = snd_atiixp_pcm_hw_free, + .prepare = snd_atiixp_capture_prepare, + .trigger = snd_atiixp_pcm_trigger, + .pointer = snd_atiixp_pcm_pointer, +}; + +/* SPDIF playback */ +static const struct snd_pcm_ops snd_atiixp_spdif_ops = { + .open = snd_atiixp_spdif_open, + .close = snd_atiixp_spdif_close, + .hw_params = snd_atiixp_pcm_hw_params, + .hw_free = snd_atiixp_pcm_hw_free, + .prepare = snd_atiixp_spdif_prepare, + .trigger = snd_atiixp_pcm_trigger, + .pointer = snd_atiixp_pcm_pointer, +}; + +static const struct ac97_pcm atiixp_pcm_defs[] = { + /* front PCM */ + { + .exclusive = 1, + .r = { { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT) | + (1 << AC97_SLOT_PCM_CENTER) | + (1 << AC97_SLOT_PCM_SLEFT) | + (1 << AC97_SLOT_PCM_SRIGHT) | + (1 << AC97_SLOT_LFE) + } + } + }, + /* PCM IN #1 */ + { + .stream = 1, + .exclusive = 1, + .r = { { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT) + } + } + }, + /* S/PDIF OUT (optional) */ + { + .exclusive = 1, + .spdif = 1, + .r = { { + .slots = (1 << AC97_SLOT_SPDIF_LEFT2) | + (1 << AC97_SLOT_SPDIF_RIGHT2) + } + } + }, +}; + +static const struct atiixp_dma_ops snd_atiixp_playback_dma_ops = { + .type = ATI_DMA_PLAYBACK, + .llp_offset = ATI_REG_OUT_DMA_LINKPTR, + .dt_cur = ATI_REG_OUT_DMA_DT_CUR, + .enable_dma = atiixp_out_enable_dma, + .enable_transfer = atiixp_out_enable_transfer, + .flush_dma = atiixp_out_flush_dma, +}; + +static const struct atiixp_dma_ops snd_atiixp_capture_dma_ops = { + .type = ATI_DMA_CAPTURE, + .llp_offset = ATI_REG_IN_DMA_LINKPTR, + .dt_cur = ATI_REG_IN_DMA_DT_CUR, + .enable_dma = atiixp_in_enable_dma, + .enable_transfer = atiixp_in_enable_transfer, + .flush_dma = atiixp_in_flush_dma, +}; + +static const struct atiixp_dma_ops snd_atiixp_spdif_dma_ops = { + .type = ATI_DMA_SPDIF, + .llp_offset = ATI_REG_SPDF_DMA_LINKPTR, + .dt_cur = ATI_REG_SPDF_DMA_DT_CUR, + .enable_dma = atiixp_spdif_enable_dma, + .enable_transfer = atiixp_spdif_enable_transfer, + .flush_dma = atiixp_spdif_flush_dma, +}; + + +static int snd_atiixp_pcm_new(struct atiixp *chip) +{ + struct snd_pcm *pcm; + struct snd_pcm_chmap *chmap; + struct snd_ac97_bus *pbus = chip->ac97_bus; + int err, i, num_pcms; + + /* initialize constants */ + chip->dmas[ATI_DMA_PLAYBACK].ops = &snd_atiixp_playback_dma_ops; + chip->dmas[ATI_DMA_CAPTURE].ops = &snd_atiixp_capture_dma_ops; + if (! chip->spdif_over_aclink) + chip->dmas[ATI_DMA_SPDIF].ops = &snd_atiixp_spdif_dma_ops; + + /* assign AC97 pcm */ + if (chip->spdif_over_aclink) + num_pcms = 3; + else + num_pcms = 2; + err = snd_ac97_pcm_assign(pbus, num_pcms, atiixp_pcm_defs); + if (err < 0) + return err; + for (i = 0; i < num_pcms; i++) + chip->pcms[i] = &pbus->pcms[i]; + + chip->max_channels = 2; + if (pbus->pcms[ATI_PCM_OUT].r[0].slots & (1 << AC97_SLOT_PCM_SLEFT)) { + if (pbus->pcms[ATI_PCM_OUT].r[0].slots & (1 << AC97_SLOT_LFE)) + chip->max_channels = 6; + else + chip->max_channels = 4; + } + + /* PCM #0: analog I/O */ + err = snd_pcm_new(chip->card, "ATI IXP AC97", + ATI_PCMDEV_ANALOG, 1, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_atiixp_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_atiixp_capture_ops); + pcm->private_data = chip; + strscpy(pcm->name, "ATI IXP AC97"); + chip->pcmdevs[ATI_PCMDEV_ANALOG] = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 128*1024); + + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_alt_chmaps, chip->max_channels, 0, + &chmap); + if (err < 0) + return err; + chmap->channel_mask = SND_PCM_CHMAP_MASK_2468; + chip->ac97[0]->chmaps[SNDRV_PCM_STREAM_PLAYBACK] = chmap; + + /* no SPDIF support on codec? */ + if (chip->pcms[ATI_PCM_SPDIF] && ! chip->pcms[ATI_PCM_SPDIF]->rates) + return 0; + + /* FIXME: non-48k sample rate doesn't work on my test machine with AD1888 */ + if (chip->pcms[ATI_PCM_SPDIF]) + chip->pcms[ATI_PCM_SPDIF]->rates = SNDRV_PCM_RATE_48000; + + /* PCM #1: spdif playback */ + err = snd_pcm_new(chip->card, "ATI IXP IEC958", + ATI_PCMDEV_DIGITAL, 1, 0, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_atiixp_spdif_ops); + pcm->private_data = chip; + if (chip->spdif_over_aclink) + strscpy(pcm->name, "ATI IXP IEC958 (AC97)"); + else + strscpy(pcm->name, "ATI IXP IEC958 (Direct)"); + chip->pcmdevs[ATI_PCMDEV_DIGITAL] = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 128*1024); + + /* pre-select AC97 SPDIF slots 10/11 */ + for (i = 0; i < NUM_ATI_CODECS; i++) { + if (chip->ac97[i]) + snd_ac97_update_bits(chip->ac97[i], + AC97_EXTENDED_STATUS, + 0x03 << 4, 0x03 << 4); + } + + return 0; +} + + + +/* + * interrupt handler + */ +static irqreturn_t snd_atiixp_interrupt(int irq, void *dev_id) +{ + struct atiixp *chip = dev_id; + unsigned int status; + + status = atiixp_read(chip, ISR); + + if (! status) + return IRQ_NONE; + + /* process audio DMA */ + if (status & ATI_REG_ISR_OUT_XRUN) + snd_atiixp_xrun_dma(chip, &chip->dmas[ATI_DMA_PLAYBACK]); + else if (status & ATI_REG_ISR_OUT_STATUS) + snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_PLAYBACK]); + if (status & ATI_REG_ISR_IN_XRUN) + snd_atiixp_xrun_dma(chip, &chip->dmas[ATI_DMA_CAPTURE]); + else if (status & ATI_REG_ISR_IN_STATUS) + snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_CAPTURE]); + if (! chip->spdif_over_aclink) { + if (status & ATI_REG_ISR_SPDF_XRUN) + snd_atiixp_xrun_dma(chip, &chip->dmas[ATI_DMA_SPDIF]); + else if (status & ATI_REG_ISR_SPDF_STATUS) + snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_SPDIF]); + } + + /* for codec detection */ + if (status & CODEC_CHECK_BITS) { + unsigned int detected; + detected = status & CODEC_CHECK_BITS; + guard(spinlock)(&chip->reg_lock); + chip->codec_not_ready_bits |= detected; + atiixp_update(chip, IER, detected, 0); /* disable the detected irqs */ + } + + /* ack */ + atiixp_write(chip, ISR, status); + + return IRQ_HANDLED; +} + + +/* + * ac97 mixer section + */ + +static const struct ac97_quirk ac97_quirks[] = { + { + .subvendor = 0x103c, + .subdevice = 0x006b, + .name = "HP Pavilion ZV5030US", + .type = AC97_TUNE_MUTE_LED + }, + { + .subvendor = 0x103c, + .subdevice = 0x308b, + .name = "HP nx6125", + .type = AC97_TUNE_MUTE_LED + }, + { + .subvendor = 0x103c, + .subdevice = 0x3091, + .name = "unknown HP", + .type = AC97_TUNE_MUTE_LED + }, + { } /* terminator */ +}; + +static int snd_atiixp_mixer_new(struct atiixp *chip, int clock, + const char *quirk_override) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int i, err; + int codec_count; + static const struct snd_ac97_bus_ops ops = { + .write = snd_atiixp_ac97_write, + .read = snd_atiixp_ac97_read, + }; + static const unsigned int codec_skip[NUM_ATI_CODECS] = { + ATI_REG_ISR_CODEC0_NOT_READY, + ATI_REG_ISR_CODEC1_NOT_READY, + ATI_REG_ISR_CODEC2_NOT_READY, + }; + + if (snd_atiixp_codec_detect(chip) < 0) + return -ENXIO; + + err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus); + if (err < 0) + return err; + pbus->clock = clock; + chip->ac97_bus = pbus; + + codec_count = 0; + for (i = 0; i < NUM_ATI_CODECS; i++) { + if (chip->codec_not_ready_bits & codec_skip[i]) + continue; + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.pci = chip->pci; + ac97.num = i; + ac97.scaps = AC97_SCAP_SKIP_MODEM | AC97_SCAP_POWER_SAVE; + if (! chip->spdif_over_aclink) + ac97.scaps |= AC97_SCAP_NO_SPDIF; + err = snd_ac97_mixer(pbus, &ac97, &chip->ac97[i]); + if (err < 0) { + chip->ac97[i] = NULL; /* to be sure */ + dev_dbg(chip->card->dev, + "codec %d not available for audio\n", i); + continue; + } + codec_count++; + } + + if (! codec_count) { + dev_err(chip->card->dev, "no codec available\n"); + return -ENODEV; + } + + snd_ac97_tune_hardware(chip->ac97[0], ac97_quirks, quirk_override); + + return 0; +} + + +/* + * power management + */ +static int snd_atiixp_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct atiixp *chip = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + for (i = 0; i < NUM_ATI_CODECS; i++) + snd_ac97_suspend(chip->ac97[i]); + snd_atiixp_aclink_down(chip); + snd_atiixp_chip_stop(chip); + return 0; +} + +static int snd_atiixp_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct atiixp *chip = card->private_data; + int i; + + snd_atiixp_aclink_reset(chip); + snd_atiixp_chip_start(chip); + + for (i = 0; i < NUM_ATI_CODECS; i++) + snd_ac97_resume(chip->ac97[i]); + + for (i = 0; i < NUM_ATI_PCMDEVS; i++) + if (chip->pcmdevs[i]) { + struct atiixp_dma *dma = &chip->dmas[i]; + if (dma->substream && dma->suspended) { + dma->ops->enable_dma(chip, 1); + dma->substream->ops->prepare(dma->substream); + writel((u32)dma->desc_buf.addr | ATI_REG_LINKPTR_EN, + chip->remap_addr + dma->ops->llp_offset); + } + } + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_atiixp_pm, snd_atiixp_suspend, snd_atiixp_resume); + +/* + * proc interface for register dump + */ + +static void snd_atiixp_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct atiixp *chip = entry->private_data; + int i; + + for (i = 0; i < 256; i += 4) + snd_iprintf(buffer, "%02x: %08x\n", i, readl(chip->remap_addr + i)); +} + +static void snd_atiixp_proc_init(struct atiixp *chip) +{ + snd_card_ro_proc_new(chip->card, "atiixp", chip, snd_atiixp_proc_read); +} + + +/* + * destructor + */ + +static void snd_atiixp_free(struct snd_card *card) +{ + snd_atiixp_chip_stop(card->private_data); +} + +/* + * constructor for chip instance + */ +static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) +{ + struct atiixp *chip = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); + mutex_init(&chip->open_mutex); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->remap_addr = pcim_iomap_region(pci, 0, "ATI IXP AC97"); + if (IS_ERR(chip->remap_addr)) + return PTR_ERR(chip->remap_addr); + chip->addr = pci_resource_start(pci, 0); + + if (devm_request_irq(&pci->dev, pci->irq, snd_atiixp_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_atiixp_free; + pci_set_master(pci); + + return 0; +} + + +static int __snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct atiixp *chip; + int err; + + err = snd_devm_card_new(&pci->dev, index, id, THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + strscpy(card->driver, spdif_aclink ? "ATIIXP" : "ATIIXP-SPDMA"); + strscpy(card->shortname, "ATI IXP"); + err = snd_atiixp_init(card, pci); + if (err < 0) + return err; + + err = snd_atiixp_aclink_reset(chip); + if (err < 0) + return err; + + chip->spdif_over_aclink = spdif_aclink; + + err = snd_atiixp_mixer_new(chip, ac97_clock, ac97_quirk); + if (err < 0) + return err; + + err = snd_atiixp_pcm_new(chip); + if (err < 0) + return err; + + snd_atiixp_proc_init(chip); + + snd_atiixp_chip_start(chip); + + snprintf(card->longname, sizeof(card->longname), + "%s rev %x with %s at %#lx, irq %i", card->shortname, + pci->revision, + chip->ac97[0] ? snd_ac97_get_short_name(chip->ac97[0]) : "?", + chip->addr, chip->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + return 0; +} + +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); +} + +static struct pci_driver atiixp_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_atiixp_ids, + .probe = snd_atiixp_probe, + .driver = { + .pm = &snd_atiixp_pm, + }, +}; + +module_pci_driver(atiixp_driver); diff --git a/sound/pci/atiixp_modem.c b/sound/pci/atiixp_modem.c new file mode 100644 index 000000000..8aaeb197c --- /dev/null +++ b/sound/pci/atiixp_modem.c @@ -0,0 +1,1246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ATI IXP 150/200/250 AC97 modem controllers + * + * Copyright (c) 2004 Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("ATI IXP MC97 controller"); +MODULE_LICENSE("GPL"); + +static int index = -2; /* Exclude the first card */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static int ac97_clock = 48000; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for ATI IXP controller."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for ATI IXP controller."); +module_param(ac97_clock, int, 0444); +MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz)."); + +/* just for backward compatibility */ +static bool enable; +module_param(enable, bool, 0444); + + +/* + */ + +#define ATI_REG_ISR 0x00 /* interrupt source */ +#define ATI_REG_ISR_MODEM_IN_XRUN (1U<<0) +#define ATI_REG_ISR_MODEM_IN_STATUS (1U<<1) +#define ATI_REG_ISR_MODEM_OUT1_XRUN (1U<<2) +#define ATI_REG_ISR_MODEM_OUT1_STATUS (1U<<3) +#define ATI_REG_ISR_MODEM_OUT2_XRUN (1U<<4) +#define ATI_REG_ISR_MODEM_OUT2_STATUS (1U<<5) +#define ATI_REG_ISR_MODEM_OUT3_XRUN (1U<<6) +#define ATI_REG_ISR_MODEM_OUT3_STATUS (1U<<7) +#define ATI_REG_ISR_PHYS_INTR (1U<<8) +#define ATI_REG_ISR_PHYS_MISMATCH (1U<<9) +#define ATI_REG_ISR_CODEC0_NOT_READY (1U<<10) +#define ATI_REG_ISR_CODEC1_NOT_READY (1U<<11) +#define ATI_REG_ISR_CODEC2_NOT_READY (1U<<12) +#define ATI_REG_ISR_NEW_FRAME (1U<<13) +#define ATI_REG_ISR_MODEM_GPIO_DATA (1U<<14) + +#define ATI_REG_IER 0x04 /* interrupt enable */ +#define ATI_REG_IER_MODEM_IN_XRUN_EN (1U<<0) +#define ATI_REG_IER_MODEM_STATUS_EN (1U<<1) +#define ATI_REG_IER_MODEM_OUT1_XRUN_EN (1U<<2) +#define ATI_REG_IER_MODEM_OUT2_XRUN_EN (1U<<4) +#define ATI_REG_IER_MODEM_OUT3_XRUN_EN (1U<<6) +#define ATI_REG_IER_PHYS_INTR_EN (1U<<8) +#define ATI_REG_IER_PHYS_MISMATCH_EN (1U<<9) +#define ATI_REG_IER_CODEC0_INTR_EN (1U<<10) +#define ATI_REG_IER_CODEC1_INTR_EN (1U<<11) +#define ATI_REG_IER_CODEC2_INTR_EN (1U<<12) +#define ATI_REG_IER_NEW_FRAME_EN (1U<<13) /* (RO */ +#define ATI_REG_IER_MODEM_GPIO_DATA_EN (1U<<14) /* (WO) modem is running */ +#define ATI_REG_IER_MODEM_SET_BUS_BUSY (1U<<15) + +#define ATI_REG_CMD 0x08 /* command */ +#define ATI_REG_CMD_POWERDOWN (1U<<0) +#define ATI_REG_CMD_MODEM_RECEIVE_EN (1U<<1) /* modem only */ +#define ATI_REG_CMD_MODEM_SEND1_EN (1U<<2) /* modem only */ +#define ATI_REG_CMD_MODEM_SEND2_EN (1U<<3) /* modem only */ +#define ATI_REG_CMD_MODEM_SEND3_EN (1U<<4) /* modem only */ +#define ATI_REG_CMD_MODEM_STATUS_MEM (1U<<5) /* modem only */ +#define ATI_REG_CMD_MODEM_IN_DMA_EN (1U<<8) /* modem only */ +#define ATI_REG_CMD_MODEM_OUT_DMA1_EN (1U<<9) /* modem only */ +#define ATI_REG_CMD_MODEM_OUT_DMA2_EN (1U<<10) /* modem only */ +#define ATI_REG_CMD_MODEM_OUT_DMA3_EN (1U<<11) /* modem only */ +#define ATI_REG_CMD_AUDIO_PRESENT (1U<<20) +#define ATI_REG_CMD_MODEM_GPIO_THRU_DMA (1U<<22) /* modem only */ +#define ATI_REG_CMD_LOOPBACK_EN (1U<<23) +#define ATI_REG_CMD_PACKED_DIS (1U<<24) +#define ATI_REG_CMD_BURST_EN (1U<<25) +#define ATI_REG_CMD_PANIC_EN (1U<<26) +#define ATI_REG_CMD_MODEM_PRESENT (1U<<27) +#define ATI_REG_CMD_ACLINK_ACTIVE (1U<<28) +#define ATI_REG_CMD_AC_SOFT_RESET (1U<<29) +#define ATI_REG_CMD_AC_SYNC (1U<<30) +#define ATI_REG_CMD_AC_RESET (1U<<31) + +#define ATI_REG_PHYS_OUT_ADDR 0x0c +#define ATI_REG_PHYS_OUT_CODEC_MASK (3U<<0) +#define ATI_REG_PHYS_OUT_RW (1U<<2) +#define ATI_REG_PHYS_OUT_ADDR_EN (1U<<8) +#define ATI_REG_PHYS_OUT_ADDR_SHIFT 9 +#define ATI_REG_PHYS_OUT_DATA_SHIFT 16 + +#define ATI_REG_PHYS_IN_ADDR 0x10 +#define ATI_REG_PHYS_IN_READ_FLAG (1U<<8) +#define ATI_REG_PHYS_IN_ADDR_SHIFT 9 +#define ATI_REG_PHYS_IN_DATA_SHIFT 16 + +#define ATI_REG_SLOTREQ 0x14 + +#define ATI_REG_COUNTER 0x18 +#define ATI_REG_COUNTER_SLOT (3U<<0) /* slot # */ +#define ATI_REG_COUNTER_BITCLOCK (31U<<8) + +#define ATI_REG_IN_FIFO_THRESHOLD 0x1c + +#define ATI_REG_MODEM_IN_DMA_LINKPTR 0x20 +#define ATI_REG_MODEM_IN_DMA_DT_START 0x24 /* RO */ +#define ATI_REG_MODEM_IN_DMA_DT_NEXT 0x28 /* RO */ +#define ATI_REG_MODEM_IN_DMA_DT_CUR 0x2c /* RO */ +#define ATI_REG_MODEM_IN_DMA_DT_SIZE 0x30 +#define ATI_REG_MODEM_OUT_FIFO 0x34 /* output threshold */ +#define ATI_REG_MODEM_OUT1_DMA_THRESHOLD_MASK (0xf<<16) +#define ATI_REG_MODEM_OUT1_DMA_THRESHOLD_SHIFT 16 +#define ATI_REG_MODEM_OUT_DMA1_LINKPTR 0x38 +#define ATI_REG_MODEM_OUT_DMA2_LINKPTR 0x3c +#define ATI_REG_MODEM_OUT_DMA3_LINKPTR 0x40 +#define ATI_REG_MODEM_OUT_DMA1_DT_START 0x44 +#define ATI_REG_MODEM_OUT_DMA1_DT_NEXT 0x48 +#define ATI_REG_MODEM_OUT_DMA1_DT_CUR 0x4c +#define ATI_REG_MODEM_OUT_DMA2_DT_START 0x50 +#define ATI_REG_MODEM_OUT_DMA2_DT_NEXT 0x54 +#define ATI_REG_MODEM_OUT_DMA2_DT_CUR 0x58 +#define ATI_REG_MODEM_OUT_DMA3_DT_START 0x5c +#define ATI_REG_MODEM_OUT_DMA3_DT_NEXT 0x60 +#define ATI_REG_MODEM_OUT_DMA3_DT_CUR 0x64 +#define ATI_REG_MODEM_OUT_DMA12_DT_SIZE 0x68 +#define ATI_REG_MODEM_OUT_DMA3_DT_SIZE 0x6c +#define ATI_REG_MODEM_OUT_FIFO_USED 0x70 +#define ATI_REG_MODEM_OUT_GPIO 0x74 +#define ATI_REG_MODEM_OUT_GPIO_EN 1 +#define ATI_REG_MODEM_OUT_GPIO_DATA_SHIFT 5 +#define ATI_REG_MODEM_IN_GPIO 0x78 + +#define ATI_REG_MODEM_MIRROR 0x7c +#define ATI_REG_AUDIO_MIRROR 0x80 + +#define ATI_REG_MODEM_FIFO_FLUSH 0x88 +#define ATI_REG_MODEM_FIFO_OUT1_FLUSH (1U<<0) +#define ATI_REG_MODEM_FIFO_OUT2_FLUSH (1U<<1) +#define ATI_REG_MODEM_FIFO_OUT3_FLUSH (1U<<2) +#define ATI_REG_MODEM_FIFO_IN_FLUSH (1U<<3) + +/* LINKPTR */ +#define ATI_REG_LINKPTR_EN (1U<<0) + +#define ATI_MAX_DESCRIPTORS 256 /* max number of descriptor packets */ + + +struct atiixp_modem; + +/* + * DMA packate descriptor + */ + +struct atiixp_dma_desc { + __le32 addr; /* DMA buffer address */ + u16 status; /* status bits */ + u16 size; /* size of the packet in dwords */ + __le32 next; /* address of the next packet descriptor */ +}; + +/* + * stream enum + */ +enum { ATI_DMA_PLAYBACK, ATI_DMA_CAPTURE, NUM_ATI_DMAS }; /* DMAs */ +enum { ATI_PCM_OUT, ATI_PCM_IN, NUM_ATI_PCMS }; /* AC97 pcm slots */ +enum { ATI_PCMDEV_ANALOG, NUM_ATI_PCMDEVS }; /* pcm devices */ + +#define NUM_ATI_CODECS 3 + + +/* + * constants and callbacks for each DMA type + */ +struct atiixp_dma_ops { + int type; /* ATI_DMA_XXX */ + unsigned int llp_offset; /* LINKPTR offset */ + unsigned int dt_cur; /* DT_CUR offset */ + /* called from open callback */ + void (*enable_dma)(struct atiixp_modem *chip, int on); + /* called from trigger (START/STOP) */ + void (*enable_transfer)(struct atiixp_modem *chip, int on); + /* called from trigger (STOP only) */ + void (*flush_dma)(struct atiixp_modem *chip); +}; + +/* + * DMA stream + */ +struct atiixp_dma { + const struct atiixp_dma_ops *ops; + struct snd_dma_buffer desc_buf; + struct snd_pcm_substream *substream; /* assigned PCM substream */ + unsigned int buf_addr, buf_bytes; /* DMA buffer address, bytes */ + unsigned int period_bytes, periods; + int opened; + int running; + int pcm_open_flag; + int ac97_pcm_type; /* index # of ac97_pcm to access, -1 = not used */ +}; + +/* + * ATI IXP chip + */ +struct atiixp_modem { + struct snd_card *card; + struct pci_dev *pci; + + struct resource *res; /* memory i/o */ + unsigned long addr; + void __iomem *remap_addr; + int irq; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97[NUM_ATI_CODECS]; + + spinlock_t reg_lock; + + struct atiixp_dma dmas[NUM_ATI_DMAS]; + struct ac97_pcm *pcms[NUM_ATI_PCMS]; + struct snd_pcm *pcmdevs[NUM_ATI_PCMDEVS]; + + int max_channels; /* max. channels for PCM out */ + + unsigned int codec_not_ready_bits; /* for codec detection */ + + int spdif_over_aclink; /* passed from the module option */ + struct mutex open_mutex; /* playback open mutex */ +}; + + +/* + */ +static const struct pci_device_id snd_atiixp_ids[] = { + { PCI_VDEVICE(ATI, 0x434d) }, /* SB200 */ + { PCI_VDEVICE(ATI, 0x4378) }, /* SB400 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_atiixp_ids); + + +/* + * lowlevel functions + */ + +/* + * update the bits of the given register. + * return 1 if the bits changed. + */ +static int snd_atiixp_update_bits(struct atiixp_modem *chip, unsigned int reg, + unsigned int mask, unsigned int value) +{ + void __iomem *addr = chip->remap_addr + reg; + unsigned int data, old_data; + old_data = data = readl(addr); + data &= ~mask; + data |= value; + if (old_data == data) + return 0; + writel(data, addr); + return 1; +} + +/* + * macros for easy use + */ +#define atiixp_write(chip,reg,value) \ + writel(value, chip->remap_addr + ATI_REG_##reg) +#define atiixp_read(chip,reg) \ + readl(chip->remap_addr + ATI_REG_##reg) +#define atiixp_update(chip,reg,mask,val) \ + snd_atiixp_update_bits(chip, ATI_REG_##reg, mask, val) + +/* + * handling DMA packets + * + * we allocate a linear buffer for the DMA, and split it to each packet. + * in a future version, a scatter-gather buffer should be implemented. + */ + +#define ATI_DESC_LIST_SIZE \ + PAGE_ALIGN(ATI_MAX_DESCRIPTORS * sizeof(struct atiixp_dma_desc)) + +/* + * build packets ring for the given buffer size. + * + * IXP handles the buffer descriptors, which are connected as a linked + * list. although we can change the list dynamically, in this version, + * a static RING of buffer descriptors is used. + * + * the ring is built in this function, and is set up to the hardware. + */ +static int atiixp_build_dma_packets(struct atiixp_modem *chip, + struct atiixp_dma *dma, + struct snd_pcm_substream *substream, + unsigned int periods, + unsigned int period_bytes) +{ + unsigned int i; + u32 addr, desc_addr; + + if (periods > ATI_MAX_DESCRIPTORS) + return -ENOMEM; + + if (dma->desc_buf.area == NULL) { + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + ATI_DESC_LIST_SIZE, &dma->desc_buf) < 0) + return -ENOMEM; + dma->period_bytes = dma->periods = 0; /* clear */ + } + + if (dma->periods == periods && dma->period_bytes == period_bytes) + return 0; + + /* reset DMA before changing the descriptor table */ + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + writel(0, chip->remap_addr + dma->ops->llp_offset); + dma->ops->enable_dma(chip, 0); + dma->ops->enable_dma(chip, 1); + } + + /* fill the entries */ + addr = (u32)substream->runtime->dma_addr; + desc_addr = (u32)dma->desc_buf.addr; + for (i = 0; i < periods; i++) { + struct atiixp_dma_desc *desc; + desc = &((struct atiixp_dma_desc *)dma->desc_buf.area)[i]; + desc->addr = cpu_to_le32(addr); + desc->status = 0; + desc->size = period_bytes >> 2; /* in dwords */ + desc_addr += sizeof(struct atiixp_dma_desc); + if (i == periods - 1) + desc->next = cpu_to_le32((u32)dma->desc_buf.addr); + else + desc->next = cpu_to_le32(desc_addr); + addr += period_bytes; + } + + writel((u32)dma->desc_buf.addr | ATI_REG_LINKPTR_EN, + chip->remap_addr + dma->ops->llp_offset); + + dma->period_bytes = period_bytes; + dma->periods = periods; + + return 0; +} + +/* + * remove the ring buffer and release it if assigned + */ +static void atiixp_clear_dma_packets(struct atiixp_modem *chip, + struct atiixp_dma *dma, + struct snd_pcm_substream *substream) +{ + if (dma->desc_buf.area) { + writel(0, chip->remap_addr + dma->ops->llp_offset); + snd_dma_free_pages(&dma->desc_buf); + dma->desc_buf.area = NULL; + } +} + +/* + * AC97 interface + */ +static int snd_atiixp_acquire_codec(struct atiixp_modem *chip) +{ + int timeout = 1000; + + while (atiixp_read(chip, PHYS_OUT_ADDR) & ATI_REG_PHYS_OUT_ADDR_EN) { + if (! timeout--) { + dev_warn(chip->card->dev, "codec acquire timeout\n"); + return -EBUSY; + } + udelay(1); + } + return 0; +} + +static unsigned short snd_atiixp_codec_read(struct atiixp_modem *chip, + unsigned short codec, + unsigned short reg) +{ + unsigned int data; + int timeout; + + if (snd_atiixp_acquire_codec(chip) < 0) + return 0xffff; + data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | + ATI_REG_PHYS_OUT_ADDR_EN | + ATI_REG_PHYS_OUT_RW | + codec; + atiixp_write(chip, PHYS_OUT_ADDR, data); + if (snd_atiixp_acquire_codec(chip) < 0) + return 0xffff; + timeout = 1000; + do { + data = atiixp_read(chip, PHYS_IN_ADDR); + if (data & ATI_REG_PHYS_IN_READ_FLAG) + return data >> ATI_REG_PHYS_IN_DATA_SHIFT; + udelay(1); + } while (--timeout); + /* time out may happen during reset */ + if (reg < 0x7c) + dev_warn(chip->card->dev, "codec read timeout (reg %x)\n", reg); + return 0xffff; +} + + +static void snd_atiixp_codec_write(struct atiixp_modem *chip, + unsigned short codec, + unsigned short reg, unsigned short val) +{ + unsigned int data; + + if (snd_atiixp_acquire_codec(chip) < 0) + return; + data = ((unsigned int)val << ATI_REG_PHYS_OUT_DATA_SHIFT) | + ((unsigned int)reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | + ATI_REG_PHYS_OUT_ADDR_EN | codec; + atiixp_write(chip, PHYS_OUT_ADDR, data); +} + + +static unsigned short snd_atiixp_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct atiixp_modem *chip = ac97->private_data; + return snd_atiixp_codec_read(chip, ac97->num, reg); + +} + +static void snd_atiixp_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct atiixp_modem *chip = ac97->private_data; + if (reg == AC97_GPIO_STATUS) { + atiixp_write(chip, MODEM_OUT_GPIO, + (val << ATI_REG_MODEM_OUT_GPIO_DATA_SHIFT) | ATI_REG_MODEM_OUT_GPIO_EN); + return; + } + snd_atiixp_codec_write(chip, ac97->num, reg, val); +} + +/* + * reset AC link + */ +static int snd_atiixp_aclink_reset(struct atiixp_modem *chip) +{ + int timeout; + + /* reset powerdoewn */ + if (atiixp_update(chip, CMD, ATI_REG_CMD_POWERDOWN, 0)) + udelay(10); + + /* perform a software reset */ + atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, ATI_REG_CMD_AC_SOFT_RESET); + atiixp_read(chip, CMD); + udelay(10); + atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, 0); + + timeout = 10; + while (! (atiixp_read(chip, CMD) & ATI_REG_CMD_ACLINK_ACTIVE)) { + /* do a hard reset */ + atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET, + ATI_REG_CMD_AC_SYNC); + atiixp_read(chip, CMD); + msleep(1); + atiixp_update(chip, CMD, ATI_REG_CMD_AC_RESET, ATI_REG_CMD_AC_RESET); + if (!--timeout) { + dev_err(chip->card->dev, "codec reset timeout\n"); + break; + } + } + + /* deassert RESET and assert SYNC to make sure */ + atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET, + ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET); + + return 0; +} + +static int snd_atiixp_aclink_down(struct atiixp_modem *chip) +{ + // if (atiixp_read(chip, MODEM_MIRROR) & 0x1) /* modem running, too? */ + // return -EBUSY; + atiixp_update(chip, CMD, + ATI_REG_CMD_POWERDOWN | ATI_REG_CMD_AC_RESET, + ATI_REG_CMD_POWERDOWN); + return 0; +} + +/* + * auto-detection of codecs + * + * the IXP chip can generate interrupts for the non-existing codecs. + * NEW_FRAME interrupt is used to make sure that the interrupt is generated + * even if all three codecs are connected. + */ + +#define ALL_CODEC_NOT_READY \ + (ATI_REG_ISR_CODEC0_NOT_READY |\ + ATI_REG_ISR_CODEC1_NOT_READY |\ + ATI_REG_ISR_CODEC2_NOT_READY) +#define CODEC_CHECK_BITS (ALL_CODEC_NOT_READY|ATI_REG_ISR_NEW_FRAME) + +static int snd_atiixp_codec_detect(struct atiixp_modem *chip) +{ + int timeout; + + chip->codec_not_ready_bits = 0; + atiixp_write(chip, IER, CODEC_CHECK_BITS); + /* wait for the interrupts */ + timeout = 50; + while (timeout-- > 0) { + msleep(1); + if (chip->codec_not_ready_bits) + break; + } + atiixp_write(chip, IER, 0); /* disable irqs */ + + if ((chip->codec_not_ready_bits & ALL_CODEC_NOT_READY) == ALL_CODEC_NOT_READY) { + dev_err(chip->card->dev, "no codec detected!\n"); + return -ENXIO; + } + return 0; +} + + +/* + * enable DMA and irqs + */ +static int snd_atiixp_chip_start(struct atiixp_modem *chip) +{ + unsigned int reg; + + /* set up spdif, enable burst mode */ + reg = atiixp_read(chip, CMD); + reg |= ATI_REG_CMD_BURST_EN; + if(!(reg & ATI_REG_CMD_MODEM_PRESENT)) + reg |= ATI_REG_CMD_MODEM_PRESENT; + atiixp_write(chip, CMD, reg); + + /* clear all interrupt source */ + atiixp_write(chip, ISR, 0xffffffff); + /* enable irqs */ + atiixp_write(chip, IER, + ATI_REG_IER_MODEM_STATUS_EN | + ATI_REG_IER_MODEM_IN_XRUN_EN | + ATI_REG_IER_MODEM_OUT1_XRUN_EN); + return 0; +} + + +/* + * disable DMA and IRQs + */ +static int snd_atiixp_chip_stop(struct atiixp_modem *chip) +{ + /* clear interrupt source */ + atiixp_write(chip, ISR, atiixp_read(chip, ISR)); + /* disable irqs */ + atiixp_write(chip, IER, 0); + return 0; +} + + +/* + * PCM section + */ + +/* + * pointer callback simplly reads XXX_DMA_DT_CUR register as the current + * position. when SG-buffer is implemented, the offset must be calculated + * correctly... + */ +static snd_pcm_uframes_t snd_atiixp_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct atiixp_dma *dma = runtime->private_data; + unsigned int curptr; + int timeout = 1000; + + while (timeout--) { + curptr = readl(chip->remap_addr + dma->ops->dt_cur); + if (curptr < dma->buf_addr) + continue; + curptr -= dma->buf_addr; + if (curptr >= dma->buf_bytes) + continue; + return bytes_to_frames(runtime, curptr); + } + dev_dbg(chip->card->dev, "invalid DMA pointer read 0x%x (buf=%x)\n", + readl(chip->remap_addr + dma->ops->dt_cur), dma->buf_addr); + return 0; +} + +/* + * XRUN detected, and stop the PCM substream + */ +static void snd_atiixp_xrun_dma(struct atiixp_modem *chip, + struct atiixp_dma *dma) +{ + if (! dma->substream || ! dma->running) + return; + dev_dbg(chip->card->dev, "XRUN detected (DMA %d)\n", dma->ops->type); + snd_pcm_stop_xrun(dma->substream); +} + +/* + * the period ack. update the substream. + */ +static void snd_atiixp_update_dma(struct atiixp_modem *chip, + struct atiixp_dma *dma) +{ + if (! dma->substream || ! dma->running) + return; + snd_pcm_period_elapsed(dma->substream); +} + +/* set BUS_BUSY interrupt bit if any DMA is running */ +/* call with spinlock held */ +static void snd_atiixp_check_bus_busy(struct atiixp_modem *chip) +{ + unsigned int bus_busy; + if (atiixp_read(chip, CMD) & (ATI_REG_CMD_MODEM_SEND1_EN | + ATI_REG_CMD_MODEM_RECEIVE_EN)) + bus_busy = ATI_REG_IER_MODEM_SET_BUS_BUSY; + else + bus_busy = 0; + atiixp_update(chip, IER, ATI_REG_IER_MODEM_SET_BUS_BUSY, bus_busy); +} + +/* common trigger callback + * calling the lowlevel callbacks in it + */ +static int snd_atiixp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + struct atiixp_dma *dma = substream->runtime->private_data; + int err = 0; + + if (snd_BUG_ON(!dma->ops->enable_transfer || + !dma->ops->flush_dma)) + return -EINVAL; + + guard(spinlock)(&chip->reg_lock); + switch(cmd) { + case SNDRV_PCM_TRIGGER_START: + dma->ops->enable_transfer(chip, 1); + dma->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + dma->ops->enable_transfer(chip, 0); + dma->running = 0; + break; + default: + err = -EINVAL; + break; + } + if (! err) { + snd_atiixp_check_bus_busy(chip); + if (cmd == SNDRV_PCM_TRIGGER_STOP) { + dma->ops->flush_dma(chip); + snd_atiixp_check_bus_busy(chip); + } + } + return err; +} + + +/* + * lowlevel callbacks for each DMA type + * + * every callback is supposed to be called in chip->reg_lock spinlock + */ + +/* flush FIFO of analog OUT DMA */ +static void atiixp_out_flush_dma(struct atiixp_modem *chip) +{ + atiixp_write(chip, MODEM_FIFO_FLUSH, ATI_REG_MODEM_FIFO_OUT1_FLUSH); +} + +/* enable/disable analog OUT DMA */ +static void atiixp_out_enable_dma(struct atiixp_modem *chip, int on) +{ + unsigned int data; + data = atiixp_read(chip, CMD); + if (on) { + if (data & ATI_REG_CMD_MODEM_OUT_DMA1_EN) + return; + atiixp_out_flush_dma(chip); + data |= ATI_REG_CMD_MODEM_OUT_DMA1_EN; + } else + data &= ~ATI_REG_CMD_MODEM_OUT_DMA1_EN; + atiixp_write(chip, CMD, data); +} + +/* start/stop transfer over OUT DMA */ +static void atiixp_out_enable_transfer(struct atiixp_modem *chip, int on) +{ + atiixp_update(chip, CMD, ATI_REG_CMD_MODEM_SEND1_EN, + on ? ATI_REG_CMD_MODEM_SEND1_EN : 0); +} + +/* enable/disable analog IN DMA */ +static void atiixp_in_enable_dma(struct atiixp_modem *chip, int on) +{ + atiixp_update(chip, CMD, ATI_REG_CMD_MODEM_IN_DMA_EN, + on ? ATI_REG_CMD_MODEM_IN_DMA_EN : 0); +} + +/* start/stop analog IN DMA */ +static void atiixp_in_enable_transfer(struct atiixp_modem *chip, int on) +{ + if (on) { + unsigned int data = atiixp_read(chip, CMD); + if (! (data & ATI_REG_CMD_MODEM_RECEIVE_EN)) { + data |= ATI_REG_CMD_MODEM_RECEIVE_EN; + atiixp_write(chip, CMD, data); + } + } else + atiixp_update(chip, CMD, ATI_REG_CMD_MODEM_RECEIVE_EN, 0); +} + +/* flush FIFO of analog IN DMA */ +static void atiixp_in_flush_dma(struct atiixp_modem *chip) +{ + atiixp_write(chip, MODEM_FIFO_FLUSH, ATI_REG_MODEM_FIFO_IN_FLUSH); +} + +/* set up slots and formats for analog OUT */ +static int snd_atiixp_playback_prepare(struct snd_pcm_substream *substream) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + unsigned int data; + + guard(spinlock_irq)(&chip->reg_lock); + /* set output threshold */ + data = atiixp_read(chip, MODEM_OUT_FIFO); + data &= ~ATI_REG_MODEM_OUT1_DMA_THRESHOLD_MASK; + data |= 0x04 << ATI_REG_MODEM_OUT1_DMA_THRESHOLD_SHIFT; + atiixp_write(chip, MODEM_OUT_FIFO, data); + return 0; +} + +/* set up slots and formats for analog IN */ +static int snd_atiixp_capture_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * hw_params - allocate the buffer and set up buffer descriptors + */ +static int snd_atiixp_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + struct atiixp_dma *dma = substream->runtime->private_data; + int err; + int i; + + dma->buf_addr = substream->runtime->dma_addr; + dma->buf_bytes = params_buffer_bytes(hw_params); + + err = atiixp_build_dma_packets(chip, dma, substream, + params_periods(hw_params), + params_period_bytes(hw_params)); + if (err < 0) + return err; + + /* set up modem rate */ + for (i = 0; i < NUM_ATI_CODECS; i++) { + if (! chip->ac97[i]) + continue; + snd_ac97_write(chip->ac97[i], AC97_LINE1_RATE, params_rate(hw_params)); + snd_ac97_write(chip->ac97[i], AC97_LINE1_LEVEL, 0); + } + + return err; +} + +static int snd_atiixp_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + struct atiixp_dma *dma = substream->runtime->private_data; + + atiixp_clear_dma_packets(chip, dma, substream); + return 0; +} + + +/* + * pcm hardware definition, identical for all DMA types + */ +static const struct snd_pcm_hardware snd_atiixp_pcm_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 256 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = ATI_MAX_DESCRIPTORS, +}; + +static int snd_atiixp_pcm_open(struct snd_pcm_substream *substream, + struct atiixp_dma *dma, int pcm_type) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + static const unsigned int rates[] = { 8000, 9600, 12000, 16000 }; + static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + + if (snd_BUG_ON(!dma->ops || !dma->ops->enable_dma)) + return -EINVAL; + + if (dma->opened) + return -EBUSY; + dma->substream = substream; + runtime->hw = snd_atiixp_pcm_hw; + dma->ac97_pcm_type = pcm_type; + err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_rates); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + runtime->private_data = dma; + + /* enable DMA bits */ + scoped_guard(spinlock_irq, &chip->reg_lock) { + dma->ops->enable_dma(chip, 1); + } + dma->opened = 1; + + return 0; +} + +static int snd_atiixp_pcm_close(struct snd_pcm_substream *substream, + struct atiixp_dma *dma) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + /* disable DMA bits */ + if (snd_BUG_ON(!dma->ops || !dma->ops->enable_dma)) + return -EINVAL; + scoped_guard(spinlock_irq, &chip->reg_lock) { + dma->ops->enable_dma(chip, 0); + } + dma->substream = NULL; + dma->opened = 0; + return 0; +} + +/* + */ +static int snd_atiixp_playback_open(struct snd_pcm_substream *substream) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + + guard(mutex)(&chip->open_mutex); + return snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 0); +} + +static int snd_atiixp_playback_close(struct snd_pcm_substream *substream) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + + guard(mutex)(&chip->open_mutex); + return snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]); +} + +static int snd_atiixp_capture_open(struct snd_pcm_substream *substream) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + return snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_CAPTURE], 1); +} + +static int snd_atiixp_capture_close(struct snd_pcm_substream *substream) +{ + struct atiixp_modem *chip = snd_pcm_substream_chip(substream); + return snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_CAPTURE]); +} + + +/* AC97 playback */ +static const struct snd_pcm_ops snd_atiixp_playback_ops = { + .open = snd_atiixp_playback_open, + .close = snd_atiixp_playback_close, + .hw_params = snd_atiixp_pcm_hw_params, + .hw_free = snd_atiixp_pcm_hw_free, + .prepare = snd_atiixp_playback_prepare, + .trigger = snd_atiixp_pcm_trigger, + .pointer = snd_atiixp_pcm_pointer, +}; + +/* AC97 capture */ +static const struct snd_pcm_ops snd_atiixp_capture_ops = { + .open = snd_atiixp_capture_open, + .close = snd_atiixp_capture_close, + .hw_params = snd_atiixp_pcm_hw_params, + .hw_free = snd_atiixp_pcm_hw_free, + .prepare = snd_atiixp_capture_prepare, + .trigger = snd_atiixp_pcm_trigger, + .pointer = snd_atiixp_pcm_pointer, +}; + +static const struct atiixp_dma_ops snd_atiixp_playback_dma_ops = { + .type = ATI_DMA_PLAYBACK, + .llp_offset = ATI_REG_MODEM_OUT_DMA1_LINKPTR, + .dt_cur = ATI_REG_MODEM_OUT_DMA1_DT_CUR, + .enable_dma = atiixp_out_enable_dma, + .enable_transfer = atiixp_out_enable_transfer, + .flush_dma = atiixp_out_flush_dma, +}; + +static const struct atiixp_dma_ops snd_atiixp_capture_dma_ops = { + .type = ATI_DMA_CAPTURE, + .llp_offset = ATI_REG_MODEM_IN_DMA_LINKPTR, + .dt_cur = ATI_REG_MODEM_IN_DMA_DT_CUR, + .enable_dma = atiixp_in_enable_dma, + .enable_transfer = atiixp_in_enable_transfer, + .flush_dma = atiixp_in_flush_dma, +}; + +static int snd_atiixp_pcm_new(struct atiixp_modem *chip) +{ + struct snd_pcm *pcm; + int err; + + /* initialize constants */ + chip->dmas[ATI_DMA_PLAYBACK].ops = &snd_atiixp_playback_dma_ops; + chip->dmas[ATI_DMA_CAPTURE].ops = &snd_atiixp_capture_dma_ops; + + /* PCM #0: analog I/O */ + err = snd_pcm_new(chip->card, "ATI IXP MC97", ATI_PCMDEV_ANALOG, 1, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_atiixp_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_atiixp_capture_ops); + pcm->dev_class = SNDRV_PCM_CLASS_MODEM; + pcm->private_data = chip; + strscpy(pcm->name, "ATI IXP MC97"); + chip->pcmdevs[ATI_PCMDEV_ANALOG] = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 128*1024); + + return 0; +} + + + +/* + * interrupt handler + */ +static irqreturn_t snd_atiixp_interrupt(int irq, void *dev_id) +{ + struct atiixp_modem *chip = dev_id; + unsigned int status; + + status = atiixp_read(chip, ISR); + + if (! status) + return IRQ_NONE; + + /* process audio DMA */ + if (status & ATI_REG_ISR_MODEM_OUT1_XRUN) + snd_atiixp_xrun_dma(chip, &chip->dmas[ATI_DMA_PLAYBACK]); + else if (status & ATI_REG_ISR_MODEM_OUT1_STATUS) + snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_PLAYBACK]); + if (status & ATI_REG_ISR_MODEM_IN_XRUN) + snd_atiixp_xrun_dma(chip, &chip->dmas[ATI_DMA_CAPTURE]); + else if (status & ATI_REG_ISR_MODEM_IN_STATUS) + snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_CAPTURE]); + + /* for codec detection */ + if (status & CODEC_CHECK_BITS) { + unsigned int detected; + detected = status & CODEC_CHECK_BITS; + guard(spinlock)(&chip->reg_lock); + chip->codec_not_ready_bits |= detected; + atiixp_update(chip, IER, detected, 0); /* disable the detected irqs */ + } + + /* ack */ + atiixp_write(chip, ISR, status); + + return IRQ_HANDLED; +} + + +/* + * ac97 mixer section + */ + +static int snd_atiixp_mixer_new(struct atiixp_modem *chip, int clock) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int i, err; + int codec_count; + static const struct snd_ac97_bus_ops ops = { + .write = snd_atiixp_ac97_write, + .read = snd_atiixp_ac97_read, + }; + static const unsigned int codec_skip[NUM_ATI_CODECS] = { + ATI_REG_ISR_CODEC0_NOT_READY, + ATI_REG_ISR_CODEC1_NOT_READY, + ATI_REG_ISR_CODEC2_NOT_READY, + }; + + if (snd_atiixp_codec_detect(chip) < 0) + return -ENXIO; + + err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus); + if (err < 0) + return err; + pbus->clock = clock; + chip->ac97_bus = pbus; + + codec_count = 0; + for (i = 0; i < NUM_ATI_CODECS; i++) { + if (chip->codec_not_ready_bits & codec_skip[i]) + continue; + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.pci = chip->pci; + ac97.num = i; + ac97.scaps = AC97_SCAP_SKIP_AUDIO | AC97_SCAP_POWER_SAVE; + err = snd_ac97_mixer(pbus, &ac97, &chip->ac97[i]); + if (err < 0) { + chip->ac97[i] = NULL; /* to be sure */ + dev_dbg(chip->card->dev, + "codec %d not available for modem\n", i); + continue; + } + codec_count++; + } + + if (! codec_count) { + dev_err(chip->card->dev, "no codec available\n"); + return -ENODEV; + } + + /* snd_ac97_tune_hardware(chip->ac97, ac97_quirks); */ + + return 0; +} + + +/* + * power management + */ +static int snd_atiixp_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct atiixp_modem *chip = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + for (i = 0; i < NUM_ATI_CODECS; i++) + snd_ac97_suspend(chip->ac97[i]); + snd_atiixp_aclink_down(chip); + snd_atiixp_chip_stop(chip); + return 0; +} + +static int snd_atiixp_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct atiixp_modem *chip = card->private_data; + int i; + + snd_atiixp_aclink_reset(chip); + snd_atiixp_chip_start(chip); + + for (i = 0; i < NUM_ATI_CODECS; i++) + snd_ac97_resume(chip->ac97[i]); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_atiixp_pm, snd_atiixp_suspend, snd_atiixp_resume); + +/* + * proc interface for register dump + */ + +static void snd_atiixp_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct atiixp_modem *chip = entry->private_data; + int i; + + for (i = 0; i < 256; i += 4) + snd_iprintf(buffer, "%02x: %08x\n", i, readl(chip->remap_addr + i)); +} + +static void snd_atiixp_proc_init(struct atiixp_modem *chip) +{ + snd_card_ro_proc_new(chip->card, "atiixp-modem", chip, + snd_atiixp_proc_read); +} + + +/* + * destructor + */ + +static void snd_atiixp_free(struct snd_card *card) +{ + snd_atiixp_chip_stop(card->private_data); +} + +/* + * constructor for chip instance + */ +static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) +{ + struct atiixp_modem *chip = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); + mutex_init(&chip->open_mutex); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->remap_addr = pcim_iomap_region(pci, 0, "ATI IXP MC97"); + if (IS_ERR(chip->remap_addr)) + return PTR_ERR(chip->remap_addr); + chip->addr = pci_resource_start(pci, 0); + + if (devm_request_irq(&pci->dev, pci->irq, snd_atiixp_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_atiixp_free; + pci_set_master(pci); + + return 0; +} + + +static int __snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct atiixp_modem *chip; + int err; + + err = snd_devm_card_new(&pci->dev, index, id, THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + strscpy(card->driver, "ATIIXP-MODEM"); + strscpy(card->shortname, "ATI IXP Modem"); + err = snd_atiixp_init(card, pci); + if (err < 0) + return err; + + err = snd_atiixp_aclink_reset(chip); + if (err < 0) + return err; + + err = snd_atiixp_mixer_new(chip, ac97_clock); + if (err < 0) + return err; + + err = snd_atiixp_pcm_new(chip); + if (err < 0) + return err; + + snd_atiixp_proc_init(chip); + + snd_atiixp_chip_start(chip); + + sprintf(card->longname, "%s rev %x at 0x%lx, irq %i", + card->shortname, pci->revision, chip->addr, chip->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + return 0; +} + +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); +} + +static struct pci_driver atiixp_modem_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_atiixp_ids, + .probe = snd_atiixp_probe, + .driver = { + .pm = &snd_atiixp_pm, + }, +}; + +module_pci_driver(atiixp_modem_driver); diff --git a/sound/pci/au88x0/Makefile b/sound/pci/au88x0/Makefile new file mode 100644 index 000000000..5ec5abdee --- /dev/null +++ b/sound/pci/au88x0/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-au8810-y := au8810.o +snd-au8820-y := au8820.o +snd-au8830-y := au8830.o + +obj-$(CONFIG_SND_AU8810) += snd-au8810.o +obj-$(CONFIG_SND_AU8820) += snd-au8820.o +obj-$(CONFIG_SND_AU8830) += snd-au8830.o diff --git a/sound/pci/au88x0/au8810.c b/sound/pci/au88x0/au8810.c new file mode 100644 index 000000000..f712e32d3 --- /dev/null +++ b/sound/pci/au88x0/au8810.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "au8810.h" +#include "au88x0.h" +static const struct pci_device_id snd_vortex_ids[] = { + { PCI_VDEVICE(AUREAL, PCI_DEVICE_ID_AUREAL_ADVANTAGE), .driver_data = 1 }, + { } +}; + +#include "au88x0_core.c" +#include "au88x0_pcm.c" +#include "au88x0_mixer.c" +#include "au88x0_mpu401.c" +#include "au88x0_game.c" +#include "au88x0_eq.c" +#include "au88x0_a3d.c" +#include "au88x0_xtalk.c" +#include "au88x0.c" diff --git a/sound/pci/au88x0/au8810.h b/sound/pci/au88x0/au8810.h new file mode 100644 index 000000000..94f110320 --- /dev/null +++ b/sound/pci/au88x0/au8810.h @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + Aureal Advantage Soundcard driver. + */ + +#define CHIP_AU8810 + +#define CARD_NAME "Aureal Advantage" +#define CARD_NAME_SHORT "au8810" + +#define NR_ADB 0x10 +#define NR_WT 0x00 +#define NR_SRC 0x10 +#define NR_A3D 0x10 +#define NR_MIXIN 0x20 +#define NR_MIXOUT 0x10 + + +/* ADBDMA */ +#define VORTEX_ADBDMA_STAT 0x27e00 /* read only, subbuffer, DMA pos */ +#define POS_MASK 0x00000fff +#define POS_SHIFT 0x0 +#define ADB_SUBBUF_MASK 0x00003000 /* ADB only. */ +#define ADB_SUBBUF_SHIFT 0xc /* ADB only. */ +#define VORTEX_ADBDMA_CTRL 0x27180 /* write only; format, flags, DMA pos */ +#define OFFSET_MASK 0x00000fff +#define OFFSET_SHIFT 0x0 +#define IE_MASK 0x00001000 /* interrupt enable. */ +#define IE_SHIFT 0xc +#define DIR_MASK 0x00002000 /* Direction */ +#define DIR_SHIFT 0xd +#define FMT_MASK 0x0003c000 +#define FMT_SHIFT 0xe +// The ADB masks and shift also are valid for the wtdma, except if specified otherwise. +#define VORTEX_ADBDMA_BUFCFG0 0x27100 +#define VORTEX_ADBDMA_BUFCFG1 0x27104 +#define VORTEX_ADBDMA_BUFBASE 0x27000 +#define VORTEX_ADBDMA_START 0x27c00 /* Which subbuffer starts */ + +#define VORTEX_ADBDMA_STATUS 0x27A90 /* stored at AdbDma->this_10 / 2 DWORD in size. */ + +/* WTDMA */ +#define VORTEX_WTDMA_CTRL 0x27fd8 /* format, DMA pos */ +#define VORTEX_WTDMA_STAT 0x27fe8 /* DMA subbuf, DMA pos */ +#define WT_SUBBUF_MASK 0x3 +#define WT_SUBBUF_SHIFT 0xc +#define VORTEX_WTDMA_BUFBASE 0x27fc0 +#define VORTEX_WTDMA_BUFCFG0 0x27fd0 +#define VORTEX_WTDMA_BUFCFG1 0x27fd4 +#define VORTEX_WTDMA_START 0x27fe4 /* which subbuffer is first */ + +/* ADB */ +#define VORTEX_ADB_SR 0x28400 /* Samplerates enable/disable */ +#define VORTEX_ADB_RTBASE 0x28000 +#define VORTEX_ADB_RTBASE_COUNT 173 +#define VORTEX_ADB_CHNBASE 0x282b4 +#define VORTEX_ADB_CHNBASE_COUNT 24 +#define ROUTE_MASK 0xffff +#define SOURCE_MASK 0xff00 +#define ADB_MASK 0xff +#define ADB_SHIFT 0x8 + +/* ADB address */ +#define OFFSET_ADBDMA 0x00 +#define OFFSET_SRCIN 0x40 +#define OFFSET_SRCOUT 0x20 +#define OFFSET_MIXIN 0x50 +#define OFFSET_MIXOUT 0x30 +#define OFFSET_CODECIN 0x70 +#define OFFSET_CODECOUT 0x88 +#define OFFSET_SPORTIN 0x78 /* ch 0x13 */ +#define OFFSET_SPORTOUT 0x90 +#define OFFSET_SPDIFOUT 0x92 /* ch 0x14 check this! */ +#define OFFSET_EQIN 0xa0 +#define OFFSET_EQOUT 0x7e /* 2 routes on ch 0x11 */ +#define OFFSET_XTALKOUT 0x66 /* crosstalk canceller (source) */ +#define OFFSET_XTALKIN 0x96 /* crosstalk canceller (sink) */ +#define OFFSET_A3DIN 0x70 /* ADB sink. */ +#define OFFSET_A3DOUT 0xA6 /* ADB source. 2 routes per slice = 8 */ +#define OFFSET_EFXIN 0x80 /* ADB sink. */ +#define OFFSET_EFXOUT 0x68 /* ADB source. */ + +/* ADB route translate helper */ +#define ADB_DMA(x) (x) +#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT) +#define ADB_SRCIN(x) (x + OFFSET_SRCIN) +#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT) +#define ADB_MIXIN(x) (x + OFFSET_MIXIN) +#define ADB_CODECIN(x) (x + OFFSET_CODECIN) +#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT) +#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN) +#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT) +#define ADB_SPDIFOUT(x) (x + OFFSET_SPDIFOUT) +#define ADB_EQIN(x) (x + OFFSET_EQIN) +#define ADB_EQOUT(x) (x + OFFSET_EQOUT) +#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT) /* 0x10 A3D blocks */ +#define ADB_A3DIN(x) (x + OFFSET_A3DIN) +#define ADB_XTALKIN(x) (x + OFFSET_XTALKIN) +#define ADB_XTALKOUT(x) (x + OFFSET_XTALKOUT) + +#define MIX_OUTL 0xe +#define MIX_OUTR 0xf +#define MIX_INL 0x1e +#define MIX_INR 0x1f +#define MIX_DEFIGAIN 0x08 /* 0x8 => 6dB */ +#define MIX_DEFOGAIN 0x08 + +/* MIXER */ +#define VORTEX_MIXER_SR 0x21f00 +#define VORTEX_MIXER_CLIP 0x21f80 +#define VORTEX_MIXER_CHNBASE 0x21e40 +#define VORTEX_MIXER_RTBASE 0x21e00 +#define MIXER_RTBASE_SIZE 0x38 +#define VORTEX_MIX_ENIN 0x21a00 /* Input enable bits. 4 bits wide. */ +#define VORTEX_MIX_SMP 0x21c00 /* AU8820: 0x9c00 */ + +/* MIX */ +#define VORTEX_MIX_INVOL_A 0x21000 /* in? */ +#define VORTEX_MIX_INVOL_B 0x20000 /* out? */ +#define VORTEX_MIX_VOL_A 0x21800 +#define VORTEX_MIX_VOL_B 0x20800 + +#define VOL_MIN 0x80 /* Input volume when muted. */ +#define VOL_MAX 0x7f /* FIXME: Not confirmed! Just guessed. */ + +/* SRC */ +#define VORTEX_SRC_CHNBASE 0x26c40 +#define VORTEX_SRC_RTBASE 0x26c00 +#define VORTEX_SRCBLOCK_SR 0x26cc0 +#define VORTEX_SRC_SOURCE 0x26cc4 +#define VORTEX_SRC_SOURCESIZE 0x26cc8 +/* Params + 0x26e00 : 1 U0 + 0x26e40 : 2 CR + 0x26e80 : 3 U3 + 0x26ec0 : 4 DRIFT1 + 0x26f00 : 5 U1 + 0x26f40 : 6 DRIFT2 + 0x26f80 : 7 U2 : Target rate, direction +*/ + +#define VORTEX_SRC_CONVRATIO 0x26e40 +#define VORTEX_SRC_DRIFT0 0x26e80 +#define VORTEX_SRC_DRIFT1 0x26ec0 +#define VORTEX_SRC_DRIFT2 0x26f40 +#define VORTEX_SRC_U0 0x26e00 +#define U0_SLOWLOCK 0x200 +#define VORTEX_SRC_U1 0x26f00 +#define VORTEX_SRC_U2 0x26f80 +#define VORTEX_SRC_DATA 0x26800 /* 0xc800 */ +#define VORTEX_SRC_DATA0 0x26000 + +/* FIFO */ +#define VORTEX_FIFO_ADBCTRL 0x16100 /* Control bits. */ +#define VORTEX_FIFO_WTCTRL 0x16000 +#define FIFO_RDONLY 0x00000001 +#define FIFO_CTRL 0x00000002 /* Allow ctrl. ? */ +#define FIFO_VALID 0x00000010 +#define FIFO_EMPTY 0x00000020 +#define FIFO_U0 0x00001000 /* Unknown. */ +#define FIFO_U1 0x00010000 +#define FIFO_SIZE_BITS 5 +#define FIFO_SIZE (1<this_10 / 2 DWORD in size. */ + +/* ADB */ +#define VORTEX_ADB_SR 0x10a00 /* Samplerates enable/disable */ +#define VORTEX_ADB_RTBASE 0x10800 +#define VORTEX_ADB_RTBASE_COUNT 103 +#define VORTEX_ADB_CHNBASE 0x1099c +#define VORTEX_ADB_CHNBASE_COUNT 22 +#define ROUTE_MASK 0x3fff +#define ADB_MASK 0x7f +#define ADB_SHIFT 0x7 +//#define ADB_MIX_MASK 0xf +/* ADB address */ +#define OFFSET_ADBDMA 0x00 +#define OFFSET_SRCOUT 0x10 /* on channel 0x11 */ +#define OFFSET_SRCIN 0x10 /* on channel < 0x11 */ +#define OFFSET_MIXOUT 0x20 /* source */ +#define OFFSET_MIXIN 0x30 /* sink */ +#define OFFSET_CODECIN 0x48 /* ADB source */ +#define OFFSET_CODECOUT 0x58 /* ADB sink/target */ +#define OFFSET_SPORTOUT 0x60 /* sink */ +#define OFFSET_SPORTIN 0x50 /* source */ +#define OFFSET_EFXOUT 0x50 /* sink */ +#define OFFSET_EFXIN 0x40 /* source */ +#define OFFSET_A3DOUT 0x00 /* This card has no HRTF :( */ +#define OFFSET_A3DIN 0x00 +#define OFFSET_WTOUT 0x58 /* */ + +/* ADB route translate helper */ +#define ADB_DMA(x) (x + OFFSET_ADBDMA) +#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT) +#define ADB_SRCIN(x) (x + OFFSET_SRCIN) +#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT) +#define ADB_MIXIN(x) (x + OFFSET_MIXIN) +#define ADB_CODECIN(x) (x + OFFSET_CODECIN) +#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT) +#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT) +#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN) /* */ +#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT) /* 8 A3D blocks */ +#define ADB_A3DIN(x) (x + OFFSET_A3DIN) +#define ADB_WTOUT(x,y) (y + OFFSET_WTOUT) + +/* WTDMA */ +#define VORTEX_WTDMA_CTRL 0x10500 /* format, DMA pos */ +#define VORTEX_WTDMA_STAT 0x10500 /* DMA subbuf, DMA pos */ +#define WT_SUBBUF_MASK (0x3 << WT_SUBBUF_SHIFT) +#define WT_SUBBUF_SHIFT 0x15 +#define VORTEX_WTDMA_BUFBASE 0x10000 +#define VORTEX_WTDMA_BUFCFG0 0x10300 +#define VORTEX_WTDMA_BUFCFG1 0x10304 +#define VORTEX_WTDMA_START 0x10640 /* which subbuffer is first */ + +#define VORTEX_WT_BASE 0x9000 + +/* MIXER */ +#define VORTEX_MIXER_SR 0x9f00 +#define VORTEX_MIXER_CLIP 0x9f80 +#define VORTEX_MIXER_CHNBASE 0x9e40 +#define VORTEX_MIXER_RTBASE 0x9e00 +#define MIXER_RTBASE_SIZE 0x26 +#define VORTEX_MIX_ENIN 0x9a00 /* Input enable bits. 4 bits wide. */ +#define VORTEX_MIX_SMP 0x9c00 + +/* MIX */ +#define VORTEX_MIX_INVOL_A 0x9000 /* in? */ +#define VORTEX_MIX_INVOL_B 0x8000 /* out? */ +#define VORTEX_MIX_VOL_A 0x9800 +#define VORTEX_MIX_VOL_B 0x8800 + +#define VOL_MIN 0x80 /* Input volume when muted. */ +#define VOL_MAX 0x7f /* FIXME: Not confirmed! Just guessed. */ + +//#define MIX_OUTL 0xe +//#define MIX_OUTR 0xf +//#define MIX_INL 0xe +//#define MIX_INR 0xf +#define MIX_DEFIGAIN 0x08 /* 0x8 => 6dB */ +#define MIX_DEFOGAIN 0x08 + +/* SRC */ +#define VORTEX_SRCBLOCK_SR 0xccc0 +#define VORTEX_SRC_CHNBASE 0xcc40 +#define VORTEX_SRC_RTBASE 0xcc00 +#define VORTEX_SRC_SOURCE 0xccc4 +#define VORTEX_SRC_SOURCESIZE 0xccc8 +#define VORTEX_SRC_U0 0xce00 +#define VORTEX_SRC_DRIFT0 0xce80 +#define VORTEX_SRC_DRIFT1 0xcec0 +#define VORTEX_SRC_U1 0xcf00 +#define VORTEX_SRC_DRIFT2 0xcf40 +#define VORTEX_SRC_U2 0xcf80 +#define VORTEX_SRC_DATA 0xc800 +#define VORTEX_SRC_DATA0 0xc000 +#define VORTEX_SRC_CONVRATIO 0xce40 +//#define SRC_RATIO(x) ((((x<<15)/48000) + 1)/2) /* Playback */ +//#define SRC_RATIO2(x) ((((48000<<15)/x) + 1)/2) /* Recording */ + +/* FIFO */ +#define VORTEX_FIFO_ADBCTRL 0xf800 /* Control bits. */ +#define VORTEX_FIFO_WTCTRL 0xf840 +#define FIFO_RDONLY 0x00000001 +#define FIFO_CTRL 0x00000002 /* Allow ctrl. ? */ +#define FIFO_VALID 0x00000010 +#define FIFO_EMPTY 0x00000020 +#define FIFO_U0 0x00001000 /* Unknown. */ +#define FIFO_U1 0x00010000 +#define FIFO_SIZE_BITS 5 +#define FIFO_SIZE (1<this_10 / 2 DWORD in size. */ +/* Starting at the MSB, each pair of bits seem to be the current DMA page. */ +/* This current page bits are consistent (same value) with VORTEX_ADBDMA_STAT) */ + +/* DMA */ +#define VORTEX_ENGINE_CTRL 0x27ae8 +#define ENGINE_INIT 0x1380000 + +/* WTDMA */ +#define VORTEX_WTDMA_CTRL 0x27900 /* format, DMA pos */ +#define VORTEX_WTDMA_STAT 0x27d00 /* DMA subbuf, DMA pos */ +#define WT_SUBBUF_MASK 0x3 +#define WT_SUBBUF_SHIFT 0xc +#define VORTEX_WTDMA_BUFBASE 0x27000 +#define VORTEX_WTDMA_BUFCFG0 0x27600 +#define VORTEX_WTDMA_BUFCFG1 0x27604 +#define VORTEX_WTDMA_START 0x27b00 /* which subbuffer is first */ + +/* ADB */ +#define VORTEX_ADB_SR 0x28400 /* Samplerates enable/disable */ +#define VORTEX_ADB_RTBASE 0x28000 +#define VORTEX_ADB_RTBASE_COUNT 173 +#define VORTEX_ADB_CHNBASE 0x282b4 +#define VORTEX_ADB_CHNBASE_COUNT 24 +#define ROUTE_MASK 0xffff +#define SOURCE_MASK 0xff00 +#define ADB_MASK 0xff +#define ADB_SHIFT 0x8 +/* ADB address */ +#define OFFSET_ADBDMA 0x00 +#define OFFSET_ADBDMAB 0x20 +#define OFFSET_SRCIN 0x40 +#define OFFSET_SRCOUT 0x20 /* ch 0x11 */ +#define OFFSET_MIXIN 0x50 /* ch 0x11 */ +#define OFFSET_MIXOUT 0x30 /* ch 0x11 */ +#define OFFSET_CODECIN 0x70 /* ch 0x11 */ /* adb source */ +#define OFFSET_CODECOUT 0x88 /* ch 0x11 */ /* adb target */ +#define OFFSET_SPORTIN 0x78 /* ch 0x13 ADB source. 2 routes. */ +#define OFFSET_SPORTOUT 0x90 /* ch 0x13 ADB sink. 2 routes. */ +#define OFFSET_SPDIFIN 0x7A /* ch 0x14 ADB source. */ +#define OFFSET_SPDIFOUT 0x92 /* ch 0x14 ADB sink. */ +#define OFFSET_AC98IN 0x7c /* ch 0x14 ADB source. */ +#define OFFSET_AC98OUT 0x94 /* ch 0x14 ADB sink. */ +#define OFFSET_EQIN 0xa0 /* ch 0x11 */ +#define OFFSET_EQOUT 0x7e /* ch 0x11 */ /* 2 routes on ch 0x11 */ +#define OFFSET_A3DIN 0x70 /* ADB sink. */ +#define OFFSET_A3DOUT 0xA6 /* ADB source. 2 routes per slice = 8 */ +#define OFFSET_WT0 0x40 /* WT bank 0 output. 0x40 - 0x65 */ +#define OFFSET_WT1 0x80 /* WT bank 1 output. 0x80 - 0xA5 */ +/* WT sources offset : 0x00-0x1f Direct stream. */ +/* WT sources offset : 0x20-0x25 Mixed Output. */ +#define OFFSET_XTALKOUT 0x66 /* crosstalk canceller (source) 2 routes */ +#define OFFSET_XTALKIN 0x96 /* crosstalk canceller (sink). 10 routes */ +#define OFFSET_EFXOUT 0x68 /* ADB source. 8 routes. */ +#define OFFSET_EFXIN 0x80 /* ADB sink. 8 routes. */ + +/* ADB route translate helper */ +#define ADB_DMA(x) (x) +#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT) +#define ADB_SRCIN(x) (x + OFFSET_SRCIN) +#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT) +#define ADB_MIXIN(x) (x + OFFSET_MIXIN) +#define ADB_CODECIN(x) (x + OFFSET_CODECIN) +#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT) +#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN) +#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT) +#define ADB_SPDIFIN(x) (x + OFFSET_SPDIFIN) +#define ADB_SPDIFOUT(x) (x + OFFSET_SPDIFOUT) +#define ADB_EQIN(x) (x + OFFSET_EQIN) +#define ADB_EQOUT(x) (x + OFFSET_EQOUT) +#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT) /* 0x10 A3D blocks */ +#define ADB_A3DIN(x) (x + OFFSET_A3DIN) +//#define ADB_WTOUT(x) ((x6dB (6dB = x4) 16 to 18 bit conversion? */ + +/* MIXER */ +#define VORTEX_MIXER_SR 0x21f00 +#define VORTEX_MIXER_CLIP 0x21f80 +#define VORTEX_MIXER_CHNBASE 0x21e40 +#define VORTEX_MIXER_RTBASE 0x21e00 +#define MIXER_RTBASE_SIZE 0x38 +#define VORTEX_MIX_ENIN 0x21a00 /* Input enable bits. 4 bits wide. */ +#define VORTEX_MIX_SMP 0x21c00 /* wave data buffers. AU8820: 0x9c00 */ + +/* MIX */ +#define VORTEX_MIX_INVOL_B 0x20000 /* Input volume current */ +#define VORTEX_MIX_VOL_B 0x20800 /* Output Volume current */ +#define VORTEX_MIX_INVOL_A 0x21000 /* Input Volume target */ +#define VORTEX_MIX_VOL_A 0x21800 /* Output Volume target */ + +#define VOL_MIN 0x80 /* Input volume when muted. */ +#define VOL_MAX 0x7f /* FIXME: Not confirmed! Just guessed. */ + +/* SRC */ +#define VORTEX_SRC_CHNBASE 0x26c40 +#define VORTEX_SRC_RTBASE 0x26c00 +#define VORTEX_SRCBLOCK_SR 0x26cc0 +#define VORTEX_SRC_SOURCE 0x26cc4 +#define VORTEX_SRC_SOURCESIZE 0x26cc8 +/* Params + 0x26e00 : 1 U0 + 0x26e40 : 2 CR + 0x26e80 : 3 U3 + 0x26ec0 : 4 DRIFT1 + 0x26f00 : 5 U1 + 0x26f40 : 6 DRIFT2 + 0x26f80 : 7 U2 : Target rate, direction +*/ + +#define VORTEX_SRC_CONVRATIO 0x26e40 +#define VORTEX_SRC_DRIFT0 0x26e80 +#define VORTEX_SRC_DRIFT1 0x26ec0 +#define VORTEX_SRC_DRIFT2 0x26f40 +#define VORTEX_SRC_U0 0x26e00 +#define U0_SLOWLOCK 0x200 +#define VORTEX_SRC_U1 0x26f00 +#define VORTEX_SRC_U2 0x26f80 +#define VORTEX_SRC_DATA 0x26800 /* 0xc800 */ +#define VORTEX_SRC_DATA0 0x26000 + +/* FIFO */ +#define VORTEX_FIFO_ADBCTRL 0x16100 /* Control bits. */ +#define VORTEX_FIFO_WTCTRL 0x16000 +#define FIFO_RDONLY 0x00000001 +#define FIFO_CTRL 0x00000002 /* Allow ctrl. ? */ +#define FIFO_VALID 0x00000010 +#define FIFO_EMPTY 0x00000020 +#define FIFO_U0 0x00002000 /* Unknown. */ +#define FIFO_U1 0x00040000 +#define FIFO_SIZE_BITS 6 +#define FIFO_SIZE (1<<(FIFO_SIZE_BITS)) // 0x40 +#define FIFO_MASK (FIFO_SIZE-1) //0x3f /* at shift left 0xc */ +#define FIFO_BITS 0x1c400000 +#define VORTEX_FIFO_ADBDATA 0x14000 +#define VORTEX_FIFO_WTDATA 0x10000 + +#define VORTEX_FIFO_GIRT 0x17000 /* wt0, wt1, adb */ +#define GIRT_COUNT 3 + +/* CODEC */ + +#define VORTEX_CODEC_CHN 0x29080 /* The name "CHN" is wrong. */ + +#define VORTEX_CODEC_CTRL 0x29184 +#define VORTEX_CODEC_IO 0x29188 + +#define VORTEX_CODEC_SPORTCTRL 0x2918c + +#define VORTEX_CODEC_EN 0x29190 +#define EN_AUDIO0 0x00000300 +#define EN_MODEM 0x00000c00 +#define EN_AUDIO1 0x00003000 +#define EN_SPORT 0x00030000 +#define EN_SPDIF 0x000c0000 +#define EN_CODEC (EN_AUDIO1 | EN_AUDIO0) + +#define VORTEX_SPDIF_SMPRATE 0x29194 + +#define VORTEX_SPDIF_FLAGS 0x2205c +#define VORTEX_SPDIF_CFG0 0x291D0 /* status data */ +#define VORTEX_SPDIF_CFG1 0x291D4 + +#define VORTEX_SMP_TIME 0x29198 /* Sample counter/timer */ +#define VORTEX_SMP_TIMER 0x2919c +#define VORTEX_CODEC2_CTRL 0x291a0 + +#define VORTEX_MODEM_CTRL 0x291ac + +/* IRQ */ +#define VORTEX_IRQ_SOURCE 0x2a000 /* Interrupt source flags. */ +#define VORTEX_IRQ_CTRL 0x2a004 /* Interrupt source mask. */ + +//#define VORTEX_IRQ_U0 0x2a008 /* ?? */ +#define VORTEX_STAT 0x2a008 /* Some sort of status */ +#define STAT_IRQ 0x00000001 /* This bitis set if the IRQ is valid. */ + +#define VORTEX_CTRL 0x2a00c +#define CTRL_MIDI_EN 0x00000001 +#define CTRL_MIDI_PORT 0x00000060 +#define CTRL_GAME_EN 0x00000008 +#define CTRL_GAME_PORT 0x00000e00 +#define CTRL_IRQ_ENABLE 0x00004000 +#define CTRL_SPDIF 0x00000000 /* unknown. Please find this value */ +#define CTRL_SPORT 0x00200000 +#define CTRL_RST 0x00800000 +#define CTRL_UNKNOWN 0x01000000 + +/* write: Timer period config / read: TIMER IRQ ack. */ +#define VORTEX_IRQ_STAT 0x2919c + + /* MIDI *//* GAME. */ +#define VORTEX_MIDI_DATA 0x28800 +#define VORTEX_MIDI_CMD 0x28804 /* Write command / Read status */ + +#define VORTEX_GAME_LEGACY 0x28808 +#define VORTEX_CTRL2 0x2880c +#define CTRL2_GAME_ADCMODE 0x40 +#define VORTEX_GAME_AXIS 0x28810 /* Axis base register. 4 axis's */ +#define AXIS_SIZE 4 +#define AXIS_RANGE 0x1fff diff --git a/sound/pci/au88x0/au88x0.c b/sound/pci/au88x0/au88x0.c new file mode 100644 index 000000000..bb0294579 --- /dev/null +++ b/sound/pci/au88x0/au88x0.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for the Aureal Vortex family of soundprocessors. + * Author: Manuel Jander (mjander@embedded.cl) + * + * This driver is the result of the OpenVortex Project from Savannah + * (savannah.nongnu.org/projects/openvortex). I would like to thank + * the developers of OpenVortex, Jeff Muizelaar and Kester Maddock, from + * whom i got plenty of help, and their codebase was invaluable. + * Thanks to the ALSA developers, they helped a lot working out + * the ALSA part. + * Thanks also to Sourceforge for maintaining the old binary drivers, + * and the forum, where developers could communicate. + * + * Now at least i can play Legacy DOOM with MIDI music :-) + */ + +#include "au88x0.h" +#include +#include +#include +#include +#include +#include +#include + +// module parameters (see "Module Parameters") +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static int pcifix[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 255 }; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); +module_param_array(pcifix, int, NULL, 0444); +MODULE_PARM_DESC(pcifix, "Enable VIA-workaround for " CARD_NAME " soundcard."); + +MODULE_DESCRIPTION("Aureal vortex"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pci, snd_vortex_ids); + +static void vortex_fix_latency(struct pci_dev *vortex) +{ + int rc; + rc = pci_write_config_byte(vortex, 0x40, 0xff); + if (!rc) { + dev_info(&vortex->dev, "vortex latency is 0xff\n"); + } else { + dev_warn(&vortex->dev, + "could not set vortex latency: pci error 0x%x\n", rc); + } +} + +static void vortex_fix_agp_bridge(struct pci_dev *via) +{ + int rc; + u8 value; + + /* + * only set the bit (Extend PCI#2 Internal Master for + * Efficient Handling of Dummy Requests) if the can + * read the config and it is not already set + */ + + rc = pci_read_config_byte(via, 0x42, &value); + if (!rc) { + if (!(value & 0x10)) + rc = pci_write_config_byte(via, 0x42, value | 0x10); + } + if (!rc) { + dev_info(&via->dev, "bridge config is 0x%x\n", value | 0x10); + } else { + dev_warn(&via->dev, + "could not set vortex latency: pci error 0x%x\n", rc); + } +} + +static void snd_vortex_workaround(struct pci_dev *vortex, int fix) +{ + struct pci_dev *via = NULL; + + /* autodetect if workarounds are required */ + if (fix == 255) { + /* VIA KT133 */ + via = pci_get_device(PCI_VENDOR_ID_VIA, + PCI_DEVICE_ID_VIA_8365_1, NULL); + /* VIA Apollo */ + if (via == NULL) { + via = pci_get_device(PCI_VENDOR_ID_VIA, + PCI_DEVICE_ID_VIA_82C598_1, NULL); + /* AMD Irongate */ + if (via == NULL) + via = pci_get_device(PCI_VENDOR_ID_AMD, + PCI_DEVICE_ID_AMD_FE_GATE_7007, NULL); + } + if (via) { + dev_info(&vortex->dev, + "Activating latency workaround...\n"); + vortex_fix_latency(vortex); + vortex_fix_agp_bridge(via); + } + } else { + if (fix & 0x1) + vortex_fix_latency(vortex); + if (fix & 0x2) + via = pci_get_device(PCI_VENDOR_ID_VIA, + PCI_DEVICE_ID_VIA_8365_1, NULL); + else if (fix & 0x4) + via = pci_get_device(PCI_VENDOR_ID_VIA, + PCI_DEVICE_ID_VIA_82C598_1, NULL); + else if (fix & 0x8) + via = pci_get_device(PCI_VENDOR_ID_AMD, + PCI_DEVICE_ID_AMD_FE_GATE_7007, NULL); + if (via) + vortex_fix_agp_bridge(via); + } + pci_dev_put(via); +} + +// component-destructor +// (see "Management of Cards and Components") +static void snd_vortex_free(struct snd_card *card) +{ + vortex_t *vortex = card->private_data; + + vortex_gameport_unregister(vortex); + vortex_core_shutdown(vortex); +} + +// chip-specific constructor +// (see "Management of Cards and Components") +static int +snd_vortex_create(struct snd_card *card, struct pci_dev *pci) +{ + vortex_t *chip = card->private_data; + int err; + + // check PCI availability (DMA). + err = pcim_enable_device(pci); + if (err < 0) + return err; + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32))) { + dev_err(card->dev, "error to set DMA mask\n"); + return -ENXIO; + } + + chip->card = card; + + // initialize the stuff + chip->pci_dev = pci; + chip->vendor = pci->vendor; + chip->device = pci->device; + chip->card = card; + chip->irq = -1; + + // (1) PCI resource allocation + // Get MMIO area + // + chip->mmio = pcim_iomap_region(pci, 0, KBUILD_MODNAME); + if (IS_ERR(chip->mmio)) + return PTR_ERR(chip->mmio); + + chip->io = pci_resource_start(pci, 0); + + /* Init audio core. + * This must be done before we do request_irq otherwise we can get spurious + * interrupts that we do not handle properly and make a mess of things */ + err = vortex_core_init(chip); + if (err) { + dev_err(card->dev, "hw core init failed\n"); + return err; + } + + err = devm_request_irq(&pci->dev, pci->irq, vortex_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (err) { + dev_err(card->dev, "cannot grab irq\n"); + return err; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_vortex_free; + + pci_set_master(pci); + // End of PCI setup. + return 0; +} + +// constructor -- see "Constructor" sub-section +static int +__snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + vortex_t *chip; + int err; + + // (1) + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + // (2) + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + // (3) + err = snd_vortex_create(card, pci); + if (err < 0) + return err; + snd_vortex_workaround(pci, pcifix[dev]); + + // Card details needed in snd_vortex_midi + strscpy(card->driver, CARD_NAME_SHORT); + sprintf(card->shortname, "Aureal Vortex %s", CARD_NAME_SHORT); + sprintf(card->longname, "%s at 0x%lx irq %i", + card->shortname, chip->io, chip->irq); + + // (4) Alloc components. + err = snd_vortex_mixer(chip); + if (err < 0) + return err; + // ADB pcm. + err = snd_vortex_new_pcm(chip, VORTEX_PCM_ADB, NR_PCM); + if (err < 0) + return err; +#ifndef CHIP_AU8820 + // ADB SPDIF + err = snd_vortex_new_pcm(chip, VORTEX_PCM_SPDIF, 1); + if (err < 0) + return err; + // A3D + err = snd_vortex_new_pcm(chip, VORTEX_PCM_A3D, NR_A3D); + if (err < 0) + return err; +#endif + /* + // ADB I2S + if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_I2S, 1)) < 0) { + return err; + } + */ +#ifndef CHIP_AU8810 + // WT pcm. + err = snd_vortex_new_pcm(chip, VORTEX_PCM_WT, NR_WT); + if (err < 0) + return err; +#endif + err = snd_vortex_midi(chip); + if (err < 0) + return err; + + vortex_gameport_register(chip); + +#if 0 + if (snd_seq_device_new(card, 1, SNDRV_SEQ_DEV_ID_VORTEX_SYNTH, + sizeof(snd_vortex_synth_arg_t), &wave) < 0 + || wave == NULL) { + dev_err(card->dev, "Can't initialize Aureal wavetable synth\n"); + } else { + snd_vortex_synth_arg_t *arg; + + arg = SNDRV_SEQ_DEVICE_ARGPTR(wave); + strscpy(wave->name, "Aureal Synth"); + arg->hwptr = vortex; + arg->index = 1; + arg->seq_ports = seq_ports[dev]; + arg->max_voices = max_synth_voices[dev]; + } +#endif + + // (5) + err = pci_read_config_word(pci, PCI_DEVICE_ID, &chip->device); + if (err) + return pcibios_err_to_errno(err); + err = pci_read_config_word(pci, PCI_VENDOR_ID, &chip->vendor); + if (err) + return pcibios_err_to_errno(err); + chip->rev = pci->revision; +#ifdef CHIP_AU8830 + if ((chip->rev) != 0xfe && (chip->rev) != 0xfa) { + dev_alert(card->dev, + "The revision (%x) of your card has not been seen before.\n", + chip->rev); + dev_alert(card->dev, + "Please email the results of 'lspci -vv' to openvortex-dev@nongnu.org.\n"); + return -ENODEV; + } +#endif + + // (6) + err = snd_card_register(card); + if (err < 0) + return err; + // (7) + pci_set_drvdata(pci, card); + dev++; + vortex_connect_default(chip, 1); + vortex_enable_int(chip); + return 0; +} + +static int +snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_vortex_probe(pci, pci_id)); +} + +// pci_driver definition +static struct pci_driver vortex_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_vortex_ids, + .probe = snd_vortex_probe, +}; + +module_pci_driver(vortex_driver); diff --git a/sound/pci/au88x0/au88x0.h b/sound/pci/au88x0/au88x0.h new file mode 100644 index 000000000..6cbb2bc4a --- /dev/null +++ b/sound/pci/au88x0/au88x0.h @@ -0,0 +1,281 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + */ + +#ifndef __SOUND_AU88X0_H +#define __SOUND_AU88X0_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CHIP_AU8820 +#include "au88x0_eq.h" +#include "au88x0_a3d.h" +#endif +#ifndef CHIP_AU8810 +#include "au88x0_wt.h" +#endif + +#define hwread(x,y) readl((x)+(y)) +#define hwwrite(x,y,z) writel((z),(x)+(y)) + +/* Vortex MPU401 defines. */ +#define MIDI_CLOCK_DIV 0x61 +/* Standart MPU401 defines. */ +#define MPU401_RESET 0xff +#define MPU401_ENTER_UART 0x3f +#define MPU401_ACK 0xfe + +// Get src register value to convert from x to y. +#define SRC_RATIO(x,y) ((((x<<15)/y) + 1)/2) + +/* FIFO software state constants. */ +#define FIFO_STOP 0 +#define FIFO_START 1 +#define FIFO_PAUSE 2 + +/* IRQ flags */ +#define IRQ_ERR_MASK 0x00ff +#define IRQ_FATAL 0x0001 +#define IRQ_PARITY 0x0002 +#define IRQ_REG 0x0004 +#define IRQ_FIFO 0x0008 +#define IRQ_DMA 0x0010 +#define IRQ_PCMOUT 0x0020 /* PCM OUT page crossing */ +#define IRQ_TIMER 0x1000 +#define IRQ_MIDI 0x2000 +#define IRQ_MODEM 0x4000 + +/* ADB Resource */ +#define VORTEX_RESOURCE_DMA 0x00000000 +#define VORTEX_RESOURCE_SRC 0x00000001 +#define VORTEX_RESOURCE_MIXIN 0x00000002 +#define VORTEX_RESOURCE_MIXOUT 0x00000003 +#define VORTEX_RESOURCE_A3D 0x00000004 +#define VORTEX_RESOURCE_LAST 0x00000005 + +/* codec io: VORTEX_CODEC_IO bits */ +#define VORTEX_CODEC_ID_SHIFT 24 +#define VORTEX_CODEC_WRITE 0x00800000 +#define VORTEX_CODEC_ADDSHIFT 16 +#define VORTEX_CODEC_ADDMASK 0x7f0000 +#define VORTEX_CODEC_DATSHIFT 0 +#define VORTEX_CODEC_DATMASK 0xffff + +/* Check for SDAC bit in "Extended audio ID" AC97 register */ +//#define VORTEX_IS_QUAD(x) (((x)->codec == NULL) ? 0 : ((x)->codec->ext_id&0x80)) +#define VORTEX_IS_QUAD(x) ((x)->isquad) +/* Check if chip has bug. */ +#define IS_BAD_CHIP(x) (\ + (x->rev == 0xfe && x->device == PCI_DEVICE_ID_AUREAL_VORTEX_2) || \ + (x->rev == 0xfe && x->device == PCI_DEVICE_ID_AUREAL_ADVANTAGE)) + + +/* PCM devices */ +#define VORTEX_PCM_ADB 0 +#define VORTEX_PCM_SPDIF 1 +#define VORTEX_PCM_A3D 2 +#define VORTEX_PCM_WT 3 +#define VORTEX_PCM_I2S 4 +#define VORTEX_PCM_LAST 5 + +#define MIX_CAPT(x) (vortex->mixcapt[x]) +#define MIX_PLAYB(x) (vortex->mixplayb[x]) +#define MIX_SPDIF(x) (vortex->mixspdif[x]) + +#define NR_WTPB 0x20 /* WT channels per each bank. */ +#define NR_PCM 0x10 + +struct pcm_vol { + struct snd_kcontrol *kctl; + int active; + int dma; + int mixin[4]; + int vol[4]; +}; + +/* Structs */ +typedef struct { + //int this_08; /* Still unknown */ + int fifo_enabled; /* this_24 */ + int fifo_status; /* this_1c */ + u32 dma_ctrl; /* this_78 (ADB), this_7c (WT) */ + int dma_unknown; /* this_74 (ADB), this_78 (WT). WDM: +8 */ + int cfg0; + int cfg1; + + int nr_ch; /* Nr of PCM channels in use */ + int type; /* Output type (ac97, a3d, spdif, i2s, dsp) */ + int dma; /* Hardware DMA index. */ + int dir; /* Stream Direction. */ + u32 resources[5]; + + /* Virtual page extender stuff */ + int nr_periods; + int period_bytes; + int period_real; + int period_virt; + + struct snd_pcm_substream *substream; +} stream_t; + +typedef struct snd_vortex vortex_t; +struct snd_vortex { + /* ALSA structs. */ + struct snd_card *card; + struct snd_pcm *pcm[VORTEX_PCM_LAST]; + + struct snd_rawmidi *rmidi; /* Legacy Midi interface. */ + struct snd_ac97 *codec; + + /* Stream structs. */ + stream_t dma_adb[NR_ADB]; + int spdif_sr; +#ifndef CHIP_AU8810 + stream_t dma_wt[NR_WT]; + wt_voice_t wt_voice[NR_WT]; /* WT register cache. */ + s8 mixwt[(NR_WT / NR_WTPB) * 6]; /* WT mixin objects */ +#endif + + /* Global resources */ + s8 mixcapt[2]; + s8 mixplayb[4]; +#ifndef CHIP_AU8820 + s8 mixspdif[2]; + s8 mixa3d[2]; /* mixers which collect all a3d streams. */ + s8 mixxtlk[2]; /* crosstalk canceler mixer inputs. */ +#endif + u32 fixed_res[5]; + +#ifndef CHIP_AU8820 + /* Hardware equalizer structs */ + eqlzr_t eq; + /* A3D structs */ + a3dsrc_t a3d[NR_A3D]; + /* Xtalk canceler */ + int xt_mode; /* 1: speakers, 0:headphones. */ +#endif + struct pcm_vol pcm_vol[NR_PCM]; + + int isquad; /* cache of extended ID codec flag. */ + + /* Gameport stuff. */ + struct gameport *gameport; + + /* PCI hardware resources */ + unsigned long io; + void __iomem *mmio; + unsigned int irq; + spinlock_t lock; + + /* PCI device */ + struct pci_dev *pci_dev; + u16 vendor; + u16 device; + u8 rev; +}; + +/* Functions. */ + +/* SRC */ +static void vortex_adb_setsrc(vortex_t * vortex, int adbdma, + unsigned int cvrt, int dir); + +/* DMA Engines. */ +static void vortex_adbdma_setbuffers(vortex_t * vortex, int adbdma, + int size, int count); +static void vortex_adbdma_setmode(vortex_t * vortex, int adbdma, int ie, + int dir, int fmt, int d, + u32 offset); +static void vortex_adbdma_setstartbuffer(vortex_t * vortex, int adbdma, int sb); +#ifndef CHIP_AU8810 +static void vortex_wtdma_setbuffers(vortex_t * vortex, int wtdma, + int size, int count); +static void vortex_wtdma_setmode(vortex_t * vortex, int wtdma, int ie, int fmt, int d, /*int e, */ + u32 offset); +static void vortex_wtdma_setstartbuffer(vortex_t * vortex, int wtdma, int sb); +#endif + +static void vortex_adbdma_startfifo(vortex_t * vortex, int adbdma); +//static void vortex_adbdma_stopfifo(vortex_t *vortex, int adbdma); +static void vortex_adbdma_pausefifo(vortex_t * vortex, int adbdma); +static void vortex_adbdma_resumefifo(vortex_t * vortex, int adbdma); +static inline int vortex_adbdma_getlinearpos(vortex_t * vortex, int adbdma); +static void vortex_adbdma_resetup(vortex_t *vortex, int adbdma); + +#ifndef CHIP_AU8810 +static void vortex_wtdma_startfifo(vortex_t * vortex, int wtdma); +static void vortex_wtdma_stopfifo(vortex_t * vortex, int wtdma); +static void vortex_wtdma_pausefifo(vortex_t * vortex, int wtdma); +static void vortex_wtdma_resumefifo(vortex_t * vortex, int wtdma); +static inline int vortex_wtdma_getlinearpos(vortex_t * vortex, int wtdma); +#endif + +/* global stuff. */ +static void vortex_codec_init(vortex_t * vortex); +static void vortex_codec_write(struct snd_ac97 * codec, unsigned short addr, + unsigned short data); +static unsigned short vortex_codec_read(struct snd_ac97 * codec, unsigned short addr); +static void vortex_spdif_init(vortex_t * vortex, int spdif_sr, int spdif_mode); + +static int vortex_core_init(vortex_t * card); +static int vortex_core_shutdown(vortex_t * card); +static void vortex_enable_int(vortex_t * card); +static irqreturn_t vortex_interrupt(int irq, void *dev_id); +static int vortex_alsafmt_aspfmt(snd_pcm_format_t alsafmt, vortex_t *v); + +/* Connection stuff. */ +static void vortex_connect_default(vortex_t * vortex, int en); +static int vortex_adb_allocroute(vortex_t * vortex, int dma, int nr_ch, + int dir, int type, int subdev); +static int vortex_adb_checkinout(vortex_t * vortex, int resmap[], int out, + int restype); +#ifndef CHIP_AU8810 +static int vortex_wt_allocroute(vortex_t * vortex, int dma, int nr_ch); +static void vortex_wt_connect(vortex_t * vortex, int en); +static void vortex_wt_init(vortex_t * vortex); +#endif + +static void vortex_route(vortex_t * vortex, int en, unsigned char channel, + unsigned char source, unsigned char dest); +#if 0 +static void vortex_routes(vortex_t * vortex, int en, unsigned char channel, + unsigned char source, unsigned char dest0, + unsigned char dest1); +#endif +static void vortex_connection_mixin_mix(vortex_t * vortex, int en, + unsigned char mixin, + unsigned char mix, int a); +static void vortex_mix_setinputvolumebyte(vortex_t * vortex, + unsigned char mix, int mixin, + unsigned char vol); +static void vortex_mix_setvolumebyte(vortex_t * vortex, unsigned char mix, + unsigned char vol); + +/* A3D functions. */ +#ifndef CHIP_AU8820 +static void vortex_Vort3D_enable(vortex_t * v); +static void vortex_Vort3D_disable(vortex_t * v); +static void vortex_Vort3D_connect(vortex_t * vortex, int en); +static void vortex_Vort3D_InitializeSource(a3dsrc_t *a, int en, vortex_t *v); +#endif + +/* Driver stuff. */ +static int vortex_gameport_register(vortex_t * card); +static void vortex_gameport_unregister(vortex_t * card); +#ifndef CHIP_AU8820 +static int vortex_eq_init(vortex_t * vortex); +static int vortex_eq_free(vortex_t * vortex); +#endif +/* ALSA stuff. */ +static int snd_vortex_new_pcm(vortex_t * vortex, int idx, int nr); +static int snd_vortex_mixer(vortex_t * vortex); +static int snd_vortex_midi(vortex_t * vortex); +#endif diff --git a/sound/pci/au88x0/au88x0_a3d.c b/sound/pci/au88x0/au88x0_a3d.c new file mode 100644 index 000000000..d5cafaa22 --- /dev/null +++ b/sound/pci/au88x0/au88x0_a3d.c @@ -0,0 +1,906 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/*************************************************************************** + * au88x0_a3d.c + * + * Fri Jul 18 14:16:22 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.net + * + * A3D. You may think i'm crazy, but this may work someday. Who knows... + ****************************************************************************/ + +/* + */ + +#include "au88x0_a3d.h" +#include "au88x0_a3ddata.c" +#include "au88x0_xtalk.h" +#include "au88x0.h" + +static void +a3dsrc_SetTimeConsts(a3dsrc_t * a, short HrtfTrack, short ItdTrack, + short GTrack, short CTrack) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_HrtfTrackTC), HrtfTrack); + hwwrite(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_ITDTrackTC), ItdTrack); + hwwrite(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_GainTrackTC), GTrack); + hwwrite(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_CoeffTrackTC), CTrack); +} + +#if 0 +static void +a3dsrc_GetTimeConsts(a3dsrc_t * a, short *HrtfTrack, short *ItdTrack, + short *GTrack, short *CTrack) +{ + // stub! +} + +#endif +/* Atmospheric absorption. */ + +static void +a3dsrc_SetAtmosTarget(a3dsrc_t * a, short aa, short b, short c, short d, + short e) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_A21Target), + (e << 0x10) | d); + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_B10Target), + (b << 0x10) | aa); + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_B2Target), c); +} + +static void +a3dsrc_SetAtmosCurrent(a3dsrc_t * a, short aa, short b, short c, short d, + short e) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_A12Current), + (e << 0x10) | d); + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_B01Current), + (b << 0x10) | aa); + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_B2Current), c); +} + +static void +a3dsrc_SetAtmosState(a3dsrc_t * a, short x1, short x2, short y1, short y2) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_x1), x1); + hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_x2), x2); + hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_y1), y1); + hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_y2), y2); +} + +#if 0 +static void +a3dsrc_GetAtmosTarget(a3dsrc_t * a, short *aa, short *b, short *c, + short *d, short *e) +{ +} +static void +a3dsrc_GetAtmosCurrent(a3dsrc_t * a, short *bb01, short *ab01, short *b2, + short *aa12, short *ba12) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + *aa12 = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_A12Current)); + *ba12 = + hwread(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_A12Current)); + *ab01 = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_B01Current)); + *bb01 = + hwread(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_B01Current)); + *b2 = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_B2Current)); +} + +static void +a3dsrc_GetAtmosState(a3dsrc_t * a, short *x1, short *x2, short *y1, short *y2) +{ + +} + +#endif +/* HRTF */ + +static void +a3dsrc_SetHrtfTarget(a3dsrc_t * a, a3d_Hrtf_t const aa, a3d_Hrtf_t const b) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int i; + + for (i = 0; i < HRTF_SZ; i++) + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, + A3D_B_HrtfTarget) + (i << 2), + (b[i] << 0x10) | aa[i]); +} + +static void +a3dsrc_SetHrtfCurrent(a3dsrc_t * a, a3d_Hrtf_t const aa, a3d_Hrtf_t const b) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int i; + + for (i = 0; i < HRTF_SZ; i++) + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, + A3D_B_HrtfCurrent) + (i << 2), + (b[i] << 0x10) | aa[i]); +} + +static void +a3dsrc_SetHrtfState(a3dsrc_t * a, a3d_Hrtf_t const aa, a3d_Hrtf_t const b) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int i; + + for (i = 0; i < HRTF_SZ; i++) + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, + A3D_B_HrtfDelayLine) + (i << 2), + (b[i] << 0x10) | aa[i]); +} + +static void a3dsrc_SetHrtfOutput(a3dsrc_t * a, short left, short right) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_HrtfOutL), left); + hwwrite(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_HrtfOutR), right); +} + +#if 0 +static void a3dsrc_GetHrtfTarget(a3dsrc_t * a, a3d_Hrtf_t aa, a3d_Hrtf_t b) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int i; + + for (i = 0; i < HRTF_SZ; i++) + aa[i] = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, + A3D_A_HrtfTarget + (i << 2))); + for (i = 0; i < HRTF_SZ; i++) + b[i] = + hwread(vortex->mmio, + a3d_addrB(a->slice, a->source, + A3D_B_HrtfTarget + (i << 2))); +} + +static void a3dsrc_GetHrtfCurrent(a3dsrc_t * a, a3d_Hrtf_t aa, a3d_Hrtf_t b) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int i; + + for (i = 0; i < HRTF_SZ; i++) + aa[i] = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, + A3D_A_HrtfCurrent + (i << 2))); + for (i = 0; i < HRTF_SZ; i++) + b[i] = + hwread(vortex->mmio, + a3d_addrB(a->slice, a->source, + A3D_B_HrtfCurrent + (i << 2))); +} + +static void a3dsrc_GetHrtfState(a3dsrc_t * a, a3d_Hrtf_t aa, a3d_Hrtf_t b) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int i; + // FIXME: verify this! + for (i = 0; i < HRTF_SZ; i++) + aa[i] = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, + A3D_A_HrtfDelayLine + (i << 2))); + for (i = 0; i < HRTF_SZ; i++) + b[i] = + hwread(vortex->mmio, + a3d_addrB(a->slice, a->source, + A3D_B_HrtfDelayLine + (i << 2))); +} + +static void a3dsrc_GetHrtfOutput(a3dsrc_t * a, short *left, short *right) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + *left = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_HrtfOutL)); + *right = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_HrtfOutR)); +} + +#endif + +/* Interaural Time Difference. + * "The other main clue that humans use to locate sounds, is called + * Interaural Time Difference (ITD). The differences in distance from + * the sound source to a listeners ears means that the sound will + * reach one ear slightly before the other....", found somewhere with google.*/ +static void a3dsrc_SetItdTarget(a3dsrc_t * a, short litd, short ritd) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + + if (litd < 0) + litd = 0; + if (litd > 0x57FF) + litd = 0x57FF; + if (ritd < 0) + ritd = 0; + if (ritd > 0x57FF) + ritd = 0x57FF; + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_ITDTarget), + (ritd << 0x10) | litd); + //hwwrite(vortex->mmio, addr(0x191DF+5, this04, this08), (ritd<<0x10)|litd); +} + +static void a3dsrc_SetItdCurrent(a3dsrc_t * a, short litd, short ritd) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + + if (litd < 0) + litd = 0; + if (litd > 0x57FF) + litd = 0x57FF; + if (ritd < 0) + ritd = 0; + if (ritd > 0x57FF) + ritd = 0x57FF; + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_ITDCurrent), + (ritd << 0x10) | litd); + //hwwrite(vortex->mmio, addr(0x191DF+1, this04, this08), (ritd<<0x10)|litd); +} + +static void a3dsrc_SetItdDline(a3dsrc_t * a, a3d_ItdDline_t const dline) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int i; + /* 45 != 40 -> Check this ! */ + for (i = 0; i < DLINE_SZ; i++) + hwwrite(vortex->mmio, + a3d_addrA(a->slice, a->source, + A3D_A_ITDDelayLine) + (i << 2), dline[i]); +} + +#if 0 +static void a3dsrc_GetItdTarget(a3dsrc_t * a, short *litd, short *ritd) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + *ritd = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_ITDTarget)); + *litd = + hwread(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_ITDTarget)); +} + +static void a3dsrc_GetItdCurrent(a3dsrc_t * a, short *litd, short *ritd) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + + *ritd = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_ITDCurrent)); + *litd = + hwread(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_ITDCurrent)); +} + +static void a3dsrc_GetItdDline(a3dsrc_t * a, a3d_ItdDline_t dline) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int i; + + for (i = 0; i < DLINE_SZ; i++) + dline[i] = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, + A3D_A_ITDDelayLine + (i << 2))); +} + +#endif +/* This is may be used for ILD Interaural Level Difference. */ + +static void a3dsrc_SetGainTarget(a3dsrc_t * a, short left, short right) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_GainTarget), + (right << 0x10) | left); +} + +static void a3dsrc_SetGainCurrent(a3dsrc_t * a, short left, short right) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_GainCurrent), + (right << 0x10) | left); +} + +#if 0 +static void a3dsrc_GetGainTarget(a3dsrc_t * a, short *left, short *right) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + *right = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_GainTarget)); + *left = + hwread(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_GainTarget)); +} + +static void a3dsrc_GetGainCurrent(a3dsrc_t * a, short *left, short *right) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + *right = + hwread(vortex->mmio, + a3d_addrA(a->slice, a->source, A3D_A_GainCurrent)); + *left = + hwread(vortex->mmio, + a3d_addrB(a->slice, a->source, A3D_B_GainCurrent)); +} + +/* CA3dIO this func seems to be inlined all over this place. */ +static void CA3dIO_WriteReg(a3dsrc_t * a, unsigned long addr, short aa, short b) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, addr, (aa << 0x10) | b); +} + +#endif +/* Generic A3D stuff */ + +static void a3dsrc_SetA3DSampleRate(a3dsrc_t * a, int sr) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int esp0 = 0; + + esp0 = (((esp0 & 0x7fffffff) | 0xB8000000) & 0x7) | ((sr & 0x1f) << 3); + hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd), esp0); + //hwwrite(vortex->mmio, 0x19C38 + (this08<<0xd), esp0); +} + +static void a3dsrc_EnableA3D(a3dsrc_t * a) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd), + 0xF0000001); + //hwwrite(vortex->mmio, 0x19C38 + (this08<<0xd), 0xF0000001); +} + +static void a3dsrc_DisableA3D(a3dsrc_t * a) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd), + 0xF0000000); +} + +static void a3dsrc_SetA3DControlReg(a3dsrc_t * a, unsigned long ctrl) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd), ctrl); +} + +static void a3dsrc_SetA3DPointerReg(a3dsrc_t * a, unsigned long ptr) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + hwwrite(vortex->mmio, A3D_SLICE_Pointers + ((a->slice) << 0xd), ptr); +} + +#if 0 +static void a3dsrc_GetA3DSampleRate(a3dsrc_t * a, int *sr) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + *sr = ((hwread(vortex->mmio, A3D_SLICE_Control + (a->slice << 0xd)) + >> 3) & 0x1f); + //*sr = ((hwread(vortex->mmio, 0x19C38 + (this08<<0xd))>>3)&0x1f); +} + +static void a3dsrc_GetA3DControlReg(a3dsrc_t * a, unsigned long *ctrl) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + *ctrl = hwread(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd)); +} + +static void a3dsrc_GetA3DPointerReg(a3dsrc_t * a, unsigned long *ptr) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + *ptr = hwread(vortex->mmio, A3D_SLICE_Pointers + ((a->slice) << 0xd)); +} + +#endif +static void a3dsrc_ZeroSliceIO(a3dsrc_t * a) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + int i; + + for (i = 0; i < 8; i++) + hwwrite(vortex->mmio, + A3D_SLICE_VDBDest + + ((((a->slice) << 0xb) + i) << 2), 0); + for (i = 0; i < 4; i++) + hwwrite(vortex->mmio, + A3D_SLICE_VDBSource + + ((((a->slice) << 0xb) + i) << 2), 0); +} + +/* Reset Single A3D source. */ +static void a3dsrc_ZeroState(a3dsrc_t * a) +{ + /* + pr_debug( "vortex: ZeroState slice: %d, source %d\n", + a->slice, a->source); + */ + a3dsrc_SetAtmosState(a, 0, 0, 0, 0); + a3dsrc_SetHrtfState(a, A3dHrirZeros, A3dHrirZeros); + a3dsrc_SetItdDline(a, A3dItdDlineZeros); + a3dsrc_SetHrtfOutput(a, 0, 0); + a3dsrc_SetTimeConsts(a, 0, 0, 0, 0); + + a3dsrc_SetAtmosCurrent(a, 0, 0, 0, 0, 0); + a3dsrc_SetAtmosTarget(a, 0, 0, 0, 0, 0); + a3dsrc_SetItdCurrent(a, 0, 0); + a3dsrc_SetItdTarget(a, 0, 0); + a3dsrc_SetGainCurrent(a, 0, 0); + a3dsrc_SetGainTarget(a, 0, 0); + + a3dsrc_SetHrtfCurrent(a, A3dHrirZeros, A3dHrirZeros); + a3dsrc_SetHrtfTarget(a, A3dHrirZeros, A3dHrirZeros); +} + +/* Reset entire A3D engine */ +static void a3dsrc_ZeroStateA3D(a3dsrc_t *a, vortex_t *v) +{ + int i, var, var2; + + if ((a->vortex) == NULL) { + dev_err(v->card->dev, + "ZeroStateA3D: ERROR: a->vortex is NULL\n"); + return; + } + + a3dsrc_SetA3DControlReg(a, 0); + a3dsrc_SetA3DPointerReg(a, 0); + + var = a->slice; + var2 = a->source; + for (i = 0; i < 4; i++) { + a->slice = i; + a3dsrc_ZeroSliceIO(a); + //a3dsrc_ZeroState(a); + } + a->source = var2; + a->slice = var; +} + +/* Program A3D block as pass through */ +static void a3dsrc_ProgramPipe(a3dsrc_t * a) +{ + a3dsrc_SetTimeConsts(a, 0, 0, 0, 0); + a3dsrc_SetAtmosCurrent(a, 0, 0x4000, 0, 0, 0); + a3dsrc_SetAtmosTarget(a, 0x4000, 0, 0, 0, 0); + a3dsrc_SetItdCurrent(a, 0, 0); + a3dsrc_SetItdTarget(a, 0, 0); + a3dsrc_SetGainCurrent(a, 0x7fff, 0x7fff); + a3dsrc_SetGainTarget(a, 0x7fff, 0x7fff); + + /* SET HRTF HERE */ + + /* Single spike leads to identity transfer function. */ + a3dsrc_SetHrtfCurrent(a, A3dHrirImpulse, A3dHrirImpulse); + a3dsrc_SetHrtfTarget(a, A3dHrirImpulse, A3dHrirImpulse); + + /* Test: Sounds saturated. */ + //a3dsrc_SetHrtfCurrent(a, A3dHrirSatTest, A3dHrirSatTest); + //a3dsrc_SetHrtfTarget(a, A3dHrirSatTest, A3dHrirSatTest); +} + +/* VDB = Vortex audio Dataflow Bus */ +#if 0 +static void a3dsrc_ClearVDBData(a3dsrc_t * a, unsigned long aa) +{ + vortex_t *vortex = (vortex_t *) (a->vortex); + + // ((aa >> 2) << 8) - (aa >> 2) + hwwrite(vortex->mmio, + a3d_addrS(a->slice, A3D_SLICE_VDBDest) + (a->source << 2), 0); + hwwrite(vortex->mmio, + a3d_addrS(a->slice, + A3D_SLICE_VDBDest + 4) + (a->source << 2), 0); + /* + hwwrite(vortex->mmio, 0x19c00 + (((aa>>2)*255*4)+aa)*8, 0); + hwwrite(vortex->mmio, 0x19c04 + (((aa>>2)*255*4)+aa)*8, 0); + */ +} +#endif + +/* A3D HwSource stuff. */ + +static void vortex_A3dSourceHw_Initialize(vortex_t * v, int source, int slice) +{ + a3dsrc_t *a3dsrc = &(v->a3d[source + (slice * 4)]); + //a3dsrc_t *a3dsrc = &(v->a3d[source + (slice*4)]); + + a3dsrc->vortex = (void *)v; + a3dsrc->source = source; /* source */ + a3dsrc->slice = slice; /* slice */ + a3dsrc_ZeroState(a3dsrc); + /* Added by me. */ + a3dsrc_SetA3DSampleRate(a3dsrc, 0x11); +} + +static int Vort3DRend_Initialize(vortex_t * v, unsigned short mode) +{ + v->xt_mode = mode; /* this_14 */ + + vortex_XtalkHw_init(v); + vortex_XtalkHw_SetGainsAllChan(v); + switch (v->xt_mode) { + case XT_SPEAKER0: + vortex_XtalkHw_ProgramXtalkNarrow(v); + break; + case XT_SPEAKER1: + vortex_XtalkHw_ProgramXtalkWide(v); + break; + default: + case XT_HEADPHONE: + vortex_XtalkHw_ProgramPipe(v); + break; + case XT_DIAMOND: + vortex_XtalkHw_ProgramDiamondXtalk(v); + break; + } + vortex_XtalkHw_SetSampleRate(v, 0x11); + vortex_XtalkHw_Enable(v); + return 0; +} + +/* 3D Sound entry points. */ + +static int vortex_a3d_register_controls(vortex_t * vortex); +static void vortex_a3d_unregister_controls(vortex_t * vortex); +/* A3D base support init/shudown */ +static void vortex_Vort3D_enable(vortex_t *v) +{ + int i; + + Vort3DRend_Initialize(v, XT_HEADPHONE); + for (i = 0; i < NR_A3D; i++) { + vortex_A3dSourceHw_Initialize(v, i % 4, i >> 2); + a3dsrc_ZeroStateA3D(&v->a3d[0], v); + } + /* Register ALSA controls */ + vortex_a3d_register_controls(v); +} + +static void vortex_Vort3D_disable(vortex_t * v) +{ + vortex_XtalkHw_Disable(v); + vortex_a3d_unregister_controls(v); +} + +/* Make A3D subsystem connections. */ +static void vortex_Vort3D_connect(vortex_t * v, int en) +{ + int i; + +// Disable AU8810 routes, since they seem to be wrong (in au8810.h). +#ifdef CHIP_AU8810 + return; +#endif + +#if 1 + /* Alloc Xtalk mixin resources */ + v->mixxtlk[0] = + vortex_adb_checkinout(v, v->fixed_res, en, VORTEX_RESOURCE_MIXIN); + if (v->mixxtlk[0] < 0) { + dev_warn(v->card->dev, + "vortex_Vort3D: ERROR: not enough free mixer resources.\n"); + return; + } + v->mixxtlk[1] = + vortex_adb_checkinout(v, v->fixed_res, en, VORTEX_RESOURCE_MIXIN); + if (v->mixxtlk[1] < 0) { + dev_warn(v->card->dev, + "vortex_Vort3D: ERROR: not enough free mixer resources.\n"); + return; + } +#endif + + /* Connect A3D -> XTALK */ + for (i = 0; i < 4; i++) { + // 2 outputs per each A3D slice. + vortex_route(v, en, 0x11, ADB_A3DOUT(i * 2), ADB_XTALKIN(i)); + vortex_route(v, en, 0x11, ADB_A3DOUT(i * 2) + 1, ADB_XTALKIN(5 + i)); + } +#if 0 + vortex_route(v, en, 0x11, ADB_XTALKOUT(0), ADB_EQIN(2)); + vortex_route(v, en, 0x11, ADB_XTALKOUT(1), ADB_EQIN(3)); +#else + /* Connect XTalk -> mixer */ + vortex_route(v, en, 0x11, ADB_XTALKOUT(0), ADB_MIXIN(v->mixxtlk[0])); + vortex_route(v, en, 0x11, ADB_XTALKOUT(1), ADB_MIXIN(v->mixxtlk[1])); + vortex_connection_mixin_mix(v, en, v->mixxtlk[0], v->mixplayb[0], 0); + vortex_connection_mixin_mix(v, en, v->mixxtlk[1], v->mixplayb[1], 0); + vortex_mix_setinputvolumebyte(v, v->mixplayb[0], v->mixxtlk[0], + en ? MIX_DEFIGAIN : VOL_MIN); + vortex_mix_setinputvolumebyte(v, v->mixplayb[1], v->mixxtlk[1], + en ? MIX_DEFIGAIN : VOL_MIN); + if (VORTEX_IS_QUAD(v)) { + vortex_connection_mixin_mix(v, en, v->mixxtlk[0], + v->mixplayb[2], 0); + vortex_connection_mixin_mix(v, en, v->mixxtlk[1], + v->mixplayb[3], 0); + vortex_mix_setinputvolumebyte(v, v->mixplayb[2], + v->mixxtlk[0], + en ? MIX_DEFIGAIN : VOL_MIN); + vortex_mix_setinputvolumebyte(v, v->mixplayb[3], + v->mixxtlk[1], + en ? MIX_DEFIGAIN : VOL_MIN); + } +#endif +} + +/* Initialize one single A3D source. */ +static void vortex_Vort3D_InitializeSource(a3dsrc_t *a, int en, vortex_t *v) +{ + if (a->vortex == NULL) { + dev_warn(v->card->dev, + "Vort3D_InitializeSource: A3D source not initialized\n"); + return; + } + if (en) { + a3dsrc_ProgramPipe(a); + a3dsrc_SetA3DSampleRate(a, 0x11); + a3dsrc_SetTimeConsts(a, HrtfTCDefault, + ItdTCDefault, GainTCDefault, + CoefTCDefault); + /* Remark: zero gain is muted. */ + //a3dsrc_SetGainTarget(a,0,0); + //a3dsrc_SetGainCurrent(a,0,0); + a3dsrc_EnableA3D(a); + } else { + a3dsrc_DisableA3D(a); + a3dsrc_ZeroState(a); + } +} + +/* Conversion of coordinates into 3D parameters. */ + +static void vortex_a3d_coord2hrtf(a3d_Hrtf_t hrtf, int *coord) +{ + /* FIXME: implement this. */ + +} +static void vortex_a3d_coord2itd(a3d_Itd_t itd, int *coord) +{ + /* FIXME: implement this. */ + +} +static void vortex_a3d_coord2ild(a3d_LRGains_t ild, int left, int right) +{ + /* FIXME: implement this. */ + +} +static void vortex_a3d_translate_filter(a3d_atmos_t filter, int *params) +{ + /* FIXME: implement this. */ + +} + +/* ALSA control interface. */ + +static int +snd_vortex_a3d_hrtf_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 6; + uinfo->value.integer.min = 0x00000000; + uinfo->value.integer.max = 0xffffffff; + return 0; +} +static int +snd_vortex_a3d_itd_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 = 0x00000000; + uinfo->value.integer.max = 0xffffffff; + return 0; +} +static int +snd_vortex_a3d_ild_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 = 0x00000000; + uinfo->value.integer.max = 0xffffffff; + return 0; +} +static int +snd_vortex_a3d_filter_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 4; + uinfo->value.integer.min = 0x00000000; + uinfo->value.integer.max = 0xffffffff; + return 0; +} + +static int +snd_vortex_a3d_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + //a3dsrc_t *a = snd_kcontrol_chip(kcontrol); + /* No read yet. Would this be really useable/needed ? */ + + return 0; +} + +static int +snd_vortex_a3d_hrtf_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + a3dsrc_t *a = snd_kcontrol_chip(kcontrol); + int i; + int coord[6]; + for (i = 0; i < 6; i++) + coord[i] = ucontrol->value.integer.value[i]; + /* Translate orientation coordinates to a3d params. */ + vortex_a3d_coord2hrtf(a->hrtf[0], coord); + vortex_a3d_coord2hrtf(a->hrtf[1], coord); + a3dsrc_SetHrtfTarget(a, a->hrtf[0], a->hrtf[1]); + a3dsrc_SetHrtfCurrent(a, a->hrtf[0], a->hrtf[1]); + return 1; +} + +static int +snd_vortex_a3d_itd_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + a3dsrc_t *a = snd_kcontrol_chip(kcontrol); + int coord[6]; + int i; + for (i = 0; i < 6; i++) + coord[i] = ucontrol->value.integer.value[i]; + /* Translate orientation coordinates to a3d params. */ + vortex_a3d_coord2itd(a->hrtf[0], coord); + vortex_a3d_coord2itd(a->hrtf[1], coord); + /* Inter aural time difference. */ + a3dsrc_SetItdTarget(a, a->itd[0], a->itd[1]); + a3dsrc_SetItdCurrent(a, a->itd[0], a->itd[1]); + a3dsrc_SetItdDline(a, a->dline); + return 1; +} + +static int +snd_vortex_a3d_ild_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + a3dsrc_t *a = snd_kcontrol_chip(kcontrol); + int l, r; + /* There may be some scale tranlation needed here. */ + l = ucontrol->value.integer.value[0]; + r = ucontrol->value.integer.value[1]; + vortex_a3d_coord2ild(a->ild, l, r); + /* Left Right panning. */ + a3dsrc_SetGainTarget(a, l, r); + a3dsrc_SetGainCurrent(a, l, r); + return 1; +} + +static int +snd_vortex_a3d_filter_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + a3dsrc_t *a = snd_kcontrol_chip(kcontrol); + int i; + int params[6]; + for (i = 0; i < 6; i++) + params[i] = ucontrol->value.integer.value[i]; + /* Translate generic filter params to a3d filter params. */ + vortex_a3d_translate_filter(a->filter, params); + /* Atmospheric absorption and filtering. */ + a3dsrc_SetAtmosTarget(a, a->filter[0], + a->filter[1], a->filter[2], + a->filter[3], a->filter[4]); + a3dsrc_SetAtmosCurrent(a, a->filter[0], + a->filter[1], a->filter[2], + a->filter[3], a->filter[4]); + return 1; +} + +static const struct snd_kcontrol_new vortex_a3d_kcontrol = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Playback PCM advanced processing", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_vortex_a3d_hrtf_info, + .get = snd_vortex_a3d_get, + .put = snd_vortex_a3d_hrtf_put, +}; + +/* Control (un)registration. */ +static int vortex_a3d_register_controls(vortex_t *vortex) +{ + struct snd_kcontrol *kcontrol; + int err, i; + /* HRTF controls. */ + for (i = 0; i < NR_A3D; i++) { + kcontrol = snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i]); + if (!kcontrol) + return -ENOMEM; + kcontrol->id.numid = CTRLID_HRTF; + kcontrol->info = snd_vortex_a3d_hrtf_info; + kcontrol->put = snd_vortex_a3d_hrtf_put; + err = snd_ctl_add(vortex->card, kcontrol); + if (err < 0) + return err; + } + /* ITD controls. */ + for (i = 0; i < NR_A3D; i++) { + kcontrol = snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i]); + if (!kcontrol) + return -ENOMEM; + kcontrol->id.numid = CTRLID_ITD; + kcontrol->info = snd_vortex_a3d_itd_info; + kcontrol->put = snd_vortex_a3d_itd_put; + err = snd_ctl_add(vortex->card, kcontrol); + if (err < 0) + return err; + } + /* ILD (gains) controls. */ + for (i = 0; i < NR_A3D; i++) { + kcontrol = snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i]); + if (!kcontrol) + return -ENOMEM; + kcontrol->id.numid = CTRLID_GAINS; + kcontrol->info = snd_vortex_a3d_ild_info; + kcontrol->put = snd_vortex_a3d_ild_put; + err = snd_ctl_add(vortex->card, kcontrol); + if (err < 0) + return err; + } + /* Filter controls. */ + for (i = 0; i < NR_A3D; i++) { + kcontrol = snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i]); + if (!kcontrol) + return -ENOMEM; + kcontrol->id.numid = CTRLID_FILTER; + kcontrol->info = snd_vortex_a3d_filter_info; + kcontrol->put = snd_vortex_a3d_filter_put; + err = snd_ctl_add(vortex->card, kcontrol); + if (err < 0) + return err; + } + return 0; +} + +static void vortex_a3d_unregister_controls(vortex_t * vortex) +{ + +} + +/* End of File*/ diff --git a/sound/pci/au88x0/au88x0_a3d.h b/sound/pci/au88x0/au88x0_a3d.h new file mode 100644 index 000000000..4078173da --- /dev/null +++ b/sound/pci/au88x0/au88x0_a3d.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/*************************************************************************** + * au88x0_a3d.h + * + * Fri Jul 18 14:16:03 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.net + ****************************************************************************/ + +/* + */ + +#ifndef _AU88X0_A3D_H +#define _AU88X0_A3D_H + +//#include + +#define HRTF_SZ 0x38 +#define DLINE_SZ 0x28 + +#define CTRLID_HRTF 1 +#define CTRLID_ITD 2 +#define CTRLID_ILD 4 +#define CTRLID_FILTER 8 +#define CTRLID_GAINS 16 + +/* 3D parameter structs */ +typedef unsigned short int a3d_Hrtf_t[HRTF_SZ]; +typedef unsigned short int a3d_ItdDline_t[DLINE_SZ]; +typedef unsigned short int a3d_atmos_t[5]; +typedef unsigned short int a3d_LRGains_t[2]; +typedef unsigned short int a3d_Itd_t[2]; +typedef unsigned short int a3d_Ild_t[2]; + +typedef struct { + void *vortex; // Formerly CAsp4HwIO*, now vortex_t*. + unsigned int source; /* this_04 */ + unsigned int slice; /* this_08 */ + a3d_Hrtf_t hrtf[2]; + a3d_Itd_t itd; + a3d_Ild_t ild; + a3d_ItdDline_t dline; + a3d_atmos_t filter; +} a3dsrc_t; + +/* First Register bank */ + +#define A3D_A_HrtfCurrent 0x18000 /* 56 ULONG */ +#define A3D_A_GainCurrent 0x180E0 +#define A3D_A_GainTarget 0x180E4 +#define A3D_A_A12Current 0x180E8 /* Atmospheric current. */ +#define A3D_A_A21Target 0x180EC /* Atmospheric target */ +#define A3D_A_B01Current 0x180F0 /* Atmospheric current */ +#define A3D_A_B10Target 0x180F4 /* Atmospheric target */ +#define A3D_A_B2Current 0x180F8 /* Atmospheric current */ +#define A3D_A_B2Target 0x180FC /* Atmospheric target */ +#define A3D_A_HrtfTarget 0x18100 /* 56 ULONG */ +#define A3D_A_ITDCurrent 0x181E0 +#define A3D_A_ITDTarget 0x181E4 +#define A3D_A_HrtfDelayLine 0x181E8 /* 56 ULONG */ +#define A3D_A_ITDDelayLine 0x182C8 /* 40/45 ULONG */ +#define A3D_A_HrtfTrackTC 0x1837C /* Time Constants */ +#define A3D_A_GainTrackTC 0x18380 +#define A3D_A_CoeffTrackTC 0x18384 +#define A3D_A_ITDTrackTC 0x18388 +#define A3D_A_x1 0x1838C +#define A3D_A_x2 0x18390 +#define A3D_A_y1 0x18394 +#define A3D_A_y2 0x18398 +#define A3D_A_HrtfOutL 0x1839C +#define A3D_A_HrtfOutR 0x183A0 +#define A3D_A_TAIL 0x183A4 + +/* Second register bank */ +#define A3D_B_HrtfCurrent 0x19000 /* 56 ULONG */ +#define A3D_B_GainCurrent 0x190E0 +#define A3D_B_GainTarget 0x190E4 +#define A3D_B_A12Current 0x190E8 +#define A3D_B_A21Target 0x190EC +#define A3D_B_B01Current 0x190F0 +#define A3D_B_B10Target 0x190F4 +#define A3D_B_B2Current 0x190F8 +#define A3D_B_B2Target 0x190FC +#define A3D_B_HrtfTarget 0x19100 /* 56 ULONG */ +#define A3D_B_ITDCurrent 0x191E0 +#define A3D_B_ITDTarget 0x191E4 +#define A3D_B_HrtfDelayLine 0x191E8 /* 56 ULONG */ +#define A3D_B_TAIL 0x192C8 + +/* There are 4 slices, 4 a3d each = 16 a3d sources. */ +#define A3D_SLICE_BANK_A 0x18000 /* 4 sources */ +#define A3D_SLICE_BANK_B 0x19000 /* 4 sources */ +#define A3D_SLICE_VDBDest 0x19C00 /* 8 ULONG */ +#define A3D_SLICE_VDBSource 0x19C20 /* 4 ULONG */ +#define A3D_SLICE_ABReg 0x19C30 +#define A3D_SLICE_CReg 0x19C34 +#define A3D_SLICE_Control 0x19C38 +#define A3D_SLICE_DebugReserved 0x19C3c /* Dangerous! */ +#define A3D_SLICE_Pointers 0x19C40 +#define A3D_SLICE_TAIL 0x1A000 + +// Slice size: 0x2000 +// Source size: 0x3A4, 0x2C8 + +/* Address generator macro. */ +#define a3d_addrA(slice,source,reg) (((slice)<<0xd)+((source)*0x3A4)+(reg)) +#define a3d_addrB(slice,source,reg) (((slice)<<0xd)+((source)*0x2C8)+(reg)) +#define a3d_addrS(slice,reg) (((slice)<<0xd)+(reg)) +//#define a3d_addr(slice,source,reg) (((reg)>=0x19000) ? a3d_addr2((slice),(source),(reg)) : a3d_addr1((slice),(source),(reg))) + +#endif /* _AU88X0_A3D_H */ diff --git a/sound/pci/au88x0/au88x0_a3ddata.c b/sound/pci/au88x0/au88x0_a3ddata.c new file mode 100644 index 000000000..a5da3b3a5 --- /dev/null +++ b/sound/pci/au88x0/au88x0_a3ddata.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/*************************************************************************** + * au88x0_a3ddata.c + * + * Wed Nov 19 21:11:32 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.org + ****************************************************************************/ + +/* + */ + +/* Constant initializer values. */ + +static const a3d_Hrtf_t A3dHrirZeros = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + 0, 0, 0 +}; + +static __maybe_unused const a3d_Hrtf_t A3dHrirImpulse = { + 0x7fff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + 0, 0, 0 +}; + +static __maybe_unused const a3d_Hrtf_t A3dHrirOnes = { + 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, + 0x7fff, + 0x7fff, + 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, + 0x7fff, + 0x7fff, + 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, + 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, + 0x7fff, + 0x7fff, + 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, + 0x7fff, + 0x7fff, + 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff +}; + +static __maybe_unused const a3d_Hrtf_t A3dHrirSatTest = { + 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, + 0x7fff, + 0x7fff, + 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, + 0x8001, + 0x8001, + 0x7fff, 0x0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static __maybe_unused const a3d_Hrtf_t A3dHrirDImpulse = { + 0, 0x7fff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + 0, 0, 0 +}; + +static const a3d_ItdDline_t A3dItdDlineZeros = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static short const GainTCDefault = 0x300; +static short const ItdTCDefault = 0x0C8; +static short const HrtfTCDefault = 0x147; +static short const CoefTCDefault = 0x300; diff --git a/sound/pci/au88x0/au88x0_core.c b/sound/pci/au88x0/au88x0_core.c new file mode 100644 index 000000000..e5d867637 --- /dev/null +++ b/sound/pci/au88x0/au88x0_core.c @@ -0,0 +1,2862 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* + Vortex core low level functions. + + Author: Manuel Jander (mjander@users.sourceforge.cl) + These functions are mainly the result of translations made + from the original disassembly of the au88x0 binary drivers, + written by Aureal before they went down. + Many thanks to the Jeff Muizelaar, Kester Maddock, and whoever + contributed to the OpenVortex project. + The author of this file, put the few available pieces together + and translated the rest of the riddle (Mix, Src and connection stuff). + Some things are still to be discovered, and their meanings are unclear. + + Some of these functions aren't intended to be really used, rather + to help to understand how does the AU88X0 chips work. Keep them in, because + they could be used somewhere in the future. + + This code hasn't been tested or proof read thoroughly. If you wanna help, + take a look at the AU88X0 assembly and check if this matches. + Functions tested ok so far are (they show the desired effect + at least): + vortex_routes(); (1 bug fixed). + vortex_adb_addroute(); + vortex_adb_addroutes(); + vortex_connect_codecplay(); + vortex_src_flushbuffers(); + vortex_adbdma_setmode(); note: still some unknown arguments! + vortex_adbdma_startfifo(); + vortex_adbdma_stopfifo(); + vortex_fifo_setadbctrl(); note: still some unknown arguments! + vortex_mix_setinputvolumebyte(); + vortex_mix_enableinput(); + vortex_mixer_addWTD(); (fixed) + vortex_connection_adbdma_src_src(); + vortex_connection_adbdma_src(); + vortex_src_change_convratio(); + vortex_src_addWTD(); (fixed) + + History: + + 01-03-2003 First revision. + 01-21-2003 Some bug fixes. + 17-02-2003 many bugfixes after a big versioning mess. + 18-02-2003 JAAAAAHHHUUUUUU!!!! The mixer works !! I'm just so happy ! + (2 hours later...) I cant believe it! Im really lucky today. + Now the SRC is working too! Yeah! XMMS works ! + 20-02-2003 First steps into the ALSA world. + 28-02-2003 As my birthday present, i discovered how the DMA buffer pages really + work :-). It was all wrong. + 12-03-2003 ALSA driver starts working (2 channels). + 16-03-2003 More srcblock_setupchannel discoveries. + 12-04-2003 AU8830 playback support. Recording in the works. + 17-04-2003 vortex_route() and vortex_routes() bug fixes. AU8830 recording + works now, but chipn' dale effect is still there. + 16-05-2003 SrcSetupChannel cleanup. Moved the Src setup stuff entirely + into au88x0_pcm.c . + 06-06-2003 Buffer shifter bugfix. Mixer volume fix. + 07-12-2003 A3D routing finally fixed. Believed to be OK. + 25-03-2004 Many thanks to Claudia, for such valuable bug reports. + +*/ + +#include "au88x0.h" +#include "au88x0_a3d.h" +#include + +/* MIXER (CAsp4Mix.s and CAsp4Mixer.s) */ + +// FIXME: get rid of this. +static int mchannels[NR_MIXIN]; +static int rampchs[NR_MIXIN]; + +static void vortex_mixer_en_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_MIXER_SR, + hwread(vortex->mmio, VORTEX_MIXER_SR) | (0x1 << channel)); +} +static void vortex_mixer_dis_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_MIXER_SR, + hwread(vortex->mmio, VORTEX_MIXER_SR) & ~(0x1 << channel)); +} + +#if 0 +static void +vortex_mix_muteinputgain(vortex_t * vortex, unsigned char mix, + unsigned char channel) +{ + hwwrite(vortex->mmio, VORTEX_MIX_INVOL_A + ((mix << 5) + channel), + 0x80); + hwwrite(vortex->mmio, VORTEX_MIX_INVOL_B + ((mix << 5) + channel), + 0x80); +} + +static int vortex_mix_getvolume(vortex_t * vortex, unsigned char mix) +{ + int a; + a = hwread(vortex->mmio, VORTEX_MIX_VOL_A + (mix << 2)) & 0xff; + //FP2LinearFrac(a); + return (a); +} + +static int +vortex_mix_getinputvolume(vortex_t * vortex, unsigned char mix, + int channel, int *vol) +{ + int a; + if (!(mchannels[mix] & (1 << channel))) + return 0; + a = hwread(vortex->mmio, + VORTEX_MIX_INVOL_A + (((mix << 5) + channel) << 2)); + /* + if (rampchs[mix] == 0) + a = FP2LinearFrac(a); + else + a = FP2LinearFracWT(a); + */ + *vol = a; + return (0); +} + +static unsigned int vortex_mix_boost6db(unsigned char vol) +{ + return (vol + 8); /* WOW! what a complex function! */ +} + +static void vortex_mix_rampvolume(vortex_t * vortex, int mix) +{ + int ch; + char a; + // This function is intended for ramping down only (see vortex_disableinput()). + for (ch = 0; ch < 0x20; ch++) { + if (((1 << ch) & rampchs[mix]) == 0) + continue; + a = hwread(vortex->mmio, + VORTEX_MIX_INVOL_B + (((mix << 5) + ch) << 2)); + if (a > -126) { + a -= 2; + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_A + + (((mix << 5) + ch) << 2), a); + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_B + + (((mix << 5) + ch) << 2), a); + } else + vortex_mix_killinput(vortex, mix, ch); + } +} + +static int +vortex_mix_getenablebit(vortex_t * vortex, unsigned char mix, int mixin) +{ + int addr, temp; + if (mixin >= 0) + addr = mixin; + else + addr = mixin + 3; + addr = ((mix << 3) + (addr >> 2)) << 2; + temp = hwread(vortex->mmio, VORTEX_MIX_ENIN + addr); + return ((temp >> (mixin & 3)) & 1); +} +#endif +static void +vortex_mix_setvolumebyte(vortex_t * vortex, unsigned char mix, + unsigned char vol) +{ + int temp; + hwwrite(vortex->mmio, VORTEX_MIX_VOL_A + (mix << 2), vol); + if (1) { /*if (this_10) */ + temp = hwread(vortex->mmio, VORTEX_MIX_VOL_B + (mix << 2)); + if ((temp != 0x80) || (vol == 0x80)) + return; + } + hwwrite(vortex->mmio, VORTEX_MIX_VOL_B + (mix << 2), vol); +} + +static void +vortex_mix_setinputvolumebyte(vortex_t * vortex, unsigned char mix, + int mixin, unsigned char vol) +{ + int temp; + + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_A + (((mix << 5) + mixin) << 2), vol); + if (1) { /* this_10, initialized to 1. */ + temp = + hwread(vortex->mmio, + VORTEX_MIX_INVOL_B + (((mix << 5) + mixin) << 2)); + if ((temp != 0x80) || (vol == 0x80)) + return; + } + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_B + (((mix << 5) + mixin) << 2), vol); +} + +static void +vortex_mix_setenablebit(vortex_t * vortex, unsigned char mix, int mixin, int en) +{ + int temp, addr; + + if (mixin < 0) + addr = (mixin + 3); + else + addr = mixin; + addr = ((mix << 3) + (addr >> 2)) << 2; + temp = hwread(vortex->mmio, VORTEX_MIX_ENIN + addr); + if (en) + temp |= (1 << (mixin & 3)); + else + temp &= ~(1 << (mixin & 3)); + /* Mute input. Astatic void crackling? */ + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_B + (((mix << 5) + mixin) << 2), 0x80); + /* Looks like clear buffer. */ + hwwrite(vortex->mmio, VORTEX_MIX_SMP + (mixin << 2), 0x0); + hwwrite(vortex->mmio, VORTEX_MIX_SMP + 4 + (mixin << 2), 0x0); + /* Write enable bit. */ + hwwrite(vortex->mmio, VORTEX_MIX_ENIN + addr, temp); +} + +static void +vortex_mix_killinput(vortex_t * vortex, unsigned char mix, int mixin) +{ + rampchs[mix] &= ~(1 << mixin); + vortex_mix_setinputvolumebyte(vortex, mix, mixin, 0x80); + mchannels[mix] &= ~(1 << mixin); + vortex_mix_setenablebit(vortex, mix, mixin, 0); +} + +static void +vortex_mix_enableinput(vortex_t * vortex, unsigned char mix, int mixin) +{ + vortex_mix_killinput(vortex, mix, mixin); + if ((mchannels[mix] & (1 << mixin)) == 0) { + vortex_mix_setinputvolumebyte(vortex, mix, mixin, 0x80); /*0x80 : mute */ + mchannels[mix] |= (1 << mixin); + } + vortex_mix_setenablebit(vortex, mix, mixin, 1); +} + +static void +vortex_mix_disableinput(vortex_t * vortex, unsigned char mix, int channel, + int ramp) +{ + if (ramp) { + rampchs[mix] |= (1 << channel); + // Register callback. + //vortex_mix_startrampvolume(vortex); + vortex_mix_killinput(vortex, mix, channel); + } else + vortex_mix_killinput(vortex, mix, channel); +} + +static int +vortex_mixer_addWTD(vortex_t * vortex, unsigned char mix, unsigned char ch) +{ + int temp, lifeboat = 0, prev; + + temp = hwread(vortex->mmio, VORTEX_MIXER_SR); + if ((temp & (1 << ch)) == 0) { + hwwrite(vortex->mmio, VORTEX_MIXER_CHNBASE + (ch << 2), mix); + vortex_mixer_en_sr(vortex, ch); + return 1; + } + prev = VORTEX_MIXER_CHNBASE + (ch << 2); + temp = hwread(vortex->mmio, prev); + while (temp & 0x10) { + prev = VORTEX_MIXER_RTBASE + ((temp & 0xf) << 2); + temp = hwread(vortex->mmio, prev); + //printk(KERN_INFO "vortex: mixAddWTD: while addr=%x, val=%x\n", prev, temp); + if ((++lifeboat) > 0xf) { + dev_err(vortex->card->dev, + "vortex_mixer_addWTD: lifeboat overflow\n"); + return 0; + } + } + hwwrite(vortex->mmio, VORTEX_MIXER_RTBASE + ((temp & 0xf) << 2), mix); + hwwrite(vortex->mmio, prev, (temp & 0xf) | 0x10); + return 1; +} + +static int +vortex_mixer_delWTD(vortex_t * vortex, unsigned char mix, unsigned char ch) +{ + int esp14 = -1, esp18, eax, ebx, edx, ebp, esi = 0; + //int esp1f=edi(while)=src, esp10=ch; + + eax = hwread(vortex->mmio, VORTEX_MIXER_SR); + if (((1 << ch) & eax) == 0) { + dev_err(vortex->card->dev, "mix ALARM %x\n", eax); + return 0; + } + ebp = VORTEX_MIXER_CHNBASE + (ch << 2); + esp18 = hwread(vortex->mmio, ebp); + if (esp18 & 0x10) { + ebx = (esp18 & 0xf); + if (mix == ebx) { + ebx = VORTEX_MIXER_RTBASE + (mix << 2); + edx = hwread(vortex->mmio, ebx); + //7b60 + hwwrite(vortex->mmio, ebp, edx); + hwwrite(vortex->mmio, ebx, 0); + } else { + //7ad3 + edx = + hwread(vortex->mmio, + VORTEX_MIXER_RTBASE + (ebx << 2)); + //printk(KERN_INFO "vortex: mixdelWTD: 1 addr=%x, val=%x, src=%x\n", ebx, edx, src); + while ((edx & 0xf) != mix) { + if ((esi) > 0xf) { + dev_err(vortex->card->dev, + "mixdelWTD: error lifeboat overflow\n"); + return 0; + } + esp14 = ebx; + ebx = edx & 0xf; + ebp = ebx << 2; + edx = + hwread(vortex->mmio, + VORTEX_MIXER_RTBASE + ebp); + //printk(KERN_INFO "vortex: mixdelWTD: while addr=%x, val=%x\n", ebp, edx); + esi++; + } + //7b30 + ebp = ebx << 2; + if (edx & 0x10) { /* Delete entry in between others */ + ebx = VORTEX_MIXER_RTBASE + ((edx & 0xf) << 2); + edx = hwread(vortex->mmio, ebx); + //7b60 + hwwrite(vortex->mmio, + VORTEX_MIXER_RTBASE + ebp, edx); + hwwrite(vortex->mmio, ebx, 0); + //printk(KERN_INFO "vortex mixdelWTD between addr= 0x%x, val= 0x%x\n", ebp, edx); + } else { /* Delete last entry */ + //7b83 + if (esp14 == -1) + hwwrite(vortex->mmio, + VORTEX_MIXER_CHNBASE + + (ch << 2), esp18 & 0xef); + else { + ebx = (0xffffffe0 & edx) | (0xf & ebx); + hwwrite(vortex->mmio, + VORTEX_MIXER_RTBASE + + (esp14 << 2), ebx); + //printk(KERN_INFO "vortex mixdelWTD last addr= 0x%x, val= 0x%x\n", esp14, ebx); + } + hwwrite(vortex->mmio, + VORTEX_MIXER_RTBASE + ebp, 0); + return 1; + } + } + } else { + //printk(KERN_INFO "removed last mix\n"); + //7be0 + vortex_mixer_dis_sr(vortex, ch); + hwwrite(vortex->mmio, ebp, 0); + } + return 1; +} + +static void vortex_mixer_init(vortex_t * vortex) +{ + u32 addr; + int x; + + // FIXME: get rid of this crap. + memset(mchannels, 0, NR_MIXOUT * sizeof(int)); + memset(rampchs, 0, NR_MIXOUT * sizeof(int)); + + addr = VORTEX_MIX_SMP + 0x17c; + for (x = 0x5f; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } + addr = VORTEX_MIX_ENIN + 0x1fc; + for (x = 0x7f; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } + addr = VORTEX_MIX_SMP + 0x17c; + for (x = 0x5f; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } + addr = VORTEX_MIX_INVOL_A + 0x7fc; + for (x = 0x1ff; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x80); + addr -= 4; + } + addr = VORTEX_MIX_VOL_A + 0x3c; + for (x = 0xf; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x80); + addr -= 4; + } + addr = VORTEX_MIX_INVOL_B + 0x7fc; + for (x = 0x1ff; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x80); + addr -= 4; + } + addr = VORTEX_MIX_VOL_B + 0x3c; + for (x = 0xf; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x80); + addr -= 4; + } + addr = VORTEX_MIXER_RTBASE + (MIXER_RTBASE_SIZE - 1) * 4; + for (x = (MIXER_RTBASE_SIZE - 1); x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x0); + addr -= 4; + } + hwwrite(vortex->mmio, VORTEX_MIXER_SR, 0); + + /* Set clipping ceiling (this may be all wrong). */ + /* + for (x = 0; x < 0x80; x++) { + hwwrite(vortex->mmio, VORTEX_MIXER_CLIP + (x << 2), 0x3ffff); + } + */ + /* + call CAsp4Mix__Initialize_CAsp4HwIO____CAsp4Mixer____ + Register ISR callback for volume smooth fade out. + Maybe this avoids clicks when press "stop" ? + */ +} + +/* SRC (CAsp4Src.s and CAsp4SrcBlock) */ + +static void vortex_src_en_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_SRCBLOCK_SR, + hwread(vortex->mmio, VORTEX_SRCBLOCK_SR) | (0x1 << channel)); +} + +static void vortex_src_dis_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_SRCBLOCK_SR, + hwread(vortex->mmio, VORTEX_SRCBLOCK_SR) & ~(0x1 << channel)); +} + +static void vortex_src_flushbuffers(vortex_t * vortex, unsigned char src) +{ + int i; + + for (i = 0x1f; i >= 0; i--) + hwwrite(vortex->mmio, + VORTEX_SRC_DATA0 + (src << 7) + (i << 2), 0); + hwwrite(vortex->mmio, VORTEX_SRC_DATA + (src << 3), 0); + hwwrite(vortex->mmio, VORTEX_SRC_DATA + (src << 3) + 4, 0); +} + +static void vortex_src_cleardrift(vortex_t * vortex, unsigned char src) +{ + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT0 + (src << 2), 0); + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT1 + (src << 2), 0); + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT2 + (src << 2), 1); +} + +static void +vortex_src_set_throttlesource(vortex_t * vortex, unsigned char src, int en) +{ + int temp; + + temp = hwread(vortex->mmio, VORTEX_SRC_SOURCE); + if (en) + temp |= 1 << src; + else + temp &= ~(1 << src); + hwwrite(vortex->mmio, VORTEX_SRC_SOURCE, temp); +} + +static int +vortex_src_persist_convratio(vortex_t * vortex, unsigned char src, int ratio) +{ + int temp, lifeboat = 0; + + do { + hwwrite(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2), ratio); + temp = hwread(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2)); + if ((++lifeboat) > 0x9) { + dev_err(vortex->card->dev, "Src cvr fail\n"); + break; + } + } + while (temp != ratio); + return temp; +} + +#if 0 +static void vortex_src_slowlock(vortex_t * vortex, unsigned char src) +{ + int temp; + + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT2 + (src << 2), 1); + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT0 + (src << 2), 0); + temp = hwread(vortex->mmio, VORTEX_SRC_U0 + (src << 2)); + if (temp & 0x200) + hwwrite(vortex->mmio, VORTEX_SRC_U0 + (src << 2), + temp & ~0x200L); +} + +static void +vortex_src_change_convratio(vortex_t * vortex, unsigned char src, int ratio) +{ + int temp, a; + + if ((ratio & 0x10000) && (ratio != 0x10000)) { + if (ratio & 0x3fff) + a = (0x11 - ((ratio >> 0xe) & 0x3)) - 1; + else + a = (0x11 - ((ratio >> 0xe) & 0x3)) - 2; + } else + a = 0xc; + temp = hwread(vortex->mmio, VORTEX_SRC_U0 + (src << 2)); + if (((temp >> 4) & 0xf) != a) + hwwrite(vortex->mmio, VORTEX_SRC_U0 + (src << 2), + (temp & 0xf) | ((a & 0xf) << 4)); + + vortex_src_persist_convratio(vortex, src, ratio); +} + +static int +vortex_src_checkratio(vortex_t * vortex, unsigned char src, + unsigned int desired_ratio) +{ + int hw_ratio, lifeboat = 0; + + hw_ratio = hwread(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2)); + + while (hw_ratio != desired_ratio) { + hwwrite(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2), desired_ratio); + + if ((lifeboat++) > 15) { + pr_err( "Vortex: could not set src-%d from %d to %d\n", + src, hw_ratio, desired_ratio); + break; + } + } + + return hw_ratio; +} + +#endif +/* + Objective: Set samplerate for given SRC module. + Arguments: + card: pointer to vortex_t strcut. + src: Integer index of the SRC module. + cr: Current sample rate conversion factor. + b: unknown 16 bit value. + sweep: Enable Samplerate fade from cr toward tr flag. + dirplay: 1: playback, 0: recording. + sl: Slow Lock flag. + tr: Target samplerate conversion. + thsource: Throttle source flag (no idea what that means). +*/ +static void vortex_src_setupchannel(vortex_t * card, unsigned char src, + unsigned int cr, unsigned int b, int sweep, int d, + int dirplay, int sl, unsigned int tr, int thsource) +{ + // noplayback: d=2,4,7,0xa,0xb when using first 2 src's. + // c: enables pitch sweep. + // looks like g is c related. Maybe g is a sweep parameter ? + // g = cvr + // dirplay: 0 = recording, 1 = playback + // d = src hw index. + + int esi, ebp = 0, esp10; + + vortex_src_flushbuffers(card, src); + + if (sweep) { + if ((tr & 0x10000) && (tr != 0x10000)) { + tr = 0; + esi = 0x7; + } else { + if ((((short)tr) < 0) && (tr != 0x8000)) { + tr = 0; + esi = 0x8; + } else { + tr = 1; + esi = 0xc; + } + } + } else { + if ((cr & 0x10000) && (cr != 0x10000)) { + tr = 0; /*ebx = 0 */ + esi = 0x11 - ((cr >> 0xe) & 7); + if (cr & 0x3fff) + esi -= 1; + else + esi -= 2; + } else { + tr = 1; + esi = 0xc; + } + } + vortex_src_cleardrift(card, src); + vortex_src_set_throttlesource(card, src, thsource); + + if ((dirplay == 0) && (sweep == 0)) { + if (tr) + esp10 = 0xf; + else + esp10 = 0xc; + ebp = 0; + } else { + if (tr) + ebp = 0xf; + else + ebp = 0xc; + esp10 = 0; + } + hwwrite(card->mmio, VORTEX_SRC_U0 + (src << 2), + (sl << 0x9) | (sweep << 0x8) | ((esi & 0xf) << 4) | d); + /* 0xc0 esi=0xc c=f=0 d=0 */ + vortex_src_persist_convratio(card, src, cr); + hwwrite(card->mmio, VORTEX_SRC_U1 + (src << 2), b & 0xffff); + /* 0 b=0 */ + hwwrite(card->mmio, VORTEX_SRC_U2 + (src << 2), + (tr << 0x11) | (dirplay << 0x10) | (ebp << 0x8) | esp10); + /* 0x30f00 e=g=1 esp10=0 ebp=f */ + //printk(KERN_INFO "vortex: SRC %d, d=0x%x, esi=0x%x, esp10=0x%x, ebp=0x%x\n", src, d, esi, esp10, ebp); +} + +static void vortex_srcblock_init(vortex_t * vortex) +{ + u32 addr; + int x; + hwwrite(vortex->mmio, VORTEX_SRC_SOURCESIZE, 0x1ff); + /* + for (x=0; x<0x10; x++) { + vortex_src_init(&vortex_src[x], x); + } + */ + //addr = 0xcc3c; + //addr = 0x26c3c; + addr = VORTEX_SRC_RTBASE + 0x3c; + for (x = 0xf; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } + //addr = 0xcc94; + //addr = 0x26c94; + addr = VORTEX_SRC_CHNBASE + 0x54; + for (x = 0x15; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } +} + +static int +vortex_src_addWTD(vortex_t * vortex, unsigned char src, unsigned char ch) +{ + int temp, lifeboat = 0, prev; + // esp13 = src + + temp = hwread(vortex->mmio, VORTEX_SRCBLOCK_SR); + if ((temp & (1 << ch)) == 0) { + hwwrite(vortex->mmio, VORTEX_SRC_CHNBASE + (ch << 2), src); + vortex_src_en_sr(vortex, ch); + return 1; + } + prev = VORTEX_SRC_CHNBASE + (ch << 2); /*ebp */ + temp = hwread(vortex->mmio, prev); + //while (temp & NR_SRC) { + while (temp & 0x10) { + prev = VORTEX_SRC_RTBASE + ((temp & 0xf) << 2); /*esp12 */ + //prev = VORTEX_SRC_RTBASE + ((temp & (NR_SRC-1)) << 2); /*esp12*/ + temp = hwread(vortex->mmio, prev); + //printk(KERN_INFO "vortex: srcAddWTD: while addr=%x, val=%x\n", prev, temp); + if ((++lifeboat) > 0xf) { + dev_err(vortex->card->dev, + "vortex_src_addWTD: lifeboat overflow\n"); + return 0; + } + } + hwwrite(vortex->mmio, VORTEX_SRC_RTBASE + ((temp & 0xf) << 2), src); + //hwwrite(vortex->mmio, prev, (temp & (NR_SRC-1)) | NR_SRC); + hwwrite(vortex->mmio, prev, (temp & 0xf) | 0x10); + return 1; +} + +static int +vortex_src_delWTD(vortex_t * vortex, unsigned char src, unsigned char ch) +{ + int esp14 = -1, esp18, eax, ebx, edx, ebp, esi = 0; + //int esp1f=edi(while)=src, esp10=ch; + + eax = hwread(vortex->mmio, VORTEX_SRCBLOCK_SR); + if (((1 << ch) & eax) == 0) { + dev_err(vortex->card->dev, "src alarm\n"); + return 0; + } + ebp = VORTEX_SRC_CHNBASE + (ch << 2); + esp18 = hwread(vortex->mmio, ebp); + if (esp18 & 0x10) { + ebx = (esp18 & 0xf); + if (src == ebx) { + ebx = VORTEX_SRC_RTBASE + (src << 2); + edx = hwread(vortex->mmio, ebx); + //7b60 + hwwrite(vortex->mmio, ebp, edx); + hwwrite(vortex->mmio, ebx, 0); + } else { + //7ad3 + edx = + hwread(vortex->mmio, + VORTEX_SRC_RTBASE + (ebx << 2)); + //printk(KERN_INFO "vortex: srcdelWTD: 1 addr=%x, val=%x, src=%x\n", ebx, edx, src); + while ((edx & 0xf) != src) { + if ((esi) > 0xf) { + dev_warn(vortex->card->dev, + "srcdelWTD: error, lifeboat overflow\n"); + return 0; + } + esp14 = ebx; + ebx = edx & 0xf; + ebp = ebx << 2; + edx = + hwread(vortex->mmio, + VORTEX_SRC_RTBASE + ebp); + //printk(KERN_INFO "vortex: srcdelWTD: while addr=%x, val=%x\n", ebp, edx); + esi++; + } + //7b30 + ebp = ebx << 2; + if (edx & 0x10) { /* Delete entry in between others */ + ebx = VORTEX_SRC_RTBASE + ((edx & 0xf) << 2); + edx = hwread(vortex->mmio, ebx); + //7b60 + hwwrite(vortex->mmio, + VORTEX_SRC_RTBASE + ebp, edx); + hwwrite(vortex->mmio, ebx, 0); + //printk(KERN_INFO "vortex srcdelWTD between addr= 0x%x, val= 0x%x\n", ebp, edx); + } else { /* Delete last entry */ + //7b83 + if (esp14 == -1) + hwwrite(vortex->mmio, + VORTEX_SRC_CHNBASE + + (ch << 2), esp18 & 0xef); + else { + ebx = (0xffffffe0 & edx) | (0xf & ebx); + hwwrite(vortex->mmio, + VORTEX_SRC_RTBASE + + (esp14 << 2), ebx); + //printk(KERN_INFO"vortex srcdelWTD last addr= 0x%x, val= 0x%x\n", esp14, ebx); + } + hwwrite(vortex->mmio, + VORTEX_SRC_RTBASE + ebp, 0); + return 1; + } + } + } else { + //7be0 + vortex_src_dis_sr(vortex, ch); + hwwrite(vortex->mmio, ebp, 0); + } + return 1; +} + + /*FIFO*/ + +static void +vortex_fifo_clearadbdata(vortex_t * vortex, int fifo, int x) +{ + for (x--; x >= 0; x--) + hwwrite(vortex->mmio, + VORTEX_FIFO_ADBDATA + + (((fifo << FIFO_SIZE_BITS) + x) << 2), 0); +} + +#if 0 +static void vortex_fifo_adbinitialize(vortex_t * vortex, int fifo, int j) +{ + vortex_fifo_clearadbdata(vortex, fifo, FIFO_SIZE); +#ifdef CHIP_AU8820 + hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2), + (FIFO_U1 | ((j & FIFO_MASK) << 0xb))); +#else + hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2), + (FIFO_U1 | ((j & FIFO_MASK) << 0xc))); +#endif +} +#endif +static void vortex_fifo_setadbvalid(vortex_t * vortex, int fifo, int en) +{ + hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2), + (hwread(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2)) & + 0xffffffef) | ((1 & en) << 4) | FIFO_U1); +} + +static void +vortex_fifo_setadbctrl(vortex_t * vortex, int fifo, int stereo, int priority, + int empty, int valid, int f) +{ + int temp, lifeboat = 0; + //int this_8[NR_ADB] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; /* position */ + int this_4 = 0x2; + /* f seems priority related. + * CAsp4AdbDma::SetPriority is the only place that calls SetAdbCtrl with f set to 1 + * every where else it is set to 0. It seems, however, that CAsp4AdbDma::SetPriority + * is never called, thus the f related bits remain a mystery for now. + */ + do { + temp = hwread(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2)); + if (lifeboat++ > 0xbb8) { + dev_err(vortex->card->dev, + "vortex_fifo_setadbctrl fail\n"); + break; + } + } + while (temp & FIFO_RDONLY); + + // AU8830 semes to take some special care about fifo content (data). + // But i'm just to lazy to translate that :) + if (valid) { + if ((temp & FIFO_VALID) == 0) { + //this_8[fifo] = 0; + vortex_fifo_clearadbdata(vortex, fifo, FIFO_SIZE); // this_4 +#ifdef CHIP_AU8820 + temp = (this_4 & 0x1f) << 0xb; +#else + temp = (this_4 & 0x3f) << 0xc; +#endif + temp = (temp & 0xfffffffd) | ((stereo & 1) << 1); + temp = (temp & 0xfffffff3) | ((priority & 3) << 2); + temp = (temp & 0xffffffef) | ((valid & 1) << 4); + temp |= FIFO_U1; + temp = (temp & 0xffffffdf) | ((empty & 1) << 5); +#ifdef CHIP_AU8820 + temp = (temp & 0xfffbffff) | ((f & 1) << 0x12); +#endif +#ifdef CHIP_AU8830 + temp = (temp & 0xf7ffffff) | ((f & 1) << 0x1b); + temp = (temp & 0xefffffff) | ((f & 1) << 0x1c); +#endif +#ifdef CHIP_AU8810 + temp = (temp & 0xfeffffff) | ((f & 1) << 0x18); + temp = (temp & 0xfdffffff) | ((f & 1) << 0x19); +#endif + } + } else { + if (temp & FIFO_VALID) { +#ifdef CHIP_AU8820 + temp = ((f & 1) << 0x12) | (temp & 0xfffbffef); +#endif +#ifdef CHIP_AU8830 + temp = + ((f & 1) << 0x1b) | (temp & 0xe7ffffef) | FIFO_BITS; +#endif +#ifdef CHIP_AU8810 + temp = + ((f & 1) << 0x18) | (temp & 0xfcffffef) | FIFO_BITS; +#endif + } else + /*if (this_8[fifo]) */ + vortex_fifo_clearadbdata(vortex, fifo, FIFO_SIZE); + } + hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2), temp); + hwread(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2)); +} + +#ifndef CHIP_AU8810 +static void vortex_fifo_clearwtdata(vortex_t * vortex, int fifo, int x) +{ + if (x < 1) + return; + for (x--; x >= 0; x--) + hwwrite(vortex->mmio, + VORTEX_FIFO_WTDATA + + (((fifo << FIFO_SIZE_BITS) + x) << 2), 0); +} + +static void vortex_fifo_wtinitialize(vortex_t * vortex, int fifo, int j) +{ + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); +#ifdef CHIP_AU8820 + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), + (FIFO_U1 | ((j & FIFO_MASK) << 0xb))); +#else + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), + (FIFO_U1 | ((j & FIFO_MASK) << 0xc))); +#endif +} + +static void vortex_fifo_setwtvalid(vortex_t * vortex, int fifo, int en) +{ + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), + (hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)) & + 0xffffffef) | ((en & 1) << 4) | FIFO_U1); +} + +static void +vortex_fifo_setwtctrl(vortex_t * vortex, int fifo, int ctrl, int priority, + int empty, int valid, int f) +{ + int temp = 0, lifeboat = 0; + int this_4 = 2; + + do { + temp = hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)); + if (lifeboat++ > 0xbb8) { + dev_err(vortex->card->dev, + "vortex_fifo_setwtctrl fail\n"); + break; + } + } + while (temp & FIFO_RDONLY); + + if (valid) { + if ((temp & FIFO_VALID) == 0) { + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); // this_4 +#ifdef CHIP_AU8820 + temp = (this_4 & 0x1f) << 0xb; +#else + temp = (this_4 & 0x3f) << 0xc; +#endif + temp = (temp & 0xfffffffd) | ((ctrl & 1) << 1); + temp = (temp & 0xfffffff3) | ((priority & 3) << 2); + temp = (temp & 0xffffffef) | ((valid & 1) << 4); + temp |= FIFO_U1; + temp = (temp & 0xffffffdf) | ((empty & 1) << 5); +#ifdef CHIP_AU8820 + temp = (temp & 0xfffbffff) | ((f & 1) << 0x12); +#endif +#ifdef CHIP_AU8830 + temp = (temp & 0xf7ffffff) | ((f & 1) << 0x1b); + temp = (temp & 0xefffffff) | ((f & 1) << 0x1c); +#endif +#ifdef CHIP_AU8810 + temp = (temp & 0xfeffffff) | ((f & 1) << 0x18); + temp = (temp & 0xfdffffff) | ((f & 1) << 0x19); +#endif + } + } else { + if (temp & FIFO_VALID) { +#ifdef CHIP_AU8820 + temp = ((f & 1) << 0x12) | (temp & 0xfffbffef); +#endif +#ifdef CHIP_AU8830 + temp = + ((f & 1) << 0x1b) | (temp & 0xe7ffffef) | FIFO_BITS; +#endif +#ifdef CHIP_AU8810 + temp = + ((f & 1) << 0x18) | (temp & 0xfcffffef) | FIFO_BITS; +#endif + } else + /*if (this_8[fifo]) */ + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); + } + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)); + +/* + do { + temp = hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)); + if (lifeboat++ > 0xbb8) { + pr_err( "Vortex: vortex_fifo_setwtctrl fail (hanging)\n"); + break; + } + } while ((temp & FIFO_RDONLY)&&(temp & FIFO_VALID)&&(temp != 0xFFFFFFFF)); + + + if (valid) { + if (temp & FIFO_VALID) { + temp = 0x40000; + //temp |= 0x08000000; + //temp |= 0x10000000; + //temp |= 0x04000000; + //temp |= 0x00400000; + temp |= 0x1c400000; + temp &= 0xFFFFFFF3; + temp &= 0xFFFFFFEF; + temp |= (valid & 1) << 4; + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + return; + } else { + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); + return; + } + } else { + temp &= 0xffffffef; + temp |= 0x08000000; + temp |= 0x10000000; + temp |= 0x04000000; + temp |= 0x00400000; + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + temp = hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)); + //((temp >> 6) & 0x3f) + + priority = 0; + if (((temp & 0x0fc0) ^ ((temp >> 6) & 0x0fc0)) & 0FFFFFFC0) + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); + valid = 0xfb; + temp = (temp & 0xfffffffd) | ((ctrl & 1) << 1); + temp = (temp & 0xfffdffff) | ((f & 1) << 0x11); + temp = (temp & 0xfffffff3) | ((priority & 3) << 2); + temp = (temp & 0xffffffef) | ((valid & 1) << 4); + temp = (temp & 0xffffffdf) | ((empty & 1) << 5); + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + } + + */ + + /* + temp = (temp & 0xfffffffd) | ((ctrl & 1) << 1); + temp = (temp & 0xfffdffff) | ((f & 1) << 0x11); + temp = (temp & 0xfffffff3) | ((priority & 3) << 2); + temp = (temp & 0xffffffef) | ((valid & 1) << 4); + temp = (temp & 0xffffffdf) | ((empty & 1) << 5); + #ifdef FIFO_BITS + temp = temp | FIFO_BITS | 40000; + #endif + // 0x1c440010, 0x1c400000 + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + */ +} + +#endif +static void vortex_fifo_init(vortex_t * vortex) +{ + int x; + u32 addr; + + /* ADB DMA channels fifos. */ + addr = VORTEX_FIFO_ADBCTRL + ((NR_ADB - 1) * 4); + for (x = NR_ADB - 1; x >= 0; x--) { + hwwrite(vortex->mmio, addr, (FIFO_U0 | FIFO_U1)); + if (hwread(vortex->mmio, addr) != (FIFO_U0 | FIFO_U1)) + dev_err(vortex->card->dev, "bad adb fifo reset!\n"); + vortex_fifo_clearadbdata(vortex, x, FIFO_SIZE); + addr -= 4; + } + +#ifndef CHIP_AU8810 + /* WT DMA channels fifos. */ + addr = VORTEX_FIFO_WTCTRL + ((NR_WT - 1) * 4); + for (x = NR_WT - 1; x >= 0; x--) { + hwwrite(vortex->mmio, addr, FIFO_U0); + if (hwread(vortex->mmio, addr) != FIFO_U0) + dev_err(vortex->card->dev, + "bad wt fifo reset (0x%08x, 0x%08x)!\n", + addr, hwread(vortex->mmio, addr)); + vortex_fifo_clearwtdata(vortex, x, FIFO_SIZE); + addr -= 4; + } +#endif + /* trigger... */ +#ifdef CHIP_AU8820 + hwwrite(vortex->mmio, 0xf8c0, 0xd03); //0x0843 0xd6b +#else +#ifdef CHIP_AU8830 + hwwrite(vortex->mmio, 0x17000, 0x61); /* wt a */ + hwwrite(vortex->mmio, 0x17004, 0x61); /* wt b */ +#endif + hwwrite(vortex->mmio, 0x17008, 0x61); /* adb */ +#endif +} + +/* ADBDMA */ + +static void vortex_adbdma_init(vortex_t * vortex) +{ +} + +static void vortex_adbdma_setfirstbuffer(vortex_t * vortex, int adbdma) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + + hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2), + dma->dma_ctrl); +} + +static void vortex_adbdma_setstartbuffer(vortex_t * vortex, int adbdma, int sb) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + //hwwrite(vortex->mmio, VORTEX_ADBDMA_START + (adbdma << 2), sb << (((NR_ADB-1)-((adbdma&0xf)*2)))); + hwwrite(vortex->mmio, VORTEX_ADBDMA_START + (adbdma << 2), + sb << ((0xf - (adbdma & 0xf)) * 2)); + dma->period_real = dma->period_virt = sb; +} + +static void +vortex_adbdma_setbuffers(vortex_t * vortex, int adbdma, + int psize, int count) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + + dma->period_bytes = psize; + dma->nr_periods = count; + + dma->cfg0 = 0; + dma->cfg1 = 0; + switch (count) { + /* Four or more pages */ + default: + case 4: + dma->cfg1 |= 0x88000000 | 0x44000000 | 0x30000000 | (psize - 1); + hwwrite(vortex->mmio, + VORTEX_ADBDMA_BUFBASE + (adbdma << 4) + 0xc, + snd_pcm_sgbuf_get_addr(dma->substream, psize * 3)); + fallthrough; + /* 3 pages */ + case 3: + dma->cfg0 |= 0x12000000; + dma->cfg1 |= 0x80000000 | 0x40000000 | ((psize - 1) << 0xc); + hwwrite(vortex->mmio, + VORTEX_ADBDMA_BUFBASE + (adbdma << 4) + 0x8, + snd_pcm_sgbuf_get_addr(dma->substream, psize * 2)); + fallthrough; + /* 2 pages */ + case 2: + dma->cfg0 |= 0x88000000 | 0x44000000 | 0x10000000 | (psize - 1); + hwwrite(vortex->mmio, + VORTEX_ADBDMA_BUFBASE + (adbdma << 4) + 0x4, + snd_pcm_sgbuf_get_addr(dma->substream, psize)); + fallthrough; + /* 1 page */ + case 1: + dma->cfg0 |= 0x80000000 | 0x40000000 | ((psize - 1) << 0xc); + hwwrite(vortex->mmio, + VORTEX_ADBDMA_BUFBASE + (adbdma << 4), + snd_pcm_sgbuf_get_addr(dma->substream, 0)); + break; + } + /* + pr_debug( "vortex: cfg0 = 0x%x\nvortex: cfg1=0x%x\n", + dma->cfg0, dma->cfg1); + */ + hwwrite(vortex->mmio, VORTEX_ADBDMA_BUFCFG0 + (adbdma << 3), dma->cfg0); + hwwrite(vortex->mmio, VORTEX_ADBDMA_BUFCFG1 + (adbdma << 3), dma->cfg1); + + vortex_adbdma_setfirstbuffer(vortex, adbdma); + vortex_adbdma_setstartbuffer(vortex, adbdma, 0); +} + +static void +vortex_adbdma_setmode(vortex_t * vortex, int adbdma, int ie, int dir, + int fmt, int stereo, u32 offset) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + + dma->dma_unknown = stereo; + dma->dma_ctrl = + ((offset & OFFSET_MASK) | (dma->dma_ctrl & ~OFFSET_MASK)); + /* Enable PCMOUT interrupts. */ + dma->dma_ctrl = + (dma->dma_ctrl & ~IE_MASK) | ((ie << IE_SHIFT) & IE_MASK); + + dma->dma_ctrl = + (dma->dma_ctrl & ~DIR_MASK) | ((dir << DIR_SHIFT) & DIR_MASK); + dma->dma_ctrl = + (dma->dma_ctrl & ~FMT_MASK) | ((fmt << FMT_SHIFT) & FMT_MASK); + + hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2), + dma->dma_ctrl); + hwread(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2)); +} + +static int vortex_adbdma_bufshift(vortex_t * vortex, int adbdma) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + int page, p, pp, delta, i; + + page = + (hwread(vortex->mmio, VORTEX_ADBDMA_STAT + (adbdma << 2)) & + ADB_SUBBUF_MASK) >> ADB_SUBBUF_SHIFT; + if (dma->nr_periods >= 4) + delta = (page - dma->period_real) & 3; + else { + delta = (page - dma->period_real); + if (delta < 0) + delta += dma->nr_periods; + } + if (delta == 0) + return 0; + + /* refresh hw page table */ + if (dma->nr_periods > 4) { + for (i = 0; i < delta; i++) { + /* p: audio buffer page index */ + p = dma->period_virt + i + 4; + if (p >= dma->nr_periods) + p -= dma->nr_periods; + /* pp: hardware DMA page index. */ + pp = dma->period_real + i; + if (pp >= 4) + pp -= 4; + //hwwrite(vortex->mmio, VORTEX_ADBDMA_BUFBASE+(((adbdma << 2)+pp) << 2), dma->table[p].addr); + hwwrite(vortex->mmio, + VORTEX_ADBDMA_BUFBASE + (((adbdma << 2) + pp) << 2), + snd_pcm_sgbuf_get_addr(dma->substream, + dma->period_bytes * p)); + /* Force write through cache. */ + hwread(vortex->mmio, VORTEX_ADBDMA_BUFBASE + + (((adbdma << 2) + pp) << 2)); + } + } + dma->period_virt += delta; + dma->period_real = page; + if (dma->period_virt >= dma->nr_periods) + dma->period_virt -= dma->nr_periods; + if (delta != 1) + dev_info(vortex->card->dev, + "%d virt=%d, real=%d, delta=%d\n", + adbdma, dma->period_virt, dma->period_real, delta); + + return delta; +} + + +static void vortex_adbdma_resetup(vortex_t *vortex, int adbdma) { + stream_t *dma = &vortex->dma_adb[adbdma]; + int p, pp, i; + + /* refresh hw page table */ + for (i=0 ; i < 4 && i < dma->nr_periods; i++) { + /* p: audio buffer page index */ + p = dma->period_virt + i; + if (p >= dma->nr_periods) + p -= dma->nr_periods; + /* pp: hardware DMA page index. */ + pp = dma->period_real + i; + if (dma->nr_periods < 4) { + if (pp >= dma->nr_periods) + pp -= dma->nr_periods; + } + else { + if (pp >= 4) + pp -= 4; + } + hwwrite(vortex->mmio, + VORTEX_ADBDMA_BUFBASE + (((adbdma << 2) + pp) << 2), + snd_pcm_sgbuf_get_addr(dma->substream, + dma->period_bytes * p)); + /* Force write through cache. */ + hwread(vortex->mmio, VORTEX_ADBDMA_BUFBASE + (((adbdma << 2)+pp) << 2)); + } +} + +static inline int vortex_adbdma_getlinearpos(vortex_t * vortex, int adbdma) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + int temp, page, delta; + + temp = hwread(vortex->mmio, VORTEX_ADBDMA_STAT + (adbdma << 2)); + page = (temp & ADB_SUBBUF_MASK) >> ADB_SUBBUF_SHIFT; + if (dma->nr_periods >= 4) + delta = (page - dma->period_real) & 3; + else { + delta = (page - dma->period_real); + if (delta < 0) + delta += dma->nr_periods; + } + return (dma->period_virt + delta) * dma->period_bytes + + (temp & (dma->period_bytes - 1)); +} + +static void vortex_adbdma_startfifo(vortex_t * vortex, int adbdma) +{ + int this_8 = 0 /*empty */ , this_4 = 0 /*priority */ ; + stream_t *dma = &vortex->dma_adb[adbdma]; + + switch (dma->fifo_status) { + case FIFO_START: + vortex_fifo_setadbvalid(vortex, adbdma, + dma->fifo_enabled ? 1 : 0); + break; + case FIFO_STOP: + this_8 = 1; + hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2), + dma->dma_ctrl); + vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown, + this_4, this_8, + dma->fifo_enabled ? 1 : 0, 0); + break; + case FIFO_PAUSE: + vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown, + this_4, this_8, + dma->fifo_enabled ? 1 : 0, 0); + break; + } + dma->fifo_status = FIFO_START; +} + +static void vortex_adbdma_resumefifo(vortex_t * vortex, int adbdma) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + + int this_8 = 1, this_4 = 0; + switch (dma->fifo_status) { + case FIFO_STOP: + hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2), + dma->dma_ctrl); + vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown, + this_4, this_8, + dma->fifo_enabled ? 1 : 0, 0); + break; + case FIFO_PAUSE: + vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown, + this_4, this_8, + dma->fifo_enabled ? 1 : 0, 0); + break; + } + dma->fifo_status = FIFO_START; +} + +static void vortex_adbdma_pausefifo(vortex_t * vortex, int adbdma) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + + int this_8 = 0, this_4 = 0; + switch (dma->fifo_status) { + case FIFO_START: + vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown, + this_4, this_8, 0, 0); + break; + case FIFO_STOP: + hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2), + dma->dma_ctrl); + vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown, + this_4, this_8, 0, 0); + break; + } + dma->fifo_status = FIFO_PAUSE; +} + +static void vortex_adbdma_stopfifo(vortex_t * vortex, int adbdma) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + + int this_4 = 0, this_8 = 0; + if (dma->fifo_status == FIFO_START) + vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown, + this_4, this_8, 0, 0); + else if (dma->fifo_status == FIFO_STOP) + return; + dma->fifo_status = FIFO_STOP; + dma->fifo_enabled = 0; +} + +/* WTDMA */ + +#ifndef CHIP_AU8810 +static void vortex_wtdma_setfirstbuffer(vortex_t * vortex, int wtdma) +{ + //int this_7c=dma_ctrl; + stream_t *dma = &vortex->dma_wt[wtdma]; + + hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2), dma->dma_ctrl); +} + +static void vortex_wtdma_setstartbuffer(vortex_t * vortex, int wtdma, int sb) +{ + stream_t *dma = &vortex->dma_wt[wtdma]; + //hwwrite(vortex->mmio, VORTEX_WTDMA_START + (wtdma << 2), sb << ((0x1f-(wtdma&0xf)*2))); + hwwrite(vortex->mmio, VORTEX_WTDMA_START + (wtdma << 2), + sb << ((0xf - (wtdma & 0xf)) * 2)); + dma->period_real = dma->period_virt = sb; +} + +static void +vortex_wtdma_setbuffers(vortex_t * vortex, int wtdma, + int psize, int count) +{ + stream_t *dma = &vortex->dma_wt[wtdma]; + + dma->period_bytes = psize; + dma->nr_periods = count; + + dma->cfg0 = 0; + dma->cfg1 = 0; + switch (count) { + /* Four or more pages */ + default: + case 4: + dma->cfg1 |= 0x88000000 | 0x44000000 | 0x30000000 | (psize-1); + hwwrite(vortex->mmio, VORTEX_WTDMA_BUFBASE + (wtdma << 4) + 0xc, + snd_pcm_sgbuf_get_addr(dma->substream, psize * 3)); + fallthrough; + /* 3 pages */ + case 3: + dma->cfg0 |= 0x12000000; + dma->cfg1 |= 0x80000000 | 0x40000000 | ((psize-1) << 0xc); + hwwrite(vortex->mmio, VORTEX_WTDMA_BUFBASE + (wtdma << 4) + 0x8, + snd_pcm_sgbuf_get_addr(dma->substream, psize * 2)); + fallthrough; + /* 2 pages */ + case 2: + dma->cfg0 |= 0x88000000 | 0x44000000 | 0x10000000 | (psize-1); + hwwrite(vortex->mmio, VORTEX_WTDMA_BUFBASE + (wtdma << 4) + 0x4, + snd_pcm_sgbuf_get_addr(dma->substream, psize)); + fallthrough; + /* 1 page */ + case 1: + dma->cfg0 |= 0x80000000 | 0x40000000 | ((psize-1) << 0xc); + hwwrite(vortex->mmio, VORTEX_WTDMA_BUFBASE + (wtdma << 4), + snd_pcm_sgbuf_get_addr(dma->substream, 0)); + break; + } + hwwrite(vortex->mmio, VORTEX_WTDMA_BUFCFG0 + (wtdma << 3), dma->cfg0); + hwwrite(vortex->mmio, VORTEX_WTDMA_BUFCFG1 + (wtdma << 3), dma->cfg1); + + vortex_wtdma_setfirstbuffer(vortex, wtdma); + vortex_wtdma_setstartbuffer(vortex, wtdma, 0); +} + +static void +vortex_wtdma_setmode(vortex_t * vortex, int wtdma, int ie, int fmt, int d, + /*int e, */ u32 offset) +{ + stream_t *dma = &vortex->dma_wt[wtdma]; + + //dma->this_08 = e; + dma->dma_unknown = d; + dma->dma_ctrl = 0; + dma->dma_ctrl = + ((offset & OFFSET_MASK) | (dma->dma_ctrl & ~OFFSET_MASK)); + /* PCMOUT interrupt */ + dma->dma_ctrl = + (dma->dma_ctrl & ~IE_MASK) | ((ie << IE_SHIFT) & IE_MASK); + /* Always playback. */ + dma->dma_ctrl |= (1 << DIR_SHIFT); + /* Audio Format */ + dma->dma_ctrl = + (dma->dma_ctrl & FMT_MASK) | ((fmt << FMT_SHIFT) & FMT_MASK); + /* Write into hardware */ + hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2), dma->dma_ctrl); +} + +static int vortex_wtdma_bufshift(vortex_t * vortex, int wtdma) +{ + stream_t *dma = &vortex->dma_wt[wtdma]; + int page, p, pp, delta, i; + + page = + (hwread(vortex->mmio, VORTEX_WTDMA_STAT + (wtdma << 2)) + >> WT_SUBBUF_SHIFT) & WT_SUBBUF_MASK; + if (dma->nr_periods >= 4) + delta = (page - dma->period_real) & 3; + else { + delta = (page - dma->period_real); + if (delta < 0) + delta += dma->nr_periods; + } + if (delta == 0) + return 0; + + /* refresh hw page table */ + if (dma->nr_periods > 4) { + for (i = 0; i < delta; i++) { + /* p: audio buffer page index */ + p = dma->period_virt + i + 4; + if (p >= dma->nr_periods) + p -= dma->nr_periods; + /* pp: hardware DMA page index. */ + pp = dma->period_real + i; + if (pp >= 4) + pp -= 4; + hwwrite(vortex->mmio, + VORTEX_WTDMA_BUFBASE + + (((wtdma << 2) + pp) << 2), + snd_pcm_sgbuf_get_addr(dma->substream, + dma->period_bytes * p)); + /* Force write through cache. */ + hwread(vortex->mmio, VORTEX_WTDMA_BUFBASE + + (((wtdma << 2) + pp) << 2)); + } + } + dma->period_virt += delta; + if (dma->period_virt >= dma->nr_periods) + dma->period_virt -= dma->nr_periods; + dma->period_real = page; + + if (delta != 1) + dev_warn(vortex->card->dev, "wt virt = %d, delta = %d\n", + dma->period_virt, delta); + + return delta; +} + +#if 0 +static void +vortex_wtdma_getposition(vortex_t * vortex, int wtdma, int *subbuf, int *pos) +{ + int temp; + temp = hwread(vortex->mmio, VORTEX_WTDMA_STAT + (wtdma << 2)); + *subbuf = (temp >> WT_SUBBUF_SHIFT) & WT_SUBBUF_MASK; + *pos = temp & POS_MASK; +} + +static int vortex_wtdma_getcursubuffer(vortex_t * vortex, int wtdma) +{ + return ((hwread(vortex->mmio, VORTEX_WTDMA_STAT + (wtdma << 2)) >> + POS_SHIFT) & POS_MASK); +} +#endif +static inline int vortex_wtdma_getlinearpos(vortex_t * vortex, int wtdma) +{ + stream_t *dma = &vortex->dma_wt[wtdma]; + int temp; + + temp = hwread(vortex->mmio, VORTEX_WTDMA_STAT + (wtdma << 2)); + temp = (dma->period_virt * dma->period_bytes) + (temp & (dma->period_bytes - 1)); + return temp; +} + +static void vortex_wtdma_startfifo(vortex_t * vortex, int wtdma) +{ + stream_t *dma = &vortex->dma_wt[wtdma]; + int this_8 = 0, this_4 = 0; + + switch (dma->fifo_status) { + case FIFO_START: + vortex_fifo_setwtvalid(vortex, wtdma, + dma->fifo_enabled ? 1 : 0); + break; + case FIFO_STOP: + this_8 = 1; + hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2), + dma->dma_ctrl); + vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown, + this_4, this_8, + dma->fifo_enabled ? 1 : 0, 0); + break; + case FIFO_PAUSE: + vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown, + this_4, this_8, + dma->fifo_enabled ? 1 : 0, 0); + break; + } + dma->fifo_status = FIFO_START; +} + +static void vortex_wtdma_resumefifo(vortex_t * vortex, int wtdma) +{ + stream_t *dma = &vortex->dma_wt[wtdma]; + + int this_8 = 0, this_4 = 0; + switch (dma->fifo_status) { + case FIFO_STOP: + hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2), + dma->dma_ctrl); + vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown, + this_4, this_8, + dma->fifo_enabled ? 1 : 0, 0); + break; + case FIFO_PAUSE: + vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown, + this_4, this_8, + dma->fifo_enabled ? 1 : 0, 0); + break; + } + dma->fifo_status = FIFO_START; +} + +static void vortex_wtdma_pausefifo(vortex_t * vortex, int wtdma) +{ + stream_t *dma = &vortex->dma_wt[wtdma]; + + int this_8 = 0, this_4 = 0; + switch (dma->fifo_status) { + case FIFO_START: + vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown, + this_4, this_8, 0, 0); + break; + case FIFO_STOP: + hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2), + dma->dma_ctrl); + vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown, + this_4, this_8, 0, 0); + break; + } + dma->fifo_status = FIFO_PAUSE; +} + +static void vortex_wtdma_stopfifo(vortex_t * vortex, int wtdma) +{ + stream_t *dma = &vortex->dma_wt[wtdma]; + + int this_4 = 0, this_8 = 0; + if (dma->fifo_status == FIFO_START) + vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown, + this_4, this_8, 0, 0); + else if (dma->fifo_status == FIFO_STOP) + return; + dma->fifo_status = FIFO_STOP; + dma->fifo_enabled = 0; +} + +#endif +/* ADB Routes */ + +typedef int ADBRamLink; +static void vortex_adb_init(vortex_t * vortex) +{ + int i; + /* it looks like we are writing more than we need to... + * if we write what we are supposed to it breaks things... */ + hwwrite(vortex->mmio, VORTEX_ADB_SR, 0); + for (i = 0; i < VORTEX_ADB_RTBASE_COUNT; i++) + hwwrite(vortex->mmio, VORTEX_ADB_RTBASE + (i << 2), + hwread(vortex->mmio, + VORTEX_ADB_RTBASE + (i << 2)) | ROUTE_MASK); + for (i = 0; i < VORTEX_ADB_CHNBASE_COUNT; i++) { + hwwrite(vortex->mmio, VORTEX_ADB_CHNBASE + (i << 2), + hwread(vortex->mmio, + VORTEX_ADB_CHNBASE + (i << 2)) | ROUTE_MASK); + } +} + +static void vortex_adb_en_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_ADB_SR, + hwread(vortex->mmio, VORTEX_ADB_SR) | (0x1 << channel)); +} + +static void vortex_adb_dis_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_ADB_SR, + hwread(vortex->mmio, VORTEX_ADB_SR) & ~(0x1 << channel)); +} + +static void +vortex_adb_addroutes(vortex_t * vortex, unsigned char channel, + ADBRamLink * route, int rnum) +{ + int temp, prev, lifeboat = 0; + + if ((rnum <= 0) || (route == NULL)) + return; + /* Write last routes. */ + rnum--; + hwwrite(vortex->mmio, + VORTEX_ADB_RTBASE + ((route[rnum] & ADB_MASK) << 2), + ROUTE_MASK); + while (rnum > 0) { + hwwrite(vortex->mmio, + VORTEX_ADB_RTBASE + + ((route[rnum - 1] & ADB_MASK) << 2), route[rnum]); + rnum--; + } + /* Write first route. */ + temp = + hwread(vortex->mmio, + VORTEX_ADB_CHNBASE + (channel << 2)) & ADB_MASK; + if (temp == ADB_MASK) { + /* First entry on this channel. */ + hwwrite(vortex->mmio, VORTEX_ADB_CHNBASE + (channel << 2), + route[0]); + vortex_adb_en_sr(vortex, channel); + return; + } + /* Not first entry on this channel. Need to link. */ + do { + prev = temp; + temp = + hwread(vortex->mmio, + VORTEX_ADB_RTBASE + (temp << 2)) & ADB_MASK; + if ((lifeboat++) > ADB_MASK) { + dev_err(vortex->card->dev, + "vortex_adb_addroutes: unending route! 0x%x\n", + *route); + return; + } + } + while (temp != ADB_MASK); + hwwrite(vortex->mmio, VORTEX_ADB_RTBASE + (prev << 2), route[0]); +} + +static void +vortex_adb_delroutes(vortex_t * vortex, unsigned char channel, + ADBRamLink route0, ADBRamLink route1) +{ + int temp, lifeboat = 0, prev; + + /* Find route. */ + temp = + hwread(vortex->mmio, + VORTEX_ADB_CHNBASE + (channel << 2)) & ADB_MASK; + if (temp == (route0 & ADB_MASK)) { + temp = + hwread(vortex->mmio, + VORTEX_ADB_RTBASE + ((route1 & ADB_MASK) << 2)); + if ((temp & ADB_MASK) == ADB_MASK) + vortex_adb_dis_sr(vortex, channel); + hwwrite(vortex->mmio, VORTEX_ADB_CHNBASE + (channel << 2), + temp); + return; + } + do { + prev = temp; + temp = + hwread(vortex->mmio, + VORTEX_ADB_RTBASE + (prev << 2)) & ADB_MASK; + if (((lifeboat++) > ADB_MASK) || (temp == ADB_MASK)) { + dev_err(vortex->card->dev, + "vortex_adb_delroutes: route not found! 0x%x\n", + route0); + return; + } + } + while (temp != (route0 & ADB_MASK)); + temp = hwread(vortex->mmio, VORTEX_ADB_RTBASE + (temp << 2)); + if ((temp & ADB_MASK) == route1) + temp = hwread(vortex->mmio, VORTEX_ADB_RTBASE + (temp << 2)); + /* Make bridge over deleted route. */ + hwwrite(vortex->mmio, VORTEX_ADB_RTBASE + (prev << 2), temp); +} + +static void +vortex_route(vortex_t * vortex, int en, unsigned char channel, + unsigned char source, unsigned char dest) +{ + ADBRamLink route; + + route = ((source & ADB_MASK) << ADB_SHIFT) | (dest & ADB_MASK); + if (en) { + vortex_adb_addroutes(vortex, channel, &route, 1); + if ((source < (OFFSET_SRCOUT + NR_SRC)) + && (source >= OFFSET_SRCOUT)) + vortex_src_addWTD(vortex, (source - OFFSET_SRCOUT), + channel); + else if ((source < (OFFSET_MIXOUT + NR_MIXOUT)) + && (source >= OFFSET_MIXOUT)) + vortex_mixer_addWTD(vortex, + (source - OFFSET_MIXOUT), channel); + } else { + vortex_adb_delroutes(vortex, channel, route, route); + if ((source < (OFFSET_SRCOUT + NR_SRC)) + && (source >= OFFSET_SRCOUT)) + vortex_src_delWTD(vortex, (source - OFFSET_SRCOUT), + channel); + else if ((source < (OFFSET_MIXOUT + NR_MIXOUT)) + && (source >= OFFSET_MIXOUT)) + vortex_mixer_delWTD(vortex, + (source - OFFSET_MIXOUT), channel); + } +} + +#if 0 +static void +vortex_routes(vortex_t * vortex, int en, unsigned char channel, + unsigned char source, unsigned char dest0, unsigned char dest1) +{ + ADBRamLink route[2]; + + route[0] = ((source & ADB_MASK) << ADB_SHIFT) | (dest0 & ADB_MASK); + route[1] = ((source & ADB_MASK) << ADB_SHIFT) | (dest1 & ADB_MASK); + + if (en) { + vortex_adb_addroutes(vortex, channel, route, 2); + if ((source < (OFFSET_SRCOUT + NR_SRC)) + && (source >= (OFFSET_SRCOUT))) + vortex_src_addWTD(vortex, (source - OFFSET_SRCOUT), + channel); + else if ((source < (OFFSET_MIXOUT + NR_MIXOUT)) + && (source >= (OFFSET_MIXOUT))) + vortex_mixer_addWTD(vortex, + (source - OFFSET_MIXOUT), channel); + } else { + vortex_adb_delroutes(vortex, channel, route[0], route[1]); + if ((source < (OFFSET_SRCOUT + NR_SRC)) + && (source >= (OFFSET_SRCOUT))) + vortex_src_delWTD(vortex, (source - OFFSET_SRCOUT), + channel); + else if ((source < (OFFSET_MIXOUT + NR_MIXOUT)) + && (source >= (OFFSET_MIXOUT))) + vortex_mixer_delWTD(vortex, + (source - OFFSET_MIXOUT), channel); + } +} + +#endif +/* Route two sources to same target. Sources must be of same class !!! */ +static void +vortex_routeLRT(vortex_t * vortex, int en, unsigned char ch, + unsigned char source0, unsigned char source1, + unsigned char dest) +{ + ADBRamLink route[2]; + + route[0] = ((source0 & ADB_MASK) << ADB_SHIFT) | (dest & ADB_MASK); + route[1] = ((source1 & ADB_MASK) << ADB_SHIFT) | (dest & ADB_MASK); + + if (dest < 0x10) + route[1] = (route[1] & ~ADB_MASK) | (dest + 0x20); /* fifo A */ + + if (en) { + vortex_adb_addroutes(vortex, ch, route, 2); + if ((source0 < (OFFSET_SRCOUT + NR_SRC)) + && (source0 >= OFFSET_SRCOUT)) { + vortex_src_addWTD(vortex, + (source0 - OFFSET_SRCOUT), ch); + vortex_src_addWTD(vortex, + (source1 - OFFSET_SRCOUT), ch); + } else if ((source0 < (OFFSET_MIXOUT + NR_MIXOUT)) + && (source0 >= OFFSET_MIXOUT)) { + vortex_mixer_addWTD(vortex, + (source0 - OFFSET_MIXOUT), ch); + vortex_mixer_addWTD(vortex, + (source1 - OFFSET_MIXOUT), ch); + } + } else { + vortex_adb_delroutes(vortex, ch, route[0], route[1]); + if ((source0 < (OFFSET_SRCOUT + NR_SRC)) + && (source0 >= OFFSET_SRCOUT)) { + vortex_src_delWTD(vortex, + (source0 - OFFSET_SRCOUT), ch); + vortex_src_delWTD(vortex, + (source1 - OFFSET_SRCOUT), ch); + } else if ((source0 < (OFFSET_MIXOUT + NR_MIXOUT)) + && (source0 >= OFFSET_MIXOUT)) { + vortex_mixer_delWTD(vortex, + (source0 - OFFSET_MIXOUT), ch); + vortex_mixer_delWTD(vortex, + (source1 - OFFSET_MIXOUT), ch); + } + } +} + +/* Connection stuff */ + +// Connect adbdma to src('s). +static void +vortex_connection_adbdma_src(vortex_t * vortex, int en, unsigned char ch, + unsigned char adbdma, unsigned char src) +{ + vortex_route(vortex, en, ch, ADB_DMA(adbdma), ADB_SRCIN(src)); +} + +// Connect SRC to mixin. +static void +vortex_connection_src_mixin(vortex_t * vortex, int en, + unsigned char channel, unsigned char src, + unsigned char mixin) +{ + vortex_route(vortex, en, channel, ADB_SRCOUT(src), ADB_MIXIN(mixin)); +} + +// Connect mixin with mix output. +static void +vortex_connection_mixin_mix(vortex_t * vortex, int en, unsigned char mixin, + unsigned char mix, int a) +{ + if (en) { + vortex_mix_enableinput(vortex, mix, mixin); + vortex_mix_setinputvolumebyte(vortex, mix, mixin, MIX_DEFIGAIN); // added to original code. + } else + vortex_mix_disableinput(vortex, mix, mixin, a); +} + +// Connect absolute address to mixin. +static void +vortex_connection_adb_mixin(vortex_t * vortex, int en, + unsigned char channel, unsigned char source, + unsigned char mixin) +{ + vortex_route(vortex, en, channel, source, ADB_MIXIN(mixin)); +} + +static void +vortex_connection_src_adbdma(vortex_t * vortex, int en, unsigned char ch, + unsigned char src, unsigned char adbdma) +{ + vortex_route(vortex, en, ch, ADB_SRCOUT(src), ADB_DMA(adbdma)); +} + +static void +vortex_connection_src_src_adbdma(vortex_t * vortex, int en, + unsigned char ch, unsigned char src0, + unsigned char src1, unsigned char adbdma) +{ + + vortex_routeLRT(vortex, en, ch, ADB_SRCOUT(src0), ADB_SRCOUT(src1), + ADB_DMA(adbdma)); +} + +// mix to absolute address. +static void +vortex_connection_mix_adb(vortex_t * vortex, int en, unsigned char ch, + unsigned char mix, unsigned char dest) +{ + vortex_route(vortex, en, ch, ADB_MIXOUT(mix), dest); + vortex_mix_setvolumebyte(vortex, mix, MIX_DEFOGAIN); // added to original code. +} + +// mixer to src. +static void +vortex_connection_mix_src(vortex_t * vortex, int en, unsigned char ch, + unsigned char mix, unsigned char src) +{ + vortex_route(vortex, en, ch, ADB_MIXOUT(mix), ADB_SRCIN(src)); + vortex_mix_setvolumebyte(vortex, mix, MIX_DEFOGAIN); // added to original code. +} + +#if 0 +static void +vortex_connection_adbdma_src_src(vortex_t * vortex, int en, + unsigned char channel, + unsigned char adbdma, unsigned char src0, + unsigned char src1) +{ + vortex_routes(vortex, en, channel, ADB_DMA(adbdma), + ADB_SRCIN(src0), ADB_SRCIN(src1)); +} + +// Connect two mix to AdbDma. +static void +vortex_connection_mix_mix_adbdma(vortex_t * vortex, int en, + unsigned char ch, unsigned char mix0, + unsigned char mix1, unsigned char adbdma) +{ + + ADBRamLink routes[2]; + routes[0] = + (((mix0 + + OFFSET_MIXOUT) & ADB_MASK) << ADB_SHIFT) | (adbdma & ADB_MASK); + routes[1] = + (((mix1 + OFFSET_MIXOUT) & ADB_MASK) << ADB_SHIFT) | ((adbdma + + 0x20) & + ADB_MASK); + if (en) { + vortex_adb_addroutes(vortex, ch, routes, 0x2); + vortex_mixer_addWTD(vortex, mix0, ch); + vortex_mixer_addWTD(vortex, mix1, ch); + } else { + vortex_adb_delroutes(vortex, ch, routes[0], routes[1]); + vortex_mixer_delWTD(vortex, mix0, ch); + vortex_mixer_delWTD(vortex, mix1, ch); + } +} +#endif + +/* CODEC connect. */ + +static void +vortex_connect_codecplay(vortex_t * vortex, int en, unsigned char mixers[]) +{ +#ifdef CHIP_AU8820 + vortex_connection_mix_adb(vortex, en, 0x11, mixers[0], ADB_CODECOUT(0)); + vortex_connection_mix_adb(vortex, en, 0x11, mixers[1], ADB_CODECOUT(1)); +#else +#if 1 + // Connect front channels through EQ. + vortex_connection_mix_adb(vortex, en, 0x11, mixers[0], ADB_EQIN(0)); + vortex_connection_mix_adb(vortex, en, 0x11, mixers[1], ADB_EQIN(1)); + /* Lower volume, since EQ has some gain. */ + vortex_mix_setvolumebyte(vortex, mixers[0], 0); + vortex_mix_setvolumebyte(vortex, mixers[1], 0); + vortex_route(vortex, en, 0x11, ADB_EQOUT(0), ADB_CODECOUT(0)); + vortex_route(vortex, en, 0x11, ADB_EQOUT(1), ADB_CODECOUT(1)); + + /* Check if reg 0x28 has SDAC bit set. */ + if (VORTEX_IS_QUAD(vortex)) { + /* Rear channel. Note: ADB_CODECOUT(0+2) and (1+2) is for AC97 modem */ + vortex_connection_mix_adb(vortex, en, 0x11, mixers[2], + ADB_CODECOUT(0 + 4)); + vortex_connection_mix_adb(vortex, en, 0x11, mixers[3], + ADB_CODECOUT(1 + 4)); + /* pr_debug( "SDAC detected "); */ + } +#else + // Use plain direct output to codec. + vortex_connection_mix_adb(vortex, en, 0x11, mixers[0], ADB_CODECOUT(0)); + vortex_connection_mix_adb(vortex, en, 0x11, mixers[1], ADB_CODECOUT(1)); +#endif +#endif +} + +static void +vortex_connect_codecrec(vortex_t * vortex, int en, unsigned char mixin0, + unsigned char mixin1) +{ + /* + Enable: 0x1, 0x1 + Channel: 0x11, 0x11 + ADB Source address: 0x48, 0x49 + Destination Asp4Topology_0x9c,0x98 + */ + vortex_connection_adb_mixin(vortex, en, 0x11, ADB_CODECIN(0), mixin0); + vortex_connection_adb_mixin(vortex, en, 0x11, ADB_CODECIN(1), mixin1); +} + +// Higher level ADB audio path (de)allocator. + +/* Resource manager */ +static const int resnum[VORTEX_RESOURCE_LAST] = + { NR_ADB, NR_SRC, NR_MIXIN, NR_MIXOUT, NR_A3D }; +/* + Checkout/Checkin resource of given type. + resmap: resource map to be used. If NULL means that we want to allocate + a DMA resource (root of all other resources of a dma channel). + out: Mean checkout if != 0. Else mean Checkin resource. + restype: Indicates type of resource to be checked in or out. +*/ +static int +vortex_adb_checkinout(vortex_t * vortex, int resmap[], int out, int restype) +{ + int i, qty = resnum[restype], resinuse = 0; + + if (out) { + /* Gather used resources by all streams. */ + for (i = 0; i < NR_ADB; i++) { + resinuse |= vortex->dma_adb[i].resources[restype]; + } + resinuse |= vortex->fixed_res[restype]; + /* Find and take free resource. */ + for (i = 0; i < qty; i++) { + if ((resinuse & (1 << i)) == 0) { + if (resmap != NULL) + resmap[restype] |= (1 << i); + else + vortex->dma_adb[i].resources[restype] |= (1 << i); + /* + pr_debug( + "vortex: ResManager: type %d out %d\n", + restype, i); + */ + return i; + } + } + } else { + if (resmap == NULL) + return -EINVAL; + /* Checkin first resource of type restype. */ + for (i = 0; i < qty; i++) { + if (resmap[restype] & (1 << i)) { + resmap[restype] &= ~(1 << i); + /* + pr_debug( + "vortex: ResManager: type %d in %d\n", + restype, i); + */ + return i; + } + } + } + dev_err(vortex->card->dev, + "FATAL: ResManager: resource type %d exhausted.\n", + restype); + return -ENOMEM; +} + +/* Default Connections */ + +static void vortex_connect_default(vortex_t * vortex, int en) +{ + // Connect AC97 codec. + vortex->mixplayb[0] = vortex_adb_checkinout(vortex, vortex->fixed_res, en, + VORTEX_RESOURCE_MIXOUT); + vortex->mixplayb[1] = vortex_adb_checkinout(vortex, vortex->fixed_res, en, + VORTEX_RESOURCE_MIXOUT); + if (VORTEX_IS_QUAD(vortex)) { + vortex->mixplayb[2] = vortex_adb_checkinout(vortex, vortex->fixed_res, en, + VORTEX_RESOURCE_MIXOUT); + vortex->mixplayb[3] = vortex_adb_checkinout(vortex, vortex->fixed_res, en, + VORTEX_RESOURCE_MIXOUT); + } + vortex_connect_codecplay(vortex, en, vortex->mixplayb); + + vortex->mixcapt[0] = vortex_adb_checkinout(vortex, vortex->fixed_res, en, + VORTEX_RESOURCE_MIXIN); + vortex->mixcapt[1] = vortex_adb_checkinout(vortex, vortex->fixed_res, en, + VORTEX_RESOURCE_MIXIN); + vortex_connect_codecrec(vortex, en, MIX_CAPT(0), MIX_CAPT(1)); + + // Connect SPDIF +#ifndef CHIP_AU8820 + vortex->mixspdif[0] = vortex_adb_checkinout(vortex, vortex->fixed_res, en, + VORTEX_RESOURCE_MIXOUT); + vortex->mixspdif[1] = vortex_adb_checkinout(vortex, vortex->fixed_res, en, + VORTEX_RESOURCE_MIXOUT); + vortex_connection_mix_adb(vortex, en, 0x14, vortex->mixspdif[0], + ADB_SPDIFOUT(0)); + vortex_connection_mix_adb(vortex, en, 0x14, vortex->mixspdif[1], + ADB_SPDIFOUT(1)); +#endif + // Connect WT +#ifndef CHIP_AU8810 + vortex_wt_connect(vortex, en); +#endif + // A3D (crosstalk canceler and A3D slices). AU8810 disabled for now. +#ifndef CHIP_AU8820 + vortex_Vort3D_connect(vortex, en); +#endif + // Connect I2S + + // Connect DSP interface for SQ3500 turbo (not here i think...) + + // Connect AC98 modem codec + +} + +/* + Allocate nr_ch pcm audio routes if dma < 0. If dma >= 0, existing routes + are deallocated. + dma: DMA engine routes to be deallocated when dma >= 0. + nr_ch: Number of channels to be de/allocated. + dir: direction of stream. Uses same values as substream->stream. + type: Type of audio output/source (codec, spdif, i2s, dsp, etc) + Return: Return allocated DMA or same DMA passed as "dma" when dma >= 0. +*/ +static int +vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir, + int type, int subdev) +{ + stream_t *stream; + int i, en; + struct pcm_vol *p; + + if (dma >= 0) { + en = 0; + vortex_adb_checkinout(vortex, + vortex->dma_adb[dma].resources, en, + VORTEX_RESOURCE_DMA); + } else { + en = 1; + dma = vortex_adb_checkinout(vortex, NULL, en, + VORTEX_RESOURCE_DMA); + if (dma < 0) + return -EBUSY; + } + + stream = &vortex->dma_adb[dma]; + stream->dma = dma; + stream->dir = dir; + stream->type = type; + + /* PLAYBACK ROUTES. */ + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + int src[4], mix[4], ch_top; +#ifndef CHIP_AU8820 + int a3d = 0; +#endif + /* Get SRC and MIXER hardware resources. */ + if (stream->type != VORTEX_PCM_SPDIF) { + for (i = 0; i < nr_ch; i++) { + src[i] = vortex_adb_checkinout(vortex, + stream->resources, en, + VORTEX_RESOURCE_SRC); + if (src[i] < 0) { + memset(stream->resources, 0, + sizeof(stream->resources)); + return -EBUSY; + } + if (stream->type != VORTEX_PCM_A3D) { + mix[i] = vortex_adb_checkinout(vortex, + stream->resources, + en, + VORTEX_RESOURCE_MIXIN); + if (mix[i] < 0) { + memset(stream->resources, + 0, + sizeof(stream->resources)); + return -EBUSY; + } + } + } + } +#ifndef CHIP_AU8820 + if (stream->type == VORTEX_PCM_A3D) { + a3d = vortex_adb_checkinout(vortex, + stream->resources, en, + VORTEX_RESOURCE_A3D); + if (a3d < 0) { + memset(stream->resources, 0, + sizeof(stream->resources)); + dev_err(vortex->card->dev, + "out of A3D sources. Sorry\n"); + return -EBUSY; + } + /* (De)Initialize A3D hardware source. */ + vortex_Vort3D_InitializeSource(&vortex->a3d[a3d], en, + vortex); + } + /* Make SPDIF out exclusive to "spdif" device when in use. */ + if ((stream->type == VORTEX_PCM_SPDIF) && (en)) { + vortex_route(vortex, 0, 0x14, + ADB_MIXOUT(vortex->mixspdif[0]), + ADB_SPDIFOUT(0)); + vortex_route(vortex, 0, 0x14, + ADB_MIXOUT(vortex->mixspdif[1]), + ADB_SPDIFOUT(1)); + } +#endif + /* Make playback routes. */ + for (i = 0; i < nr_ch; i++) { + if (stream->type == VORTEX_PCM_ADB) { + vortex_connection_adbdma_src(vortex, en, + src[nr_ch - 1], + dma, + src[i]); + vortex_connection_src_mixin(vortex, en, + 0x11, src[i], + mix[i]); + vortex_connection_mixin_mix(vortex, en, + mix[i], + MIX_PLAYB(i), 0); +#ifndef CHIP_AU8820 + vortex_connection_mixin_mix(vortex, en, + mix[i], + MIX_SPDIF(i % 2), 0); + vortex_mix_setinputvolumebyte(vortex, + MIX_SPDIF(i % 2), + mix[i], + MIX_DEFIGAIN); +#endif + } +#ifndef CHIP_AU8820 + if (stream->type == VORTEX_PCM_A3D) { + vortex_connection_adbdma_src(vortex, en, + src[nr_ch - 1], + dma, + src[i]); + vortex_route(vortex, en, 0x11, ADB_SRCOUT(src[i]), ADB_A3DIN(a3d)); + /* XTalk test. */ + //vortex_route(vortex, en, 0x11, dma, ADB_XTALKIN(i?9:4)); + //vortex_route(vortex, en, 0x11, ADB_SRCOUT(src[i]), ADB_XTALKIN(i?4:9)); + } + if (stream->type == VORTEX_PCM_SPDIF) + vortex_route(vortex, en, 0x14, + ADB_DMA(stream->dma), + ADB_SPDIFOUT(i)); +#endif + } + if (stream->type != VORTEX_PCM_SPDIF && stream->type != VORTEX_PCM_A3D) { + ch_top = (VORTEX_IS_QUAD(vortex) ? 4 : 2); + for (i = nr_ch; i < ch_top; i++) { + vortex_connection_mixin_mix(vortex, en, + mix[i % nr_ch], + MIX_PLAYB(i), 0); +#ifndef CHIP_AU8820 + vortex_connection_mixin_mix(vortex, en, + mix[i % nr_ch], + MIX_SPDIF(i % 2), + 0); + vortex_mix_setinputvolumebyte(vortex, + MIX_SPDIF(i % 2), + mix[i % nr_ch], + MIX_DEFIGAIN); +#endif + } + if (stream->type == VORTEX_PCM_ADB && en) { + p = &vortex->pcm_vol[subdev]; + p->dma = dma; + for (i = 0; i < nr_ch; i++) + p->mixin[i] = mix[i]; + for (i = 0; i < ch_top; i++) + p->vol[i] = 0; + } + } +#ifndef CHIP_AU8820 + else { + if (nr_ch == 1 && stream->type == VORTEX_PCM_SPDIF) + vortex_route(vortex, en, 0x14, + ADB_DMA(stream->dma), + ADB_SPDIFOUT(1)); + } + /* Reconnect SPDIF out when "spdif" device is down. */ + if ((stream->type == VORTEX_PCM_SPDIF) && (!en)) { + vortex_route(vortex, 1, 0x14, + ADB_MIXOUT(vortex->mixspdif[0]), + ADB_SPDIFOUT(0)); + vortex_route(vortex, 1, 0x14, + ADB_MIXOUT(vortex->mixspdif[1]), + ADB_SPDIFOUT(1)); + } +#endif + /* CAPTURE ROUTES. */ + } else { + int src[2], mix[2]; + + if (nr_ch < 1) + return -EINVAL; + + /* Get SRC and MIXER hardware resources. */ + for (i = 0; i < nr_ch; i++) { + mix[i] = vortex_adb_checkinout(vortex, + stream->resources, en, + VORTEX_RESOURCE_MIXOUT); + if (mix[i] < 0) { + memset(stream->resources, 0, + sizeof(stream->resources)); + return -EBUSY; + } + src[i] = vortex_adb_checkinout(vortex, + stream->resources, en, + VORTEX_RESOURCE_SRC); + if (src[i] < 0) { + memset(stream->resources, 0, + sizeof(stream->resources)); + return -EBUSY; + } + } + + /* Make capture routes. */ + vortex_connection_mixin_mix(vortex, en, MIX_CAPT(0), mix[0], 0); + vortex_connection_mix_src(vortex, en, 0x11, mix[0], src[0]); + if (nr_ch == 1) { + vortex_connection_mixin_mix(vortex, en, + MIX_CAPT(1), mix[0], 0); + vortex_connection_src_adbdma(vortex, en, + src[0], + src[0], dma); + } else { + vortex_connection_mixin_mix(vortex, en, + MIX_CAPT(1), mix[1], 0); + vortex_connection_mix_src(vortex, en, 0x11, mix[1], + src[1]); + vortex_connection_src_src_adbdma(vortex, en, + src[1], src[0], + src[1], dma); + } + } + vortex->dma_adb[dma].nr_ch = nr_ch; + +#if 0 + /* AC97 Codec channel setup. FIXME: this has no effect on some cards !! */ + if (nr_ch < 4) { + /* Copy stereo to rear channel (surround) */ + snd_ac97_write_cache(vortex->codec, + AC97_SIGMATEL_DAC2INVERT, + snd_ac97_read(vortex->codec, + AC97_SIGMATEL_DAC2INVERT) + | 4); + } else { + /* Allow separate front and rear channels. */ + snd_ac97_write_cache(vortex->codec, + AC97_SIGMATEL_DAC2INVERT, + snd_ac97_read(vortex->codec, + AC97_SIGMATEL_DAC2INVERT) + & ~((u32) + 4)); + } +#endif + return dma; +} + +/* + Set the SampleRate of the SRC's attached to the given DMA engine. + */ +static void +vortex_adb_setsrc(vortex_t * vortex, int adbdma, unsigned int rate, int dir) +{ + stream_t *stream = &(vortex->dma_adb[adbdma]); + int i, cvrt; + + /* dir=1:play ; dir=0:rec */ + if (dir) + cvrt = SRC_RATIO(rate, 48000); + else + cvrt = SRC_RATIO(48000, rate); + + /* Setup SRC's */ + for (i = 0; i < NR_SRC; i++) { + if (stream->resources[VORTEX_RESOURCE_SRC] & (1 << i)) + vortex_src_setupchannel(vortex, i, cvrt, 0, 0, i, dir, 1, cvrt, dir); + } +} + +// Timer and ISR functions. + +static void vortex_settimer(vortex_t * vortex, int period) +{ + //set the timer period to 48000ths of a second. + hwwrite(vortex->mmio, VORTEX_IRQ_STAT, period); +} + +#if 0 +static void vortex_enable_timer_int(vortex_t * card) +{ + hwwrite(card->mmio, VORTEX_IRQ_CTRL, + hwread(card->mmio, VORTEX_IRQ_CTRL) | IRQ_TIMER | 0x60); +} + +static void vortex_disable_timer_int(vortex_t * card) +{ + hwwrite(card->mmio, VORTEX_IRQ_CTRL, + hwread(card->mmio, VORTEX_IRQ_CTRL) & ~IRQ_TIMER); +} + +#endif +static void vortex_enable_int(vortex_t * card) +{ + // CAsp4ISR__EnableVortexInt_void_ + hwwrite(card->mmio, VORTEX_CTRL, + hwread(card->mmio, VORTEX_CTRL) | CTRL_IRQ_ENABLE); + hwwrite(card->mmio, VORTEX_IRQ_CTRL, + (hwread(card->mmio, VORTEX_IRQ_CTRL) & 0xffffefc0) | 0x24); +} + +static void vortex_disable_int(vortex_t * card) +{ + hwwrite(card->mmio, VORTEX_CTRL, + hwread(card->mmio, VORTEX_CTRL) & ~CTRL_IRQ_ENABLE); +} + +static irqreturn_t vortex_interrupt(int irq, void *dev_id) +{ + vortex_t *vortex = dev_id; + int i, handled; + u32 source; + + //check if the interrupt is ours. + if (!(hwread(vortex->mmio, VORTEX_STAT) & 0x1)) + return IRQ_NONE; + + // This is the Interrupt Enable flag we set before (consistency check). + if (!(hwread(vortex->mmio, VORTEX_CTRL) & CTRL_IRQ_ENABLE)) + return IRQ_NONE; + + source = hwread(vortex->mmio, VORTEX_IRQ_SOURCE); + // Reset IRQ flags. + hwwrite(vortex->mmio, VORTEX_IRQ_SOURCE, source); + hwread(vortex->mmio, VORTEX_IRQ_SOURCE); + // Is at least one IRQ flag set? + if (source == 0) { + dev_err(vortex->card->dev, "missing irq source\n"); + return IRQ_NONE; + } + + handled = 0; + // Attend every interrupt source. + if (unlikely(source & IRQ_ERR_MASK)) { + if (source & IRQ_FATAL) { + dev_err(vortex->card->dev, "IRQ fatal error\n"); + } + if (source & IRQ_PARITY) { + dev_err(vortex->card->dev, "IRQ parity error\n"); + } + if (source & IRQ_REG) { + dev_err(vortex->card->dev, "IRQ reg error\n"); + } + if (source & IRQ_FIFO) { + dev_err(vortex->card->dev, "IRQ fifo error\n"); + } + if (source & IRQ_DMA) { + dev_err(vortex->card->dev, "IRQ dma error\n"); + } + handled = 1; + } + if (source & IRQ_PCMOUT) { + /* ALSA period acknowledge. */ + spin_lock(&vortex->lock); + for (i = 0; i < NR_ADB; i++) { + if (vortex->dma_adb[i].fifo_status == FIFO_START) { + if (!vortex_adbdma_bufshift(vortex, i)) + continue; + spin_unlock(&vortex->lock); + snd_pcm_period_elapsed(vortex->dma_adb[i]. + substream); + spin_lock(&vortex->lock); + } + } +#ifndef CHIP_AU8810 + for (i = 0; i < NR_WT; i++) { + if (vortex->dma_wt[i].fifo_status == FIFO_START) { + /* FIXME: we ignore the return value from + * vortex_wtdma_bufshift() below as the delta + * calculation seems not working for wavetable + * by some reason + */ + vortex_wtdma_bufshift(vortex, i); + spin_unlock(&vortex->lock); + snd_pcm_period_elapsed(vortex->dma_wt[i]. + substream); + spin_lock(&vortex->lock); + } + } +#endif + spin_unlock(&vortex->lock); + handled = 1; + } + //Acknowledge the Timer interrupt + if (source & IRQ_TIMER) { + hwread(vortex->mmio, VORTEX_IRQ_STAT); + handled = 1; + } + if ((source & IRQ_MIDI) && vortex->rmidi) { + snd_mpu401_uart_interrupt(vortex->irq, + vortex->rmidi->private_data); + handled = 1; + } + + if (!handled) { + dev_err(vortex->card->dev, "unknown irq source %x\n", source); + } + return IRQ_RETVAL(handled); +} + +/* Codec */ + +#define POLL_COUNT 1000 +static void vortex_codec_init(vortex_t * vortex) +{ + int i; + + for (i = 0; i < 32; i++) { + /* the windows driver writes -i, so we write -i */ + hwwrite(vortex->mmio, (VORTEX_CODEC_CHN + (i << 2)), -i); + msleep(2); + } + if (0) { + hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x8068); + msleep(1); + hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x00e8); + msleep(1); + } else { + hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x00a8); + msleep(2); + hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x80a8); + msleep(2); + hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x80e8); + msleep(2); + hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x80a8); + msleep(2); + hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x00a8); + msleep(2); + hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x00e8); + } + for (i = 0; i < 32; i++) { + hwwrite(vortex->mmio, (VORTEX_CODEC_CHN + (i << 2)), -i); + msleep(5); + } + hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0xe8); + msleep(1); + /* Enable codec channels 0 and 1. */ + hwwrite(vortex->mmio, VORTEX_CODEC_EN, + hwread(vortex->mmio, VORTEX_CODEC_EN) | EN_CODEC); +} + +static void +vortex_codec_write(struct snd_ac97 * codec, unsigned short addr, unsigned short data) +{ + + vortex_t *card = (vortex_t *) codec->private_data; + unsigned int lifeboat = 0; + + /* wait for transactions to clear */ + while (!(hwread(card->mmio, VORTEX_CODEC_CTRL) & 0x100)) { + udelay(100); + if (lifeboat++ > POLL_COUNT) { + dev_err(card->card->dev, "ac97 codec stuck busy\n"); + return; + } + } + /* write register */ + hwwrite(card->mmio, VORTEX_CODEC_IO, + ((addr << VORTEX_CODEC_ADDSHIFT) & VORTEX_CODEC_ADDMASK) | + ((data << VORTEX_CODEC_DATSHIFT) & VORTEX_CODEC_DATMASK) | + VORTEX_CODEC_WRITE | + (codec->num << VORTEX_CODEC_ID_SHIFT) ); + + /* Flush Caches. */ + hwread(card->mmio, VORTEX_CODEC_IO); +} + +static unsigned short vortex_codec_read(struct snd_ac97 * codec, unsigned short addr) +{ + + vortex_t *card = (vortex_t *) codec->private_data; + u32 read_addr, data; + unsigned lifeboat = 0; + + /* wait for transactions to clear */ + while (!(hwread(card->mmio, VORTEX_CODEC_CTRL) & 0x100)) { + udelay(100); + if (lifeboat++ > POLL_COUNT) { + dev_err(card->card->dev, "ac97 codec stuck busy\n"); + return 0xffff; + } + } + /* set up read address */ + read_addr = ((addr << VORTEX_CODEC_ADDSHIFT) & VORTEX_CODEC_ADDMASK) | + (codec->num << VORTEX_CODEC_ID_SHIFT) ; + hwwrite(card->mmio, VORTEX_CODEC_IO, read_addr); + + /* wait for address */ + do { + udelay(100); + data = hwread(card->mmio, VORTEX_CODEC_IO); + if (lifeboat++ > POLL_COUNT) { + dev_err(card->card->dev, + "ac97 address never arrived\n"); + return 0xffff; + } + } while ((data & VORTEX_CODEC_ADDMASK) != + (addr << VORTEX_CODEC_ADDSHIFT)); + + /* return data. */ + return (u16) (data & VORTEX_CODEC_DATMASK); +} + +/* SPDIF support */ + +static void vortex_spdif_init(vortex_t * vortex, int spdif_sr, int spdif_mode) +{ + int i, this_38 = 0, this_04 = 0, this_08 = 0, this_0c = 0; + + /* CAsp4Spdif::InitializeSpdifHardware(void) */ + hwwrite(vortex->mmio, VORTEX_SPDIF_FLAGS, + hwread(vortex->mmio, VORTEX_SPDIF_FLAGS) & 0xfff3fffd); + //for (i=0x291D4; i<0x29200; i+=4) + for (i = 0; i < 11; i++) + hwwrite(vortex->mmio, VORTEX_SPDIF_CFG1 + (i << 2), 0); + //hwwrite(vortex->mmio, 0x29190, hwread(vortex->mmio, 0x29190) | 0xc0000); + hwwrite(vortex->mmio, VORTEX_CODEC_EN, + hwread(vortex->mmio, VORTEX_CODEC_EN) | EN_SPDIF); + + /* CAsp4Spdif::ProgramSRCInHardware(enum SPDIF_SR,enum SPDIFMODE) */ + if (this_04 && this_08) { + int edi; + + i = (((0x5DC00000 / spdif_sr) + 1) >> 1); + if (i > 0x800) { + if (i < 0x1ffff) + edi = (i >> 1); + else + edi = 0x1ffff; + } else { + edi = 0x800; + } + /* this_04 and this_08 are the CASp4Src's (samplerate converters) */ + vortex_src_setupchannel(vortex, this_04, edi, 0, 1, + this_0c, 1, 0, edi, 1); + vortex_src_setupchannel(vortex, this_08, edi, 0, 1, + this_0c, 1, 0, edi, 1); + } + + i = spdif_sr; + spdif_sr |= 0x8c; + switch (i) { + case 32000: + this_38 &= 0xFFFFFFFE; + this_38 &= 0xFFFFFFFD; + this_38 &= 0xF3FFFFFF; + this_38 |= 0x03000000; /* set 32khz samplerate */ + this_38 &= 0xFFFFFF3F; + spdif_sr &= 0xFFFFFFFD; + spdif_sr |= 1; + break; + case 44100: + this_38 &= 0xFFFFFFFE; + this_38 &= 0xFFFFFFFD; + this_38 &= 0xF0FFFFFF; + this_38 |= 0x03000000; + this_38 &= 0xFFFFFF3F; + spdif_sr &= 0xFFFFFFFC; + break; + case 48000: + if (spdif_mode == 1) { + this_38 &= 0xFFFFFFFE; + this_38 &= 0xFFFFFFFD; + this_38 &= 0xF2FFFFFF; + this_38 |= 0x02000000; /* set 48khz samplerate */ + this_38 &= 0xFFFFFF3F; + } else { + /* J. Gordon Wolfe: I think this stuff is for AC3 */ + this_38 |= 0x00000003; + this_38 &= 0xFFFFFFBF; + this_38 |= 0x80; + } + spdif_sr |= 2; + spdif_sr &= 0xFFFFFFFE; + break; + + } + /* looks like the next 2 lines transfer a 16-bit value into 2 8-bit + registers. seems to be for the standard IEC/SPDIF initialization + stuff */ + hwwrite(vortex->mmio, VORTEX_SPDIF_CFG0, this_38 & 0xffff); + hwwrite(vortex->mmio, VORTEX_SPDIF_CFG1, this_38 >> 0x10); + hwwrite(vortex->mmio, VORTEX_SPDIF_SMPRATE, spdif_sr); +} + +/* Initialization */ + +static int vortex_core_init(vortex_t *vortex) +{ + + dev_info(vortex->card->dev, "init started\n"); + /* Hardware Init. */ + hwwrite(vortex->mmio, VORTEX_CTRL, 0xffffffff); + msleep(5); + hwwrite(vortex->mmio, VORTEX_CTRL, + hwread(vortex->mmio, VORTEX_CTRL) & 0xffdfffff); + msleep(5); + /* Reset IRQ flags */ + hwwrite(vortex->mmio, VORTEX_IRQ_SOURCE, 0xffffffff); + hwread(vortex->mmio, VORTEX_IRQ_STAT); + + vortex_codec_init(vortex); + +#ifdef CHIP_AU8830 + hwwrite(vortex->mmio, VORTEX_CTRL, + hwread(vortex->mmio, VORTEX_CTRL) | 0x1000000); +#endif + + /* Init audio engine. */ + vortex_adbdma_init(vortex); + hwwrite(vortex->mmio, VORTEX_ENGINE_CTRL, 0x0); //, 0xc83c7e58, 0xc5f93e58 + vortex_adb_init(vortex); + /* Init processing blocks. */ + vortex_fifo_init(vortex); + vortex_mixer_init(vortex); + vortex_srcblock_init(vortex); +#ifndef CHIP_AU8820 + vortex_eq_init(vortex); + vortex_spdif_init(vortex, 48000, 1); + vortex_Vort3D_enable(vortex); +#endif +#ifndef CHIP_AU8810 + vortex_wt_init(vortex); +#endif + // Moved to au88x0.c + //vortex_connect_default(vortex, 1); + + vortex_settimer(vortex, 0x90); + // Enable Interrupts. + // vortex_enable_int() must be first !! + // hwwrite(vortex->mmio, VORTEX_IRQ_CTRL, 0); + // vortex_enable_int(vortex); + //vortex_enable_timer_int(vortex); + //vortex_disable_timer_int(vortex); + + dev_info(vortex->card->dev, "init.... done.\n"); + spin_lock_init(&vortex->lock); + + return 0; +} + +static int vortex_core_shutdown(vortex_t * vortex) +{ + + dev_info(vortex->card->dev, "shutdown started\n"); +#ifndef CHIP_AU8820 + vortex_eq_free(vortex); + vortex_Vort3D_disable(vortex); +#endif + //vortex_disable_timer_int(vortex); + vortex_disable_int(vortex); + vortex_connect_default(vortex, 0); + /* Reset all DMA fifos. */ + vortex_fifo_init(vortex); + /* Erase all audio routes. */ + vortex_adb_init(vortex); + + /* Disable MPU401 */ + //hwwrite(vortex->mmio, VORTEX_IRQ_CTRL, hwread(vortex->mmio, VORTEX_IRQ_CTRL) & ~IRQ_MIDI); + //hwwrite(vortex->mmio, VORTEX_CTRL, hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_EN); + + hwwrite(vortex->mmio, VORTEX_IRQ_CTRL, 0); + hwwrite(vortex->mmio, VORTEX_CTRL, 0); + msleep(5); + hwwrite(vortex->mmio, VORTEX_IRQ_SOURCE, 0xffff); + + dev_info(vortex->card->dev, "shutdown.... done.\n"); + return 0; +} + +/* Alsa support. */ + +static int vortex_alsafmt_aspfmt(snd_pcm_format_t alsafmt, vortex_t *v) +{ + int fmt; + + switch (alsafmt) { + case SNDRV_PCM_FORMAT_U8: + fmt = 0x1; + break; + case SNDRV_PCM_FORMAT_MU_LAW: + fmt = 0x2; + break; + case SNDRV_PCM_FORMAT_A_LAW: + fmt = 0x3; + break; + case SNDRV_PCM_FORMAT_SPECIAL: + fmt = 0x4; /* guess. */ + break; + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + fmt = 0x5; /* guess. */ + break; + case SNDRV_PCM_FORMAT_S16_LE: + fmt = 0x8; + break; + case SNDRV_PCM_FORMAT_S16_BE: + fmt = 0x9; /* check this... */ + break; + default: + fmt = 0x8; + dev_err(v->card->dev, + "format unsupported %d\n", alsafmt); + break; + } + return fmt; +} + +/* Some not yet useful translations. */ +#if 0 +typedef enum { + ASPFMTLINEAR16 = 0, /* 0x8 */ + ASPFMTLINEAR8, /* 0x1 */ + ASPFMTULAW, /* 0x2 */ + ASPFMTALAW, /* 0x3 */ + ASPFMTSPORT, /* ? */ + ASPFMTSPDIF, /* ? */ +} ASPENCODING; + +static int +vortex_translateformat(vortex_t * vortex, char bits, char nch, int encod) +{ + int a, this_194; + + if ((bits != 8) && (bits != 16)) + return -1; + + switch (encod) { + case 0: + if (bits == 0x10) + a = 8; // 16 bit + break; + case 1: + if (bits == 8) + a = 1; // 8 bit + break; + case 2: + a = 2; // U_LAW + break; + case 3: + a = 3; // A_LAW + break; + } + switch (nch) { + case 1: + this_194 = 0; + break; + case 2: + this_194 = 1; + break; + case 4: + this_194 = 1; + break; + case 6: + this_194 = 1; + break; + } + return (a); +} + +static void vortex_cdmacore_setformat(vortex_t * vortex, int bits, int nch) +{ + short int d, this_148; + + d = ((bits >> 3) * nch); + this_148 = 0xbb80 / d; +} +#endif diff --git a/sound/pci/au88x0/au88x0_eq.c b/sound/pci/au88x0/au88x0_eq.c new file mode 100644 index 000000000..81a63b5bb --- /dev/null +++ b/sound/pci/au88x0/au88x0_eq.c @@ -0,0 +1,922 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/*************************************************************************** + * au88x0_eq.c + * Aureal Vortex Hardware EQ control/access. + * + * Sun Jun 8 18:19:19 2003 + * 2003 Manuel Jander (mjander@users.sourceforge.net) + * + * 02 July 2003: First time something works :) + * November 2003: A3D Bypass code completed but untested. + * + * TODO: + * - Debug (testing) + * - Test peak visualization support. + * + ****************************************************************************/ + +/* + */ + +/* + The Aureal Hardware EQ is found on AU8810 and AU8830 chips only. + it has 4 inputs (2 for general mix, 2 for A3D) and 2 outputs (supposed + to be routed to the codec). +*/ + +#include "au88x0.h" +#include "au88x0_eq.h" +#include "au88x0_eqdata.c" + +#define VORTEX_EQ_BASE 0x2b000 +#define VORTEX_EQ_DEST (VORTEX_EQ_BASE + 0x410) +#define VORTEX_EQ_SOURCE (VORTEX_EQ_BASE + 0x430) +#define VORTEX_EQ_CTRL (VORTEX_EQ_BASE + 0x440) + +#define VORTEX_BAND_COEFF_SIZE 0x30 + +/* CEqHw.s */ +static void vortex_EqHw_SetTimeConsts(vortex_t * vortex, u16 gain, u16 level) +{ + hwwrite(vortex->mmio, 0x2b3c4, gain); + hwwrite(vortex->mmio, 0x2b3c8, level); +} + +static inline u16 sign_invert(u16 a) +{ + /* -(-32768) -> -32768 so we do -(-32768) -> 32767 to make the result positive */ + if (a == (u16)-32768) + return 32767; + else + return -a; +} + +static void vortex_EqHw_SetLeftCoefs(vortex_t *vortex, const u16 coefs[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int i = 0, n /*esp2c */; + + for (n = 0; n < eqhw->this04; n++) { + hwwrite(vortex->mmio, 0x2b000 + n * 0x30, coefs[i + 0]); + hwwrite(vortex->mmio, 0x2b004 + n * 0x30, coefs[i + 1]); + + if (eqhw->this08 == 0) { + hwwrite(vortex->mmio, 0x2b008 + n * 0x30, coefs[i + 2]); + hwwrite(vortex->mmio, 0x2b00c + n * 0x30, coefs[i + 3]); + hwwrite(vortex->mmio, 0x2b010 + n * 0x30, coefs[i + 4]); + } else { + hwwrite(vortex->mmio, 0x2b008 + n * 0x30, sign_invert(coefs[2 + i])); + hwwrite(vortex->mmio, 0x2b00c + n * 0x30, sign_invert(coefs[3 + i])); + hwwrite(vortex->mmio, 0x2b010 + n * 0x30, sign_invert(coefs[4 + i])); + } + i += 5; + } +} + +static void vortex_EqHw_SetRightCoefs(vortex_t *vortex, const u16 coefs[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int i = 0, n /*esp2c */; + + for (n = 0; n < eqhw->this04; n++) { + hwwrite(vortex->mmio, 0x2b1e0 + n * 0x30, coefs[0 + i]); + hwwrite(vortex->mmio, 0x2b1e4 + n * 0x30, coefs[1 + i]); + + if (eqhw->this08 == 0) { + hwwrite(vortex->mmio, 0x2b1e8 + n * 0x30, coefs[2 + i]); + hwwrite(vortex->mmio, 0x2b1ec + n * 0x30, coefs[3 + i]); + hwwrite(vortex->mmio, 0x2b1f0 + n * 0x30, coefs[4 + i]); + } else { + hwwrite(vortex->mmio, 0x2b1e8 + n * 0x30, sign_invert(coefs[2 + i])); + hwwrite(vortex->mmio, 0x2b1ec + n * 0x30, sign_invert(coefs[3 + i])); + hwwrite(vortex->mmio, 0x2b1f0 + n * 0x30, sign_invert(coefs[4 + i])); + } + i += 5; + } + +} + +static void vortex_EqHw_SetLeftStates(vortex_t *vortex, const u16 a[], const u16 b[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int i = 0, ebx; + + hwwrite(vortex->mmio, 0x2b3fc, a[0]); + hwwrite(vortex->mmio, 0x2b400, a[1]); + + for (ebx = 0; ebx < eqhw->this04; ebx++) { + hwwrite(vortex->mmio, 0x2b014 + (i * 0xc), b[i]); + hwwrite(vortex->mmio, 0x2b018 + (i * 0xc), b[1 + i]); + hwwrite(vortex->mmio, 0x2b01c + (i * 0xc), b[2 + i]); + hwwrite(vortex->mmio, 0x2b020 + (i * 0xc), b[3 + i]); + i += 4; + } +} + +static void vortex_EqHw_SetRightStates(vortex_t *vortex, const u16 a[], const u16 b[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int i = 0, ebx; + + hwwrite(vortex->mmio, 0x2b404, a[0]); + hwwrite(vortex->mmio, 0x2b408, a[1]); + + for (ebx = 0; ebx < eqhw->this04; ebx++) { + hwwrite(vortex->mmio, 0x2b1f4 + (i * 0xc), b[i]); + hwwrite(vortex->mmio, 0x2b1f8 + (i * 0xc), b[1 + i]); + hwwrite(vortex->mmio, 0x2b1fc + (i * 0xc), b[2 + i]); + hwwrite(vortex->mmio, 0x2b200 + (i * 0xc), b[3 + i]); + i += 4; + } +} + +#if 0 +static void vortex_EqHw_GetTimeConsts(vortex_t * vortex, u16 * a, u16 * b) +{ + *a = hwread(vortex->mmio, 0x2b3c4); + *b = hwread(vortex->mmio, 0x2b3c8); +} + +static void vortex_EqHw_GetLeftCoefs(vortex_t * vortex, u16 a[]) +{ + +} + +static void vortex_EqHw_GetRightCoefs(vortex_t * vortex, u16 a[]) +{ + +} + +static void vortex_EqHw_GetLeftStates(vortex_t * vortex, u16 * a, u16 b[]) +{ + +} + +static void vortex_EqHw_GetRightStates(vortex_t * vortex, u16 * a, u16 b[]) +{ + +} + +#endif +/* Mix Gains */ +static void vortex_EqHw_SetBypassGain(vortex_t * vortex, u16 a, u16 b) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + if (eqhw->this08 == 0) { + hwwrite(vortex->mmio, 0x2b3d4, a); + hwwrite(vortex->mmio, 0x2b3ec, b); + } else { + hwwrite(vortex->mmio, 0x2b3d4, sign_invert(a)); + hwwrite(vortex->mmio, 0x2b3ec, sign_invert(b)); + } +} + +static void vortex_EqHw_SetA3DBypassGain(vortex_t * vortex, u16 a, u16 b) +{ + + hwwrite(vortex->mmio, 0x2b3e0, a); + hwwrite(vortex->mmio, 0x2b3f8, b); +} + +#if 0 +static void vortex_EqHw_SetCurrBypassGain(vortex_t * vortex, u16 a, u16 b) +{ + + hwwrite(vortex->mmio, 0x2b3d0, a); + hwwrite(vortex->mmio, 0x2b3e8, b); +} + +static void vortex_EqHw_SetCurrA3DBypassGain(vortex_t * vortex, u16 a, u16 b) +{ + + hwwrite(vortex->mmio, 0x2b3dc, a); + hwwrite(vortex->mmio, 0x2b3f4, b); +} + +#endif +static void +vortex_EqHw_SetLeftGainsSingleTarget(vortex_t * vortex, u16 index, u16 b) +{ + hwwrite(vortex->mmio, 0x2b02c + (index * 0x30), b); +} + +static void +vortex_EqHw_SetRightGainsSingleTarget(vortex_t * vortex, u16 index, u16 b) +{ + hwwrite(vortex->mmio, 0x2b20c + (index * 0x30), b); +} + +static void vortex_EqHw_SetLeftGainsTarget(vortex_t *vortex, const u16 a[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int ebx; + + for (ebx = 0; ebx < eqhw->this04; ebx++) { + hwwrite(vortex->mmio, 0x2b02c + ebx * 0x30, a[ebx]); + } +} + +static void vortex_EqHw_SetRightGainsTarget(vortex_t *vortex, const u16 a[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int ebx; + + for (ebx = 0; ebx < eqhw->this04; ebx++) { + hwwrite(vortex->mmio, 0x2b20c + ebx * 0x30, a[ebx]); + } +} + +static void vortex_EqHw_SetLeftGainsCurrent(vortex_t *vortex, const u16 a[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int ebx; + + for (ebx = 0; ebx < eqhw->this04; ebx++) { + hwwrite(vortex->mmio, 0x2b028 + ebx * 0x30, a[ebx]); + } +} + +static void vortex_EqHw_SetRightGainsCurrent(vortex_t *vortex, const u16 a[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int ebx; + + for (ebx = 0; ebx < eqhw->this04; ebx++) { + hwwrite(vortex->mmio, 0x2b208 + ebx * 0x30, a[ebx]); + } +} + +#if 0 +static void vortex_EqHw_GetLeftGainsTarget(vortex_t * vortex, u16 a[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int ebx = 0; + + if (eqhw->this04 < 0) + return; + + do { + a[ebx] = hwread(vortex->mmio, 0x2b02c + ebx * 0x30); + ebx++; + } + while (ebx < eqhw->this04); +} + +static void vortex_EqHw_GetRightGainsTarget(vortex_t * vortex, u16 a[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int ebx = 0; + + if (eqhw->this04 < 0) + return; + + do { + a[ebx] = hwread(vortex->mmio, 0x2b20c + ebx * 0x30); + ebx++; + } + while (ebx < eqhw->this04); +} + +static void vortex_EqHw_GetLeftGainsCurrent(vortex_t * vortex, u16 a[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int ebx = 0; + + if (eqhw->this04 < 0) + return; + + do { + a[ebx] = hwread(vortex->mmio, 0x2b028 + ebx * 0x30); + ebx++; + } + while (ebx < eqhw->this04); +} + +static void vortex_EqHw_GetRightGainsCurrent(vortex_t * vortex, u16 a[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int ebx = 0; + + if (eqhw->this04 < 0) + return; + + do { + a[ebx] = hwread(vortex->mmio, 0x2b208 + ebx * 0x30); + ebx++; + } + while (ebx < eqhw->this04); +} + +#endif +/* EQ band levels settings */ +static void vortex_EqHw_SetLevels(vortex_t *vortex, const u16 peaks[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int i; + + /* set left peaks */ + for (i = 0; i < eqhw->this04; i++) { + hwwrite(vortex->mmio, 0x2b024 + i * VORTEX_BAND_COEFF_SIZE, peaks[i]); + } + + hwwrite(vortex->mmio, 0x2b3cc, peaks[eqhw->this04]); + hwwrite(vortex->mmio, 0x2b3d8, peaks[eqhw->this04 + 1]); + + /* set right peaks */ + for (i = 0; i < eqhw->this04; i++) { + hwwrite(vortex->mmio, 0x2b204 + i * VORTEX_BAND_COEFF_SIZE, + peaks[i + (eqhw->this04 + 2)]); + } + + hwwrite(vortex->mmio, 0x2b3e4, peaks[2 + (eqhw->this04 * 2)]); + hwwrite(vortex->mmio, 0x2b3f0, peaks[3 + (eqhw->this04 * 2)]); +} + +#if 0 +static void vortex_EqHw_GetLevels(vortex_t * vortex, u16 a[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int ebx; + + if (eqhw->this04 < 0) + return; + + ebx = 0; + do { + a[ebx] = hwread(vortex->mmio, 0x2b024 + ebx * 0x30); + ebx++; + } + while (ebx < eqhw->this04); + + a[eqhw->this04] = hwread(vortex->mmio, 0x2b3cc); + a[eqhw->this04 + 1] = hwread(vortex->mmio, 0x2b3d8); + + ebx = 0; + do { + a[ebx + (eqhw->this04 + 2)] = + hwread(vortex->mmio, 0x2b204 + ebx * 0x30); + ebx++; + } + while (ebx < eqhw->this04); + + a[2 + (eqhw->this04 * 2)] = hwread(vortex->mmio, 0x2b3e4); + a[3 + (eqhw->this04 * 2)] = hwread(vortex->mmio, 0x2b3f0); +} + +#endif +/* Global Control */ +static void vortex_EqHw_SetControlReg(vortex_t * vortex, u32 reg) +{ + hwwrite(vortex->mmio, 0x2b440, reg); +} + +static void vortex_EqHw_SetSampleRate(vortex_t * vortex, u32 sr) +{ + hwwrite(vortex->mmio, 0x2b440, ((sr & 0x1f) << 3) | 0xb800); +} + +#if 0 +static void vortex_EqHw_GetControlReg(vortex_t * vortex, u32 *reg) +{ + *reg = hwread(vortex->mmio, 0x2b440); +} + +static void vortex_EqHw_GetSampleRate(vortex_t * vortex, u32 *sr) +{ + *sr = (hwread(vortex->mmio, 0x2b440) >> 3) & 0x1f; +} + +#endif +static void vortex_EqHw_Enable(vortex_t * vortex) +{ + hwwrite(vortex->mmio, VORTEX_EQ_CTRL, 0xf001); +} + +static void vortex_EqHw_Disable(vortex_t * vortex) +{ + hwwrite(vortex->mmio, VORTEX_EQ_CTRL, 0xf000); +} + +/* Reset (zero) buffers */ +static void vortex_EqHw_ZeroIO(vortex_t * vortex) +{ + int i; + for (i = 0; i < 0x8; i++) + hwwrite(vortex->mmio, VORTEX_EQ_DEST + (i << 2), 0x0); + for (i = 0; i < 0x4; i++) + hwwrite(vortex->mmio, VORTEX_EQ_SOURCE + (i << 2), 0x0); +} + +static void vortex_EqHw_ZeroA3DIO(vortex_t * vortex) +{ + int i; + for (i = 0; i < 0x4; i++) + hwwrite(vortex->mmio, VORTEX_EQ_DEST + (i << 2), 0x0); +} + +static void vortex_EqHw_ZeroState(vortex_t * vortex) +{ + + vortex_EqHw_SetControlReg(vortex, 0); + vortex_EqHw_ZeroIO(vortex); + hwwrite(vortex->mmio, 0x2b3c0, 0); + + vortex_EqHw_SetTimeConsts(vortex, 0, 0); + + vortex_EqHw_SetLeftCoefs(vortex, asEqCoefsZeros); + vortex_EqHw_SetRightCoefs(vortex, asEqCoefsZeros); + + vortex_EqHw_SetLeftGainsCurrent(vortex, eq_gains_zero); + vortex_EqHw_SetRightGainsCurrent(vortex, eq_gains_zero); + vortex_EqHw_SetLeftGainsTarget(vortex, eq_gains_zero); + vortex_EqHw_SetRightGainsTarget(vortex, eq_gains_zero); + + vortex_EqHw_SetBypassGain(vortex, 0, 0); + //vortex_EqHw_SetCurrBypassGain(vortex, 0, 0); + vortex_EqHw_SetA3DBypassGain(vortex, 0, 0); + //vortex_EqHw_SetCurrA3DBypassGain(vortex, 0, 0); + vortex_EqHw_SetLeftStates(vortex, eq_states_zero, asEqOutStateZeros); + vortex_EqHw_SetRightStates(vortex, eq_states_zero, asEqOutStateZeros); + vortex_EqHw_SetLevels(vortex, (u16 *) eq_levels); +} + +/* Program coeficients as pass through */ +static void vortex_EqHw_ProgramPipe(vortex_t * vortex) +{ + vortex_EqHw_SetTimeConsts(vortex, 0, 0); + + vortex_EqHw_SetLeftCoefs(vortex, asEqCoefsPipes); + vortex_EqHw_SetRightCoefs(vortex, asEqCoefsPipes); + + vortex_EqHw_SetLeftGainsCurrent(vortex, eq_gains_current); + vortex_EqHw_SetRightGainsCurrent(vortex, eq_gains_current); + vortex_EqHw_SetLeftGainsTarget(vortex, eq_gains_current); + vortex_EqHw_SetRightGainsTarget(vortex, eq_gains_current); +} + +/* Program EQ block as 10 band Equalizer */ +static void +vortex_EqHw_Program10Band(vortex_t * vortex, auxxEqCoeffSet_t * coefset) +{ + + vortex_EqHw_SetTimeConsts(vortex, 0xc, 0x7fe0); + + vortex_EqHw_SetLeftCoefs(vortex, coefset->LeftCoefs); + vortex_EqHw_SetRightCoefs(vortex, coefset->RightCoefs); + + vortex_EqHw_SetLeftGainsCurrent(vortex, coefset->LeftGains); + + vortex_EqHw_SetRightGainsTarget(vortex, coefset->RightGains); + vortex_EqHw_SetLeftGainsTarget(vortex, coefset->LeftGains); + + vortex_EqHw_SetRightGainsCurrent(vortex, coefset->RightGains); +} + +/* Read all EQ peaks. (think VU meter) */ +static void vortex_EqHw_GetTenBandLevels(vortex_t * vortex, u16 peaks[]) +{ + eqhw_t *eqhw = &(vortex->eq.this04); + int i; + + if (eqhw->this04 <= 0) + return; + + for (i = 0; i < eqhw->this04; i++) + peaks[i] = hwread(vortex->mmio, 0x2B024 + i * 0x30); + for (i = 0; i < eqhw->this04; i++) + peaks[i + eqhw->this04] = + hwread(vortex->mmio, 0x2B204 + i * 0x30); +} + +/* CEqlzr.s */ + +static int vortex_Eqlzr_GetLeftGain(vortex_t * vortex, u16 index, u16 * gain) +{ + eqlzr_t *eq = &(vortex->eq); + + if (eq->this28) { + *gain = eq->this130[index]; + return 0; + } + return 1; +} + +static void vortex_Eqlzr_SetLeftGain(vortex_t * vortex, u16 index, u16 gain) +{ + eqlzr_t *eq = &(vortex->eq); + + if (eq->this28 == 0) + return; + + eq->this130[index] = gain; + if (eq->this54) + return; + + vortex_EqHw_SetLeftGainsSingleTarget(vortex, index, gain); +} + +static int vortex_Eqlzr_GetRightGain(vortex_t * vortex, u16 index, u16 * gain) +{ + eqlzr_t *eq = &(vortex->eq); + + if (eq->this28) { + *gain = eq->this130[index + eq->this10]; + return 0; + } + return 1; +} + +static void vortex_Eqlzr_SetRightGain(vortex_t * vortex, u16 index, u16 gain) +{ + eqlzr_t *eq = &(vortex->eq); + + if (eq->this28 == 0) + return; + + eq->this130[index + eq->this10] = gain; + if (eq->this54) + return; + + vortex_EqHw_SetRightGainsSingleTarget(vortex, index, gain); +} + +#if 0 +static int +vortex_Eqlzr_GetAllBands(vortex_t * vortex, u16 * gains, s32 *cnt) +{ + eqlzr_t *eq = &(vortex->eq); + int si = 0; + + if (eq->this10 == 0) + return 1; + + { + if (vortex_Eqlzr_GetLeftGain(vortex, si, &gains[si])) + return 1; + if (vortex_Eqlzr_GetRightGain + (vortex, si, &gains[si + eq->this10])) + return 1; + si++; + } + while (eq->this10 > si) ; + *cnt = si * 2; + return 0; +} +#endif +static int vortex_Eqlzr_SetAllBandsFromActiveCoeffSet(vortex_t * vortex) +{ + eqlzr_t *eq = &(vortex->eq); + + vortex_EqHw_SetLeftGainsTarget(vortex, eq->this130); + vortex_EqHw_SetRightGainsTarget(vortex, eq->this130 + eq->this10); + + return 0; +} + +static int +vortex_Eqlzr_SetAllBands(vortex_t *vortex, const u16 gains[], s32 count) +{ + eqlzr_t *eq = &(vortex->eq); + int i; + + if (((eq->this10) * 2 != count) || (eq->this28 == 0)) + return 1; + + for (i = 0; i < count; i++) { + eq->this130[i] = gains[i]; + } + + if (eq->this54) + return 0; + return vortex_Eqlzr_SetAllBandsFromActiveCoeffSet(vortex); +} + +static void +vortex_Eqlzr_SetA3dBypassGain(vortex_t * vortex, u32 a, u32 b) +{ + eqlzr_t *eq = &(vortex->eq); + u32 eax, ebx; + + eq->this58 = a; + eq->this5c = b; + if (eq->this54) + eax = eq->this0e; + else + eax = eq->this0a; + ebx = (eax * eq->this58) >> 0x10; + eax = (eax * eq->this5c) >> 0x10; + vortex_EqHw_SetA3DBypassGain(vortex, ebx, eax); +} + +static void vortex_Eqlzr_ProgramA3dBypassGain(vortex_t * vortex) +{ + eqlzr_t *eq = &(vortex->eq); + u32 eax, ebx; + + if (eq->this54) + eax = eq->this0e; + else + eax = eq->this0a; + ebx = (eax * eq->this58) >> 0x10; + eax = (eax * eq->this5c) >> 0x10; + vortex_EqHw_SetA3DBypassGain(vortex, ebx, eax); +} + +static void vortex_Eqlzr_ShutDownA3d(vortex_t * vortex) +{ + if (vortex != NULL) + vortex_EqHw_ZeroA3DIO(vortex); +} + +static void vortex_Eqlzr_SetBypass(vortex_t * vortex, u32 bp) +{ + eqlzr_t *eq = &(vortex->eq); + + if ((eq->this28) && (bp == 0)) { + /* EQ enabled */ + vortex_Eqlzr_SetAllBandsFromActiveCoeffSet(vortex); + vortex_EqHw_SetBypassGain(vortex, eq->this08, eq->this08); + } else { + /* EQ disabled. */ + vortex_EqHw_SetLeftGainsTarget(vortex, eq->this14_array); + vortex_EqHw_SetRightGainsTarget(vortex, eq->this14_array); + vortex_EqHw_SetBypassGain(vortex, eq->this0c, eq->this0c); + } + vortex_Eqlzr_ProgramA3dBypassGain(vortex); +} + +static void vortex_Eqlzr_ReadAndSetActiveCoefSet(vortex_t * vortex) +{ + eqlzr_t *eq = &(vortex->eq); + + /* Set EQ BiQuad filter coeficients */ + memcpy(&(eq->coefset), &asEqCoefsNormal, sizeof(auxxEqCoeffSet_t)); + /* Set EQ Band gain levels and dump into hardware registers. */ + vortex_Eqlzr_SetAllBands(vortex, eq_gains_normal, eq->this10 * 2); +} + +static int vortex_Eqlzr_GetAllPeaks(vortex_t * vortex, u16 * peaks, int *count) +{ + eqlzr_t *eq = &(vortex->eq); + + if (eq->this10 == 0) + return 1; + *count = eq->this10 * 2; + vortex_EqHw_GetTenBandLevels(vortex, peaks); + return 0; +} + +#if 0 +static auxxEqCoeffSet_t *vortex_Eqlzr_GetActiveCoefSet(vortex_t * vortex) +{ + eqlzr_t *eq = &(vortex->eq); + + return (&(eq->coefset)); +} +#endif +static void vortex_Eqlzr_init(vortex_t * vortex) +{ + eqlzr_t *eq = &(vortex->eq); + + /* Object constructor */ + //eq->this04 = 0; + eq->this08 = 0; /* Bypass gain with EQ in use. */ + eq->this0a = 0x5999; + eq->this0c = 0x5999; /* Bypass gain with EQ disabled. */ + eq->this0e = 0x5999; + + eq->this10 = 0xa; /* 10 eq frequency bands. */ + eq->this04.this04 = eq->this10; + eq->this28 = 0x1; /* if 1 => Allow read access to this130 (gains) */ + eq->this54 = 0x0; /* if 1 => Dont Allow access to hardware (gains) */ + eq->this58 = 0xffff; + eq->this5c = 0xffff; + + /* Set gains. */ + memset(eq->this14_array, 0, sizeof(eq->this14_array)); + + /* Actual init. */ + vortex_EqHw_ZeroState(vortex); + vortex_EqHw_SetSampleRate(vortex, 0x11); + vortex_Eqlzr_ReadAndSetActiveCoefSet(vortex); + + vortex_EqHw_Program10Band(vortex, &(eq->coefset)); + vortex_Eqlzr_SetBypass(vortex, eq->this54); + vortex_Eqlzr_SetA3dBypassGain(vortex, 0, 0); + vortex_EqHw_Enable(vortex); +} + +static void vortex_Eqlzr_shutdown(vortex_t * vortex) +{ + vortex_Eqlzr_ShutDownA3d(vortex); + vortex_EqHw_ProgramPipe(vortex); + vortex_EqHw_Disable(vortex); +} + +/* ALSA interface */ + +/* Control interface */ +#define snd_vortex_eqtoggle_info snd_ctl_boolean_mono_info + +static int +snd_vortex_eqtoggle_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + eqlzr_t *eq = &(vortex->eq); + //int i = kcontrol->private_value; + + ucontrol->value.integer.value[0] = eq->this54 ? 0 : 1; + + return 0; +} + +static int +snd_vortex_eqtoggle_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + eqlzr_t *eq = &(vortex->eq); + //int i = kcontrol->private_value; + + eq->this54 = ucontrol->value.integer.value[0] ? 0 : 1; + vortex_Eqlzr_SetBypass(vortex, eq->this54); + + return 1; /* Allways changes */ +} + +static const struct snd_kcontrol_new vortex_eqtoggle_kcontrol = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "EQ Enable", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = snd_vortex_eqtoggle_info, + .get = snd_vortex_eqtoggle_get, + .put = snd_vortex_eqtoggle_put +}; + +static int +snd_vortex_eq_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 = 0x0000; + uinfo->value.integer.max = 0x7fff; + return 0; +} + +static int +snd_vortex_eq_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + int i = kcontrol->private_value; + u16 gainL = 0, gainR = 0; + + vortex_Eqlzr_GetLeftGain(vortex, i, &gainL); + vortex_Eqlzr_GetRightGain(vortex, i, &gainR); + ucontrol->value.integer.value[0] = gainL; + ucontrol->value.integer.value[1] = gainR; + return 0; +} + +static int +snd_vortex_eq_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + int changed = 0, i = kcontrol->private_value; + u16 gainL = 0, gainR = 0; + + vortex_Eqlzr_GetLeftGain(vortex, i, &gainL); + vortex_Eqlzr_GetRightGain(vortex, i, &gainR); + + if (gainL != ucontrol->value.integer.value[0]) { + vortex_Eqlzr_SetLeftGain(vortex, i, + ucontrol->value.integer.value[0]); + changed = 1; + } + if (gainR != ucontrol->value.integer.value[1]) { + vortex_Eqlzr_SetRightGain(vortex, i, + ucontrol->value.integer.value[1]); + changed = 1; + } + return changed; +} + +static const struct snd_kcontrol_new vortex_eq_kcontrol = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = " .", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = snd_vortex_eq_info, + .get = snd_vortex_eq_get, + .put = snd_vortex_eq_put +}; + +static int +snd_vortex_peaks_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 20; + uinfo->value.integer.min = 0x0000; + uinfo->value.integer.max = 0x7fff; + return 0; +} + +static int +snd_vortex_peaks_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + int i, count = 0; + u16 peaks[20]; + + vortex_Eqlzr_GetAllPeaks(vortex, peaks, &count); + if (count != 20) { + dev_err(vortex->card->dev, + "peak count error 20 != %d\n", count); + return -1; + } + for (i = 0; i < 20; i++) + ucontrol->value.integer.value[i] = peaks[i]; + + return 0; +} + +static const struct snd_kcontrol_new vortex_levels_kcontrol = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "EQ Peaks", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_vortex_peaks_info, + .get = snd_vortex_peaks_get, +}; + +/* EQ band gain labels. */ +static const char * const EqBandLabels[10] = { + "EQ0 31Hz\0", + "EQ1 63Hz\0", + "EQ2 125Hz\0", + "EQ3 250Hz\0", + "EQ4 500Hz\0", + "EQ5 1KHz\0", + "EQ6 2KHz\0", + "EQ7 4KHz\0", + "EQ8 8KHz\0", + "EQ9 16KHz\0", +}; + +/* ALSA driver entry points. Init and exit. */ +static int vortex_eq_init(vortex_t *vortex) +{ + struct snd_kcontrol *kcontrol; + int err, i; + + vortex_Eqlzr_init(vortex); + + kcontrol = snd_ctl_new1(&vortex_eqtoggle_kcontrol, vortex); + if (!kcontrol) + return -ENOMEM; + kcontrol->private_value = 0; + err = snd_ctl_add(vortex->card, kcontrol); + if (err < 0) + return err; + + /* EQ gain controls */ + for (i = 0; i < 10; i++) { + kcontrol = snd_ctl_new1(&vortex_eq_kcontrol, vortex); + if (!kcontrol) + return -ENOMEM; + snprintf(kcontrol->id.name, sizeof(kcontrol->id.name), + "%s Playback Volume", EqBandLabels[i]); + kcontrol->private_value = i; + err = snd_ctl_add(vortex->card, kcontrol); + if (err < 0) + return err; + //vortex->eqctrl[i] = kcontrol; + } + /* EQ band levels */ + kcontrol = snd_ctl_new1(&vortex_levels_kcontrol, vortex); + if (!kcontrol) + return -ENOMEM; + err = snd_ctl_add(vortex->card, kcontrol); + if (err < 0) + return err; + + return 0; +} + +static int vortex_eq_free(vortex_t * vortex) +{ + /* + //FIXME: segfault because vortex->eqctrl[i] == 4 + int i; + for (i=0; i<10; i++) { + if (vortex->eqctrl[i]) + snd_ctl_remove(vortex->card, vortex->eqctrl[i]); + } + */ + vortex_Eqlzr_shutdown(vortex); + return 0; +} + +/* End */ diff --git a/sound/pci/au88x0/au88x0_eq.h b/sound/pci/au88x0/au88x0_eq.h new file mode 100644 index 000000000..797cdae1d --- /dev/null +++ b/sound/pci/au88x0/au88x0_eq.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef AU88X0_EQ_H +#define AU88X0_EQ_H + +/*************************************************************************** + * au88x0_eq.h + * + * Definitions and constant data for the Aureal Hardware EQ. + * + * Sun Jun 8 18:23:38 2003 + * Author: Manuel Jander (mjander@users.sourceforge.net) + ****************************************************************************/ + +typedef struct { + u16 LeftCoefs[50]; //0x4 + u16 RightCoefs[50]; // 0x68 + u16 LeftGains[10]; //0xd0 + u16 RightGains[10]; //0xe4 +} auxxEqCoeffSet_t; + +typedef struct { + s32 this04; /* How many filters for each side (default = 10) */ + s32 this08; /* inited to cero. Stereo flag? */ +} eqhw_t; + +typedef struct { + eqhw_t this04; /* CHwEq */ + u16 this08; /* Bad codec flag ? SetBypassGain: bypass gain */ + u16 this0a; + u16 this0c; /* SetBypassGain: bypass gain when this28 is not set. */ + u16 this0e; + + s32 this10; /* How many gains are used for each side (right or left). */ + u16 this14_array[10]; /* SetLeftGainsTarget: Left (and right?) EQ gains */ + s32 this28; /* flag related to EQ enabled or not. Gang flag ? */ + s32 this54; /* SetBypass */ + s32 this58; + s32 this5c; + /*0x60 */ auxxEqCoeffSet_t coefset; + /* 50 u16 word each channel. */ + u16 this130[20]; /* Left and Right gains */ +} eqlzr_t; + +#endif diff --git a/sound/pci/au88x0/au88x0_eqdata.c b/sound/pci/au88x0/au88x0_eqdata.c new file mode 100644 index 000000000..a74f266f0 --- /dev/null +++ b/sound/pci/au88x0/au88x0_eqdata.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Data structs */ + +static const u16 asEqCoefsZeros[50] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const u16 asEqCoefsPipes[64] = { + 0x0000, 0x0000, + 0x0000, 0x0666, 0x0000, 0x0000, 0x0666, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0666, 0x0000, 0x0000, 0x0666, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0666, 0x0000, 0x0000, 0x0666, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0666, 0x0000, 0x0000, 0x0666, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0666, 0x0000, 0x0000, 0x066a, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000 +}; + +/* More coef sets can be found in the win2k "inf" file. */ +static const auxxEqCoeffSet_t asEqCoefsNormal = { + .LeftCoefs = { + 0x7e60, 0xc19e, 0x0001, 0x0002, 0x0001, + 0x7fa0, 0xc05f, 0x004f, 0x0000, 0xffb1, + 0x7f3f, 0xc0bc, 0x00c2, 0x0000, 0xff3e, + 0x7e78, 0xc177, 0x011f, 0x0000, 0xfee1, + 0x7cd6, 0xc2e5, 0x025c, 0x0000, 0xfda4, + 0x7949, 0xc5aa, 0x0467, 0x0000, 0xfb99, + 0x7120, 0xcadf, 0x0864, 0x0000, 0xf79c, + 0x5d33, 0xd430, 0x0f7e, 0x0000, 0xf082, + 0x2beb, 0xe3ca, 0x1bd3, 0x0000, 0xe42d, + 0xd740, 0xf01d, 0x2ac5, 0x0000, 0xd53b}, + + .RightCoefs = { + 0x7e60, 0xc19e, 0x0001, 0x0002, 0x0001, + 0x7fa0, 0xc05f, 0x004f, 0x0000, 0xffb1, + 0x7f3f, 0xc0bc, 0x00c2, 0x0000, 0xff3e, + 0x7e78, 0xc177, 0x011f, 0x0000, 0xfee1, + 0x7cd6, 0xc2e5, 0x025c, 0x0000, 0xfda4, + 0x7949, 0xc5aa, 0x0467, 0x0000, 0xfb99, + 0x7120, 0xcadf, 0x0864, 0x0000, 0xf79c, + 0x5d33, 0xd430, 0x0f7e, 0x0000, 0xf082, + 0x2beb, 0xe3ca, 0x1bd3, 0x0000, 0xe42d, + 0xd740, 0xf01d, 0x2ac5, 0x0000, 0xd53b}, + + .LeftGains = { + 0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96, + 0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96}, + .RightGains = { + 0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96, + 0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96} +}; + +static const u16 eq_gains_normal[20] = { + 0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96, + 0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96, + 0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96, + 0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96 +}; + +/* _rodatab60 */ +static const u16 eq_gains_zero[10] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +/* _rodatab7c: ProgramPipe */ +static const u16 eq_gains_current[12] = { + 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, + 0x7fff, + 0x7fff, 0x7fff, 0x7fff +}; + +/* _rodatab78 */ +static const u16 eq_states_zero[2] = { 0x0000, 0x0000 }; + +static const u16 asEqOutStateZeros[48] = { + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000 +}; + +/*_rodataba0:*/ +static const u16 eq_levels[64] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; diff --git a/sound/pci/au88x0/au88x0_game.c b/sound/pci/au88x0/au88x0_game.c new file mode 100644 index 000000000..51c154e34 --- /dev/null +++ b/sound/pci/au88x0/au88x0_game.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Manuel Jander. + * + * Based on the work of: + * Vojtech Pavlik + * Raymond Ingles + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to , or by paper mail: + * Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00 Czech Republic + * + * Based 90% on Vojtech Pavlik pcigame driver. + * Merged and modified by Manuel Jander, for the OpenVortex + * driver. (email: mjander@embedded.cl). + */ + +#include +#include +#include +#include +#include "au88x0.h" +#include +#include + +#if IS_REACHABLE(CONFIG_GAMEPORT) + +#define VORTEX_GAME_DWAIT 20 /* 20 ms */ + +static unsigned char vortex_game_read(struct gameport *gameport) +{ + vortex_t *vortex = gameport_get_port_data(gameport); + return hwread(vortex->mmio, VORTEX_GAME_LEGACY); +} + +static void vortex_game_trigger(struct gameport *gameport) +{ + vortex_t *vortex = gameport_get_port_data(gameport); + hwwrite(vortex->mmio, VORTEX_GAME_LEGACY, 0xff); +} + +static int +vortex_game_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + vortex_t *vortex = gameport_get_port_data(gameport); + int i; + + *buttons = (~hwread(vortex->mmio, VORTEX_GAME_LEGACY) >> 4) & 0xf; + + for (i = 0; i < 4; i++) { + axes[i] = + hwread(vortex->mmio, VORTEX_GAME_AXIS + (i * AXIS_SIZE)); + if (axes[i] == AXIS_RANGE) + axes[i] = -1; + } + return 0; +} + +static int vortex_game_open(struct gameport *gameport, int mode) +{ + vortex_t *vortex = gameport_get_port_data(gameport); + + switch (mode) { + case GAMEPORT_MODE_COOKED: + hwwrite(vortex->mmio, VORTEX_CTRL2, + hwread(vortex->mmio, + VORTEX_CTRL2) | CTRL2_GAME_ADCMODE); + msleep(VORTEX_GAME_DWAIT); + return 0; + case GAMEPORT_MODE_RAW: + hwwrite(vortex->mmio, VORTEX_CTRL2, + hwread(vortex->mmio, + VORTEX_CTRL2) & ~CTRL2_GAME_ADCMODE); + return 0; + default: + return -1; + } + + return 0; +} + +static int vortex_gameport_register(vortex_t *vortex) +{ + struct gameport *gp; + + vortex->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(vortex->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "AU88x0 Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(vortex->pci_dev)); + gameport_set_dev_parent(gp, &vortex->pci_dev->dev); + + gp->read = vortex_game_read; + gp->trigger = vortex_game_trigger; + gp->cooked_read = vortex_game_cooked_read; + gp->open = vortex_game_open; + + gameport_set_port_data(gp, vortex); + gp->fuzz = 64; + + gameport_register_port(gp); + + return 0; +} + +static void vortex_gameport_unregister(vortex_t * vortex) +{ + if (vortex->gameport) { + gameport_unregister_port(vortex->gameport); + vortex->gameport = NULL; + } +} + +#else +static inline int vortex_gameport_register(vortex_t * vortex) { return -ENOSYS; } +static inline void vortex_gameport_unregister(vortex_t * vortex) { } +#endif diff --git a/sound/pci/au88x0/au88x0_mixer.c b/sound/pci/au88x0/au88x0_mixer.c new file mode 100644 index 000000000..00781a7fd --- /dev/null +++ b/sound/pci/au88x0/au88x0_mixer.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Vortex Mixer support. + * + * There is much more than just the AC97 mixer... + * + */ + +#include +#include +#include +#include "au88x0.h" + +static int remove_ctl(struct snd_card *card, const char *name) +{ + struct snd_ctl_elem_id id; + memset(&id, 0, sizeof(id)); + strscpy(id.name, name); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + return snd_ctl_remove_id(card, &id); +} + +static int snd_vortex_mixer(vortex_t *vortex) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = vortex_codec_write, + .read = vortex_codec_read, + }; + + err = snd_ac97_bus(vortex->card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + memset(&ac97, 0, sizeof(ac97)); + // Initialize AC97 codec stuff. + ac97.private_data = vortex; + ac97.scaps = AC97_SCAP_NO_SPDIF; + err = snd_ac97_mixer(pbus, &ac97, &vortex->codec); + vortex->isquad = ((vortex->codec == NULL) ? 0 : (vortex->codec->ext_id&0x80)); + remove_ctl(vortex->card, "Master Mono Playback Volume"); + remove_ctl(vortex->card, "Master Mono Playback Switch"); + return err; +} diff --git a/sound/pci/au88x0/au88x0_mpu401.c b/sound/pci/au88x0/au88x0_mpu401.c new file mode 100644 index 000000000..164f6b703 --- /dev/null +++ b/sound/pci/au88x0/au88x0_mpu401.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of MPU-401 in UART mode + * + * Modified for the Aureal Vortex based Soundcards + * by Manuel Jander (mjande@embedded.cl). + */ + +#include +#include +#include +#include +#include "au88x0.h" + +/* Check for mpu401 mmio support. */ +/* MPU401 legacy support is only provided as a emergency fallback * + * for older versions of ALSA. Its usage is strongly discouraged. */ +#ifndef MPU401_HW_AUREAL +#define VORTEX_MPU401_LEGACY +#endif + +/* Vortex MPU401 defines. */ +#define MIDI_CLOCK_DIV 0x61 +/* Standart MPU401 defines. */ +#define MPU401_RESET 0xff +#define MPU401_ENTER_UART 0x3f +#define MPU401_ACK 0xfe + +static int snd_vortex_midi(vortex_t *vortex) +{ + struct snd_rawmidi *rmidi; + int temp, mode; + struct snd_mpu401 *mpu; + unsigned long port; + +#ifdef VORTEX_MPU401_LEGACY + /* EnableHardCodedMPU401Port() */ + /* Enable Legacy MIDI Interface port. */ + port = (0x03 << 5); /* FIXME: static address. 0x330 */ + temp = + (hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_PORT) | + CTRL_MIDI_EN | port; + hwwrite(vortex->mmio, VORTEX_CTRL, temp); +#else + /* Disable Legacy MIDI Interface port. */ + temp = + (hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_PORT) & + ~CTRL_MIDI_EN; + hwwrite(vortex->mmio, VORTEX_CTRL, temp); +#endif + /* Mpu401UartInit() */ + mode = 1; + temp = hwread(vortex->mmio, VORTEX_CTRL2) & 0xffff00cf; + temp |= (MIDI_CLOCK_DIV << 8) | ((mode >> 24) & 0xff) << 4; + hwwrite(vortex->mmio, VORTEX_CTRL2, temp); + hwwrite(vortex->mmio, VORTEX_MIDI_CMD, MPU401_RESET); + + /* Check if anything is OK. */ + temp = hwread(vortex->mmio, VORTEX_MIDI_DATA); + if (temp != MPU401_ACK /*0xfe */ ) { + dev_err(vortex->card->dev, "midi port doesn't acknowledge!\n"); + return -ENODEV; + } + /* Enable MPU401 interrupts. */ + hwwrite(vortex->mmio, VORTEX_IRQ_CTRL, + hwread(vortex->mmio, VORTEX_IRQ_CTRL) | IRQ_MIDI); + + /* Create MPU401 instance. */ +#ifdef VORTEX_MPU401_LEGACY + temp = snd_mpu401_uart_new(vortex->card, 0, MPU401_HW_MPU401, 0x330, + MPU401_INFO_IRQ_HOOK, -1, &rmidi); + if (temp) { + hwwrite(vortex->mmio, VORTEX_CTRL, + (hwread(vortex->mmio, VORTEX_CTRL) & + ~CTRL_MIDI_PORT) & ~CTRL_MIDI_EN); + return temp; + } +#else + port = (unsigned long)(vortex->mmio + VORTEX_MIDI_DATA); + temp = snd_mpu401_uart_new(vortex->card, 0, MPU401_HW_AUREAL, port, + MPU401_INFO_INTEGRATED | MPU401_INFO_MMIO | + MPU401_INFO_IRQ_HOOK, -1, &rmidi); + if (temp) { + hwwrite(vortex->mmio, VORTEX_CTRL, + (hwread(vortex->mmio, VORTEX_CTRL) & + ~CTRL_MIDI_PORT) & ~CTRL_MIDI_EN); + return temp; + } + mpu = rmidi->private_data; + mpu->cport = (unsigned long)(vortex->mmio + VORTEX_MIDI_CMD); +#endif + /* Overwrite MIDI name */ + snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI %d", CARD_NAME_SHORT , vortex->card->number); + + vortex->rmidi = rmidi; + return 0; +} diff --git a/sound/pci/au88x0/au88x0_pcm.c b/sound/pci/au88x0/au88x0_pcm.c new file mode 100644 index 000000000..546f71220 --- /dev/null +++ b/sound/pci/au88x0/au88x0_pcm.c @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* + * Vortex PCM ALSA driver. + * + * Supports ADB and WT DMA. Unfortunately, WT channels do not run yet. + * It remains stuck,and DMA transfers do not happen. + */ +#include +#include +#include +#include +#include +#include "au88x0.h" + +#define VORTEX_PCM_TYPE(x) (x->name[40]) + +/* hardware definition */ +static const struct snd_pcm_hardware snd_vortex_playback_hw_adb = { + .info = + (SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */ + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x10000, + .period_bytes_min = 0x20, + .period_bytes_max = 0x1000, + .periods_min = 2, + .periods_max = 1024, +}; + +#ifndef CHIP_AU8820 +static const struct snd_pcm_hardware snd_vortex_playback_hw_a3d = { + .info = + (SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */ + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 0x10000, + .period_bytes_min = 0x100, + .period_bytes_max = 0x1000, + .periods_min = 2, + .periods_max = 64, +}; +#endif +static const struct snd_pcm_hardware snd_vortex_playback_hw_spdif = { + .info = + (SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */ + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE | SNDRV_PCM_FMTBIT_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW, + .rates = + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x10000, + .period_bytes_min = 0x100, + .period_bytes_max = 0x1000, + .periods_min = 2, + .periods_max = 64, +}; + +#ifndef CHIP_AU8810 +static const struct snd_pcm_hardware snd_vortex_playback_hw_wt = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS, // SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x10000, + .period_bytes_min = 0x0400, + .period_bytes_max = 0x1000, + .periods_min = 2, + .periods_max = 64, +}; +#endif +#ifdef CHIP_AU8830 +static const unsigned int au8830_channels[3] = { + 1, 2, 4, +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_au8830_channels = { + .count = ARRAY_SIZE(au8830_channels), + .list = au8830_channels, + .mask = 0, +}; +#endif + +static void vortex_notify_pcm_vol_change(struct snd_card *card, + struct snd_kcontrol *kctl, int activate) +{ + if (activate) + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + else + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &(kctl->id)); +} + +/* open callback */ +static int snd_vortex_pcm_open(struct snd_pcm_substream *substream) +{ + vortex_t *vortex = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + /* Force equal size periods */ + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + /* Avoid PAGE_SIZE boundary to fall inside of a period. */ + err = snd_pcm_hw_constraint_pow2(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES); + if (err < 0) + return err; + + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 64); + + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) { +#ifndef CHIP_AU8820 + if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_A3D) { + runtime->hw = snd_vortex_playback_hw_a3d; + } +#endif + if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_SPDIF) { + runtime->hw = snd_vortex_playback_hw_spdif; + switch (vortex->spdif_sr) { + case 32000: + runtime->hw.rates = SNDRV_PCM_RATE_32000; + break; + case 44100: + runtime->hw.rates = SNDRV_PCM_RATE_44100; + break; + case 48000: + runtime->hw.rates = SNDRV_PCM_RATE_48000; + break; + } + } + if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB + || VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_I2S) + runtime->hw = snd_vortex_playback_hw_adb; +#ifdef CHIP_AU8830 + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + VORTEX_IS_QUAD(vortex) && + VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB) { + runtime->hw.channels_max = 4; + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_constraints_au8830_channels); + } +#endif + substream->runtime->private_data = NULL; + } +#ifndef CHIP_AU8810 + else { + runtime->hw = snd_vortex_playback_hw_wt; + substream->runtime->private_data = NULL; + } +#endif + return 0; +} + +/* close callback */ +static int snd_vortex_pcm_close(struct snd_pcm_substream *substream) +{ + //vortex_t *chip = snd_pcm_substream_chip(substream); + stream_t *stream = (stream_t *) substream->runtime->private_data; + + // the hardware-specific codes will be here + if (stream != NULL) { + stream->substream = NULL; + stream->nr_ch = 0; + } + substream->runtime->private_data = NULL; + return 0; +} + +/* hw_params callback */ +static int +snd_vortex_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + vortex_t *chip = snd_pcm_substream_chip(substream); + stream_t *stream = (stream_t *) (substream->runtime->private_data); + + /* + pr_info( "Vortex: periods %d, period_bytes %d, channels = %d\n", params_periods(hw_params), + params_period_bytes(hw_params), params_channels(hw_params)); + */ + spin_lock_irq(&chip->lock); + // Make audio routes and config buffer DMA. + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) { + int dma, type = VORTEX_PCM_TYPE(substream->pcm); + /* Dealloc any routes. */ + if (stream != NULL) + vortex_adb_allocroute(chip, stream->dma, + stream->nr_ch, stream->dir, + stream->type, + substream->number); + /* Alloc routes. */ + dma = + vortex_adb_allocroute(chip, -1, + params_channels(hw_params), + substream->stream, type, + substream->number); + if (dma < 0) { + spin_unlock_irq(&chip->lock); + return dma; + } + stream = substream->runtime->private_data = &chip->dma_adb[dma]; + stream->substream = substream; + /* Setup Buffers. */ + vortex_adbdma_setbuffers(chip, dma, + params_period_bytes(hw_params), + params_periods(hw_params)); + if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB) { + chip->pcm_vol[substream->number].active = 1; + vortex_notify_pcm_vol_change(chip->card, + chip->pcm_vol[substream->number].kctl, 1); + } + } +#ifndef CHIP_AU8810 + else { + /* if (stream != NULL) + vortex_wt_allocroute(chip, substream->number, 0); */ + vortex_wt_allocroute(chip, substream->number, + params_channels(hw_params)); + stream = substream->runtime->private_data = + &chip->dma_wt[substream->number]; + stream->dma = substream->number; + stream->substream = substream; + vortex_wtdma_setbuffers(chip, substream->number, + params_period_bytes(hw_params), + params_periods(hw_params)); + } +#endif + spin_unlock_irq(&chip->lock); + return 0; +} + +/* hw_free callback */ +static int snd_vortex_pcm_hw_free(struct snd_pcm_substream *substream) +{ + vortex_t *chip = snd_pcm_substream_chip(substream); + stream_t *stream = (stream_t *) (substream->runtime->private_data); + + spin_lock_irq(&chip->lock); + // Delete audio routes. + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) { + if (stream != NULL) { + if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB) { + chip->pcm_vol[substream->number].active = 0; + vortex_notify_pcm_vol_change(chip->card, + chip->pcm_vol[substream->number].kctl, + 0); + } + vortex_adb_allocroute(chip, stream->dma, + stream->nr_ch, stream->dir, + stream->type, + substream->number); + } + } +#ifndef CHIP_AU8810 + else { + if (stream != NULL) + vortex_wt_allocroute(chip, stream->dma, 0); + } +#endif + substream->runtime->private_data = NULL; + spin_unlock_irq(&chip->lock); + + return 0; +} + +/* prepare callback */ +static int snd_vortex_pcm_prepare(struct snd_pcm_substream *substream) +{ + vortex_t *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + stream_t *stream = (stream_t *) substream->runtime->private_data; + int dma = stream->dma, fmt, dir; + + // set up the hardware with the current configuration. + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = 1; + else + dir = 0; + fmt = vortex_alsafmt_aspfmt(runtime->format, chip); + spin_lock_irq(&chip->lock); + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) { + vortex_adbdma_setmode(chip, dma, 1, dir, fmt, + runtime->channels == 1 ? 0 : 1, 0); + vortex_adbdma_setstartbuffer(chip, dma, 0); + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_SPDIF) + vortex_adb_setsrc(chip, dma, runtime->rate, dir); + } +#ifndef CHIP_AU8810 + else { + vortex_wtdma_setmode(chip, dma, 1, fmt, 0, 0); + // FIXME: Set rate (i guess using vortex_wt_writereg() somehow). + vortex_wtdma_setstartbuffer(chip, dma, 0); + } +#endif + spin_unlock_irq(&chip->lock); + return 0; +} + +/* trigger callback */ +static int snd_vortex_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + vortex_t *chip = snd_pcm_substream_chip(substream); + stream_t *stream = (stream_t *) substream->runtime->private_data; + int dma = stream->dma; + + spin_lock(&chip->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + // do something to start the PCM engine + //printk(KERN_INFO "vortex: start %d\n", dma); + stream->fifo_enabled = 1; + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) { + vortex_adbdma_resetup(chip, dma); + vortex_adbdma_startfifo(chip, dma); + } +#ifndef CHIP_AU8810 + else { + dev_info(chip->card->dev, "wt start %d\n", dma); + vortex_wtdma_startfifo(chip, dma); + } +#endif + break; + case SNDRV_PCM_TRIGGER_STOP: + // do something to stop the PCM engine + //printk(KERN_INFO "vortex: stop %d\n", dma); + stream->fifo_enabled = 0; + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) + vortex_adbdma_stopfifo(chip, dma); +#ifndef CHIP_AU8810 + else { + dev_info(chip->card->dev, "wt stop %d\n", dma); + vortex_wtdma_stopfifo(chip, dma); + } +#endif + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + //printk(KERN_INFO "vortex: pause %d\n", dma); + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) + vortex_adbdma_pausefifo(chip, dma); +#ifndef CHIP_AU8810 + else + vortex_wtdma_pausefifo(chip, dma); +#endif + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + //printk(KERN_INFO "vortex: resume %d\n", dma); + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) + vortex_adbdma_resumefifo(chip, dma); +#ifndef CHIP_AU8810 + else + vortex_wtdma_resumefifo(chip, dma); +#endif + break; + default: + spin_unlock(&chip->lock); + return -EINVAL; + } + spin_unlock(&chip->lock); + return 0; +} + +/* pointer callback */ +static snd_pcm_uframes_t snd_vortex_pcm_pointer(struct snd_pcm_substream *substream) +{ + vortex_t *chip = snd_pcm_substream_chip(substream); + stream_t *stream = (stream_t *) substream->runtime->private_data; + int dma = stream->dma; + snd_pcm_uframes_t current_ptr = 0; + + spin_lock(&chip->lock); + if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) + current_ptr = vortex_adbdma_getlinearpos(chip, dma); +#ifndef CHIP_AU8810 + else + current_ptr = vortex_wtdma_getlinearpos(chip, dma); +#endif + //printk(KERN_INFO "vortex: pointer = 0x%x\n", current_ptr); + spin_unlock(&chip->lock); + current_ptr = bytes_to_frames(substream->runtime, current_ptr); + if (current_ptr >= substream->runtime->buffer_size) + current_ptr = 0; + return current_ptr; +} + +/* operators */ +static const struct snd_pcm_ops snd_vortex_playback_ops = { + .open = snd_vortex_pcm_open, + .close = snd_vortex_pcm_close, + .hw_params = snd_vortex_pcm_hw_params, + .hw_free = snd_vortex_pcm_hw_free, + .prepare = snd_vortex_pcm_prepare, + .trigger = snd_vortex_pcm_trigger, + .pointer = snd_vortex_pcm_pointer, +}; + +/* +* definitions of capture are omitted here... +*/ + +static const char * const vortex_pcm_prettyname[VORTEX_PCM_LAST] = { + CARD_NAME " ADB", + CARD_NAME " SPDIF", + CARD_NAME " A3D", + CARD_NAME " WT", + CARD_NAME " I2S", +}; +static const char * const vortex_pcm_name[VORTEX_PCM_LAST] = { + "adb", + "spdif", + "a3d", + "wt", + "i2s", +}; + +/* SPDIF kcontrol */ + +static int snd_vortex_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_vortex_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS; + return 0; +} + +static int snd_vortex_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + ucontrol->value.iec958.status[0] = 0x00; + ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL|IEC958_AES1_CON_DIGDIGCONV_ID; + ucontrol->value.iec958.status[2] = 0x00; + switch (vortex->spdif_sr) { + case 32000: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_32000; break; + case 44100: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_44100; break; + case 48000: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000; break; + } + return 0; +} + +static int snd_vortex_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + int spdif_sr = 48000; + switch (ucontrol->value.iec958.status[3] & IEC958_AES3_CON_FS) { + case IEC958_AES3_CON_FS_32000: spdif_sr = 32000; break; + case IEC958_AES3_CON_FS_44100: spdif_sr = 44100; break; + case IEC958_AES3_CON_FS_48000: spdif_sr = 48000; break; + } + if (spdif_sr == vortex->spdif_sr) + return 0; + vortex->spdif_sr = spdif_sr; + vortex_spdif_init(vortex, vortex->spdif_sr, 1); + return 1; +} + +/* spdif controls */ +static const struct snd_kcontrol_new snd_vortex_mixer_spdif[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_vortex_spdif_info, + .get = snd_vortex_spdif_get, + .put = snd_vortex_spdif_put, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK), + .info = snd_vortex_spdif_info, + .get = snd_vortex_spdif_mask_get + }, +}; + +/* subdevice PCM Volume control */ + +static int snd_vortex_pcm_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = (VORTEX_IS_QUAD(vortex) ? 4 : 2); + uinfo->value.integer.min = -128; + uinfo->value.integer.max = 32; + return 0; +} + +static int snd_vortex_pcm_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + int subdev = kcontrol->id.subdevice; + struct pcm_vol *p = &vortex->pcm_vol[subdev]; + int max_chn = (VORTEX_IS_QUAD(vortex) ? 4 : 2); + for (i = 0; i < max_chn; i++) + ucontrol->value.integer.value[i] = p->vol[i]; + return 0; +} + +static int snd_vortex_pcm_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + int changed = 0; + int mixin; + unsigned char vol; + vortex_t *vortex = snd_kcontrol_chip(kcontrol); + int subdev = kcontrol->id.subdevice; + struct pcm_vol *p = &vortex->pcm_vol[subdev]; + int max_chn = (VORTEX_IS_QUAD(vortex) ? 4 : 2); + for (i = 0; i < max_chn; i++) { + if (p->vol[i] != ucontrol->value.integer.value[i]) { + p->vol[i] = ucontrol->value.integer.value[i]; + if (p->active) { + switch (vortex->dma_adb[p->dma].nr_ch) { + case 1: + mixin = p->mixin[0]; + break; + case 2: + default: + mixin = p->mixin[(i < 2) ? i : (i - 2)]; + break; + case 4: + mixin = p->mixin[i]; + break; + } + vol = p->vol[i]; + vortex_mix_setinputvolumebyte(vortex, + vortex->mixplayb[i], mixin, vol); + } + changed = 1; + } + } + return changed; +} + +static const DECLARE_TLV_DB_MINMAX(vortex_pcm_vol_db_scale, -9600, 2400); + +static const struct snd_kcontrol_new snd_vortex_pcm_vol = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .info = snd_vortex_pcm_vol_info, + .get = snd_vortex_pcm_vol_get, + .put = snd_vortex_pcm_vol_put, + .tlv = { .p = vortex_pcm_vol_db_scale }, +}; + +/* create a pcm device */ +static int snd_vortex_new_pcm(vortex_t *chip, int idx, int nr) +{ + struct snd_pcm *pcm; + struct snd_kcontrol *kctl; + int i; + int err, nr_capt; + + if (!chip || idx < 0 || idx >= VORTEX_PCM_LAST) + return -ENODEV; + + /* idx indicates which kind of PCM device. ADB, SPDIF, I2S and A3D share the + * same dma engine. WT uses it own separate dma engine which can't capture. */ + if (idx == VORTEX_PCM_ADB) + nr_capt = nr; + else + nr_capt = 0; + err = snd_pcm_new(chip->card, vortex_pcm_prettyname[idx], idx, nr, + nr_capt, &pcm); + if (err < 0) + return err; + snprintf(pcm->name, sizeof(pcm->name), + "%s %s", CARD_NAME_SHORT, vortex_pcm_name[idx]); + chip->pcm[idx] = pcm; + // This is an evil hack, but it saves a lot of duplicated code. + VORTEX_PCM_TYPE(pcm) = idx; + pcm->private_data = chip; + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_vortex_playback_ops); + if (idx == VORTEX_PCM_ADB) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_vortex_playback_ops); + + /* pre-allocation of Scatter-Gather buffers */ + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci_dev->dev, 0x10000, 0x10000); + + switch (VORTEX_PCM_TYPE(pcm)) { + case VORTEX_PCM_ADB: + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_std_chmaps, + VORTEX_IS_QUAD(chip) ? 4 : 2, + 0, NULL); + if (err < 0) + return err; + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_CAPTURE, + snd_pcm_std_chmaps, 2, 0, NULL); + if (err < 0) + return err; + break; +#ifdef CHIP_AU8830 + case VORTEX_PCM_A3D: + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_std_chmaps, 1, 0, NULL); + if (err < 0) + return err; + break; +#endif + } + + if (VORTEX_PCM_TYPE(pcm) == VORTEX_PCM_SPDIF) { + for (i = 0; i < ARRAY_SIZE(snd_vortex_mixer_spdif); i++) { + kctl = snd_ctl_new1(&snd_vortex_mixer_spdif[i], chip); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + } + if (VORTEX_PCM_TYPE(pcm) == VORTEX_PCM_ADB) { + for (i = 0; i < NR_PCM; i++) { + chip->pcm_vol[i].active = 0; + chip->pcm_vol[i].dma = -1; + kctl = snd_ctl_new1(&snd_vortex_pcm_vol, chip); + if (!kctl) + return -ENOMEM; + chip->pcm_vol[i].kctl = kctl; + kctl->id.device = 0; + kctl->id.subdevice = i; + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + } + return 0; +} diff --git a/sound/pci/au88x0/au88x0_synth.c b/sound/pci/au88x0/au88x0_synth.c new file mode 100644 index 000000000..84d961e7c --- /dev/null +++ b/sound/pci/au88x0/au88x0_synth.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* + * Someday its supposed to make use of the WT DMA engine + * for a Wavetable synthesizer. + */ + +#include "au88x0.h" +#include "au88x0_wt.h" + +static void vortex_fifo_setwtvalid(vortex_t * vortex, int fifo, int en); +static void vortex_connection_adb_mixin(vortex_t * vortex, int en, + unsigned char channel, + unsigned char source, + unsigned char mixin); +static void vortex_connection_mixin_mix(vortex_t * vortex, int en, + unsigned char mixin, + unsigned char mix, int a); +static void vortex_fifo_wtinitialize(vortex_t * vortex, int fifo, int j); +static int vortex_wt_SetReg(vortex_t * vortex, unsigned char reg, int wt, + u32 val); + +/* WT */ + +/* Put 2 WT channels together for one stereo interlaced channel. */ +static void vortex_wt_setstereo(vortex_t * vortex, u32 wt, u32 stereo) +{ + int temp; + + //temp = hwread(vortex->mmio, 0x80 + ((wt >> 0x5)<< 0xf) + (((wt & 0x1f) >> 1) << 2)); + temp = hwread(vortex->mmio, WT_STEREO(wt)); + temp = (temp & 0xfe) | (stereo & 1); + //hwwrite(vortex->mmio, 0x80 + ((wt >> 0x5)<< 0xf) + (((wt & 0x1f) >> 1) << 2), temp); + hwwrite(vortex->mmio, WT_STEREO(wt), temp); +} + +/* Join to mixdown route. */ +static void vortex_wt_setdsout(vortex_t * vortex, u32 wt, int en) +{ + int temp; + + /* There is one DSREG register for each bank (32 voices each). */ + temp = hwread(vortex->mmio, WT_DSREG((wt >= 0x20) ? 1 : 0)); + if (en) + temp |= (1 << (wt & 0x1f)); + else + temp &= ~(1 << (wt & 0x1f)); + hwwrite(vortex->mmio, WT_DSREG((wt >= 0x20) ? 1 : 0), temp); +} + +/* Setup WT route. */ +static int vortex_wt_allocroute(vortex_t * vortex, int wt, int nr_ch) +{ + wt_voice_t *voice = &(vortex->wt_voice[wt]); + int temp; + + //FIXME: WT audio routing. + if (nr_ch) { + vortex_fifo_wtinitialize(vortex, wt, 1); + vortex_fifo_setwtvalid(vortex, wt, 1); + vortex_wt_setstereo(vortex, wt, nr_ch - 1); + } else + vortex_fifo_setwtvalid(vortex, wt, 0); + + /* Set mixdown mode. */ + vortex_wt_setdsout(vortex, wt, 1); + /* Set other parameter registers. */ + hwwrite(vortex->mmio, WT_SRAMP(0), 0x880000); + //hwwrite(vortex->mmio, WT_GMODE(0), 0xffffffff); +#ifdef CHIP_AU8830 + hwwrite(vortex->mmio, WT_SRAMP(1), 0x880000); + //hwwrite(vortex->mmio, WT_GMODE(1), 0xffffffff); +#endif + hwwrite(vortex->mmio, WT_PARM(wt, 0), 0); + hwwrite(vortex->mmio, WT_PARM(wt, 1), 0); + hwwrite(vortex->mmio, WT_PARM(wt, 2), 0); + + temp = hwread(vortex->mmio, WT_PARM(wt, 3)); + dev_dbg(vortex->card->dev, "WT PARM3: %x\n", temp); + //hwwrite(vortex->mmio, WT_PARM(wt, 3), temp); + + hwwrite(vortex->mmio, WT_DELAY(wt, 0), 0); + hwwrite(vortex->mmio, WT_DELAY(wt, 1), 0); + hwwrite(vortex->mmio, WT_DELAY(wt, 2), 0); + hwwrite(vortex->mmio, WT_DELAY(wt, 3), 0); + + dev_dbg(vortex->card->dev, "WT GMODE: %x\n", + hwread(vortex->mmio, WT_GMODE(wt))); + + hwwrite(vortex->mmio, WT_PARM(wt, 2), 0xffffffff); + hwwrite(vortex->mmio, WT_PARM(wt, 3), 0xcff1c810); + + voice->parm0 = voice->parm1 = 0xcfb23e2f; + hwwrite(vortex->mmio, WT_PARM(wt, 0), voice->parm0); + hwwrite(vortex->mmio, WT_PARM(wt, 1), voice->parm1); + dev_dbg(vortex->card->dev, "WT GMODE 2 : %x\n", + hwread(vortex->mmio, WT_GMODE(wt))); + return 0; +} + + +static void vortex_wt_connect(vortex_t * vortex, int en) +{ + int i, ii, mix; + +#define NR_WTROUTES 6 +#ifdef CHIP_AU8830 +#define NR_WTBLOCKS 2 +#else +#define NR_WTBLOCKS 1 +#endif + + for (i = 0; i < NR_WTBLOCKS; i++) { + for (ii = 0; ii < NR_WTROUTES; ii++) { + mix = + vortex_adb_checkinout(vortex, + vortex->fixed_res, en, + VORTEX_RESOURCE_MIXIN); + vortex->mixwt[(i * NR_WTROUTES) + ii] = mix; + + vortex_route(vortex, en, 0x11, + ADB_WTOUT(i, ii + 0x20), ADB_MIXIN(mix)); + + vortex_connection_mixin_mix(vortex, en, mix, + vortex->mixplayb[ii % 2], 0); + if (VORTEX_IS_QUAD(vortex)) + vortex_connection_mixin_mix(vortex, en, + mix, + vortex->mixplayb[2 + + (ii % 2)], 0); + } + } + for (i = 0; i < NR_WT; i++) { + hwwrite(vortex->mmio, WT_RUN(i), 1); + } +} + +/* Read WT Register */ +#if 0 +static int vortex_wt_GetReg(vortex_t * vortex, char reg, int wt) +{ + //int eax, esi; + + if (reg == 4) { + return hwread(vortex->mmio, WT_PARM(wt, 3)); + } + if (reg == 7) { + return hwread(vortex->mmio, WT_GMODE(wt)); + } + + return 0; +} + +/* WT hardware abstraction layer generic register interface. */ +static int +vortex_wt_SetReg2(vortex_t * vortex, unsigned char reg, int wt, + u16 val) +{ + /* + int eax, edx; + + if (wt >= NR_WT) // 0x40 -> NR_WT + return 0; + + if ((reg - 0x20) > 0) { + if ((reg - 0x21) != 0) + return 0; + eax = ((((b & 0xff) << 0xb) + (edx & 0xff)) << 4) + 0x208; // param 2 + } else { + eax = ((((b & 0xff) << 0xb) + (edx & 0xff)) << 4) + 0x20a; // param 3 + } + hwwrite(vortex->mmio, eax, c); + */ + return 1; +} + +/*public: static void __thiscall CWTHal::SetReg(unsigned char,int,unsigned long) */ +#endif +static int +vortex_wt_SetReg(vortex_t * vortex, unsigned char reg, int wt, + u32 val) +{ + int ecx; + + if ((reg == 5) || ((reg >= 7) && (reg <= 10)) || (reg == 0xc)) { + if (wt >= (NR_WT / NR_WT_PB)) { + dev_warn(vortex->card->dev, + "WT SetReg: bank out of range. reg=0x%x, wt=%d\n", + reg, wt); + return 0; + } + } else { + if (wt >= NR_WT) { + dev_err(vortex->card->dev, + "WT SetReg: voice out of range\n"); + return 0; + } + } + if (reg > 0xc) + return 0; + + switch (reg) { + /* Voice specific parameters */ + case 0: /* running */ + /* + pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n", + WT_RUN(wt), (int)val); + */ + hwwrite(vortex->mmio, WT_RUN(wt), val); + return 0xc; + case 1: /* param 0 */ + /* + pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n", + WT_PARM(wt,0), (int)val); + */ + hwwrite(vortex->mmio, WT_PARM(wt, 0), val); + return 0xc; + case 2: /* param 1 */ + /* + pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n", + WT_PARM(wt,1), (int)val); + */ + hwwrite(vortex->mmio, WT_PARM(wt, 1), val); + return 0xc; + case 3: /* param 2 */ + /* + pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n", + WT_PARM(wt,2), (int)val); + */ + hwwrite(vortex->mmio, WT_PARM(wt, 2), val); + return 0xc; + case 4: /* param 3 */ + /* + pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n", + WT_PARM(wt,3), (int)val); + */ + hwwrite(vortex->mmio, WT_PARM(wt, 3), val); + return 0xc; + case 6: /* mute */ + /* + pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n", + WT_MUTE(wt), (int)val); + */ + hwwrite(vortex->mmio, WT_MUTE(wt), val); + return 0xc; + case 0xb: + /* delay */ + /* + pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n", + WT_DELAY(wt,0), (int)val); + */ + hwwrite(vortex->mmio, WT_DELAY(wt, 3), val); + hwwrite(vortex->mmio, WT_DELAY(wt, 2), val); + hwwrite(vortex->mmio, WT_DELAY(wt, 1), val); + hwwrite(vortex->mmio, WT_DELAY(wt, 0), val); + return 0xc; + /* Global WT block parameters */ + case 5: /* sramp */ + ecx = WT_SRAMP(wt); + break; + case 8: /* aramp */ + ecx = WT_ARAMP(wt); + break; + case 9: /* mramp */ + ecx = WT_MRAMP(wt); + break; + case 0xa: /* ctrl */ + ecx = WT_CTRL(wt); + break; + case 0xc: /* ds_reg */ + ecx = WT_DSREG(wt); + break; + default: + return 0; + } + /* + pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n", ecx, (int)val); + */ + hwwrite(vortex->mmio, ecx, val); + return 1; +} + +static void vortex_wt_init(vortex_t * vortex) +{ + u32 var4, var8, varc, var10 = 0, edi; + + var10 &= 0xFFFFFFE3; + var10 |= 0x22; + var10 &= 0xFFFFFEBF; + var10 |= 0x80; + var10 |= 0x200; + var10 &= 0xfffffffe; + var10 &= 0xfffffbff; + var10 |= 0x1800; + // var10 = 0x1AA2 + var4 = 0x10000000; + varc = 0x00830000; + var8 = 0x00830000; + + /* Init Bank registers. */ + for (edi = 0; edi < (NR_WT / NR_WT_PB); edi++) { + vortex_wt_SetReg(vortex, 0xc, edi, 0); /* ds_reg */ + vortex_wt_SetReg(vortex, 0xa, edi, var10); /* ctrl */ + vortex_wt_SetReg(vortex, 0x9, edi, var4); /* mramp */ + vortex_wt_SetReg(vortex, 0x8, edi, varc); /* aramp */ + vortex_wt_SetReg(vortex, 0x5, edi, var8); /* sramp */ + } + /* Init Voice registers. */ + for (edi = 0; edi < NR_WT; edi++) { + vortex_wt_SetReg(vortex, 0x4, edi, 0); /* param 3 0x20c */ + vortex_wt_SetReg(vortex, 0x3, edi, 0); /* param 2 0x208 */ + vortex_wt_SetReg(vortex, 0x2, edi, 0); /* param 1 0x204 */ + vortex_wt_SetReg(vortex, 0x1, edi, 0); /* param 0 0x200 */ + vortex_wt_SetReg(vortex, 0xb, edi, 0); /* delay 0x400 - 0x40c */ + } + var10 |= 1; + for (edi = 0; edi < (NR_WT / NR_WT_PB); edi++) + vortex_wt_SetReg(vortex, 0xa, edi, var10); /* ctrl */ +} + +/* Extract of CAdbTopology::SetVolume(struct _ASPVOLUME *) */ +#if 0 +static void vortex_wt_SetVolume(vortex_t * vortex, int wt, int vol[]) +{ + wt_voice_t *voice = &(vortex->wt_voice[wt]); + int ecx = vol[1], eax = vol[0]; + + /* This is pure guess */ + voice->parm0 &= 0xff00ffff; + voice->parm0 |= (vol[0] & 0xff) << 0x10; + voice->parm1 &= 0xff00ffff; + voice->parm1 |= (vol[1] & 0xff) << 0x10; + + /* This is real */ + hwwrite(vortex, WT_PARM(wt, 0), voice->parm0); + hwwrite(vortex, WT_PARM(wt, 1), voice->parm0); + + if (voice->this_1D0 & 4) { + eax >>= 8; + ecx = eax; + if (ecx < 0x80) + ecx = 0x7f; + voice->parm3 &= 0xFFFFC07F; + voice->parm3 |= (ecx & 0x7f) << 7; + voice->parm3 &= 0xFFFFFF80; + voice->parm3 |= (eax & 0x7f); + } else { + voice->parm3 &= 0xFFE03FFF; + voice->parm3 |= (eax & 0xFE00) << 5; + } + + hwwrite(vortex, WT_PARM(wt, 3), voice->parm3); +} + +/* Extract of CAdbTopology::SetFrequency(unsigned long arg_0) */ +static void vortex_wt_SetFrequency(vortex_t * vortex, int wt, unsigned int sr) +{ + wt_voice_t *voice = &(vortex->wt_voice[wt]); + u32 eax, edx; + + //FIXME: 64 bit operation. + eax = ((sr << 0xf) * 0x57619F1) & 0xffffffff; + edx = (((sr << 0xf) * 0x57619F1)) >> 0x20; + + edx >>= 0xa; + edx <<= 1; + if (edx) { + if (edx & 0x0FFF80000) + eax = 0x7fff; + else { + edx <<= 0xd; + eax = 7; + while ((edx & 0x80000000) == 0) { + edx <<= 1; + eax--; + if (eax == 0) + break; + } + if (eax) + edx <<= 1; + eax <<= 0xc; + edx >>= 0x14; + eax |= edx; + } + } else + eax = 0; + voice->parm0 &= 0xffff0001; + voice->parm0 |= (eax & 0x7fff) << 1; + voice->parm1 = voice->parm0 | 1; + // Wt: this_1D4 + //AuWt::WriteReg((ulong)(this_1DC<<4)+0x200, (ulong)this_1E4); + //AuWt::WriteReg((ulong)(this_1DC<<4)+0x204, (ulong)this_1E8); + hwwrite(vortex->mmio, WT_PARM(wt, 0), voice->parm0); + hwwrite(vortex->mmio, WT_PARM(wt, 1), voice->parm1); +} +#endif + +/* End of File */ diff --git a/sound/pci/au88x0/au88x0_wt.h b/sound/pci/au88x0/au88x0_wt.h new file mode 100644 index 000000000..7b2cffad8 --- /dev/null +++ b/sound/pci/au88x0/au88x0_wt.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*************************************************************************** + * WT register offsets. + * + * Wed Oct 22 13:50:20 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.org + ****************************************************************************/ +#ifndef _AU88X0_WT_H +#define _AU88X0_WT_H + +/* WT channels are grouped in banks. Each bank has 0x20 channels. */ +/* Bank register address boundary is 0x8000 */ + +#define NR_WT_PB 0x20 + +/* WT bank base register (as dword address). */ +#define WT_BAR(x) (((x)&0xffe0)<<0x8) +#define WT_BANK(x) (x>>5) +/* WT Bank registers */ +#define WT_CTRL(bank) (((((bank)&1)<<0xd) + 0x00)<<2) /* 0x0000 */ +#define WT_SRAMP(bank) (((((bank)&1)<<0xd) + 0x01)<<2) /* 0x0004 */ +#define WT_DSREG(bank) (((((bank)&1)<<0xd) + 0x02)<<2) /* 0x0008 */ +#define WT_MRAMP(bank) (((((bank)&1)<<0xd) + 0x03)<<2) /* 0x000c */ +#define WT_GMODE(bank) (((((bank)&1)<<0xd) + 0x04)<<2) /* 0x0010 */ +#define WT_ARAMP(bank) (((((bank)&1)<<0xd) + 0x05)<<2) /* 0x0014 */ +/* WT Voice registers */ +#define WT_STEREO(voice) ((WT_BAR(voice)+ 0x20 +(((voice)&0x1f)>>1))<<2) /* 0x0080 */ +#define WT_MUTE(voice) ((WT_BAR(voice)+ 0x40 +((voice)&0x1f))<<2) /* 0x0100 */ +#define WT_RUN(voice) ((WT_BAR(voice)+ 0x60 +((voice)&0x1f))<<2) /* 0x0180 */ +/* Some kind of parameters. */ +/* PARM0, PARM1 : Filter (0xFF000000), SampleRate (0x0000FFFF) */ +/* PARM2, PARM3 : Still unknown */ +#define WT_PARM(x,y) (((WT_BAR(x))+ 0x80 +(((x)&0x1f)<<2)+(y))<<2) /* 0x0200 */ +#define WT_DELAY(x,y) (((WT_BAR(x))+ 0x100 +(((x)&0x1f)<<2)+(y))<<2) /* 0x0400 */ + +/* Numeric indexes used by SetReg() and GetReg() */ +#if 0 +enum { + run = 0, /* 0 W 1:run 0:stop */ + parm0, /* 1 W filter, samplerate */ + parm1, /* 2 W filter, samplerate */ + parm2, /* 3 W */ + parm3, /* 4 RW volume. This value is calculated using floating point ops. */ + sramp, /* 5 W */ + mute, /* 6 W 1:mute, 0:unmute */ + gmode, /* 7 RO Looks like only bit0 is used. */ + aramp, /* 8 W */ + mramp, /* 9 W */ + ctrl, /* a W */ + delay, /* b W All 4 values are written at once with same value. */ + dsreg, /* c (R)W */ +} wt_reg; +#endif + +typedef struct { + u32 parm0; /* this_1E4 */ + u32 parm1; /* this_1E8 */ + u32 parm2; /* this_1EC */ + u32 parm3; /* this_1F0 */ + u32 this_1D0; +} wt_voice_t; + +#endif /* _AU88X0_WT_H */ + +/* End of file */ diff --git a/sound/pci/au88x0/au88x0_xtalk.c b/sound/pci/au88x0/au88x0_xtalk.c new file mode 100644 index 000000000..27859536d --- /dev/null +++ b/sound/pci/au88x0/au88x0_xtalk.c @@ -0,0 +1,783 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/*************************************************************************** + * au88x0_cxtalk.c + * + * Wed Nov 19 16:29:47 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.org + ****************************************************************************/ + +/* + */ + +#include "au88x0_xtalk.h" + +/* Data (a whole lot of data.... ) */ + +static short const sXtalkWideKLeftEq = 0x269C; +static short const sXtalkWideKRightEq = 0x269C; +static short const sXtalkWideKLeftXt = 0xF25E; +static __maybe_unused short const sXtalkWideKRightXt = 0xF25E; +static short const sXtalkWideShiftLeftEq = 1; +static short const sXtalkWideShiftRightEq = 1; +static short const sXtalkWideShiftLeftXt = 0; +static __maybe_unused short const sXtalkWideShiftRightXt = 0; +static unsigned short const wXtalkWideLeftDelay = 0xd; +static unsigned short const wXtalkWideRightDelay = 0xd; +static short const sXtalkNarrowKLeftEq = 0x468D; +static short const sXtalkNarrowKRightEq = 0x468D; +static short const sXtalkNarrowKLeftXt = 0xF82E; +static __maybe_unused short const sXtalkNarrowKRightXt = 0xF82E; +static short const sXtalkNarrowShiftLeftEq = 0x3; +static short const sXtalkNarrowShiftRightEq = 0x3; +static short const sXtalkNarrowShiftLeftXt = 0; +static __maybe_unused short const sXtalkNarrowShiftRightXt = 0; +static unsigned short const wXtalkNarrowLeftDelay = 0x7; +static unsigned short const wXtalkNarrowRightDelay = 0x7; + +static __maybe_unused xtalk_gains_t const asXtalkGainsDefault = { + 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, + 0x4000, 0x4000, 0x4000, 0x4000, 0x4000 +}; + +static __maybe_unused xtalk_gains_t const asXtalkGainsTest = { + 0x7fff, 0x8000, 0x0000, 0x0000, 0x0001, + 0xffff, 0x4000, 0xc000, 0x0002, 0xfffe +}; + +static __maybe_unused xtalk_gains_t const asXtalkGains1Chan = { + 0x7FFF, 0, 0, 0, 0, + 0x7FFF, 0, 0, 0, 0, +}; + +// Input gain for 4 A3D slices. One possible input pair is left zero. +static xtalk_gains_t const asXtalkGainsAllChan = { + 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0, + 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0 +}; + +static xtalk_gains_t const asXtalkGainsZeros = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static xtalk_dline_t const alXtalkDlineZeros = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +static __maybe_unused xtalk_dline_t const alXtalkDlineTest = { + 0x0000fc18, 0xfff03e8, 0x000186a0, 0xfffe7960, 1, 0xffffffff, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static xtalk_instate_t const asXtalkInStateZeros = { + 0, 0, 0, 0 +}; + +static __maybe_unused xtalk_instate_t const asXtalkInStateTest = { + 0x0080, 0xff80, 0x0001, 0xffff +}; + +static xtalk_state_t const asXtalkOutStateZeros = { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} +}; + +static short const sDiamondKLeftEq = 0x401d; +static short const sDiamondKRightEq = 0x401d; +static short const sDiamondKLeftXt = 0xF90E; +static __maybe_unused short const sDiamondKRightXt = 0xF90E; +static short const sDiamondShiftLeftEq = 1; +static short const sDiamondShiftRightEq = 1; +static short const sDiamondShiftLeftXt = 0; +static __maybe_unused short const sDiamondShiftRightXt = 0; +static unsigned short const wDiamondLeftDelay = 0xb; +static unsigned short const wDiamondRightDelay = 0xb; + +static xtalk_coefs_t const asXtalkWideCoefsLeftEq = { + {0xEC4C, 0xDCE9, 0xFDC2, 0xFEEC, 0}, + {0x5F60, 0xCBCB, 0xFC26, 0x0305, 0}, + {0x340B, 0xe8f5, 0x236c, 0xe40d, 0}, + {0x76d5, 0xc78d, 0x05ac, 0xfa5b, 0}, + {0x7F04, 0xC0FA, 0x0263, 0xFDA2, 0} +}; +static xtalk_coefs_t const asXtalkWideCoefsRightEq = { + {0xEC4C, 0xDCE9, 0xFDC2, 0xFEEC, 0}, + {0x5F60, 0xCBCB, 0xFC26, 0x0305, 0}, + {0x340B, 0xe8f5, 0x236c, 0xe40d, 0}, + {0x76d5, 0xc78d, 0x05ac, 0xfa5b, 0}, + {0x7F04, 0xC0FA, 0x0263, 0xFDA2, 0} +}; +static xtalk_coefs_t const asXtalkWideCoefsLeftXt = { + {0x55c6, 0xc97b, 0x005b, 0x0047, 0}, + {0x6a60, 0xca20, 0xffc6, 0x0040, 0}, + {0x6411, 0xd711, 0xfca1, 0x0190, 0}, + {0x77dc, 0xc79e, 0xffb8, 0x000a, 0}, + {0, 0, 0, 0, 0} +}; +static __maybe_unused xtalk_coefs_t const asXtalkWideCoefsRightXt = { + {0x55c6, 0xc97b, 0x005b, 0x0047, 0}, + {0x6a60, 0xca20, 0xffc6, 0x0040, 0}, + {0x6411, 0xd711, 0xfca1, 0x0190, 0}, + {0x77dc, 0xc79e, 0xffb8, 0x000a, 0}, + {0, 0, 0, 0, 0} +}; +static xtalk_coefs_t const asXtalkNarrowCoefsLeftEq = { + {0x50B5, 0xD07C, 0x026D, 0xFD21, 0}, + {0x460F, 0xE44F, 0xF75E, 0xEFA6, 0}, + {0x556D, 0xDCAB, 0x2098, 0xF0F2, 0}, + {0x7E03, 0xC1F0, 0x007D, 0xFF89, 0}, + {0x383E, 0xFD9D, 0xB278, 0x4547, 0} +}; + +static xtalk_coefs_t const asXtalkNarrowCoefsRightEq = { + {0x50B5, 0xD07C, 0x026D, 0xFD21, 0}, + {0x460F, 0xE44F, 0xF75E, 0xEFA6, 0}, + {0x556D, 0xDCAB, 0x2098, 0xF0F2, 0}, + {0x7E03, 0xC1F0, 0x007D, 0xFF89, 0}, + {0x383E, 0xFD9D, 0xB278, 0x4547, 0} +}; + +static xtalk_coefs_t const asXtalkNarrowCoefsLeftXt = { + {0x3CB2, 0xDF49, 0xF6EA, 0x095B, 0}, + {0x6777, 0xC915, 0xFEAF, 0x00B1, 0}, + {0x7762, 0xC7D9, 0x025B, 0xFDA6, 0}, + {0x6B7A, 0xD2AA, 0xF2FB, 0x0B64, 0}, + {0, 0, 0, 0, 0} +}; + +static __maybe_unused xtalk_coefs_t const asXtalkNarrowCoefsRightXt = { + {0x3CB2, 0xDF49, 0xF6EA, 0x095B, 0}, + {0x6777, 0xC915, 0xFEAF, 0x00B1, 0}, + {0x7762, 0xC7D9, 0x025B, 0xFDA6, 0}, + {0x6B7A, 0xD2AA, 0xF2FB, 0x0B64, 0}, + {0, 0, 0, 0, 0} +}; + +static xtalk_coefs_t const asXtalkCoefsZeros = { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0} +}; + +static xtalk_coefs_t const asXtalkCoefsPipe = { + {0, 0, 0x0FA0, 0, 0}, + {0, 0, 0x0FA0, 0, 0}, + {0, 0, 0x0FA0, 0, 0}, + {0, 0, 0x0FA0, 0, 0}, + {0, 0, 0x1180, 0, 0}, +}; +static __maybe_unused xtalk_coefs_t const asXtalkCoefsNegPipe = { + {0, 0, 0xF380, 0, 0}, + {0, 0, 0xF380, 0, 0}, + {0, 0, 0xF380, 0, 0}, + {0, 0, 0xF380, 0, 0}, + {0, 0, 0xF200, 0, 0} +}; + +static __maybe_unused xtalk_coefs_t const asXtalkCoefsNumTest = { + {0, 0, 0xF380, 0x8000, 0x6D60}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0} +}; + +static __maybe_unused xtalk_coefs_t const asXtalkCoefsDenTest = { + {0xC000, 0x2000, 0x4000, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0} +}; + +static __maybe_unused xtalk_state_t const asXtalkOutStateTest = { + {0x7FFF, 0x0004, 0xFFFC, 0}, + {0xFE00, 0x0008, 0xFFF8, 0x4000}, + {0x0200, 0x0010, 0xFFF0, 0xC000}, + {0x8000, 0x0020, 0xFFE0, 0}, + {0, 0, 0, 0} +}; + +static xtalk_coefs_t const asDiamondCoefsLeftEq = { + {0x0F1E, 0x2D05, 0xF8E3, 0x07C8, 0}, + {0x45E2, 0xCA51, 0x0448, 0xFCE7, 0}, + {0xA93E, 0xDBD5, 0x022C, 0x028A, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0} +}; + +static xtalk_coefs_t const asDiamondCoefsRightEq = { + {0x0F1E, 0x2D05, 0xF8E3, 0x07C8, 0}, + {0x45E2, 0xCA51, 0x0448, 0xFCE7, 0}, + {0xA93E, 0xDBD5, 0x022C, 0x028A, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0} +}; + +static xtalk_coefs_t const asDiamondCoefsLeftXt = { + {0x3B50, 0xFE08, 0xF959, 0x0060, 0}, + {0x9FCB, 0xD8F1, 0x00A2, 0x003A, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0} +}; + +static __maybe_unused xtalk_coefs_t const asDiamondCoefsRightXt = { + {0x3B50, 0xFE08, 0xF959, 0x0060, 0}, + {0x9FCB, 0xD8F1, 0x00A2, 0x003A, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0} +}; + + /**/ +/* XTalk EQ and XT */ +static void +vortex_XtalkHw_SetLeftEQ(vortex_t * vortex, short arg_0, short arg_4, + xtalk_coefs_t const coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + hwwrite(vortex->mmio, 0x24200 + i * 0x24, coefs[i][0]); + hwwrite(vortex->mmio, 0x24204 + i * 0x24, coefs[i][1]); + hwwrite(vortex->mmio, 0x24208 + i * 0x24, coefs[i][2]); + hwwrite(vortex->mmio, 0x2420c + i * 0x24, coefs[i][3]); + hwwrite(vortex->mmio, 0x24210 + i * 0x24, coefs[i][4]); + } + hwwrite(vortex->mmio, 0x24538, arg_0 & 0xffff); + hwwrite(vortex->mmio, 0x2453C, arg_4 & 0xffff); +} + +static void +vortex_XtalkHw_SetRightEQ(vortex_t * vortex, short arg_0, short arg_4, + xtalk_coefs_t const coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + hwwrite(vortex->mmio, 0x242b4 + i * 0x24, coefs[i][0]); + hwwrite(vortex->mmio, 0x242b8 + i * 0x24, coefs[i][1]); + hwwrite(vortex->mmio, 0x242bc + i * 0x24, coefs[i][2]); + hwwrite(vortex->mmio, 0x242c0 + i * 0x24, coefs[i][3]); + hwwrite(vortex->mmio, 0x242c4 + i * 0x24, coefs[i][4]); + } + hwwrite(vortex->mmio, 0x24540, arg_0 & 0xffff); + hwwrite(vortex->mmio, 0x24544, arg_4 & 0xffff); +} + +static void +vortex_XtalkHw_SetLeftXT(vortex_t * vortex, short arg_0, short arg_4, + xtalk_coefs_t const coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + hwwrite(vortex->mmio, 0x24368 + i * 0x24, coefs[i][0]); + hwwrite(vortex->mmio, 0x2436c + i * 0x24, coefs[i][1]); + hwwrite(vortex->mmio, 0x24370 + i * 0x24, coefs[i][2]); + hwwrite(vortex->mmio, 0x24374 + i * 0x24, coefs[i][3]); + hwwrite(vortex->mmio, 0x24378 + i * 0x24, coefs[i][4]); + } + hwwrite(vortex->mmio, 0x24548, arg_0 & 0xffff); + hwwrite(vortex->mmio, 0x2454C, arg_4 & 0xffff); +} + +static void +vortex_XtalkHw_SetRightXT(vortex_t * vortex, short arg_0, short arg_4, + xtalk_coefs_t const coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + hwwrite(vortex->mmio, 0x2441C + i * 0x24, coefs[i][0]); + hwwrite(vortex->mmio, 0x24420 + i * 0x24, coefs[i][1]); + hwwrite(vortex->mmio, 0x24424 + i * 0x24, coefs[i][2]); + hwwrite(vortex->mmio, 0x24428 + i * 0x24, coefs[i][3]); + hwwrite(vortex->mmio, 0x2442C + i * 0x24, coefs[i][4]); + } + hwwrite(vortex->mmio, 0x24550, arg_0 & 0xffff); + hwwrite(vortex->mmio, 0x24554, arg_4 & 0xffff); +} + +static void +vortex_XtalkHw_SetLeftEQStates(vortex_t * vortex, + xtalk_instate_t const arg_0, + xtalk_state_t const coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + hwwrite(vortex->mmio, 0x24214 + i * 0x24, coefs[i][0]); + hwwrite(vortex->mmio, 0x24218 + i * 0x24, coefs[i][1]); + hwwrite(vortex->mmio, 0x2421C + i * 0x24, coefs[i][2]); + hwwrite(vortex->mmio, 0x24220 + i * 0x24, coefs[i][3]); + } + hwwrite(vortex->mmio, 0x244F8, arg_0[0]); + hwwrite(vortex->mmio, 0x244FC, arg_0[1]); + hwwrite(vortex->mmio, 0x24500, arg_0[2]); + hwwrite(vortex->mmio, 0x24504, arg_0[3]); +} + +static void +vortex_XtalkHw_SetRightEQStates(vortex_t * vortex, + xtalk_instate_t const arg_0, + xtalk_state_t const coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + hwwrite(vortex->mmio, 0x242C8 + i * 0x24, coefs[i][0]); + hwwrite(vortex->mmio, 0x242CC + i * 0x24, coefs[i][1]); + hwwrite(vortex->mmio, 0x242D0 + i * 0x24, coefs[i][2]); + hwwrite(vortex->mmio, 0x244D4 + i * 0x24, coefs[i][3]); + } + hwwrite(vortex->mmio, 0x24508, arg_0[0]); + hwwrite(vortex->mmio, 0x2450C, arg_0[1]); + hwwrite(vortex->mmio, 0x24510, arg_0[2]); + hwwrite(vortex->mmio, 0x24514, arg_0[3]); +} + +static void +vortex_XtalkHw_SetLeftXTStates(vortex_t * vortex, + xtalk_instate_t const arg_0, + xtalk_state_t const coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + hwwrite(vortex->mmio, 0x2437C + i * 0x24, coefs[i][0]); + hwwrite(vortex->mmio, 0x24380 + i * 0x24, coefs[i][1]); + hwwrite(vortex->mmio, 0x24384 + i * 0x24, coefs[i][2]); + hwwrite(vortex->mmio, 0x24388 + i * 0x24, coefs[i][3]); + } + hwwrite(vortex->mmio, 0x24518, arg_0[0]); + hwwrite(vortex->mmio, 0x2451C, arg_0[1]); + hwwrite(vortex->mmio, 0x24520, arg_0[2]); + hwwrite(vortex->mmio, 0x24524, arg_0[3]); +} + +static void +vortex_XtalkHw_SetRightXTStates(vortex_t * vortex, + xtalk_instate_t const arg_0, + xtalk_state_t const coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + hwwrite(vortex->mmio, 0x24430 + i * 0x24, coefs[i][0]); + hwwrite(vortex->mmio, 0x24434 + i * 0x24, coefs[i][1]); + hwwrite(vortex->mmio, 0x24438 + i * 0x24, coefs[i][2]); + hwwrite(vortex->mmio, 0x2443C + i * 0x24, coefs[i][3]); + } + hwwrite(vortex->mmio, 0x24528, arg_0[0]); + hwwrite(vortex->mmio, 0x2452C, arg_0[1]); + hwwrite(vortex->mmio, 0x24530, arg_0[2]); + hwwrite(vortex->mmio, 0x24534, arg_0[3]); +} + +#if 0 +static void +vortex_XtalkHw_GetLeftEQ(vortex_t * vortex, short *arg_0, short *arg_4, + xtalk_coefs_t coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + coefs[i][0] = hwread(vortex->mmio, 0x24200 + i * 0x24); + coefs[i][1] = hwread(vortex->mmio, 0x24204 + i * 0x24); + coefs[i][2] = hwread(vortex->mmio, 0x24208 + i * 0x24); + coefs[i][3] = hwread(vortex->mmio, 0x2420c + i * 0x24); + coefs[i][4] = hwread(vortex->mmio, 0x24210 + i * 0x24); + } + *arg_0 = hwread(vortex->mmio, 0x24538) & 0xffff; + *arg_4 = hwread(vortex->mmio, 0x2453c) & 0xffff; +} + +static void +vortex_XtalkHw_GetRightEQ(vortex_t * vortex, short *arg_0, short *arg_4, + xtalk_coefs_t coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + coefs[i][0] = hwread(vortex->mmio, 0x242b4 + i * 0x24); + coefs[i][1] = hwread(vortex->mmio, 0x242b8 + i * 0x24); + coefs[i][2] = hwread(vortex->mmio, 0x242bc + i * 0x24); + coefs[i][3] = hwread(vortex->mmio, 0x242c0 + i * 0x24); + coefs[i][4] = hwread(vortex->mmio, 0x242c4 + i * 0x24); + } + *arg_0 = hwread(vortex->mmio, 0x24540) & 0xffff; + *arg_4 = hwread(vortex->mmio, 0x24544) & 0xffff; +} + +static void +vortex_XtalkHw_GetLeftXT(vortex_t * vortex, short *arg_0, short *arg_4, + xtalk_coefs_t coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + coefs[i][0] = hwread(vortex->mmio, 0x24368 + i * 0x24); + coefs[i][1] = hwread(vortex->mmio, 0x2436C + i * 0x24); + coefs[i][2] = hwread(vortex->mmio, 0x24370 + i * 0x24); + coefs[i][3] = hwread(vortex->mmio, 0x24374 + i * 0x24); + coefs[i][4] = hwread(vortex->mmio, 0x24378 + i * 0x24); + } + *arg_0 = hwread(vortex->mmio, 0x24548) & 0xffff; + *arg_4 = hwread(vortex->mmio, 0x2454C) & 0xffff; +} + +static void +vortex_XtalkHw_GetRightXT(vortex_t * vortex, short *arg_0, short *arg_4, + xtalk_coefs_t coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + coefs[i][0] = hwread(vortex->mmio, 0x2441C + i * 0x24); + coefs[i][1] = hwread(vortex->mmio, 0x24420 + i * 0x24); + coefs[i][2] = hwread(vortex->mmio, 0x24424 + i * 0x24); + coefs[i][3] = hwread(vortex->mmio, 0x24428 + i * 0x24); + coefs[i][4] = hwread(vortex->mmio, 0x2442C + i * 0x24); + } + *arg_0 = hwread(vortex->mmio, 0x24550) & 0xffff; + *arg_4 = hwread(vortex->mmio, 0x24554) & 0xffff; +} + +static void +vortex_XtalkHw_GetLeftEQStates(vortex_t * vortex, xtalk_instate_t arg_0, + xtalk_state_t coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + coefs[i][0] = hwread(vortex->mmio, 0x24214 + i * 0x24); + coefs[i][1] = hwread(vortex->mmio, 0x24218 + i * 0x24); + coefs[i][2] = hwread(vortex->mmio, 0x2421C + i * 0x24); + coefs[i][3] = hwread(vortex->mmio, 0x24220 + i * 0x24); + } + arg_0[0] = hwread(vortex->mmio, 0x244F8); + arg_0[1] = hwread(vortex->mmio, 0x244FC); + arg_0[2] = hwread(vortex->mmio, 0x24500); + arg_0[3] = hwread(vortex->mmio, 0x24504); +} + +static void +vortex_XtalkHw_GetRightEQStates(vortex_t * vortex, xtalk_instate_t arg_0, + xtalk_state_t coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + coefs[i][0] = hwread(vortex->mmio, 0x242C8 + i * 0x24); + coefs[i][1] = hwread(vortex->mmio, 0x242CC + i * 0x24); + coefs[i][2] = hwread(vortex->mmio, 0x242D0 + i * 0x24); + coefs[i][3] = hwread(vortex->mmio, 0x242D4 + i * 0x24); + } + arg_0[0] = hwread(vortex->mmio, 0x24508); + arg_0[1] = hwread(vortex->mmio, 0x2450C); + arg_0[2] = hwread(vortex->mmio, 0x24510); + arg_0[3] = hwread(vortex->mmio, 0x24514); +} + +static void +vortex_XtalkHw_GetLeftXTStates(vortex_t * vortex, xtalk_instate_t arg_0, + xtalk_state_t coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + coefs[i][0] = hwread(vortex->mmio, 0x2437C + i * 0x24); + coefs[i][1] = hwread(vortex->mmio, 0x24380 + i * 0x24); + coefs[i][2] = hwread(vortex->mmio, 0x24384 + i * 0x24); + coefs[i][3] = hwread(vortex->mmio, 0x24388 + i * 0x24); + } + arg_0[0] = hwread(vortex->mmio, 0x24518); + arg_0[1] = hwread(vortex->mmio, 0x2451C); + arg_0[2] = hwread(vortex->mmio, 0x24520); + arg_0[3] = hwread(vortex->mmio, 0x24524); +} + +static void +vortex_XtalkHw_GetRightXTStates(vortex_t * vortex, xtalk_instate_t arg_0, + xtalk_state_t coefs) +{ + int i; + + for (i = 0; i < 5; i++) { + coefs[i][0] = hwread(vortex->mmio, 0x24430 + i * 0x24); + coefs[i][1] = hwread(vortex->mmio, 0x24434 + i * 0x24); + coefs[i][2] = hwread(vortex->mmio, 0x24438 + i * 0x24); + coefs[i][3] = hwread(vortex->mmio, 0x2443C + i * 0x24); + } + arg_0[0] = hwread(vortex->mmio, 0x24528); + arg_0[1] = hwread(vortex->mmio, 0x2452C); + arg_0[2] = hwread(vortex->mmio, 0x24530); + arg_0[3] = hwread(vortex->mmio, 0x24534); +} + +#endif +/* Gains */ + +static void +vortex_XtalkHw_SetGains(vortex_t * vortex, xtalk_gains_t const gains) +{ + int i; + + for (i = 0; i < XTGAINS_SZ; i++) { + hwwrite(vortex->mmio, 0x244D0 + (i * 4), gains[i]); + } +} + +static void +vortex_XtalkHw_SetGainsAllChan(vortex_t * vortex) +{ + vortex_XtalkHw_SetGains(vortex, asXtalkGainsAllChan); +} + +#if 0 +static void vortex_XtalkHw_GetGains(vortex_t * vortex, xtalk_gains_t gains) +{ + int i; + + for (i = 0; i < XTGAINS_SZ; i++) + gains[i] = hwread(vortex->mmio, 0x244D0 + i * 4); +} + +#endif +/* Delay parameters */ + +static void +vortex_XtalkHw_SetDelay(vortex_t * vortex, unsigned short right, + unsigned short left) +{ + u32 esp0 = 0; + + esp0 &= 0x1FFFFFFF; + esp0 |= 0xA0000000; + esp0 = (esp0 & 0xffffE0ff) | ((right & 0x1F) << 8); + esp0 = (esp0 & 0xfffc1fff) | ((left & 0x1F) << 0xd); + + hwwrite(vortex->mmio, 0x24660, esp0); +} + +static void +vortex_XtalkHw_SetLeftDline(vortex_t * vortex, xtalk_dline_t const dline) +{ + int i; + + for (i = 0; i < 0x20; i++) { + hwwrite(vortex->mmio, 0x24000 + (i << 2), dline[i] & 0xffff); + hwwrite(vortex->mmio, 0x24080 + (i << 2), dline[i] >> 0x10); + } +} + +static void +vortex_XtalkHw_SetRightDline(vortex_t * vortex, xtalk_dline_t const dline) +{ + int i; + + for (i = 0; i < 0x20; i++) { + hwwrite(vortex->mmio, 0x24100 + (i << 2), dline[i] & 0xffff); + hwwrite(vortex->mmio, 0x24180 + (i << 2), dline[i] >> 0x10); + } +} + +#if 0 +static void +vortex_XtalkHw_GetDelay(vortex_t * vortex, unsigned short *right, + unsigned short *left) +{ + int esp0; + + esp0 = hwread(vortex->mmio, 0x24660); + *right = (esp0 >> 8) & 0x1f; + *left = (esp0 >> 0xd) & 0x1f; +} + +static void vortex_XtalkHw_GetLeftDline(vortex_t * vortex, xtalk_dline_t dline) +{ + int i; + + for (i = 0; i < 0x20; i++) { + dline[i] = + (hwread(vortex->mmio, 0x24000 + (i << 2)) & 0xffff) | + (hwread(vortex->mmio, 0x24080 + (i << 2)) << 0x10); + } +} + +static void vortex_XtalkHw_GetRightDline(vortex_t * vortex, xtalk_dline_t dline) +{ + int i; + + for (i = 0; i < 0x20; i++) { + dline[i] = + (hwread(vortex->mmio, 0x24100 + (i << 2)) & 0xffff) | + (hwread(vortex->mmio, 0x24180 + (i << 2)) << 0x10); + } +} + +#endif +/* Control/Global stuff */ + +#if 0 +static void vortex_XtalkHw_SetControlReg(vortex_t * vortex, u32 ctrl) +{ + hwwrite(vortex->mmio, 0x24660, ctrl); +} +static void vortex_XtalkHw_GetControlReg(vortex_t * vortex, u32 *ctrl) +{ + *ctrl = hwread(vortex->mmio, 0x24660); +} +#endif +static void vortex_XtalkHw_SetSampleRate(vortex_t * vortex, u32 sr) +{ + u32 temp; + + temp = (hwread(vortex->mmio, 0x24660) & 0x1FFFFFFF) | 0xC0000000; + temp = (temp & 0xffffff07) | ((sr & 0x1f) << 3); + hwwrite(vortex->mmio, 0x24660, temp); +} + +#if 0 +static void vortex_XtalkHw_GetSampleRate(vortex_t * vortex, u32 *sr) +{ + *sr = (hwread(vortex->mmio, 0x24660) >> 3) & 0x1f; +} + +#endif +static void vortex_XtalkHw_Enable(vortex_t * vortex) +{ + u32 temp; + + temp = (hwread(vortex->mmio, 0x24660) & 0x1FFFFFFF) | 0xC0000000; + temp |= 1; + hwwrite(vortex->mmio, 0x24660, temp); + +} + +static void vortex_XtalkHw_Disable(vortex_t * vortex) +{ + u32 temp; + + temp = (hwread(vortex->mmio, 0x24660) & 0x1FFFFFFF) | 0xC0000000; + temp &= 0xfffffffe; + hwwrite(vortex->mmio, 0x24660, temp); + +} + +static void vortex_XtalkHw_ZeroIO(vortex_t * vortex) +{ + int i; + + for (i = 0; i < 20; i++) + hwwrite(vortex->mmio, 0x24600 + (i << 2), 0); + for (i = 0; i < 4; i++) + hwwrite(vortex->mmio, 0x24650 + (i << 2), 0); +} + +static void vortex_XtalkHw_ZeroState(vortex_t * vortex) +{ + vortex_XtalkHw_ZeroIO(vortex); // inlined + + vortex_XtalkHw_SetLeftEQ(vortex, 0, 0, asXtalkCoefsZeros); + vortex_XtalkHw_SetRightEQ(vortex, 0, 0, asXtalkCoefsZeros); + + vortex_XtalkHw_SetLeftXT(vortex, 0, 0, asXtalkCoefsZeros); + vortex_XtalkHw_SetRightXT(vortex, 0, 0, asXtalkCoefsZeros); + + vortex_XtalkHw_SetGains(vortex, asXtalkGainsZeros); // inlined + + vortex_XtalkHw_SetDelay(vortex, 0, 0); // inlined + + vortex_XtalkHw_SetLeftDline(vortex, alXtalkDlineZeros); // inlined + vortex_XtalkHw_SetRightDline(vortex, alXtalkDlineZeros); // inlined + vortex_XtalkHw_SetLeftDline(vortex, alXtalkDlineZeros); // inlined + vortex_XtalkHw_SetRightDline(vortex, alXtalkDlineZeros); // inlined + + vortex_XtalkHw_SetLeftEQStates(vortex, asXtalkInStateZeros, + asXtalkOutStateZeros); + vortex_XtalkHw_SetRightEQStates(vortex, asXtalkInStateZeros, + asXtalkOutStateZeros); + vortex_XtalkHw_SetLeftXTStates(vortex, asXtalkInStateZeros, + asXtalkOutStateZeros); + vortex_XtalkHw_SetRightXTStates(vortex, asXtalkInStateZeros, + asXtalkOutStateZeros); +} + +static void vortex_XtalkHw_ProgramPipe(vortex_t * vortex) +{ + + vortex_XtalkHw_SetLeftEQ(vortex, 0, 1, asXtalkCoefsPipe); + vortex_XtalkHw_SetRightEQ(vortex, 0, 1, asXtalkCoefsPipe); + vortex_XtalkHw_SetLeftXT(vortex, 0, 0, asXtalkCoefsZeros); + vortex_XtalkHw_SetRightXT(vortex, 0, 0, asXtalkCoefsZeros); + + vortex_XtalkHw_SetDelay(vortex, 0, 0); // inlined +} + +static void vortex_XtalkHw_ProgramXtalkWide(vortex_t * vortex) +{ + + vortex_XtalkHw_SetLeftEQ(vortex, sXtalkWideKLeftEq, + sXtalkWideShiftLeftEq, asXtalkWideCoefsLeftEq); + vortex_XtalkHw_SetRightEQ(vortex, sXtalkWideKRightEq, + sXtalkWideShiftRightEq, + asXtalkWideCoefsRightEq); + vortex_XtalkHw_SetLeftXT(vortex, sXtalkWideKLeftXt, + sXtalkWideShiftLeftXt, asXtalkWideCoefsLeftXt); + vortex_XtalkHw_SetRightXT(vortex, sXtalkWideKLeftXt, + sXtalkWideShiftLeftXt, + asXtalkWideCoefsLeftXt); + + vortex_XtalkHw_SetDelay(vortex, wXtalkWideRightDelay, wXtalkWideLeftDelay); // inlined +} + +static void vortex_XtalkHw_ProgramXtalkNarrow(vortex_t * vortex) +{ + + vortex_XtalkHw_SetLeftEQ(vortex, sXtalkNarrowKLeftEq, + sXtalkNarrowShiftLeftEq, + asXtalkNarrowCoefsLeftEq); + vortex_XtalkHw_SetRightEQ(vortex, sXtalkNarrowKRightEq, + sXtalkNarrowShiftRightEq, + asXtalkNarrowCoefsRightEq); + vortex_XtalkHw_SetLeftXT(vortex, sXtalkNarrowKLeftXt, + sXtalkNarrowShiftLeftXt, + asXtalkNarrowCoefsLeftXt); + vortex_XtalkHw_SetRightXT(vortex, sXtalkNarrowKLeftXt, + sXtalkNarrowShiftLeftXt, + asXtalkNarrowCoefsLeftXt); + + vortex_XtalkHw_SetDelay(vortex, wXtalkNarrowRightDelay, wXtalkNarrowLeftDelay); // inlined +} + +static void vortex_XtalkHw_ProgramDiamondXtalk(vortex_t * vortex) +{ + + //sDiamondKLeftEq,sDiamondKRightXt,asDiamondCoefsLeftEq + vortex_XtalkHw_SetLeftEQ(vortex, sDiamondKLeftEq, + sDiamondShiftLeftEq, asDiamondCoefsLeftEq); + vortex_XtalkHw_SetRightEQ(vortex, sDiamondKRightEq, + sDiamondShiftRightEq, asDiamondCoefsRightEq); + vortex_XtalkHw_SetLeftXT(vortex, sDiamondKLeftXt, + sDiamondShiftLeftXt, asDiamondCoefsLeftXt); + vortex_XtalkHw_SetRightXT(vortex, sDiamondKLeftXt, + sDiamondShiftLeftXt, asDiamondCoefsLeftXt); + + vortex_XtalkHw_SetDelay(vortex, wDiamondRightDelay, wDiamondLeftDelay); // inlined +} + +static void vortex_XtalkHw_init(vortex_t * vortex) +{ + vortex_XtalkHw_ZeroState(vortex); +} + +/* End of file */ diff --git a/sound/pci/au88x0/au88x0_xtalk.h b/sound/pci/au88x0/au88x0_xtalk.h new file mode 100644 index 000000000..4791bd4fe --- /dev/null +++ b/sound/pci/au88x0/au88x0_xtalk.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/*************************************************************************** + * au88x0_cxtalk.h + * + * Wed Nov 19 19:07:17 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.org + ****************************************************************************/ + +/* + */ + +/* The crosstalk canceler supports 5 stereo input channels. The result is + available at one single output route pair (stereo). */ + +#ifndef _AU88X0_CXTALK_H +#define _AU88X0_CXTALK_H + +#include "au88x0.h" + +#define XTDLINE_SZ 32 +#define XTGAINS_SZ 10 +#define XTINST_SZ 4 + +#define XT_HEADPHONE 1 +#define XT_SPEAKER0 2 +#define XT_SPEAKER1 3 +#define XT_DIAMOND 4 + +typedef u32 xtalk_dline_t[XTDLINE_SZ]; +typedef u16 xtalk_gains_t[XTGAINS_SZ]; +typedef u16 xtalk_instate_t[XTINST_SZ]; +typedef u16 xtalk_coefs_t[5][5]; +typedef u16 xtalk_state_t[5][4]; + +static void vortex_XtalkHw_SetGains(vortex_t * vortex, + xtalk_gains_t const gains); +static void vortex_XtalkHw_SetGainsAllChan(vortex_t * vortex); +static void vortex_XtalkHw_SetSampleRate(vortex_t * vortex, u32 sr); +static void vortex_XtalkHw_ProgramPipe(vortex_t * vortex); +static void vortex_XtalkHw_ProgramPipe(vortex_t * vortex); +static void vortex_XtalkHw_ProgramXtalkWide(vortex_t * vortex); +static void vortex_XtalkHw_ProgramXtalkNarrow(vortex_t * vortex); +static void vortex_XtalkHw_ProgramDiamondXtalk(vortex_t * vortex); +static void vortex_XtalkHw_Enable(vortex_t * vortex); +static void vortex_XtalkHw_Disable(vortex_t * vortex); +static void vortex_XtalkHw_init(vortex_t * vortex); + +#endif /* _AU88X0_CXTALK_H */ diff --git a/sound/pci/aw2/Makefile b/sound/pci/aw2/Makefile new file mode 100644 index 000000000..c246f7c7f --- /dev/null +++ b/sound/pci/aw2/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-aw2-y := aw2-alsa.o aw2-saa7146.o + +obj-$(CONFIG_SND_AW2) += snd-aw2.o diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c new file mode 100644 index 000000000..60a87322e --- /dev/null +++ b/sound/pci/aw2/aw2-alsa.c @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: GPL-2.0-only +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * + * This file is part of the Audiowerk2 ALSA driver + * + *****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "saa7146.h" +#include "aw2-saa7146.h" + +MODULE_AUTHOR("Cedric Bregardis , " + "Jean-Christian Hassler "); +MODULE_DESCRIPTION("Emagic Audiowerk 2 sound driver"); +MODULE_LICENSE("GPL"); + +/********************************* + * DEFINES + ********************************/ +#define CTL_ROUTE_ANALOG 0 +#define CTL_ROUTE_DIGITAL 1 + +/********************************* + * TYPEDEFS + ********************************/ + /* hardware definition */ +static const struct snd_pcm_hardware snd_aw2_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 4, + .buffer_bytes_max = 32768, + .period_bytes_min = 4096, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 1024, +}; + +static const struct snd_pcm_hardware snd_aw2_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 32768, + .period_bytes_min = 4096, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 1024, +}; + +struct aw2_pcm_device { + struct snd_pcm *pcm; + unsigned int stream_number; + struct aw2 *chip; +}; + +struct aw2 { + struct snd_aw2_saa7146 saa7146; + + struct pci_dev *pci; + int irq; + spinlock_t reg_lock; + struct mutex mtx; + + unsigned long iobase_phys; + void __iomem *iobase_virt; + + struct snd_card *card; + + struct aw2_pcm_device device_playback[NB_STREAM_PLAYBACK]; + struct aw2_pcm_device device_capture[NB_STREAM_CAPTURE]; +}; + +/********************************* + * FUNCTION DECLARATIONS + ********************************/ +static int snd_aw2_create(struct snd_card *card, struct pci_dev *pci); +static int snd_aw2_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id); +static int snd_aw2_pcm_playback_open(struct snd_pcm_substream *substream); +static int snd_aw2_pcm_playback_close(struct snd_pcm_substream *substream); +static int snd_aw2_pcm_capture_open(struct snd_pcm_substream *substream); +static int snd_aw2_pcm_capture_close(struct snd_pcm_substream *substream); +static int snd_aw2_pcm_prepare_playback(struct snd_pcm_substream *substream); +static int snd_aw2_pcm_prepare_capture(struct snd_pcm_substream *substream); +static int snd_aw2_pcm_trigger_playback(struct snd_pcm_substream *substream, + int cmd); +static int snd_aw2_pcm_trigger_capture(struct snd_pcm_substream *substream, + int cmd); +static snd_pcm_uframes_t snd_aw2_pcm_pointer_playback(struct snd_pcm_substream + *substream); +static snd_pcm_uframes_t snd_aw2_pcm_pointer_capture(struct snd_pcm_substream + *substream); +static int snd_aw2_new_pcm(struct aw2 *chip); + +static int snd_aw2_control_switch_capture_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +static int snd_aw2_control_switch_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value + *ucontrol); +static int snd_aw2_control_switch_capture_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value + *ucontrol); + +/********************************* + * VARIABLES + ********************************/ +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Audiowerk2 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the Audiowerk2 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Audiowerk2 soundcard."); + +static const struct pci_device_id snd_aw2_ids[] = { + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0, 0) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_aw2_ids); + +/* pci_driver definition */ +static struct pci_driver aw2_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_aw2_ids, + .probe = snd_aw2_probe, +}; + +module_pci_driver(aw2_driver); + +/* operators for playback PCM alsa interface */ +static const struct snd_pcm_ops snd_aw2_playback_ops = { + .open = snd_aw2_pcm_playback_open, + .close = snd_aw2_pcm_playback_close, + .prepare = snd_aw2_pcm_prepare_playback, + .trigger = snd_aw2_pcm_trigger_playback, + .pointer = snd_aw2_pcm_pointer_playback, +}; + +/* operators for capture PCM alsa interface */ +static const struct snd_pcm_ops snd_aw2_capture_ops = { + .open = snd_aw2_pcm_capture_open, + .close = snd_aw2_pcm_capture_close, + .prepare = snd_aw2_pcm_prepare_capture, + .trigger = snd_aw2_pcm_trigger_capture, + .pointer = snd_aw2_pcm_pointer_capture, +}; + +static const struct snd_kcontrol_new aw2_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Capture Route", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0xffff, + .info = snd_aw2_control_switch_capture_info, + .get = snd_aw2_control_switch_capture_get, + .put = snd_aw2_control_switch_capture_put +}; + +/********************************* + * FUNCTION IMPLEMENTATIONS + ********************************/ + +/* component-destructor */ +static void snd_aw2_free(struct snd_card *card) +{ + struct aw2 *chip = card->private_data; + + /* Free hardware */ + snd_aw2_saa7146_free(&chip->saa7146); +} + +/* chip-specific constructor */ +static int snd_aw2_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct aw2 *chip = card->private_data; + int err; + + /* initialize the PCI entry */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + pci_set_master(pci); + + /* check PCI availability (32bit DMA) */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32))) { + dev_err(card->dev, "Impossible to set 32bit mask DMA\n"); + return -ENXIO; + } + + /* initialize the stuff */ + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + /* (1) PCI resource allocation */ + chip->iobase_virt = pcim_iomap_region(pci, 0, "Audiowerk2"); + if (IS_ERR(chip->iobase_virt)) + return PTR_ERR(chip->iobase_virt); + chip->iobase_phys = pci_resource_start(pci, 0); + + /* (2) initialization of the chip hardware */ + snd_aw2_saa7146_setup(&chip->saa7146, chip->iobase_virt); + + if (devm_request_irq(&pci->dev, pci->irq, snd_aw2_saa7146_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "Cannot grab irq %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_aw2_free; + + dev_info(card->dev, + "Audiowerk 2 sound card (saa7146 chipset) detected and managed\n"); + return 0; +} + +/* constructor */ +static int snd_aw2_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct aw2 *chip; + int err; + + /* (1) Continue if device is not enabled, else inc dev */ + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + /* (2) Create card instance */ + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + /* (3) Create main component */ + err = snd_aw2_create(card, pci); + if (err < 0) + goto error; + + /* initialize mutex */ + mutex_init(&chip->mtx); + /* init spinlock */ + spin_lock_init(&chip->reg_lock); + /* (4) Define driver ID and name string */ + strscpy(card->driver, "aw2"); + strscpy(card->shortname, "Audiowerk2"); + + sprintf(card->longname, "%s with SAA7146 irq %i", + card->shortname, chip->irq); + + /* (5) Create other components */ + snd_aw2_new_pcm(chip); + + /* (6) Register card instance */ + err = snd_card_register(card); + if (err < 0) + goto error; + + /* (7) Set PCI driver data */ + pci_set_drvdata(pci, card); + + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +/* open callback */ +static int snd_aw2_pcm_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + dev_dbg(substream->pcm->card->dev, "Playback_open\n"); + runtime->hw = snd_aw2_playback_hw; + return 0; +} + +/* close callback */ +static int snd_aw2_pcm_playback_close(struct snd_pcm_substream *substream) +{ + return 0; + +} + +static int snd_aw2_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + dev_dbg(substream->pcm->card->dev, "Capture_open\n"); + runtime->hw = snd_aw2_capture_hw; + return 0; +} + +/* close callback */ +static int snd_aw2_pcm_capture_close(struct snd_pcm_substream *substream) +{ + /* TODO: something to do ? */ + return 0; +} + +/* prepare callback for playback */ +static int snd_aw2_pcm_prepare_playback(struct snd_pcm_substream *substream) +{ + struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream); + struct aw2 *chip = pcm_device->chip; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long period_size, buffer_size; + + guard(mutex)(&chip->mtx); + + period_size = snd_pcm_lib_period_bytes(substream); + buffer_size = snd_pcm_lib_buffer_bytes(substream); + + snd_aw2_saa7146_pcm_init_playback(&chip->saa7146, + pcm_device->stream_number, + runtime->dma_addr, period_size, + buffer_size); + + /* Define Interrupt callback */ + snd_aw2_saa7146_define_it_playback_callback(pcm_device->stream_number, + (snd_aw2_saa7146_it_cb) + snd_pcm_period_elapsed, + (void *)substream); + + return 0; +} + +/* prepare callback for capture */ +static int snd_aw2_pcm_prepare_capture(struct snd_pcm_substream *substream) +{ + struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream); + struct aw2 *chip = pcm_device->chip; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long period_size, buffer_size; + + guard(mutex)(&chip->mtx); + + period_size = snd_pcm_lib_period_bytes(substream); + buffer_size = snd_pcm_lib_buffer_bytes(substream); + + snd_aw2_saa7146_pcm_init_capture(&chip->saa7146, + pcm_device->stream_number, + runtime->dma_addr, period_size, + buffer_size); + + /* Define Interrupt callback */ + snd_aw2_saa7146_define_it_capture_callback(pcm_device->stream_number, + (snd_aw2_saa7146_it_cb) + snd_pcm_period_elapsed, + (void *)substream); + + return 0; +} + +/* playback trigger callback */ +static int snd_aw2_pcm_trigger_playback(struct snd_pcm_substream *substream, + int cmd) +{ + struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream); + struct aw2 *chip = pcm_device->chip; + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_aw2_saa7146_pcm_trigger_start_playback(&chip->saa7146, + pcm_device-> + stream_number); + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_aw2_saa7146_pcm_trigger_stop_playback(&chip->saa7146, + pcm_device-> + stream_number); + break; + default: + return -EINVAL; + } + return 0; +} + +/* capture trigger callback */ +static int snd_aw2_pcm_trigger_capture(struct snd_pcm_substream *substream, + int cmd) +{ + struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream); + struct aw2 *chip = pcm_device->chip; + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_aw2_saa7146_pcm_trigger_start_capture(&chip->saa7146, + pcm_device-> + stream_number); + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_aw2_saa7146_pcm_trigger_stop_capture(&chip->saa7146, + pcm_device-> + stream_number); + break; + default: + return -EINVAL; + } + return 0; +} + +/* playback pointer callback */ +static snd_pcm_uframes_t snd_aw2_pcm_pointer_playback(struct snd_pcm_substream + *substream) +{ + struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream); + struct aw2 *chip = pcm_device->chip; + unsigned int current_ptr; + + /* get the current hardware pointer */ + struct snd_pcm_runtime *runtime = substream->runtime; + current_ptr = + snd_aw2_saa7146_get_hw_ptr_playback(&chip->saa7146, + pcm_device->stream_number, + runtime->dma_area, + runtime->buffer_size); + + return bytes_to_frames(substream->runtime, current_ptr); +} + +/* capture pointer callback */ +static snd_pcm_uframes_t snd_aw2_pcm_pointer_capture(struct snd_pcm_substream + *substream) +{ + struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream); + struct aw2 *chip = pcm_device->chip; + unsigned int current_ptr; + + /* get the current hardware pointer */ + struct snd_pcm_runtime *runtime = substream->runtime; + current_ptr = + snd_aw2_saa7146_get_hw_ptr_capture(&chip->saa7146, + pcm_device->stream_number, + runtime->dma_area, + runtime->buffer_size); + + return bytes_to_frames(substream->runtime, current_ptr); +} + +/* create a pcm device */ +static int snd_aw2_new_pcm(struct aw2 *chip) +{ + struct snd_pcm *pcm_playback_ana; + struct snd_pcm *pcm_playback_num; + struct snd_pcm *pcm_capture; + struct aw2_pcm_device *pcm_device; + int err = 0; + + /* Create new Alsa PCM device */ + + err = snd_pcm_new(chip->card, "Audiowerk2 analog playback", 0, 1, 0, + &pcm_playback_ana); + if (err < 0) { + dev_err(chip->card->dev, "snd_pcm_new error (0x%X)\n", err); + return err; + } + + /* Creation ok */ + pcm_device = &chip->device_playback[NUM_STREAM_PLAYBACK_ANA]; + + /* Set PCM device name */ + strscpy(pcm_playback_ana->name, "Analog playback"); + /* Associate private data to PCM device */ + pcm_playback_ana->private_data = pcm_device; + /* set operators of PCM device */ + snd_pcm_set_ops(pcm_playback_ana, SNDRV_PCM_STREAM_PLAYBACK, + &snd_aw2_playback_ops); + /* store PCM device */ + pcm_device->pcm = pcm_playback_ana; + /* give base chip pointer to our internal pcm device + structure */ + pcm_device->chip = chip; + /* Give stream number to PCM device */ + pcm_device->stream_number = NUM_STREAM_PLAYBACK_ANA; + + /* pre-allocation of buffers */ + /* Preallocate continuous pages. */ + snd_pcm_set_managed_buffer_all(pcm_playback_ana, + SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64 * 1024, 64 * 1024); + + err = snd_pcm_new(chip->card, "Audiowerk2 digital playback", 1, 1, 0, + &pcm_playback_num); + + if (err < 0) { + dev_err(chip->card->dev, "snd_pcm_new error (0x%X)\n", err); + return err; + } + /* Creation ok */ + pcm_device = &chip->device_playback[NUM_STREAM_PLAYBACK_DIG]; + + /* Set PCM device name */ + strscpy(pcm_playback_num->name, "Digital playback"); + /* Associate private data to PCM device */ + pcm_playback_num->private_data = pcm_device; + /* set operators of PCM device */ + snd_pcm_set_ops(pcm_playback_num, SNDRV_PCM_STREAM_PLAYBACK, + &snd_aw2_playback_ops); + /* store PCM device */ + pcm_device->pcm = pcm_playback_num; + /* give base chip pointer to our internal pcm device + structure */ + pcm_device->chip = chip; + /* Give stream number to PCM device */ + pcm_device->stream_number = NUM_STREAM_PLAYBACK_DIG; + + /* pre-allocation of buffers */ + /* Preallocate continuous pages. */ + snd_pcm_set_managed_buffer_all(pcm_playback_num, + SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64 * 1024, 64 * 1024); + + err = snd_pcm_new(chip->card, "Audiowerk2 capture", 2, 0, 1, + &pcm_capture); + + if (err < 0) { + dev_err(chip->card->dev, "snd_pcm_new error (0x%X)\n", err); + return err; + } + + /* Creation ok */ + pcm_device = &chip->device_capture[NUM_STREAM_CAPTURE_ANA]; + + /* Set PCM device name */ + strscpy(pcm_capture->name, "Capture"); + /* Associate private data to PCM device */ + pcm_capture->private_data = pcm_device; + /* set operators of PCM device */ + snd_pcm_set_ops(pcm_capture, SNDRV_PCM_STREAM_CAPTURE, + &snd_aw2_capture_ops); + /* store PCM device */ + pcm_device->pcm = pcm_capture; + /* give base chip pointer to our internal pcm device + structure */ + pcm_device->chip = chip; + /* Give stream number to PCM device */ + pcm_device->stream_number = NUM_STREAM_CAPTURE_ANA; + + /* pre-allocation of buffers */ + /* Preallocate continuous pages. */ + snd_pcm_set_managed_buffer_all(pcm_capture, + SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64 * 1024, 64 * 1024); + + /* Create control */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&aw2_control, chip)); + if (err < 0) { + dev_err(chip->card->dev, "snd_ctl_add error (0x%X)\n", err); + return err; + } + + return 0; +} + +static int snd_aw2_control_switch_capture_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { + "Analog", "Digital" + }; + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_aw2_control_switch_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value + *ucontrol) +{ + struct aw2 *chip = snd_kcontrol_chip(kcontrol); + if (snd_aw2_saa7146_is_using_digital_input(&chip->saa7146)) + ucontrol->value.enumerated.item[0] = CTL_ROUTE_DIGITAL; + else + ucontrol->value.enumerated.item[0] = CTL_ROUTE_ANALOG; + return 0; +} + +static int snd_aw2_control_switch_capture_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value + *ucontrol) +{ + struct aw2 *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int is_disgital = + snd_aw2_saa7146_is_using_digital_input(&chip->saa7146); + + if (((ucontrol->value.integer.value[0] == CTL_ROUTE_DIGITAL) + && !is_disgital) + || ((ucontrol->value.integer.value[0] == CTL_ROUTE_ANALOG) + && is_disgital)) { + snd_aw2_saa7146_use_digital_input(&chip->saa7146, !is_disgital); + changed = 1; + } + return changed; +} diff --git a/sound/pci/aw2/aw2-saa7146.c b/sound/pci/aw2/aw2-saa7146.c new file mode 100644 index 000000000..c84f1a451 --- /dev/null +++ b/sound/pci/aw2/aw2-saa7146.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0-only +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * + * This file is part of the Audiowerk2 ALSA driver + * + *****************************************************************************/ + +#define AW2_SAA7146_M + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "saa7146.h" +#include "aw2-saa7146.h" + +#include "aw2-tsl.c" + +#define WRITEREG(value, addr) writel((value), chip->base_addr + (addr)) +#define READREG(addr) readl(chip->base_addr + (addr)) + +static struct snd_aw2_saa7146_cb_param + arr_substream_it_playback_cb[NB_STREAM_PLAYBACK]; +static struct snd_aw2_saa7146_cb_param + arr_substream_it_capture_cb[NB_STREAM_CAPTURE]; + +static int snd_aw2_saa7146_get_limit(int size); + +/* chip-specific destructor */ +int snd_aw2_saa7146_free(struct snd_aw2_saa7146 *chip) +{ + /* disable all irqs */ + WRITEREG(0, IER); + + /* reset saa7146 */ + WRITEREG((MRST_N << 16), MC1); + + /* Unset base addr */ + chip->base_addr = NULL; + + return 0; +} + +void snd_aw2_saa7146_setup(struct snd_aw2_saa7146 *chip, + void __iomem *pci_base_addr) +{ + /* set PCI burst/threshold + + Burst length definition + VALUE BURST LENGTH + 000 1 Dword + 001 2 Dwords + 010 4 Dwords + 011 8 Dwords + 100 16 Dwords + 101 32 Dwords + 110 64 Dwords + 111 128 Dwords + + Threshold definition + VALUE WRITE MODE READ MODE + 00 1 Dword of valid data 1 empty Dword + 01 4 Dwords of valid data 4 empty Dwords + 10 8 Dwords of valid data 8 empty Dwords + 11 16 Dwords of valid data 16 empty Dwords */ + + unsigned int acon2; + unsigned int acon1 = 0; + int i; + + /* Set base addr */ + chip->base_addr = pci_base_addr; + + /* disable all irqs */ + WRITEREG(0, IER); + + /* reset saa7146 */ + WRITEREG((MRST_N << 16), MC1); + + /* enable audio interface */ +#ifdef __BIG_ENDIAN + acon1 |= A1_SWAP; + acon1 |= A2_SWAP; +#endif + /* WS0_CTRL, WS0_SYNC: input TSL1, I2S */ + + /* At initialization WS1 and WS2 are disabled (configured as input) */ + acon1 |= 0 * WS1_CTRL; + acon1 |= 0 * WS2_CTRL; + + /* WS4 is not used. So it must not restart A2. + This is why it is configured as output (force to low) */ + acon1 |= 3 * WS4_CTRL; + + /* WS3_CTRL, WS3_SYNC: output TSL2, I2S */ + acon1 |= 2 * WS3_CTRL; + + /* A1 and A2 are active and asynchronous */ + acon1 |= 3 * AUDIO_MODE; + WRITEREG(acon1, ACON1); + + /* The following comes from original windows driver. + It is needed to have a correct behavior of input and output + simultenously, but I don't know why ! */ + WRITEREG(3 * (BurstA1_in) + 3 * (ThreshA1_in) + + 3 * (BurstA1_out) + 3 * (ThreshA1_out) + + 3 * (BurstA2_out) + 3 * (ThreshA2_out), PCI_BT_A); + + /* enable audio port pins */ + WRITEREG((EAP << 16) | EAP, MC1); + + /* enable I2C */ + WRITEREG((EI2C << 16) | EI2C, MC1); + /* enable interrupts */ + WRITEREG(A1_out | A2_out | A1_in | IIC_S | IIC_E, IER); + + /* audio configuration */ + acon2 = A2_CLKSRC | BCLK1_OEN; + WRITEREG(acon2, ACON2); + + /* By default use analog input */ + snd_aw2_saa7146_use_digital_input(chip, 0); + + /* TSL setup */ + for (i = 0; i < 8; ++i) { + WRITEREG(tsl1[i], TSL1 + (i * 4)); + WRITEREG(tsl2[i], TSL2 + (i * 4)); + } + +} + +void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip, + int stream_number, + unsigned long dma_addr, + unsigned long period_size, + unsigned long buffer_size) +{ + unsigned long dw_page, dw_limit; + + /* Configure DMA for substream + Configuration informations: ALSA has allocated continuous memory + pages. So we don't need to use MMU of saa7146. + */ + + /* No MMU -> nothing to do with PageA1, we only configure the limit of + PageAx_out register */ + /* Disable MMU */ + dw_page = (0L << 11); + + /* Configure Limit for DMA access. + The limit register defines an address limit, which generates + an interrupt if passed by the actual PCI address pointer. + '0001' means an interrupt will be generated if the lower + 6 bits (64 bytes) of the PCI address are zero. '0010' + defines a limit of 128 bytes, '0011' one of 256 bytes, and + so on up to 1 Mbyte defined by '1111'. This interrupt range + can be calculated as follows: + Range = 2^(5 + Limit) bytes. + */ + dw_limit = snd_aw2_saa7146_get_limit(period_size); + dw_page |= (dw_limit << 4); + + if (stream_number == 0) { + WRITEREG(dw_page, PageA2_out); + + /* Base address for DMA transfert. */ + /* This address has been reserved by ALSA. */ + /* This is a physical address */ + WRITEREG(dma_addr, BaseA2_out); + + /* Define upper limit for DMA access */ + WRITEREG(dma_addr + buffer_size, ProtA2_out); + + } else if (stream_number == 1) { + WRITEREG(dw_page, PageA1_out); + + /* Base address for DMA transfert. */ + /* This address has been reserved by ALSA. */ + /* This is a physical address */ + WRITEREG(dma_addr, BaseA1_out); + + /* Define upper limit for DMA access */ + WRITEREG(dma_addr + buffer_size, ProtA1_out); + } else { + pr_err("aw2: snd_aw2_saa7146_pcm_init_playback: " + "Substream number is not 0 or 1 -> not managed\n"); + } +} + +void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip, + int stream_number, unsigned long dma_addr, + unsigned long period_size, + unsigned long buffer_size) +{ + unsigned long dw_page, dw_limit; + + /* Configure DMA for substream + Configuration informations: ALSA has allocated continuous memory + pages. So we don't need to use MMU of saa7146. + */ + + /* No MMU -> nothing to do with PageA1, we only configure the limit of + PageAx_out register */ + /* Disable MMU */ + dw_page = (0L << 11); + + /* Configure Limit for DMA access. + The limit register defines an address limit, which generates + an interrupt if passed by the actual PCI address pointer. + '0001' means an interrupt will be generated if the lower + 6 bits (64 bytes) of the PCI address are zero. '0010' + defines a limit of 128 bytes, '0011' one of 256 bytes, and + so on up to 1 Mbyte defined by '1111'. This interrupt range + can be calculated as follows: + Range = 2^(5 + Limit) bytes. + */ + dw_limit = snd_aw2_saa7146_get_limit(period_size); + dw_page |= (dw_limit << 4); + + if (stream_number == 0) { + WRITEREG(dw_page, PageA1_in); + + /* Base address for DMA transfert. */ + /* This address has been reserved by ALSA. */ + /* This is a physical address */ + WRITEREG(dma_addr, BaseA1_in); + + /* Define upper limit for DMA access */ + WRITEREG(dma_addr + buffer_size, ProtA1_in); + } else { + pr_err("aw2: snd_aw2_saa7146_pcm_init_capture: " + "Substream number is not 0 -> not managed\n"); + } +} + +void snd_aw2_saa7146_define_it_playback_callback(unsigned int stream_number, + snd_aw2_saa7146_it_cb + p_it_callback, + void *p_callback_param) +{ + if (stream_number < NB_STREAM_PLAYBACK) { + arr_substream_it_playback_cb[stream_number].p_it_callback = + (snd_aw2_saa7146_it_cb) p_it_callback; + arr_substream_it_playback_cb[stream_number].p_callback_param = + (void *)p_callback_param; + } +} + +void snd_aw2_saa7146_define_it_capture_callback(unsigned int stream_number, + snd_aw2_saa7146_it_cb + p_it_callback, + void *p_callback_param) +{ + if (stream_number < NB_STREAM_CAPTURE) { + arr_substream_it_capture_cb[stream_number].p_it_callback = + (snd_aw2_saa7146_it_cb) p_it_callback; + arr_substream_it_capture_cb[stream_number].p_callback_param = + (void *)p_callback_param; + } +} + +void snd_aw2_saa7146_pcm_trigger_start_playback(struct snd_aw2_saa7146 *chip, + int stream_number) +{ + unsigned int acon1 = 0; + /* In aw8 driver, dma transfert is always active. It is + started and stopped in a larger "space" */ + acon1 = READREG(ACON1); + if (stream_number == 0) { + WRITEREG((TR_E_A2_OUT << 16) | TR_E_A2_OUT, MC1); + + /* WS2_CTRL, WS2_SYNC: output TSL2, I2S */ + acon1 |= 2 * WS2_CTRL; + WRITEREG(acon1, ACON1); + + } else if (stream_number == 1) { + WRITEREG((TR_E_A1_OUT << 16) | TR_E_A1_OUT, MC1); + + /* WS1_CTRL, WS1_SYNC: output TSL1, I2S */ + acon1 |= 1 * WS1_CTRL; + WRITEREG(acon1, ACON1); + } +} + +void snd_aw2_saa7146_pcm_trigger_stop_playback(struct snd_aw2_saa7146 *chip, + int stream_number) +{ + unsigned int acon1 = 0; + acon1 = READREG(ACON1); + if (stream_number == 0) { + /* WS2_CTRL, WS2_SYNC: output TSL2, I2S */ + acon1 &= ~(3 * WS2_CTRL); + WRITEREG(acon1, ACON1); + + WRITEREG((TR_E_A2_OUT << 16), MC1); + } else if (stream_number == 1) { + /* WS1_CTRL, WS1_SYNC: output TSL1, I2S */ + acon1 &= ~(3 * WS1_CTRL); + WRITEREG(acon1, ACON1); + + WRITEREG((TR_E_A1_OUT << 16), MC1); + } +} + +void snd_aw2_saa7146_pcm_trigger_start_capture(struct snd_aw2_saa7146 *chip, + int stream_number) +{ + /* In aw8 driver, dma transfert is always active. It is + started and stopped in a larger "space" */ + if (stream_number == 0) + WRITEREG((TR_E_A1_IN << 16) | TR_E_A1_IN, MC1); +} + +void snd_aw2_saa7146_pcm_trigger_stop_capture(struct snd_aw2_saa7146 *chip, + int stream_number) +{ + if (stream_number == 0) + WRITEREG((TR_E_A1_IN << 16), MC1); +} + +irqreturn_t snd_aw2_saa7146_interrupt(int irq, void *dev_id) +{ + unsigned int isr; + __always_unused unsigned int iicsta; + struct snd_aw2_saa7146 *chip = dev_id; + + isr = READREG(ISR); + if (!isr) + return IRQ_NONE; + + WRITEREG(isr, ISR); + + if (isr & (IIC_S | IIC_E)) { + iicsta = READREG(IICSTA); + WRITEREG(0x100, IICSTA); + } + + if (isr & A1_out) { + if (arr_substream_it_playback_cb[1].p_it_callback != NULL) { + arr_substream_it_playback_cb[1]. + p_it_callback(arr_substream_it_playback_cb[1]. + p_callback_param); + } + } + if (isr & A2_out) { + if (arr_substream_it_playback_cb[0].p_it_callback != NULL) { + arr_substream_it_playback_cb[0]. + p_it_callback(arr_substream_it_playback_cb[0]. + p_callback_param); + } + + } + if (isr & A1_in) { + if (arr_substream_it_capture_cb[0].p_it_callback != NULL) { + arr_substream_it_capture_cb[0]. + p_it_callback(arr_substream_it_capture_cb[0]. + p_callback_param); + } + } + return IRQ_HANDLED; +} + +unsigned int snd_aw2_saa7146_get_hw_ptr_playback(struct snd_aw2_saa7146 *chip, + int stream_number, + unsigned char *start_addr, + unsigned int buffer_size) +{ + long pci_adp = 0; + size_t ptr = 0; + + if (stream_number == 0) { + pci_adp = READREG(PCI_ADP3); + ptr = pci_adp - (long)start_addr; + + if (ptr == buffer_size) + ptr = 0; + } + if (stream_number == 1) { + pci_adp = READREG(PCI_ADP1); + ptr = pci_adp - (size_t) start_addr; + + if (ptr == buffer_size) + ptr = 0; + } + return ptr; +} + +unsigned int snd_aw2_saa7146_get_hw_ptr_capture(struct snd_aw2_saa7146 *chip, + int stream_number, + unsigned char *start_addr, + unsigned int buffer_size) +{ + size_t pci_adp = 0; + size_t ptr = 0; + if (stream_number == 0) { + pci_adp = READREG(PCI_ADP2); + ptr = pci_adp - (size_t) start_addr; + + if (ptr == buffer_size) + ptr = 0; + } + return ptr; +} + +void snd_aw2_saa7146_use_digital_input(struct snd_aw2_saa7146 *chip, + int use_digital) +{ + /* FIXME: switch between analog and digital input does not always work. + It can produce a kind of white noise. It seams that received data + are inverted sometime (endian inversion). Why ? I don't know, maybe + a problem of synchronization... However for the time being I have + not found the problem. Workaround: switch again (and again) between + digital and analog input until it works. */ + if (use_digital) + WRITEREG(0x40, GPIO_CTRL); + else + WRITEREG(0x50, GPIO_CTRL); +} + +int snd_aw2_saa7146_is_using_digital_input(struct snd_aw2_saa7146 *chip) +{ + unsigned int reg_val = READREG(GPIO_CTRL); + if ((reg_val & 0xFF) == 0x40) + return 1; + else + return 0; +} + + +static int snd_aw2_saa7146_get_limit(int size) +{ + int limitsize = 32; + int limit = 0; + while (limitsize < size) { + limitsize *= 2; + limit++; + } + return limit; +} diff --git a/sound/pci/aw2/aw2-saa7146.h b/sound/pci/aw2/aw2-saa7146.h new file mode 100644 index 000000000..3a3de56b9 --- /dev/null +++ b/sound/pci/aw2/aw2-saa7146.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * + * This file is part of the Audiowerk2 ALSA driver + * + *****************************************************************************/ + +#ifndef AW2_SAA7146_H +#define AW2_SAA7146_H + +#define NB_STREAM_PLAYBACK 2 +#define NB_STREAM_CAPTURE 1 + +#define NUM_STREAM_PLAYBACK_ANA 0 +#define NUM_STREAM_PLAYBACK_DIG 1 + +#define NUM_STREAM_CAPTURE_ANA 0 + +struct snd_pcm_substream; +typedef void (*snd_aw2_saa7146_it_cb) (struct snd_pcm_substream *); + +struct snd_aw2_saa7146_cb_param { + snd_aw2_saa7146_it_cb p_it_callback; + struct snd_pcm_substream *p_callback_param; +}; + +/* definition of the chip-specific record */ + +struct snd_aw2_saa7146 { + void __iomem *base_addr; +}; + +extern void snd_aw2_saa7146_setup(struct snd_aw2_saa7146 *chip, + void __iomem *pci_base_addr); +extern int snd_aw2_saa7146_free(struct snd_aw2_saa7146 *chip); + +extern void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip, + int stream_number, + unsigned long dma_addr, + unsigned long period_size, + unsigned long buffer_size); +extern void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip, + int stream_number, + unsigned long dma_addr, + unsigned long period_size, + unsigned long buffer_size); +extern void snd_aw2_saa7146_define_it_playback_callback(unsigned int + stream_number, + snd_aw2_saa7146_it_cb + p_it_callback, + void *p_callback_param); +extern void snd_aw2_saa7146_define_it_capture_callback(unsigned int + stream_number, + snd_aw2_saa7146_it_cb + p_it_callback, + void *p_callback_param); +extern void snd_aw2_saa7146_pcm_trigger_start_capture(struct snd_aw2_saa7146 + *chip, int stream_number); +extern void snd_aw2_saa7146_pcm_trigger_stop_capture(struct snd_aw2_saa7146 + *chip, int stream_number); + +extern void snd_aw2_saa7146_pcm_trigger_start_playback(struct snd_aw2_saa7146 + *chip, + int stream_number); +extern void snd_aw2_saa7146_pcm_trigger_stop_playback(struct snd_aw2_saa7146 + *chip, int stream_number); + +extern irqreturn_t snd_aw2_saa7146_interrupt(int irq, void *dev_id); +extern unsigned int snd_aw2_saa7146_get_hw_ptr_playback(struct snd_aw2_saa7146 + *chip, + int stream_number, + unsigned char + *start_addr, + unsigned int + buffer_size); +extern unsigned int snd_aw2_saa7146_get_hw_ptr_capture(struct snd_aw2_saa7146 + *chip, + int stream_number, + unsigned char + *start_addr, + unsigned int + buffer_size); + +extern void snd_aw2_saa7146_use_digital_input(struct snd_aw2_saa7146 *chip, + int use_digital); + +extern int snd_aw2_saa7146_is_using_digital_input(struct snd_aw2_saa7146 + *chip); + +#endif diff --git a/sound/pci/aw2/aw2-tsl.c b/sound/pci/aw2/aw2-tsl.c new file mode 100644 index 000000000..2f35b0880 --- /dev/null +++ b/sound/pci/aw2/aw2-tsl.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * Copyright 1998 Emagic Soft- und Hardware GmbH + * Copyright 2002 Martijn Sipkema + * + * This file is part of the Audiowerk2 ALSA driver + * + *****************************************************************************/ + +#define TSL_WS0 (1UL << 31) +#define TSL_WS1 (1UL << 30) +#define TSL_WS2 (1UL << 29) +#define TSL_WS3 (1UL << 28) +#define TSL_WS4 (1UL << 27) +#define TSL_DIS_A1 (1UL << 24) +#define TSL_SDW_A1 (1UL << 23) +#define TSL_SIB_A1 (1UL << 22) +#define TSL_SF_A1 (1UL << 21) +#define TSL_LF_A1 (1UL << 20) +#define TSL_BSEL_A1 (1UL << 17) +#define TSL_DOD_A1 (1UL << 15) +#define TSL_LOW_A1 (1UL << 14) +#define TSL_DIS_A2 (1UL << 11) +#define TSL_SDW_A2 (1UL << 10) +#define TSL_SIB_A2 (1UL << 9) +#define TSL_SF_A2 (1UL << 8) +#define TSL_LF_A2 (1UL << 7) +#define TSL_BSEL_A2 (1UL << 4) +#define TSL_DOD_A2 (1UL << 2) +#define TSL_LOW_A2 (1UL << 1) +#define TSL_EOS (1UL << 0) + + /* Audiowerk8 hardware setup: */ + /* WS0, SD4, TSL1 - Analog/ digital in */ + /* WS1, SD0, TSL1 - Analog out #1, digital out */ + /* WS2, SD2, TSL1 - Analog out #2 */ + /* WS3, SD1, TSL2 - Analog out #3 */ + /* WS4, SD3, TSL2 - Analog out #4 */ + + /* Audiowerk8 timing: */ + /* Timeslot: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ... */ + + /* A1_INPUT: */ + /* SD4: <_ADC-L_>-------<_ADC-R_>-------< */ + /* WS0: _______________/---------------\_ */ + + /* A1_OUTPUT: */ + /* SD0: <_1-L___>-------<_1-R___>-------< */ + /* WS1: _______________/---------------\_ */ + /* SD2: >-------<_2-L___>-------<_2-R___> */ + /* WS2: -------\_______________/--------- */ + + /* A2_OUTPUT: */ + /* SD1: <_3-L___>-------<_3-R___>-------< */ + /* WS3: _______________/---------------\_ */ + /* SD3: >-------<_4-L___>-------<_4-R___> */ + /* WS4: -------\_______________/--------- */ + +static const int tsl1[8] = { + 1 * TSL_SDW_A1 | 3 * TSL_BSEL_A1 | + 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_LF_A1, + + 1 * TSL_SDW_A1 | 2 * TSL_BSEL_A1 | + 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1, + + 0 * TSL_SDW_A1 | 3 * TSL_BSEL_A1 | + 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1, + + 0 * TSL_SDW_A1 | 2 * TSL_BSEL_A1 | + 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1, + + 1 * TSL_SDW_A1 | 1 * TSL_BSEL_A1 | + 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0, + + 1 * TSL_SDW_A1 | 0 * TSL_BSEL_A1 | + 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0, + + 0 * TSL_SDW_A1 | 1 * TSL_BSEL_A1 | + 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0, + + 0 * TSL_SDW_A1 | 0 * TSL_BSEL_A1 | 0 * TSL_DIS_A1 | + 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0 | TSL_SF_A1 | TSL_EOS, +}; + +static const int tsl2[8] = { + 0 * TSL_SDW_A2 | 3 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_LF_A2, + 0 * TSL_SDW_A2 | 2 * TSL_BSEL_A2 | 2 * TSL_DOD_A2, + 0 * TSL_SDW_A2 | 3 * TSL_BSEL_A2 | 2 * TSL_DOD_A2, + 0 * TSL_SDW_A2 | 2 * TSL_BSEL_A2 | 2 * TSL_DOD_A2, + 0 * TSL_SDW_A2 | 1 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2, + 0 * TSL_SDW_A2 | 0 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2, + 0 * TSL_SDW_A2 | 1 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2, + 0 * TSL_SDW_A2 | 0 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2 | TSL_EOS +}; diff --git a/sound/pci/aw2/saa7146.h b/sound/pci/aw2/saa7146.h new file mode 100644 index 000000000..90492a4c4 --- /dev/null +++ b/sound/pci/aw2/saa7146.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * + * This file is part of the Audiowerk2 ALSA driver + * + *****************************************************************************/ + +/* SAA7146 registers */ +#define PCI_BT_A 0x4C +#define IICTFR 0x8C +#define IICSTA 0x90 +#define BaseA1_in 0x94 +#define ProtA1_in 0x98 +#define PageA1_in 0x9C +#define BaseA1_out 0xA0 +#define ProtA1_out 0xA4 +#define PageA1_out 0xA8 +#define BaseA2_in 0xAC +#define ProtA2_in 0xB0 +#define PageA2_in 0xB4 +#define BaseA2_out 0xB8 +#define ProtA2_out 0xBC +#define PageA2_out 0xC0 +#define IER 0xDC +#define GPIO_CTRL 0xE0 +#define ACON1 0xF4 +#define ACON2 0xF8 +#define MC1 0xFC +#define MC2 0x100 +#define ISR 0x10C +#define PSR 0x110 +#define SSR 0x114 +#define PCI_ADP1 0x12C +#define PCI_ADP2 0x130 +#define PCI_ADP3 0x134 +#define PCI_ADP4 0x138 +#define LEVEL_REP 0x140 +#define FB_BUFFER1 0x144 +#define FB_BUFFER2 0x148 +#define TSL1 0x180 +#define TSL2 0x1C0 + +#define ME (1UL << 11) +#define LIMIT (1UL << 4) +#define PV (1UL << 3) + +/* PSR/ISR/IER */ +#define PPEF (1UL << 31) +#define PABO (1UL << 30) +#define IIC_S (1UL << 17) +#define IIC_E (1UL << 16) +#define A2_in (1UL << 15) +#define A2_out (1UL << 14) +#define A1_in (1UL << 13) +#define A1_out (1UL << 12) +#define AFOU (1UL << 11) +#define PIN3 (1UL << 6) +#define PIN2 (1UL << 5) +#define PIN1 (1UL << 4) +#define PIN0 (1UL << 3) +#define ECS (1UL << 2) +#define EC3S (1UL << 1) +#define EC0S (1UL << 0) + +/* SSR */ +#define PRQ (1UL << 31) +#define PMA (1UL << 30) +#define IIC_EA (1UL << 21) +#define IIC_EW (1UL << 20) +#define IIC_ER (1UL << 19) +#define IIC_EL (1UL << 18) +#define IIC_EF (1UL << 17) +#define AF2_in (1UL << 10) +#define AF2_out (1UL << 9) +#define AF1_in (1UL << 8) +#define AF1_out (1UL << 7) +#define EC5S (1UL << 3) +#define EC4S (1UL << 2) +#define EC2S (1UL << 1) +#define EC1S (1UL << 0) + +/* PCI_BT_A */ +#define BurstA1_in (1UL << 26) +#define ThreshA1_in (1UL << 24) +#define BurstA1_out (1UL << 18) +#define ThreshA1_out (1UL << 16) +#define BurstA2_in (1UL << 10) +#define ThreshA2_in (1UL << 8) +#define BurstA2_out (1UL << 2) +#define ThreshA2_out (1UL << 0) + +/* MC1 */ +#define MRST_N (1UL << 15) +#define EAP (1UL << 9) +#define EI2C (1UL << 8) +#define TR_E_A2_OUT (1UL << 3) +#define TR_E_A2_IN (1UL << 2) +#define TR_E_A1_OUT (1UL << 1) +#define TR_E_A1_IN (1UL << 0) + +/* MC2 */ +#define UPLD_IIC (1UL << 0) + +/* ACON1 */ +#define AUDIO_MODE (1UL << 29) +#define MAXLEVEL (1UL << 22) +#define A1_SWAP (1UL << 21) +#define A2_SWAP (1UL << 20) +#define WS0_CTRL (1UL << 18) +#define WS0_SYNC (1UL << 16) +#define WS1_CTRL (1UL << 14) +#define WS1_SYNC (1UL << 12) +#define WS2_CTRL (1UL << 10) +#define WS2_SYNC (1UL << 8) +#define WS3_CTRL (1UL << 6) +#define WS3_SYNC (1UL << 4) +#define WS4_CTRL (1UL << 2) +#define WS4_SYNC (1UL << 0) + +/* ACON2 */ +#define A1_CLKSRC (1UL << 27) +#define A2_CLKSRC (1UL << 22) +#define INVERT_BCLK1 (1UL << 21) +#define INVERT_BCLK2 (1UL << 20) +#define BCLK1_OEN (1UL << 19) +#define BCLK2_OEN (1UL << 18) + +/* IICSTA */ +#define IICCC (1UL << 8) +#define ABORT (1UL << 7) +#define SPERR (1UL << 6) +#define APERR (1UL << 5) +#define DTERR (1UL << 4) +#define DRERR (1UL << 3) +#define AL (1UL << 2) +#define ERR (1UL << 1) +#define BUSY (1UL << 0) + +/* IICTFR */ +#define BYTE2 (1UL << 24) +#define BYTE1 (1UL << 16) +#define BYTE0 (1UL << 8) +#define ATRR2 (1UL << 6) +#define ATRR1 (1UL << 4) +#define ATRR0 (1UL << 2) +#define ERR (1UL << 1) +#define BUSY (1UL << 0) + +#define START 3 +#define CONT 2 +#define STOP 1 +#define NOP 0 diff --git a/sound/pci/azt3328.c b/sound/pci/azt3328.c new file mode 100644 index 000000000..ccca82417 --- /dev/null +++ b/sound/pci/azt3328.c @@ -0,0 +1,2631 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* azt3328.c - driver for Aztech AZF3328 based soundcards (e.g. PCI168). + * Copyright (C) 2002, 2005 - 2011 by Andreas Mohr + * + * Framework borrowed from Bart Hartgers's als4000.c. + * Driver developed on PCI168 AP(W) version (PCI rev. 10, subsystem ID 1801), + * found in a Fujitsu-Siemens PC ("Cordant", aluminum case). + * Other versions are: + * PCI168 A(W), sub ID 1800 + * PCI168 A/AP, sub ID 8000 + * Please give me feedback in case you try my driver with one of these!! + * + * Keywords: Windows XP Vista 168nt4-125.zip 168win95-125.zip PCI 168 download + * (XP/Vista do not support this card at all but every Linux distribution + * has very good support out of the box; + * just to make sure that the right people hit this and get to know that, + * despite the high level of Internet ignorance - as usual :-P - + * about very good support for this card - on Linux!) + * + * NOTES + * Since Aztech does not provide any chipset documentation, + * even on repeated request to various addresses, + * and the answer that was finally given was negative + * (and I was stupid enough to manage to get hold of a PCI168 soundcard + * in the first place >:-P}), + * I was forced to base this driver on reverse engineering + * (3 weeks' worth of evenings filled with driver work). + * (and no, I did NOT go the easy way: to pick up a SB PCI128 for 9 Euros) + * + * It is quite likely that the AZF3328 chip is the PCI cousin of the + * AZF3318 ("azt1020 pnp", "MM Pro 16") ISA chip, given very similar specs. + * + * The AZF3328 chip (note: AZF3328, *not* AZT3328, that's just the driver name + * for compatibility reasons) from Azfin (joint-venture of Aztech and Fincitec, + * Fincitec acquired by National Semiconductor in 2002, together with the + * Fincitec-related company ARSmikro) has the following features: + * + * - compatibility & compliance: + * - Microsoft PC 97 ("PC 97 Hardware Design Guide", + * http://www.microsoft.com/whdc/archive/pcguides.mspx) + * - Microsoft PC 98 Baseline Audio + * - MPU401 UART + * - Sound Blaster Emulation (DOS Box) + * - builtin AC97 conformant codec (SNR over 80dB) + * Note that "conformant" != "compliant"!! this chip's mixer register layout + * *differs* from the standard AC97 layout: + * they chose to not implement the headphone register (which is not a + * problem since it's merely optional), yet when doing this, they committed + * the grave sin of letting other registers follow immediately instead of + * keeping a headphone dummy register, thereby shifting the mixer register + * addresses illegally. So far unfortunately it looks like the very flexible + * ALSA AC97 support is still not enough to easily compensate for such a + * grave layout violation despite all tweaks and quirks mechanisms it offers. + * Well, not quite: now ac97 layer is much improved (bus-specific ops!), + * thus I was able to implement support - it's actually working quite well. + * An interesting item might be Aztech AMR 2800-W, since it's an AC97 + * modem card which might reveal the Aztech-specific codec ID which + * we might want to pretend, too. Dito PCI168's brother, PCI368, + * where the advertising datasheet says it's AC97-based and has a + * Digital Enhanced Game Port. + * - builtin genuine OPL3 - verified to work fine, 20080506 + * - full duplex 16bit playback/record at independent sampling rate + * - MPU401 (+ legacy address support, claimed by one official spec sheet) + * FIXME: how to enable legacy addr?? + * - game port (legacy address support) + * - builtin DirectInput support, helps reduce CPU overhead (interrupt-driven + * features supported). - See common term "Digital Enhanced Game Port"... + * (probably DirectInput 3.0 spec - confirm) + * - builtin 3D enhancement (said to be YAMAHA Ymersion) + * - built-in General DirectX timer having a 20 bits counter + * with 1us resolution (see below!) + * - I2S serial output port for external DAC + * [FIXME: 3.3V or 5V level? maximum rate is 66.2kHz right?] + * - supports 33MHz PCI spec 2.1, PCI power management 1.0, compliant with ACPI + * - supports hardware volume control + * - single chip low cost solution (128 pin QFP) + * - supports programmable Sub-vendor and Sub-system ID [24C02 SEEPROM chip] + * required for Microsoft's logo compliance (FIXME: where?) + * At least the Trident 4D Wave DX has one bit somewhere + * to enable writes to PCI subsystem VID registers, that should be it. + * This might easily be in extended PCI reg space, since PCI168 also has + * some custom data starting at 0x80. What kind of config settings + * are located in our extended PCI space anyway?? + * - PCI168 AP(W) card: power amplifier with 4 Watts/channel at 4 Ohms + * [TDA1517P chip] + * + * Note that this driver now is actually *better* than the Windows driver, + * since it additionally supports the card's 1MHz DirectX timer - just try + * the following snd-seq module parameters etc.: + * - options snd-seq seq_default_timer_class=2 seq_default_timer_sclass=0 + * seq_default_timer_card=0 seq_client_load=1 seq_default_timer_device=0 + * seq_default_timer_subdevice=0 seq_default_timer_resolution=1000000 + * - "timidity -iAv -B2,8 -Os -EFreverb=0" + * - "pmidi -p 128:0 jazz.mid" + * + * OPL3 hardware playback testing, try something like: + * cat /proc/asound/hwdep + * and + * aconnect -o + * Then use + * sbiload -Dhw:x,y --opl3 /usr/share/sounds/opl3/std.o3 ......./drums.o3 + * where x,y is the xx-yy number as given in hwdep. + * Then try + * pmidi -p a:b jazz.mid + * where a:b is the client number plus 0 usually, as given by aconnect above. + * Oh, and make sure to unmute the FM mixer control (doh!) + * NOTE: power use during OPL3 playback is _VERY_ high (70W --> 90W!) + * despite no CPU activity, possibly due to hindering ACPI idling somehow. + * Shouldn't be a problem of the AZF3328 chip itself, I'd hope. + * Higher PCM / FM mixer levels seem to conflict (causes crackling), + * at least sometimes. Maybe even use with hardware sequencer timer above :) + * adplay/adplug-utils might soon offer hardware-based OPL3 playback, too. + * + * Certain PCI versions of this card are susceptible to DMA traffic underruns + * in some systems (resulting in sound crackling/clicking/popping), + * probably because they don't have a DMA FIFO buffer or so. + * Overview (PCI ID/PCI subID/PCI rev.): + * - no DMA crackling on SiS735: 0x50DC/0x1801/16 + * - unknown performance: 0x50DC/0x1801/10 + * (well, it's not bad on an Athlon 1800 with now very optimized IRQ handler) + * + * Crackling happens with VIA chipsets or, in my case, an SiS735, which is + * supposed to be very fast and supposed to get rid of crackling much + * better than a VIA, yet ironically I still get crackling, like many other + * people with the same chipset. + * Possible remedies: + * - use speaker (amplifier) output instead of headphone output + * (in case crackling is due to overloaded output clipping) + * - plug card into a different PCI slot, preferably one that isn't shared + * too much (this helps a lot, but not completely!) + * - get rid of PCI VGA card, use AGP instead + * - upgrade or downgrade BIOS + * - fiddle with PCI latency settings (setpci -v -s BUSID latency_timer=XX) + * Not too helpful. + * - Disable ACPI/power management/"Auto Detect RAM/PCI Clk" in BIOS + * + * BUGS + * - full-duplex might *still* be problematic, however a recent test was fine + * - (non-bug) "Bass/Treble or 3D settings don't work" - they do get evaluated + * if you set PCM output switch to "pre 3D" instead of "post 3D". + * If this can't be set, then get a mixer application that Isn't Stupid (tm) + * (e.g. kmix, gamix) - unfortunately several are!! + * - locking is not entirely clean, especially the audio stream activity + * ints --> may be racy + * - an _unconnected_ secondary joystick at the gameport will be reported + * to be "active" (floating values, not precisely -1) due to the way we need + * to read the Digital Enhanced Game Port. Not sure whether it is fixable. + * + * TODO + * - use PCI_VDEVICE + * - verify driver status on x86_64 + * - test multi-card driver operation + * - (ab)use 1MHz DirectX timer as kernel clocksource + * - test MPU401 MIDI playback etc. + * - add more power micro-management (disable various units of the card + * as long as they're unused, to improve audio quality and save power). + * However this requires more I/O ports which I haven't figured out yet + * and which thus might not even exist... + * The standard suspend/resume functionality could probably make use of + * some improvement, too... + * - figure out what all unknown port bits are responsible for + * - figure out some cleverly evil scheme to possibly make ALSA AC97 code + * fully accept our quite incompatible ""AC97"" mixer and thus save some + * code (but I'm not too optimistic that doing this is possible at all) + * - use MMIO (memory-mapped I/O)? Slightly faster access, e.g. for gameport. + */ + +#include +#include +#include /* WARN_ONCE */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* + * Config switch, to use ALSA's AC97 layer instead of old custom mixer crap. + * If the AC97 compatibility parts we needed to implement locally turn out + * to work nicely, then remove the old implementation eventually. + */ +#define AZF_USE_AC97_LAYER 1 + +#ifdef AZF_USE_AC97_LAYER +#include +#endif +#include "azt3328.h" + +MODULE_AUTHOR("Andreas Mohr "); +MODULE_DESCRIPTION("Aztech AZF3328 (PCI168)"); +MODULE_LICENSE("GPL"); + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_GAMEPORT 1 +#endif + +/* === Debug settings === + Further diagnostic functionality than the settings below + does not need to be provided, since one can easily write a POSIX shell script + to dump the card's I/O ports (those listed in lspci -v -v): + dump() + { + local descr=$1; local addr=$2; local count=$3 + + echo "${descr}: ${count} @ ${addr}:" + dd if=/dev/port skip=`printf %d ${addr}` count=${count} bs=1 \ + 2>/dev/null| hexdump -C + } + and then use something like + "dump joy200 0x200 8", "dump mpu388 0x388 4", "dump joy 0xb400 8", + "dump codec00 0xa800 32", "dump mixer 0xb800 64", "dump synth 0xbc00 8", + possibly within a "while true; do ... sleep 1; done" loop. + Tweaking ports could be done using + VALSTRING="`printf "%02x" $value`" + printf "\x""$VALSTRING"|dd of=/dev/port seek=`printf %d ${addr}` bs=1 \ + 2>/dev/null +*/ + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for AZF3328 soundcard."); + +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for AZF3328 soundcard."); + +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable AZF3328 soundcard."); + +static int seqtimer_scaling = 128; +module_param(seqtimer_scaling, int, 0444); +MODULE_PARM_DESC(seqtimer_scaling, "Set 1024000Hz sequencer timer scale factor (lockup danger!). Default 128."); + +enum snd_azf3328_codec_type { + /* warning: fixed indices (also used for bitmask checks!) */ + AZF_CODEC_PLAYBACK = 0, + AZF_CODEC_CAPTURE = 1, + AZF_CODEC_I2S_OUT = 2, +}; + +struct snd_azf3328_codec_data { + unsigned long io_base; /* keep first! (avoid offset calc) */ + unsigned int dma_base; /* helper to avoid an indirection in hotpath */ + spinlock_t *lock; /* TODO: convert to our own per-codec lock member */ + struct snd_pcm_substream *substream; + bool running; + enum snd_azf3328_codec_type type; + const char *name; +}; + +struct snd_azf3328 { + /* often-used fields towards beginning, then grouped */ + + unsigned long ctrl_io; /* usually 0xb000, size 128 */ + unsigned long game_io; /* usually 0xb400, size 8 */ + unsigned long mpu_io; /* usually 0xb800, size 4 */ + unsigned long opl3_io; /* usually 0xbc00, size 8 */ + unsigned long mixer_io; /* usually 0xc000, size 64 */ + + spinlock_t reg_lock; + + struct snd_timer *timer; + + struct snd_pcm *pcm[3]; + + /* playback, recording and I2S out codecs */ + struct snd_azf3328_codec_data codecs[3]; + +#ifdef AZF_USE_AC97_LAYER + struct snd_ac97 *ac97; +#endif + + struct snd_card *card; + struct snd_rawmidi *rmidi; + +#ifdef SUPPORT_GAMEPORT + struct gameport *gameport; + u16 axes[4]; +#endif + + struct pci_dev *pci; + int irq; + + /* register 0x6a is write-only, thus need to remember setting. + * If we need to add more registers here, then we might try to fold this + * into some transparent combined shadow register handling with + * CONFIG_PM register storage below, but that's slightly difficult. */ + u16 shadow_reg_ctrl_6AH; + + /* register value containers for power management + * Note: not always full I/O range preserved (similar to Win driver!) */ + u32 saved_regs_ctrl[AZF_ALIGN(AZF_IO_SIZE_CTRL_PM) / 4]; + u32 saved_regs_game[AZF_ALIGN(AZF_IO_SIZE_GAME_PM) / 4]; + u32 saved_regs_mpu[AZF_ALIGN(AZF_IO_SIZE_MPU_PM) / 4]; + u32 saved_regs_opl3[AZF_ALIGN(AZF_IO_SIZE_OPL3_PM) / 4]; + u32 saved_regs_mixer[AZF_ALIGN(AZF_IO_SIZE_MIXER_PM) / 4]; +}; + +static const struct pci_device_id snd_azf3328_ids[] = { + { PCI_DEVICE(0x122D, 0x50DC) }, /* PCI168/3328 */ + { PCI_DEVICE(0x122D, 0x80DA) }, /* 3328 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_azf3328_ids); + + +static int +snd_azf3328_io_reg_setb(unsigned reg, u8 mask, bool do_set) +{ + /* Well, strictly spoken, the inb/outb sequence isn't atomic + and would need locking. However we currently don't care + since it potentially complicates matters. */ + u8 prev = inb(reg), new; + + new = (do_set) ? (prev|mask) : (prev & ~mask); + /* we need to always write the new value no matter whether it differs + * or not, since some register bits don't indicate their setting */ + outb(new, reg); + if (new != prev) + return 1; + + return 0; +} + +static inline void +snd_azf3328_codec_outb(const struct snd_azf3328_codec_data *codec, + unsigned reg, + u8 value +) +{ + outb(value, codec->io_base + reg); +} + +static inline u8 +snd_azf3328_codec_inb(const struct snd_azf3328_codec_data *codec, unsigned reg) +{ + return inb(codec->io_base + reg); +} + +static inline void +snd_azf3328_codec_outw(const struct snd_azf3328_codec_data *codec, + unsigned reg, + u16 value +) +{ + outw(value, codec->io_base + reg); +} + +static inline u16 +snd_azf3328_codec_inw(const struct snd_azf3328_codec_data *codec, unsigned reg) +{ + return inw(codec->io_base + reg); +} + +static inline void +snd_azf3328_codec_outl_multi(const struct snd_azf3328_codec_data *codec, + unsigned reg, const void *buffer, int count +) +{ + unsigned long addr = codec->io_base + reg; + if (count) { + const u32 *buf = buffer; + do { + outl(*buf++, addr); + addr += 4; + } while (--count); + } +} + +static inline u32 +snd_azf3328_codec_inl(const struct snd_azf3328_codec_data *codec, unsigned reg) +{ + return inl(codec->io_base + reg); +} + +static inline void +snd_azf3328_ctrl_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value) +{ + outb(value, chip->ctrl_io + reg); +} + +static inline u8 +snd_azf3328_ctrl_inb(const struct snd_azf3328 *chip, unsigned reg) +{ + return inb(chip->ctrl_io + reg); +} + +static inline u16 +snd_azf3328_ctrl_inw(const struct snd_azf3328 *chip, unsigned reg) +{ + return inw(chip->ctrl_io + reg); +} + +static inline void +snd_azf3328_ctrl_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value) +{ + outw(value, chip->ctrl_io + reg); +} + +static inline void +snd_azf3328_ctrl_outl(const struct snd_azf3328 *chip, unsigned reg, u32 value) +{ + outl(value, chip->ctrl_io + reg); +} + +static inline void __maybe_unused +snd_azf3328_game_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value) +{ + outb(value, chip->game_io + reg); +} + +static inline void __maybe_unused +snd_azf3328_game_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value) +{ + outw(value, chip->game_io + reg); +} + +static inline u8 __maybe_unused +snd_azf3328_game_inb(const struct snd_azf3328 *chip, unsigned reg) +{ + return inb(chip->game_io + reg); +} + +static inline u16 __maybe_unused +snd_azf3328_game_inw(const struct snd_azf3328 *chip, unsigned reg) +{ + return inw(chip->game_io + reg); +} + +static inline void +snd_azf3328_mixer_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value) +{ + outw(value, chip->mixer_io + reg); +} + +static inline u16 +snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, unsigned reg) +{ + return inw(chip->mixer_io + reg); +} + +#define AZF_MUTE_BIT 0x80 + +static bool +snd_azf3328_mixer_mute_control(const struct snd_azf3328 *chip, + unsigned reg, bool do_mute +) +{ + unsigned long portbase = chip->mixer_io + reg + 1; + bool updated; + + /* the mute bit is on the *second* (i.e. right) register of a + * left/right channel setting */ + updated = snd_azf3328_io_reg_setb(portbase, AZF_MUTE_BIT, do_mute); + + /* indicate whether it was muted before */ + return (do_mute) ? !updated : updated; +} + +static inline bool +snd_azf3328_mixer_mute_control_master(const struct snd_azf3328 *chip, + bool do_mute +) +{ + return snd_azf3328_mixer_mute_control( + chip, + IDX_MIXER_PLAY_MASTER, + do_mute + ); +} + +static inline bool +snd_azf3328_mixer_mute_control_pcm(const struct snd_azf3328 *chip, + bool do_mute +) +{ + return snd_azf3328_mixer_mute_control( + chip, + IDX_MIXER_WAVEOUT, + do_mute + ); +} + +static inline void +snd_azf3328_mixer_reset(const struct snd_azf3328 *chip) +{ + /* reset (close) mixer: + * first mute master volume, then reset + */ + snd_azf3328_mixer_mute_control_master(chip, 1); + snd_azf3328_mixer_outw(chip, IDX_MIXER_RESET, 0x0000); +} + +#ifdef AZF_USE_AC97_LAYER + +static inline void +snd_azf3328_mixer_ac97_map_unsupported(const struct snd_azf3328 *chip, + unsigned short reg, const char *mode) +{ + /* need to add some more or less clever emulation? */ + dev_warn(chip->card->dev, + "missing %s emulation for AC97 register 0x%02x!\n", + mode, reg); +} + +/* + * Need to have _special_ AC97 mixer hardware register index mapper, + * to compensate for the issue of a rather AC97-incompatible hardware layout. + */ +#define AZF_REG_MASK 0x3f +#define AZF_AC97_REG_UNSUPPORTED 0x8000 +#define AZF_AC97_REG_REAL_IO_READ 0x4000 +#define AZF_AC97_REG_REAL_IO_WRITE 0x2000 +#define AZF_AC97_REG_REAL_IO_RW \ + (AZF_AC97_REG_REAL_IO_READ | AZF_AC97_REG_REAL_IO_WRITE) +#define AZF_AC97_REG_EMU_IO_READ 0x0400 +#define AZF_AC97_REG_EMU_IO_WRITE 0x0200 +#define AZF_AC97_REG_EMU_IO_RW \ + (AZF_AC97_REG_EMU_IO_READ | AZF_AC97_REG_EMU_IO_WRITE) +static unsigned short +snd_azf3328_mixer_ac97_map_reg_idx(unsigned short reg) +{ + static const struct { + unsigned short azf_reg; + } azf_reg_mapper[] = { + /* Especially when taking into consideration + * mono/stereo-based sequence of azf vs. AC97 control series, + * it's quite obvious that azf simply got rid + * of the AC97_HEADPHONE control at its intended offset, + * thus shifted _all_ controls by one, + * and _then_ simply added it as an FMSYNTH control at the end, + * to make up for the offset. + * This means we'll have to translate indices here as + * needed and then do some tiny AC97 patch action + * (snd_ac97_rename_vol_ctl() etc.) - that's it. + */ + { /* AC97_RESET */ IDX_MIXER_RESET + | AZF_AC97_REG_REAL_IO_WRITE + | AZF_AC97_REG_EMU_IO_READ }, + { /* AC97_MASTER */ IDX_MIXER_PLAY_MASTER }, + /* note large shift: AC97_HEADPHONE to IDX_MIXER_FMSYNTH! */ + { /* AC97_HEADPHONE */ IDX_MIXER_FMSYNTH }, + { /* AC97_MASTER_MONO */ IDX_MIXER_MODEMOUT }, + { /* AC97_MASTER_TONE */ IDX_MIXER_BASSTREBLE }, + { /* AC97_PC_BEEP */ IDX_MIXER_PCBEEP }, + { /* AC97_PHONE */ IDX_MIXER_MODEMIN }, + { /* AC97_MIC */ IDX_MIXER_MIC }, + { /* AC97_LINE */ IDX_MIXER_LINEIN }, + { /* AC97_CD */ IDX_MIXER_CDAUDIO }, + { /* AC97_VIDEO */ IDX_MIXER_VIDEO }, + { /* AC97_AUX */ IDX_MIXER_AUX }, + { /* AC97_PCM */ IDX_MIXER_WAVEOUT }, + { /* AC97_REC_SEL */ IDX_MIXER_REC_SELECT }, + { /* AC97_REC_GAIN */ IDX_MIXER_REC_VOLUME }, + { /* AC97_REC_GAIN_MIC */ AZF_AC97_REG_EMU_IO_RW }, + { /* AC97_GENERAL_PURPOSE */ IDX_MIXER_ADVCTL2 }, + { /* AC97_3D_CONTROL */ IDX_MIXER_ADVCTL1 }, + }; + + unsigned short reg_azf = AZF_AC97_REG_UNSUPPORTED; + + /* azf3328 supports the low-numbered and low-spec:ed range + of AC97 regs only */ + if (reg <= AC97_3D_CONTROL) { + unsigned short reg_idx = reg / 2; + reg_azf = azf_reg_mapper[reg_idx].azf_reg; + /* a translation-only entry means it's real read/write: */ + if (!(reg_azf & ~AZF_REG_MASK)) + reg_azf |= AZF_AC97_REG_REAL_IO_RW; + } else { + switch (reg) { + case AC97_POWERDOWN: + reg_azf = AZF_AC97_REG_EMU_IO_RW; + break; + case AC97_EXTENDED_ID: + reg_azf = AZF_AC97_REG_EMU_IO_READ; + break; + case AC97_EXTENDED_STATUS: + /* I don't know what the h*ll AC97 layer + * would consult this _extended_ register for + * given a base-AC97-advertised card, + * but let's just emulate it anyway :-P + */ + reg_azf = AZF_AC97_REG_EMU_IO_RW; + break; + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + reg_azf = AZF_AC97_REG_EMU_IO_READ; + break; + } + } + return reg_azf; +} + +static const unsigned short +azf_emulated_ac97_caps = + AC97_BC_DEDICATED_MIC | + AC97_BC_BASS_TREBLE | + /* Headphone is an FM Synth control here */ + AC97_BC_HEADPHONE | + /* no AC97_BC_LOUDNESS! */ + /* mask 0x7c00 is + vendor-specific 3D enhancement + vendor indicator. + Since there actually _is_ an + entry for Aztech Labs + (13), make damn sure + to indicate it. */ + (13 << 10); + +static const unsigned short +azf_emulated_ac97_powerdown = + /* pretend everything to be active */ + AC97_PD_ADC_STATUS | + AC97_PD_DAC_STATUS | + AC97_PD_MIXER_STATUS | + AC97_PD_VREF_STATUS; + +/* + * Emulated, _inofficial_ vendor ID + * (there might be some devices such as the MR 2800-W + * which could reveal the real Aztech AC97 ID). + * We choose to use "AZT" prefix, and then use 1 to indicate PCI168 + * (better don't use 0x68 since there's a PCI368 as well). + */ +static const unsigned int +azf_emulated_ac97_vendor_id = 0x415a5401; + +static unsigned short +snd_azf3328_mixer_ac97_read(struct snd_ac97 *ac97, unsigned short reg_ac97) +{ + const struct snd_azf3328 *chip = ac97->private_data; + unsigned short reg_azf = snd_azf3328_mixer_ac97_map_reg_idx(reg_ac97); + unsigned short reg_val = 0; + bool unsupported = false; + + dev_dbg(chip->card->dev, "snd_azf3328_mixer_ac97_read reg_ac97 %u\n", + reg_ac97); + if (reg_azf & AZF_AC97_REG_UNSUPPORTED) + unsupported = true; + else { + if (reg_azf & AZF_AC97_REG_REAL_IO_READ) + reg_val = snd_azf3328_mixer_inw(chip, + reg_azf & AZF_REG_MASK); + else { + /* + * Proceed with dummy I/O read, + * to ensure compatible timing where this may matter. + * (ALSA AC97 layer usually doesn't call I/O functions + * due to intelligent I/O caching anyway) + * Choose a mixer register that's thoroughly unrelated + * to common audio (try to minimize distortion). + */ + snd_azf3328_mixer_inw(chip, IDX_MIXER_SOMETHING30H); + } + + if (reg_azf & AZF_AC97_REG_EMU_IO_READ) { + switch (reg_ac97) { + case AC97_RESET: + reg_val |= azf_emulated_ac97_caps; + break; + case AC97_POWERDOWN: + reg_val |= azf_emulated_ac97_powerdown; + break; + case AC97_EXTENDED_ID: + case AC97_EXTENDED_STATUS: + /* AFAICS we simply can't support anything: */ + reg_val |= 0; + break; + case AC97_VENDOR_ID1: + reg_val = azf_emulated_ac97_vendor_id >> 16; + break; + case AC97_VENDOR_ID2: + reg_val = azf_emulated_ac97_vendor_id & 0xffff; + break; + default: + unsupported = true; + break; + } + } + } + if (unsupported) + snd_azf3328_mixer_ac97_map_unsupported(chip, reg_ac97, "read"); + + return reg_val; +} + +static void +snd_azf3328_mixer_ac97_write(struct snd_ac97 *ac97, + unsigned short reg_ac97, unsigned short val) +{ + const struct snd_azf3328 *chip = ac97->private_data; + unsigned short reg_azf = snd_azf3328_mixer_ac97_map_reg_idx(reg_ac97); + bool unsupported = false; + + dev_dbg(chip->card->dev, + "snd_azf3328_mixer_ac97_write reg_ac97 %u val %u\n", + reg_ac97, val); + if (reg_azf & AZF_AC97_REG_UNSUPPORTED) + unsupported = true; + else { + if (reg_azf & AZF_AC97_REG_REAL_IO_WRITE) + snd_azf3328_mixer_outw( + chip, + reg_azf & AZF_REG_MASK, + val + ); + else + if (reg_azf & AZF_AC97_REG_EMU_IO_WRITE) { + switch (reg_ac97) { + case AC97_REC_GAIN_MIC: + case AC97_POWERDOWN: + case AC97_EXTENDED_STATUS: + /* + * Silently swallow these writes. + * Since for most registers our card doesn't + * actually support a comparable feature, + * this is exactly what we should do here. + * The AC97 layer's I/O caching probably + * automatically takes care of all the rest... + * (remembers written values etc.) + */ + break; + default: + unsupported = true; + break; + } + } + } + if (unsupported) + snd_azf3328_mixer_ac97_map_unsupported(chip, reg_ac97, "write"); +} + +static int +snd_azf3328_mixer_new(struct snd_azf3328 *chip) +{ + struct snd_ac97_bus *bus; + struct snd_ac97_template ac97; + static const struct snd_ac97_bus_ops ops = { + .write = snd_azf3328_mixer_ac97_write, + .read = snd_azf3328_mixer_ac97_read, + }; + int rc; + + memset(&ac97, 0, sizeof(ac97)); + ac97.scaps = AC97_SCAP_SKIP_MODEM + | AC97_SCAP_AUDIO /* we support audio! */ + | AC97_SCAP_NO_SPDIF; + ac97.private_data = chip; + ac97.pci = chip->pci; + + /* + * ALSA's AC97 layer has terrible init crackling issues, + * unfortunately, and since it makes use of AC97_RESET, + * there's no use trying to mute Master Playback proactively. + */ + + rc = snd_ac97_bus(chip->card, 0, &ops, NULL, &bus); + if (!rc) + rc = snd_ac97_mixer(bus, &ac97, &chip->ac97); + /* + * Make sure to complain loudly in case of AC97 init failure, + * since failure may happen quite often, + * due to this card being a very quirky AC97 "lookalike". + */ + if (rc) + dev_err(chip->card->dev, "AC97 init failed, err %d!\n", rc); + + /* If we return an error here, then snd_card_free() should + * free up any ac97 codecs that got created, as well as the bus. + */ + return rc; +} +#else /* AZF_USE_AC97_LAYER */ +static void +snd_azf3328_mixer_write_volume_gradually(const struct snd_azf3328 *chip, + unsigned reg, + unsigned char dst_vol_left, + unsigned char dst_vol_right, + int chan_sel, int delay +) +{ + unsigned long portbase = chip->mixer_io + reg; + unsigned char curr_vol_left = 0, curr_vol_right = 0; + int left_change = 0, right_change = 0; + + if (chan_sel & SET_CHAN_LEFT) { + curr_vol_left = inb(portbase + 1); + + /* take care of muting flag contained in left channel */ + if (curr_vol_left & AZF_MUTE_BIT) + dst_vol_left |= AZF_MUTE_BIT; + else + dst_vol_left &= ~AZF_MUTE_BIT; + + left_change = (curr_vol_left > dst_vol_left) ? -1 : 1; + } + + if (chan_sel & SET_CHAN_RIGHT) { + curr_vol_right = inb(portbase + 0); + + right_change = (curr_vol_right > dst_vol_right) ? -1 : 1; + } + + do { + if (left_change) { + if (curr_vol_left != dst_vol_left) { + curr_vol_left += left_change; + outb(curr_vol_left, portbase + 1); + } else + left_change = 0; + } + if (right_change) { + if (curr_vol_right != dst_vol_right) { + curr_vol_right += right_change; + + /* during volume change, the right channel is crackling + * somewhat more than the left channel, unfortunately. + * This seems to be a hardware issue. */ + outb(curr_vol_right, portbase + 0); + } else + right_change = 0; + } + if (delay) + mdelay(delay); + } while ((left_change) || (right_change)); +} + +/* + * general mixer element + */ +struct azf3328_mixer_reg { + unsigned reg; + unsigned int lchan_shift, rchan_shift; + unsigned int mask; + unsigned int invert: 1; + unsigned int stereo: 1; + unsigned int enum_c: 4; +}; + +#define COMPOSE_MIXER_REG(reg,lchan_shift,rchan_shift,mask,invert,stereo,enum_c) \ + ((reg) | (lchan_shift << 8) | (rchan_shift << 12) | \ + (mask << 16) | \ + (invert << 24) | \ + (stereo << 25) | \ + (enum_c << 26)) + +static void snd_azf3328_mixer_reg_decode(struct azf3328_mixer_reg *r, unsigned long val) +{ + r->reg = val & 0xff; + r->lchan_shift = (val >> 8) & 0x0f; + r->rchan_shift = (val >> 12) & 0x0f; + r->mask = (val >> 16) & 0xff; + r->invert = (val >> 24) & 1; + r->stereo = (val >> 25) & 1; + r->enum_c = (val >> 26) & 0x0f; +} + +/* + * mixer switches/volumes + */ + +#define AZF3328_MIXER_SWITCH(xname, reg, shift, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer, \ + .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \ + .private_value = COMPOSE_MIXER_REG(reg, shift, 0, 0x1, invert, 0, 0), \ +} + +#define AZF3328_MIXER_VOL_STEREO(xname, reg, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer, \ + .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \ + .private_value = COMPOSE_MIXER_REG(reg, 8, 0, mask, invert, 1, 0), \ +} + +#define AZF3328_MIXER_VOL_MONO(xname, reg, mask, is_right_chan) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer, \ + .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \ + .private_value = COMPOSE_MIXER_REG(reg, is_right_chan ? 0 : 8, 0, mask, 1, 0, 0), \ +} + +#define AZF3328_MIXER_VOL_SPECIAL(xname, reg, mask, shift, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer, \ + .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \ + .private_value = COMPOSE_MIXER_REG(reg, shift, 0, mask, invert, 0, 0), \ +} + +#define AZF3328_MIXER_ENUM(xname, reg, enum_c, shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer_enum, \ + .get = snd_azf3328_get_mixer_enum, .put = snd_azf3328_put_mixer_enum, \ + .private_value = COMPOSE_MIXER_REG(reg, shift, 0, 0, 0, 0, enum_c), \ +} + +static int +snd_azf3328_info_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct azf3328_mixer_reg reg; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + uinfo->type = reg.mask == 1 ? + SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = reg.stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = reg.mask; + return 0; +} + +static int +snd_azf3328_get_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); + struct azf3328_mixer_reg reg; + u16 oreg, val; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + + oreg = snd_azf3328_mixer_inw(chip, reg.reg); + val = (oreg >> reg.lchan_shift) & reg.mask; + if (reg.invert) + val = reg.mask - val; + ucontrol->value.integer.value[0] = val; + if (reg.stereo) { + val = (oreg >> reg.rchan_shift) & reg.mask; + if (reg.invert) + val = reg.mask - val; + ucontrol->value.integer.value[1] = val; + } + dev_dbg(chip->card->dev, + "get: %02x is %04x -> vol %02lx|%02lx (shift %02d|%02d, mask %02x, inv. %d, stereo %d)\n", + reg.reg, oreg, + ucontrol->value.integer.value[0], ucontrol->value.integer.value[1], + reg.lchan_shift, reg.rchan_shift, reg.mask, reg.invert, reg.stereo); + return 0; +} + +static int +snd_azf3328_put_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); + struct azf3328_mixer_reg reg; + u16 oreg, nreg, val; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + oreg = snd_azf3328_mixer_inw(chip, reg.reg); + val = ucontrol->value.integer.value[0] & reg.mask; + if (reg.invert) + val = reg.mask - val; + nreg = oreg & ~(reg.mask << reg.lchan_shift); + nreg |= (val << reg.lchan_shift); + if (reg.stereo) { + val = ucontrol->value.integer.value[1] & reg.mask; + if (reg.invert) + val = reg.mask - val; + nreg &= ~(reg.mask << reg.rchan_shift); + nreg |= (val << reg.rchan_shift); + } + if (reg.mask >= 0x07) /* it's a volume control, so better take care */ + snd_azf3328_mixer_write_volume_gradually( + chip, reg.reg, nreg >> 8, nreg & 0xff, + /* just set both channels, doesn't matter */ + SET_CHAN_LEFT|SET_CHAN_RIGHT, + 0); + else + snd_azf3328_mixer_outw(chip, reg.reg, nreg); + + dev_dbg(chip->card->dev, + "put: %02x to %02lx|%02lx, oreg %04x; shift %02d|%02d -> nreg %04x; after: %04x\n", + reg.reg, ucontrol->value.integer.value[0], ucontrol->value.integer.value[1], + oreg, reg.lchan_shift, reg.rchan_shift, + nreg, snd_azf3328_mixer_inw(chip, reg.reg)); + return (nreg != oreg); +} + +static int +snd_azf3328_info_mixer_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts1[] = { + "Mic1", "Mic2" + }; + static const char * const texts2[] = { + "Mix", "Mic" + }; + static const char * const texts3[] = { + "Mic", "CD", "Video", "Aux", + "Line", "Mix", "Mix Mono", "Phone" + }; + static const char * const texts4[] = { + "pre 3D", "post 3D" + }; + struct azf3328_mixer_reg reg; + const char * const *p = NULL; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + if (reg.reg == IDX_MIXER_ADVCTL2) { + switch(reg.lchan_shift) { + case 8: /* modem out sel */ + p = texts1; + break; + case 9: /* mono sel source */ + p = texts2; + break; + case 15: /* PCM Out Path */ + p = texts4; + break; + } + } else if (reg.reg == IDX_MIXER_REC_SELECT) + p = texts3; + + return snd_ctl_enum_info(uinfo, + (reg.reg == IDX_MIXER_REC_SELECT) ? 2 : 1, + reg.enum_c, p); +} + +static int +snd_azf3328_get_mixer_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); + struct azf3328_mixer_reg reg; + unsigned short val; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + val = snd_azf3328_mixer_inw(chip, reg.reg); + if (reg.reg == IDX_MIXER_REC_SELECT) { + ucontrol->value.enumerated.item[0] = (val >> 8) & (reg.enum_c - 1); + ucontrol->value.enumerated.item[1] = (val >> 0) & (reg.enum_c - 1); + } else + ucontrol->value.enumerated.item[0] = (val >> reg.lchan_shift) & (reg.enum_c - 1); + + dev_dbg(chip->card->dev, + "get_enum: %02x is %04x -> %d|%d (shift %02d, enum_c %d)\n", + reg.reg, val, ucontrol->value.enumerated.item[0], ucontrol->value.enumerated.item[1], + reg.lchan_shift, reg.enum_c); + return 0; +} + +static int +snd_azf3328_put_mixer_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); + struct azf3328_mixer_reg reg; + u16 oreg, nreg, val; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + oreg = snd_azf3328_mixer_inw(chip, reg.reg); + val = oreg; + if (reg.reg == IDX_MIXER_REC_SELECT) { + if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U || + ucontrol->value.enumerated.item[1] > reg.enum_c - 1U) + return -EINVAL; + val = (ucontrol->value.enumerated.item[0] << 8) | + (ucontrol->value.enumerated.item[1] << 0); + } else { + if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U) + return -EINVAL; + val &= ~((reg.enum_c - 1) << reg.lchan_shift); + val |= (ucontrol->value.enumerated.item[0] << reg.lchan_shift); + } + snd_azf3328_mixer_outw(chip, reg.reg, val); + nreg = val; + + dev_dbg(chip->card->dev, + "put_enum: %02x to %04x, oreg %04x\n", reg.reg, val, oreg); + return (nreg != oreg); +} + +static const struct snd_kcontrol_new snd_azf3328_mixer_controls[] = { + AZF3328_MIXER_SWITCH("Master Playback Switch", IDX_MIXER_PLAY_MASTER, 15, 1), + AZF3328_MIXER_VOL_STEREO("Master Playback Volume", IDX_MIXER_PLAY_MASTER, 0x1f, 1), + AZF3328_MIXER_SWITCH("PCM Playback Switch", IDX_MIXER_WAVEOUT, 15, 1), + AZF3328_MIXER_VOL_STEREO("PCM Playback Volume", + IDX_MIXER_WAVEOUT, 0x1f, 1), + AZF3328_MIXER_SWITCH("PCM 3D Bypass Playback Switch", + IDX_MIXER_ADVCTL2, 7, 1), + AZF3328_MIXER_SWITCH("FM Playback Switch", IDX_MIXER_FMSYNTH, 15, 1), + AZF3328_MIXER_VOL_STEREO("FM Playback Volume", IDX_MIXER_FMSYNTH, 0x1f, 1), + AZF3328_MIXER_SWITCH("CD Playback Switch", IDX_MIXER_CDAUDIO, 15, 1), + AZF3328_MIXER_VOL_STEREO("CD Playback Volume", IDX_MIXER_CDAUDIO, 0x1f, 1), + AZF3328_MIXER_SWITCH("Capture Switch", IDX_MIXER_REC_VOLUME, 15, 1), + AZF3328_MIXER_VOL_STEREO("Capture Volume", IDX_MIXER_REC_VOLUME, 0x0f, 0), + AZF3328_MIXER_ENUM("Capture Source", IDX_MIXER_REC_SELECT, 8, 0), + AZF3328_MIXER_SWITCH("Mic Playback Switch", IDX_MIXER_MIC, 15, 1), + AZF3328_MIXER_VOL_MONO("Mic Playback Volume", IDX_MIXER_MIC, 0x1f, 1), + AZF3328_MIXER_SWITCH("Mic Boost (+20dB)", IDX_MIXER_MIC, 6, 0), + AZF3328_MIXER_SWITCH("Line Playback Switch", IDX_MIXER_LINEIN, 15, 1), + AZF3328_MIXER_VOL_STEREO("Line Playback Volume", IDX_MIXER_LINEIN, 0x1f, 1), + AZF3328_MIXER_SWITCH("Beep Playback Switch", IDX_MIXER_PCBEEP, 15, 1), + AZF3328_MIXER_VOL_SPECIAL("Beep Playback Volume", IDX_MIXER_PCBEEP, 0x0f, 1, 1), + AZF3328_MIXER_SWITCH("Video Playback Switch", IDX_MIXER_VIDEO, 15, 1), + AZF3328_MIXER_VOL_STEREO("Video Playback Volume", IDX_MIXER_VIDEO, 0x1f, 1), + AZF3328_MIXER_SWITCH("Aux Playback Switch", IDX_MIXER_AUX, 15, 1), + AZF3328_MIXER_VOL_STEREO("Aux Playback Volume", IDX_MIXER_AUX, 0x1f, 1), + AZF3328_MIXER_SWITCH("Modem Playback Switch", IDX_MIXER_MODEMOUT, 15, 1), + AZF3328_MIXER_VOL_MONO("Modem Playback Volume", IDX_MIXER_MODEMOUT, 0x1f, 1), + AZF3328_MIXER_SWITCH("Modem Capture Switch", IDX_MIXER_MODEMIN, 15, 1), + AZF3328_MIXER_VOL_MONO("Modem Capture Volume", IDX_MIXER_MODEMIN, 0x1f, 1), + AZF3328_MIXER_ENUM("Mic Select", IDX_MIXER_ADVCTL2, 2, 8), + AZF3328_MIXER_ENUM("Mono Output Select", IDX_MIXER_ADVCTL2, 2, 9), + AZF3328_MIXER_ENUM("PCM Output Route", IDX_MIXER_ADVCTL2, 2, 15), /* PCM Out Path, place in front since it controls *both* 3D and Bass/Treble! */ + AZF3328_MIXER_VOL_SPECIAL("Tone Control - Treble", IDX_MIXER_BASSTREBLE, 0x07, 1, 0), + AZF3328_MIXER_VOL_SPECIAL("Tone Control - Bass", IDX_MIXER_BASSTREBLE, 0x07, 9, 0), + AZF3328_MIXER_SWITCH("3D Control - Switch", IDX_MIXER_ADVCTL2, 13, 0), + AZF3328_MIXER_VOL_SPECIAL("3D Control - Width", IDX_MIXER_ADVCTL1, 0x07, 1, 0), /* "3D Width" */ + AZF3328_MIXER_VOL_SPECIAL("3D Control - Depth", IDX_MIXER_ADVCTL1, 0x03, 8, 0), /* "Hifi 3D" */ +#if MIXER_TESTING + AZF3328_MIXER_SWITCH("0", IDX_MIXER_ADVCTL2, 0, 0), + AZF3328_MIXER_SWITCH("1", IDX_MIXER_ADVCTL2, 1, 0), + AZF3328_MIXER_SWITCH("2", IDX_MIXER_ADVCTL2, 2, 0), + AZF3328_MIXER_SWITCH("3", IDX_MIXER_ADVCTL2, 3, 0), + AZF3328_MIXER_SWITCH("4", IDX_MIXER_ADVCTL2, 4, 0), + AZF3328_MIXER_SWITCH("5", IDX_MIXER_ADVCTL2, 5, 0), + AZF3328_MIXER_SWITCH("6", IDX_MIXER_ADVCTL2, 6, 0), + AZF3328_MIXER_SWITCH("7", IDX_MIXER_ADVCTL2, 7, 0), + AZF3328_MIXER_SWITCH("8", IDX_MIXER_ADVCTL2, 8, 0), + AZF3328_MIXER_SWITCH("9", IDX_MIXER_ADVCTL2, 9, 0), + AZF3328_MIXER_SWITCH("10", IDX_MIXER_ADVCTL2, 10, 0), + AZF3328_MIXER_SWITCH("11", IDX_MIXER_ADVCTL2, 11, 0), + AZF3328_MIXER_SWITCH("12", IDX_MIXER_ADVCTL2, 12, 0), + AZF3328_MIXER_SWITCH("13", IDX_MIXER_ADVCTL2, 13, 0), + AZF3328_MIXER_SWITCH("14", IDX_MIXER_ADVCTL2, 14, 0), + AZF3328_MIXER_SWITCH("15", IDX_MIXER_ADVCTL2, 15, 0), +#endif +}; + +static const u16 snd_azf3328_init_values[][2] = { + { IDX_MIXER_PLAY_MASTER, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_MODEMOUT, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_BASSTREBLE, 0x0000 }, + { IDX_MIXER_PCBEEP, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_MODEMIN, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_MIC, MIXER_MUTE_MASK|0x001f }, + { IDX_MIXER_LINEIN, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_CDAUDIO, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_VIDEO, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_AUX, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_WAVEOUT, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_FMSYNTH, MIXER_MUTE_MASK|0x1f1f }, + { IDX_MIXER_REC_VOLUME, MIXER_MUTE_MASK|0x0707 }, +}; + +static int +snd_azf3328_mixer_new(struct snd_azf3328 *chip) +{ + struct snd_card *card; + const struct snd_kcontrol_new *sw; + unsigned int idx; + int err; + + if (snd_BUG_ON(!chip || !chip->card)) + return -EINVAL; + + card = chip->card; + + /* mixer reset */ + snd_azf3328_mixer_outw(chip, IDX_MIXER_RESET, 0x0000); + + /* mute and zero volume channels */ + for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_init_values); ++idx) { + snd_azf3328_mixer_outw(chip, + snd_azf3328_init_values[idx][0], + snd_azf3328_init_values[idx][1]); + } + + /* add mixer controls */ + sw = snd_azf3328_mixer_controls; + for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_mixer_controls); + ++idx, ++sw) { + err = snd_ctl_add(chip->card, snd_ctl_new1(sw, chip)); + if (err < 0) + return err; + } + snd_component_add(card, "AZF3328 mixer"); + strscpy(card->mixername, "AZF3328 mixer"); + + return 0; +} +#endif /* AZF_USE_AC97_LAYER */ + +static void +snd_azf3328_codec_setfmt(struct snd_azf3328_codec_data *codec, + enum azf_freq_t bitrate, + unsigned int format_width, + unsigned int channels +) +{ + u16 val = 0xff00; + u8 freq = 0; + + switch (bitrate) { + case AZF_FREQ_4000: freq = SOUNDFORMAT_FREQ_SUSPECTED_4000; break; + case AZF_FREQ_4800: freq = SOUNDFORMAT_FREQ_SUSPECTED_4800; break; + case AZF_FREQ_5512: + /* the AZF3328 names it "5510" for some strange reason */ + freq = SOUNDFORMAT_FREQ_5510; break; + case AZF_FREQ_6620: freq = SOUNDFORMAT_FREQ_6620; break; + case AZF_FREQ_8000: freq = SOUNDFORMAT_FREQ_8000; break; + case AZF_FREQ_9600: freq = SOUNDFORMAT_FREQ_9600; break; + case AZF_FREQ_11025: freq = SOUNDFORMAT_FREQ_11025; break; + case AZF_FREQ_13240: freq = SOUNDFORMAT_FREQ_SUSPECTED_13240; break; + case AZF_FREQ_16000: freq = SOUNDFORMAT_FREQ_16000; break; + case AZF_FREQ_22050: freq = SOUNDFORMAT_FREQ_22050; break; + case AZF_FREQ_32000: freq = SOUNDFORMAT_FREQ_32000; break; + default: + pr_warn("azf3328: unknown bitrate %d, assuming 44.1kHz!\n", bitrate); + fallthrough; + case AZF_FREQ_44100: freq = SOUNDFORMAT_FREQ_44100; break; + case AZF_FREQ_48000: freq = SOUNDFORMAT_FREQ_48000; break; + case AZF_FREQ_66200: freq = SOUNDFORMAT_FREQ_SUSPECTED_66200; break; + } + /* val = 0xff07; 3m27.993s (65301Hz; -> 64000Hz???) hmm, 66120, 65967, 66123 */ + /* val = 0xff09; 17m15.098s (13123,478Hz; -> 12000Hz???) hmm, 13237.2Hz? */ + /* val = 0xff0a; 47m30.599s (4764,891Hz; -> 4800Hz???) yup, 4803Hz */ + /* val = 0xff0c; 57m0.510s (4010,263Hz; -> 4000Hz???) yup, 4003Hz */ + /* val = 0xff05; 5m11.556s (... -> 44100Hz) */ + /* val = 0xff03; 10m21.529s (21872,463Hz; -> 22050Hz???) */ + /* val = 0xff0f; 20m41.883s (10937,993Hz; -> 11025Hz???) */ + /* val = 0xff0d; 41m23.135s (5523,600Hz; -> 5512Hz???) */ + /* val = 0xff0e; 28m30.777s (8017Hz; -> 8000Hz???) */ + + val |= freq; + + if (channels == 2) + val |= SOUNDFORMAT_FLAG_2CHANNELS; + + if (format_width == 16) + val |= SOUNDFORMAT_FLAG_16BIT; + + guard(spinlock_irqsave)(codec->lock); + + /* set bitrate/format */ + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_SOUNDFORMAT, val); + + /* changing the bitrate/format settings switches off the + * audio output with an annoying click in case of 8/16bit format change + * (maybe shutting down DAC/ADC?), thus immediately + * do some tweaking to reenable it and get rid of the clicking + * (FIXME: yes, it works, but what exactly am I doing here?? :) + * FIXME: does this have some side effects for full-duplex + * or other dramatic side effects? */ + /* do it for non-capture codecs only */ + if (codec->type != AZF_CODEC_CAPTURE) + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS) | + DMA_RUN_SOMETHING1 | + DMA_RUN_SOMETHING2 | + SOMETHING_ALMOST_ALWAYS_SET | + DMA_EPILOGUE_SOMETHING | + DMA_SOMETHING_ELSE + ); +} + +static inline void +snd_azf3328_codec_setfmt_lowpower(struct snd_azf3328_codec_data *codec +) +{ + /* choose lowest frequency for low power consumption. + * While this will cause louder noise due to rather coarse frequency, + * it should never matter since output should always + * get disabled properly when idle anyway. */ + snd_azf3328_codec_setfmt(codec, AZF_FREQ_4000, 8, 1); +} + +static void +snd_azf3328_ctrl_reg_6AH_update(struct snd_azf3328 *chip, + unsigned bitmask, + bool enable +) +{ + bool do_mask = !enable; + if (do_mask) + chip->shadow_reg_ctrl_6AH |= bitmask; + else + chip->shadow_reg_ctrl_6AH &= ~bitmask; + dev_dbg(chip->card->dev, + "6AH_update mask 0x%04x do_mask %d: val 0x%04x\n", + bitmask, do_mask, chip->shadow_reg_ctrl_6AH); + snd_azf3328_ctrl_outw(chip, IDX_IO_6AH, chip->shadow_reg_ctrl_6AH); +} + +static inline void +snd_azf3328_ctrl_enable_codecs(struct snd_azf3328 *chip, bool enable) +{ + dev_dbg(chip->card->dev, "codec_enable %d\n", enable); + /* no idea what exactly is being done here, but I strongly assume it's + * PM related */ + snd_azf3328_ctrl_reg_6AH_update( + chip, IO_6A_PAUSE_PLAYBACK_BIT8, enable + ); +} + +static void +snd_azf3328_ctrl_codec_activity(struct snd_azf3328 *chip, + enum snd_azf3328_codec_type codec_type, + bool enable +) +{ + struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type]; + bool need_change = (codec->running != enable); + + dev_dbg(chip->card->dev, + "codec_activity: %s codec, enable %d, need_change %d\n", + codec->name, enable, need_change + ); + if (need_change) { + static const struct { + enum snd_azf3328_codec_type other1; + enum snd_azf3328_codec_type other2; + } peer_codecs[3] = + { { AZF_CODEC_CAPTURE, AZF_CODEC_I2S_OUT }, + { AZF_CODEC_PLAYBACK, AZF_CODEC_I2S_OUT }, + { AZF_CODEC_PLAYBACK, AZF_CODEC_CAPTURE } }; + bool call_function; + + if (enable) + /* if enable codec, call enable_codecs func + to enable codec supply... */ + call_function = 1; + else { + /* ...otherwise call enable_codecs func + (which globally shuts down operation of codecs) + only in case the other codecs are currently + not active either! */ + call_function = + ((!chip->codecs[peer_codecs[codec_type].other1] + .running) + && (!chip->codecs[peer_codecs[codec_type].other2] + .running)); + } + if (call_function) + snd_azf3328_ctrl_enable_codecs(chip, enable); + + /* ...and adjust clock, too + * (reduce noise and power consumption) */ + if (!enable) + snd_azf3328_codec_setfmt_lowpower(codec); + codec->running = enable; + } +} + +static void +snd_azf3328_codec_setdmaa(struct snd_azf3328 *chip, + struct snd_azf3328_codec_data *codec, + unsigned long addr, + unsigned int period_bytes, + unsigned int buffer_bytes +) +{ + WARN_ONCE(period_bytes & 1, "odd period length!?\n"); + WARN_ONCE(buffer_bytes != 2 * period_bytes, + "missed our input expectations! %u vs. %u\n", + buffer_bytes, period_bytes); + if (!codec->running) { + /* AZF3328 uses a two buffer pointer DMA transfer approach */ + + /* width 32bit (prevent overflow): */ + u32 area_length; + struct codec_setup_io { + u32 dma_start_1; + u32 dma_start_2; + u32 dma_lengths; + } __packed setup_io; + + area_length = buffer_bytes/2; + + setup_io.dma_start_1 = addr; + setup_io.dma_start_2 = addr+area_length; + + dev_dbg(chip->card->dev, + "setdma: buffers %08x[%u] / %08x[%u], %u, %u\n", + setup_io.dma_start_1, area_length, + setup_io.dma_start_2, area_length, + period_bytes, buffer_bytes); + + /* Hmm, are we really supposed to decrement this by 1?? + Most definitely certainly not: configuring full length does + work properly (i.e. likely better), and BTW we + violated possibly differing frame sizes with this... + + area_length--; |* max. index *| + */ + + /* build combined I/O buffer length word */ + setup_io.dma_lengths = (area_length << 16) | (area_length); + + guard(spinlock_irqsave)(codec->lock); + snd_azf3328_codec_outl_multi( + codec, IDX_IO_CODEC_DMA_START_1, &setup_io, 3 + ); + } +} + +static int +snd_azf3328_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_azf3328_codec_data *codec = runtime->private_data; +#if 0 + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); +#endif + + codec->dma_base = runtime->dma_addr; + +#if 0 + snd_azf3328_codec_setfmt(codec, + runtime->rate, + snd_pcm_format_width(runtime->format), + runtime->channels); + snd_azf3328_codec_setdmaa(chip, codec, + runtime->dma_addr, count, size); +#endif + return 0; +} + +static int +snd_azf3328_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_azf3328_codec_data *codec = runtime->private_data; + int result = 0; + u16 flags1; + bool previously_muted = false; + bool is_main_mixer_playback_codec = (AZF_CODEC_PLAYBACK == codec->type); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev_dbg(chip->card->dev, "START PCM %s\n", codec->name); + + if (is_main_mixer_playback_codec) { + /* mute WaveOut (avoid clicking during setup) */ + previously_muted = + snd_azf3328_mixer_mute_control_pcm( + chip, 1 + ); + } + + snd_azf3328_codec_setfmt(codec, + runtime->rate, + snd_pcm_format_width(runtime->format), + runtime->channels); + + scoped_guard(spinlock, codec->lock) { + /* first, remember current value: */ + flags1 = snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS); + + /* stop transfer */ + flags1 &= ~DMA_RESUME; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); + + /* FIXME: clear interrupts or what??? */ + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_IRQTYPE, 0xffff); + } + + snd_azf3328_codec_setdmaa(chip, codec, runtime->dma_addr, + snd_pcm_lib_period_bytes(substream), + snd_pcm_lib_buffer_bytes(substream) + ); + + scoped_guard(spinlock, codec->lock) { +#ifdef WIN9X + /* FIXME: enable playback/recording??? */ + flags1 |= DMA_RUN_SOMETHING1 | DMA_RUN_SOMETHING2; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); + + /* start transfer again */ + /* FIXME: what is this value (0x0010)??? */ + flags1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); +#else /* NT4 */ + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + 0x0000); + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + DMA_RUN_SOMETHING1); + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + DMA_RUN_SOMETHING1 | + DMA_RUN_SOMETHING2); + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + DMA_RESUME | + SOMETHING_ALMOST_ALWAYS_SET | + DMA_EPILOGUE_SOMETHING | + DMA_SOMETHING_ELSE); +#endif + } + snd_azf3328_ctrl_codec_activity(chip, codec->type, 1); + + if (is_main_mixer_playback_codec) { + /* now unmute WaveOut */ + if (!previously_muted) + snd_azf3328_mixer_mute_control_pcm( + chip, 0 + ); + } + + dev_dbg(chip->card->dev, "PCM STARTED %s\n", codec->name); + break; + case SNDRV_PCM_TRIGGER_RESUME: + dev_dbg(chip->card->dev, "PCM RESUME %s\n", codec->name); + /* resume codec if we were active */ + scoped_guard(spinlock, codec->lock) { + if (codec->running) + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + snd_azf3328_codec_inw( + codec, IDX_IO_CODEC_DMA_FLAGS + ) | DMA_RESUME + ); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(chip->card->dev, "PCM STOP %s\n", codec->name); + + if (is_main_mixer_playback_codec) { + /* mute WaveOut (avoid clicking during setup) */ + previously_muted = + snd_azf3328_mixer_mute_control_pcm( + chip, 1 + ); + } + + scoped_guard(spinlock, codec->lock) { + /* first, remember current value: */ + flags1 = snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS); + + /* stop transfer */ + flags1 &= ~DMA_RESUME; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); + + /* hmm, is this really required? we're resetting the same bit + * immediately thereafter... */ + flags1 |= DMA_RUN_SOMETHING1; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); + + flags1 &= ~DMA_RUN_SOMETHING1; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); + } + snd_azf3328_ctrl_codec_activity(chip, codec->type, 0); + + if (is_main_mixer_playback_codec) { + /* now unmute WaveOut */ + if (!previously_muted) + snd_azf3328_mixer_mute_control_pcm( + chip, 0 + ); + } + + dev_dbg(chip->card->dev, "PCM STOPPED %s\n", codec->name); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + dev_dbg(chip->card->dev, "PCM SUSPEND %s\n", codec->name); + /* make sure codec is stopped */ + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + snd_azf3328_codec_inw( + codec, IDX_IO_CODEC_DMA_FLAGS + ) & ~DMA_RESUME + ); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + WARN(1, "FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + WARN(1, "FIXME: SNDRV_PCM_TRIGGER_PAUSE_RELEASE NIY!\n"); + break; + default: + WARN(1, "FIXME: unknown trigger mode!\n"); + return -EINVAL; + } + + return result; +} + +static snd_pcm_uframes_t +snd_azf3328_pcm_pointer(struct snd_pcm_substream *substream +) +{ + const struct snd_azf3328_codec_data *codec = + substream->runtime->private_data; + unsigned long result; + snd_pcm_uframes_t frmres; + + result = snd_azf3328_codec_inl(codec, IDX_IO_CODEC_DMA_CURRPOS); + + /* calculate offset */ +#ifdef QUERY_HARDWARE + result -= snd_azf3328_codec_inl(codec, IDX_IO_CODEC_DMA_START_1); +#else + result -= codec->dma_base; +#endif + frmres = bytes_to_frames( substream->runtime, result); + dev_dbg(substream->pcm->card->dev, "%08li %s @ 0x%8lx, frames %8ld\n", + jiffies, codec->name, result, frmres); + return frmres; +} + +/******************************************************************/ + +#ifdef SUPPORT_GAMEPORT +static inline void +snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip, + bool enable +) +{ + snd_azf3328_io_reg_setb( + chip->game_io+IDX_GAME_HWCONFIG, + GAME_HWCFG_IRQ_ENABLE, + enable + ); +} + +static inline void +snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip, + bool enable +) +{ + snd_azf3328_io_reg_setb( + chip->game_io+IDX_GAME_HWCONFIG, + GAME_HWCFG_LEGACY_ADDRESS_ENABLE, + enable + ); +} + +static void +snd_azf3328_gameport_set_counter_frequency(struct snd_azf3328 *chip, + unsigned int freq_cfg +) +{ + snd_azf3328_io_reg_setb( + chip->game_io+IDX_GAME_HWCONFIG, + 0x02, + (freq_cfg & 1) != 0 + ); + snd_azf3328_io_reg_setb( + chip->game_io+IDX_GAME_HWCONFIG, + 0x04, + (freq_cfg & 2) != 0 + ); +} + +static inline void +snd_azf3328_gameport_axis_circuit_enable(struct snd_azf3328 *chip, bool enable) +{ + snd_azf3328_ctrl_reg_6AH_update( + chip, IO_6A_SOMETHING2_GAMEPORT, enable + ); +} + +static inline void +snd_azf3328_gameport_interrupt(struct snd_azf3328 *chip) +{ + /* + * skeleton handler only + * (we do not want axis reading in interrupt handler - too much load!) + */ + dev_dbg(chip->card->dev, "gameport irq\n"); + + /* this should ACK the gameport IRQ properly, hopefully. */ + snd_azf3328_game_inw(chip, IDX_GAME_AXIS_VALUE); +} + +static int +snd_azf3328_gameport_open(struct gameport *gameport, int mode) +{ + struct snd_azf3328 *chip = gameport_get_port_data(gameport); + int res; + + dev_dbg(chip->card->dev, "gameport_open, mode %d\n", mode); + switch (mode) { + case GAMEPORT_MODE_COOKED: + case GAMEPORT_MODE_RAW: + res = 0; + break; + default: + res = -1; + break; + } + + snd_azf3328_gameport_set_counter_frequency(chip, + GAME_HWCFG_ADC_COUNTER_FREQ_STD); + snd_azf3328_gameport_axis_circuit_enable(chip, (res == 0)); + + return res; +} + +static void +snd_azf3328_gameport_close(struct gameport *gameport) +{ + struct snd_azf3328 *chip = gameport_get_port_data(gameport); + + dev_dbg(chip->card->dev, "gameport_close\n"); + snd_azf3328_gameport_set_counter_frequency(chip, + GAME_HWCFG_ADC_COUNTER_FREQ_1_200); + snd_azf3328_gameport_axis_circuit_enable(chip, 0); +} + +static int +snd_azf3328_gameport_cooked_read(struct gameport *gameport, + int *axes, + int *buttons +) +{ + struct snd_azf3328 *chip = gameport_get_port_data(gameport); + int i; + u8 val; + + if (snd_BUG_ON(!chip)) + return 0; + + guard(spinlock_irqsave)(&chip->reg_lock); + val = snd_azf3328_game_inb(chip, IDX_GAME_LEGACY_COMPATIBLE); + *buttons = (~(val) >> 4) & 0xf; + + /* ok, this one is a bit dirty: cooked_read is being polled by a timer, + * thus we're atomic and cannot actively wait in here + * (which would be useful for us since it probably would be better + * to trigger a measurement in here, then wait a short amount of + * time until it's finished, then read values of _this_ measurement). + * + * Thus we simply resort to reading values if they're available already + * and trigger the next measurement. + */ + + val = snd_azf3328_game_inb(chip, IDX_GAME_AXES_CONFIG); + if (val & GAME_AXES_SAMPLING_READY) { + for (i = 0; i < ARRAY_SIZE(chip->axes); ++i) { + /* configure the axis to read */ + val = (i << 4) | 0x0f; + snd_azf3328_game_outb(chip, IDX_GAME_AXES_CONFIG, val); + + chip->axes[i] = snd_azf3328_game_inw( + chip, IDX_GAME_AXIS_VALUE + ); + } + } + + /* trigger next sampling of axes, to be evaluated the next time we + * enter this function */ + + /* for some very, very strange reason we cannot enable + * Measurement Ready monitoring for all axes here, + * at least not when only one joystick connected */ + val = 0x03; /* we're able to monitor axes 1 and 2 only */ + snd_azf3328_game_outb(chip, IDX_GAME_AXES_CONFIG, val); + + snd_azf3328_game_outw(chip, IDX_GAME_AXIS_VALUE, 0xffff); + + for (i = 0; i < ARRAY_SIZE(chip->axes); i++) { + axes[i] = chip->axes[i]; + if (axes[i] == 0xffff) + axes[i] = -1; + } + + dev_dbg(chip->card->dev, "cooked_read: axes %d %d %d %d buttons %d\n", + axes[0], axes[1], axes[2], axes[3], *buttons); + + return 0; +} + +static int +snd_azf3328_gameport(struct snd_azf3328 *chip, int dev) +{ + struct gameport *gp; + + chip->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(chip->card->dev, "cannot alloc memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "AZF3328 Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci)); + gameport_set_dev_parent(gp, &chip->pci->dev); + gp->io = chip->game_io; + gameport_set_port_data(gp, chip); + + gp->open = snd_azf3328_gameport_open; + gp->close = snd_azf3328_gameport_close; + gp->fuzz = 16; /* seems ok */ + gp->cooked_read = snd_azf3328_gameport_cooked_read; + + /* DISABLE legacy address: we don't need it! */ + snd_azf3328_gameport_legacy_address_enable(chip, 0); + + snd_azf3328_gameport_set_counter_frequency(chip, + GAME_HWCFG_ADC_COUNTER_FREQ_1_200); + snd_azf3328_gameport_axis_circuit_enable(chip, 0); + + gameport_register_port(chip->gameport); + + return 0; +} + +static void +snd_azf3328_gameport_free(struct snd_azf3328 *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } + snd_azf3328_gameport_irq_enable(chip, 0); +} +#else +static inline int +snd_azf3328_gameport(struct snd_azf3328 *chip, int dev) { return -ENOSYS; } +static inline void +snd_azf3328_gameport_free(struct snd_azf3328 *chip) { } +static inline void +snd_azf3328_gameport_interrupt(struct snd_azf3328 *chip) +{ + dev_warn(chip->card->dev, "huh, game port IRQ occurred!?\n"); +} +#endif /* SUPPORT_GAMEPORT */ + +/******************************************************************/ + +static inline void +snd_azf3328_irq_log_unknown_type(struct snd_azf3328 *chip, u8 which) +{ + dev_dbg(chip->card->dev, + "unknown IRQ type (%x) occurred, please report!\n", + which); +} + +static inline void +snd_azf3328_pcm_interrupt(struct snd_azf3328 *chip, + const struct snd_azf3328_codec_data *first_codec, + u8 status +) +{ + u8 which; + enum snd_azf3328_codec_type codec_type; + const struct snd_azf3328_codec_data *codec = first_codec; + + for (codec_type = AZF_CODEC_PLAYBACK; + codec_type <= AZF_CODEC_I2S_OUT; + ++codec_type, ++codec) { + + /* skip codec if there's no interrupt for it */ + if (!(status & (1 << codec_type))) + continue; + + scoped_guard(spinlock, codec->lock) { + which = snd_azf3328_codec_inb(codec, IDX_IO_CODEC_IRQTYPE); + /* ack all IRQ types immediately */ + snd_azf3328_codec_outb(codec, IDX_IO_CODEC_IRQTYPE, which); + } + + if (codec->substream) { + snd_pcm_period_elapsed(codec->substream); + dev_dbg(chip->card->dev, "%s period done (#%x), @ %x\n", + codec->name, + which, + snd_azf3328_codec_inl( + codec, IDX_IO_CODEC_DMA_CURRPOS)); + } else + dev_warn(chip->card->dev, "irq handler problem!\n"); + if (which & IRQ_SOMETHING) + snd_azf3328_irq_log_unknown_type(chip, which); + } +} + +static irqreturn_t +snd_azf3328_interrupt(int irq, void *dev_id) +{ + struct snd_azf3328 *chip = dev_id; + u8 status; + static unsigned long irq_count; + + status = snd_azf3328_ctrl_inb(chip, IDX_IO_IRQSTATUS); + + /* fast path out, to ease interrupt sharing */ + if (!(status & + (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT + |IRQ_GAMEPORT|IRQ_MPU401|IRQ_TIMER) + )) + return IRQ_NONE; /* must be interrupt for another device */ + + dev_dbg(chip->card->dev, + "irq_count %ld! IDX_IO_IRQSTATUS %04x\n", + irq_count++ /* debug-only */, + status); + + if (status & IRQ_TIMER) { + /* dev_dbg(chip->card->dev, "timer %ld\n", + snd_azf3328_codec_inl(chip, IDX_IO_TIMER_VALUE) + & TIMER_VALUE_MASK + ); */ + if (chip->timer) + snd_timer_interrupt(chip->timer, chip->timer->sticks); + /* ACK timer */ + scoped_guard(spinlock, &chip->reg_lock) { + snd_azf3328_ctrl_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x07); + } + dev_dbg(chip->card->dev, "timer IRQ\n"); + } + + if (status & (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT)) + snd_azf3328_pcm_interrupt(chip, chip->codecs, status); + + if (status & IRQ_GAMEPORT) + snd_azf3328_gameport_interrupt(chip); + + /* MPU401 has less critical IRQ requirements + * than timer and playback/recording, right? */ + if (status & IRQ_MPU401) { + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); + + /* hmm, do we have to ack the IRQ here somehow? + * If so, then I don't know how yet... */ + dev_dbg(chip->card->dev, "MPU401 IRQ\n"); + } + return IRQ_HANDLED; +} + +/*****************************************************************/ + +/* as long as we think we have identical snd_pcm_hardware parameters + for playback, capture and i2s out, we can use the same physical struct + since the struct is simply being copied into a member. +*/ +static const struct snd_pcm_hardware snd_azf3328_hardware = +{ + /* FIXME!! Correct? */ + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_5512 | + SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT, + .rate_min = AZF_FREQ_4000, + .rate_max = AZF_FREQ_66200, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (64*1024), + .period_bytes_min = 1024, + .period_bytes_max = (32*1024), + /* We simply have two DMA areas (instead of a list of descriptors + such as other cards); I believe that this is a fixed hardware + attribute and there isn't much driver magic to be done to expand it. + Thus indicate that we have at least and at most 2 periods. */ + .periods_min = 2, + .periods_max = 2, + /* FIXME: maybe that card actually has a FIFO? + * Hmm, it seems newer revisions do have one, but we still don't know + * its size... */ + .fifo_size = 0, +}; + + +static const unsigned int snd_azf3328_fixed_rates[] = { + AZF_FREQ_4000, + AZF_FREQ_4800, + AZF_FREQ_5512, + AZF_FREQ_6620, + AZF_FREQ_8000, + AZF_FREQ_9600, + AZF_FREQ_11025, + AZF_FREQ_13240, + AZF_FREQ_16000, + AZF_FREQ_22050, + AZF_FREQ_32000, + AZF_FREQ_44100, + AZF_FREQ_48000, + AZF_FREQ_66200 +}; + +static const struct snd_pcm_hw_constraint_list snd_azf3328_hw_constraints_rates = { + .count = ARRAY_SIZE(snd_azf3328_fixed_rates), + .list = snd_azf3328_fixed_rates, + .mask = 0, +}; + +/*****************************************************************/ + +static int +snd_azf3328_pcm_open(struct snd_pcm_substream *substream, + enum snd_azf3328_codec_type codec_type +) +{ + struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type]; + + codec->substream = substream; + + /* same parameters for all our codecs - at least we think so... */ + runtime->hw = snd_azf3328_hardware; + + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &snd_azf3328_hw_constraints_rates); + runtime->private_data = codec; + return 0; +} + +static int +snd_azf3328_pcm_playback_open(struct snd_pcm_substream *substream) +{ + return snd_azf3328_pcm_open(substream, AZF_CODEC_PLAYBACK); +} + +static int +snd_azf3328_pcm_capture_open(struct snd_pcm_substream *substream) +{ + return snd_azf3328_pcm_open(substream, AZF_CODEC_CAPTURE); +} + +static int +snd_azf3328_pcm_i2s_out_open(struct snd_pcm_substream *substream) +{ + return snd_azf3328_pcm_open(substream, AZF_CODEC_I2S_OUT); +} + +static int +snd_azf3328_pcm_close(struct snd_pcm_substream *substream +) +{ + struct snd_azf3328_codec_data *codec = + substream->runtime->private_data; + + codec->substream = NULL; + return 0; +} + +/******************************************************************/ + +static const struct snd_pcm_ops snd_azf3328_playback_ops = { + .open = snd_azf3328_pcm_playback_open, + .close = snd_azf3328_pcm_close, + .prepare = snd_azf3328_pcm_prepare, + .trigger = snd_azf3328_pcm_trigger, + .pointer = snd_azf3328_pcm_pointer +}; + +static const struct snd_pcm_ops snd_azf3328_capture_ops = { + .open = snd_azf3328_pcm_capture_open, + .close = snd_azf3328_pcm_close, + .prepare = snd_azf3328_pcm_prepare, + .trigger = snd_azf3328_pcm_trigger, + .pointer = snd_azf3328_pcm_pointer +}; + +static const struct snd_pcm_ops snd_azf3328_i2s_out_ops = { + .open = snd_azf3328_pcm_i2s_out_open, + .close = snd_azf3328_pcm_close, + .prepare = snd_azf3328_pcm_prepare, + .trigger = snd_azf3328_pcm_trigger, + .pointer = snd_azf3328_pcm_pointer +}; + +static int +snd_azf3328_pcm(struct snd_azf3328 *chip) +{ + /* pcm devices */ + enum { AZF_PCMDEV_STD, AZF_PCMDEV_I2S_OUT, NUM_AZF_PCMDEVS }; + + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "AZF3328 DSP", AZF_PCMDEV_STD, + 1, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_azf3328_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_azf3328_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + strscpy(pcm->name, chip->card->shortname); + /* same pcm object for playback/capture (see snd_pcm_new() above) */ + chip->pcm[AZF_CODEC_PLAYBACK] = pcm; + chip->pcm[AZF_CODEC_CAPTURE] = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + 64*1024, 64*1024); + + err = snd_pcm_new(chip->card, "AZF3328 I2S OUT", AZF_PCMDEV_I2S_OUT, + 1, 0, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_azf3328_i2s_out_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + strscpy(pcm->name, chip->card->shortname); + chip->pcm[AZF_CODEC_I2S_OUT] = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + 64*1024, 64*1024); + + return 0; +} + +/******************************************************************/ + +/*** NOTE: the physical timer resolution actually is 1024000 ticks per second + *** (probably derived from main crystal via a divider of 24), + *** but announcing those attributes to user-space would make programs + *** configure the timer to a 1 tick value, resulting in an absolutely fatal + *** timer IRQ storm. + *** Thus I chose to announce a down-scaled virtual timer to the outside and + *** calculate real timer countdown values internally. + *** (the scale factor can be set via module parameter "seqtimer_scaling"). + ***/ + +static int +snd_azf3328_timer_start(struct snd_timer *timer) +{ + struct snd_azf3328 *chip; + unsigned int delay; + + chip = snd_timer_chip(timer); + delay = ((timer->sticks * seqtimer_scaling) - 1) & TIMER_VALUE_MASK; + if (delay < 49) { + /* uhoh, that's not good, since user-space won't know about + * this timing tweak + * (we need to do it to avoid a lockup, though) */ + + dev_dbg(chip->card->dev, "delay was too low (%d)!\n", delay); + delay = 49; /* minimum time is 49 ticks */ + } + dev_dbg(chip->card->dev, "setting timer countdown value %d\n", delay); + delay |= TIMER_COUNTDOWN_ENABLE | TIMER_IRQ_ENABLE; + guard(spinlock_irqsave)(&chip->reg_lock); + snd_azf3328_ctrl_outl(chip, IDX_IO_TIMER_VALUE, delay); + return 0; +} + +static int +snd_azf3328_timer_stop(struct snd_timer *timer) +{ + struct snd_azf3328 *chip; + + chip = snd_timer_chip(timer); + guard(spinlock_irqsave)(&chip->reg_lock); + /* disable timer countdown and interrupt */ + /* Hmm, should we write TIMER_IRQ_ACK here? + YES indeed, otherwise a rogue timer operation - which prompts + ALSA(?) to call repeated stop() in vain, but NOT start() - + will never end (value 0x03 is kept shown in control byte). + Simply manually poking 0x04 _once_ immediately successfully stops + the hardware/ALSA interrupt activity. */ + snd_azf3328_ctrl_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x04); + return 0; +} + + +static int +snd_azf3328_timer_precise_resolution(struct snd_timer *timer, + unsigned long *num, unsigned long *den) +{ + *num = 1; + *den = 1024000 / seqtimer_scaling; + return 0; +} + +static struct snd_timer_hardware snd_azf3328_timer_hw = { + .flags = SNDRV_TIMER_HW_AUTO, + .resolution = 977, /* 1000000/1024000 = 0.9765625us */ + .ticks = 1024000, /* max tick count, defined by the value register; actually it's not 1024000, but 1048576, but we don't care */ + .start = snd_azf3328_timer_start, + .stop = snd_azf3328_timer_stop, + .precise_resolution = snd_azf3328_timer_precise_resolution, +}; + +static int +snd_azf3328_timer(struct snd_azf3328 *chip, int device) +{ + struct snd_timer *timer = NULL; + struct snd_timer_id tid; + int err; + + 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; + + snd_azf3328_timer_hw.resolution *= seqtimer_scaling; + snd_azf3328_timer_hw.ticks /= seqtimer_scaling; + + err = snd_timer_new(chip->card, "AZF3328", &tid, &timer); + if (err < 0) + goto out; + + strscpy(timer->name, "AZF3328 timer"); + timer->private_data = chip; + timer->hw = snd_azf3328_timer_hw; + + chip->timer = timer; + + snd_azf3328_timer_stop(timer); + + err = 0; + +out: + return err; +} + +/******************************************************************/ + +static void +snd_azf3328_free(struct snd_card *card) +{ + struct snd_azf3328 *chip = card->private_data; + + snd_azf3328_mixer_reset(chip); + + snd_azf3328_timer_stop(chip->timer); + snd_azf3328_gameport_free(chip); +} + +#if 0 +/* check whether a bit can be modified */ +static void +snd_azf3328_test_bit(unsigned unsigned reg, int bit) +{ + unsigned char val, valoff, valon; + + val = inb(reg); + + outb(val & ~(1 << bit), reg); + valoff = inb(reg); + + outb(val|(1 << bit), reg); + valon = inb(reg); + + outb(val, reg); + + printk(KERN_DEBUG "reg %04x bit %d: %02x %02x %02x\n", + reg, bit, val, valoff, valon + ); +} +#endif + +static inline void +snd_azf3328_debug_show_ports(const struct snd_azf3328 *chip) +{ + u16 tmp; + + dev_dbg(chip->card->dev, + "ctrl_io 0x%lx, game_io 0x%lx, mpu_io 0x%lx, " + "opl3_io 0x%lx, mixer_io 0x%lx, irq %d\n", + chip->ctrl_io, chip->game_io, chip->mpu_io, + chip->opl3_io, chip->mixer_io, chip->irq); + + dev_dbg(chip->card->dev, + "game %02x %02x %02x %02x %02x %02x\n", + snd_azf3328_game_inb(chip, 0), + snd_azf3328_game_inb(chip, 1), + snd_azf3328_game_inb(chip, 2), + snd_azf3328_game_inb(chip, 3), + snd_azf3328_game_inb(chip, 4), + snd_azf3328_game_inb(chip, 5)); + + for (tmp = 0; tmp < 0x07; tmp += 1) + dev_dbg(chip->card->dev, + "mpu_io 0x%04x\n", inb(chip->mpu_io + tmp)); + + for (tmp = 0; tmp <= 0x07; tmp += 1) + dev_dbg(chip->card->dev, + "0x%02x: game200 0x%04x, game208 0x%04x\n", + tmp, inb(0x200 + tmp), inb(0x208 + tmp)); + + for (tmp = 0; tmp <= 0x01; tmp += 1) + dev_dbg(chip->card->dev, + "0x%02x: mpu300 0x%04x, mpu310 0x%04x, mpu320 0x%04x, " + "mpu330 0x%04x opl388 0x%04x opl38c 0x%04x\n", + tmp, + inb(0x300 + tmp), + inb(0x310 + tmp), + inb(0x320 + tmp), + inb(0x330 + tmp), + inb(0x388 + tmp), + inb(0x38c + tmp)); + + for (tmp = 0; tmp < AZF_IO_SIZE_CTRL; tmp += 2) + dev_dbg(chip->card->dev, + "ctrl 0x%02x: 0x%04x\n", + tmp, snd_azf3328_ctrl_inw(chip, tmp)); + + for (tmp = 0; tmp < AZF_IO_SIZE_MIXER; tmp += 2) + dev_dbg(chip->card->dev, + "mixer 0x%02x: 0x%04x\n", + tmp, snd_azf3328_mixer_inw(chip, tmp)); +} + +static int +snd_azf3328_create(struct snd_card *card, + struct pci_dev *pci, + unsigned long device_type) +{ + struct snd_azf3328 *chip = card->private_data; + int err; + u8 dma_init; + enum snd_azf3328_codec_type codec_type; + struct snd_azf3328_codec_data *codec_setup; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + /* check if we can restrict PCI DMA transfers to 24 bits */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(24))) { + dev_err(card->dev, + "architecture does not support 24bit PCI busmaster DMA\n" + ); + return -ENXIO; + } + + err = pcim_request_all_regions(pci, "Aztech AZF3328"); + if (err < 0) + return err; + + chip->ctrl_io = pci_resource_start(pci, 0); + chip->game_io = pci_resource_start(pci, 1); + chip->mpu_io = pci_resource_start(pci, 2); + chip->opl3_io = pci_resource_start(pci, 3); + chip->mixer_io = pci_resource_start(pci, 4); + + codec_setup = &chip->codecs[AZF_CODEC_PLAYBACK]; + codec_setup->io_base = chip->ctrl_io + AZF_IO_OFFS_CODEC_PLAYBACK; + codec_setup->lock = &chip->reg_lock; + codec_setup->type = AZF_CODEC_PLAYBACK; + codec_setup->name = "PLAYBACK"; + + codec_setup = &chip->codecs[AZF_CODEC_CAPTURE]; + codec_setup->io_base = chip->ctrl_io + AZF_IO_OFFS_CODEC_CAPTURE; + codec_setup->lock = &chip->reg_lock; + codec_setup->type = AZF_CODEC_CAPTURE; + codec_setup->name = "CAPTURE"; + + codec_setup = &chip->codecs[AZF_CODEC_I2S_OUT]; + codec_setup->io_base = chip->ctrl_io + AZF_IO_OFFS_CODEC_I2S_OUT; + codec_setup->lock = &chip->reg_lock; + codec_setup->type = AZF_CODEC_I2S_OUT; + codec_setup->name = "I2S_OUT"; + + if (devm_request_irq(&pci->dev, pci->irq, snd_azf3328_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_azf3328_free; + pci_set_master(pci); + + snd_azf3328_debug_show_ports(chip); + + /* create mixer interface & switches */ + err = snd_azf3328_mixer_new(chip); + if (err < 0) + return err; + + /* standard codec init stuff */ + /* default DMA init value */ + dma_init = DMA_RUN_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE; + + for (codec_type = AZF_CODEC_PLAYBACK; + codec_type <= AZF_CODEC_I2S_OUT; ++codec_type) { + struct snd_azf3328_codec_data *codec = + &chip->codecs[codec_type]; + + /* shutdown codecs to reduce power / noise */ + /* have ...ctrl_codec_activity() act properly */ + codec->running = true; + snd_azf3328_ctrl_codec_activity(chip, codec_type, 0); + + guard(spinlock_irq)(codec->lock); + snd_azf3328_codec_outb(codec, IDX_IO_CODEC_DMA_FLAGS, + dma_init); + } + + return 0; +} + +static int +__snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_azf3328 *chip; + struct snd_opl3 *opl3; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + strscpy(card->driver, "AZF3328"); + strscpy(card->shortname, "Aztech AZF3328 (PCI168)"); + + err = snd_azf3328_create(card, pci, pci_id->driver_data); + if (err < 0) + return err; + + /* chose to use MPU401_HW_AZT2320 ID instead of MPU401_HW_MPU401, + since our hardware ought to be similar, thus use same ID. */ + err = snd_mpu401_uart_new( + card, 0, + MPU401_HW_AZT2320, chip->mpu_io, + MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK, + -1, &chip->rmidi + ); + if (err < 0) { + dev_err(card->dev, "no MPU-401 device at 0x%lx?\n", + chip->mpu_io + ); + return err; + } + + err = snd_azf3328_timer(chip, 0); + if (err < 0) + return err; + + err = snd_azf3328_pcm(chip); + if (err < 0) + return err; + + if (snd_opl3_create(card, chip->opl3_io, chip->opl3_io+2, + OPL3_HW_AUTO, 1, &opl3) < 0) { + dev_err(card->dev, "no OPL3 device at 0x%lx-0x%lx?\n", + chip->opl3_io, chip->opl3_io+2 + ); + } else { + /* need to use IDs 1, 2 since ID 0 is snd_azf3328_timer above */ + 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; + opl3->private_data = chip; + } + + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, chip->ctrl_io, chip->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + +#ifdef MODULE + dev_info(card->dev, + "Sound driver for Aztech AZF3328-based soundcards such as PCI168.\n"); + dev_info(card->dev, + "Hardware was completely undocumented, unfortunately.\n"); + dev_info(card->dev, + "Feel free to contact andi AT lisas.de for bug reports etc.!\n"); + dev_info(card->dev, + "User-scalable sequencer timer set to %dHz (1024000Hz / %d).\n", + 1024000 / seqtimer_scaling, seqtimer_scaling); +#endif + + snd_azf3328_gameport(chip, dev); + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int +snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_azf3328_probe(pci, pci_id)); +} + +static inline void +snd_azf3328_suspend_regs(const struct snd_azf3328 *chip, + unsigned long io_addr, unsigned count, u32 *saved_regs) +{ + unsigned reg; + + for (reg = 0; reg < count; ++reg) { + *saved_regs = inl(io_addr); + dev_dbg(chip->card->dev, "suspend: io 0x%04lx: 0x%08x\n", + io_addr, *saved_regs); + ++saved_regs; + io_addr += sizeof(*saved_regs); + } +} + +static inline void +snd_azf3328_resume_regs(const struct snd_azf3328 *chip, + const u32 *saved_regs, + unsigned long io_addr, + unsigned count +) +{ + unsigned reg; + + for (reg = 0; reg < count; ++reg) { + outl(*saved_regs, io_addr); + dev_dbg(chip->card->dev, + "resume: io 0x%04lx: 0x%08x --> 0x%08x\n", + io_addr, *saved_regs, inl(io_addr)); + ++saved_regs; + io_addr += sizeof(*saved_regs); + } +} + +static inline void +snd_azf3328_suspend_ac97(struct snd_azf3328 *chip) +{ +#ifdef AZF_USE_AC97_LAYER + snd_ac97_suspend(chip->ac97); +#else + snd_azf3328_suspend_regs(chip, chip->mixer_io, + ARRAY_SIZE(chip->saved_regs_mixer), chip->saved_regs_mixer); + + /* make sure to disable master volume etc. to prevent looping sound */ + snd_azf3328_mixer_mute_control_master(chip, 1); + snd_azf3328_mixer_mute_control_pcm(chip, 1); +#endif /* AZF_USE_AC97_LAYER */ +} + +static inline void +snd_azf3328_resume_ac97(const struct snd_azf3328 *chip) +{ +#ifdef AZF_USE_AC97_LAYER + snd_ac97_resume(chip->ac97); +#else + snd_azf3328_resume_regs(chip, chip->saved_regs_mixer, chip->mixer_io, + ARRAY_SIZE(chip->saved_regs_mixer)); + + /* unfortunately with 32bit transfers, IDX_MIXER_PLAY_MASTER (0x02) + and IDX_MIXER_RESET (offset 0x00) get touched at the same time, + resulting in a mixer reset condition persisting until _after_ + master vol was restored. Thus master vol needs an extra restore. */ + outw(((u16 *)chip->saved_regs_mixer)[1], chip->mixer_io + 2); +#endif /* AZF_USE_AC97_LAYER */ +} + +static int +snd_azf3328_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_azf3328 *chip = card->private_data; + u16 *saved_regs_ctrl_u16; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + snd_azf3328_suspend_ac97(chip); + + snd_azf3328_suspend_regs(chip, chip->ctrl_io, + ARRAY_SIZE(chip->saved_regs_ctrl), chip->saved_regs_ctrl); + + /* manually store the one currently relevant write-only reg, too */ + saved_regs_ctrl_u16 = (u16 *)chip->saved_regs_ctrl; + saved_regs_ctrl_u16[IDX_IO_6AH / 2] = chip->shadow_reg_ctrl_6AH; + + snd_azf3328_suspend_regs(chip, chip->game_io, + ARRAY_SIZE(chip->saved_regs_game), chip->saved_regs_game); + snd_azf3328_suspend_regs(chip, chip->mpu_io, + ARRAY_SIZE(chip->saved_regs_mpu), chip->saved_regs_mpu); + snd_azf3328_suspend_regs(chip, chip->opl3_io, + ARRAY_SIZE(chip->saved_regs_opl3), chip->saved_regs_opl3); + return 0; +} + +static int +snd_azf3328_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + const struct snd_azf3328 *chip = card->private_data; + + snd_azf3328_resume_regs(chip, chip->saved_regs_game, chip->game_io, + ARRAY_SIZE(chip->saved_regs_game)); + snd_azf3328_resume_regs(chip, chip->saved_regs_mpu, chip->mpu_io, + ARRAY_SIZE(chip->saved_regs_mpu)); + snd_azf3328_resume_regs(chip, chip->saved_regs_opl3, chip->opl3_io, + ARRAY_SIZE(chip->saved_regs_opl3)); + + snd_azf3328_resume_ac97(chip); + + snd_azf3328_resume_regs(chip, chip->saved_regs_ctrl, chip->ctrl_io, + ARRAY_SIZE(chip->saved_regs_ctrl)); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_azf3328_pm, snd_azf3328_suspend, snd_azf3328_resume); + +static struct pci_driver azf3328_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_azf3328_ids, + .probe = snd_azf3328_probe, + .driver = { + .pm = &snd_azf3328_pm, + }, +}; + +module_pci_driver(azf3328_driver); diff --git a/sound/pci/azt3328.h b/sound/pci/azt3328.h new file mode 100644 index 000000000..6f9022784 --- /dev/null +++ b/sound/pci/azt3328.h @@ -0,0 +1,343 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_AZT3328_H +#define __SOUND_AZT3328_H + +/* "PU" == "power-up value", as tested on PCI168 PCI rev. 10 + * "WRITE_ONLY" == register does not indicate actual bit values */ + +/*** main I/O area port indices ***/ +/* (only 0x70 of 0x80 bytes saved/restored by Windows driver) */ +#define AZF_IO_SIZE_CTRL 0x80 +#define AZF_IO_SIZE_CTRL_PM 0x70 + +/* the driver initialisation suggests a layout of 4 areas + * within the main card control I/O: + * from 0x00 (playback codec), from 0x20 (recording codec) + * and from 0x40 (most certainly I2S out codec). + * And another area from 0x60 to 0x6f (DirectX timer, IRQ management, + * power management etc.???). */ + +#define AZF_IO_OFFS_CODEC_PLAYBACK 0x00 +#define AZF_IO_OFFS_CODEC_CAPTURE 0x20 +#define AZF_IO_OFFS_CODEC_I2S_OUT 0x40 + +#define IDX_IO_CODEC_DMA_FLAGS 0x00 /* PU:0x0000 */ + /* able to reactivate output after output muting due to 8/16bit + * output change, just like 0x0002. + * 0x0001 is the only bit that's able to start the DMA counter */ + #define DMA_RESUME 0x0001 /* paused if cleared? */ + /* 0x0002 *temporarily* set during DMA stopping. hmm + * both 0x0002 and 0x0004 set in playback setup. */ + /* able to reactivate output after output muting due to 8/16bit + * output change, just like 0x0001. */ + #define DMA_RUN_SOMETHING1 0x0002 /* \ alternated (toggled) */ + /* 0x0004: NOT able to reactivate output */ + #define DMA_RUN_SOMETHING2 0x0004 /* / bits */ + #define SOMETHING_ALMOST_ALWAYS_SET 0x0008 /* ???; can be modified */ + #define DMA_EPILOGUE_SOMETHING 0x0010 + #define DMA_SOMETHING_ELSE 0x0020 /* ??? */ + #define SOMETHING_UNMODIFIABLE 0xffc0 /* unused? not modifiable */ +#define IDX_IO_CODEC_IRQTYPE 0x02 /* PU:0x0001 */ + /* write back to flags in case flags are set, in order to ACK IRQ in handler + * (bit 1 of port 0x64 indicates interrupt for one of these three types) + * sometimes in this case it just writes 0xffff to globally ACK all IRQs + * settings written are not reflected when reading back, though. + * seems to be IRQ, too (frequently used: port |= 0x07 !), but who knows? */ + #define IRQ_SOMETHING 0x0001 /* something & ACK */ + #define IRQ_FINISHED_DMABUF_1 0x0002 /* 1st dmabuf finished & ACK */ + #define IRQ_FINISHED_DMABUF_2 0x0004 /* 2nd dmabuf finished & ACK */ + #define IRQMASK_SOME_STATUS_1 0x0008 /* \ related bits */ + #define IRQMASK_SOME_STATUS_2 0x0010 /* / (checked together in loop) */ + #define IRQMASK_UNMODIFIABLE 0xffe0 /* unused? not modifiable */ + /* start address of 1st DMA transfer area, PU:0x00000000 */ +#define IDX_IO_CODEC_DMA_START_1 0x04 + /* start address of 2nd DMA transfer area, PU:0x00000000 */ +#define IDX_IO_CODEC_DMA_START_2 0x08 + /* both lengths of DMA transfer areas, PU:0x00000000 + length1: offset 0x0c, length2: offset 0x0e */ +#define IDX_IO_CODEC_DMA_LENGTHS 0x0c +#define IDX_IO_CODEC_DMA_CURRPOS 0x10 /* current DMA position, PU:0x00000000 */ + /* offset within current DMA transfer area, PU:0x0000 */ +#define IDX_IO_CODEC_DMA_CURROFS 0x14 +#define IDX_IO_CODEC_SOUNDFORMAT 0x16 /* PU:0x0010 */ + /* all unspecified bits can't be modified */ + #define SOUNDFORMAT_FREQUENCY_MASK 0x000f + #define SOUNDFORMAT_XTAL1 0x00 + #define SOUNDFORMAT_XTAL2 0x01 + /* all _SUSPECTED_ values are not used by Windows drivers, so we don't + * have any hard facts, only rough measurements. + * All we know is that the crystal used on the board has 24.576MHz, + * like many soundcards (which results in the frequencies below when + * using certain divider values selected by the values below) */ + #define SOUNDFORMAT_FREQ_SUSPECTED_4000 0x0c | SOUNDFORMAT_XTAL1 + #define SOUNDFORMAT_FREQ_SUSPECTED_4800 0x0a | SOUNDFORMAT_XTAL1 + #define SOUNDFORMAT_FREQ_5510 0x0c | SOUNDFORMAT_XTAL2 + #define SOUNDFORMAT_FREQ_6620 0x0a | SOUNDFORMAT_XTAL2 + #define SOUNDFORMAT_FREQ_8000 0x00 | SOUNDFORMAT_XTAL1 /* also 0x0e | SOUNDFORMAT_XTAL1? */ + #define SOUNDFORMAT_FREQ_9600 0x08 | SOUNDFORMAT_XTAL1 + #define SOUNDFORMAT_FREQ_11025 0x00 | SOUNDFORMAT_XTAL2 /* also 0x0e | SOUNDFORMAT_XTAL2? */ + #define SOUNDFORMAT_FREQ_SUSPECTED_13240 0x08 | SOUNDFORMAT_XTAL2 /* seems to be 6620 *2 */ + #define SOUNDFORMAT_FREQ_16000 0x02 | SOUNDFORMAT_XTAL1 + #define SOUNDFORMAT_FREQ_22050 0x02 | SOUNDFORMAT_XTAL2 + #define SOUNDFORMAT_FREQ_32000 0x04 | SOUNDFORMAT_XTAL1 + #define SOUNDFORMAT_FREQ_44100 0x04 | SOUNDFORMAT_XTAL2 + #define SOUNDFORMAT_FREQ_48000 0x06 | SOUNDFORMAT_XTAL1 + #define SOUNDFORMAT_FREQ_SUSPECTED_66200 0x06 | SOUNDFORMAT_XTAL2 /* 66200 (13240 * 5); 64000 may have been nicer :-\ */ + #define SOUNDFORMAT_FLAG_16BIT 0x0010 + #define SOUNDFORMAT_FLAG_2CHANNELS 0x0020 + + +/* define frequency helpers, for maximum value safety */ +enum azf_freq_t { +#define AZF_FREQ(rate) AZF_FREQ_##rate = rate + AZF_FREQ(4000), + AZF_FREQ(4800), + AZF_FREQ(5512), + AZF_FREQ(6620), + AZF_FREQ(8000), + AZF_FREQ(9600), + AZF_FREQ(11025), + AZF_FREQ(13240), + AZF_FREQ(16000), + AZF_FREQ(22050), + AZF_FREQ(32000), + AZF_FREQ(44100), + AZF_FREQ(48000), + AZF_FREQ(66200), +#undef AZF_FREQ +}; + +/** DirectX timer, main interrupt area (FIXME: and something else?) **/ +#define IDX_IO_TIMER_VALUE 0x60 /* found this timer area by pure luck :-) */ + /* timer countdown value; triggers IRQ when timer is finished */ + #define TIMER_VALUE_MASK 0x000fffffUL + /* activate timer countdown */ + #define TIMER_COUNTDOWN_ENABLE 0x01000000UL + /* trigger timer IRQ on zero transition */ + #define TIMER_IRQ_ENABLE 0x02000000UL + /* being set in IRQ handler in case port 0x00 (hmm, not port 0x64!?!?) + * had 0x0020 set upon IRQ handler */ + #define TIMER_IRQ_ACK 0x04000000UL +#define IDX_IO_IRQSTATUS 0x64 + /* some IRQ bit in here might also be used to signal a power-management timer + * timeout, to request shutdown of the chip (e.g. AD1815JS has such a thing). + * OPL3 hardware contains several timers which confusingly in most cases + * are NOT routed to an IRQ, but some designs (e.g. LM4560) DO support that, + * so I wouldn't be surprised at all to discover that AZF3328 + * supports that thing as well... */ + + #define IRQ_PLAYBACK 0x0001 + #define IRQ_RECORDING 0x0002 + #define IRQ_I2S_OUT 0x0004 /* this IS I2S, right!? (untested) */ + #define IRQ_GAMEPORT 0x0008 /* Interrupt of Digital(ly) Enhanced Game Port */ + #define IRQ_MPU401 0x0010 + #define IRQ_TIMER 0x0020 /* DirectX timer */ + #define IRQ_UNKNOWN2 0x0040 /* probably unused, or possibly OPL3 timer? */ + #define IRQ_UNKNOWN3 0x0080 /* probably unused, or possibly OPL3 timer? */ +#define IDX_IO_66H 0x66 /* writing 0xffff returns 0x0000 */ + /* this is set to e.g. 0x3ff or 0x300, and writable; + * maybe some buffer limit, but I couldn't find out more, PU:0x00ff: */ +#define IDX_IO_SOME_VALUE 0x68 + #define IO_68_RANDOM_TOGGLE1 0x0100 /* toggles randomly */ + #define IO_68_RANDOM_TOGGLE2 0x0200 /* toggles randomly */ + /* umm, nope, behaviour of these bits changes depending on what we wrote + * to 0x6b!! + * And they change upon playback/stop, too: + * Writing a value to 0x68 will display this exact value during playback, + * too but when stopped it can fall back to a rather different + * seemingly random value). Hmm, possibly this is a register which + * has a remote shadow which needs proper device supply which only exists + * in case playback is active? Or is this driver-induced? + */ + +/* this WORD can be set to have bits 0x0028 activated (FIXME: correct??); + * actually inhibits PCM playback!!! maybe power management??: */ +#define IDX_IO_6AH 0x6A /* WRITE_ONLY! */ + /* bit 5: enabling this will activate permanent counting of bytes 2/3 + * at gameport I/O (0xb402/3) (equal values each) and cause + * gameport legacy I/O at 0x0200 to be _DISABLED_! + * Is this Digital Enhanced Game Port Enable??? Or maybe it's Testmode + * for Enhanced Digital Gameport (see 4D Wave DX card): */ + #define IO_6A_SOMETHING1_GAMEPORT 0x0020 + /* bit 8; sure, this _pauses_ playback (later resumes at same spot!), + * but what the heck is this really about??: */ + #define IO_6A_PAUSE_PLAYBACK_BIT8 0x0100 + /* bit 9; sure, this _pauses_ playback (later resumes at same spot!), + * but what the heck is this really about??: */ + #define IO_6A_PAUSE_PLAYBACK_BIT9 0x0200 + /* BIT8 and BIT9 are _NOT_ able to affect OPL3 MIDI playback, + * thus it suggests influence on PCM only!! + * However OTOH there seems to be no bit anywhere around here + * which is able to disable OPL3... */ + /* bit 10: enabling this actually changes values at legacy gameport + * I/O address (0x200); is this enabling of the Digital Enhanced Game Port??? + * Or maybe this simply switches off the NE558 circuit, since enabling this + * still lets us evaluate button states, but not axis states */ + #define IO_6A_SOMETHING2_GAMEPORT 0x0400 + /* writing 0x0300: causes quite some crackling during + * PC activity such as switching windows (PCI traffic?? + * --> FIFO/timing settings???) */ + /* writing 0x0100 plus/or 0x0200 inhibits playback */ + /* since the Windows .INF file has Flag_Enable_JoyStick and + * Flag_Enable_SB_DOS_Emulation directly together, it stands to reason + * that some other bit in this same register might be responsible + * for SB DOS Emulation activation (note that the file did NOT define + * a switch for OPL3!) */ +#define IDX_IO_6CH 0x6C /* unknown; fully read-writable */ +#define IDX_IO_6EH 0x6E + /* writing 0xffff returns 0x83fe (or 0x03fe only). + * writing 0x83 (and only 0x83!!) to 0x6f will cause 0x6c to switch + * from 0000 to ffff. */ + +/* further I/O indices not saved/restored and not readable after writing, + * so probably not used */ + + +/*** Gameport area port indices ***/ +/* (only 0x06 of 0x08 bytes saved/restored by Windows driver) */ +#define AZF_IO_SIZE_GAME 0x08 +#define AZF_IO_SIZE_GAME_PM 0x06 + +enum { + AZF_GAME_LEGACY_IO_PORT = 0x200 +}; + +#define IDX_GAME_LEGACY_COMPATIBLE 0x00 + /* in some operation mode, writing anything to this port + * triggers an interrupt: + * yup, that's in case IDX_GAME_01H has one of the + * axis measurement bits enabled + * (and of course one needs to have GAME_HWCFG_IRQ_ENABLE, too) */ + +#define IDX_GAME_AXES_CONFIG 0x01 + /* NOTE: layout of this register awfully similar (read: "identical??") + * to AD1815JS.pdf (p.29) */ + + /* enables axis 1 (X axis) measurement: */ + #define GAME_AXES_ENABLE_1 0x01 + /* enables axis 2 (Y axis) measurement: */ + #define GAME_AXES_ENABLE_2 0x02 + /* enables axis 3 (X axis) measurement: */ + #define GAME_AXES_ENABLE_3 0x04 + /* enables axis 4 (Y axis) measurement: */ + #define GAME_AXES_ENABLE_4 0x08 + /* selects the current axis to read the measured value of + * (at IDX_GAME_AXIS_VALUE): + * 00 = axis 1, 01 = axis 2, 10 = axis 3, 11 = axis 4: */ + #define GAME_AXES_READ_MASK 0x30 + /* enable to have the latch continuously accept ADC values + * (and continuously cause interrupts in case interrupts are enabled); + * AD1815JS.pdf says it's ~16ms interval there: */ + #define GAME_AXES_LATCH_ENABLE 0x40 + /* joystick data (measured axes) ready for reading: */ + #define GAME_AXES_SAMPLING_READY 0x80 + + /* NOTE: other card specs (SiS960 and others!) state that the + * game position latches should be frozen when reading and be freed + * (== reset?) after reading!!! + * Freezing most likely means disabling 0x40 (GAME_AXES_LATCH_ENABLE), + * but how to free the value? */ + /* An internet search for "gameport latch ADC" should provide some insight + * into how to program such a gameport system. */ + + /* writing 0xf0 to 01H once reset both counters to 0, in some special mode!? + * yup, in case 6AH 0x20 is not enabled + * (and 0x40 is sufficient, 0xf0 is not needed) */ + +#define IDX_GAME_AXIS_VALUE 0x02 + /* R: value of currently configured axis (word value!); + * W: trigger axis measurement */ + +#define IDX_GAME_HWCONFIG 0x04 + /* note: bits 4 to 7 are never set (== 0) when reading! + * --> reserved bits? */ + /* enables IRQ notification upon axes measurement ready: */ + #define GAME_HWCFG_IRQ_ENABLE 0x01 + /* these bits choose a different frequency for the + * internal ADC counter increment. + * hmm, seems to be a combo of bits: + * 00 --> standard frequency + * 10 --> 1/2 + * 01 --> 1/20 + * 11 --> 1/200: */ + #define GAME_HWCFG_ADC_COUNTER_FREQ_MASK 0x06 + + /* FIXME: these values might be reversed... */ + #define GAME_HWCFG_ADC_COUNTER_FREQ_STD 0 + #define GAME_HWCFG_ADC_COUNTER_FREQ_1_2 1 + #define GAME_HWCFG_ADC_COUNTER_FREQ_1_20 2 + #define GAME_HWCFG_ADC_COUNTER_FREQ_1_200 3 + + /* enable gameport legacy I/O address (0x200) + * I was unable to locate any configurability for a different address: */ + #define GAME_HWCFG_LEGACY_ADDRESS_ENABLE 0x08 + +/*** MPU401 ***/ +#define AZF_IO_SIZE_MPU 0x04 +#define AZF_IO_SIZE_MPU_PM 0x04 + +/*** OPL3 synth ***/ +/* (only 0x06 of 0x08 bytes saved/restored by Windows driver) */ +#define AZF_IO_SIZE_OPL3 0x08 +#define AZF_IO_SIZE_OPL3_PM 0x06 +/* hmm, given that a standard OPL3 has 4 registers only, + * there might be some enhanced functionality lurking at the end + * (especially since register 0x04 has a "non-empty" value 0xfe) */ + +/*** mixer I/O area port indices ***/ +/* (only 0x22 of 0x40 bytes saved/restored by Windows driver) + * UNFORTUNATELY azf3328 is NOT truly AC97 compliant: see main file intro */ +#define AZF_IO_SIZE_MIXER 0x40 +#define AZF_IO_SIZE_MIXER_PM 0x22 + + #define MIXER_VOLUME_RIGHT_MASK 0x001f + #define MIXER_VOLUME_LEFT_MASK 0x1f00 + #define MIXER_MUTE_MASK 0x8000 +#define IDX_MIXER_RESET 0x00 /* does NOT seem to have AC97 ID bits */ +#define IDX_MIXER_PLAY_MASTER 0x02 +#define IDX_MIXER_MODEMOUT 0x04 +#define IDX_MIXER_BASSTREBLE 0x06 + #define MIXER_BASSTREBLE_TREBLE_VOLUME_MASK 0x000e + #define MIXER_BASSTREBLE_BASS_VOLUME_MASK 0x0e00 +#define IDX_MIXER_PCBEEP 0x08 +#define IDX_MIXER_MODEMIN 0x0a +#define IDX_MIXER_MIC 0x0c + #define MIXER_MIC_MICGAIN_20DB_ENHANCEMENT_MASK 0x0040 +#define IDX_MIXER_LINEIN 0x0e +#define IDX_MIXER_CDAUDIO 0x10 +#define IDX_MIXER_VIDEO 0x12 +#define IDX_MIXER_AUX 0x14 +#define IDX_MIXER_WAVEOUT 0x16 +#define IDX_MIXER_FMSYNTH 0x18 +#define IDX_MIXER_REC_SELECT 0x1a + #define MIXER_REC_SELECT_MIC 0x00 + #define MIXER_REC_SELECT_CD 0x01 + #define MIXER_REC_SELECT_VIDEO 0x02 + #define MIXER_REC_SELECT_AUX 0x03 + #define MIXER_REC_SELECT_LINEIN 0x04 + #define MIXER_REC_SELECT_MIXSTEREO 0x05 + #define MIXER_REC_SELECT_MIXMONO 0x06 + #define MIXER_REC_SELECT_MONOIN 0x07 +#define IDX_MIXER_REC_VOLUME 0x1c +#define IDX_MIXER_ADVCTL1 0x1e + /* unlisted bits are unmodifiable */ + #define MIXER_ADVCTL1_3DWIDTH_MASK 0x000e + #define MIXER_ADVCTL1_HIFI3D_MASK 0x0300 /* yup, this is missing the high bit that official AC97 contains, plus it doesn't have linear bit value range behaviour but instead acts weirdly (possibly we're dealing with two *different* 3D settings here??) */ +#define IDX_MIXER_ADVCTL2 0x20 /* subset of AC97_GENERAL_PURPOSE reg! */ + /* unlisted bits are unmodifiable */ + #define MIXER_ADVCTL2_LPBK 0x0080 /* Loopback mode -- Win driver: "WaveOut3DBypass"? mutes WaveOut at LineOut */ + #define MIXER_ADVCTL2_MS 0x0100 /* Mic Select 0=Mic1, 1=Mic2 -- Win driver: "ModemOutSelect"?? */ + #define MIXER_ADVCTL2_MIX 0x0200 /* Mono output select 0=Mix, 1=Mic; Win driver: "MonoSelectSource"?? */ + #define MIXER_ADVCTL2_3D 0x2000 /* 3D Enhancement 1=on */ + #define MIXER_ADVCTL2_POP 0x8000 /* Pcm Out Path, 0=pre 3D, 1=post 3D */ + +#define IDX_MIXER_SOMETHING30H 0x30 /* used, but unknown??? */ + +/* driver internal flags */ +#define SET_CHAN_LEFT 1 +#define SET_CHAN_RIGHT 2 + +/* helper macro to align I/O port ranges to 32bit I/O width */ +#define AZF_ALIGN(x) (((x) + 3) & (~3)) + +#endif /* __SOUND_AZT3328_H */ diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c new file mode 100644 index 000000000..383def1f2 --- /dev/null +++ b/sound/pci/bt87x.c @@ -0,0 +1,918 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * bt87x.c - Brooktree Bt878/Bt879 driver for ALSA + * + * Copyright (c) Clemens Ladisch + * + * based on btaudio.c by Gerd Knorr + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("Brooktree Bt87x audio driver"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -2}; /* Exclude the first card */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ +static int digital_rate[SNDRV_CARDS]; /* digital input rate */ +static bool load_all; /* allow to load cards not the allowlist */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Bt87x soundcard"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Bt87x soundcard"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Bt87x soundcard"); +module_param_array(digital_rate, int, NULL, 0444); +MODULE_PARM_DESC(digital_rate, "Digital input rate for Bt87x soundcard"); +module_param(load_all, bool, 0444); +MODULE_PARM_DESC(load_all, "Allow to load cards not on the allowlist"); + + +/* register offsets */ +#define REG_INT_STAT 0x100 /* interrupt status */ +#define REG_INT_MASK 0x104 /* interrupt mask */ +#define REG_GPIO_DMA_CTL 0x10c /* audio control */ +#define REG_PACKET_LEN 0x110 /* audio packet lengths */ +#define REG_RISC_STRT_ADD 0x114 /* RISC program start address */ +#define REG_RISC_COUNT 0x120 /* RISC program counter */ + +/* interrupt bits */ +#define INT_OFLOW (1 << 3) /* audio A/D overflow */ +#define INT_RISCI (1 << 11) /* RISC instruction IRQ bit set */ +#define INT_FBUS (1 << 12) /* FIFO overrun due to bus access latency */ +#define INT_FTRGT (1 << 13) /* FIFO overrun due to target latency */ +#define INT_FDSR (1 << 14) /* FIFO data stream resynchronization */ +#define INT_PPERR (1 << 15) /* PCI parity error */ +#define INT_RIPERR (1 << 16) /* RISC instruction parity error */ +#define INT_PABORT (1 << 17) /* PCI master or target abort */ +#define INT_OCERR (1 << 18) /* invalid opcode */ +#define INT_SCERR (1 << 19) /* sync counter overflow */ +#define INT_RISC_EN (1 << 27) /* DMA controller running */ +#define INT_RISCS_SHIFT 28 /* RISC status bits */ + +/* audio control bits */ +#define CTL_FIFO_ENABLE (1 << 0) /* enable audio data FIFO */ +#define CTL_RISC_ENABLE (1 << 1) /* enable audio DMA controller */ +#define CTL_PKTP_4 (0 << 2) /* packet mode FIFO trigger point - 4 DWORDs */ +#define CTL_PKTP_8 (1 << 2) /* 8 DWORDs */ +#define CTL_PKTP_16 (2 << 2) /* 16 DWORDs */ +#define CTL_ACAP_EN (1 << 4) /* enable audio capture */ +#define CTL_DA_APP (1 << 5) /* GPIO input */ +#define CTL_DA_IOM_AFE (0 << 6) /* audio A/D input */ +#define CTL_DA_IOM_DA (1 << 6) /* digital audio input */ +#define CTL_DA_SDR_SHIFT 8 /* DDF first stage decimation rate */ +#define CTL_DA_SDR_MASK (0xf<< 8) +#define CTL_DA_LMT (1 << 12) /* limit audio data values */ +#define CTL_DA_ES2 (1 << 13) /* enable DDF stage 2 */ +#define CTL_DA_SBR (1 << 14) /* samples rounded to 8 bits */ +#define CTL_DA_DPM (1 << 15) /* data packet mode */ +#define CTL_DA_LRD_SHIFT 16 /* ALRCK delay */ +#define CTL_DA_MLB (1 << 21) /* MSB/LSB format */ +#define CTL_DA_LRI (1 << 22) /* left/right indication */ +#define CTL_DA_SCE (1 << 23) /* sample clock edge */ +#define CTL_A_SEL_STV (0 << 24) /* TV tuner audio input */ +#define CTL_A_SEL_SFM (1 << 24) /* FM audio input */ +#define CTL_A_SEL_SML (2 << 24) /* mic/line audio input */ +#define CTL_A_SEL_SMXC (3 << 24) /* MUX bypass */ +#define CTL_A_SEL_SHIFT 24 +#define CTL_A_SEL_MASK (3 << 24) +#define CTL_A_PWRDN (1 << 26) /* analog audio power-down */ +#define CTL_A_G2X (1 << 27) /* audio gain boost */ +#define CTL_A_GAIN_SHIFT 28 /* audio input gain */ +#define CTL_A_GAIN_MASK (0xf<<28) + +/* RISC instruction opcodes */ +#define RISC_WRITE (0x1 << 28) /* write FIFO data to memory at address */ +#define RISC_WRITEC (0x5 << 28) /* write FIFO data to memory at current address */ +#define RISC_SKIP (0x2 << 28) /* skip FIFO data */ +#define RISC_JUMP (0x7 << 28) /* jump to address */ +#define RISC_SYNC (0x8 << 28) /* synchronize with FIFO */ + +/* RISC instruction bits */ +#define RISC_BYTES_ENABLE (0xf << 12) /* byte enable bits */ +#define RISC_RESYNC ( 1 << 15) /* disable FDSR errors */ +#define RISC_SET_STATUS_SHIFT 16 /* set status bits */ +#define RISC_RESET_STATUS_SHIFT 20 /* clear status bits */ +#define RISC_IRQ ( 1 << 24) /* interrupt */ +#define RISC_EOL ( 1 << 26) /* end of line */ +#define RISC_SOL ( 1 << 27) /* start of line */ + +/* SYNC status bits values */ +#define RISC_SYNC_FM1 0x6 +#define RISC_SYNC_VRO 0xc + +#define ANALOG_CLOCK 1792000 +#ifdef CONFIG_SND_BT87X_OVERCLOCK +#define CLOCK_DIV_MIN 1 +#else +#define CLOCK_DIV_MIN 4 +#endif +#define CLOCK_DIV_MAX 15 + +#define ERROR_INTERRUPTS (INT_FBUS | INT_FTRGT | INT_PPERR | \ + INT_RIPERR | INT_PABORT | INT_OCERR) +#define MY_INTERRUPTS (INT_RISCI | ERROR_INTERRUPTS) + +/* SYNC, one WRITE per line, one extra WRITE per page boundary, SYNC, JUMP */ +#define MAX_RISC_SIZE ((1 + 255 + (PAGE_ALIGN(255 * 4092) / PAGE_SIZE - 1) + 1 + 1) * 8) + +/* Cards with configuration information */ +enum snd_bt87x_boardid { + SND_BT87X_BOARD_UNKNOWN, + SND_BT87X_BOARD_GENERIC, /* both an & dig interfaces, 32kHz */ + SND_BT87X_BOARD_ANALOG, /* board with no external A/D */ + SND_BT87X_BOARD_OSPREY2x0, + SND_BT87X_BOARD_OSPREY440, + SND_BT87X_BOARD_AVPHONE98, +}; + +/* Card configuration */ +struct snd_bt87x_board { + int dig_rate; /* Digital input sampling rate */ + u32 digital_fmt; /* Register settings for digital input */ + unsigned no_analog:1; /* No analog input */ + unsigned no_digital:1; /* No digital input */ +}; + +static const struct snd_bt87x_board snd_bt87x_boards[] = { + [SND_BT87X_BOARD_UNKNOWN] = { + .dig_rate = 32000, /* just a guess */ + }, + [SND_BT87X_BOARD_GENERIC] = { + .dig_rate = 32000, + }, + [SND_BT87X_BOARD_ANALOG] = { + .no_digital = 1, + }, + [SND_BT87X_BOARD_OSPREY2x0] = { + .dig_rate = 44100, + .digital_fmt = CTL_DA_LRI | (1 << CTL_DA_LRD_SHIFT), + }, + [SND_BT87X_BOARD_OSPREY440] = { + .dig_rate = 32000, + .digital_fmt = CTL_DA_LRI | (1 << CTL_DA_LRD_SHIFT), + .no_analog = 1, + }, + [SND_BT87X_BOARD_AVPHONE98] = { + .dig_rate = 48000, + }, +}; + +struct snd_bt87x { + struct snd_card *card; + struct pci_dev *pci; + struct snd_bt87x_board board; + + void __iomem *mmio; + int irq; + + spinlock_t reg_lock; + unsigned long opened; + struct snd_pcm_substream *substream; + + struct snd_dma_buffer dma_risc; + unsigned int line_bytes; + unsigned int lines; + + u32 reg_control; + u32 interrupt_mask; + + int current_line; + + int pci_parity_errors; +}; + +enum { DEVICE_DIGITAL, DEVICE_ANALOG }; + +static inline u32 snd_bt87x_readl(struct snd_bt87x *chip, u32 reg) +{ + return readl(chip->mmio + reg); +} + +static inline void snd_bt87x_writel(struct snd_bt87x *chip, u32 reg, u32 value) +{ + writel(value, chip->mmio + reg); +} + +static int snd_bt87x_create_risc(struct snd_bt87x *chip, struct snd_pcm_substream *substream, + unsigned int periods, unsigned int period_bytes) +{ + unsigned int i, offset; + __le32 *risc; + + if (chip->dma_risc.area == NULL) { + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + PAGE_ALIGN(MAX_RISC_SIZE), &chip->dma_risc) < 0) + return -ENOMEM; + } + risc = (__le32 *)chip->dma_risc.area; + offset = 0; + *risc++ = cpu_to_le32(RISC_SYNC | RISC_SYNC_FM1); + *risc++ = cpu_to_le32(0); + for (i = 0; i < periods; ++i) { + u32 rest; + + rest = period_bytes; + do { + u32 cmd, len; + unsigned int addr; + + len = PAGE_SIZE - (offset % PAGE_SIZE); + if (len > rest) + len = rest; + cmd = RISC_WRITE | len; + if (rest == period_bytes) { + u32 block = i * 16 / periods; + cmd |= RISC_SOL; + cmd |= block << RISC_SET_STATUS_SHIFT; + cmd |= (~block & 0xf) << RISC_RESET_STATUS_SHIFT; + } + if (len == rest) + cmd |= RISC_EOL | RISC_IRQ; + *risc++ = cpu_to_le32(cmd); + addr = snd_pcm_sgbuf_get_addr(substream, offset); + *risc++ = cpu_to_le32(addr); + offset += len; + rest -= len; + } while (rest > 0); + } + *risc++ = cpu_to_le32(RISC_SYNC | RISC_SYNC_VRO); + *risc++ = cpu_to_le32(0); + *risc++ = cpu_to_le32(RISC_JUMP); + *risc++ = cpu_to_le32(chip->dma_risc.addr); + chip->line_bytes = period_bytes; + chip->lines = periods; + return 0; +} + +static void snd_bt87x_free_risc(struct snd_bt87x *chip) +{ + if (chip->dma_risc.area) { + snd_dma_free_pages(&chip->dma_risc); + chip->dma_risc.area = NULL; + } +} + +static void snd_bt87x_pci_error(struct snd_bt87x *chip, unsigned int status) +{ + int pci_status = pci_status_get_and_clear_errors(chip->pci); + + if (pci_status != PCI_STATUS_DETECTED_PARITY) + dev_err(chip->card->dev, + "Aieee - PCI error! status %#08x, PCI status %#04x\n", + status & ERROR_INTERRUPTS, pci_status); + else { + dev_err(chip->card->dev, + "Aieee - PCI parity error detected!\n"); + /* error 'handling' similar to aic7xxx_pci.c: */ + chip->pci_parity_errors++; + if (chip->pci_parity_errors > 20) { + dev_err(chip->card->dev, + "Too many PCI parity errors observed.\n"); + dev_err(chip->card->dev, + "Some device on this bus is generating bad parity.\n"); + dev_err(chip->card->dev, + "This is an error *observed by*, not *generated by*, this card.\n"); + dev_err(chip->card->dev, + "PCI parity error checking has been disabled.\n"); + chip->interrupt_mask &= ~(INT_PPERR | INT_RIPERR); + snd_bt87x_writel(chip, REG_INT_MASK, chip->interrupt_mask); + } + } +} + +static irqreturn_t snd_bt87x_interrupt(int irq, void *dev_id) +{ + struct snd_bt87x *chip = dev_id; + unsigned int status, irq_status; + + status = snd_bt87x_readl(chip, REG_INT_STAT); + irq_status = status & chip->interrupt_mask; + if (!irq_status) + return IRQ_NONE; + snd_bt87x_writel(chip, REG_INT_STAT, irq_status); + + if (irq_status & ERROR_INTERRUPTS) { + if (irq_status & (INT_FBUS | INT_FTRGT)) + dev_warn(chip->card->dev, + "FIFO overrun, status %#08x\n", status); + if (irq_status & INT_OCERR) + dev_err(chip->card->dev, + "internal RISC error, status %#08x\n", status); + if (irq_status & (INT_PPERR | INT_RIPERR | INT_PABORT)) + snd_bt87x_pci_error(chip, irq_status); + } + if ((irq_status & INT_RISCI) && (chip->reg_control & CTL_ACAP_EN)) { + int current_block, irq_block; + + /* assume that exactly one line has been recorded */ + chip->current_line = (chip->current_line + 1) % chip->lines; + /* but check if some interrupts have been skipped */ + current_block = chip->current_line * 16 / chip->lines; + irq_block = status >> INT_RISCS_SHIFT; + if (current_block != irq_block) + chip->current_line = DIV_ROUND_UP(irq_block * chip->lines, + 16); + + snd_pcm_period_elapsed(chip->substream); + } + return IRQ_HANDLED; +} + +static const struct snd_pcm_hardware snd_bt87x_digital_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = 0, /* set at runtime */ + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 255 * 4092, + .period_bytes_min = 32, + .period_bytes_max = 4092, + .periods_min = 2, + .periods_max = 255, +}; + +static const struct snd_pcm_hardware snd_bt87x_analog_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = ANALOG_CLOCK / CLOCK_DIV_MAX, + .rate_max = ANALOG_CLOCK / CLOCK_DIV_MIN, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 255 * 4092, + .period_bytes_min = 32, + .period_bytes_max = 4092, + .periods_min = 2, + .periods_max = 255, +}; + +static int snd_bt87x_set_digital_hw(struct snd_bt87x *chip, struct snd_pcm_runtime *runtime) +{ + chip->reg_control |= CTL_DA_IOM_DA | CTL_A_PWRDN; + runtime->hw = snd_bt87x_digital_hw; + runtime->hw.rates = snd_pcm_rate_to_rate_bit(chip->board.dig_rate); + runtime->hw.rate_min = chip->board.dig_rate; + runtime->hw.rate_max = chip->board.dig_rate; + return 0; +} + +static int snd_bt87x_set_analog_hw(struct snd_bt87x *chip, struct snd_pcm_runtime *runtime) +{ + static const struct snd_ratnum analog_clock = { + .num = ANALOG_CLOCK, + .den_min = CLOCK_DIV_MIN, + .den_max = CLOCK_DIV_MAX, + .den_step = 1 + }; + static const struct snd_pcm_hw_constraint_ratnums constraint_rates = { + .nrats = 1, + .rats = &analog_clock + }; + + chip->reg_control &= ~(CTL_DA_IOM_DA | CTL_A_PWRDN); + runtime->hw = snd_bt87x_analog_hw; + return snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); +} + +static int snd_bt87x_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_bt87x *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + if (test_and_set_bit(0, &chip->opened)) + return -EBUSY; + + if (substream->pcm->device == DEVICE_DIGITAL) + err = snd_bt87x_set_digital_hw(chip, runtime); + else + err = snd_bt87x_set_analog_hw(chip, runtime); + if (err < 0) + goto _error; + + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto _error; + + chip->substream = substream; + return 0; + +_error: + clear_bit(0, &chip->opened); + smp_mb__after_atomic(); + return err; +} + +static int snd_bt87x_close(struct snd_pcm_substream *substream) +{ + struct snd_bt87x *chip = snd_pcm_substream_chip(substream); + + scoped_guard(spinlock_irq, &chip->reg_lock) { + chip->reg_control |= CTL_A_PWRDN; + snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control); + } + + chip->substream = NULL; + clear_bit(0, &chip->opened); + smp_mb__after_atomic(); + return 0; +} + +static int snd_bt87x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bt87x *chip = snd_pcm_substream_chip(substream); + + return snd_bt87x_create_risc(chip, substream, + params_periods(hw_params), + params_period_bytes(hw_params)); +} + +static int snd_bt87x_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bt87x *chip = snd_pcm_substream_chip(substream); + + snd_bt87x_free_risc(chip); + return 0; +} + +static int snd_bt87x_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bt87x *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int decimation; + + guard(spinlock_irq)(&chip->reg_lock); + chip->reg_control &= ~(CTL_DA_SDR_MASK | CTL_DA_SBR); + decimation = (ANALOG_CLOCK + runtime->rate / 4) / runtime->rate; + chip->reg_control |= decimation << CTL_DA_SDR_SHIFT; + if (runtime->format == SNDRV_PCM_FORMAT_S8) + chip->reg_control |= CTL_DA_SBR; + snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control); + return 0; +} + +static int snd_bt87x_start(struct snd_bt87x *chip) +{ + guard(spinlock)(&chip->reg_lock); + chip->current_line = 0; + chip->reg_control |= CTL_FIFO_ENABLE | CTL_RISC_ENABLE | CTL_ACAP_EN; + snd_bt87x_writel(chip, REG_RISC_STRT_ADD, chip->dma_risc.addr); + snd_bt87x_writel(chip, REG_PACKET_LEN, + chip->line_bytes | (chip->lines << 16)); + snd_bt87x_writel(chip, REG_INT_MASK, chip->interrupt_mask); + snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control); + return 0; +} + +static int snd_bt87x_stop(struct snd_bt87x *chip) +{ + guard(spinlock)(&chip->reg_lock); + chip->reg_control &= ~(CTL_FIFO_ENABLE | CTL_RISC_ENABLE | CTL_ACAP_EN); + snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control); + snd_bt87x_writel(chip, REG_INT_MASK, 0); + snd_bt87x_writel(chip, REG_INT_STAT, MY_INTERRUPTS); + return 0; +} + +static int snd_bt87x_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bt87x *chip = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + return snd_bt87x_start(chip); + case SNDRV_PCM_TRIGGER_STOP: + return snd_bt87x_stop(chip); + default: + return -EINVAL; + } +} + +static snd_pcm_uframes_t snd_bt87x_pointer(struct snd_pcm_substream *substream) +{ + struct snd_bt87x *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + return (snd_pcm_uframes_t)bytes_to_frames(runtime, chip->current_line * chip->line_bytes); +} + +static const struct snd_pcm_ops snd_bt87x_pcm_ops = { + .open = snd_bt87x_pcm_open, + .close = snd_bt87x_close, + .hw_params = snd_bt87x_hw_params, + .hw_free = snd_bt87x_hw_free, + .prepare = snd_bt87x_prepare, + .trigger = snd_bt87x_trigger, + .pointer = snd_bt87x_pointer, +}; + +static int snd_bt87x_capture_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = 0; + info->value.integer.max = 15; + return 0; +} + +static int snd_bt87x_capture_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol); + + value->value.integer.value[0] = (chip->reg_control & CTL_A_GAIN_MASK) >> CTL_A_GAIN_SHIFT; + return 0; +} + +static int snd_bt87x_capture_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol); + u32 old_control; + int changed; + + guard(spinlock_irq)(&chip->reg_lock); + old_control = chip->reg_control; + chip->reg_control = (chip->reg_control & ~CTL_A_GAIN_MASK) + | (value->value.integer.value[0] << CTL_A_GAIN_SHIFT); + snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control); + changed = old_control != chip->reg_control; + return changed; +} + +static const struct snd_kcontrol_new snd_bt87x_capture_volume = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Volume", + .info = snd_bt87x_capture_volume_info, + .get = snd_bt87x_capture_volume_get, + .put = snd_bt87x_capture_volume_put, +}; + +#define snd_bt87x_capture_boost_info snd_ctl_boolean_mono_info + +static int snd_bt87x_capture_boost_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol); + + value->value.integer.value[0] = !! (chip->reg_control & CTL_A_G2X); + return 0; +} + +static int snd_bt87x_capture_boost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol); + u32 old_control; + int changed; + + guard(spinlock_irq)(&chip->reg_lock); + old_control = chip->reg_control; + chip->reg_control = (chip->reg_control & ~CTL_A_G2X) + | (value->value.integer.value[0] ? CTL_A_G2X : 0); + snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control); + changed = chip->reg_control != old_control; + return changed; +} + +static const struct snd_kcontrol_new snd_bt87x_capture_boost = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Boost", + .info = snd_bt87x_capture_boost_info, + .get = snd_bt87x_capture_boost_get, + .put = snd_bt87x_capture_boost_put, +}; + +static int snd_bt87x_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + static const char *const texts[3] = {"TV Tuner", "FM", "Mic/Line"}; + + return snd_ctl_enum_info(info, 1, 3, texts); +} + +static int snd_bt87x_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol); + + value->value.enumerated.item[0] = (chip->reg_control & CTL_A_SEL_MASK) >> CTL_A_SEL_SHIFT; + return 0; +} + +static int snd_bt87x_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol); + u32 old_control; + int changed; + + guard(spinlock_irq)(&chip->reg_lock); + old_control = chip->reg_control; + chip->reg_control = (chip->reg_control & ~CTL_A_SEL_MASK) + | (value->value.enumerated.item[0] << CTL_A_SEL_SHIFT); + snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control); + changed = chip->reg_control != old_control; + return changed; +} + +static const struct snd_kcontrol_new snd_bt87x_capture_source = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_bt87x_capture_source_info, + .get = snd_bt87x_capture_source_get, + .put = snd_bt87x_capture_source_put, +}; + +static void snd_bt87x_free(struct snd_card *card) +{ + struct snd_bt87x *chip = card->private_data; + + snd_bt87x_stop(chip); +} + +static int snd_bt87x_pcm(struct snd_bt87x *chip, int device, char *name) +{ + int err; + struct snd_pcm *pcm; + + err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + strscpy(pcm->name, name); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_bt87x_pcm_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci->dev, + 128 * 1024, + ALIGN(255 * 4092, 1024)); + return 0; +} + +static int snd_bt87x_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct snd_bt87x *chip = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + chip->card = card; + chip->pci = pci; + chip->irq = -1; + spin_lock_init(&chip->reg_lock); + + chip->mmio = pcim_iomap_region(pci, 0, "Bt87x audio"); + if (IS_ERR(chip->mmio)) + return PTR_ERR(chip->mmio); + + chip->reg_control = CTL_A_PWRDN | CTL_DA_ES2 | + CTL_PKTP_16 | (15 << CTL_DA_SDR_SHIFT); + chip->interrupt_mask = MY_INTERRUPTS; + snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control); + snd_bt87x_writel(chip, REG_INT_MASK, 0); + snd_bt87x_writel(chip, REG_INT_STAT, MY_INTERRUPTS); + + err = devm_request_irq(&pci->dev, pci->irq, snd_bt87x_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (err < 0) { + dev_err(card->dev, "cannot grab irq %d\n", pci->irq); + return err; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_bt87x_free; + pci_set_master(pci); + + return 0; +} + +#define BT_DEVICE(chip, subvend, subdev, id) \ + { .vendor = PCI_VENDOR_ID_BROOKTREE, \ + .device = chip, \ + .subvendor = subvend, .subdevice = subdev, \ + .driver_data = SND_BT87X_BOARD_ ## id } +/* driver_data is the card id for that device */ + +static const struct pci_device_id snd_bt87x_ids[] = { + /* Hauppauge WinTV series */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x0070, 0x13eb, GENERIC), + /* Hauppauge WinTV series */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_879, 0x0070, 0x13eb, GENERIC), + /* Viewcast Osprey 200 */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x0070, 0xff01, OSPREY2x0), + /* Viewcast Osprey 440 (rate is configurable via gpio) */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x0070, 0xff07, OSPREY440), + /* ATI TV-Wonder */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1002, 0x0001, GENERIC), + /* Leadtek Winfast tv 2000xp delux */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x107d, 0x6606, GENERIC), + /* Pinnacle PCTV */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x11bd, 0x0012, GENERIC), + /* Voodoo TV 200 */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x121a, 0x3000, GENERIC), + /* Askey Computer Corp. MagicTView'99 */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x144f, 0x3000, GENERIC), + /* AVerMedia Studio No. 103, 203, ...? */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1461, 0x0003, AVPHONE98), + /* Prolink PixelView PV-M4900 */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1554, 0x4011, GENERIC), + /* Pinnacle Studio PCTV rave */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0xbd11, 0x1200, GENERIC), + { } +}; +MODULE_DEVICE_TABLE(pci, snd_bt87x_ids); + +/* cards known not to have audio + * (DVB cards use the audio function to transfer MPEG data) */ +static struct { + unsigned short subvendor, subdevice; +} denylist[] = { + {0x0071, 0x0101}, /* Nebula Electronics DigiTV */ + {0x11bd, 0x001c}, /* Pinnacle PCTV Sat */ + {0x11bd, 0x0026}, /* Pinnacle PCTV SAT CI */ + {0x1461, 0x0761}, /* AVermedia AverTV DVB-T */ + {0x1461, 0x0771}, /* AVermedia DVB-T 771 */ + {0x1822, 0x0001}, /* Twinhan VisionPlus DVB-T */ + {0x18ac, 0xd500}, /* DVICO FusionHDTV 5 Lite */ + {0x18ac, 0xdb10}, /* DVICO FusionHDTV DVB-T Lite */ + {0x18ac, 0xdb11}, /* Ultraview DVB-T Lite */ + {0x270f, 0xfc00}, /* Chaintech Digitop DST-1000 DVB-S */ + {0x7063, 0x2000}, /* pcHDTV HD-2000 TV */ +}; + +static struct pci_driver driver; + +/* return the id of the card, or a negative value if it's on the denylist */ +static int snd_bt87x_detect_card(struct pci_dev *pci) +{ + int i; + const struct pci_device_id *supported; + + supported = pci_match_id(snd_bt87x_ids, pci); + if (supported && supported->driver_data > 0) + return supported->driver_data; + + for (i = 0; i < ARRAY_SIZE(denylist); ++i) + if (denylist[i].subvendor == pci->subsystem_vendor && + denylist[i].subdevice == pci->subsystem_device) { + dev_dbg(&pci->dev, + "card %#04x-%#04x:%#04x has no audio\n", + pci->device, pci->subsystem_vendor, pci->subsystem_device); + return -EBUSY; + } + + dev_info(&pci->dev, "unknown card %#04x-%#04x:%#04x\n", + pci->device, pci->subsystem_vendor, pci->subsystem_device); + dev_info(&pci->dev, "please mail id, board name, and, " + "if it works, the correct digital_rate option to " + "\n"); + return SND_BT87X_BOARD_UNKNOWN; +} + +static int __snd_bt87x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_bt87x *chip; + int err; + enum snd_bt87x_boardid boardid; + + if (!pci_id->driver_data) { + err = snd_bt87x_detect_card(pci); + if (err < 0) + return -ENODEV; + boardid = err; + } else + boardid = pci_id->driver_data; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + ++dev; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + err = snd_bt87x_create(card, pci); + if (err < 0) + return err; + + memcpy(&chip->board, &snd_bt87x_boards[boardid], sizeof(chip->board)); + + if (!chip->board.no_digital) { + if (digital_rate[dev] > 0) + chip->board.dig_rate = digital_rate[dev]; + + chip->reg_control |= chip->board.digital_fmt; + + err = snd_bt87x_pcm(chip, DEVICE_DIGITAL, "Bt87x Digital"); + if (err < 0) + return err; + } + if (!chip->board.no_analog) { + err = snd_bt87x_pcm(chip, DEVICE_ANALOG, "Bt87x Analog"); + if (err < 0) + return err; + err = snd_ctl_add(card, snd_ctl_new1( + &snd_bt87x_capture_volume, chip)); + if (err < 0) + return err; + err = snd_ctl_add(card, snd_ctl_new1( + &snd_bt87x_capture_boost, chip)); + if (err < 0) + return err; + err = snd_ctl_add(card, snd_ctl_new1( + &snd_bt87x_capture_source, chip)); + if (err < 0) + return err; + } + dev_info(card->dev, "bt87x%d: Using board %d, %sanalog, %sdigital " + "(rate %d Hz)\n", dev, boardid, + chip->board.no_analog ? "no " : "", + chip->board.no_digital ? "no " : "", chip->board.dig_rate); + + strscpy(card->driver, "Bt87x"); + sprintf(card->shortname, "Brooktree Bt%x", pci->device); + sprintf(card->longname, "%s at %#llx, irq %i", + card->shortname, (unsigned long long)pci_resource_start(pci, 0), + chip->irq); + strscpy(card->mixername, "Bt87x"); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + ++dev; + return 0; +} + +static int snd_bt87x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_bt87x_probe(pci, pci_id)); +} + +/* default entries for all Bt87x cards - it's not exported */ +/* driver_data is set to 0 to call detection */ +static const struct pci_device_id snd_bt87x_default_ids[] = { + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, PCI_ANY_ID, PCI_ANY_ID, UNKNOWN), + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_879, PCI_ANY_ID, PCI_ANY_ID, UNKNOWN), + { } +}; + +static struct pci_driver driver = { + .name = KBUILD_MODNAME, + .id_table = snd_bt87x_ids, + .probe = snd_bt87x_probe, +}; + +static int __init alsa_card_bt87x_init(void) +{ + if (load_all) + driver.id_table = snd_bt87x_default_ids; + return pci_register_driver(&driver); +} + +static void __exit alsa_card_bt87x_exit(void) +{ + pci_unregister_driver(&driver); +} + +module_init(alsa_card_bt87x_init) +module_exit(alsa_card_bt87x_exit) diff --git a/sound/pci/ca0106/Makefile b/sound/pci/ca0106/Makefile new file mode 100644 index 000000000..693dc4d80 --- /dev/null +++ b/sound/pci/ca0106/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-ca0106-y := ca0106_main.o ca0106_mixer.o ca_midi.o +snd-ca0106-$(CONFIG_SND_PROC_FS) += ca0106_proc.o + +obj-$(CONFIG_SND_CA0106) += snd-ca0106.o diff --git a/sound/pci/ca0106/ca0106.h b/sound/pci/ca0106/ca0106.h new file mode 100644 index 000000000..991b1c5d4 --- /dev/null +++ b/sound/pci/ca0106/ca0106.h @@ -0,0 +1,726 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2004 James Courtier-Dutton + * Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit + * Version: 0.0.22 + * + * FEATURES currently supported: + * See ca0106_main.c for features. + * + * Changelog: + * Support interrupts per period. + * Removed noise from Center/LFE channel when in Analog mode. + * Rename and remove mixer controls. + * 0.0.6 + * Use separate card based DMA buffer for periods table list. + * 0.0.7 + * Change remove and rename ctrls into lists. + * 0.0.8 + * Try to fix capture sources. + * 0.0.9 + * Fix AC3 output. + * Enable S32_LE format support. + * 0.0.10 + * Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".) + * 0.0.11 + * Add Model name recognition. + * 0.0.12 + * Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period. + * Remove redundent "voice" handling. + * 0.0.13 + * Single trigger call for multi channels. + * 0.0.14 + * Set limits based on what the sound card hardware can do. + * playback periods_min=2, periods_max=8 + * capture hw constraints require period_size = n * 64 bytes. + * playback hw constraints require period_size = n * 64 bytes. + * 0.0.15 + * Separated ca0106.c into separate functional .c files. + * 0.0.16 + * Implement 192000 sample rate. + * 0.0.17 + * Add support for SB0410 and SB0413. + * 0.0.18 + * Modified Copyright message. + * 0.0.19 + * Added I2C and SPI registers. Filled in interrupt enable. + * 0.0.20 + * Added GPIO info for SB Live 24bit. + * 0.0.21 + * Implement support for Line-in capture on SB Live 24bit. + * 0.0.22 + * Add support for mute control on SB Live 24bit (cards w/ SPI DAC) + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + */ + +/************************************************************************************************/ +/* PCI function 0 registers, address = + PCIBASE0 */ +/************************************************************************************************/ + +#define CA0106_PTR 0x00 /* Indexed register set pointer register */ + /* NOTE: The CHANNELNUM and ADDRESS words can */ + /* be modified independently of each other. */ + /* CNL[1:0], ADDR[27:16] */ + +#define CA0106_DATA 0x04 /* Indexed register set data register */ + /* DATA[31:0] */ + +#define CA0106_IPR 0x08 /* Global interrupt pending register */ + /* Clear pending interrupts by writing a 1 to */ + /* the relevant bits and zero to the other bits */ +#define IPR_MIDI_RX_B 0x00020000 /* MIDI UART-B Receive buffer non-empty */ +#define IPR_MIDI_TX_B 0x00010000 /* MIDI UART-B Transmit buffer empty */ +#define IPR_SPDIF_IN_USER 0x00004000 /* SPDIF input user data has 16 more bits */ +#define IPR_SPDIF_OUT_USER 0x00002000 /* SPDIF output user data needs 16 more bits */ +#define IPR_SPDIF_OUT_FRAME 0x00001000 /* SPDIF frame about to start */ +#define IPR_SPI 0x00000800 /* SPI transaction completed */ +#define IPR_I2C_EEPROM 0x00000400 /* I2C EEPROM transaction completed */ +#define IPR_I2C_DAC 0x00000200 /* I2C DAC transaction completed */ +#define IPR_AI 0x00000100 /* Audio pending register changed. See PTR reg 0x76 */ +#define IPR_GPI 0x00000080 /* General Purpose input changed */ +#define IPR_SRC_LOCKED 0x00000040 /* SRC lock status changed */ +#define IPR_SPDIF_STATUS 0x00000020 /* SPDIF status changed */ +#define IPR_TIMER2 0x00000010 /* 192000Hz Timer */ +#define IPR_TIMER1 0x00000008 /* 44100Hz Timer */ +#define IPR_MIDI_RX_A 0x00000004 /* MIDI UART-A Receive buffer non-empty */ +#define IPR_MIDI_TX_A 0x00000002 /* MIDI UART-A Transmit buffer empty */ +#define IPR_PCI 0x00000001 /* PCI Bus error */ + +#define CA0106_INTE 0x0c /* Interrupt enable register */ + +#define INTE_MIDI_RX_B 0x00020000 /* MIDI UART-B Receive buffer non-empty */ +#define INTE_MIDI_TX_B 0x00010000 /* MIDI UART-B Transmit buffer empty */ +#define INTE_SPDIF_IN_USER 0x00004000 /* SPDIF input user data has 16 more bits */ +#define INTE_SPDIF_OUT_USER 0x00002000 /* SPDIF output user data needs 16 more bits */ +#define INTE_SPDIF_OUT_FRAME 0x00001000 /* SPDIF frame about to start */ +#define INTE_SPI 0x00000800 /* SPI transaction completed */ +#define INTE_I2C_EEPROM 0x00000400 /* I2C EEPROM transaction completed */ +#define INTE_I2C_DAC 0x00000200 /* I2C DAC transaction completed */ +#define INTE_AI 0x00000100 /* Audio pending register changed. See PTR reg 0x75 */ +#define INTE_GPI 0x00000080 /* General Purpose input changed */ +#define INTE_SRC_LOCKED 0x00000040 /* SRC lock status changed */ +#define INTE_SPDIF_STATUS 0x00000020 /* SPDIF status changed */ +#define INTE_TIMER2 0x00000010 /* 192000Hz Timer */ +#define INTE_TIMER1 0x00000008 /* 44100Hz Timer */ +#define INTE_MIDI_RX_A 0x00000004 /* MIDI UART-A Receive buffer non-empty */ +#define INTE_MIDI_TX_A 0x00000002 /* MIDI UART-A Transmit buffer empty */ +#define INTE_PCI 0x00000001 /* PCI Bus error */ + +#define CA0106_UNKNOWN10 0x10 /* Unknown ??. Defaults to 0 */ +#define CA0106_HCFG 0x14 /* Hardware config register */ + /* 0x1000 causes AC3 to fails. It adds a dither bit. */ + +#define HCFG_STAC 0x10000000 /* Special mode for STAC9460 Codec. */ +#define HCFG_CAPTURE_I2S_BYPASS 0x08000000 /* 1 = bypass I2S input async SRC. */ +#define HCFG_CAPTURE_SPDIF_BYPASS 0x04000000 /* 1 = bypass SPDIF input async SRC. */ +#define HCFG_PLAYBACK_I2S_BYPASS 0x02000000 /* 0 = I2S IN mixer output, 1 = I2S IN1. */ +#define HCFG_FORCE_LOCK 0x01000000 /* For test only. Force input SRC tracker to lock. */ +#define HCFG_PLAYBACK_ATTENUATION 0x00006000 /* Playback attenuation mask. 0 = 0dB, 1 = 6dB, 2 = 12dB, 3 = Mute. */ +#define HCFG_PLAYBACK_DITHER 0x00001000 /* 1 = Add dither bit to all playback channels. */ +#define HCFG_PLAYBACK_S32_LE 0x00000800 /* 1 = S32_LE, 0 = S16_LE */ +#define HCFG_CAPTURE_S32_LE 0x00000400 /* 1 = S32_LE, 0 = S16_LE (S32_LE current not working) */ +#define HCFG_8_CHANNEL_PLAY 0x00000200 /* 1 = 8 channels, 0 = 2 channels per substream.*/ +#define HCFG_8_CHANNEL_CAPTURE 0x00000100 /* 1 = 8 channels, 0 = 2 channels per substream.*/ +#define HCFG_MONO 0x00000080 /* 1 = I2S Input mono */ +#define HCFG_I2S_OUTPUT 0x00000010 /* 1 = I2S Output disabled */ +#define HCFG_AC97 0x00000008 /* 0 = AC97 1.0, 1 = AC97 2.0 */ +#define HCFG_LOCK_PLAYBACK_CACHE 0x00000004 /* 1 = Cancel bustmaster accesses to soundcache */ + /* NOTE: This should generally never be used. */ +#define HCFG_LOCK_CAPTURE_CACHE 0x00000002 /* 1 = Cancel bustmaster accesses to soundcache */ + /* NOTE: This should generally never be used. */ +#define HCFG_AUDIOENABLE 0x00000001 /* 0 = CODECs transmit zero-valued samples */ + /* Should be set to 1 when the EMU10K1 is */ + /* completely initialized. */ +#define CA0106_GPIO 0x18 /* Defaults: 005f03a3-Analog, 005f02a2-SPDIF. */ + /* Here pins 0,1,2,3,4,,6 are output. 5,7 are input */ + /* For the Audigy LS, pin 0 (or bit 8) controls the SPDIF/Analog jack. */ + /* SB Live 24bit: + * bit 8 0 = SPDIF in and out / 1 = Analog (Mic or Line)-in. + * bit 9 0 = Mute / 1 = Analog out. + * bit 10 0 = Line-in / 1 = Mic-in. + * bit 11 0 = ? / 1 = ? + * bit 12 0 = 48 Khz / 1 = 96 Khz Analog out on SB Live 24bit. + * bit 13 0 = ? / 1 = ? + * bit 14 0 = Mute / 1 = Analog out + * bit 15 0 = ? / 1 = ? + * Both bit 9 and bit 14 have to be set for analog sound to work on the SB Live 24bit. + */ + /* 8 general purpose programmable In/Out pins. + * GPI [8:0] Read only. Default 0. + * GPO [15:8] Default 0x9. (Default to SPDIF jack enabled for SPDIF) + * GPO Enable [23:16] Default 0x0f. Setting a bit to 1, causes the pin to be an output pin. + */ +#define CA0106_AC97DATA 0x1c /* AC97 register set data register (16 bit) */ + +#define CA0106_AC97ADDRESS 0x1e /* AC97 register set address register (8 bit) */ + +/********************************************************************************************************/ +/* CA0106 pointer-offset register set, accessed through the PTR and DATA registers */ +/********************************************************************************************************/ + +/* Initially all registers from 0x00 to 0x3f have zero contents. */ +#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */ + /* One list entry: 4 bytes for DMA address, + * 4 bytes for period_size << 16. + * One list entry is 8 bytes long. + * One list entry for each period in the buffer. + */ + /* ADDR[31:0], Default: 0x0 */ +#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */ + /* SIZE[21:16], Default: 0x8 */ +#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */ + /* PTR[5:0], Default: 0x0 */ +#define PLAYBACK_UNKNOWN3 0x03 /* Not used ?? */ +#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA address */ + /* DMA[31:0], Default: 0x0 */ +#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size. win2000 uses 0x04000000 */ + /* SIZE[31:16], Default: 0x0 */ +#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */ + /* POINTER[15:0], Default: 0x0 */ +#define PLAYBACK_PERIOD_END_ADDR 0x07 /* Playback fifo end address */ + /* END_ADDR[15:0], FLAG[16] 0 = don't stop, 1 = stop */ +#define PLAYBACK_FIFO_OFFSET_ADDRESS 0x08 /* Current fifo offset address [21:16] */ + /* Cache size valid [5:0] */ +#define PLAYBACK_UNKNOWN9 0x09 /* 0x9 to 0xf Unused */ +#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */ + /* DMA[31:0], Default: 0x0 */ +#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */ + /* SIZE[31:16], Default: 0x0 */ +#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */ + /* POINTER[15:0], Default: 0x0 */ +#define CAPTURE_FIFO_OFFSET_ADDRESS 0x13 /* Current fifo offset address [21:16] */ + /* Cache size valid [5:0] */ +#define PLAYBACK_LAST_SAMPLE 0x20 /* The sample currently being played */ +/* 0x21 - 0x3f unused */ +#define BASIC_INTERRUPT 0x40 /* Used by both playback and capture interrupt handler */ + /* Playback (0x1< Center Speaker, 2 -> Sub Woofer, 3 -> Ground, 4 -> Ground + * For Digital: 1 -> Front SPDIF, 2 -> Rear SPDIF, 3 -> Center/Subwoofer SPDIF, 4 -> Ground. + * Standard 4 pole Video A/V cable with RCA outputs: 1 -> White, 2 -> Yellow, 3 -> Shield on all three, 4 -> Red. + * So, from this you can see that you cannot use a Standard 4 pole Video A/V cable with the SB Audigy LS card. + */ +/* The Front SPDIF PCM gets mixed with samples from the AC97 codec, so can only work for Stereo PCM and not AC3/DTS + * The Rear SPDIF can be used for Stereo PCM and also AC3/DTS + * The Center/LFE SPDIF cannot be used for AC3/DTS, but can be used for Stereo PCM. + * Summary: For ALSA we use the Rear channel for SPDIF Digital AC3/DTS output + */ +/* A standard 2 pole mono mini-jack to RCA plug can be used for SPDIF Stereo PCM output from the Front channel. + * A standard 3 pole stereo mini-jack to 2 RCA plugs can be used for SPDIF AC3/DTS and Stereo PCM output utilising the Rear channel and just one of the RCA plugs. + */ +#define SPCS0 0x41 /* SPDIF output Channel Status 0 register. For Rear. default=0x02108004, non-audio=0x02108006 */ +#define SPCS1 0x42 /* SPDIF output Channel Status 1 register. For Front */ +#define SPCS2 0x43 /* SPDIF output Channel Status 2 register. For Center/LFE */ +#define SPCS3 0x44 /* SPDIF output Channel Status 3 register. Unknown */ + /* When Channel set to 0: */ +#define SPCS_CLKACCYMASK 0x30000000 /* Clock accuracy */ +#define SPCS_CLKACCY_1000PPM 0x00000000 /* 1000 parts per million */ +#define SPCS_CLKACCY_50PPM 0x10000000 /* 50 parts per million */ +#define SPCS_CLKACCY_VARIABLE 0x20000000 /* Variable accuracy */ +#define SPCS_SAMPLERATEMASK 0x0f000000 /* Sample rate */ +#define SPCS_SAMPLERATE_44 0x00000000 /* 44.1kHz sample rate */ +#define SPCS_SAMPLERATE_48 0x02000000 /* 48kHz sample rate */ +#define SPCS_SAMPLERATE_32 0x03000000 /* 32kHz sample rate */ +#define SPCS_CHANNELNUMMASK 0x00f00000 /* Channel number */ +#define SPCS_CHANNELNUM_UNSPEC 0x00000000 /* Unspecified channel number */ +#define SPCS_CHANNELNUM_LEFT 0x00100000 /* Left channel */ +#define SPCS_CHANNELNUM_RIGHT 0x00200000 /* Right channel */ +#define SPCS_SOURCENUMMASK 0x000f0000 /* Source number */ +#define SPCS_SOURCENUM_UNSPEC 0x00000000 /* Unspecified source number */ +#define SPCS_GENERATIONSTATUS 0x00008000 /* Originality flag (see IEC-958 spec) */ +#define SPCS_CATEGORYCODEMASK 0x00007f00 /* Category code (see IEC-958 spec) */ +#define SPCS_MODEMASK 0x000000c0 /* Mode (see IEC-958 spec) */ +#define SPCS_EMPHASISMASK 0x00000038 /* Emphasis */ +#define SPCS_EMPHASIS_NONE 0x00000000 /* No emphasis */ +#define SPCS_EMPHASIS_50_15 0x00000008 /* 50/15 usec 2 channel */ +#define SPCS_COPYRIGHT 0x00000004 /* Copyright asserted flag -- do not modify */ +#define SPCS_NOTAUDIODATA 0x00000002 /* 0 = Digital audio, 1 = not audio */ +#define SPCS_PROFESSIONAL 0x00000001 /* 0 = Consumer (IEC-958), 1 = pro (AES3-1992) */ + + /* When Channel set to 1: */ +#define SPCS_WORD_LENGTH_MASK 0x0000000f /* Word Length Mask */ +#define SPCS_WORD_LENGTH_16 0x00000008 /* Word Length 16 bit */ +#define SPCS_WORD_LENGTH_17 0x00000006 /* Word Length 17 bit */ +#define SPCS_WORD_LENGTH_18 0x00000004 /* Word Length 18 bit */ +#define SPCS_WORD_LENGTH_19 0x00000002 /* Word Length 19 bit */ +#define SPCS_WORD_LENGTH_20A 0x0000000a /* Word Length 20 bit */ +#define SPCS_WORD_LENGTH_20 0x00000009 /* Word Length 20 bit (both 0xa and 0x9 are 20 bit) */ +#define SPCS_WORD_LENGTH_21 0x00000007 /* Word Length 21 bit */ +#define SPCS_WORD_LENGTH_22 0x00000005 /* Word Length 22 bit */ +#define SPCS_WORD_LENGTH_23 0x00000003 /* Word Length 23 bit */ +#define SPCS_WORD_LENGTH_24 0x0000000b /* Word Length 24 bit */ +#define SPCS_ORIGINAL_SAMPLE_RATE_MASK 0x000000f0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_NONE 0x00000000 /* Original Sample rate not indicated */ +#define SPCS_ORIGINAL_SAMPLE_RATE_16000 0x00000010 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_RES1 0x00000020 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_32000 0x00000030 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_12000 0x00000040 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_11025 0x00000050 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_8000 0x00000060 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_RES2 0x00000070 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_192000 0x00000080 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_24000 0x00000090 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_96000 0x000000a0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_48000 0x000000b0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_176400 0x000000c0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_22050 0x000000d0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_88200 0x000000e0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_44100 0x000000f0 /* Original Sample rate */ + +#define SPDIF_SELECT1 0x45 /* Enables SPDIF or Analogue outputs 0-SPDIF, 0xf00-Analogue */ + /* 0x100 - Front, 0x800 - Rear, 0x200 - Center/LFE. + * But as the jack is shared, use 0xf00. + * The Windows2000 driver uses 0x0000000f for both digital and analog. + * 0xf00 introduces interesting noises onto the Center/LFE. + * If you turn the volume up, you hear computer noise, + * e.g. mouse moving, changing between app windows etc. + * So, I am going to set this to 0x0000000f all the time now, + * same as the windows driver does. + * Use register SPDIF_SELECT2(0x72) to switch between SPDIF and Analog. + */ + /* When Channel = 0: + * Wide SPDIF format [3:0] (one bit for each channel) (0=20bit, 1=24bit) + * Tristate SPDIF Output [11:8] (one bit for each channel) (0=Not tristate, 1=Tristate) + * SPDIF Bypass enable [19:16] (one bit for each channel) (0=Not bypass, 1=Bypass) + */ + /* When Channel = 1: + * SPDIF 0 User data [7:0] + * SPDIF 1 User data [15:8] + * SPDIF 0 User data [23:16] + * SPDIF 0 User data [31:24] + * User data can be sent by using the SPDIF output frame pending and SPDIF output user bit interrupts. + */ +#define WATERMARK 0x46 /* Test bit to indicate cache usage level */ +#define SPDIF_INPUT_STATUS 0x49 /* SPDIF Input status register. Bits the same as SPCS. + * When Channel = 0: Bits the same as SPCS channel 0. + * When Channel = 1: Bits the same as SPCS channel 1. + * When Channel = 2: + * SPDIF Input User data [16:0] + * SPDIF Input Frame count [21:16] + */ +#define CAPTURE_CACHE_DATA 0x50 /* 0x50-0x5f Recorded samples. */ +#define CAPTURE_SOURCE 0x60 /* Capture Source 0 = MIC */ +#define CAPTURE_SOURCE_CHANNEL0 0xf0000000 /* Mask for selecting the Capture sources */ +#define CAPTURE_SOURCE_CHANNEL1 0x0f000000 /* 0 - SPDIF mixer output. */ +#define CAPTURE_SOURCE_CHANNEL2 0x00f00000 /* 1 - What you hear or . 2 - ?? */ +#define CAPTURE_SOURCE_CHANNEL3 0x000f0000 /* 3 - Mic in, Line in, TAD in, Aux in. */ +#define CAPTURE_SOURCE_RECORD_MAP 0x0000ffff /* Default 0x00e4 */ + /* Record Map [7:0] (2 bits per channel) 0=mapped to channel 0, 1=mapped to channel 1, 2=mapped to channel2, 3=mapped to channel3 + * Record source select for channel 0 [18:16] + * Record source select for channel 1 [22:20] + * Record source select for channel 2 [26:24] + * Record source select for channel 3 [30:28] + * 0 - SPDIF mixer output. + * 1 - i2s mixer output. + * 2 - SPDIF input. + * 3 - i2s input. + * 4 - AC97 capture. + * 5 - SRC output. + */ +#define CAPTURE_VOLUME1 0x61 /* Capture volume per channel 0-3 */ +#define CAPTURE_VOLUME2 0x62 /* Capture volume per channel 4-7 */ + +#define PLAYBACK_ROUTING1 0x63 /* Playback routing of channels 0-7. Effects AC3 output. Default 0x32765410 */ +#define ROUTING1_REAR 0x77000000 /* Channel_id 0 sends to 10, Channel_id 1 sends to 32 */ +#define ROUTING1_NULL 0x00770000 /* Channel_id 2 sends to 54, Channel_id 3 sends to 76 */ +#define ROUTING1_CENTER_LFE 0x00007700 /* 0x32765410 means, send Channel_id 0 to FRONT, Channel_id 1 to REAR */ +#define ROUTING1_FRONT 0x00000077 /* Channel_id 2 to CENTER_LFE, Channel_id 3 to NULL. */ + /* Channel_id's handle stereo channels. Channel X is a single mono channel */ + /* Host is input from the PCI bus. */ + /* Host channel 0 [2:0] -> SPDIF Mixer/Router channel 0-7. + * Host channel 1 [6:4] -> SPDIF Mixer/Router channel 0-7. + * Host channel 2 [10:8] -> SPDIF Mixer/Router channel 0-7. + * Host channel 3 [14:12] -> SPDIF Mixer/Router channel 0-7. + * Host channel 4 [18:16] -> SPDIF Mixer/Router channel 0-7. + * Host channel 5 [22:20] -> SPDIF Mixer/Router channel 0-7. + * Host channel 6 [26:24] -> SPDIF Mixer/Router channel 0-7. + * Host channel 7 [30:28] -> SPDIF Mixer/Router channel 0-7. + */ + +#define PLAYBACK_ROUTING2 0x64 /* Playback Routing . Feeding Capture channels back into Playback. Effects AC3 output. Default 0x76767676 */ + /* SRC is input from the capture inputs. */ + /* SRC channel 0 [2:0] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 1 [6:4] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 2 [10:8] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 3 [14:12] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 4 [18:16] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 5 [22:20] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 6 [26:24] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 7 [30:28] -> SPDIF Mixer/Router channel 0-7. + */ + +#define PLAYBACK_MUTE 0x65 /* Unknown. While playing 0x0, while silent 0x00fc0000 */ + /* SPDIF Mixer input control: + * Invert SRC to SPDIF Mixer [7-0] (One bit per channel) + * Invert Host to SPDIF Mixer [15:8] (One bit per channel) + * SRC to SPDIF Mixer disable [23:16] (One bit per channel) + * Host to SPDIF Mixer disable [31:24] (One bit per channel) + */ +#define PLAYBACK_VOLUME1 0x66 /* Playback SPDIF volume per channel. Set to the same PLAYBACK_VOLUME(0x6a) */ + /* PLAYBACK_VOLUME1 must be set to 30303030 for SPDIF AC3 Playback */ + /* SPDIF mixer input volume. 0=12dB, 0x30=0dB, 0xFE=-51.5dB, 0xff=Mute */ + /* One register for each of the 4 stereo streams. */ + /* SRC Right volume [7:0] + * SRC Left volume [15:8] + * Host Right volume [23:16] + * Host Left volume [31:24] + */ +#define CAPTURE_ROUTING1 0x67 /* Capture Routing. Default 0x32765410 */ + /* Similar to register 0x63, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */ +#define CAPTURE_ROUTING2 0x68 /* Unknown Routing. Default 0x76767676 */ + /* Similar to register 0x64, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */ +#define CAPTURE_MUTE 0x69 /* Unknown. While capturing 0x0, while silent 0x00fc0000 */ + /* Similar to register 0x65, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */ +#define PLAYBACK_VOLUME2 0x6a /* Playback Analog volume per channel. Does not effect AC3 output */ + /* Similar to register 0x66, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */ +#define UNKNOWN6b 0x6b /* Unknown. Readonly. Default 00400000 00400000 00400000 00400000 */ +#define MIDI_UART_A_DATA 0x6c /* Midi Uart A Data */ +#define MIDI_UART_A_CMD 0x6d /* Midi Uart A Command/Status */ +#define MIDI_UART_B_DATA 0x6e /* Midi Uart B Data (currently unused) */ +#define MIDI_UART_B_CMD 0x6f /* Midi Uart B Command/Status (currently unused) */ + +/* unique channel identifier for midi->channel */ + +#define CA0106_MIDI_CHAN_A 0x1 +#define CA0106_MIDI_CHAN_B 0x2 + +/* from mpu401 */ + +#define CA0106_MIDI_INPUT_AVAIL 0x80 +#define CA0106_MIDI_OUTPUT_READY 0x40 +#define CA0106_MPU401_RESET 0xff +#define CA0106_MPU401_ENTER_UART 0x3f +#define CA0106_MPU401_ACK 0xfe + +#define SAMPLE_RATE_TRACKER_STATUS 0x70 /* Readonly. Default 00108000 00108000 00500000 00500000 */ + /* Estimated sample rate [19:0] Relative to 48kHz. 0x8000 = 1.0 + * Rate Locked [20] + * SPDIF Locked [21] For SPDIF channel only. + * Valid Audio [22] For SPDIF channel only. + */ +#define CAPTURE_CONTROL 0x71 /* Some sort of routing. default = 40c81000 30303030 30300000 00700000 */ + /* Channel_id 0: 0x40c81000 must be changed to 0x40c80000 for SPDIF AC3 input or output. */ + /* Channel_id 1: 0xffffffff(mute) 0x30303030(max) controls CAPTURE feedback into PLAYBACK. */ + /* Sample rate output control register Channel=0 + * Sample output rate [1:0] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz) + * Sample input rate [3:2] (0=48kHz, 1=Not available, 2=96kHz, 3=192Khz) + * SRC input source select [4] 0=Audio from digital mixer, 1=Audio from analog source. + * Record rate [9:8] (0=48kHz, 1=Not available, 2=96kHz, 3=192Khz) + * Record mixer output enable [12:10] + * I2S input rate master mode [15:14] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz) + * I2S output rate [17:16] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz) + * I2S output source select [18] (0=Audio from host, 1=Audio from SRC) + * Record mixer I2S enable [20:19] (enable/disable i2sin1 and i2sin0) + * I2S output master clock select [21] (0=256*I2S output rate, 1=512*I2S output rate.) + * I2S input master clock select [22] (0=256*I2S input rate, 1=512*I2S input rate.) + * I2S input mode [23] (0=Slave, 1=Master) + * SPDIF output rate [25:24] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz) + * SPDIF output source select [26] (0=host, 1=SRC) + * Not used [27] + * Record Source 0 input [29:28] (0=SPDIF in, 1=I2S in, 2=AC97 Mic, 3=AC97 PCM) + * Record Source 1 input [31:30] (0=SPDIF in, 1=I2S in, 2=AC97 Mic, 3=AC97 PCM) + */ + /* Sample rate output control register Channel=1 + * I2S Input 0 volume Right [7:0] + * I2S Input 0 volume Left [15:8] + * I2S Input 1 volume Right [23:16] + * I2S Input 1 volume Left [31:24] + */ + /* Sample rate output control register Channel=2 + * SPDIF Input volume Right [23:16] + * SPDIF Input volume Left [31:24] + */ + /* Sample rate output control register Channel=3 + * No used + */ +#define SPDIF_SELECT2 0x72 /* Some sort of routing. Channel_id 0 only. default = 0x0f0f003f. Analog 0x000b0000, Digital 0x0b000000 */ +#define ROUTING2_FRONT_MASK 0x00010000 /* Enable for Front speakers. */ +#define ROUTING2_CENTER_LFE_MASK 0x00020000 /* Enable for Center/LFE speakers. */ +#define ROUTING2_REAR_MASK 0x00080000 /* Enable for Rear speakers. */ + /* Audio output control + * AC97 output enable [5:0] + * I2S output enable [19:16] + * SPDIF output enable [27:24] + */ +#define UNKNOWN73 0x73 /* Unknown. Readonly. Default 0x0 */ +#define CHIP_VERSION 0x74 /* P17 Chip version. Channel_id 0 only. Default 00000071 */ +#define EXTENDED_INT_MASK 0x75 /* Used by both playback and capture interrupt handler */ + /* Sets which Interrupts are enabled. */ + /* 0x00000001 = Half period. Playback. + * 0x00000010 = Full period. Playback. + * 0x00000100 = Half buffer. Playback. + * 0x00001000 = Full buffer. Playback. + * 0x00010000 = Half buffer. Capture. + * 0x00100000 = Full buffer. Capture. + * Capture can only do 2 periods. + * 0x01000000 = End audio. Playback. + * 0x40000000 = Half buffer Playback,Caputre xrun. + * 0x80000000 = Full buffer Playback,Caputre xrun. + */ +#define EXTENDED_INT 0x76 /* Used by both playback and capture interrupt handler */ + /* Shows which interrupts are active at the moment. */ + /* Same bit layout as EXTENDED_INT_MASK */ +#define COUNTER77 0x77 /* Counter range 0 to 0x3fffff, 192000 counts per second. */ +#define COUNTER78 0x78 /* Counter range 0 to 0x3fffff, 44100 counts per second. */ +#define EXTENDED_INT_TIMER 0x79 /* Channel_id 0 only. Used by both playback and capture interrupt handler */ + /* Causes interrupts based on timer intervals. */ +#define SPI 0x7a /* SPI: Serial Interface Register */ +#define I2C_A 0x7b /* I2C Address. 32 bit */ +#define I2C_D0 0x7c /* I2C Data Port 0. 32 bit */ +#define I2C_D1 0x7d /* I2C Data Port 1. 32 bit */ +//I2C values +#define I2C_A_ADC_ADD_MASK 0x000000fe //The address is a 7 bit address +#define I2C_A_ADC_RW_MASK 0x00000001 //bit mask for R/W +#define I2C_A_ADC_TRANS_MASK 0x00000010 //Bit mask for I2c address DAC value +#define I2C_A_ADC_ABORT_MASK 0x00000020 //Bit mask for I2C transaction abort flag +#define I2C_A_ADC_LAST_MASK 0x00000040 //Bit mask for Last word transaction +#define I2C_A_ADC_BYTE_MASK 0x00000080 //Bit mask for Byte Mode + +#define I2C_A_ADC_ADD 0x00000034 //This is the Device address for ADC +#define I2C_A_ADC_READ 0x00000001 //To perform a read operation +#define I2C_A_ADC_START 0x00000100 //Start I2C transaction +#define I2C_A_ADC_ABORT 0x00000200 //I2C transaction abort +#define I2C_A_ADC_LAST 0x00000400 //I2C last transaction +#define I2C_A_ADC_BYTE 0x00000800 //I2C one byte mode + +#define I2C_D_ADC_REG_MASK 0xfe000000 //ADC address register +#define I2C_D_ADC_DAT_MASK 0x01ff0000 //ADC data register + +#define ADC_TIMEOUT 0x00000007 //ADC Timeout Clock Disable +#define ADC_IFC_CTRL 0x0000000b //ADC Interface Control +#define ADC_MASTER 0x0000000c //ADC Master Mode Control +#define ADC_POWER 0x0000000d //ADC PowerDown Control +#define ADC_ATTEN_ADCL 0x0000000e //ADC Attenuation ADCL +#define ADC_ATTEN_ADCR 0x0000000f //ADC Attenuation ADCR +#define ADC_ALC_CTRL1 0x00000010 //ADC ALC Control 1 +#define ADC_ALC_CTRL2 0x00000011 //ADC ALC Control 2 +#define ADC_ALC_CTRL3 0x00000012 //ADC ALC Control 3 +#define ADC_NOISE_CTRL 0x00000013 //ADC Noise Gate Control +#define ADC_LIMIT_CTRL 0x00000014 //ADC Limiter Control +#define ADC_MUX 0x00000015 //ADC Mux offset + +#if 0 +/* FIXME: Not tested yet. */ +#define ADC_GAIN_MASK 0x000000ff //Mask for ADC Gain +#define ADC_ZERODB 0x000000cf //Value to set ADC to 0dB +#define ADC_MUTE_MASK 0x000000c0 //Mask for ADC mute +#define ADC_MUTE 0x000000c0 //Value to mute ADC +#define ADC_OSR 0x00000008 //Mask for ADC oversample rate select +#define ADC_TIMEOUT_DISABLE 0x00000008 //Value and mask to disable Timeout clock +#define ADC_HPF_DISABLE 0x00000100 //Value and mask to disable High pass filter +#define ADC_TRANWIN_MASK 0x00000070 //Mask for Length of Transient Window +#endif + +#define ADC_MUX_MASK 0x0000000f //Mask for ADC Mux +#define ADC_MUX_PHONE 0x00000001 //Value to select TAD at ADC Mux (Not used) +#define ADC_MUX_MIC 0x00000002 //Value to select Mic at ADC Mux +#define ADC_MUX_LINEIN 0x00000004 //Value to select LineIn at ADC Mux +#define ADC_MUX_AUX 0x00000008 //Value to select Aux at ADC Mux + +#define SET_CHANNEL 0 /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */ +#define PCM_FRONT_CHANNEL 0 +#define PCM_REAR_CHANNEL 1 +#define PCM_CENTER_LFE_CHANNEL 2 +#define PCM_UNKNOWN_CHANNEL 3 +#define CONTROL_FRONT_CHANNEL 0 +#define CONTROL_REAR_CHANNEL 3 +#define CONTROL_CENTER_LFE_CHANNEL 1 +#define CONTROL_UNKNOWN_CHANNEL 2 + + +/* Based on WM8768 Datasheet Rev 4.2 page 32 */ +#define SPI_REG_MASK 0x1ff /* 16-bit SPI writes have a 7-bit address */ +#define SPI_REG_SHIFT 9 /* followed by 9 bits of data */ + +#define SPI_LDA1_REG 0 /* digital attenuation */ +#define SPI_RDA1_REG 1 +#define SPI_LDA2_REG 4 +#define SPI_RDA2_REG 5 +#define SPI_LDA3_REG 6 +#define SPI_RDA3_REG 7 +#define SPI_LDA4_REG 13 +#define SPI_RDA4_REG 14 +#define SPI_MASTDA_REG 8 + +#define SPI_DA_BIT_UPDATE (1<<8) /* update attenuation values */ +#define SPI_DA_BIT_0dB 0xff /* 0 dB */ +#define SPI_DA_BIT_infdB 0x00 /* inf dB attenuation (mute) */ + +#define SPI_PL_REG 2 +#define SPI_PL_BIT_L_M (0<<5) /* left channel = mute */ +#define SPI_PL_BIT_L_L (1<<5) /* left channel = left */ +#define SPI_PL_BIT_L_R (2<<5) /* left channel = right */ +#define SPI_PL_BIT_L_C (3<<5) /* left channel = (L+R)/2 */ +#define SPI_PL_BIT_R_M (0<<7) /* right channel = mute */ +#define SPI_PL_BIT_R_L (1<<7) /* right channel = left */ +#define SPI_PL_BIT_R_R (2<<7) /* right channel = right */ +#define SPI_PL_BIT_R_C (3<<7) /* right channel = (L+R)/2 */ +#define SPI_IZD_REG 2 +#define SPI_IZD_BIT (0<<4) /* infinite zero detect */ + +#define SPI_FMT_REG 3 +#define SPI_FMT_BIT_RJ (0<<0) /* right justified mode */ +#define SPI_FMT_BIT_LJ (1<<0) /* left justified mode */ +#define SPI_FMT_BIT_I2S (2<<0) /* I2S mode */ +#define SPI_FMT_BIT_DSP (3<<0) /* DSP Modes A or B */ +#define SPI_LRP_REG 3 +#define SPI_LRP_BIT (1<<2) /* invert LRCLK polarity */ +#define SPI_BCP_REG 3 +#define SPI_BCP_BIT (1<<3) /* invert BCLK polarity */ +#define SPI_IWL_REG 3 +#define SPI_IWL_BIT_16 (0<<4) /* 16-bit world length */ +#define SPI_IWL_BIT_20 (1<<4) /* 20-bit world length */ +#define SPI_IWL_BIT_24 (2<<4) /* 24-bit world length */ +#define SPI_IWL_BIT_32 (3<<4) /* 32-bit world length */ + +#define SPI_MS_REG 10 +#define SPI_MS_BIT (1<<5) /* master mode */ +#define SPI_RATE_REG 10 /* only applies in master mode */ +#define SPI_RATE_BIT_128 (0<<6) /* MCLK = LRCLK * 128 */ +#define SPI_RATE_BIT_192 (1<<6) +#define SPI_RATE_BIT_256 (2<<6) +#define SPI_RATE_BIT_384 (3<<6) +#define SPI_RATE_BIT_512 (4<<6) +#define SPI_RATE_BIT_768 (5<<6) + +/* They really do label the bit for the 4th channel "4" and not "3" */ +#define SPI_DMUTE0_REG 9 +#define SPI_DMUTE1_REG 9 +#define SPI_DMUTE2_REG 9 +#define SPI_DMUTE4_REG 15 +#define SPI_DMUTE0_BIT (1<<3) +#define SPI_DMUTE1_BIT (1<<4) +#define SPI_DMUTE2_BIT (1<<5) +#define SPI_DMUTE4_BIT (1<<2) + +#define SPI_PHASE0_REG 3 +#define SPI_PHASE1_REG 3 +#define SPI_PHASE2_REG 3 +#define SPI_PHASE4_REG 15 +#define SPI_PHASE0_BIT (1<<6) +#define SPI_PHASE1_BIT (1<<7) +#define SPI_PHASE2_BIT (1<<8) +#define SPI_PHASE4_BIT (1<<3) + +#define SPI_PDWN_REG 2 /* power down all DACs */ +#define SPI_PDWN_BIT (1<<2) +#define SPI_DACD0_REG 10 /* power down individual DACs */ +#define SPI_DACD1_REG 10 +#define SPI_DACD2_REG 10 +#define SPI_DACD4_REG 15 +#define SPI_DACD0_BIT (1<<1) +#define SPI_DACD1_BIT (1<<2) +#define SPI_DACD2_BIT (1<<3) +#define SPI_DACD4_BIT (1<<0) /* datasheet error says it's 1 */ + +#define SPI_PWRDNALL_REG 10 /* power down everything */ +#define SPI_PWRDNALL_BIT (1<<4) + +#include "ca_midi.h" + +struct snd_ca0106; + +struct snd_ca0106_channel { + struct snd_ca0106 *emu; + int number; + int use; + void (*interrupt)(struct snd_ca0106 *emu, struct snd_ca0106_channel *channel); + struct snd_ca0106_pcm *epcm; +}; + +struct snd_ca0106_pcm { + struct snd_ca0106 *emu; + struct snd_pcm_substream *substream; + int channel_id; + unsigned short running; +}; + +struct snd_ca0106_details { + u32 serial; + char * name; + int ac97; /* ac97 = 0 -> Select MIC, Line in, TAD in, AUX in. + ac97 = 1 -> Default to AC97 in. */ + int gpio_type; /* gpio_type = 1 -> shared mic-in/line-in + gpio_type = 2 -> shared side-out/line-in. */ + int i2c_adc; /* with i2c_adc=1, the driver adds some capture volume + controls, phone, mic, line-in and aux. */ + u16 spi_dac; /* spi_dac = 0 -> no spi interface for DACs + spi_dac = 0x + -> specifies DAC id for each channel pair. */ +}; + +// definition of the chip-specific record +struct snd_ca0106 { + struct snd_card *card; + const struct snd_ca0106_details *details; + struct pci_dev *pci; + + unsigned long port; + int irq; + + unsigned int serial; /* serial number */ + unsigned short model; /* subsystem id */ + + spinlock_t emu_lock; + + struct snd_ac97 *ac97; + struct snd_pcm *pcm[4]; + + struct snd_ca0106_channel playback_channels[4]; + struct snd_ca0106_channel capture_channels[4]; + u32 spdif_bits[4]; /* s/pdif out default setup */ + u32 spdif_str_bits[4]; /* s/pdif out per-stream setup */ + int spdif_enable; + int capture_source; + int i2c_capture_source; + u8 i2c_capture_volume[4][2]; + int capture_mic_line_in; + + struct snd_dma_buffer *buffer; + + struct snd_ca_midi midi; + struct snd_ca_midi midi2; + + u16 spi_dac_reg[16]; + +#ifdef CONFIG_PM_SLEEP +#define NUM_SAVED_VOLUMES 9 + unsigned int saved_vol[NUM_SAVED_VOLUMES]; +#endif +}; + +int snd_ca0106_mixer(struct snd_ca0106 *emu); +int snd_ca0106_proc_init(struct snd_ca0106 * emu); + +unsigned int snd_ca0106_ptr_read(struct snd_ca0106 * emu, + unsigned int reg, + unsigned int chn); + +void snd_ca0106_ptr_write(struct snd_ca0106 *emu, + unsigned int reg, + unsigned int chn, + unsigned int data); + +int snd_ca0106_i2c_write(struct snd_ca0106 *emu, u32 reg, u32 value); + +int snd_ca0106_spi_write(struct snd_ca0106 * emu, + unsigned int data); + +#ifdef CONFIG_PM_SLEEP +void snd_ca0106_mixer_suspend(struct snd_ca0106 *chip); +void snd_ca0106_mixer_resume(struct snd_ca0106 *chip); +#else +#define snd_ca0106_mixer_suspend(chip) do { } while (0) +#define snd_ca0106_mixer_resume(chip) do { } while (0) +#endif diff --git a/sound/pci/ca0106/ca0106_main.c b/sound/pci/ca0106/ca0106_main.c new file mode 100644 index 000000000..cf923f551 --- /dev/null +++ b/sound/pci/ca0106/ca0106_main.c @@ -0,0 +1,1838 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2004 James Courtier-Dutton + * Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit + * Version: 0.0.25 + * + * FEATURES currently supported: + * Front, Rear and Center/LFE. + * Surround40 and Surround51. + * Capture from MIC an LINE IN input. + * SPDIF digital playback of PCM stereo and AC3/DTS works. + * (One can use a standard mono mini-jack to one RCA plugs cable. + * or one can use a standard stereo mini-jack to two RCA plugs cable. + * Plug one of the RCA plugs into the Coax input of the external decoder/receiver.) + * ( In theory one could output 3 different AC3 streams at once, to 3 different SPDIF outputs. ) + * Notes on how to capture sound: + * The AC97 is used in the PLAYBACK direction. + * The output from the AC97 chip, instead of reaching the speakers, is fed into the Philips 1361T ADC. + * So, to record from the MIC, set the MIC Playback volume to max, + * unmute the MIC and turn up the MASTER Playback volume. + * So, to prevent feedback when capturing, minimise the "Capture feedback into Playback" volume. + * + * The only playback controls that currently do anything are: - + * Analog Front + * Analog Rear + * Analog Center/LFE + * SPDIF Front + * SPDIF Rear + * SPDIF Center/LFE + * + * For capture from Mic in or Line in. + * Digital/Analog ( switch must be in Analog mode for CAPTURE. ) + * + * CAPTURE feedback into PLAYBACK + * + * Changelog: + * Support interrupts per period. + * Removed noise from Center/LFE channel when in Analog mode. + * Rename and remove mixer controls. + * 0.0.6 + * Use separate card based DMA buffer for periods table list. + * 0.0.7 + * Change remove and rename ctrls into lists. + * 0.0.8 + * Try to fix capture sources. + * 0.0.9 + * Fix AC3 output. + * Enable S32_LE format support. + * 0.0.10 + * Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".) + * 0.0.11 + * Add Model name recognition. + * 0.0.12 + * Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period. + * Remove redundent "voice" handling. + * 0.0.13 + * Single trigger call for multi channels. + * 0.0.14 + * Set limits based on what the sound card hardware can do. + * playback periods_min=2, periods_max=8 + * capture hw constraints require period_size = n * 64 bytes. + * playback hw constraints require period_size = n * 64 bytes. + * 0.0.15 + * Minor updates. + * 0.0.16 + * Implement 192000 sample rate. + * 0.0.17 + * Add support for SB0410 and SB0413. + * 0.0.18 + * Modified Copyright message. + * 0.0.19 + * Finally fix support for SB Live 24 bit. SB0410 and SB0413. + * The output codec needs resetting, otherwise all output is muted. + * 0.0.20 + * Merge "pci_disable_device(pci);" fixes. + * 0.0.21 + * Add 4 capture channels. (SPDIF only comes in on channel 0. ) + * Add SPDIF capture using optional digital I/O module for SB Live 24bit. (Analog capture does not yet work.) + * 0.0.22 + * Add support for MSI K8N Diamond Motherboard with onboard SB Live 24bit without AC97. From kiksen, bug #901 + * 0.0.23 + * Implement support for Line-in capture on SB Live 24bit. + * 0.0.24 + * Add support for mute control on SB Live 24bit (cards w/ SPI DAC) + * 0.0.25 + * Powerdown SPI DAC channels when not in use + * + * BUGS: + * Some stability problems when unloading the snd-ca0106 kernel module. + * -- + * + * TODO: + * 4 Capture channels, only one implemented so far. + * Other capture rates apart from 48khz not implemented. + * MIDI + * -- + * GENERAL INFO: + * Model: SB0310 + * P17 Chip: CA0106-DAT + * AC97 Codec: STAC 9721 + * ADC: Philips 1361T (Stereo 24bit) + * DAC: WM8746EDS (6-channel, 24bit, 192Khz) + * + * GENERAL INFO: + * Model: SB0410 + * P17 Chip: CA0106-DAT + * AC97 Codec: None + * ADC: WM8775EDS (4 Channel) + * DAC: CS4382 (114 dB, 24-Bit, 192 kHz, 8-Channel D/A Converter with DSD Support) + * SPDIF Out control switches between Mic in and SPDIF out. + * No sound out or mic input working yet. + * + * GENERAL INFO: + * Model: SB0413 + * P17 Chip: CA0106-DAT + * AC97 Codec: None. + * ADC: Unknown + * DAC: Unknown + * Trying to handle it like the SB0410. + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("James Courtier-Dutton "); +MODULE_DESCRIPTION("CA0106"); +MODULE_LICENSE("GPL"); + +// module parameters (see "Module Parameters") +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static uint subsystem[SNDRV_CARDS]; /* Force card subsystem model */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the CA0106 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the CA0106 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable the CA0106 soundcard."); +module_param_array(subsystem, uint, NULL, 0444); +MODULE_PARM_DESC(subsystem, "Force card subsystem model."); + +#include "ca0106.h" + +static const struct snd_ca0106_details ca0106_chip_details[] = { + /* Sound Blaster X-Fi Extreme Audio. This does not have an AC97. 53SB079000000 */ + /* It is really just a normal SB Live 24bit. */ + /* Tested: + * See ALSA bug#3251 + */ + { .serial = 0x10131102, + .name = "X-Fi Extreme Audio [SBxxxx]", + .gpio_type = 1, + .i2c_adc = 1 } , + /* Sound Blaster X-Fi Extreme Audio. This does not have an AC97. 53SB079000000 */ + /* It is really just a normal SB Live 24bit. */ + /* + * CTRL:CA0111-WTLF + * ADC: WM8775SEDS + * DAC: CS4382-KQZ + */ + /* Tested: + * Playback on front, rear, center/lfe speakers + * Capture from Mic in. + * Not-Tested: + * Capture from Line in. + * Playback to digital out. + */ + { .serial = 0x10121102, + .name = "X-Fi Extreme Audio [SB0790]", + .gpio_type = 1, + .i2c_adc = 1 } , + /* New Dell Sound Blaster Live! 7.1 24bit. This does not have an AC97. */ + /* AudigyLS[SB0310] */ + { .serial = 0x10021102, + .name = "AudigyLS [SB0310]", + .ac97 = 1 } , + /* Unknown AudigyLS that also says SB0310 on it */ + { .serial = 0x10051102, + .name = "AudigyLS [SB0310b]", + .ac97 = 1 } , + /* New Sound Blaster Live! 7.1 24bit. This does not have an AC97. 53SB041000001 */ + { .serial = 0x10061102, + .name = "Live! 7.1 24bit [SB0410]", + .gpio_type = 1, + .i2c_adc = 1 } , + /* New Dell Sound Blaster Live! 7.1 24bit. This does not have an AC97. */ + { .serial = 0x10071102, + .name = "Live! 7.1 24bit [SB0413]", + .gpio_type = 1, + .i2c_adc = 1 } , + /* New Audigy SE. Has a different DAC. */ + /* SB0570: + * CTRL:CA0106-DAT + * ADC: WM8775EDS + * DAC: WM8768GEDS + */ + { .serial = 0x100a1102, + .name = "Audigy SE [SB0570]", + .gpio_type = 1, + .i2c_adc = 1, + .spi_dac = 0x4021 } , + /* New Audigy LS. Has a different DAC. */ + /* SB0570: + * CTRL:CA0106-DAT + * ADC: WM8775EDS + * DAC: WM8768GEDS + */ + { .serial = 0x10111102, + .name = "Audigy SE OEM [SB0570a]", + .gpio_type = 1, + .i2c_adc = 1, + .spi_dac = 0x4021 } , + /* Sound Blaster 5.1vx + * Tested: Playback on front, rear, center/lfe speakers + * Not-Tested: Capture + */ + { .serial = 0x10041102, + .name = "Sound Blaster 5.1vx [SB1070]", + .gpio_type = 1, + .i2c_adc = 0, + .spi_dac = 0x0124 + } , + /* MSI K8N Diamond Motherboard with onboard SB Live 24bit without AC97 */ + /* SB0438 + * CTRL:CA0106-DAT + * ADC: WM8775SEDS + * DAC: CS4382-KQZ + */ + { .serial = 0x10091462, + .name = "MSI K8N Diamond MB [SB0438]", + .gpio_type = 2, + .i2c_adc = 1 } , + /* MSI K8N Diamond PLUS MB */ + { .serial = 0x10091102, + .name = "MSI K8N Diamond MB", + .gpio_type = 2, + .i2c_adc = 1, + .spi_dac = 0x4021 } , + /* Giga-byte GA-G1975X mobo + * Novell bnc#395807 + */ + /* FIXME: the GPIO and I2C setting aren't tested well */ + { .serial = 0x1458a006, + .name = "Giga-byte GA-G1975X", + .gpio_type = 1, + .i2c_adc = 1 }, + /* Shuttle XPC SD31P which has an onboard Creative Labs + * Sound Blaster Live! 24-bit EAX + * high-definition 7.1 audio processor". + * Added using info from andrewvegan in alsa bug #1298 + */ + { .serial = 0x30381297, + .name = "Shuttle XPC SD31P [SD31P]", + .gpio_type = 1, + .i2c_adc = 1 } , + /* Shuttle XPC SD11G5 which has an onboard Creative Labs + * Sound Blaster Live! 24-bit EAX + * high-definition 7.1 audio processor". + * Fixes ALSA bug#1600 + */ + { .serial = 0x30411297, + .name = "Shuttle XPC SD11G5 [SD11G5]", + .gpio_type = 1, + .i2c_adc = 1 } , + { .serial = 0, + .name = "AudigyLS [Unknown]" } +}; + +/* hardware definition */ +static const struct snd_pcm_hardware snd_ca0106_playback_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, + .rates = (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000), + .rate_min = 48000, + .rate_max = 192000, + .channels_min = 2, //1, + .channels_max = 2, //6, + .buffer_bytes_max = ((65536 - 64) * 8), + .period_bytes_min = 64, + .period_bytes_max = (65536 - 64), + .periods_min = 2, + .periods_max = 8, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_ca0106_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, +#if 0 /* FIXME: looks like 44.1kHz capture causes noisy output on 48kHz */ + .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000), + .rate_min = 44100, +#else + .rates = (SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000), + .rate_min = 48000, +#endif /* FIXME */ + .rate_max = 192000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 65536 - 128, + .period_bytes_min = 64, + .period_bytes_max = 32768 - 64, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +unsigned int snd_ca0106_ptr_read(struct snd_ca0106 * emu, + unsigned int reg, + unsigned int chn) +{ + unsigned int regptr; + + regptr = (reg << 16) | chn; + + guard(spinlock_irqsave)(&emu->emu_lock); + outl(regptr, emu->port + CA0106_PTR); + return inl(emu->port + CA0106_DATA); +} + +void snd_ca0106_ptr_write(struct snd_ca0106 *emu, + unsigned int reg, + unsigned int chn, + unsigned int data) +{ + unsigned int regptr; + + regptr = (reg << 16) | chn; + + guard(spinlock_irqsave)(&emu->emu_lock); + outl(regptr, emu->port + CA0106_PTR); + outl(data, emu->port + CA0106_DATA); +} + +int snd_ca0106_spi_write(struct snd_ca0106 * emu, + unsigned int data) +{ + unsigned int reset, set; + unsigned int reg, tmp; + int n, result; + reg = SPI; + if (data > 0xffff) /* Only 16bit values allowed */ + return 1; + tmp = snd_ca0106_ptr_read(emu, reg, 0); + reset = (tmp & ~0x3ffff) | 0x20000; /* Set xxx20000 */ + set = reset | 0x10000; /* Set xxx1xxxx */ + snd_ca0106_ptr_write(emu, reg, 0, reset | data); + tmp = snd_ca0106_ptr_read(emu, reg, 0); /* write post */ + snd_ca0106_ptr_write(emu, reg, 0, set | data); + result = 1; + /* Wait for status bit to return to 0 */ + for (n = 0; n < 100; n++) { + udelay(10); + tmp = snd_ca0106_ptr_read(emu, reg, 0); + if (!(tmp & 0x10000)) { + result = 0; + break; + } + } + if (result) /* Timed out */ + return 1; + snd_ca0106_ptr_write(emu, reg, 0, reset | data); + tmp = snd_ca0106_ptr_read(emu, reg, 0); /* Write post */ + return 0; +} + +/* The ADC does not support i2c read, so only write is implemented */ +int snd_ca0106_i2c_write(struct snd_ca0106 *emu, + u32 reg, + u32 value) +{ + u32 tmp; + int timeout = 0; + int status; + int retry; + if ((reg > 0x7f) || (value > 0x1ff)) { + dev_err(emu->card->dev, "i2c_write: invalid values.\n"); + return -EINVAL; + } + + tmp = reg << 25 | value << 16; + /* + dev_dbg(emu->card->dev, "I2C-write:reg=0x%x, value=0x%x\n", reg, value); + */ + /* Not sure what this I2C channel controls. */ + /* snd_ca0106_ptr_write(emu, I2C_D0, 0, tmp); */ + + /* This controls the I2C connected to the WM8775 ADC Codec */ + snd_ca0106_ptr_write(emu, I2C_D1, 0, tmp); + + for (retry = 0; retry < 10; retry++) { + /* Send the data to i2c */ + //tmp = snd_ca0106_ptr_read(emu, I2C_A, 0); + //tmp = tmp & ~(I2C_A_ADC_READ|I2C_A_ADC_LAST|I2C_A_ADC_START|I2C_A_ADC_ADD_MASK); + tmp = 0; + tmp = tmp | (I2C_A_ADC_LAST|I2C_A_ADC_START|I2C_A_ADC_ADD); + snd_ca0106_ptr_write(emu, I2C_A, 0, tmp); + + /* Wait till the transaction ends */ + while (1) { + status = snd_ca0106_ptr_read(emu, I2C_A, 0); + /*dev_dbg(emu->card->dev, "I2C:status=0x%x\n", status);*/ + timeout++; + if ((status & I2C_A_ADC_START) == 0) + break; + + if (timeout > 1000) + break; + } + //Read back and see if the transaction is successful + if ((status & I2C_A_ADC_ABORT) == 0) + break; + } + + if (retry == 10) { + dev_err(emu->card->dev, "Writing to ADC failed!\n"); + return -EINVAL; + } + + return 0; +} + + +static void snd_ca0106_intr_enable(struct snd_ca0106 *emu, unsigned int intrenb) +{ + unsigned int intr_enable; + + guard(spinlock_irqsave)(&emu->emu_lock); + intr_enable = inl(emu->port + CA0106_INTE) | intrenb; + outl(intr_enable, emu->port + CA0106_INTE); +} + +static void snd_ca0106_intr_disable(struct snd_ca0106 *emu, unsigned int intrenb) +{ + unsigned int intr_enable; + + guard(spinlock_irqsave)(&emu->emu_lock); + intr_enable = inl(emu->port + CA0106_INTE) & ~intrenb; + outl(intr_enable, emu->port + CA0106_INTE); +} + + +static void snd_ca0106_pcm_free_substream(struct snd_pcm_runtime *runtime) +{ + kfree(runtime->private_data); +} + +static const int spi_dacd_reg[] = { + SPI_DACD0_REG, + SPI_DACD1_REG, + SPI_DACD2_REG, + 0, + SPI_DACD4_REG, +}; +static const int spi_dacd_bit[] = { + SPI_DACD0_BIT, + SPI_DACD1_BIT, + SPI_DACD2_BIT, + 0, + SPI_DACD4_BIT, +}; + +static void restore_spdif_bits(struct snd_ca0106 *chip, int idx) +{ + if (chip->spdif_str_bits[idx] != chip->spdif_bits[idx]) { + chip->spdif_str_bits[idx] = chip->spdif_bits[idx]; + snd_ca0106_ptr_write(chip, SPCS0 + idx, 0, + chip->spdif_str_bits[idx]); + } +} + +static int snd_ca0106_channel_dac(struct snd_ca0106 *chip, + const struct snd_ca0106_details *details, + int channel_id) +{ + switch (channel_id) { + case PCM_FRONT_CHANNEL: + return (details->spi_dac & 0xf000) >> (4 * 3); + case PCM_REAR_CHANNEL: + return (details->spi_dac & 0x0f00) >> (4 * 2); + case PCM_CENTER_LFE_CHANNEL: + return (details->spi_dac & 0x00f0) >> (4 * 1); + case PCM_UNKNOWN_CHANNEL: + return (details->spi_dac & 0x000f) >> (4 * 0); + default: + dev_dbg(chip->card->dev, "ca0106: unknown channel_id %d\n", + channel_id); + } + return 0; +} + +static int snd_ca0106_pcm_power_dac(struct snd_ca0106 *chip, int channel_id, + int power) +{ + if (chip->details->spi_dac) { + const int dac = snd_ca0106_channel_dac(chip, chip->details, + channel_id); + const int reg = spi_dacd_reg[dac]; + const int bit = spi_dacd_bit[dac]; + + if (power) + /* Power up */ + chip->spi_dac_reg[reg] &= ~bit; + else + /* Power down */ + chip->spi_dac_reg[reg] |= bit; + if (snd_ca0106_spi_write(chip, chip->spi_dac_reg[reg]) != 0) + return -ENXIO; + } + return 0; +} + +/* open_playback callback */ +static int snd_ca0106_pcm_open_playback_channel(struct snd_pcm_substream *substream, + int channel_id) +{ + struct snd_ca0106 *chip = snd_pcm_substream_chip(substream); + struct snd_ca0106_channel *channel = &(chip->playback_channels[channel_id]); + struct snd_ca0106_pcm *epcm; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + epcm = kzalloc_obj(*epcm); + + if (epcm == NULL) + return -ENOMEM; + epcm->emu = chip; + epcm->substream = substream; + epcm->channel_id=channel_id; + + runtime->private_data = epcm; + runtime->private_free = snd_ca0106_pcm_free_substream; + + runtime->hw = snd_ca0106_playback_hw; + + channel->emu = chip; + channel->number = channel_id; + + channel->use = 1; + /* + dev_dbg(chip->card->dev, "open:channel_id=%d, chip=%p, channel=%p\n", + channel_id, chip, channel); + */ + //channel->interrupt = snd_ca0106_pcm_channel_interrupt; + channel->epcm = epcm; + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); + if (err < 0) + return err; + snd_pcm_set_sync(substream); + + /* Front channel dac should already be on */ + if (channel_id != PCM_FRONT_CHANNEL) { + err = snd_ca0106_pcm_power_dac(chip, channel_id, 1); + if (err < 0) + return err; + } + + restore_spdif_bits(chip, channel_id); + + return 0; +} + +/* close callback */ +static int snd_ca0106_pcm_close_playback(struct snd_pcm_substream *substream) +{ + struct snd_ca0106 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ca0106_pcm *epcm = runtime->private_data; + chip->playback_channels[epcm->channel_id].use = 0; + + restore_spdif_bits(chip, epcm->channel_id); + + /* Front channel dac should stay on */ + if (epcm->channel_id != PCM_FRONT_CHANNEL) { + int err; + err = snd_ca0106_pcm_power_dac(chip, epcm->channel_id, 0); + if (err < 0) + return err; + } + + /* FIXME: maybe zero others */ + return 0; +} + +static int snd_ca0106_pcm_open_playback_front(struct snd_pcm_substream *substream) +{ + return snd_ca0106_pcm_open_playback_channel(substream, PCM_FRONT_CHANNEL); +} + +static int snd_ca0106_pcm_open_playback_center_lfe(struct snd_pcm_substream *substream) +{ + return snd_ca0106_pcm_open_playback_channel(substream, PCM_CENTER_LFE_CHANNEL); +} + +static int snd_ca0106_pcm_open_playback_unknown(struct snd_pcm_substream *substream) +{ + return snd_ca0106_pcm_open_playback_channel(substream, PCM_UNKNOWN_CHANNEL); +} + +static int snd_ca0106_pcm_open_playback_rear(struct snd_pcm_substream *substream) +{ + return snd_ca0106_pcm_open_playback_channel(substream, PCM_REAR_CHANNEL); +} + +/* open_capture callback */ +static int snd_ca0106_pcm_open_capture_channel(struct snd_pcm_substream *substream, + int channel_id) +{ + struct snd_ca0106 *chip = snd_pcm_substream_chip(substream); + struct snd_ca0106_channel *channel = &(chip->capture_channels[channel_id]); + struct snd_ca0106_pcm *epcm; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + epcm = kzalloc_obj(*epcm); + if (!epcm) + return -ENOMEM; + + epcm->emu = chip; + epcm->substream = substream; + epcm->channel_id=channel_id; + + runtime->private_data = epcm; + runtime->private_free = snd_ca0106_pcm_free_substream; + + runtime->hw = snd_ca0106_capture_hw; + + channel->emu = chip; + channel->number = channel_id; + + channel->use = 1; + /* + dev_dbg(chip->card->dev, "open:channel_id=%d, chip=%p, channel=%p\n", + channel_id, chip, channel); + */ + //channel->interrupt = snd_ca0106_pcm_channel_interrupt; + channel->epcm = epcm; + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + //snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_capture_period_sizes); + err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); + if (err < 0) + return err; + return 0; +} + +/* close callback */ +static int snd_ca0106_pcm_close_capture(struct snd_pcm_substream *substream) +{ + struct snd_ca0106 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ca0106_pcm *epcm = runtime->private_data; + chip->capture_channels[epcm->channel_id].use = 0; + /* FIXME: maybe zero others */ + return 0; +} + +static int snd_ca0106_pcm_open_0_capture(struct snd_pcm_substream *substream) +{ + return snd_ca0106_pcm_open_capture_channel(substream, 0); +} + +static int snd_ca0106_pcm_open_1_capture(struct snd_pcm_substream *substream) +{ + return snd_ca0106_pcm_open_capture_channel(substream, 1); +} + +static int snd_ca0106_pcm_open_2_capture(struct snd_pcm_substream *substream) +{ + return snd_ca0106_pcm_open_capture_channel(substream, 2); +} + +static int snd_ca0106_pcm_open_3_capture(struct snd_pcm_substream *substream) +{ + return snd_ca0106_pcm_open_capture_channel(substream, 3); +} + +/* prepare playback callback */ +static int snd_ca0106_pcm_prepare_playback(struct snd_pcm_substream *substream) +{ + struct snd_ca0106 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ca0106_pcm *epcm = runtime->private_data; + int channel = epcm->channel_id; + u32 *table_base = (u32 *)(emu->buffer->area+(8*16*channel)); + u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size); + u32 hcfg_mask = HCFG_PLAYBACK_S32_LE; + u32 hcfg_set = 0x00000000; + u32 hcfg; + u32 reg40_mask = 0x30000 << (channel<<1); + u32 reg40_set = 0; + u32 reg40; + /* FIXME: Depending on mixer selection of SPDIF out or not, select the spdif rate or the DAC rate. */ + u32 reg71_mask = 0x03030000 ; /* Global. Set SPDIF rate. We only support 44100 to spdif, not to DAC. */ + u32 reg71_set = 0; + u32 reg71; + int i; + +#if 0 /* debug */ + dev_dbg(emu->card->dev, + "prepare:channel_number=%d, rate=%d, format=0x%x, " + "channels=%d, buffer_size=%ld, period_size=%ld, " + "periods=%u, frames_to_bytes=%d\n", + channel, runtime->rate, runtime->format, + runtime->channels, runtime->buffer_size, + runtime->period_size, runtime->periods, + frames_to_bytes(runtime, 1)); + dev_dbg(emu->card->dev, + "dma_addr=%x, dma_area=%p, table_base=%p\n", + runtime->dma_addr, runtime->dma_area, table_base); + dev_dbg(emu->card->dev, + "dma_addr=%x, dma_area=%p, dma_bytes(size)=%x\n", + emu->buffer->addr, emu->buffer->area, emu->buffer->bytes); +#endif /* debug */ + /* Rate can be set per channel. */ + /* reg40 control host to fifo */ + /* reg71 controls DAC rate. */ + switch (runtime->rate) { + case 44100: + reg40_set = 0x10000 << (channel<<1); + reg71_set = 0x01010000; + break; + case 48000: + reg40_set = 0; + reg71_set = 0; + break; + case 96000: + reg40_set = 0x20000 << (channel<<1); + reg71_set = 0x02020000; + break; + case 192000: + reg40_set = 0x30000 << (channel<<1); + reg71_set = 0x03030000; + break; + default: + reg40_set = 0; + reg71_set = 0; + break; + } + /* Format is a global setting */ + /* FIXME: Only let the first channel accessed set this. */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + hcfg_set = 0; + break; + case SNDRV_PCM_FORMAT_S32_LE: + hcfg_set = HCFG_PLAYBACK_S32_LE; + break; + default: + hcfg_set = 0; + break; + } + hcfg = inl(emu->port + CA0106_HCFG) ; + hcfg = (hcfg & ~hcfg_mask) | hcfg_set; + outl(hcfg, emu->port + CA0106_HCFG); + reg40 = snd_ca0106_ptr_read(emu, 0x40, 0); + reg40 = (reg40 & ~reg40_mask) | reg40_set; + snd_ca0106_ptr_write(emu, 0x40, 0, reg40); + reg71 = snd_ca0106_ptr_read(emu, 0x71, 0); + reg71 = (reg71 & ~reg71_mask) | reg71_set; + snd_ca0106_ptr_write(emu, 0x71, 0, reg71); + + /* FIXME: Check emu->buffer->size before actually writing to it. */ + for(i=0; i < runtime->periods; i++) { + table_base[i*2] = runtime->dma_addr + (i * period_size_bytes); + table_base[i*2+1] = period_size_bytes << 16; + } + + snd_ca0106_ptr_write(emu, PLAYBACK_LIST_ADDR, channel, emu->buffer->addr+(8*16*channel)); + snd_ca0106_ptr_write(emu, PLAYBACK_LIST_SIZE, channel, (runtime->periods - 1) << 19); + snd_ca0106_ptr_write(emu, PLAYBACK_LIST_PTR, channel, 0); + snd_ca0106_ptr_write(emu, PLAYBACK_DMA_ADDR, channel, runtime->dma_addr); + snd_ca0106_ptr_write(emu, PLAYBACK_PERIOD_SIZE, channel, frames_to_bytes(runtime, runtime->period_size)<<16); // buffer size in bytes + /* FIXME test what 0 bytes does. */ + snd_ca0106_ptr_write(emu, PLAYBACK_PERIOD_SIZE, channel, 0); // buffer size in bytes + snd_ca0106_ptr_write(emu, PLAYBACK_POINTER, channel, 0); + snd_ca0106_ptr_write(emu, 0x07, channel, 0x0); + snd_ca0106_ptr_write(emu, 0x08, channel, 0); + snd_ca0106_ptr_write(emu, PLAYBACK_MUTE, 0x0, 0x0); /* Unmute output */ +#if 0 + snd_ca0106_ptr_write(emu, SPCS0, 0, + SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | + SPCS_GENERATIONSTATUS | 0x00001200 | + 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT ); +#endif + + return 0; +} + +/* prepare capture callback */ +static int snd_ca0106_pcm_prepare_capture(struct snd_pcm_substream *substream) +{ + struct snd_ca0106 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ca0106_pcm *epcm = runtime->private_data; + int channel = epcm->channel_id; + u32 hcfg_mask = HCFG_CAPTURE_S32_LE; + u32 hcfg_set = 0x00000000; + u32 hcfg; + u32 over_sampling=0x2; + u32 reg71_mask = 0x0000c000 ; /* Global. Set ADC rate. */ + u32 reg71_set = 0; + u32 reg71; + +#if 0 /* debug */ + dev_dbg(emu->card->dev, + "prepare:channel_number=%d, rate=%d, format=0x%x, " + "channels=%d, buffer_size=%ld, period_size=%ld, " + "periods=%u, frames_to_bytes=%d\n", + channel, runtime->rate, runtime->format, + runtime->channels, runtime->buffer_size, + runtime->period_size, runtime->periods, + frames_to_bytes(runtime, 1)); + dev_dbg(emu->card->dev, + "dma_addr=%x, dma_area=%p, table_base=%p\n", + runtime->dma_addr, runtime->dma_area, table_base); + dev_dbg(emu->card->dev, + "dma_addr=%x, dma_area=%p, dma_bytes(size)=%x\n", + emu->buffer->addr, emu->buffer->area, emu->buffer->bytes); +#endif /* debug */ + /* reg71 controls ADC rate. */ + switch (runtime->rate) { + case 44100: + reg71_set = 0x00004000; + break; + case 48000: + reg71_set = 0; + break; + case 96000: + reg71_set = 0x00008000; + over_sampling=0xa; + break; + case 192000: + reg71_set = 0x0000c000; + over_sampling=0xa; + break; + default: + reg71_set = 0; + break; + } + /* Format is a global setting */ + /* FIXME: Only let the first channel accessed set this. */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + hcfg_set = 0; + break; + case SNDRV_PCM_FORMAT_S32_LE: + hcfg_set = HCFG_CAPTURE_S32_LE; + break; + default: + hcfg_set = 0; + break; + } + hcfg = inl(emu->port + CA0106_HCFG) ; + hcfg = (hcfg & ~hcfg_mask) | hcfg_set; + outl(hcfg, emu->port + CA0106_HCFG); + reg71 = snd_ca0106_ptr_read(emu, 0x71, 0); + reg71 = (reg71 & ~reg71_mask) | reg71_set; + snd_ca0106_ptr_write(emu, 0x71, 0, reg71); + if (emu->details->i2c_adc == 1) { /* The SB0410 and SB0413 use I2C to control ADC. */ + snd_ca0106_i2c_write(emu, ADC_MASTER, over_sampling); /* Adjust the over sampler to better suit the capture rate. */ + } + + + /* + dev_dbg(emu->card->dev, + "prepare:channel_number=%d, rate=%d, format=0x%x, channels=%d, " + "buffer_size=%ld, period_size=%ld, frames_to_bytes=%d\n", + channel, runtime->rate, runtime->format, runtime->channels, + runtime->buffer_size, runtime->period_size, + frames_to_bytes(runtime, 1)); + */ + snd_ca0106_ptr_write(emu, 0x13, channel, 0); + snd_ca0106_ptr_write(emu, CAPTURE_DMA_ADDR, channel, runtime->dma_addr); + snd_ca0106_ptr_write(emu, CAPTURE_BUFFER_SIZE, channel, frames_to_bytes(runtime, runtime->buffer_size)<<16); // buffer size in bytes + snd_ca0106_ptr_write(emu, CAPTURE_POINTER, channel, 0); + + return 0; +} + +/* trigger_playback callback */ +static int snd_ca0106_pcm_trigger_playback(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ca0106 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime; + struct snd_ca0106_pcm *epcm; + int channel; + int result = 0; + struct snd_pcm_substream *s; + u32 basic = 0; + u32 extended = 0; + u32 bits; + int running = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + default: + running = 0; + break; + } + snd_pcm_group_for_each_entry(s, substream) { + if (snd_pcm_substream_chip(s) != emu || + s->stream != SNDRV_PCM_STREAM_PLAYBACK) + continue; + runtime = s->runtime; + epcm = runtime->private_data; + channel = epcm->channel_id; + /* dev_dbg(emu->card->dev, "channel=%d\n", channel); */ + epcm->running = running; + basic |= (0x1 << channel); + extended |= (0x10 << channel); + snd_pcm_trigger_done(s, substream); + } + /* dev_dbg(emu->card->dev, "basic=0x%x, extended=0x%x\n",basic, extended); */ + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + bits = snd_ca0106_ptr_read(emu, EXTENDED_INT_MASK, 0); + bits |= extended; + snd_ca0106_ptr_write(emu, EXTENDED_INT_MASK, 0, bits); + bits = snd_ca0106_ptr_read(emu, BASIC_INTERRUPT, 0); + bits |= basic; + snd_ca0106_ptr_write(emu, BASIC_INTERRUPT, 0, bits); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + bits = snd_ca0106_ptr_read(emu, BASIC_INTERRUPT, 0); + bits &= ~basic; + snd_ca0106_ptr_write(emu, BASIC_INTERRUPT, 0, bits); + bits = snd_ca0106_ptr_read(emu, EXTENDED_INT_MASK, 0); + bits &= ~extended; + snd_ca0106_ptr_write(emu, EXTENDED_INT_MASK, 0, bits); + break; + default: + result = -EINVAL; + break; + } + return result; +} + +/* trigger_capture callback */ +static int snd_ca0106_pcm_trigger_capture(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ca0106 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ca0106_pcm *epcm = runtime->private_data; + int channel = epcm->channel_id; + int result = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_ca0106_ptr_write(emu, EXTENDED_INT_MASK, 0, snd_ca0106_ptr_read(emu, EXTENDED_INT_MASK, 0) | (0x110000<running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_ca0106_ptr_write(emu, BASIC_INTERRUPT, 0, snd_ca0106_ptr_read(emu, BASIC_INTERRUPT, 0) & ~(0x100<running = 0; + break; + default: + result = -EINVAL; + break; + } + return result; +} + +/* pointer_playback callback */ +static snd_pcm_uframes_t +snd_ca0106_pcm_pointer_playback(struct snd_pcm_substream *substream) +{ + struct snd_ca0106 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ca0106_pcm *epcm = runtime->private_data; + unsigned int ptr, prev_ptr; + int channel = epcm->channel_id; + int timeout = 10; + + if (!epcm->running) + return 0; + + prev_ptr = -1; + do { + ptr = snd_ca0106_ptr_read(emu, PLAYBACK_LIST_PTR, channel); + ptr = (ptr >> 3) * runtime->period_size; + ptr += bytes_to_frames(runtime, + snd_ca0106_ptr_read(emu, PLAYBACK_POINTER, channel)); + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + if (prev_ptr == ptr) + return ptr; + prev_ptr = ptr; + } while (--timeout); + dev_warn(emu->card->dev, "ca0106: unstable DMA pointer!\n"); + return 0; +} + +/* pointer_capture callback */ +static snd_pcm_uframes_t +snd_ca0106_pcm_pointer_capture(struct snd_pcm_substream *substream) +{ + struct snd_ca0106 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ca0106_pcm *epcm = runtime->private_data; + snd_pcm_uframes_t ptr, ptr1, ptr2 = 0; + int channel = epcm->channel_id; + + if (!epcm->running) + return 0; + + ptr1 = snd_ca0106_ptr_read(emu, CAPTURE_POINTER, channel); + ptr2 = bytes_to_frames(runtime, ptr1); + ptr=ptr2; + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + /* + dev_dbg(emu->card->dev, "ptr1 = 0x%lx, ptr2=0x%lx, ptr=0x%lx, " + "buffer_size = 0x%x, period_size = 0x%x, bits=%d, rate=%d\n", + ptr1, ptr2, ptr, (int)runtime->buffer_size, + (int)runtime->period_size, (int)runtime->frame_bits, + (int)runtime->rate); + */ + return ptr; +} + +/* operators */ +static const struct snd_pcm_ops snd_ca0106_playback_front_ops = { + .open = snd_ca0106_pcm_open_playback_front, + .close = snd_ca0106_pcm_close_playback, + .prepare = snd_ca0106_pcm_prepare_playback, + .trigger = snd_ca0106_pcm_trigger_playback, + .pointer = snd_ca0106_pcm_pointer_playback, +}; + +static const struct snd_pcm_ops snd_ca0106_capture_0_ops = { + .open = snd_ca0106_pcm_open_0_capture, + .close = snd_ca0106_pcm_close_capture, + .prepare = snd_ca0106_pcm_prepare_capture, + .trigger = snd_ca0106_pcm_trigger_capture, + .pointer = snd_ca0106_pcm_pointer_capture, +}; + +static const struct snd_pcm_ops snd_ca0106_capture_1_ops = { + .open = snd_ca0106_pcm_open_1_capture, + .close = snd_ca0106_pcm_close_capture, + .prepare = snd_ca0106_pcm_prepare_capture, + .trigger = snd_ca0106_pcm_trigger_capture, + .pointer = snd_ca0106_pcm_pointer_capture, +}; + +static const struct snd_pcm_ops snd_ca0106_capture_2_ops = { + .open = snd_ca0106_pcm_open_2_capture, + .close = snd_ca0106_pcm_close_capture, + .prepare = snd_ca0106_pcm_prepare_capture, + .trigger = snd_ca0106_pcm_trigger_capture, + .pointer = snd_ca0106_pcm_pointer_capture, +}; + +static const struct snd_pcm_ops snd_ca0106_capture_3_ops = { + .open = snd_ca0106_pcm_open_3_capture, + .close = snd_ca0106_pcm_close_capture, + .prepare = snd_ca0106_pcm_prepare_capture, + .trigger = snd_ca0106_pcm_trigger_capture, + .pointer = snd_ca0106_pcm_pointer_capture, +}; + +static const struct snd_pcm_ops snd_ca0106_playback_center_lfe_ops = { + .open = snd_ca0106_pcm_open_playback_center_lfe, + .close = snd_ca0106_pcm_close_playback, + .prepare = snd_ca0106_pcm_prepare_playback, + .trigger = snd_ca0106_pcm_trigger_playback, + .pointer = snd_ca0106_pcm_pointer_playback, +}; + +static const struct snd_pcm_ops snd_ca0106_playback_unknown_ops = { + .open = snd_ca0106_pcm_open_playback_unknown, + .close = snd_ca0106_pcm_close_playback, + .prepare = snd_ca0106_pcm_prepare_playback, + .trigger = snd_ca0106_pcm_trigger_playback, + .pointer = snd_ca0106_pcm_pointer_playback, +}; + +static const struct snd_pcm_ops snd_ca0106_playback_rear_ops = { + .open = snd_ca0106_pcm_open_playback_rear, + .close = snd_ca0106_pcm_close_playback, + .prepare = snd_ca0106_pcm_prepare_playback, + .trigger = snd_ca0106_pcm_trigger_playback, + .pointer = snd_ca0106_pcm_pointer_playback, +}; + + +static unsigned short snd_ca0106_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct snd_ca0106 *emu = ac97->private_data; + + guard(spinlock_irqsave)(&emu->emu_lock); + outb(reg, emu->port + CA0106_AC97ADDRESS); + return inw(emu->port + CA0106_AC97DATA); +} + +static void snd_ca0106_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + struct snd_ca0106 *emu = ac97->private_data; + + guard(spinlock_irqsave)(&emu->emu_lock); + outb(reg, emu->port + CA0106_AC97ADDRESS); + outw(val, emu->port + CA0106_AC97DATA); +} + +static int snd_ca0106_ac97(struct snd_ca0106 *chip) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_ca0106_ac97_write, + .read = snd_ca0106_ac97_read, + }; + + err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + pbus->no_vra = 1; /* we don't need VRA */ + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.scaps = AC97_SCAP_NO_SPDIF; + return snd_ac97_mixer(pbus, &ac97, &chip->ac97); +} + +static void ca0106_stop_chip(struct snd_ca0106 *chip); + +static void snd_ca0106_free(struct snd_card *card) +{ + struct snd_ca0106 *chip = card->private_data; + + ca0106_stop_chip(chip); +} + +static irqreturn_t snd_ca0106_interrupt(int irq, void *dev_id) +{ + unsigned int status; + + struct snd_ca0106 *chip = dev_id; + int i; + int mask; + unsigned int stat76; + struct snd_ca0106_channel *pchannel; + + status = inl(chip->port + CA0106_IPR); + if (! status) + return IRQ_NONE; + + stat76 = snd_ca0106_ptr_read(chip, EXTENDED_INT, 0); + /* + dev_dbg(emu->card->dev, "interrupt status = 0x%08x, stat76=0x%08x\n", + status, stat76); + dev_dbg(emu->card->dev, "ptr=0x%08x\n", + snd_ca0106_ptr_read(chip, PLAYBACK_POINTER, 0)); + */ + mask = 0x11; /* 0x1 for one half, 0x10 for the other half period. */ + for(i = 0; i < 4; i++) { + pchannel = &(chip->playback_channels[i]); + if (stat76 & mask) { +/* FIXME: Select the correct substream for period elapsed */ + if(pchannel->use) { + snd_pcm_period_elapsed(pchannel->epcm->substream); + /* dev_dbg(emu->card->dev, "interrupt [%d] used\n", i); */ + } + } + /* + dev_dbg(emu->card->dev, "channel=%p\n", pchannel); + dev_dbg(emu->card->dev, "interrupt stat76[%d] = %08x, use=%d, channel=%d\n", i, stat76, pchannel->use, pchannel->number); + */ + mask <<= 1; + } + mask = 0x110000; /* 0x1 for one half, 0x10 for the other half period. */ + for(i = 0; i < 4; i++) { + pchannel = &(chip->capture_channels[i]); + if (stat76 & mask) { +/* FIXME: Select the correct substream for period elapsed */ + if(pchannel->use) { + snd_pcm_period_elapsed(pchannel->epcm->substream); + /* dev_dbg(emu->card->dev, "interrupt [%d] used\n", i); */ + } + } + /* + dev_dbg(emu->card->dev, "channel=%p\n", pchannel); + dev_dbg(emu->card->dev, "interrupt stat76[%d] = %08x, use=%d, channel=%d\n", i, stat76, pchannel->use, pchannel->number); + */ + mask <<= 1; + } + + snd_ca0106_ptr_write(chip, EXTENDED_INT, 0, stat76); + + if (chip->midi.dev_id && + (status & (chip->midi.ipr_tx|chip->midi.ipr_rx))) { + if (chip->midi.interrupt) + chip->midi.interrupt(&chip->midi, status); + else + chip->midi.interrupt_disable(&chip->midi, chip->midi.tx_enable | chip->midi.rx_enable); + } + + // acknowledge the interrupt if necessary + outl(status, chip->port + CA0106_IPR); + + return IRQ_HANDLED; +} + +static const struct snd_pcm_chmap_elem surround_map[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +static const struct snd_pcm_chmap_elem clfe_map[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, + { } +}; + +static const struct snd_pcm_chmap_elem side_map[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, + { } +}; + +static int snd_ca0106_pcm(struct snd_ca0106 *emu, int device) +{ + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + const struct snd_pcm_chmap_elem *map = NULL; + int err; + + err = snd_pcm_new(emu->card, "ca0106", device, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = emu; + + switch (device) { + case 0: + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_front_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_0_ops); + map = snd_pcm_std_chmaps; + break; + case 1: + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_rear_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_1_ops); + map = surround_map; + break; + case 2: + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_center_lfe_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_2_ops); + map = clfe_map; + break; + case 3: + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_unknown_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_3_ops); + map = side_map; + break; + } + + pcm->info_flags = 0; + strscpy(pcm->name, "CA0106"); + + for(substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + substream; + substream = substream->next) { + snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV, + &emu->pci->dev, + 64*1024, 64*1024); + } + + for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + substream; + substream = substream->next) { + snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV, + &emu->pci->dev, + 64*1024, 64*1024); + } + + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, map, 2, + 1 << 2, NULL); + if (err < 0) + return err; + + emu->pcm[device] = pcm; + + return 0; +} + +#define SPI_REG(reg, value) (((reg) << SPI_REG_SHIFT) | (value)) +static const unsigned int spi_dac_init[] = { + SPI_REG(SPI_LDA1_REG, SPI_DA_BIT_0dB), /* 0dB dig. attenuation */ + SPI_REG(SPI_RDA1_REG, SPI_DA_BIT_0dB), + SPI_REG(SPI_PL_REG, SPI_PL_BIT_L_L | SPI_PL_BIT_R_R | SPI_IZD_BIT), + SPI_REG(SPI_FMT_REG, SPI_FMT_BIT_I2S | SPI_IWL_BIT_24), + SPI_REG(SPI_LDA2_REG, SPI_DA_BIT_0dB), + SPI_REG(SPI_RDA2_REG, SPI_DA_BIT_0dB), + SPI_REG(SPI_LDA3_REG, SPI_DA_BIT_0dB), + SPI_REG(SPI_RDA3_REG, SPI_DA_BIT_0dB), + SPI_REG(SPI_MASTDA_REG, SPI_DA_BIT_0dB), + SPI_REG(9, 0x00), + SPI_REG(SPI_MS_REG, SPI_DACD0_BIT | SPI_DACD1_BIT | SPI_DACD2_BIT), + SPI_REG(12, 0x00), + SPI_REG(SPI_LDA4_REG, SPI_DA_BIT_0dB), + SPI_REG(SPI_RDA4_REG, SPI_DA_BIT_0dB | SPI_DA_BIT_UPDATE), + SPI_REG(SPI_DACD4_REG, SPI_DACD4_BIT), +}; + +static const unsigned int i2c_adc_init[][2] = { + { 0x17, 0x00 }, /* Reset */ + { 0x07, 0x00 }, /* Timeout */ + { 0x0b, 0x22 }, /* Interface control */ + { 0x0c, 0x22 }, /* Master mode control */ + { 0x0d, 0x08 }, /* Powerdown control */ + { 0x0e, 0xcf }, /* Attenuation Left 0x01 = -103dB, 0xff = 24dB */ + { 0x0f, 0xcf }, /* Attenuation Right 0.5dB steps */ + { 0x10, 0x7b }, /* ALC Control 1 */ + { 0x11, 0x00 }, /* ALC Control 2 */ + { 0x12, 0x32 }, /* ALC Control 3 */ + { 0x13, 0x00 }, /* Noise gate control */ + { 0x14, 0xa6 }, /* Limiter control */ + { 0x15, ADC_MUX_LINEIN }, /* ADC Mixer control */ +}; + +static void ca0106_init_chip(struct snd_ca0106 *chip, int resume) +{ + int ch; + unsigned int def_bits; + + outl(0, chip->port + CA0106_INTE); + + /* + * Init to 0x02109204 : + * Clock accuracy = 0 (1000ppm) + * Sample Rate = 2 (48kHz) + * Audio Channel = 1 (Left of 2) + * Source Number = 0 (Unspecified) + * Generation Status = 1 (Original for Cat Code 12) + * Cat Code = 12 (Digital Signal Mixer) + * Mode = 0 (Mode 0) + * Emphasis = 0 (None) + * CP = 1 (Copyright unasserted) + * AN = 0 (Audio data) + * P = 0 (Consumer) + */ + def_bits = + SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | + SPCS_GENERATIONSTATUS | 0x00001200 | + 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT; + if (!resume) { + chip->spdif_str_bits[0] = chip->spdif_bits[0] = def_bits; + chip->spdif_str_bits[1] = chip->spdif_bits[1] = def_bits; + chip->spdif_str_bits[2] = chip->spdif_bits[2] = def_bits; + chip->spdif_str_bits[3] = chip->spdif_bits[3] = def_bits; + } + /* Only SPCS1 has been tested */ + snd_ca0106_ptr_write(chip, SPCS1, 0, chip->spdif_str_bits[1]); + snd_ca0106_ptr_write(chip, SPCS0, 0, chip->spdif_str_bits[0]); + snd_ca0106_ptr_write(chip, SPCS2, 0, chip->spdif_str_bits[2]); + snd_ca0106_ptr_write(chip, SPCS3, 0, chip->spdif_str_bits[3]); + + snd_ca0106_ptr_write(chip, PLAYBACK_MUTE, 0, 0x00fc0000); + snd_ca0106_ptr_write(chip, CAPTURE_MUTE, 0, 0x00fc0000); + + /* Write 0x8000 to AC97_REC_GAIN to mute it. */ + outb(AC97_REC_GAIN, chip->port + CA0106_AC97ADDRESS); + outw(0x8000, chip->port + CA0106_AC97DATA); +#if 0 /* FIXME: what are these? */ + snd_ca0106_ptr_write(chip, SPCS0, 0, 0x2108006); + snd_ca0106_ptr_write(chip, 0x42, 0, 0x2108006); + snd_ca0106_ptr_write(chip, 0x43, 0, 0x2108006); + snd_ca0106_ptr_write(chip, 0x44, 0, 0x2108006); +#endif + + /* OSS drivers set this. */ + /* snd_ca0106_ptr_write(chip, SPDIF_SELECT2, 0, 0xf0f003f); */ + + /* Analog or Digital output */ + snd_ca0106_ptr_write(chip, SPDIF_SELECT1, 0, 0xf); + /* 0x0b000000 for digital, 0x000b0000 for analog, from win2000 drivers. + * Use 0x000f0000 for surround71 + */ + snd_ca0106_ptr_write(chip, SPDIF_SELECT2, 0, 0x000f0000); + + chip->spdif_enable = 0; /* Set digital SPDIF output off */ + /*snd_ca0106_ptr_write(chip, 0x45, 0, 0);*/ /* Analogue out */ + /*snd_ca0106_ptr_write(chip, 0x45, 0, 0xf00);*/ /* Digital out */ + + /* goes to 0x40c80000 when doing SPDIF IN/OUT */ + snd_ca0106_ptr_write(chip, CAPTURE_CONTROL, 0, 0x40c81000); + /* (Mute) CAPTURE feedback into PLAYBACK volume. + * Only lower 16 bits matter. + */ + snd_ca0106_ptr_write(chip, CAPTURE_CONTROL, 1, 0xffffffff); + /* SPDIF IN Volume */ + snd_ca0106_ptr_write(chip, CAPTURE_CONTROL, 2, 0x30300000); + /* SPDIF IN Volume, 0x70 = (vol & 0x3f) | 0x40 */ + snd_ca0106_ptr_write(chip, CAPTURE_CONTROL, 3, 0x00700000); + + snd_ca0106_ptr_write(chip, PLAYBACK_ROUTING1, 0, 0x32765410); + snd_ca0106_ptr_write(chip, PLAYBACK_ROUTING2, 0, 0x76767676); + snd_ca0106_ptr_write(chip, CAPTURE_ROUTING1, 0, 0x32765410); + snd_ca0106_ptr_write(chip, CAPTURE_ROUTING2, 0, 0x76767676); + + for (ch = 0; ch < 4; ch++) { + /* Only high 16 bits matter */ + snd_ca0106_ptr_write(chip, CAPTURE_VOLUME1, ch, 0x30303030); + snd_ca0106_ptr_write(chip, CAPTURE_VOLUME2, ch, 0x30303030); +#if 0 /* Mute */ + snd_ca0106_ptr_write(chip, PLAYBACK_VOLUME1, ch, 0x40404040); + snd_ca0106_ptr_write(chip, PLAYBACK_VOLUME2, ch, 0x40404040); + snd_ca0106_ptr_write(chip, PLAYBACK_VOLUME1, ch, 0xffffffff); + snd_ca0106_ptr_write(chip, PLAYBACK_VOLUME2, ch, 0xffffffff); +#endif + } + if (chip->details->i2c_adc == 1) { + /* Select MIC, Line in, TAD in, AUX in */ + snd_ca0106_ptr_write(chip, CAPTURE_SOURCE, 0x0, 0x333300e4); + /* Default to CAPTURE_SOURCE to i2s in */ + if (!resume) + chip->capture_source = 3; + } else if (chip->details->ac97 == 1) { + /* Default to AC97 in */ + snd_ca0106_ptr_write(chip, CAPTURE_SOURCE, 0x0, 0x444400e4); + /* Default to CAPTURE_SOURCE to AC97 in */ + if (!resume) + chip->capture_source = 4; + } else { + /* Select MIC, Line in, TAD in, AUX in */ + snd_ca0106_ptr_write(chip, CAPTURE_SOURCE, 0x0, 0x333300e4); + /* Default to Set CAPTURE_SOURCE to i2s in */ + if (!resume) + chip->capture_source = 3; + } + + if (chip->details->gpio_type == 2) { + /* The SB0438 use GPIO differently. */ + /* FIXME: Still need to find out what the other GPIO bits do. + * E.g. For digital spdif out. + */ + outl(0x0, chip->port + CA0106_GPIO); + /* outl(0x00f0e000, chip->port + CA0106_GPIO); */ /* Analog */ + outl(0x005f5301, chip->port + CA0106_GPIO); /* Analog */ + } else if (chip->details->gpio_type == 1) { + /* The SB0410 and SB0413 use GPIO differently. */ + /* FIXME: Still need to find out what the other GPIO bits do. + * E.g. For digital spdif out. + */ + outl(0x0, chip->port + CA0106_GPIO); + /* outl(0x00f0e000, chip->port + CA0106_GPIO); */ /* Analog */ + outl(0x005f5301, chip->port + CA0106_GPIO); /* Analog */ + } else { + outl(0x0, chip->port + CA0106_GPIO); + outl(0x005f03a3, chip->port + CA0106_GPIO); /* Analog */ + /* outl(0x005f02a2, chip->port + CA0106_GPIO); */ /* SPDIF */ + } + snd_ca0106_intr_enable(chip, 0x105); /* Win2000 uses 0x1e0 */ + + /* outl(HCFG_LOCKSOUNDCACHE|HCFG_AUDIOENABLE, chip->port+HCFG); */ + /* 0x1000 causes AC3 to fails. Maybe it effects 24 bit output. */ + /* outl(0x00001409, chip->port + CA0106_HCFG); */ + /* outl(0x00000009, chip->port + CA0106_HCFG); */ + /* AC97 2.0, Enable outputs. */ + outl(HCFG_AC97 | HCFG_AUDIOENABLE, chip->port + CA0106_HCFG); + + if (chip->details->i2c_adc == 1) { + /* The SB0410 and SB0413 use I2C to control ADC. */ + int size, n; + + size = ARRAY_SIZE(i2c_adc_init); + /* dev_dbg(emu->card->dev, "I2C:array size=0x%x\n", size); */ + for (n = 0; n < size; n++) + snd_ca0106_i2c_write(chip, i2c_adc_init[n][0], + i2c_adc_init[n][1]); + for (n = 0; n < 4; n++) { + chip->i2c_capture_volume[n][0] = 0xcf; + chip->i2c_capture_volume[n][1] = 0xcf; + } + chip->i2c_capture_source = 2; /* Line in */ + /* Enable Line-in capture. MIC in currently untested. */ + /* snd_ca0106_i2c_write(chip, ADC_MUX, ADC_MUX_LINEIN); */ + } + + if (chip->details->spi_dac) { + /* The SB0570 use SPI to control DAC. */ + int size, n; + + size = ARRAY_SIZE(spi_dac_init); + for (n = 0; n < size; n++) { + int reg = spi_dac_init[n] >> SPI_REG_SHIFT; + + snd_ca0106_spi_write(chip, spi_dac_init[n]); + if (reg < ARRAY_SIZE(chip->spi_dac_reg)) + chip->spi_dac_reg[reg] = spi_dac_init[n]; + } + + /* Enable front dac only */ + snd_ca0106_pcm_power_dac(chip, PCM_FRONT_CHANNEL, 1); + } +} + +static void ca0106_stop_chip(struct snd_ca0106 *chip) +{ + /* disable interrupts */ + snd_ca0106_ptr_write(chip, BASIC_INTERRUPT, 0, 0); + outl(0, chip->port + CA0106_INTE); + snd_ca0106_ptr_write(chip, EXTENDED_INT_MASK, 0, 0); + udelay(1000); + /* disable audio */ + /* outl(HCFG_LOCKSOUNDCACHE, chip->port + HCFG); */ + outl(0, chip->port + CA0106_HCFG); + /* FIXME: We need to stop and DMA transfers here. + * But as I am not sure how yet, we cannot from the dma pages. + * So we can fix: snd-malloc: Memory leak? pages not freed = 8 + */ +} + +static int snd_ca0106_create(int dev, struct snd_card *card, + struct pci_dev *pci) +{ + struct snd_ca0106 *chip = card->private_data; + const struct snd_ca0106_details *c; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32))) { + dev_err(card->dev, "error to set 32bit mask DMA\n"); + return -ENXIO; + } + + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + spin_lock_init(&chip->emu_lock); + + err = pcim_request_all_regions(pci, "snd_ca0106"); + if (err < 0) + return err; + chip->port = pci_resource_start(pci, 0); + + if (devm_request_irq(&pci->dev, pci->irq, snd_ca0106_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "cannot grab irq\n"); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + + /* This stores the periods table. */ + chip->buffer = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, 1024); + if (!chip->buffer) + return -ENOMEM; + + pci_set_master(pci); + /* read serial */ + pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &chip->serial); + pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &chip->model); + dev_info(card->dev, "Model %04x Rev %08x Serial %08x\n", + chip->model, pci->revision, chip->serial); + strscpy(card->driver, "CA0106"); + strscpy(card->shortname, "CA0106"); + + for (c = ca0106_chip_details; c->serial; c++) { + if (subsystem[dev]) { + if (c->serial == subsystem[dev]) + break; + } else if (c->serial == chip->serial) + break; + } + chip->details = c; + if (subsystem[dev]) { + dev_info(card->dev, "Sound card name=%s, " + "subsystem=0x%x. Forced to subsystem=0x%x\n", + c->name, chip->serial, subsystem[dev]); + } + + sprintf(card->longname, "%s at 0x%lx irq %i", + c->name, chip->port, chip->irq); + + ca0106_init_chip(chip, 0); + return 0; +} + + +static void ca0106_midi_interrupt_enable(struct snd_ca_midi *midi, int intr) +{ + snd_ca0106_intr_enable((struct snd_ca0106 *)(midi->dev_id), intr); +} + +static void ca0106_midi_interrupt_disable(struct snd_ca_midi *midi, int intr) +{ + snd_ca0106_intr_disable((struct snd_ca0106 *)(midi->dev_id), intr); +} + +static unsigned char ca0106_midi_read(struct snd_ca_midi *midi, int idx) +{ + return (unsigned char)snd_ca0106_ptr_read((struct snd_ca0106 *)(midi->dev_id), + midi->port + idx, 0); +} + +static void ca0106_midi_write(struct snd_ca_midi *midi, int data, int idx) +{ + snd_ca0106_ptr_write((struct snd_ca0106 *)(midi->dev_id), midi->port + idx, 0, data); +} + +static struct snd_card *ca0106_dev_id_card(void *dev_id) +{ + return ((struct snd_ca0106 *)dev_id)->card; +} + +static int ca0106_dev_id_port(void *dev_id) +{ + return ((struct snd_ca0106 *)dev_id)->port; +} + +static int snd_ca0106_midi(struct snd_ca0106 *chip, unsigned int channel) +{ + struct snd_ca_midi *midi; + char *name; + int err; + + if (channel == CA0106_MIDI_CHAN_B) { + name = "CA0106 MPU-401 (UART) B"; + midi = &chip->midi2; + midi->tx_enable = INTE_MIDI_TX_B; + midi->rx_enable = INTE_MIDI_RX_B; + midi->ipr_tx = IPR_MIDI_TX_B; + midi->ipr_rx = IPR_MIDI_RX_B; + midi->port = MIDI_UART_B_DATA; + } else { + name = "CA0106 MPU-401 (UART)"; + midi = &chip->midi; + midi->tx_enable = INTE_MIDI_TX_A; + midi->rx_enable = INTE_MIDI_TX_B; + midi->ipr_tx = IPR_MIDI_TX_A; + midi->ipr_rx = IPR_MIDI_RX_A; + midi->port = MIDI_UART_A_DATA; + } + + midi->reset = CA0106_MPU401_RESET; + midi->enter_uart = CA0106_MPU401_ENTER_UART; + midi->ack = CA0106_MPU401_ACK; + + midi->input_avail = CA0106_MIDI_INPUT_AVAIL; + midi->output_ready = CA0106_MIDI_OUTPUT_READY; + + midi->channel = channel; + + midi->interrupt_enable = ca0106_midi_interrupt_enable; + midi->interrupt_disable = ca0106_midi_interrupt_disable; + + midi->read = ca0106_midi_read; + midi->write = ca0106_midi_write; + + midi->get_dev_id_card = ca0106_dev_id_card; + midi->get_dev_id_port = ca0106_dev_id_port; + + midi->dev_id = chip; + + err = ca_midi_init(chip, midi, 0, name); + if (err < 0) + return err; + + return 0; +} + + +static int __snd_ca0106_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_ca0106 *chip; + int i, err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + err = snd_ca0106_create(dev, card, pci); + if (err < 0) + return err; + card->private_free = snd_ca0106_free; + + for (i = 0; i < 4; i++) { + err = snd_ca0106_pcm(chip, i); + if (err < 0) + return err; + } + + if (chip->details->ac97 == 1) { + /* The SB0410 and SB0413 do not have an AC97 chip. */ + err = snd_ca0106_ac97(chip); + if (err < 0) + return err; + } + err = snd_ca0106_mixer(chip); + if (err < 0) + return err; + + dev_dbg(card->dev, "probe for MIDI channel A ..."); + err = snd_ca0106_midi(chip, CA0106_MIDI_CHAN_A); + if (err < 0) + return err; + dev_dbg(card->dev, " done.\n"); + +#ifdef CONFIG_SND_PROC_FS + snd_ca0106_proc_init(chip); +#endif + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_ca0106_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_ca0106_probe(pci, pci_id)); +} + +#ifdef CONFIG_PM_SLEEP +static int snd_ca0106_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ca0106 *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + if (chip->details->ac97) + snd_ac97_suspend(chip->ac97); + snd_ca0106_mixer_suspend(chip); + + ca0106_stop_chip(chip); + return 0; +} + +static int snd_ca0106_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ca0106 *chip = card->private_data; + int i; + + ca0106_init_chip(chip, 1); + + if (chip->details->ac97) + snd_ac97_resume(chip->ac97); + snd_ca0106_mixer_resume(chip); + if (chip->details->spi_dac) { + for (i = 0; i < ARRAY_SIZE(chip->spi_dac_reg); i++) + snd_ca0106_spi_write(chip, chip->spi_dac_reg[i]); + } + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_ca0106_pm, snd_ca0106_suspend, snd_ca0106_resume); +#define SND_CA0106_PM_OPS &snd_ca0106_pm +#else +#define SND_CA0106_PM_OPS NULL +#endif + +// PCI IDs +static const struct pci_device_id snd_ca0106_ids[] = { + { PCI_VDEVICE(CREATIVE, 0x0007) }, /* Audigy LS or Live 24bit */ + { } +}; +MODULE_DEVICE_TABLE(pci, snd_ca0106_ids); + +// pci_driver definition +static struct pci_driver ca0106_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_ca0106_ids, + .probe = snd_ca0106_probe, + .driver = { + .pm = SND_CA0106_PM_OPS, + }, +}; + +module_pci_driver(ca0106_driver); diff --git a/sound/pci/ca0106/ca0106_mixer.c b/sound/pci/ca0106/ca0106_mixer.c new file mode 100644 index 000000000..f7b6b2db8 --- /dev/null +++ b/sound/pci/ca0106/ca0106_mixer.c @@ -0,0 +1,902 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2004 James Courtier-Dutton + * Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit + * Version: 0.0.18 + * + * FEATURES currently supported: + * See ca0106_main.c for features. + * + * Changelog: + * Support interrupts per period. + * Removed noise from Center/LFE channel when in Analog mode. + * Rename and remove mixer controls. + * 0.0.6 + * Use separate card based DMA buffer for periods table list. + * 0.0.7 + * Change remove and rename ctrls into lists. + * 0.0.8 + * Try to fix capture sources. + * 0.0.9 + * Fix AC3 output. + * Enable S32_LE format support. + * 0.0.10 + * Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".) + * 0.0.11 + * Add Model name recognition. + * 0.0.12 + * Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period. + * Remove redundent "voice" handling. + * 0.0.13 + * Single trigger call for multi channels. + * 0.0.14 + * Set limits based on what the sound card hardware can do. + * playback periods_min=2, periods_max=8 + * capture hw constraints require period_size = n * 64 bytes. + * playback hw constraints require period_size = n * 64 bytes. + * 0.0.15 + * Separated ca0106.c into separate functional .c files. + * 0.0.16 + * Modified Copyright message. + * 0.0.17 + * Implement Mic and Line in Capture. + * 0.0.18 + * Add support for mute control on SB Live 24bit (cards w/ SPI DAC) + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ca0106.h" + +static void ca0106_spdif_enable(struct snd_ca0106 *emu) +{ + unsigned int val; + + if (emu->spdif_enable) { + /* Digital */ + snd_ca0106_ptr_write(emu, SPDIF_SELECT1, 0, 0xf); + snd_ca0106_ptr_write(emu, SPDIF_SELECT2, 0, 0x0b000000); + val = snd_ca0106_ptr_read(emu, CAPTURE_CONTROL, 0) & ~0x1000; + snd_ca0106_ptr_write(emu, CAPTURE_CONTROL, 0, val); + val = inl(emu->port + CA0106_GPIO) & ~0x101; + outl(val, emu->port + CA0106_GPIO); + + } else { + /* Analog */ + snd_ca0106_ptr_write(emu, SPDIF_SELECT1, 0, 0xf); + snd_ca0106_ptr_write(emu, SPDIF_SELECT2, 0, 0x000f0000); + val = snd_ca0106_ptr_read(emu, CAPTURE_CONTROL, 0) | 0x1000; + snd_ca0106_ptr_write(emu, CAPTURE_CONTROL, 0, val); + val = inl(emu->port + CA0106_GPIO) | 0x101; + outl(val, emu->port + CA0106_GPIO); + } +} + +static void ca0106_set_capture_source(struct snd_ca0106 *emu) +{ + unsigned int val = emu->capture_source; + unsigned int source, mask; + source = (val << 28) | (val << 24) | (val << 20) | (val << 16); + mask = snd_ca0106_ptr_read(emu, CAPTURE_SOURCE, 0) & 0xffff; + snd_ca0106_ptr_write(emu, CAPTURE_SOURCE, 0, source | mask); +} + +static void ca0106_set_i2c_capture_source(struct snd_ca0106 *emu, + unsigned int val, int force) +{ + unsigned int ngain, ogain; + u32 source; + + snd_ca0106_i2c_write(emu, ADC_MUX, 0); /* Mute input */ + ngain = emu->i2c_capture_volume[val][0]; /* Left */ + ogain = emu->i2c_capture_volume[emu->i2c_capture_source][0]; /* Left */ + if (force || ngain != ogain) + snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCL, ngain & 0xff); + ngain = emu->i2c_capture_volume[val][1]; /* Right */ + ogain = emu->i2c_capture_volume[emu->i2c_capture_source][1]; /* Right */ + if (force || ngain != ogain) + snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCR, ngain & 0xff); + source = 1 << val; + snd_ca0106_i2c_write(emu, ADC_MUX, source); /* Set source */ + emu->i2c_capture_source = val; +} + +static void ca0106_set_capture_mic_line_in(struct snd_ca0106 *emu) +{ + u32 tmp; + + if (emu->capture_mic_line_in) { + /* snd_ca0106_i2c_write(emu, ADC_MUX, 0); */ /* Mute input */ + tmp = inl(emu->port + CA0106_GPIO) & ~0x400; + tmp = tmp | 0x400; + outl(tmp, emu->port + CA0106_GPIO); + /* snd_ca0106_i2c_write(emu, ADC_MUX, ADC_MUX_MIC); */ + } else { + /* snd_ca0106_i2c_write(emu, ADC_MUX, 0); */ /* Mute input */ + tmp = inl(emu->port + CA0106_GPIO) & ~0x400; + outl(tmp, emu->port + CA0106_GPIO); + /* snd_ca0106_i2c_write(emu, ADC_MUX, ADC_MUX_LINEIN); */ + } +} + +static void ca0106_set_spdif_bits(struct snd_ca0106 *emu, int idx) +{ + snd_ca0106_ptr_write(emu, SPCS0 + idx, 0, emu->spdif_str_bits[idx]); +} + +/* + */ +static const DECLARE_TLV_DB_SCALE(snd_ca0106_db_scale1, -5175, 25, 1); +static const DECLARE_TLV_DB_SCALE(snd_ca0106_db_scale2, -10350, 50, 1); + +#define snd_ca0106_shared_spdif_info snd_ctl_boolean_mono_info + +static int snd_ca0106_shared_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = emu->spdif_enable; + return 0; +} + +static int snd_ca0106_shared_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + + val = !!ucontrol->value.integer.value[0]; + change = (emu->spdif_enable != val); + if (change) { + emu->spdif_enable = val; + ca0106_spdif_enable(emu); + } + return change; +} + +static int snd_ca0106_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[6] = { + "IEC958 out", "i2s mixer out", "IEC958 in", "i2s in", "AC97 in", "SRC out" + }; + + return snd_ctl_enum_info(uinfo, 1, 6, texts); +} + +static int snd_ca0106_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->capture_source; + return 0; +} + +static int snd_ca0106_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + + val = ucontrol->value.enumerated.item[0] ; + if (val >= 6) + return -EINVAL; + change = (emu->capture_source != val); + if (change) { + emu->capture_source = val; + ca0106_set_capture_source(emu); + } + return change; +} + +static int snd_ca0106_i2c_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "Phone", "Mic", "Line in", "Aux" + }; + + return snd_ctl_enum_info(uinfo, 1, 4, texts); +} + +static int snd_ca0106_i2c_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->i2c_capture_source; + return 0; +} + +static int snd_ca0106_i2c_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int source_id; + int change = 0; + /* If the capture source has changed, + * update the capture volume from the cached value + * for the particular source. + */ + source_id = ucontrol->value.enumerated.item[0] ; + if (source_id >= 4) + return -EINVAL; + change = (emu->i2c_capture_source != source_id); + if (change) { + ca0106_set_i2c_capture_source(emu, source_id, 0); + } + return change; +} + +static int snd_ca0106_capture_line_in_side_out_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "Side out", "Line in" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_ca0106_capture_mic_line_in_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "Line in", "Mic in" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_ca0106_capture_mic_line_in_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->capture_mic_line_in; + return 0; +} + +static int snd_ca0106_capture_mic_line_in_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + + val = ucontrol->value.enumerated.item[0] ; + if (val > 1) + return -EINVAL; + change = (emu->capture_mic_line_in != val); + if (change) { + emu->capture_mic_line_in = val; + ca0106_set_capture_mic_line_in(emu); + } + return change; +} + +static const struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Shared Mic/Line in Capture Switch", + .info = snd_ca0106_capture_mic_line_in_info, + .get = snd_ca0106_capture_mic_line_in_get, + .put = snd_ca0106_capture_mic_line_in_put +}; + +static const struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Shared Line in/Side out Capture Switch", + .info = snd_ca0106_capture_line_in_side_out_info, + .get = snd_ca0106_capture_mic_line_in_get, + .put = snd_ca0106_capture_mic_line_in_put +}; + + +static int snd_ca0106_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static void decode_spdif_bits(unsigned char *status, unsigned int bits) +{ + status[0] = (bits >> 0) & 0xff; + status[1] = (bits >> 8) & 0xff; + status[2] = (bits >> 16) & 0xff; + status[3] = (bits >> 24) & 0xff; +} + +static int snd_ca0106_spdif_get_default(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + decode_spdif_bits(ucontrol->value.iec958.status, + emu->spdif_bits[idx]); + return 0; +} + +static int snd_ca0106_spdif_get_stream(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + decode_spdif_bits(ucontrol->value.iec958.status, + emu->spdif_str_bits[idx]); + return 0; +} + +static int snd_ca0106_spdif_get_mask(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static unsigned int encode_spdif_bits(unsigned char *status) +{ + return ((unsigned int)status[0] << 0) | + ((unsigned int)status[1] << 8) | + ((unsigned int)status[2] << 16) | + ((unsigned int)status[3] << 24); +} + +static int snd_ca0106_spdif_put_default(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned int val; + + val = encode_spdif_bits(ucontrol->value.iec958.status); + if (val != emu->spdif_bits[idx]) { + emu->spdif_bits[idx] = val; + /* FIXME: this isn't safe, but needed to keep the compatibility + * with older alsa-lib config + */ + emu->spdif_str_bits[idx] = val; + ca0106_set_spdif_bits(emu, idx); + return 1; + } + return 0; +} + +static int snd_ca0106_spdif_put_stream(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned int val; + + val = encode_spdif_bits(ucontrol->value.iec958.status); + if (val != emu->spdif_str_bits[idx]) { + emu->spdif_str_bits[idx] = val; + ca0106_set_spdif_bits(emu, idx); + return 1; + } + return 0; +} + +static int snd_ca0106_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 = 255; + return 0; +} + +static int snd_ca0106_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int value; + int channel_id, reg; + + channel_id = (kcontrol->private_value >> 8) & 0xff; + reg = kcontrol->private_value & 0xff; + + value = snd_ca0106_ptr_read(emu, reg, channel_id); + ucontrol->value.integer.value[0] = 0xff - ((value >> 24) & 0xff); /* Left */ + ucontrol->value.integer.value[1] = 0xff - ((value >> 16) & 0xff); /* Right */ + return 0; +} + +static int snd_ca0106_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int oval, nval; + int channel_id, reg; + + channel_id = (kcontrol->private_value >> 8) & 0xff; + reg = kcontrol->private_value & 0xff; + + oval = snd_ca0106_ptr_read(emu, reg, channel_id); + nval = ((0xff - ucontrol->value.integer.value[0]) << 24) | + ((0xff - ucontrol->value.integer.value[1]) << 16); + nval |= ((0xff - ucontrol->value.integer.value[0]) << 8) | + ((0xff - ucontrol->value.integer.value[1]) ); + if (oval == nval) + return 0; + snd_ca0106_ptr_write(emu, reg, channel_id, nval); + return 1; +} + +static int snd_ca0106_i2c_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 = 255; + return 0; +} + +static int snd_ca0106_i2c_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + int source_id; + + source_id = kcontrol->private_value; + + ucontrol->value.integer.value[0] = emu->i2c_capture_volume[source_id][0]; + ucontrol->value.integer.value[1] = emu->i2c_capture_volume[source_id][1]; + return 0; +} + +static int snd_ca0106_i2c_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int ogain; + unsigned int ngain; + int source_id; + int change = 0; + + source_id = kcontrol->private_value; + ogain = emu->i2c_capture_volume[source_id][0]; /* Left */ + ngain = ucontrol->value.integer.value[0]; + if (ngain > 0xff) + return -EINVAL; + if (ogain != ngain) { + if (emu->i2c_capture_source == source_id) + snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCL, ((ngain) & 0xff) ); + emu->i2c_capture_volume[source_id][0] = ucontrol->value.integer.value[0]; + change = 1; + } + ogain = emu->i2c_capture_volume[source_id][1]; /* Right */ + ngain = ucontrol->value.integer.value[1]; + if (ngain > 0xff) + return -EINVAL; + if (ogain != ngain) { + if (emu->i2c_capture_source == source_id) + snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCR, ((ngain) & 0xff)); + emu->i2c_capture_volume[source_id][1] = ucontrol->value.integer.value[1]; + change = 1; + } + + return change; +} + +#define spi_mute_info snd_ctl_boolean_mono_info + +static int spi_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int reg = kcontrol->private_value >> SPI_REG_SHIFT; + unsigned int bit = kcontrol->private_value & SPI_REG_MASK; + + ucontrol->value.integer.value[0] = !(emu->spi_dac_reg[reg] & bit); + return 0; +} + +static int spi_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol); + unsigned int reg = kcontrol->private_value >> SPI_REG_SHIFT; + unsigned int bit = kcontrol->private_value & SPI_REG_MASK; + int ret; + + ret = emu->spi_dac_reg[reg] & bit; + if (ucontrol->value.integer.value[0]) { + if (!ret) /* bit already cleared, do nothing */ + return 0; + emu->spi_dac_reg[reg] &= ~bit; + } else { + if (ret) /* bit already set, do nothing */ + return 0; + emu->spi_dac_reg[reg] |= bit; + } + + ret = snd_ca0106_spi_write(emu, emu->spi_dac_reg[reg]); + return ret ? -EINVAL : 1; +} + +#define CA_VOLUME(xname,chid,reg) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = snd_ca0106_volume_info, \ + .get = snd_ca0106_volume_get, \ + .put = snd_ca0106_volume_put, \ + .tlv = { .p = snd_ca0106_db_scale1 }, \ + .private_value = ((chid) << 8) | (reg) \ +} + +static const struct snd_kcontrol_new snd_ca0106_volume_ctls[] = { + CA_VOLUME("Analog Front Playback Volume", + CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME2), + CA_VOLUME("Analog Rear Playback Volume", + CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME2), + CA_VOLUME("Analog Center/LFE Playback Volume", + CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME2), + CA_VOLUME("Analog Side Playback Volume", + CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME2), + + CA_VOLUME("IEC958 Front Playback Volume", + CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME1), + CA_VOLUME("IEC958 Rear Playback Volume", + CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME1), + CA_VOLUME("IEC958 Center/LFE Playback Volume", + CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME1), + CA_VOLUME("IEC958 Unknown Playback Volume", + CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME1), + + CA_VOLUME("CAPTURE feedback Playback Volume", + 1, CAPTURE_CONTROL), + + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .count = 4, + .info = snd_ca0106_spdif_info, + .get = snd_ca0106_spdif_get_mask + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Switch", + .info = snd_ca0106_shared_spdif_info, + .get = snd_ca0106_shared_spdif_get, + .put = snd_ca0106_shared_spdif_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Source Capture Enum", + .info = snd_ca0106_capture_source_info, + .get = snd_ca0106_capture_source_get, + .put = snd_ca0106_capture_source_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Source Capture Enum", + .info = snd_ca0106_i2c_capture_source_info, + .get = snd_ca0106_i2c_capture_source_get, + .put = snd_ca0106_i2c_capture_source_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .count = 4, + .info = snd_ca0106_spdif_info, + .get = snd_ca0106_spdif_get_default, + .put = snd_ca0106_spdif_put_default + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .count = 4, + .info = snd_ca0106_spdif_info, + .get = snd_ca0106_spdif_get_stream, + .put = snd_ca0106_spdif_put_stream + }, +}; + +#define I2C_VOLUME(xname,chid) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = snd_ca0106_i2c_volume_info, \ + .get = snd_ca0106_i2c_volume_get, \ + .put = snd_ca0106_i2c_volume_put, \ + .tlv = { .p = snd_ca0106_db_scale2 }, \ + .private_value = chid \ +} + +static const struct snd_kcontrol_new snd_ca0106_volume_i2c_adc_ctls[] = { + I2C_VOLUME("Phone Capture Volume", 0), + I2C_VOLUME("Mic Capture Volume", 1), + I2C_VOLUME("Line in Capture Volume", 2), + I2C_VOLUME("Aux Capture Volume", 3), +}; + +static const int spi_dmute_reg[] = { + SPI_DMUTE0_REG, + SPI_DMUTE1_REG, + SPI_DMUTE2_REG, + 0, + SPI_DMUTE4_REG, +}; +static const int spi_dmute_bit[] = { + SPI_DMUTE0_BIT, + SPI_DMUTE1_BIT, + SPI_DMUTE2_BIT, + 0, + SPI_DMUTE4_BIT, +}; + +static struct snd_kcontrol_new +snd_ca0106_volume_spi_dac_ctl(const struct snd_ca0106_details *details, + int channel_id) +{ + struct snd_kcontrol_new spi_switch = {0}; + int reg, bit; + int dac_id; + + spi_switch.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + spi_switch.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + spi_switch.info = spi_mute_info; + spi_switch.get = spi_mute_get; + spi_switch.put = spi_mute_put; + + switch (channel_id) { + case PCM_FRONT_CHANNEL: + spi_switch.name = "Analog Front Playback Switch"; + dac_id = (details->spi_dac & 0xf000) >> (4 * 3); + break; + case PCM_REAR_CHANNEL: + spi_switch.name = "Analog Rear Playback Switch"; + dac_id = (details->spi_dac & 0x0f00) >> (4 * 2); + break; + case PCM_CENTER_LFE_CHANNEL: + spi_switch.name = "Analog Center/LFE Playback Switch"; + dac_id = (details->spi_dac & 0x00f0) >> (4 * 1); + break; + case PCM_UNKNOWN_CHANNEL: + spi_switch.name = "Analog Side Playback Switch"; + dac_id = (details->spi_dac & 0x000f) >> (4 * 0); + break; + default: + /* Unused channel */ + spi_switch.name = NULL; + dac_id = 0; + } + reg = spi_dmute_reg[dac_id]; + bit = spi_dmute_bit[dac_id]; + + spi_switch.private_value = (reg << SPI_REG_SHIFT) | bit; + + return spi_switch; +} + +static int remove_ctl(struct snd_card *card, const char *name) +{ + struct snd_ctl_elem_id id; + memset(&id, 0, sizeof(id)); + strscpy(id.name, name); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + return snd_ctl_remove_id(card, &id); +} + +static int rename_ctl(struct snd_card *card, const char *src, const char *dst) +{ + struct snd_kcontrol *kctl = snd_ctl_find_id_mixer(card, src); + if (kctl) { + snd_ctl_rename(card, kctl, dst); + return 0; + } + return -ENOENT; +} + +#define ADD_CTLS(emu, ctls) \ + do { \ + int i, _err; \ + for (i = 0; i < ARRAY_SIZE(ctls); i++) { \ + _err = snd_ctl_add(card, snd_ctl_new1(&ctls[i], emu)); \ + if (_err < 0) \ + return _err; \ + } \ + } while (0) + +static +DECLARE_TLV_DB_SCALE(snd_ca0106_master_db_scale, -6375, 25, 1); + +static const char * const follower_vols[] = { + "Analog Front Playback Volume", + "Analog Rear Playback Volume", + "Analog Center/LFE Playback Volume", + "Analog Side Playback Volume", + "IEC958 Front Playback Volume", + "IEC958 Rear Playback Volume", + "IEC958 Center/LFE Playback Volume", + "IEC958 Unknown Playback Volume", + "CAPTURE feedback Playback Volume", + NULL +}; + +static const char * const follower_sws[] = { + "Analog Front Playback Switch", + "Analog Rear Playback Switch", + "Analog Center/LFE Playback Switch", + "Analog Side Playback Switch", + "IEC958 Playback Switch", + NULL +}; + +int snd_ca0106_mixer(struct snd_ca0106 *emu) +{ + int err; + struct snd_card *card = emu->card; + const char * const *c; + struct snd_kcontrol *vmaster; + static const char * const ca0106_remove_ctls[] = { + "Master Mono Playback Switch", + "Master Mono Playback Volume", + "3D Control - Switch", + "3D Control Sigmatel - Depth", + "PCM Playback Switch", + "PCM Playback Volume", + "CD Playback Switch", + "CD Playback Volume", + "Phone Playback Switch", + "Phone Playback Volume", + "Video Playback Switch", + "Video Playback Volume", + "Beep Playback Switch", + "Beep Playback Volume", + "Mono Output Select", + "Capture Source", + "Capture Switch", + "Capture Volume", + "External Amplifier", + "Sigmatel 4-Speaker Stereo Playback Switch", + "Surround Phase Inversion Playback Switch", + NULL + }; + static const char * const ca0106_rename_ctls[] = { + "Master Playback Switch", "Capture Switch", + "Master Playback Volume", "Capture Volume", + "Line Playback Switch", "AC97 Line Capture Switch", + "Line Playback Volume", "AC97 Line Capture Volume", + "Aux Playback Switch", "AC97 Aux Capture Switch", + "Aux Playback Volume", "AC97 Aux Capture Volume", + "Mic Playback Switch", "AC97 Mic Capture Switch", + "Mic Playback Volume", "AC97 Mic Capture Volume", + "Mic Select", "AC97 Mic Select", + "Mic Boost (+20dB)", "AC97 Mic Boost (+20dB)", + NULL + }; +#if 1 + for (c = ca0106_remove_ctls; *c; c++) + remove_ctl(card, *c); + for (c = ca0106_rename_ctls; *c; c += 2) + rename_ctl(card, c[0], c[1]); +#endif + + ADD_CTLS(emu, snd_ca0106_volume_ctls); + if (emu->details->i2c_adc == 1) { + ADD_CTLS(emu, snd_ca0106_volume_i2c_adc_ctls); + if (emu->details->gpio_type == 1) + err = snd_ctl_add(card, snd_ctl_new1(&snd_ca0106_capture_mic_line_in, emu)); + else /* gpio_type == 2 */ + err = snd_ctl_add(card, snd_ctl_new1(&snd_ca0106_capture_line_in_side_out, emu)); + if (err < 0) + return err; + } + if (emu->details->spi_dac) { + int i; + for (i = 0;; i++) { + struct snd_kcontrol_new ctl; + ctl = snd_ca0106_volume_spi_dac_ctl(emu->details, i); + if (!ctl.name) + break; + err = snd_ctl_add(card, snd_ctl_new1(&ctl, emu)); + if (err < 0) + return err; + } + } + + /* Create virtual master controls */ + vmaster = snd_ctl_make_virtual_master("Master Playback Volume", + snd_ca0106_master_db_scale); + if (!vmaster) + return -ENOMEM; + err = snd_ctl_add(card, vmaster); + if (err < 0) + return err; + err = snd_ctl_add_followers(card, vmaster, follower_vols); + if (err < 0) + return err; + + if (emu->details->spi_dac) { + vmaster = snd_ctl_make_virtual_master("Master Playback Switch", + NULL); + if (!vmaster) + return -ENOMEM; + err = snd_ctl_add(card, vmaster); + if (err < 0) + return err; + err = snd_ctl_add_followers(card, vmaster, follower_sws); + if (err < 0) + return err; + } + + strscpy(card->mixername, "CA0106"); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +struct ca0106_vol_tbl { + unsigned int channel_id; + unsigned int reg; +}; + +static const struct ca0106_vol_tbl saved_volumes[NUM_SAVED_VOLUMES] = { + { CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME2 }, + { CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME2 }, + { CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME2 }, + { CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME2 }, + { CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME1 }, + { CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME1 }, + { CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME1 }, + { CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME1 }, + { 1, CAPTURE_CONTROL }, +}; + +void snd_ca0106_mixer_suspend(struct snd_ca0106 *chip) +{ + int i; + + /* save volumes */ + for (i = 0; i < NUM_SAVED_VOLUMES; i++) + chip->saved_vol[i] = + snd_ca0106_ptr_read(chip, saved_volumes[i].reg, + saved_volumes[i].channel_id); +} + +void snd_ca0106_mixer_resume(struct snd_ca0106 *chip) +{ + int i; + + for (i = 0; i < NUM_SAVED_VOLUMES; i++) + snd_ca0106_ptr_write(chip, saved_volumes[i].reg, + saved_volumes[i].channel_id, + chip->saved_vol[i]); + + ca0106_spdif_enable(chip); + ca0106_set_capture_source(chip); + ca0106_set_i2c_capture_source(chip, chip->i2c_capture_source, 1); + for (i = 0; i < 4; i++) + ca0106_set_spdif_bits(chip, i); + if (chip->details->i2c_adc) + ca0106_set_capture_mic_line_in(chip); +} +#endif /* CONFIG_PM_SLEEP */ diff --git a/sound/pci/ca0106/ca0106_proc.c b/sound/pci/ca0106/ca0106_proc.c new file mode 100644 index 000000000..c181e4954 --- /dev/null +++ b/sound/pci/ca0106/ca0106_proc.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2004 James Courtier-Dutton + * Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit + * Version: 0.0.18 + * + * FEATURES currently supported: + * See ca0106_main.c for features. + * + * Changelog: + * Support interrupts per period. + * Removed noise from Center/LFE channel when in Analog mode. + * Rename and remove mixer controls. + * 0.0.6 + * Use separate card based DMA buffer for periods table list. + * 0.0.7 + * Change remove and rename ctrls into lists. + * 0.0.8 + * Try to fix capture sources. + * 0.0.9 + * Fix AC3 output. + * Enable S32_LE format support. + * 0.0.10 + * Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".) + * 0.0.11 + * Add Model name recognition. + * 0.0.12 + * Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period. + * Remove redundent "voice" handling. + * 0.0.13 + * Single trigger call for multi channels. + * 0.0.14 + * Set limits based on what the sound card hardware can do. + * playback periods_min=2, periods_max=8 + * capture hw constraints require period_size = n * 64 bytes. + * playback hw constraints require period_size = n * 64 bytes. + * 0.0.15 + * Separate ca0106.c into separate functional .c files. + * 0.0.16 + * Modified Copyright message. + * 0.0.17 + * Add iec958 file in proc file system to show status of SPDIF in. + * 0.0.18 + * Implement support for Line-in capture on SB Live 24bit. + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ca0106.h" + + +struct snd_ca0106_category_str { + int val; + const char *name; +}; + +static const struct snd_ca0106_category_str snd_ca0106_con_category[] = { + { IEC958_AES1_CON_DAT, "DAT" }, + { IEC958_AES1_CON_VCR, "VCR" }, + { IEC958_AES1_CON_MICROPHONE, "microphone" }, + { IEC958_AES1_CON_SYNTHESIZER, "synthesizer" }, + { IEC958_AES1_CON_RATE_CONVERTER, "rate converter" }, + { IEC958_AES1_CON_MIXER, "mixer" }, + { IEC958_AES1_CON_SAMPLER, "sampler" }, + { IEC958_AES1_CON_PCM_CODER, "PCM coder" }, + { IEC958_AES1_CON_IEC908_CD, "CD" }, + { IEC958_AES1_CON_NON_IEC908_CD, "non-IEC908 CD" }, + { IEC958_AES1_CON_GENERAL, "general" }, +}; + + +static void snd_ca0106_proc_dump_iec958( struct snd_info_buffer *buffer, u32 value) +{ + int i; + u32 status[4]; + status[0] = value & 0xff; + status[1] = (value >> 8) & 0xff; + status[2] = (value >> 16) & 0xff; + status[3] = (value >> 24) & 0xff; + + if (! (status[0] & IEC958_AES0_PROFESSIONAL)) { + /* consumer */ + snd_iprintf(buffer, "Mode: consumer\n"); + snd_iprintf(buffer, "Data: "); + if (!(status[0] & IEC958_AES0_NONAUDIO)) { + snd_iprintf(buffer, "audio\n"); + } else { + snd_iprintf(buffer, "non-audio\n"); + } + snd_iprintf(buffer, "Rate: "); + switch (status[3] & IEC958_AES3_CON_FS) { + case IEC958_AES3_CON_FS_44100: + snd_iprintf(buffer, "44100 Hz\n"); + break; + case IEC958_AES3_CON_FS_48000: + snd_iprintf(buffer, "48000 Hz\n"); + break; + case IEC958_AES3_CON_FS_32000: + snd_iprintf(buffer, "32000 Hz\n"); + break; + default: + snd_iprintf(buffer, "unknown\n"); + break; + } + snd_iprintf(buffer, "Copyright: "); + if (status[0] & IEC958_AES0_CON_NOT_COPYRIGHT) { + snd_iprintf(buffer, "permitted\n"); + } else { + snd_iprintf(buffer, "protected\n"); + } + snd_iprintf(buffer, "Emphasis: "); + if ((status[0] & IEC958_AES0_CON_EMPHASIS) != IEC958_AES0_CON_EMPHASIS_5015) { + snd_iprintf(buffer, "none\n"); + } else { + snd_iprintf(buffer, "50/15us\n"); + } + snd_iprintf(buffer, "Category: "); + for (i = 0; i < ARRAY_SIZE(snd_ca0106_con_category); i++) { + if ((status[1] & IEC958_AES1_CON_CATEGORY) == snd_ca0106_con_category[i].val) { + snd_iprintf(buffer, "%s\n", snd_ca0106_con_category[i].name); + break; + } + } + if (i >= ARRAY_SIZE(snd_ca0106_con_category)) { + snd_iprintf(buffer, "unknown 0x%x\n", status[1] & IEC958_AES1_CON_CATEGORY); + } + snd_iprintf(buffer, "Original: "); + if (status[1] & IEC958_AES1_CON_ORIGINAL) { + snd_iprintf(buffer, "original\n"); + } else { + snd_iprintf(buffer, "1st generation\n"); + } + snd_iprintf(buffer, "Clock: "); + switch (status[3] & IEC958_AES3_CON_CLOCK) { + case IEC958_AES3_CON_CLOCK_1000PPM: + snd_iprintf(buffer, "1000 ppm\n"); + break; + case IEC958_AES3_CON_CLOCK_50PPM: + snd_iprintf(buffer, "50 ppm\n"); + break; + case IEC958_AES3_CON_CLOCK_VARIABLE: + snd_iprintf(buffer, "variable pitch\n"); + break; + default: + snd_iprintf(buffer, "unknown\n"); + break; + } + } else { + snd_iprintf(buffer, "Mode: professional\n"); + snd_iprintf(buffer, "Data: "); + if (!(status[0] & IEC958_AES0_NONAUDIO)) { + snd_iprintf(buffer, "audio\n"); + } else { + snd_iprintf(buffer, "non-audio\n"); + } + snd_iprintf(buffer, "Rate: "); + switch (status[0] & IEC958_AES0_PRO_FS) { + case IEC958_AES0_PRO_FS_44100: + snd_iprintf(buffer, "44100 Hz\n"); + break; + case IEC958_AES0_PRO_FS_48000: + snd_iprintf(buffer, "48000 Hz\n"); + break; + case IEC958_AES0_PRO_FS_32000: + snd_iprintf(buffer, "32000 Hz\n"); + break; + default: + snd_iprintf(buffer, "unknown\n"); + break; + } + snd_iprintf(buffer, "Rate Locked: "); + if (status[0] & IEC958_AES0_PRO_FREQ_UNLOCKED) + snd_iprintf(buffer, "no\n"); + else + snd_iprintf(buffer, "yes\n"); + snd_iprintf(buffer, "Emphasis: "); + switch (status[0] & IEC958_AES0_PRO_EMPHASIS) { + case IEC958_AES0_PRO_EMPHASIS_CCITT: + snd_iprintf(buffer, "CCITT J.17\n"); + break; + case IEC958_AES0_PRO_EMPHASIS_NONE: + snd_iprintf(buffer, "none\n"); + break; + case IEC958_AES0_PRO_EMPHASIS_5015: + snd_iprintf(buffer, "50/15us\n"); + break; + case IEC958_AES0_PRO_EMPHASIS_NOTID: + default: + snd_iprintf(buffer, "unknown\n"); + break; + } + snd_iprintf(buffer, "Stereophonic: "); + if ((status[1] & IEC958_AES1_PRO_MODE) == IEC958_AES1_PRO_MODE_STEREOPHONIC) { + snd_iprintf(buffer, "stereo\n"); + } else { + snd_iprintf(buffer, "not indicated\n"); + } + snd_iprintf(buffer, "Userbits: "); + switch (status[1] & IEC958_AES1_PRO_USERBITS) { + case IEC958_AES1_PRO_USERBITS_192: + snd_iprintf(buffer, "192bit\n"); + break; + case IEC958_AES1_PRO_USERBITS_UDEF: + snd_iprintf(buffer, "user-defined\n"); + break; + default: + snd_iprintf(buffer, "unknown\n"); + break; + } + snd_iprintf(buffer, "Sample Bits: "); + switch (status[2] & IEC958_AES2_PRO_SBITS) { + case IEC958_AES2_PRO_SBITS_20: + snd_iprintf(buffer, "20 bit\n"); + break; + case IEC958_AES2_PRO_SBITS_24: + snd_iprintf(buffer, "24 bit\n"); + break; + case IEC958_AES2_PRO_SBITS_UDEF: + snd_iprintf(buffer, "user defined\n"); + break; + default: + snd_iprintf(buffer, "unknown\n"); + break; + } + snd_iprintf(buffer, "Word Length: "); + switch (status[2] & IEC958_AES2_PRO_WORDLEN) { + case IEC958_AES2_PRO_WORDLEN_22_18: + snd_iprintf(buffer, "22 bit or 18 bit\n"); + break; + case IEC958_AES2_PRO_WORDLEN_23_19: + snd_iprintf(buffer, "23 bit or 19 bit\n"); + break; + case IEC958_AES2_PRO_WORDLEN_24_20: + snd_iprintf(buffer, "24 bit or 20 bit\n"); + break; + case IEC958_AES2_PRO_WORDLEN_20_16: + snd_iprintf(buffer, "20 bit or 16 bit\n"); + break; + default: + snd_iprintf(buffer, "unknown\n"); + break; + } + } +} + +static void snd_ca0106_proc_iec958(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + u32 value; + + value = snd_ca0106_ptr_read(emu, SAMPLE_RATE_TRACKER_STATUS, 0); + snd_iprintf(buffer, "Status: %s, %s, %s\n", + (value & 0x100000) ? "Rate Locked" : "Not Rate Locked", + (value & 0x200000) ? "SPDIF Locked" : "No SPDIF Lock", + (value & 0x400000) ? "Audio Valid" : "No valid audio" ); + snd_iprintf(buffer, "Estimated sample rate: %u\n", + ((value & 0xfffff) * 48000) / 0x8000 ); + if (value & 0x200000) { + snd_iprintf(buffer, "IEC958/SPDIF input status:\n"); + value = snd_ca0106_ptr_read(emu, SPDIF_INPUT_STATUS, 0); + snd_ca0106_proc_dump_iec958(buffer, value); + } + + snd_iprintf(buffer, "\n"); +} + +static void snd_ca0106_proc_reg_write32(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + char line[64]; + u32 reg, val; + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x", ®, &val) != 2) + continue; + if (reg < 0x40 && val <= 0xffffffff) { + guard(spinlock_irqsave)(&emu->emu_lock); + outl(val, emu->port + (reg & 0xfffffffc)); + } + } +} + +static void snd_ca0106_proc_reg_read32(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + unsigned long value; + int i; + + snd_iprintf(buffer, "Registers:\n\n"); + for(i = 0; i < 0x20; i+=4) { + scoped_guard(spinlock_irqsave, &emu->emu_lock) { + value = inl(emu->port + i); + } + snd_iprintf(buffer, "Register %02X: %08lX\n", i, value); + } +} + +static void snd_ca0106_proc_reg_read16(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + unsigned int value; + int i; + + snd_iprintf(buffer, "Registers:\n\n"); + for(i = 0; i < 0x20; i+=2) { + scoped_guard(spinlock_irqsave, &emu->emu_lock) { + value = inw(emu->port + i); + } + snd_iprintf(buffer, "Register %02X: %04X\n", i, value); + } +} + +static void snd_ca0106_proc_reg_read8(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + unsigned int value; + int i; + + snd_iprintf(buffer, "Registers:\n\n"); + for(i = 0; i < 0x20; i+=1) { + scoped_guard(spinlock_irqsave, &emu->emu_lock) { + value = inb(emu->port + i); + } + snd_iprintf(buffer, "Register %02X: %02X\n", i, value); + } +} + +static void snd_ca0106_proc_reg_read1(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + unsigned long value; + int i,j; + + snd_iprintf(buffer, "Registers\n"); + for(i = 0; i < 0x40; i++) { + snd_iprintf(buffer, "%02X: ",i); + for (j = 0; j < 4; j++) { + value = snd_ca0106_ptr_read(emu, i, j); + snd_iprintf(buffer, "%08lX ", value); + } + snd_iprintf(buffer, "\n"); + } +} + +static void snd_ca0106_proc_reg_read2(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + unsigned long value; + int i,j; + + snd_iprintf(buffer, "Registers\n"); + for(i = 0x40; i < 0x80; i++) { + snd_iprintf(buffer, "%02X: ",i); + for (j = 0; j < 4; j++) { + value = snd_ca0106_ptr_read(emu, i, j); + snd_iprintf(buffer, "%08lX ", value); + } + snd_iprintf(buffer, "\n"); + } +} + +static void snd_ca0106_proc_reg_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + char line[64]; + unsigned int reg, channel_id , val; + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x %x", ®, &channel_id, &val) != 3) + continue; + if (reg < 0x80 && val <= 0xffffffff && channel_id <= 3) + snd_ca0106_ptr_write(emu, reg, channel_id, val); + } +} + +static void snd_ca0106_proc_i2c_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + char line[64]; + unsigned int reg, val; + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x", ®, &val) != 2) + continue; + if ((reg <= 0x7f) || (val <= 0x1ff)) { + snd_ca0106_i2c_write(emu, reg, val); + } + } +} + +int snd_ca0106_proc_init(struct snd_ca0106 *emu) +{ + snd_card_ro_proc_new(emu->card, "iec958", emu, snd_ca0106_proc_iec958); + snd_card_rw_proc_new(emu->card, "ca0106_reg32", emu, + snd_ca0106_proc_reg_read32, + snd_ca0106_proc_reg_write32); + snd_card_ro_proc_new(emu->card, "ca0106_reg16", emu, + snd_ca0106_proc_reg_read16); + snd_card_ro_proc_new(emu->card, "ca0106_reg8", emu, + snd_ca0106_proc_reg_read8); + snd_card_rw_proc_new(emu->card, "ca0106_regs1", emu, + snd_ca0106_proc_reg_read1, + snd_ca0106_proc_reg_write); + snd_card_rw_proc_new(emu->card, "ca0106_i2c", emu, NULL, + snd_ca0106_proc_i2c_write); + snd_card_ro_proc_new(emu->card, "ca0106_regs2", emu, + snd_ca0106_proc_reg_read2); + return 0; +} diff --git a/sound/pci/ca0106/ca_midi.c b/sound/pci/ca0106/ca_midi.c new file mode 100644 index 000000000..6efd93abd --- /dev/null +++ b/sound/pci/ca0106/ca_midi.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 10/16/2005 Tilman Kranz + * Creative Audio MIDI, for the CA0106 Driver + * Version: 0.0.1 + * + * Changelog: + * Implementation is based on mpu401 and emu10k1x and + * tested with ca0106. + * mpu401: Copyright (c) by Jaroslav Kysela + * emu10k1x: Copyright (c) by Francisco Moraes + */ + +#include +#include +#include + +#include "ca_midi.h" + +#define ca_midi_write_data(midi, data) midi->write(midi, data, 0) +#define ca_midi_write_cmd(midi, data) midi->write(midi, data, 1) +#define ca_midi_read_data(midi) midi->read(midi, 0) +#define ca_midi_read_stat(midi) midi->read(midi, 1) +#define ca_midi_input_avail(midi) (!(ca_midi_read_stat(midi) & midi->input_avail)) +#define ca_midi_output_ready(midi) (!(ca_midi_read_stat(midi) & midi->output_ready)) + +static void ca_midi_clear_rx(struct snd_ca_midi *midi) +{ + int timeout = 100000; + for (; timeout > 0 && ca_midi_input_avail(midi); timeout--) + ca_midi_read_data(midi); +#ifdef CONFIG_SND_DEBUG + if (timeout <= 0) + pr_err("ca_midi_clear_rx: timeout (status = 0x%x)\n", + ca_midi_read_stat(midi)); +#endif +} + +static void ca_midi_interrupt(struct snd_ca_midi *midi, unsigned int status) +{ + unsigned char byte; + + if (midi->rmidi == NULL) { + midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable); + return; + } + + scoped_guard(spinlock, &midi->input_lock) { + if ((status & midi->ipr_rx) && ca_midi_input_avail(midi)) { + if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) { + ca_midi_clear_rx(midi); + } else { + byte = ca_midi_read_data(midi); + if (midi->substream_input) + snd_rawmidi_receive(midi->substream_input, &byte, 1); + } + } + } + + scoped_guard(spinlock, &midi->output_lock) { + if ((status & midi->ipr_tx) && ca_midi_output_ready(midi)) { + if (midi->substream_output && + snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) { + ca_midi_write_data(midi, byte); + } else { + midi->interrupt_disable(midi, midi->tx_enable); + } + } + } +} + +static void ca_midi_cmd(struct snd_ca_midi *midi, unsigned char cmd, int ack) +{ + int timeout, ok; + + scoped_guard(spinlock_irqsave, &midi->input_lock) { + ca_midi_write_data(midi, 0x00); + /* ca_midi_clear_rx(midi); */ + + ca_midi_write_cmd(midi, cmd); + if (ack) { + ok = 0; + timeout = 10000; + while (!ok && timeout-- > 0) { + if (ca_midi_input_avail(midi)) { + if (ca_midi_read_data(midi) == midi->ack) + ok = 1; + } + } + if (!ok && ca_midi_read_data(midi) == midi->ack) + ok = 1; + } else { + ok = 1; + } + } + if (!ok) + pr_err("ca_midi_cmd: 0x%x failed at 0x%x (status = 0x%x, data = 0x%x)!!!\n", + cmd, + midi->get_dev_id_port(midi->dev_id), + ca_midi_read_stat(midi), + ca_midi_read_data(midi)); +} + +static int ca_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ca_midi *midi = substream->rmidi->private_data; + + if (snd_BUG_ON(!midi->dev_id)) + return -ENXIO; + scoped_guard(spinlock_irqsave, &midi->open_lock) { + midi->midi_mode |= CA_MIDI_MODE_INPUT; + midi->substream_input = substream; + if (midi->midi_mode & CA_MIDI_MODE_OUTPUT) + return 0; + } + ca_midi_cmd(midi, midi->reset, 1); + ca_midi_cmd(midi, midi->enter_uart, 1); + return 0; +} + +static int ca_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ca_midi *midi = substream->rmidi->private_data; + + if (snd_BUG_ON(!midi->dev_id)) + return -ENXIO; + scoped_guard(spinlock_irqsave, &midi->open_lock) { + midi->midi_mode |= CA_MIDI_MODE_OUTPUT; + midi->substream_output = substream; + if (midi->midi_mode & CA_MIDI_MODE_INPUT) + return 0; + } + ca_midi_cmd(midi, midi->reset, 1); + ca_midi_cmd(midi, midi->enter_uart, 1); + return 0; +} + +static int ca_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ca_midi *midi = substream->rmidi->private_data; + + if (snd_BUG_ON(!midi->dev_id)) + return -ENXIO; + scoped_guard(spinlock_irqsave, &midi->open_lock) { + midi->interrupt_disable(midi, midi->rx_enable); + midi->midi_mode &= ~CA_MIDI_MODE_INPUT; + midi->substream_input = NULL; + if (midi->midi_mode & CA_MIDI_MODE_OUTPUT) + return 0; + } + ca_midi_cmd(midi, midi->reset, 0); + return 0; +} + +static int ca_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ca_midi *midi = substream->rmidi->private_data; + + if (snd_BUG_ON(!midi->dev_id)) + return -ENXIO; + + scoped_guard(spinlock_irqsave, &midi->open_lock) { + midi->interrupt_disable(midi, midi->tx_enable); + midi->midi_mode &= ~CA_MIDI_MODE_OUTPUT; + midi->substream_output = NULL; + if (midi->midi_mode & CA_MIDI_MODE_INPUT) + return 0; + } + ca_midi_cmd(midi, midi->reset, 0); + return 0; +} + +static void ca_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_ca_midi *midi = substream->rmidi->private_data; + + if (snd_BUG_ON(!midi->dev_id)) + return; + + if (up) { + midi->interrupt_enable(midi,midi->rx_enable); + } else { + midi->interrupt_disable(midi, midi->rx_enable); + } +} + +static void ca_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_ca_midi *midi = substream->rmidi->private_data; + + if (snd_BUG_ON(!midi->dev_id)) + return; + + if (up) { + int max = 4; + unsigned char byte; + + scoped_guard(spinlock_irqsave, &midi->output_lock) { + + /* try to send some amount of bytes here before interrupts */ + while (max > 0) { + if (ca_midi_output_ready(midi)) { + if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT) || + snd_rawmidi_transmit(substream, &byte, 1) != 1) { + /* no more data */ + return; + } + ca_midi_write_data(midi, byte); + max--; + } else { + break; + } + } + } + midi->interrupt_enable(midi,midi->tx_enable); + + } else { + midi->interrupt_disable(midi,midi->tx_enable); + } +} + +static const struct snd_rawmidi_ops ca_midi_output = +{ + .open = ca_midi_output_open, + .close = ca_midi_output_close, + .trigger = ca_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops ca_midi_input = +{ + .open = ca_midi_input_open, + .close = ca_midi_input_close, + .trigger = ca_midi_input_trigger, +}; + +static void ca_midi_free(struct snd_ca_midi *midi) +{ + midi->interrupt = NULL; + midi->interrupt_enable = NULL; + midi->interrupt_disable = NULL; + midi->read = NULL; + midi->write = NULL; + midi->get_dev_id_card = NULL; + midi->get_dev_id_port = NULL; + midi->rmidi = NULL; +} + +static void ca_rmidi_free(struct snd_rawmidi *rmidi) +{ + ca_midi_free(rmidi->private_data); +} + +int ca_midi_init(void *dev_id, struct snd_ca_midi *midi, int device, char *name) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(midi->get_dev_id_card(midi->dev_id), name, device, 1, 1, &rmidi); + if (err < 0) + return err; + + midi->dev_id = dev_id; + midi->interrupt = ca_midi_interrupt; + + spin_lock_init(&midi->open_lock); + spin_lock_init(&midi->input_lock); + spin_lock_init(&midi->output_lock); + + strscpy(rmidi->name, name); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &ca_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &ca_midi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = midi; + rmidi->private_free = ca_rmidi_free; + + midi->rmidi = rmidi; + return 0; +} + diff --git a/sound/pci/ca0106/ca_midi.h b/sound/pci/ca0106/ca_midi.h new file mode 100644 index 000000000..1d0476c8a --- /dev/null +++ b/sound/pci/ca0106/ca_midi.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 10/16/2005 Tilman Kranz + * Creative Audio MIDI, for the CA0106 Driver + * Version: 0.0.1 + * + * Changelog: + * See ca_midi.c + */ + +#include +#include +#include + +#define CA_MIDI_MODE_INPUT MPU401_MODE_INPUT +#define CA_MIDI_MODE_OUTPUT MPU401_MODE_OUTPUT + +struct snd_ca_midi { + + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream_input; + struct snd_rawmidi_substream *substream_output; + + void *dev_id; + + spinlock_t input_lock; + spinlock_t output_lock; + spinlock_t open_lock; + + unsigned int channel; + + unsigned int midi_mode; + int port; + int tx_enable, rx_enable; + int ipr_tx, ipr_rx; + + int input_avail, output_ready; + int ack, reset, enter_uart; + + void (*interrupt)(struct snd_ca_midi *midi, unsigned int status); + void (*interrupt_enable)(struct snd_ca_midi *midi, int intr); + void (*interrupt_disable)(struct snd_ca_midi *midi, int intr); + + unsigned char (*read)(struct snd_ca_midi *midi, int idx); + void (*write)(struct snd_ca_midi *midi, int data, int idx); + + /* get info from dev_id */ + struct snd_card *(*get_dev_id_card)(void *dev_id); + int (*get_dev_id_port)(void *dev_id); +}; + +int ca_midi_init(void *card, struct snd_ca_midi *midi, int device, char *name); diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c new file mode 100644 index 000000000..9d9f784e3 --- /dev/null +++ b/sound/pci/cmipci.c @@ -0,0 +1,3293 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for C-Media CMI8338 and 8738 PCI soundcards. + * Copyright (c) 2000 by Takashi Iwai + */ + +/* Does not work. Warning may block system in capture mode */ +/* #define USE_VAR48KRATE */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("C-Media CMI8x38 PCI"); +MODULE_LICENSE("GPL"); + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK 1 +#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_PNP; /* Enable switches */ +static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +static long fm_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)]=1}; +static bool soft_ac3[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)]=1}; +#ifdef SUPPORT_JOYSTICK +static int joystick_port[SNDRV_CARDS]; +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for C-Media PCI soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for C-Media PCI soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable C-Media PCI soundcard."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port."); +module_param_hw_array(fm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port."); +module_param_array(soft_ac3, bool, NULL, 0444); +MODULE_PARM_DESC(soft_ac3, "Software-conversion of raw SPDIF packets (model 033 only)."); +#ifdef SUPPORT_JOYSTICK +module_param_hw_array(joystick_port, int, ioport, NULL, 0444); +MODULE_PARM_DESC(joystick_port, "Joystick port address."); +#endif + +/* + * CM8x38 registers definition + */ + +#define CM_REG_FUNCTRL0 0x00 +#define CM_RST_CH1 0x00080000 +#define CM_RST_CH0 0x00040000 +#define CM_CHEN1 0x00020000 /* ch1: enable */ +#define CM_CHEN0 0x00010000 /* ch0: enable */ +#define CM_PAUSE1 0x00000008 /* ch1: pause */ +#define CM_PAUSE0 0x00000004 /* ch0: pause */ +#define CM_CHADC1 0x00000002 /* ch1, 0:playback, 1:record */ +#define CM_CHADC0 0x00000001 /* ch0, 0:playback, 1:record */ + +#define CM_REG_FUNCTRL1 0x04 +#define CM_DSFC_MASK 0x0000E000 /* channel 1 (DAC?) sampling frequency */ +#define CM_DSFC_SHIFT 13 +#define CM_ASFC_MASK 0x00001C00 /* channel 0 (ADC?) sampling frequency */ +#define CM_ASFC_SHIFT 10 +#define CM_SPDF_1 0x00000200 /* SPDIF IN/OUT at channel B */ +#define CM_SPDF_0 0x00000100 /* SPDIF OUT only channel A */ +#define CM_SPDFLOOP 0x00000080 /* ext. SPDIIF/IN -> OUT loopback */ +#define CM_SPDO2DAC 0x00000040 /* SPDIF/OUT can be heard from internal DAC */ +#define CM_INTRM 0x00000020 /* master control block (MCB) interrupt enabled */ +#define CM_BREQ 0x00000010 /* bus master enabled */ +#define CM_VOICE_EN 0x00000008 /* legacy voice (SB16,FM) */ +#define CM_UART_EN 0x00000004 /* legacy UART */ +#define CM_JYSTK_EN 0x00000002 /* legacy joystick */ +#define CM_ZVPORT 0x00000001 /* ZVPORT */ + +#define CM_REG_CHFORMAT 0x08 + +#define CM_CHB3D5C 0x80000000 /* 5,6 channels */ +#define CM_FMOFFSET2 0x40000000 /* initial FM PCM offset 2 when Fmute=1 */ +#define CM_CHB3D 0x20000000 /* 4 channels */ + +#define CM_CHIP_MASK1 0x1f000000 +#define CM_CHIP_037 0x01000000 +#define CM_SETLAT48 0x00800000 /* set latency timer 48h */ +#define CM_EDGEIRQ 0x00400000 /* emulated edge trigger legacy IRQ */ +#define CM_SPD24SEL39 0x00200000 /* 24-bit spdif: model 039 */ +#define CM_AC3EN1 0x00100000 /* enable AC3: model 037 */ +#define CM_SPDIF_SELECT1 0x00080000 /* for model <= 037 ? */ +#define CM_SPD24SEL 0x00020000 /* 24bit spdif: model 037 */ +/* #define CM_SPDIF_INVERSE 0x00010000 */ /* ??? */ + +#define CM_ADCBITLEN_MASK 0x0000C000 +#define CM_ADCBITLEN_16 0x00000000 +#define CM_ADCBITLEN_15 0x00004000 +#define CM_ADCBITLEN_14 0x00008000 +#define CM_ADCBITLEN_13 0x0000C000 + +#define CM_ADCDACLEN_MASK 0x00003000 /* model 037 */ +#define CM_ADCDACLEN_060 0x00000000 +#define CM_ADCDACLEN_066 0x00001000 +#define CM_ADCDACLEN_130 0x00002000 +#define CM_ADCDACLEN_280 0x00003000 + +#define CM_ADCDLEN_MASK 0x00003000 /* model 039 */ +#define CM_ADCDLEN_ORIGINAL 0x00000000 +#define CM_ADCDLEN_EXTRA 0x00001000 +#define CM_ADCDLEN_24K 0x00002000 +#define CM_ADCDLEN_WEIGHT 0x00003000 + +#define CM_CH1_SRATE_176K 0x00000800 +#define CM_CH1_SRATE_96K 0x00000800 /* model 055? */ +#define CM_CH1_SRATE_88K 0x00000400 +#define CM_CH0_SRATE_176K 0x00000200 +#define CM_CH0_SRATE_96K 0x00000200 /* model 055? */ +#define CM_CH0_SRATE_88K 0x00000100 +#define CM_CH0_SRATE_128K 0x00000300 +#define CM_CH0_SRATE_MASK 0x00000300 + +#define CM_SPDIF_INVERSE2 0x00000080 /* model 055? */ +#define CM_DBLSPDS 0x00000040 /* double SPDIF sample rate 88.2/96 */ +#define CM_POLVALID 0x00000020 /* inverse SPDIF/IN valid bit */ +#define CM_SPDLOCKED 0x00000010 + +#define CM_CH1FMT_MASK 0x0000000C /* bit 3: 16 bits, bit 2: stereo */ +#define CM_CH1FMT_SHIFT 2 +#define CM_CH0FMT_MASK 0x00000003 /* bit 1: 16 bits, bit 0: stereo */ +#define CM_CH0FMT_SHIFT 0 + +#define CM_REG_INT_HLDCLR 0x0C +#define CM_CHIP_MASK2 0xff000000 +#define CM_CHIP_8768 0x20000000 +#define CM_CHIP_055 0x08000000 +#define CM_CHIP_039 0x04000000 +#define CM_CHIP_039_6CH 0x01000000 +#define CM_UNKNOWN_INT_EN 0x00080000 /* ? */ +#define CM_TDMA_INT_EN 0x00040000 +#define CM_CH1_INT_EN 0x00020000 +#define CM_CH0_INT_EN 0x00010000 + +#define CM_REG_INT_STATUS 0x10 +#define CM_INTR 0x80000000 +#define CM_VCO 0x08000000 /* Voice Control? CMI8738 */ +#define CM_MCBINT 0x04000000 /* Master Control Block abort cond.? */ +#define CM_UARTINT 0x00010000 +#define CM_LTDMAINT 0x00008000 +#define CM_HTDMAINT 0x00004000 +#define CM_XDO46 0x00000080 /* Modell 033? Direct programming EEPROM (read data register) */ +#define CM_LHBTOG 0x00000040 /* High/Low status from DMA ctrl register */ +#define CM_LEG_HDMA 0x00000020 /* Legacy is in High DMA channel */ +#define CM_LEG_STEREO 0x00000010 /* Legacy is in Stereo mode */ +#define CM_CH1BUSY 0x00000008 +#define CM_CH0BUSY 0x00000004 +#define CM_CHINT1 0x00000002 +#define CM_CHINT0 0x00000001 + +#define CM_REG_LEGACY_CTRL 0x14 +#define CM_NXCHG 0x80000000 /* don't map base reg dword->sample */ +#define CM_VMPU_MASK 0x60000000 /* MPU401 i/o port address */ +#define CM_VMPU_330 0x00000000 +#define CM_VMPU_320 0x20000000 +#define CM_VMPU_310 0x40000000 +#define CM_VMPU_300 0x60000000 +#define CM_ENWR8237 0x10000000 /* enable bus master to write 8237 base reg */ +#define CM_VSBSEL_MASK 0x0C000000 /* SB16 base address */ +#define CM_VSBSEL_220 0x00000000 +#define CM_VSBSEL_240 0x04000000 +#define CM_VSBSEL_260 0x08000000 +#define CM_VSBSEL_280 0x0C000000 +#define CM_FMSEL_MASK 0x03000000 /* FM OPL3 base address */ +#define CM_FMSEL_388 0x00000000 +#define CM_FMSEL_3C8 0x01000000 +#define CM_FMSEL_3E0 0x02000000 +#define CM_FMSEL_3E8 0x03000000 +#define CM_ENSPDOUT 0x00800000 /* enable XSPDIF/OUT to I/O interface */ +#define CM_SPDCOPYRHT 0x00400000 /* spdif in/out copyright bit */ +#define CM_DAC2SPDO 0x00200000 /* enable wave+fm_midi -> SPDIF/OUT */ +#define CM_INVIDWEN 0x00100000 /* internal vendor ID write enable, model 039? */ +#define CM_SETRETRY 0x00100000 /* 0: legacy i/o wait (default), 1: legacy i/o bus retry */ +#define CM_C_EEACCESS 0x00080000 /* direct programming eeprom regs */ +#define CM_C_EECS 0x00040000 +#define CM_C_EEDI46 0x00020000 +#define CM_C_EECK46 0x00010000 +#define CM_CHB3D6C 0x00008000 /* 5.1 channels support */ +#define CM_CENTR2LIN 0x00004000 /* line-in as center out */ +#define CM_BASE2LIN 0x00002000 /* line-in as bass out */ +#define CM_EXBASEN 0x00001000 /* external bass input enable */ + +#define CM_REG_MISC_CTRL 0x18 +#define CM_PWD 0x80000000 /* power down */ +#define CM_RESET 0x40000000 +#define CM_SFIL_MASK 0x30000000 /* filter control at front end DAC, model 037? */ +#define CM_VMGAIN 0x10000000 /* analog master amp +6dB, model 039? */ +#define CM_TXVX 0x08000000 /* model 037? */ +#define CM_N4SPK3D 0x04000000 /* copy front to rear */ +#define CM_SPDO5V 0x02000000 /* 5V spdif output (1 = 0.5v (coax)) */ +#define CM_SPDIF48K 0x01000000 /* write */ +#define CM_SPATUS48K 0x01000000 /* read */ +#define CM_ENDBDAC 0x00800000 /* enable double dac */ +#define CM_XCHGDAC 0x00400000 /* 0: front=ch0, 1: front=ch1 */ +#define CM_SPD32SEL 0x00200000 /* 0: 16bit SPDIF, 1: 32bit */ +#define CM_SPDFLOOPI 0x00100000 /* int. SPDIF-OUT -> int. IN */ +#define CM_FM_EN 0x00080000 /* enable legacy FM */ +#define CM_AC3EN2 0x00040000 /* enable AC3: model 039 */ +#define CM_ENWRASID 0x00010000 /* choose writable internal SUBID (audio) */ +#define CM_VIDWPDSB 0x00010000 /* model 037? */ +#define CM_SPDF_AC97 0x00008000 /* 0: SPDIF/OUT 44.1K, 1: 48K */ +#define CM_MASK_EN 0x00004000 /* activate channel mask on legacy DMA */ +#define CM_ENWRMSID 0x00002000 /* choose writable internal SUBID (modem) */ +#define CM_VIDWPPRT 0x00002000 /* model 037? */ +#define CM_SFILENB 0x00001000 /* filter stepping at front end DAC, model 037? */ +#define CM_MMODE_MASK 0x00000E00 /* model DAA interface mode */ +#define CM_SPDIF_SELECT2 0x00000100 /* for model > 039 ? */ +#define CM_ENCENTER 0x00000080 +#define CM_FLINKON 0x00000040 /* force modem link detection on, model 037 */ +#define CM_MUTECH1 0x00000040 /* mute PCI ch1 to DAC */ +#define CM_FLINKOFF 0x00000020 /* force modem link detection off, model 037 */ +#define CM_MIDSMP 0x00000010 /* 1/2 interpolation at front end DAC */ +#define CM_UPDDMA_MASK 0x0000000C /* TDMA position update notification */ +#define CM_UPDDMA_2048 0x00000000 +#define CM_UPDDMA_1024 0x00000004 +#define CM_UPDDMA_512 0x00000008 +#define CM_UPDDMA_256 0x0000000C +#define CM_TWAIT_MASK 0x00000003 /* model 037 */ +#define CM_TWAIT1 0x00000002 /* FM i/o cycle, 0: 48, 1: 64 PCICLKs */ +#define CM_TWAIT0 0x00000001 /* i/o cycle, 0: 4, 1: 6 PCICLKs */ + +#define CM_REG_TDMA_POSITION 0x1C +#define CM_TDMA_CNT_MASK 0xFFFF0000 /* current byte/word count */ +#define CM_TDMA_ADR_MASK 0x0000FFFF /* current address */ + + /* byte */ +#define CM_REG_MIXER0 0x20 +#define CM_REG_SBVR 0x20 /* write: sb16 version */ +#define CM_REG_DEV 0x20 /* read: hardware device version */ + +#define CM_REG_MIXER21 0x21 +#define CM_UNKNOWN_21_MASK 0x78 /* ? */ +#define CM_X_ADPCM 0x04 /* SB16 ADPCM enable */ +#define CM_PROINV 0x02 /* SBPro left/right channel switching */ +#define CM_X_SB16 0x01 /* SB16 compatible */ + +#define CM_REG_SB16_DATA 0x22 +#define CM_REG_SB16_ADDR 0x23 + +#define CM_REFFREQ_XIN (315*1000*1000)/22 /* 14.31818 Mhz reference clock frequency pin XIN */ +#define CM_ADCMULT_XIN 512 /* Guessed (487 best for 44.1kHz, not for 88/176kHz) */ +#define CM_TOLERANCE_RATE 0.001 /* Tolerance sample rate pitch (1000ppm) */ +#define CM_MAXIMUM_RATE 80000000 /* Note more than 80MHz */ + +#define CM_REG_MIXER1 0x24 +#define CM_FMMUTE 0x80 /* mute FM */ +#define CM_FMMUTE_SHIFT 7 +#define CM_WSMUTE 0x40 /* mute PCM */ +#define CM_WSMUTE_SHIFT 6 +#define CM_REAR2LIN 0x20 /* lin-in -> rear line out */ +#define CM_REAR2LIN_SHIFT 5 +#define CM_REAR2FRONT 0x10 /* exchange rear/front */ +#define CM_REAR2FRONT_SHIFT 4 +#define CM_WAVEINL 0x08 /* digital wave rec. left chan */ +#define CM_WAVEINL_SHIFT 3 +#define CM_WAVEINR 0x04 /* digical wave rec. right */ +#define CM_WAVEINR_SHIFT 2 +#define CM_X3DEN 0x02 /* 3D surround enable */ +#define CM_X3DEN_SHIFT 1 +#define CM_CDPLAY 0x01 /* enable SPDIF/IN PCM -> DAC */ +#define CM_CDPLAY_SHIFT 0 + +#define CM_REG_MIXER2 0x25 +#define CM_RAUXREN 0x80 /* AUX right capture */ +#define CM_RAUXREN_SHIFT 7 +#define CM_RAUXLEN 0x40 /* AUX left capture */ +#define CM_RAUXLEN_SHIFT 6 +#define CM_VAUXRM 0x20 /* AUX right mute */ +#define CM_VAUXRM_SHIFT 5 +#define CM_VAUXLM 0x10 /* AUX left mute */ +#define CM_VAUXLM_SHIFT 4 +#define CM_VADMIC_MASK 0x0e /* mic gain level (0-3) << 1 */ +#define CM_VADMIC_SHIFT 1 +#define CM_MICGAINZ 0x01 /* mic boost */ +#define CM_MICGAINZ_SHIFT 0 + +#define CM_REG_AUX_VOL 0x26 +#define CM_VAUXL_MASK 0xf0 +#define CM_VAUXR_MASK 0x0f + +#define CM_REG_MISC 0x27 +#define CM_UNKNOWN_27_MASK 0xd8 /* ? */ +#define CM_XGPO1 0x20 +// #define CM_XGPBIO 0x04 +#define CM_MIC_CENTER_LFE 0x04 /* mic as center/lfe out? (model 039 or later?) */ +#define CM_SPDIF_INVERSE 0x04 /* spdif input phase inverse (model 037) */ +#define CM_SPDVALID 0x02 /* spdif input valid check */ +#define CM_DMAUTO 0x01 /* SB16 DMA auto detect */ + +#define CM_REG_AC97 0x28 /* hmmm.. do we have ac97 link? */ +/* + * For CMI-8338 (0x28 - 0x2b) .. is this valid for CMI-8738 + * or identical with AC97 codec? + */ +#define CM_REG_EXTERN_CODEC CM_REG_AC97 + +/* + * MPU401 pci port index address 0x40 - 0x4f (CMI-8738 spec ver. 0.6) + */ +#define CM_REG_MPU_PCI 0x40 + +/* + * FM pci port index address 0x50 - 0x5f (CMI-8738 spec ver. 0.6) + */ +#define CM_REG_FM_PCI 0x50 + +/* + * access from SB-mixer port + */ +#define CM_REG_EXTENT_IND 0xf0 +#define CM_VPHONE_MASK 0xe0 /* Phone volume control (0-3) << 5 */ +#define CM_VPHONE_SHIFT 5 +#define CM_VPHOM 0x10 /* Phone mute control */ +#define CM_VSPKM 0x08 /* Speaker mute control, default high */ +#define CM_RLOOPREN 0x04 /* Rec. R-channel enable */ +#define CM_RLOOPLEN 0x02 /* Rec. L-channel enable */ +#define CM_VADMIC3 0x01 /* Mic record boost */ + +/* + * CMI-8338 spec ver 0.5 (this is not valid for CMI-8738): + * the 8 registers 0xf8 - 0xff are used for programming m/n counter by the PLL + * unit (readonly?). + */ +#define CM_REG_PLL 0xf8 + +/* + * extended registers + */ +#define CM_REG_CH0_FRAME1 0x80 /* write: base address */ +#define CM_REG_CH0_FRAME2 0x84 /* read: current address */ +#define CM_REG_CH1_FRAME1 0x88 /* 0-15: count of samples at bus master; buffer size */ +#define CM_REG_CH1_FRAME2 0x8C /* 16-31: count of samples at codec; fragment size */ + +#define CM_REG_EXT_MISC 0x90 +#define CM_ADC48K44K 0x10000000 /* ADC parameters group, 0: 44k, 1: 48k */ +#define CM_CHB3D8C 0x00200000 /* 7.1 channels support */ +#define CM_SPD32FMT 0x00100000 /* SPDIF/IN 32k sample rate */ +#define CM_ADC2SPDIF 0x00080000 /* ADC output to SPDIF/OUT */ +#define CM_SHAREADC 0x00040000 /* DAC in ADC as Center/LFE */ +#define CM_REALTCMP 0x00020000 /* monitor the CMPL/CMPR of ADC */ +#define CM_INVLRCK 0x00010000 /* invert ZVPORT's LRCK */ +#define CM_UNKNOWN_90_MASK 0x0000FFFF /* ? */ + +/* + * size of i/o region + */ +#define CM_EXTENT_CODEC 0x100 +#define CM_EXTENT_MIDI 0x2 +#define CM_EXTENT_SYNTH 0x4 + + +/* + * channels for playback / capture + */ +#define CM_CH_PLAY 0 +#define CM_CH_CAPT 1 + +/* + * flags to check device open/close + */ +#define CM_OPEN_NONE 0 +#define CM_OPEN_CH_MASK 0x01 +#define CM_OPEN_DAC 0x10 +#define CM_OPEN_ADC 0x20 +#define CM_OPEN_SPDIF 0x40 +#define CM_OPEN_MCHAN 0x80 +#define CM_OPEN_PLAYBACK (CM_CH_PLAY | CM_OPEN_DAC) +#define CM_OPEN_PLAYBACK2 (CM_CH_CAPT | CM_OPEN_DAC) +#define CM_OPEN_PLAYBACK_MULTI (CM_CH_PLAY | CM_OPEN_DAC | CM_OPEN_MCHAN) +#define CM_OPEN_CAPTURE (CM_CH_CAPT | CM_OPEN_ADC) +#define CM_OPEN_SPDIF_PLAYBACK (CM_CH_PLAY | CM_OPEN_DAC | CM_OPEN_SPDIF) +#define CM_OPEN_SPDIF_CAPTURE (CM_CH_CAPT | CM_OPEN_ADC | CM_OPEN_SPDIF) + + +#if CM_CH_PLAY == 1 +#define CM_PLAYBACK_SRATE_176K CM_CH1_SRATE_176K +#define CM_PLAYBACK_SPDF CM_SPDF_1 +#define CM_CAPTURE_SPDF CM_SPDF_0 +#else +#define CM_PLAYBACK_SRATE_176K CM_CH0_SRATE_176K +#define CM_PLAYBACK_SPDF CM_SPDF_0 +#define CM_CAPTURE_SPDF CM_SPDF_1 +#endif + + +/* + * driver data + */ + +struct cmipci_pcm { + struct snd_pcm_substream *substream; + u8 running; /* dac/adc running? */ + u8 fmt; /* format bits */ + u8 is_dac; + u8 needs_silencing; + unsigned int dma_size; /* in frames */ + unsigned int shift; + unsigned int ch; /* channel (0/1) */ + unsigned int offset; /* physical address of the buffer */ +}; + +/* mixer elements toggled/resumed during ac3 playback */ +struct cmipci_mixer_auto_switches { + const char *name; /* switch to toggle */ + int toggle_on; /* value to change when ac3 mode */ +}; +static const struct cmipci_mixer_auto_switches cm_saved_mixer[] = { + {"PCM Playback Switch", 0}, + {"IEC958 Output Switch", 1}, + {"IEC958 Mix Analog", 0}, + // {"IEC958 Out To DAC", 1}, // no longer used + {"IEC958 Loop", 0}, +}; +#define CM_SAVED_MIXERS ARRAY_SIZE(cm_saved_mixer) + +struct cmipci { + struct snd_card *card; + + struct pci_dev *pci; + unsigned int device; /* device ID */ + int irq; + + unsigned long iobase; + unsigned int ctrl; /* FUNCTRL0 current value */ + + struct snd_pcm *pcm; /* DAC/ADC PCM */ + struct snd_pcm *pcm2; /* 2nd DAC */ + struct snd_pcm *pcm_spdif; /* SPDIF */ + + int chip_version; + int max_channels; + unsigned int can_ac3_sw: 1; + unsigned int can_ac3_hw: 1; + unsigned int can_multi_ch: 1; + unsigned int can_96k: 1; /* samplerate above 48k */ + unsigned int do_soft_ac3: 1; + + unsigned int spdif_playback_avail: 1; /* spdif ready? */ + unsigned int spdif_playback_enabled: 1; /* spdif switch enabled? */ + int spdif_counter; /* for software AC3 */ + + unsigned int dig_status; + unsigned int dig_pcm_status; + + struct snd_pcm_hardware *hw_info[3]; /* for playbacks */ + + int opened[2]; /* open mode */ + struct mutex open_mutex; + + unsigned int mixer_insensitive: 1; + struct snd_kcontrol *mixer_res_ctl[CM_SAVED_MIXERS]; + int mixer_res_status[CM_SAVED_MIXERS]; + + struct cmipci_pcm channel[2]; /* ch0 - DAC, ch1 - ADC or 2nd DAC */ + + /* external MIDI */ + struct snd_rawmidi *rmidi; + +#ifdef SUPPORT_JOYSTICK + struct gameport *gameport; +#endif + + spinlock_t reg_lock; + + unsigned int saved_regs[0x20]; + unsigned char saved_mixers[0x20]; +}; + + +/* read/write operations for dword register */ +static inline void snd_cmipci_write(struct cmipci *cm, unsigned int cmd, unsigned int data) +{ + outl(data, cm->iobase + cmd); +} + +static inline unsigned int snd_cmipci_read(struct cmipci *cm, unsigned int cmd) +{ + return inl(cm->iobase + cmd); +} + +/* read/write operations for word register */ +static inline void snd_cmipci_write_w(struct cmipci *cm, unsigned int cmd, unsigned short data) +{ + outw(data, cm->iobase + cmd); +} + +static inline unsigned short snd_cmipci_read_w(struct cmipci *cm, unsigned int cmd) +{ + return inw(cm->iobase + cmd); +} + +/* read/write operations for byte register */ +static inline void snd_cmipci_write_b(struct cmipci *cm, unsigned int cmd, unsigned char data) +{ + outb(data, cm->iobase + cmd); +} + +static inline unsigned char snd_cmipci_read_b(struct cmipci *cm, unsigned int cmd) +{ + return inb(cm->iobase + cmd); +} + +/* bit operations for dword register */ +static int snd_cmipci_set_bit(struct cmipci *cm, unsigned int cmd, unsigned int flag) +{ + unsigned int val, oval; + val = oval = inl(cm->iobase + cmd); + val |= flag; + if (val == oval) + return 0; + outl(val, cm->iobase + cmd); + return 1; +} + +static int snd_cmipci_clear_bit(struct cmipci *cm, unsigned int cmd, unsigned int flag) +{ + unsigned int val, oval; + val = oval = inl(cm->iobase + cmd); + val &= ~flag; + if (val == oval) + return 0; + outl(val, cm->iobase + cmd); + return 1; +} + +/* bit operations for byte register */ +static int snd_cmipci_set_bit_b(struct cmipci *cm, unsigned int cmd, unsigned char flag) +{ + unsigned char val, oval; + val = oval = inb(cm->iobase + cmd); + val |= flag; + if (val == oval) + return 0; + outb(val, cm->iobase + cmd); + return 1; +} + +static int snd_cmipci_clear_bit_b(struct cmipci *cm, unsigned int cmd, unsigned char flag) +{ + unsigned char val, oval; + val = oval = inb(cm->iobase + cmd); + val &= ~flag; + if (val == oval) + return 0; + outb(val, cm->iobase + cmd); + return 1; +} + + +/* + * PCM interface + */ + +/* + * calculate frequency + */ + +static const unsigned int rates[] = { 5512, 11025, 22050, 44100, 8000, 16000, 32000, 48000 }; + +static unsigned int snd_cmipci_rate_freq(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rates); i++) { + if (rates[i] == rate) + return i; + } + snd_BUG(); + return 0; +} + +#ifdef USE_VAR48KRATE +/* + * Determine PLL values for frequency setup, maybe the CMI8338 (CMI8738???) + * does it this way .. maybe not. Never get any information from C-Media about + * that . + */ +static int snd_cmipci_pll_rmn(unsigned int rate, unsigned int adcmult, int *r, int *m, int *n) +{ + unsigned int delta, tolerance; + int xm, xn, xr; + + for (*r = 0; rate < CM_MAXIMUM_RATE/adcmult; *r += (1<<5)) + rate <<= 1; + *n = -1; + if (*r > 0xff) + goto out; + tolerance = rate*CM_TOLERANCE_RATE; + + for (xn = (1+2); xn < (0x1f+2); xn++) { + for (xm = (1+2); xm < (0xff+2); xm++) { + xr = ((CM_REFFREQ_XIN/adcmult) * xm) / xn; + + if (xr < rate) + delta = rate - xr; + else + delta = xr - rate; + + /* + * If we found one, remember this, + * and try to find a closer one + */ + if (delta < tolerance) { + tolerance = delta; + *m = xm - 2; + *n = xn - 2; + } + } + } +out: + return (*n > -1); +} + +/* + * Program pll register bits, I assume that the 8 registers 0xf8 up to 0xff + * are mapped onto the 8 ADC/DAC sampling frequency which can be chosen + * at the register CM_REG_FUNCTRL1 (0x04). + * Problem: other ways are also possible (any information about that?) + */ +static void snd_cmipci_set_pll(struct cmipci *cm, unsigned int rate, unsigned int slot) +{ + unsigned int reg = CM_REG_PLL + slot; + /* + * Guess that this programs at reg. 0x04 the pos 15:13/12:10 + * for DSFC/ASFC (000 up to 111). + */ + + /* FIXME: Init (Do we've to set an other register first before programming?) */ + + /* FIXME: Is this correct? Or shouldn't the m/n/r values be used for that? */ + snd_cmipci_write_b(cm, reg, rate>>8); + snd_cmipci_write_b(cm, reg, rate&0xff); + + /* FIXME: Setup (Do we've to set an other register first to enable this?) */ +} +#endif /* USE_VAR48KRATE */ + +static int snd_cmipci_playback2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + if (params_channels(hw_params) > 2) { + guard(mutex)(&cm->open_mutex); + if (cm->opened[CM_CH_PLAY]) + return -EBUSY; + /* reserve the channel A */ + cm->opened[CM_CH_PLAY] = CM_OPEN_PLAYBACK_MULTI; + } + return 0; +} + +static void snd_cmipci_ch_reset(struct cmipci *cm, int ch) +{ + int reset = CM_RST_CH0 << (cm->channel[ch].ch); + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl | reset); + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl & ~reset); + udelay(10); +} + + +/* + */ + +static const unsigned int hw_channels[] = {1, 2, 4, 6, 8}; +static const struct snd_pcm_hw_constraint_list hw_constraints_channels_4 = { + .count = 3, + .list = hw_channels, + .mask = 0, +}; +static const struct snd_pcm_hw_constraint_list hw_constraints_channels_6 = { + .count = 4, + .list = hw_channels, + .mask = 0, +}; +static const struct snd_pcm_hw_constraint_list hw_constraints_channels_8 = { + .count = 5, + .list = hw_channels, + .mask = 0, +}; + +static int set_dac_channels(struct cmipci *cm, struct cmipci_pcm *rec, int channels) +{ + if (channels > 2) { + if (!cm->can_multi_ch || !rec->ch) + return -EINVAL; + if (rec->fmt != 0x03) /* stereo 16bit only */ + return -EINVAL; + } + + if (cm->can_multi_ch) { + guard(spinlock_irq)(&cm->reg_lock); + if (channels > 2) { + snd_cmipci_set_bit(cm, CM_REG_LEGACY_CTRL, CM_NXCHG); + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_XCHGDAC); + } else { + snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_NXCHG); + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_XCHGDAC); + } + if (channels == 8) + snd_cmipci_set_bit(cm, CM_REG_EXT_MISC, CM_CHB3D8C); + else + snd_cmipci_clear_bit(cm, CM_REG_EXT_MISC, CM_CHB3D8C); + if (channels == 6) { + snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_CHB3D5C); + snd_cmipci_set_bit(cm, CM_REG_LEGACY_CTRL, CM_CHB3D6C); + } else { + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_CHB3D5C); + snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_CHB3D6C); + } + if (channels == 4) + snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_CHB3D); + else + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_CHB3D); + } + return 0; +} + + +/* + * prepare playback/capture channel + * channel to be used must have been set in rec->ch. + */ +static int snd_cmipci_pcm_prepare(struct cmipci *cm, struct cmipci_pcm *rec, + struct snd_pcm_substream *substream) +{ + unsigned int reg, freq, freq_ext, val; + unsigned int period_size; + struct snd_pcm_runtime *runtime = substream->runtime; + + rec->fmt = 0; + rec->shift = 0; + if (snd_pcm_format_width(runtime->format) >= 16) { + rec->fmt |= 0x02; + if (snd_pcm_format_width(runtime->format) > 16) + rec->shift++; /* 24/32bit */ + } + if (runtime->channels > 1) + rec->fmt |= 0x01; + if (rec->is_dac && set_dac_channels(cm, rec, runtime->channels) < 0) { + dev_dbg(cm->card->dev, "cannot set dac channels\n"); + return -EINVAL; + } + + rec->offset = runtime->dma_addr; + /* buffer and period sizes in frame */ + rec->dma_size = runtime->buffer_size << rec->shift; + period_size = runtime->period_size << rec->shift; + if (runtime->channels > 2) { + /* multi-channels */ + rec->dma_size = (rec->dma_size * runtime->channels) / 2; + period_size = (period_size * runtime->channels) / 2; + } + + guard(spinlock_irq)(&cm->reg_lock); + + /* set buffer address */ + reg = rec->ch ? CM_REG_CH1_FRAME1 : CM_REG_CH0_FRAME1; + snd_cmipci_write(cm, reg, rec->offset); + /* program sample counts */ + reg = rec->ch ? CM_REG_CH1_FRAME2 : CM_REG_CH0_FRAME2; + snd_cmipci_write_w(cm, reg, rec->dma_size - 1); + snd_cmipci_write_w(cm, reg + 2, period_size - 1); + + /* set adc/dac flag */ + val = rec->ch ? CM_CHADC1 : CM_CHADC0; + if (rec->is_dac) + cm->ctrl &= ~val; + else + cm->ctrl |= val; + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl); + /* dev_dbg(cm->card->dev, "functrl0 = %08x\n", cm->ctrl); */ + + /* set sample rate */ + freq = 0; + freq_ext = 0; + if (runtime->rate > 48000) + switch (runtime->rate) { + case 88200: freq_ext = CM_CH0_SRATE_88K; break; + case 96000: freq_ext = CM_CH0_SRATE_96K; break; + case 128000: freq_ext = CM_CH0_SRATE_128K; break; + default: snd_BUG(); break; + } + else + freq = snd_cmipci_rate_freq(runtime->rate); + val = snd_cmipci_read(cm, CM_REG_FUNCTRL1); + if (rec->ch) { + val &= ~CM_DSFC_MASK; + val |= (freq << CM_DSFC_SHIFT) & CM_DSFC_MASK; + } else { + val &= ~CM_ASFC_MASK; + val |= (freq << CM_ASFC_SHIFT) & CM_ASFC_MASK; + } + snd_cmipci_write(cm, CM_REG_FUNCTRL1, val); + dev_dbg(cm->card->dev, "functrl1 = %08x\n", val); + + /* set format */ + val = snd_cmipci_read(cm, CM_REG_CHFORMAT); + if (rec->ch) { + val &= ~CM_CH1FMT_MASK; + val |= rec->fmt << CM_CH1FMT_SHIFT; + } else { + val &= ~CM_CH0FMT_MASK; + val |= rec->fmt << CM_CH0FMT_SHIFT; + } + if (cm->can_96k) { + val &= ~(CM_CH0_SRATE_MASK << (rec->ch * 2)); + val |= freq_ext << (rec->ch * 2); + } + snd_cmipci_write(cm, CM_REG_CHFORMAT, val); + dev_dbg(cm->card->dev, "chformat = %08x\n", val); + + if (!rec->is_dac && cm->chip_version) { + if (runtime->rate > 44100) + snd_cmipci_set_bit(cm, CM_REG_EXT_MISC, CM_ADC48K44K); + else + snd_cmipci_clear_bit(cm, CM_REG_EXT_MISC, CM_ADC48K44K); + } + + rec->running = 0; + + return 0; +} + +/* + * PCM trigger/stop + */ +static int snd_cmipci_pcm_trigger(struct cmipci *cm, struct cmipci_pcm *rec, + int cmd) +{ + unsigned int inthld, chen, reset, pause; + + inthld = CM_CH0_INT_EN << rec->ch; + chen = CM_CHEN0 << rec->ch; + reset = CM_RST_CH0 << rec->ch; + pause = CM_PAUSE0 << rec->ch; + + guard(spinlock)(&cm->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + rec->running = 1; + /* set interrupt */ + snd_cmipci_set_bit(cm, CM_REG_INT_HLDCLR, inthld); + cm->ctrl |= chen; + /* enable channel */ + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl); + dev_dbg(cm->card->dev, "functrl0 = %08x\n", cm->ctrl); + break; + case SNDRV_PCM_TRIGGER_STOP: + rec->running = 0; + /* disable interrupt */ + snd_cmipci_clear_bit(cm, CM_REG_INT_HLDCLR, inthld); + /* reset */ + cm->ctrl &= ~chen; + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl | reset); + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl & ~reset); + rec->needs_silencing = rec->is_dac; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + cm->ctrl |= pause; + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + cm->ctrl &= ~pause; + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * return the current pointer + */ +static snd_pcm_uframes_t snd_cmipci_pcm_pointer(struct cmipci *cm, struct cmipci_pcm *rec, + struct snd_pcm_substream *substream) +{ + size_t ptr; + unsigned int reg, rem, tries; + + if (!rec->running) + return 0; +#if 1 // this seems better.. + reg = rec->ch ? CM_REG_CH1_FRAME2 : CM_REG_CH0_FRAME2; + for (tries = 0; tries < 3; tries++) { + rem = snd_cmipci_read_w(cm, reg); + if (rem < rec->dma_size) + goto ok; + } + dev_err(cm->card->dev, "invalid PCM pointer: %#x\n", rem); + return SNDRV_PCM_POS_XRUN; +ok: + ptr = (rec->dma_size - (rem + 1)) >> rec->shift; +#else + reg = rec->ch ? CM_REG_CH1_FRAME1 : CM_REG_CH0_FRAME1; + ptr = snd_cmipci_read(cm, reg) - rec->offset; + ptr = bytes_to_frames(substream->runtime, ptr); +#endif + if (substream->runtime->channels > 2) + ptr = (ptr * 2) / substream->runtime->channels; + return ptr; +} + +/* + * playback + */ + +static int snd_cmipci_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + return snd_cmipci_pcm_trigger(cm, &cm->channel[CM_CH_PLAY], cmd); +} + +static snd_pcm_uframes_t snd_cmipci_playback_pointer(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + return snd_cmipci_pcm_pointer(cm, &cm->channel[CM_CH_PLAY], substream); +} + + + +/* + * capture + */ + +static int snd_cmipci_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + return snd_cmipci_pcm_trigger(cm, &cm->channel[CM_CH_CAPT], cmd); +} + +static snd_pcm_uframes_t snd_cmipci_capture_pointer(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + return snd_cmipci_pcm_pointer(cm, &cm->channel[CM_CH_CAPT], substream); +} + + +/* + * hw preparation for spdif + */ + +static int snd_cmipci_spdif_default_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_cmipci_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *chip = snd_kcontrol_chip(kcontrol); + int i; + + guard(spinlock_irq)(&chip->reg_lock); + for (i = 0; i < 4; i++) + ucontrol->value.iec958.status[i] = (chip->dig_status >> (i * 8)) & 0xff; + return 0; +} + +static int snd_cmipci_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *chip = snd_kcontrol_chip(kcontrol); + int i, change; + unsigned int val; + + val = 0; + guard(spinlock_irq)(&chip->reg_lock); + for (i = 0; i < 4; i++) + val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8); + change = val != chip->dig_status; + chip->dig_status = val; + return change; +} + +static const struct snd_kcontrol_new snd_cmipci_spdif_default = +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_cmipci_spdif_default_info, + .get = snd_cmipci_spdif_default_get, + .put = snd_cmipci_spdif_default_put +}; + +static int snd_cmipci_spdif_mask_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_cmipci_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static const struct snd_kcontrol_new snd_cmipci_spdif_mask = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK), + .info = snd_cmipci_spdif_mask_info, + .get = snd_cmipci_spdif_mask_get, +}; + +static int snd_cmipci_spdif_stream_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_cmipci_spdif_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *chip = snd_kcontrol_chip(kcontrol); + int i; + + guard(spinlock_irq)(&chip->reg_lock); + for (i = 0; i < 4; i++) + ucontrol->value.iec958.status[i] = (chip->dig_pcm_status >> (i * 8)) & 0xff; + return 0; +} + +static int snd_cmipci_spdif_stream_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *chip = snd_kcontrol_chip(kcontrol); + int i, change; + unsigned int val; + + val = 0; + guard(spinlock_irq)(&chip->reg_lock); + for (i = 0; i < 4; i++) + val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8); + change = val != chip->dig_pcm_status; + chip->dig_pcm_status = val; + return change; +} + +static const struct snd_kcontrol_new snd_cmipci_spdif_stream = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .info = snd_cmipci_spdif_stream_info, + .get = snd_cmipci_spdif_stream_get, + .put = snd_cmipci_spdif_stream_put +}; + +/* + */ + +/* save mixer setting and mute for AC3 playback */ +static int save_mixer_state(struct cmipci *cm) +{ + if (! cm->mixer_insensitive) { + struct snd_ctl_elem_value *val; + unsigned int i; + + val = kmalloc_obj(*val); + if (!val) + return -ENOMEM; + for (i = 0; i < CM_SAVED_MIXERS; i++) { + struct snd_kcontrol *ctl = cm->mixer_res_ctl[i]; + if (ctl) { + int event; + memset(val, 0, sizeof(*val)); + ctl->get(ctl, val); + cm->mixer_res_status[i] = val->value.integer.value[0]; + val->value.integer.value[0] = cm_saved_mixer[i].toggle_on; + event = SNDRV_CTL_EVENT_MASK_INFO; + if (cm->mixer_res_status[i] != val->value.integer.value[0]) { + ctl->put(ctl, val); /* toggle */ + event |= SNDRV_CTL_EVENT_MASK_VALUE; + } + ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(cm->card, event, &ctl->id); + } + } + kfree(val); + cm->mixer_insensitive = 1; + } + return 0; +} + + +/* restore the previously saved mixer status */ +static void restore_mixer_state(struct cmipci *cm) +{ + if (cm->mixer_insensitive) { + struct snd_ctl_elem_value *val; + unsigned int i; + + val = kmalloc_obj(*val); + if (!val) + return; + cm->mixer_insensitive = 0; /* at first clear this; + otherwise the changes will be ignored */ + for (i = 0; i < CM_SAVED_MIXERS; i++) { + struct snd_kcontrol *ctl = cm->mixer_res_ctl[i]; + if (ctl) { + int event; + + memset(val, 0, sizeof(*val)); + ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + ctl->get(ctl, val); + event = SNDRV_CTL_EVENT_MASK_INFO; + if (val->value.integer.value[0] != cm->mixer_res_status[i]) { + val->value.integer.value[0] = cm->mixer_res_status[i]; + ctl->put(ctl, val); + event |= SNDRV_CTL_EVENT_MASK_VALUE; + } + snd_ctl_notify(cm->card, event, &ctl->id); + } + } + kfree(val); + } +} + +/* spinlock held! */ +static void setup_ac3(struct cmipci *cm, struct snd_pcm_substream *subs, int do_ac3, int rate) +{ + if (do_ac3) { + /* AC3EN for 037 */ + snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_AC3EN1); + /* AC3EN for 039 */ + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_AC3EN2); + + if (cm->can_ac3_hw) { + /* SPD24SEL for 037, 0x02 */ + /* SPD24SEL for 039, 0x20, but cannot be set */ + snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_SPD24SEL); + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL); + } else { /* can_ac3_sw */ + /* SPD32SEL for 037 & 039, 0x20 */ + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL); + /* set 176K sample rate to fix 033 HW bug */ + if (cm->chip_version == 33) { + if (rate >= 48000) { + snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_PLAYBACK_SRATE_176K); + } else { + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_PLAYBACK_SRATE_176K); + } + } + } + + } else { + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_AC3EN1); + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_AC3EN2); + + if (cm->can_ac3_hw) { + /* chip model >= 37 */ + if (snd_pcm_format_width(subs->runtime->format) > 16) { + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL); + snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_SPD24SEL); + } else { + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL); + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_SPD24SEL); + } + } else { + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL); + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_SPD24SEL); + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_PLAYBACK_SRATE_176K); + } + } +} + +static int setup_spdif_playback(struct cmipci *cm, struct snd_pcm_substream *subs, int up, int do_ac3) +{ + int rate, err; + + rate = subs->runtime->rate; + + if (up && do_ac3) { + err = save_mixer_state(cm); + if (err < 0) + return err; + } + + guard(spinlock_irq)(&cm->reg_lock); + cm->spdif_playback_avail = up; + if (up) { + /* they are controlled via "IEC958 Output Switch" */ + /* snd_cmipci_set_bit(cm, CM_REG_LEGACY_CTRL, CM_ENSPDOUT); */ + /* snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_SPDO2DAC); */ + if (cm->spdif_playback_enabled) + snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_PLAYBACK_SPDF); + setup_ac3(cm, subs, do_ac3, rate); + + if (rate == 48000 || rate == 96000) + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPDIF48K | CM_SPDF_AC97); + else + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPDIF48K | CM_SPDF_AC97); + if (rate > 48000) + snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS); + else + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS); + } else { + /* they are controlled via "IEC958 Output Switch" */ + /* snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_ENSPDOUT); */ + /* snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_SPDO2DAC); */ + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS); + snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_PLAYBACK_SPDF); + setup_ac3(cm, subs, 0, 0); + } + return 0; +} + + +/* + * preparation + */ + +/* playback - enable spdif only on the certain condition */ +static int snd_cmipci_playback_prepare(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + int rate = substream->runtime->rate; + int err, do_spdif, do_ac3 = 0; + + do_spdif = (rate >= 44100 && rate <= 96000 && + substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE && + substream->runtime->channels == 2); + if (do_spdif && cm->can_ac3_hw) + do_ac3 = cm->dig_pcm_status & IEC958_AES0_NONAUDIO; + err = setup_spdif_playback(cm, substream, do_spdif, do_ac3); + if (err < 0) + return err; + return snd_cmipci_pcm_prepare(cm, &cm->channel[CM_CH_PLAY], substream); +} + +/* playback (via device #2) - enable spdif always */ +static int snd_cmipci_playback_spdif_prepare(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + int err, do_ac3; + + if (cm->can_ac3_hw) + do_ac3 = cm->dig_pcm_status & IEC958_AES0_NONAUDIO; + else + do_ac3 = 1; /* doesn't matter */ + err = setup_spdif_playback(cm, substream, 1, do_ac3); + if (err < 0) + return err; + return snd_cmipci_pcm_prepare(cm, &cm->channel[CM_CH_PLAY], substream); +} + +/* + * Apparently, the samples last played on channel A stay in some buffer, even + * after the channel is reset, and get added to the data for the rear DACs when + * playing a multichannel stream on channel B. This is likely to generate + * wraparounds and thus distortions. + * To avoid this, we play at least one zero sample after the actual stream has + * stopped. + */ +static void snd_cmipci_silence_hack(struct cmipci *cm, struct cmipci_pcm *rec) +{ + struct snd_pcm_runtime *runtime = rec->substream->runtime; + unsigned int reg, val; + + if (rec->needs_silencing && runtime && runtime->dma_area) { + /* set up a small silence buffer */ + memset(runtime->dma_area, 0, PAGE_SIZE); + reg = rec->ch ? CM_REG_CH1_FRAME2 : CM_REG_CH0_FRAME2; + val = ((PAGE_SIZE / 4) - 1) | (((PAGE_SIZE / 4) / 2 - 1) << 16); + snd_cmipci_write(cm, reg, val); + + /* configure for 16 bits, 2 channels, 8 kHz */ + if (runtime->channels > 2) + set_dac_channels(cm, rec, 2); + scoped_guard(spinlock_irq, &cm->reg_lock) { + val = snd_cmipci_read(cm, CM_REG_FUNCTRL1); + val &= ~(CM_ASFC_MASK << (rec->ch * 3)); + val |= (4 << CM_ASFC_SHIFT) << (rec->ch * 3); + snd_cmipci_write(cm, CM_REG_FUNCTRL1, val); + val = snd_cmipci_read(cm, CM_REG_CHFORMAT); + val &= ~(CM_CH0FMT_MASK << (rec->ch * 2)); + val |= (3 << CM_CH0FMT_SHIFT) << (rec->ch * 2); + if (cm->can_96k) + val &= ~(CM_CH0_SRATE_MASK << (rec->ch * 2)); + snd_cmipci_write(cm, CM_REG_CHFORMAT, val); + + /* start stream (we don't need interrupts) */ + cm->ctrl |= CM_CHEN0 << rec->ch; + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl); + } + + msleep(1); + + /* stop and reset stream */ + scoped_guard(spinlock_irq, &cm->reg_lock) { + cm->ctrl &= ~(CM_CHEN0 << rec->ch); + val = CM_RST_CH0 << rec->ch; + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl | val); + snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl & ~val); + } + + rec->needs_silencing = 0; + } +} + +static int snd_cmipci_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + setup_spdif_playback(cm, substream, 0, 0); + restore_mixer_state(cm); + snd_cmipci_silence_hack(cm, &cm->channel[0]); + return 0; +} + +static int snd_cmipci_playback2_hw_free(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + snd_cmipci_silence_hack(cm, &cm->channel[1]); + return 0; +} + +/* capture */ +static int snd_cmipci_capture_prepare(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + return snd_cmipci_pcm_prepare(cm, &cm->channel[CM_CH_CAPT], substream); +} + +/* capture with spdif (via device #2) */ +static int snd_cmipci_capture_spdif_prepare(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + + scoped_guard(spinlock_irq, &cm->reg_lock) { + snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_CAPTURE_SPDF); + if (cm->can_96k) { + if (substream->runtime->rate > 48000) + snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS); + else + snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS); + } + if (snd_pcm_format_width(substream->runtime->format) > 16) + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL); + else + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL); + } + + return snd_cmipci_pcm_prepare(cm, &cm->channel[CM_CH_CAPT], substream); +} + +static int snd_cmipci_capture_spdif_hw_free(struct snd_pcm_substream *subs) +{ + struct cmipci *cm = snd_pcm_substream_chip(subs); + + guard(spinlock_irq)(&cm->reg_lock); + snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_CAPTURE_SPDF); + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL); + + return 0; +} + + +/* + * interrupt handler + */ +static irqreturn_t snd_cmipci_interrupt(int irq, void *dev_id) +{ + struct cmipci *cm = dev_id; + unsigned int status, mask = 0; + + /* fastpath out, to ease interrupt sharing */ + status = snd_cmipci_read(cm, CM_REG_INT_STATUS); + if (!(status & CM_INTR)) + return IRQ_NONE; + + /* acknowledge interrupt */ + scoped_guard(spinlock, &cm->reg_lock) { + if (status & CM_CHINT0) + mask |= CM_CH0_INT_EN; + if (status & CM_CHINT1) + mask |= CM_CH1_INT_EN; + snd_cmipci_clear_bit(cm, CM_REG_INT_HLDCLR, mask); + snd_cmipci_set_bit(cm, CM_REG_INT_HLDCLR, mask); + } + + if (cm->rmidi && (status & CM_UARTINT)) + snd_mpu401_uart_interrupt(irq, cm->rmidi->private_data); + + if (cm->pcm) { + if ((status & CM_CHINT0) && cm->channel[0].running) + snd_pcm_period_elapsed(cm->channel[0].substream); + if ((status & CM_CHINT1) && cm->channel[1].running) + snd_pcm_period_elapsed(cm->channel[1].substream); + } + return IRQ_HANDLED; +} + +/* + * h/w infos + */ + +/* playback on channel A */ +static const struct snd_pcm_hardware snd_cmipci_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5512, + .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 = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* capture on channel B */ +static const struct snd_pcm_hardware snd_cmipci_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5512, + .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 = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* playback on channel B - stereo 16bit only? */ +static const struct snd_pcm_hardware snd_cmipci_playback2 = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5512, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* spdif playback on channel A */ +static const struct snd_pcm_hardware snd_cmipci_playback_spdif = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* spdif playback on channel A (32bit, IEC958 subframes) */ +static const struct snd_pcm_hardware snd_cmipci_playback_iec958_subframe = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* spdif capture on channel B */ +static const struct snd_pcm_hardware snd_cmipci_capture_spdif = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + * check device open/close + */ +static int open_device_check(struct cmipci *cm, int mode, struct snd_pcm_substream *subs) +{ + int ch = mode & CM_OPEN_CH_MASK; + + /* FIXME: a file should wait until the device becomes free + * when it's opened on blocking mode. however, since the current + * pcm framework doesn't pass file pointer before actually opened, + * we can't know whether blocking mode or not in open callback.. + */ + guard(mutex)(&cm->open_mutex); + if (cm->opened[ch]) + return -EBUSY; + cm->opened[ch] = mode; + cm->channel[ch].substream = subs; + if (! (mode & CM_OPEN_DAC)) { + /* disable dual DAC mode */ + cm->channel[ch].is_dac = 0; + guard(spinlock_irq)(&cm->reg_lock); + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_ENDBDAC); + } + return 0; +} + +static void close_device_check(struct cmipci *cm, int mode) +{ + int ch = mode & CM_OPEN_CH_MASK; + + guard(mutex)(&cm->open_mutex); + if (cm->opened[ch] == mode) { + if (cm->channel[ch].substream) { + snd_cmipci_ch_reset(cm, ch); + cm->channel[ch].running = 0; + cm->channel[ch].substream = NULL; + } + cm->opened[ch] = 0; + if (! cm->channel[ch].is_dac) { + /* enable dual DAC mode again */ + cm->channel[ch].is_dac = 1; + guard(spinlock_irq)(&cm->reg_lock); + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_ENDBDAC); + } + } +} + +/* + */ + +static int snd_cmipci_playback_open(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = open_device_check(cm, CM_OPEN_PLAYBACK, substream); + if (err < 0) + return err; + runtime->hw = snd_cmipci_playback; + if (cm->chip_version == 68) { + runtime->hw.rates |= SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + runtime->hw.rate_max = 96000; + } else if (cm->chip_version == 55) { + runtime->hw.rates |= SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_128000; + runtime->hw.rate_max = 128000; + } + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x10000); + cm->dig_pcm_status = cm->dig_status; + return 0; +} + +static int snd_cmipci_capture_open(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = open_device_check(cm, CM_OPEN_CAPTURE, substream); + if (err < 0) + return err; + runtime->hw = snd_cmipci_capture; + if (cm->chip_version == 68) { // 8768 only supports 44k/48k recording + runtime->hw.rate_min = 41000; + runtime->hw.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000; + } else if (cm->chip_version == 55) { + runtime->hw.rates |= SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_128000; + runtime->hw.rate_max = 128000; + } + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x10000); + return 0; +} + +static int snd_cmipci_playback2_open(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + /* use channel B */ + err = open_device_check(cm, CM_OPEN_PLAYBACK2, substream); + if (err < 0) + return err; + runtime->hw = snd_cmipci_playback2; + guard(mutex)(&cm->open_mutex); + if (! cm->opened[CM_CH_PLAY]) { + if (cm->can_multi_ch) { + runtime->hw.channels_max = cm->max_channels; + if (cm->max_channels == 4) + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &hw_constraints_channels_4); + else if (cm->max_channels == 6) + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &hw_constraints_channels_6); + else if (cm->max_channels == 8) + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &hw_constraints_channels_8); + } + } + if (cm->chip_version == 68) { + runtime->hw.rates |= SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + runtime->hw.rate_max = 96000; + } else if (cm->chip_version == 55) { + runtime->hw.rates |= SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_128000; + runtime->hw.rate_max = 128000; + } + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x10000); + return 0; +} + +static int snd_cmipci_playback_spdif_open(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + /* use channel A */ + err = open_device_check(cm, CM_OPEN_SPDIF_PLAYBACK, substream); + if (err < 0) + return err; + if (cm->can_ac3_hw) { + runtime->hw = snd_cmipci_playback_spdif; + if (cm->chip_version >= 37) { + runtime->hw.formats |= SNDRV_PCM_FMTBIT_S32_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + } + if (cm->can_96k) { + runtime->hw.rates |= SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + runtime->hw.rate_max = 96000; + } + } else { + runtime->hw = snd_cmipci_playback_iec958_subframe; + } + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x40000); + cm->dig_pcm_status = cm->dig_status; + return 0; +} + +static int snd_cmipci_capture_spdif_open(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + /* use channel B */ + err = open_device_check(cm, CM_OPEN_SPDIF_CAPTURE, substream); + if (err < 0) + return err; + runtime->hw = snd_cmipci_capture_spdif; + if (cm->can_96k && !(cm->chip_version == 68)) { + runtime->hw.rates |= SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + runtime->hw.rate_max = 96000; + } + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x40000); + return 0; +} + + +/* + */ + +static int snd_cmipci_playback_close(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + close_device_check(cm, CM_OPEN_PLAYBACK); + return 0; +} + +static int snd_cmipci_capture_close(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + close_device_check(cm, CM_OPEN_CAPTURE); + return 0; +} + +static int snd_cmipci_playback2_close(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + close_device_check(cm, CM_OPEN_PLAYBACK2); + close_device_check(cm, CM_OPEN_PLAYBACK_MULTI); + return 0; +} + +static int snd_cmipci_playback_spdif_close(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + close_device_check(cm, CM_OPEN_SPDIF_PLAYBACK); + return 0; +} + +static int snd_cmipci_capture_spdif_close(struct snd_pcm_substream *substream) +{ + struct cmipci *cm = snd_pcm_substream_chip(substream); + close_device_check(cm, CM_OPEN_SPDIF_CAPTURE); + return 0; +} + + +/* + */ + +static const struct snd_pcm_ops snd_cmipci_playback_ops = { + .open = snd_cmipci_playback_open, + .close = snd_cmipci_playback_close, + .hw_free = snd_cmipci_playback_hw_free, + .prepare = snd_cmipci_playback_prepare, + .trigger = snd_cmipci_playback_trigger, + .pointer = snd_cmipci_playback_pointer, +}; + +static const struct snd_pcm_ops snd_cmipci_capture_ops = { + .open = snd_cmipci_capture_open, + .close = snd_cmipci_capture_close, + .prepare = snd_cmipci_capture_prepare, + .trigger = snd_cmipci_capture_trigger, + .pointer = snd_cmipci_capture_pointer, +}; + +static const struct snd_pcm_ops snd_cmipci_playback2_ops = { + .open = snd_cmipci_playback2_open, + .close = snd_cmipci_playback2_close, + .hw_params = snd_cmipci_playback2_hw_params, + .hw_free = snd_cmipci_playback2_hw_free, + .prepare = snd_cmipci_capture_prepare, /* channel B */ + .trigger = snd_cmipci_capture_trigger, /* channel B */ + .pointer = snd_cmipci_capture_pointer, /* channel B */ +}; + +static const struct snd_pcm_ops snd_cmipci_playback_spdif_ops = { + .open = snd_cmipci_playback_spdif_open, + .close = snd_cmipci_playback_spdif_close, + .hw_free = snd_cmipci_playback_hw_free, + .prepare = snd_cmipci_playback_spdif_prepare, /* set up rate */ + .trigger = snd_cmipci_playback_trigger, + .pointer = snd_cmipci_playback_pointer, +}; + +static const struct snd_pcm_ops snd_cmipci_capture_spdif_ops = { + .open = snd_cmipci_capture_spdif_open, + .close = snd_cmipci_capture_spdif_close, + .hw_free = snd_cmipci_capture_spdif_hw_free, + .prepare = snd_cmipci_capture_spdif_prepare, + .trigger = snd_cmipci_capture_trigger, + .pointer = snd_cmipci_capture_pointer, +}; + + +/* + */ + +static int snd_cmipci_pcm_new(struct cmipci *cm, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(cm->card, cm->card->driver, device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cmipci_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cmipci_capture_ops); + + pcm->private_data = cm; + pcm->info_flags = 0; + strscpy(pcm->name, "C-Media PCI DAC/ADC"); + cm->pcm = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &cm->pci->dev, 64*1024, 128*1024); + + return 0; +} + +static int snd_cmipci_pcm2_new(struct cmipci *cm, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(cm->card, cm->card->driver, device, 1, 0, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cmipci_playback2_ops); + + pcm->private_data = cm; + pcm->info_flags = 0; + strscpy(pcm->name, "C-Media PCI 2nd DAC"); + cm->pcm2 = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &cm->pci->dev, 64*1024, 128*1024); + + return 0; +} + +static int snd_cmipci_pcm_spdif_new(struct cmipci *cm, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(cm->card, cm->card->driver, device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cmipci_playback_spdif_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cmipci_capture_spdif_ops); + + pcm->private_data = cm; + pcm->info_flags = 0; + strscpy(pcm->name, "C-Media PCI IEC958"); + cm->pcm_spdif = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &cm->pci->dev, 64*1024, 128*1024); + + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_alt_chmaps, cm->max_channels, 0, + NULL); + if (err < 0) + return err; + + return 0; +} + +/* + * mixer interface: + * - CM8338/8738 has a compatible mixer interface with SB16, but + * lack of some elements like tone control, i/o gain and AGC. + * - Access to native registers: + * - A 3D switch + * - Output mute switches + */ + +static void snd_cmipci_mixer_write(struct cmipci *s, unsigned char idx, unsigned char data) +{ + outb(idx, s->iobase + CM_REG_SB16_ADDR); + outb(data, s->iobase + CM_REG_SB16_DATA); +} + +static unsigned char snd_cmipci_mixer_read(struct cmipci *s, unsigned char idx) +{ + unsigned char v; + + outb(idx, s->iobase + CM_REG_SB16_ADDR); + v = inb(s->iobase + CM_REG_SB16_DATA); + return v; +} + +/* + * general mixer element + */ +struct cmipci_sb_reg { + unsigned int left_reg, right_reg; + unsigned int left_shift, right_shift; + unsigned int mask; + unsigned int invert: 1; + unsigned int stereo: 1; +}; + +#define COMPOSE_SB_REG(lreg,rreg,lshift,rshift,mask,invert,stereo) \ + ((lreg) | ((rreg) << 8) | (lshift << 16) | (rshift << 19) | (mask << 24) | (invert << 22) | (stereo << 23)) + +#define CMIPCI_DOUBLE(xname, left_reg, right_reg, left_shift, right_shift, mask, invert, stereo) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_cmipci_info_volume, \ + .get = snd_cmipci_get_volume, .put = snd_cmipci_put_volume, \ + .private_value = COMPOSE_SB_REG(left_reg, right_reg, left_shift, right_shift, mask, invert, stereo), \ +} + +#define CMIPCI_SB_VOL_STEREO(xname,reg,shift,mask) CMIPCI_DOUBLE(xname, reg, reg+1, shift, shift, mask, 0, 1) +#define CMIPCI_SB_VOL_MONO(xname,reg,shift,mask) CMIPCI_DOUBLE(xname, reg, reg, shift, shift, mask, 0, 0) +#define CMIPCI_SB_SW_STEREO(xname,lshift,rshift) CMIPCI_DOUBLE(xname, SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, lshift, rshift, 1, 0, 1) +#define CMIPCI_SB_SW_MONO(xname,shift) CMIPCI_DOUBLE(xname, SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, shift, shift, 1, 0, 0) + +static void cmipci_sb_reg_decode(struct cmipci_sb_reg *r, unsigned long val) +{ + r->left_reg = val & 0xff; + r->right_reg = (val >> 8) & 0xff; + r->left_shift = (val >> 16) & 0x07; + r->right_shift = (val >> 19) & 0x07; + r->invert = (val >> 22) & 1; + r->stereo = (val >> 23) & 1; + r->mask = (val >> 24) & 0xff; +} + +static int snd_cmipci_info_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct cmipci_sb_reg reg; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + uinfo->type = reg.mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = reg.stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = reg.mask; + return 0; +} + +static int snd_cmipci_get_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + struct cmipci_sb_reg reg; + int val; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + guard(spinlock_irq)(&cm->reg_lock); + val = (snd_cmipci_mixer_read(cm, reg.left_reg) >> reg.left_shift) & reg.mask; + if (reg.invert) + val = reg.mask - val; + ucontrol->value.integer.value[0] = val; + if (reg.stereo) { + val = (snd_cmipci_mixer_read(cm, reg.right_reg) >> reg.right_shift) & reg.mask; + if (reg.invert) + val = reg.mask - val; + ucontrol->value.integer.value[1] = val; + } + return 0; +} + +static int snd_cmipci_put_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + struct cmipci_sb_reg reg; + int change; + int left, right, oleft, oright; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + left = ucontrol->value.integer.value[0] & reg.mask; + if (reg.invert) + left = reg.mask - left; + left <<= reg.left_shift; + if (reg.stereo) { + right = ucontrol->value.integer.value[1] & reg.mask; + if (reg.invert) + right = reg.mask - right; + right <<= reg.right_shift; + } else + right = 0; + guard(spinlock_irq)(&cm->reg_lock); + oleft = snd_cmipci_mixer_read(cm, reg.left_reg); + left |= oleft & ~(reg.mask << reg.left_shift); + change = left != oleft; + if (reg.stereo) { + if (reg.left_reg != reg.right_reg) { + snd_cmipci_mixer_write(cm, reg.left_reg, left); + oright = snd_cmipci_mixer_read(cm, reg.right_reg); + } else + oright = left; + right |= oright & ~(reg.mask << reg.right_shift); + change |= right != oright; + snd_cmipci_mixer_write(cm, reg.right_reg, right); + } else + snd_cmipci_mixer_write(cm, reg.left_reg, left); + return change; +} + +/* + * input route (left,right) -> (left,right) + */ +#define CMIPCI_SB_INPUT_SW(xname, left_shift, right_shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_cmipci_info_input_sw, \ + .get = snd_cmipci_get_input_sw, .put = snd_cmipci_put_input_sw, \ + .private_value = COMPOSE_SB_REG(SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, left_shift, right_shift, 1, 0, 1), \ +} + +static int snd_cmipci_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_cmipci_get_input_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + struct cmipci_sb_reg reg; + int val1, val2; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + guard(spinlock_irq)(&cm->reg_lock); + val1 = snd_cmipci_mixer_read(cm, reg.left_reg); + val2 = snd_cmipci_mixer_read(cm, reg.right_reg); + ucontrol->value.integer.value[0] = (val1 >> reg.left_shift) & 1; + ucontrol->value.integer.value[1] = (val2 >> reg.left_shift) & 1; + ucontrol->value.integer.value[2] = (val1 >> reg.right_shift) & 1; + ucontrol->value.integer.value[3] = (val2 >> reg.right_shift) & 1; + return 0; +} + +static int snd_cmipci_put_input_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + struct cmipci_sb_reg reg; + int change; + int val1, val2, oval1, oval2; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + guard(spinlock_irq)(&cm->reg_lock); + oval1 = snd_cmipci_mixer_read(cm, reg.left_reg); + oval2 = snd_cmipci_mixer_read(cm, reg.right_reg); + val1 = oval1 & ~((1 << reg.left_shift) | (1 << reg.right_shift)); + val2 = oval2 & ~((1 << reg.left_shift) | (1 << reg.right_shift)); + val1 |= (ucontrol->value.integer.value[0] & 1) << reg.left_shift; + val2 |= (ucontrol->value.integer.value[1] & 1) << reg.left_shift; + val1 |= (ucontrol->value.integer.value[2] & 1) << reg.right_shift; + val2 |= (ucontrol->value.integer.value[3] & 1) << reg.right_shift; + change = val1 != oval1 || val2 != oval2; + snd_cmipci_mixer_write(cm, reg.left_reg, val1); + snd_cmipci_mixer_write(cm, reg.right_reg, val2); + return change; +} + +/* + * native mixer switches/volumes + */ + +#define CMIPCI_MIXER_SW_STEREO(xname, reg, lshift, rshift, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_cmipci_info_native_mixer, \ + .get = snd_cmipci_get_native_mixer, .put = snd_cmipci_put_native_mixer, \ + .private_value = COMPOSE_SB_REG(reg, reg, lshift, rshift, 1, invert, 1), \ +} + +#define CMIPCI_MIXER_SW_MONO(xname, reg, shift, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_cmipci_info_native_mixer, \ + .get = snd_cmipci_get_native_mixer, .put = snd_cmipci_put_native_mixer, \ + .private_value = COMPOSE_SB_REG(reg, reg, shift, shift, 1, invert, 0), \ +} + +#define CMIPCI_MIXER_VOL_STEREO(xname, reg, lshift, rshift, mask) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_cmipci_info_native_mixer, \ + .get = snd_cmipci_get_native_mixer, .put = snd_cmipci_put_native_mixer, \ + .private_value = COMPOSE_SB_REG(reg, reg, lshift, rshift, mask, 0, 1), \ +} + +#define CMIPCI_MIXER_VOL_MONO(xname, reg, shift, mask) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_cmipci_info_native_mixer, \ + .get = snd_cmipci_get_native_mixer, .put = snd_cmipci_put_native_mixer, \ + .private_value = COMPOSE_SB_REG(reg, reg, shift, shift, mask, 0, 0), \ +} + +static int snd_cmipci_info_native_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct cmipci_sb_reg reg; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + uinfo->type = reg.mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = reg.stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = reg.mask; + return 0; + +} + +static int snd_cmipci_get_native_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + struct cmipci_sb_reg reg; + unsigned char oreg, val; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + guard(spinlock_irq)(&cm->reg_lock); + oreg = inb(cm->iobase + reg.left_reg); + val = (oreg >> reg.left_shift) & reg.mask; + if (reg.invert) + val = reg.mask - val; + ucontrol->value.integer.value[0] = val; + if (reg.stereo) { + val = (oreg >> reg.right_shift) & reg.mask; + if (reg.invert) + val = reg.mask - val; + ucontrol->value.integer.value[1] = val; + } + return 0; +} + +static int snd_cmipci_put_native_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + struct cmipci_sb_reg reg; + unsigned char oreg, nreg, val; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + guard(spinlock_irq)(&cm->reg_lock); + oreg = inb(cm->iobase + reg.left_reg); + val = ucontrol->value.integer.value[0] & reg.mask; + if (reg.invert) + val = reg.mask - val; + nreg = oreg & ~(reg.mask << reg.left_shift); + nreg |= (val << reg.left_shift); + if (reg.stereo) { + val = ucontrol->value.integer.value[1] & reg.mask; + if (reg.invert) + val = reg.mask - val; + nreg &= ~(reg.mask << reg.right_shift); + nreg |= (val << reg.right_shift); + } + outb(nreg, cm->iobase + reg.left_reg); + return (nreg != oreg); +} + +/* + * special case - check mixer sensitivity + */ +static int snd_cmipci_get_native_mixer_sensitive(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + //struct cmipci *cm = snd_kcontrol_chip(kcontrol); + return snd_cmipci_get_native_mixer(kcontrol, ucontrol); +} + +static int snd_cmipci_put_native_mixer_sensitive(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + if (cm->mixer_insensitive) { + /* ignored */ + return 0; + } + return snd_cmipci_put_native_mixer(kcontrol, ucontrol); +} + + +static const struct snd_kcontrol_new snd_cmipci_mixers[] = { + CMIPCI_SB_VOL_STEREO("Master Playback Volume", SB_DSP4_MASTER_DEV, 3, 31), + CMIPCI_MIXER_SW_MONO("3D Control - Switch", CM_REG_MIXER1, CM_X3DEN_SHIFT, 0), + CMIPCI_SB_VOL_STEREO("PCM Playback Volume", SB_DSP4_PCM_DEV, 3, 31), + //CMIPCI_MIXER_SW_MONO("PCM Playback Switch", CM_REG_MIXER1, CM_WSMUTE_SHIFT, 1), + { /* switch with sensitivity */ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = snd_cmipci_info_native_mixer, + .get = snd_cmipci_get_native_mixer_sensitive, + .put = snd_cmipci_put_native_mixer_sensitive, + .private_value = COMPOSE_SB_REG(CM_REG_MIXER1, CM_REG_MIXER1, CM_WSMUTE_SHIFT, CM_WSMUTE_SHIFT, 1, 1, 0), + }, + CMIPCI_MIXER_SW_STEREO("PCM Capture Switch", CM_REG_MIXER1, CM_WAVEINL_SHIFT, CM_WAVEINR_SHIFT, 0), + CMIPCI_SB_VOL_STEREO("Synth Playback Volume", SB_DSP4_SYNTH_DEV, 3, 31), + CMIPCI_MIXER_SW_MONO("Synth Playback Switch", CM_REG_MIXER1, CM_FMMUTE_SHIFT, 1), + CMIPCI_SB_INPUT_SW("Synth Capture Route", 6, 5), + CMIPCI_SB_VOL_STEREO("CD Playback Volume", SB_DSP4_CD_DEV, 3, 31), + CMIPCI_SB_SW_STEREO("CD Playback Switch", 2, 1), + CMIPCI_SB_INPUT_SW("CD Capture Route", 2, 1), + CMIPCI_SB_VOL_STEREO("Line Playback Volume", SB_DSP4_LINE_DEV, 3, 31), + CMIPCI_SB_SW_STEREO("Line Playback Switch", 4, 3), + CMIPCI_SB_INPUT_SW("Line Capture Route", 4, 3), + CMIPCI_SB_VOL_MONO("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31), + CMIPCI_SB_SW_MONO("Mic Playback Switch", 0), + CMIPCI_DOUBLE("Mic Capture Switch", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0, 1, 0, 0), + CMIPCI_SB_VOL_MONO("Beep Playback Volume", SB_DSP4_SPEAKER_DEV, 6, 3), + CMIPCI_MIXER_VOL_STEREO("Aux Playback Volume", CM_REG_AUX_VOL, 4, 0, 15), + CMIPCI_MIXER_SW_STEREO("Aux Playback Switch", CM_REG_MIXER2, CM_VAUXLM_SHIFT, CM_VAUXRM_SHIFT, 0), + CMIPCI_MIXER_SW_STEREO("Aux Capture Switch", CM_REG_MIXER2, CM_RAUXLEN_SHIFT, CM_RAUXREN_SHIFT, 0), + CMIPCI_MIXER_SW_MONO("Mic Boost Playback Switch", CM_REG_MIXER2, CM_MICGAINZ_SHIFT, 1), + CMIPCI_MIXER_VOL_MONO("Mic Capture Volume", CM_REG_MIXER2, CM_VADMIC_SHIFT, 7), + CMIPCI_SB_VOL_MONO("Phone Playback Volume", CM_REG_EXTENT_IND, 5, 7), + CMIPCI_DOUBLE("Phone Playback Switch", CM_REG_EXTENT_IND, CM_REG_EXTENT_IND, 4, 4, 1, 0, 0), + CMIPCI_DOUBLE("Beep Playback Switch", CM_REG_EXTENT_IND, CM_REG_EXTENT_IND, 3, 3, 1, 0, 0), + CMIPCI_DOUBLE("Mic Boost Capture Switch", CM_REG_EXTENT_IND, CM_REG_EXTENT_IND, 0, 0, 1, 0, 0), +}; + +/* + * other switches + */ + +struct cmipci_switch_args { + int reg; /* register index */ + unsigned int mask; /* mask bits */ + unsigned int mask_on; /* mask bits to turn on */ + unsigned int is_byte: 1; /* byte access? */ + unsigned int ac3_sensitive: 1; /* access forbidden during + * non-audio operation? + */ +}; + +#define snd_cmipci_uswitch_info snd_ctl_boolean_mono_info + +static int _snd_cmipci_uswitch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + struct cmipci_switch_args *args) +{ + unsigned int val; + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&cm->reg_lock); + if (args->ac3_sensitive && cm->mixer_insensitive) { + ucontrol->value.integer.value[0] = 0; + return 0; + } + if (args->is_byte) + val = inb(cm->iobase + args->reg); + else + val = snd_cmipci_read(cm, args->reg); + ucontrol->value.integer.value[0] = ((val & args->mask) == args->mask_on) ? 1 : 0; + return 0; +} + +static int snd_cmipci_uswitch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci_switch_args *args; + args = (struct cmipci_switch_args *)kcontrol->private_value; + if (snd_BUG_ON(!args)) + return -EINVAL; + return _snd_cmipci_uswitch_get(kcontrol, ucontrol, args); +} + +static int _snd_cmipci_uswitch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + struct cmipci_switch_args *args) +{ + unsigned int val; + int change; + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&cm->reg_lock); + if (args->ac3_sensitive && cm->mixer_insensitive) { + /* ignored */ + return 0; + } + if (args->is_byte) + val = inb(cm->iobase + args->reg); + else + val = snd_cmipci_read(cm, args->reg); + change = (val & args->mask) != (ucontrol->value.integer.value[0] ? + args->mask_on : (args->mask & ~args->mask_on)); + if (change) { + val &= ~args->mask; + if (ucontrol->value.integer.value[0]) + val |= args->mask_on; + else + val |= (args->mask & ~args->mask_on); + if (args->is_byte) + outb((unsigned char)val, cm->iobase + args->reg); + else + snd_cmipci_write(cm, args->reg, val); + } + return change; +} + +static int snd_cmipci_uswitch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci_switch_args *args; + args = (struct cmipci_switch_args *)kcontrol->private_value; + if (snd_BUG_ON(!args)) + return -EINVAL; + return _snd_cmipci_uswitch_put(kcontrol, ucontrol, args); +} + +#define DEFINE_SWITCH_ARG(sname, xreg, xmask, xmask_on, xis_byte, xac3) \ +static struct cmipci_switch_args cmipci_switch_arg_##sname = { \ + .reg = xreg, \ + .mask = xmask, \ + .mask_on = xmask_on, \ + .is_byte = xis_byte, \ + .ac3_sensitive = xac3, \ +} + +#define DEFINE_BIT_SWITCH_ARG(sname, xreg, xmask, xis_byte, xac3) \ + DEFINE_SWITCH_ARG(sname, xreg, xmask, xmask, xis_byte, xac3) + +#if 0 /* these will be controlled in pcm device */ +DEFINE_BIT_SWITCH_ARG(spdif_in, CM_REG_FUNCTRL1, CM_SPDF_1, 0, 0); +DEFINE_BIT_SWITCH_ARG(spdif_out, CM_REG_FUNCTRL1, CM_SPDF_0, 0, 0); +#endif +DEFINE_BIT_SWITCH_ARG(spdif_in_sel1, CM_REG_CHFORMAT, CM_SPDIF_SELECT1, 0, 0); +DEFINE_BIT_SWITCH_ARG(spdif_in_sel2, CM_REG_MISC_CTRL, CM_SPDIF_SELECT2, 0, 0); +DEFINE_BIT_SWITCH_ARG(spdif_enable, CM_REG_LEGACY_CTRL, CM_ENSPDOUT, 0, 0); +DEFINE_BIT_SWITCH_ARG(spdo2dac, CM_REG_FUNCTRL1, CM_SPDO2DAC, 0, 1); +DEFINE_BIT_SWITCH_ARG(spdi_valid, CM_REG_MISC, CM_SPDVALID, 1, 0); +DEFINE_BIT_SWITCH_ARG(spdif_copyright, CM_REG_LEGACY_CTRL, CM_SPDCOPYRHT, 0, 0); +DEFINE_BIT_SWITCH_ARG(spdif_dac_out, CM_REG_LEGACY_CTRL, CM_DAC2SPDO, 0, 1); +DEFINE_SWITCH_ARG(spdo_5v, CM_REG_MISC_CTRL, CM_SPDO5V, 0, 0, 0); /* inverse: 0 = 5V */ +// DEFINE_BIT_SWITCH_ARG(spdo_48k, CM_REG_MISC_CTRL, CM_SPDF_AC97|CM_SPDIF48K, 0, 1); +DEFINE_BIT_SWITCH_ARG(spdif_loop, CM_REG_FUNCTRL1, CM_SPDFLOOP, 0, 1); +DEFINE_BIT_SWITCH_ARG(spdi_monitor, CM_REG_MIXER1, CM_CDPLAY, 1, 0); +/* DEFINE_BIT_SWITCH_ARG(spdi_phase, CM_REG_CHFORMAT, CM_SPDIF_INVERSE, 0, 0); */ +DEFINE_BIT_SWITCH_ARG(spdi_phase, CM_REG_MISC, CM_SPDIF_INVERSE, 1, 0); +DEFINE_BIT_SWITCH_ARG(spdi_phase2, CM_REG_CHFORMAT, CM_SPDIF_INVERSE2, 0, 0); +#if CM_CH_PLAY == 1 +DEFINE_SWITCH_ARG(exchange_dac, CM_REG_MISC_CTRL, CM_XCHGDAC, 0, 0, 0); /* reversed */ +#else +DEFINE_SWITCH_ARG(exchange_dac, CM_REG_MISC_CTRL, CM_XCHGDAC, CM_XCHGDAC, 0, 0); +#endif +DEFINE_BIT_SWITCH_ARG(fourch, CM_REG_MISC_CTRL, CM_N4SPK3D, 0, 0); +// DEFINE_BIT_SWITCH_ARG(line_rear, CM_REG_MIXER1, CM_REAR2LIN, 1, 0); +// DEFINE_BIT_SWITCH_ARG(line_bass, CM_REG_LEGACY_CTRL, CM_CENTR2LIN|CM_BASE2LIN, 0, 0); +// DEFINE_BIT_SWITCH_ARG(joystick, CM_REG_FUNCTRL1, CM_JYSTK_EN, 0, 0); /* now module option */ +DEFINE_SWITCH_ARG(modem, CM_REG_MISC_CTRL, CM_FLINKON|CM_FLINKOFF, CM_FLINKON, 0, 0); + +#define DEFINE_SWITCH(sname, stype, sarg) \ +{ .name = sname, \ + .iface = stype, \ + .info = snd_cmipci_uswitch_info, \ + .get = snd_cmipci_uswitch_get, \ + .put = snd_cmipci_uswitch_put, \ + .private_value = (unsigned long)&cmipci_switch_arg_##sarg,\ +} + +#define DEFINE_CARD_SWITCH(sname, sarg) DEFINE_SWITCH(sname, SNDRV_CTL_ELEM_IFACE_CARD, sarg) +#define DEFINE_MIXER_SWITCH(sname, sarg) DEFINE_SWITCH(sname, SNDRV_CTL_ELEM_IFACE_MIXER, sarg) + + +/* + * callbacks for spdif output switch + * needs toggle two registers.. + */ +static int snd_cmipci_spdout_enable_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int changed; + changed = _snd_cmipci_uswitch_get(kcontrol, ucontrol, &cmipci_switch_arg_spdif_enable); + changed |= _snd_cmipci_uswitch_get(kcontrol, ucontrol, &cmipci_switch_arg_spdo2dac); + return changed; +} + +static int snd_cmipci_spdout_enable_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *chip = snd_kcontrol_chip(kcontrol); + int changed; + changed = _snd_cmipci_uswitch_put(kcontrol, ucontrol, &cmipci_switch_arg_spdif_enable); + changed |= _snd_cmipci_uswitch_put(kcontrol, ucontrol, &cmipci_switch_arg_spdo2dac); + if (changed) { + if (ucontrol->value.integer.value[0]) { + if (chip->spdif_playback_avail) + snd_cmipci_set_bit(chip, CM_REG_FUNCTRL1, CM_PLAYBACK_SPDF); + } else { + if (chip->spdif_playback_avail) + snd_cmipci_clear_bit(chip, CM_REG_FUNCTRL1, CM_PLAYBACK_SPDF); + } + } + chip->spdif_playback_enabled = ucontrol->value.integer.value[0]; + return changed; +} + + +static int snd_cmipci_line_in_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + static const char *const texts[3] = { + "Line-In", "Rear Output", "Bass Output" + }; + + return snd_ctl_enum_info(uinfo, 1, + cm->chip_version >= 39 ? 3 : 2, texts); +} + +static inline unsigned int get_line_in_mode(struct cmipci *cm) +{ + unsigned int val; + if (cm->chip_version >= 39) { + val = snd_cmipci_read(cm, CM_REG_LEGACY_CTRL); + if (val & (CM_CENTR2LIN | CM_BASE2LIN)) + return 2; + } + val = snd_cmipci_read_b(cm, CM_REG_MIXER1); + if (val & CM_REAR2LIN) + return 1; + return 0; +} + +static int snd_cmipci_line_in_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&cm->reg_lock); + ucontrol->value.enumerated.item[0] = get_line_in_mode(cm); + return 0; +} + +static int snd_cmipci_line_in_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + int change; + + guard(spinlock_irq)(&cm->reg_lock); + if (ucontrol->value.enumerated.item[0] == 2) + change = snd_cmipci_set_bit(cm, CM_REG_LEGACY_CTRL, CM_CENTR2LIN | CM_BASE2LIN); + else + change = snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_CENTR2LIN | CM_BASE2LIN); + if (ucontrol->value.enumerated.item[0] == 1) + change |= snd_cmipci_set_bit_b(cm, CM_REG_MIXER1, CM_REAR2LIN); + else + change |= snd_cmipci_clear_bit_b(cm, CM_REG_MIXER1, CM_REAR2LIN); + return change; +} + +static int snd_cmipci_mic_in_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[2] = { "Mic-In", "Center/LFE Output" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_cmipci_mic_in_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + + /* same bit as spdi_phase */ + guard(spinlock_irq)(&cm->reg_lock); + ucontrol->value.enumerated.item[0] = + (snd_cmipci_read_b(cm, CM_REG_MISC) & CM_SPDIF_INVERSE) ? 1 : 0; + return 0; +} + +static int snd_cmipci_mic_in_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + int change; + + guard(spinlock_irq)(&cm->reg_lock); + if (ucontrol->value.enumerated.item[0]) + change = snd_cmipci_set_bit_b(cm, CM_REG_MISC, CM_SPDIF_INVERSE); + else + change = snd_cmipci_clear_bit_b(cm, CM_REG_MISC, CM_SPDIF_INVERSE); + return change; +} + +/* both for CM8338/8738 */ +static const struct snd_kcontrol_new snd_cmipci_mixer_switches[] = { + DEFINE_MIXER_SWITCH("Four Channel Mode", fourch), + { + .name = "Line-In Mode", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_cmipci_line_in_mode_info, + .get = snd_cmipci_line_in_mode_get, + .put = snd_cmipci_line_in_mode_put, + }, +}; + +/* for non-multichannel chips */ +static const struct snd_kcontrol_new snd_cmipci_nomulti_switch = +DEFINE_MIXER_SWITCH("Exchange DAC", exchange_dac); + +/* only for CM8738 */ +static const struct snd_kcontrol_new snd_cmipci_8738_mixer_switches[] = { +#if 0 /* controlled in pcm device */ + DEFINE_MIXER_SWITCH("IEC958 In Record", spdif_in), + DEFINE_MIXER_SWITCH("IEC958 Out", spdif_out), + DEFINE_MIXER_SWITCH("IEC958 Out To DAC", spdo2dac), +#endif + // DEFINE_MIXER_SWITCH("IEC958 Output Switch", spdif_enable), + { .name = "IEC958 Output Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_cmipci_uswitch_info, + .get = snd_cmipci_spdout_enable_get, + .put = snd_cmipci_spdout_enable_put, + }, + DEFINE_MIXER_SWITCH("IEC958 In Valid", spdi_valid), + DEFINE_MIXER_SWITCH("IEC958 Copyright", spdif_copyright), + DEFINE_MIXER_SWITCH("IEC958 5V", spdo_5v), +// DEFINE_MIXER_SWITCH("IEC958 In/Out 48KHz", spdo_48k), + DEFINE_MIXER_SWITCH("IEC958 Loop", spdif_loop), + DEFINE_MIXER_SWITCH("IEC958 In Monitor", spdi_monitor), +}; + +/* only for model 033/037 */ +static const struct snd_kcontrol_new snd_cmipci_old_mixer_switches[] = { + DEFINE_MIXER_SWITCH("IEC958 Mix Analog", spdif_dac_out), + DEFINE_MIXER_SWITCH("IEC958 In Phase Inverse", spdi_phase), + DEFINE_MIXER_SWITCH("IEC958 In Select", spdif_in_sel1), +}; + +/* only for model 039 or later */ +static const struct snd_kcontrol_new snd_cmipci_extra_mixer_switches[] = { + DEFINE_MIXER_SWITCH("IEC958 In Select", spdif_in_sel2), + DEFINE_MIXER_SWITCH("IEC958 In Phase Inverse", spdi_phase2), + { + .name = "Mic-In Mode", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_cmipci_mic_in_mode_info, + .get = snd_cmipci_mic_in_mode_get, + .put = snd_cmipci_mic_in_mode_put, + } +}; + +/* card control switches */ +static const struct snd_kcontrol_new snd_cmipci_modem_switch = +DEFINE_CARD_SWITCH("Modem", modem); + + +static int snd_cmipci_mixer_new(struct cmipci *cm, int pcm_spdif_device) +{ + struct snd_card *card; + const struct snd_kcontrol_new *sw; + struct snd_kcontrol *kctl; + unsigned int idx; + int err; + + if (snd_BUG_ON(!cm || !cm->card)) + return -EINVAL; + + card = cm->card; + + strscpy(card->mixername, "CMedia PCI"); + + scoped_guard(spinlock_irq, &cm->reg_lock) { + snd_cmipci_mixer_write(cm, 0x00, 0x00); /* mixer reset */ + } + + for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_mixers); idx++) { + if (cm->chip_version == 68) { // 8768 has no PCM volume + if (!strcmp(snd_cmipci_mixers[idx].name, + "PCM Playback Volume")) + continue; + } + err = snd_ctl_add(card, snd_ctl_new1(&snd_cmipci_mixers[idx], cm)); + if (err < 0) + return err; + } + + /* mixer switches */ + sw = snd_cmipci_mixer_switches; + for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_mixer_switches); idx++, sw++) { + err = snd_ctl_add(cm->card, snd_ctl_new1(sw, cm)); + if (err < 0) + return err; + } + if (! cm->can_multi_ch) { + err = snd_ctl_add(cm->card, snd_ctl_new1(&snd_cmipci_nomulti_switch, cm)); + if (err < 0) + return err; + } + if (cm->device == PCI_DEVICE_ID_CMEDIA_CM8738 || + cm->device == PCI_DEVICE_ID_CMEDIA_CM8738B) { + sw = snd_cmipci_8738_mixer_switches; + for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_8738_mixer_switches); idx++, sw++) { + err = snd_ctl_add(cm->card, snd_ctl_new1(sw, cm)); + if (err < 0) + return err; + } + if (cm->can_ac3_hw) { + kctl = snd_ctl_new1(&snd_cmipci_spdif_default, cm); + if (!kctl) + return -ENOMEM; + kctl->id.device = pcm_spdif_device; + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + kctl = snd_ctl_new1(&snd_cmipci_spdif_mask, cm); + if (!kctl) + return -ENOMEM; + kctl->id.device = pcm_spdif_device; + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + kctl = snd_ctl_new1(&snd_cmipci_spdif_stream, cm); + if (!kctl) + return -ENOMEM; + kctl->id.device = pcm_spdif_device; + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + if (cm->chip_version <= 37) { + sw = snd_cmipci_old_mixer_switches; + for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_old_mixer_switches); idx++, sw++) { + err = snd_ctl_add(cm->card, snd_ctl_new1(sw, cm)); + if (err < 0) + return err; + } + } + } + if (cm->chip_version >= 39) { + sw = snd_cmipci_extra_mixer_switches; + for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_extra_mixer_switches); idx++, sw++) { + err = snd_ctl_add(cm->card, snd_ctl_new1(sw, cm)); + if (err < 0) + return err; + } + } + + /* card switches */ + /* + * newer chips don't have the register bits to force modem link + * detection; the bit that was FLINKON now mutes CH1 + */ + if (cm->chip_version < 39) { + err = snd_ctl_add(cm->card, + snd_ctl_new1(&snd_cmipci_modem_switch, cm)); + if (err < 0) + return err; + } + + for (idx = 0; idx < CM_SAVED_MIXERS; idx++) { + struct snd_kcontrol *ctl; + ctl = snd_ctl_find_id_mixer(cm->card, cm_saved_mixer[idx].name); + if (ctl) + cm->mixer_res_ctl[idx] = ctl; + } + + return 0; +} + + +/* + * proc interface + */ + +static void snd_cmipci_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct cmipci *cm = entry->private_data; + int i, v; + + snd_iprintf(buffer, "%s\n", cm->card->longname); + for (i = 0; i < 0x94; i++) { + if (i == 0x28) + i = 0x90; + v = inb(cm->iobase + i); + if (i % 4 == 0) + snd_iprintf(buffer, "\n%02x:", i); + snd_iprintf(buffer, " %02x", v); + } + snd_iprintf(buffer, "\n"); +} + +static void snd_cmipci_proc_init(struct cmipci *cm) +{ + snd_card_ro_proc_new(cm->card, "cmipci", cm, snd_cmipci_proc_read); +} + +static const struct pci_device_id snd_cmipci_ids[] = { + {PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338A) }, + {PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338B) }, + {PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738) }, + {PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738B) }, + {PCI_VDEVICE(AL, PCI_DEVICE_ID_CMEDIA_CM8738) }, + { } +}; + + +/* + * check chip version and capabilities + * driver name is modified according to the chip model + */ +static void query_chip(struct cmipci *cm) +{ + unsigned int detect; + + /* check reg 0Ch, bit 24-31 */ + detect = snd_cmipci_read(cm, CM_REG_INT_HLDCLR) & CM_CHIP_MASK2; + if (! detect) { + /* check reg 08h, bit 24-28 */ + detect = snd_cmipci_read(cm, CM_REG_CHFORMAT) & CM_CHIP_MASK1; + switch (detect) { + case 0: + cm->chip_version = 33; + if (cm->do_soft_ac3) + cm->can_ac3_sw = 1; + else + cm->can_ac3_hw = 1; + break; + case CM_CHIP_037: + cm->chip_version = 37; + cm->can_ac3_hw = 1; + break; + default: + cm->chip_version = 39; + cm->can_ac3_hw = 1; + break; + } + cm->max_channels = 2; + } else { + if (detect & CM_CHIP_039) { + cm->chip_version = 39; + if (detect & CM_CHIP_039_6CH) /* 4 or 6 channels */ + cm->max_channels = 6; + else + cm->max_channels = 4; + } else if (detect & CM_CHIP_8768) { + cm->chip_version = 68; + cm->max_channels = 8; + cm->can_96k = 1; + } else { + cm->chip_version = 55; + cm->max_channels = 6; + cm->can_96k = 1; + } + cm->can_ac3_hw = 1; + cm->can_multi_ch = 1; + } +} + +#ifdef SUPPORT_JOYSTICK +static int snd_cmipci_create_gameport(struct cmipci *cm, int dev) +{ + static const int ports[] = { 0x201, 0x200, 0 }; /* FIXME: majority is 0x201? */ + struct gameport *gp; + struct resource *r = NULL; + int i, io_port = 0; + + if (joystick_port[dev] == 0) + return -ENODEV; + + if (joystick_port[dev] == 1) { /* auto-detect */ + for (i = 0; ports[i]; i++) { + io_port = ports[i]; + r = devm_request_region(&cm->pci->dev, io_port, 1, + "CMIPCI gameport"); + if (r) + break; + } + } else { + io_port = joystick_port[dev]; + r = devm_request_region(&cm->pci->dev, io_port, 1, + "CMIPCI gameport"); + } + + if (!r) { + dev_warn(cm->card->dev, "cannot reserve joystick ports\n"); + return -EBUSY; + } + + cm->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(cm->card->dev, "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + gameport_set_name(gp, "C-Media Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(cm->pci)); + gameport_set_dev_parent(gp, &cm->pci->dev); + gp->io = io_port; + + snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_JYSTK_EN); + + gameport_register_port(cm->gameport); + + return 0; +} + +static void snd_cmipci_free_gameport(struct cmipci *cm) +{ + if (cm->gameport) { + gameport_unregister_port(cm->gameport); + cm->gameport = NULL; + + snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_JYSTK_EN); + } +} +#else +static inline int snd_cmipci_create_gameport(struct cmipci *cm, int dev) { return -ENOSYS; } +static inline void snd_cmipci_free_gameport(struct cmipci *cm) { } +#endif + +static void snd_cmipci_free(struct snd_card *card) +{ + struct cmipci *cm = card->private_data; + + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_FM_EN); + snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_ENSPDOUT); + snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0); /* disable ints */ + snd_cmipci_ch_reset(cm, CM_CH_PLAY); + snd_cmipci_ch_reset(cm, CM_CH_CAPT); + snd_cmipci_write(cm, CM_REG_FUNCTRL0, 0); /* disable channels */ + snd_cmipci_write(cm, CM_REG_FUNCTRL1, 0); + + /* reset mixer */ + snd_cmipci_mixer_write(cm, 0, 0); + + snd_cmipci_free_gameport(cm); +} + +static int snd_cmipci_create_fm(struct cmipci *cm, long fm_port) +{ + long iosynth; + unsigned int val; + struct snd_opl3 *opl3; + int err; + + if (!fm_port) + goto disable_fm; + + if (cm->chip_version >= 39) { + /* first try FM regs in PCI port range */ + iosynth = cm->iobase + CM_REG_FM_PCI; + err = snd_opl3_create(cm->card, iosynth, iosynth + 2, + OPL3_HW_OPL3, 1, &opl3); + } else { + err = -EIO; + } + if (err < 0) { + /* then try legacy ports */ + val = snd_cmipci_read(cm, CM_REG_LEGACY_CTRL) & ~CM_FMSEL_MASK; + iosynth = fm_port; + switch (iosynth) { + case 0x3E8: val |= CM_FMSEL_3E8; break; + case 0x3E0: val |= CM_FMSEL_3E0; break; + case 0x3C8: val |= CM_FMSEL_3C8; break; + case 0x388: val |= CM_FMSEL_388; break; + default: + goto disable_fm; + } + snd_cmipci_write(cm, CM_REG_LEGACY_CTRL, val); + /* enable FM */ + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_FM_EN); + + if (snd_opl3_create(cm->card, iosynth, iosynth + 2, + OPL3_HW_OPL3, 0, &opl3) < 0) { + dev_err(cm->card->dev, + "no OPL device at %#lx, skipping...\n", + iosynth); + goto disable_fm; + } + } + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) { + dev_err(cm->card->dev, "cannot create OPL3 hwdep\n"); + return err; + } + return 0; + + disable_fm: + snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_FMSEL_MASK); + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_FM_EN); + return 0; +} + +static int snd_cmipci_create(struct snd_card *card, struct pci_dev *pci, + int dev) +{ + struct cmipci *cm = card->private_data; + int err; + unsigned int val; + long iomidi = 0; + int integrated_midi = 0; + char modelstr[16]; + int pcm_index, pcm_spdif_index; + static const struct pci_device_id intel_82437vx[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437VX) }, + { }, + }; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&cm->reg_lock); + mutex_init(&cm->open_mutex); + cm->device = pci->device; + cm->card = card; + cm->pci = pci; + cm->irq = -1; + cm->channel[0].ch = 0; + cm->channel[1].ch = 1; + cm->channel[0].is_dac = cm->channel[1].is_dac = 1; /* dual DAC mode */ + + err = pcim_request_all_regions(pci, card->driver); + if (err < 0) + return err; + cm->iobase = pci_resource_start(pci, 0); + + if (devm_request_irq(&pci->dev, pci->irq, snd_cmipci_interrupt, + IRQF_SHARED, KBUILD_MODNAME, cm)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + cm->irq = pci->irq; + card->sync_irq = cm->irq; + card->private_free = snd_cmipci_free; + + pci_set_master(cm->pci); + + /* + * check chip version, max channels and capabilities + */ + + cm->chip_version = 0; + cm->max_channels = 2; + cm->do_soft_ac3 = soft_ac3[dev]; + + if (pci->device != PCI_DEVICE_ID_CMEDIA_CM8338A && + pci->device != PCI_DEVICE_ID_CMEDIA_CM8338B) + query_chip(cm); + /* added -MCx suffix for chip supporting multi-channels */ + if (cm->can_multi_ch) { + int l = strlen(cm->card->driver); + scnprintf(cm->card->driver + l, sizeof(cm->card->driver) - l, + "-MC%d", cm->max_channels); + } else if (cm->can_ac3_sw) + strlcat(cm->card->driver, "-SWIEC", sizeof(cm->card->driver)); + + cm->dig_status = SNDRV_PCM_DEFAULT_CON_SPDIF; + cm->dig_pcm_status = SNDRV_PCM_DEFAULT_CON_SPDIF; + +#if CM_CH_PLAY == 1 + cm->ctrl = CM_CHADC0; /* default FUNCNTRL0 */ +#else + cm->ctrl = CM_CHADC1; /* default FUNCNTRL0 */ +#endif + + /* initialize codec registers */ + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_RESET); + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_RESET); + snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0); /* disable ints */ + snd_cmipci_ch_reset(cm, CM_CH_PLAY); + snd_cmipci_ch_reset(cm, CM_CH_CAPT); + snd_cmipci_write(cm, CM_REG_FUNCTRL0, 0); /* disable channels */ + snd_cmipci_write(cm, CM_REG_FUNCTRL1, 0); + + snd_cmipci_write(cm, CM_REG_CHFORMAT, 0); + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_ENDBDAC|CM_N4SPK3D); +#if CM_CH_PLAY == 1 + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_XCHGDAC); +#else + snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_XCHGDAC); +#endif + if (cm->chip_version) { + snd_cmipci_write_b(cm, CM_REG_EXT_MISC, 0x20); /* magic */ + snd_cmipci_write_b(cm, CM_REG_EXT_MISC + 1, 0x09); /* more magic */ + } + /* Set Bus Master Request */ + snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_BREQ); + + /* Assume TX and compatible chip set (Autodetection required for VX chip sets) */ + switch (pci->device) { + case PCI_DEVICE_ID_CMEDIA_CM8738: + case PCI_DEVICE_ID_CMEDIA_CM8738B: + if (!pci_dev_present(intel_82437vx)) + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_TXVX); + break; + default: + break; + } + + if (cm->chip_version < 68) { + val = pci->device < 0x110 ? 8338 : 8738; + } else { + switch (snd_cmipci_read_b(cm, CM_REG_INT_HLDCLR + 3) & 0x03) { + case 0: + val = 8769; + break; + case 2: + val = 8762; + break; + default: + switch ((pci->subsystem_vendor << 16) | + pci->subsystem_device) { + case 0x13f69761: + case 0x584d3741: + case 0x584d3751: + case 0x584d3761: + case 0x584d3771: + case 0x72848384: + val = 8770; + break; + default: + val = 8768; + break; + } + } + } + sprintf(card->shortname, "C-Media CMI%u", val); + if (cm->chip_version < 68) + scnprintf(modelstr, sizeof(modelstr), + " (model %d)", cm->chip_version); + else + modelstr[0] = '\0'; + scnprintf(card->longname, sizeof(card->longname), + "%s%s at %#lx, irq %i", + card->shortname, modelstr, cm->iobase, cm->irq); + + if (cm->chip_version >= 39) { + val = snd_cmipci_read_b(cm, CM_REG_MPU_PCI + 1); + if (val != 0x00 && val != 0xff) { + if (mpu_port[dev]) + iomidi = cm->iobase + CM_REG_MPU_PCI; + integrated_midi = 1; + } + } + if (!integrated_midi) { + val = 0; + iomidi = mpu_port[dev]; + switch (iomidi) { + case 0x320: val = CM_VMPU_320; break; + case 0x310: val = CM_VMPU_310; break; + case 0x300: val = CM_VMPU_300; break; + case 0x330: val = CM_VMPU_330; break; + default: + iomidi = 0; break; + } + if (iomidi > 0) { + snd_cmipci_write(cm, CM_REG_LEGACY_CTRL, val); + /* enable UART */ + snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_UART_EN); + if (inb(iomidi + 1) == 0xff) { + dev_err(cm->card->dev, + "cannot enable MPU-401 port at %#lx\n", + iomidi); + snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, + CM_UART_EN); + iomidi = 0; + } + } + } + + if (cm->chip_version < 68) { + err = snd_cmipci_create_fm(cm, fm_port[dev]); + if (err < 0) + return err; + } + + /* reset mixer */ + snd_cmipci_mixer_write(cm, 0, 0); + + snd_cmipci_proc_init(cm); + + /* create pcm devices */ + pcm_index = pcm_spdif_index = 0; + err = snd_cmipci_pcm_new(cm, pcm_index); + if (err < 0) + return err; + pcm_index++; + err = snd_cmipci_pcm2_new(cm, pcm_index); + if (err < 0) + return err; + pcm_index++; + if (cm->can_ac3_hw || cm->can_ac3_sw) { + pcm_spdif_index = pcm_index; + err = snd_cmipci_pcm_spdif_new(cm, pcm_index); + if (err < 0) + return err; + } + + /* create mixer interface & switches */ + err = snd_cmipci_mixer_new(cm, pcm_spdif_index); + if (err < 0) + return err; + + if (iomidi > 0) { + err = snd_mpu401_uart_new(card, 0, MPU401_HW_CMIPCI, + iomidi, + (integrated_midi ? + MPU401_INFO_INTEGRATED : 0) | + MPU401_INFO_IRQ_HOOK, + -1, &cm->rmidi); + if (err < 0) + dev_err(cm->card->dev, + "no UART401 device at 0x%lx\n", iomidi); + } + +#ifdef USE_VAR48KRATE + for (val = 0; val < ARRAY_SIZE(rates); val++) + snd_cmipci_set_pll(cm, rates[val], val); + + /* + * (Re-)Enable external switch spdo_48k + */ + snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPDIF48K|CM_SPDF_AC97); +#endif /* USE_VAR48KRATE */ + + if (snd_cmipci_create_gameport(cm, dev) < 0) + snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_JYSTK_EN); + + return 0; +} + +/* + */ + +MODULE_DEVICE_TABLE(pci, snd_cmipci_ids); + +static int snd_cmipci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (! enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct cmipci), &card); + if (err < 0) + return err; + + switch (pci->device) { + case PCI_DEVICE_ID_CMEDIA_CM8738: + case PCI_DEVICE_ID_CMEDIA_CM8738B: + strscpy(card->driver, "CMI8738"); + break; + case PCI_DEVICE_ID_CMEDIA_CM8338A: + case PCI_DEVICE_ID_CMEDIA_CM8338B: + strscpy(card->driver, "CMI8338"); + break; + default: + strscpy(card->driver, "CMIPCI"); + break; + } + + err = snd_cmipci_create(card, pci, dev); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + pci_set_drvdata(pci, card); + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +/* + * power management + */ +static const unsigned char saved_regs[] = { + CM_REG_FUNCTRL1, CM_REG_CHFORMAT, CM_REG_LEGACY_CTRL, CM_REG_MISC_CTRL, + CM_REG_MIXER0, CM_REG_MIXER1, CM_REG_MIXER2, CM_REG_AUX_VOL, CM_REG_PLL, + CM_REG_CH0_FRAME1, CM_REG_CH0_FRAME2, + CM_REG_CH1_FRAME1, CM_REG_CH1_FRAME2, CM_REG_EXT_MISC, + CM_REG_INT_STATUS, CM_REG_INT_HLDCLR, CM_REG_FUNCTRL0, +}; + +static const unsigned char saved_mixers[] = { + SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1, + SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1, + SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1, + 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, + CM_REG_EXTENT_IND, SB_DSP4_OUTPUT_SW, + SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, +}; + +static int snd_cmipci_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct cmipci *cm = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + /* save registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + cm->saved_regs[i] = snd_cmipci_read(cm, saved_regs[i]); + for (i = 0; i < ARRAY_SIZE(saved_mixers); i++) + cm->saved_mixers[i] = snd_cmipci_mixer_read(cm, saved_mixers[i]); + + /* disable ints */ + snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0); + return 0; +} + +static int snd_cmipci_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct cmipci *cm = card->private_data; + int i; + + /* reset / initialize to a sane state */ + snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0); + snd_cmipci_ch_reset(cm, CM_CH_PLAY); + snd_cmipci_ch_reset(cm, CM_CH_CAPT); + snd_cmipci_mixer_write(cm, 0, 0); + + /* restore registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + snd_cmipci_write(cm, saved_regs[i], cm->saved_regs[i]); + for (i = 0; i < ARRAY_SIZE(saved_mixers); i++) + snd_cmipci_mixer_write(cm, saved_mixers[i], cm->saved_mixers[i]); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_cmipci_pm, snd_cmipci_suspend, snd_cmipci_resume); + +static struct pci_driver cmipci_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_cmipci_ids, + .probe = snd_cmipci_probe, + .driver = { + .pm = &snd_cmipci_pm, + }, +}; + +module_pci_driver(cmipci_driver); diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c new file mode 100644 index 000000000..f51f4bb63 --- /dev/null +++ b/sound/pci/cs4281.c @@ -0,0 +1,1983 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Cirrus Logic CS4281 based PCI soundcard + * Copyright (c) by Jaroslav Kysela , + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Cirrus Logic CS4281"); +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_PNP; /* Enable switches */ +static bool dual_codec[SNDRV_CARDS]; /* dual codec */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for CS4281 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for CS4281 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable CS4281 soundcard."); +module_param_array(dual_codec, bool, NULL, 0444); +MODULE_PARM_DESC(dual_codec, "Secondary Codec ID (0 = disabled)."); + +/* + * Direct registers + */ + +#define CS4281_BA0_SIZE 0x1000 +#define CS4281_BA1_SIZE 0x10000 + +/* + * BA0 registers + */ +#define BA0_HISR 0x0000 /* Host Interrupt Status Register */ +#define BA0_HISR_INTENA (1<<31) /* Internal Interrupt Enable Bit */ +#define BA0_HISR_MIDI (1<<22) /* MIDI port interrupt */ +#define BA0_HISR_FIFOI (1<<20) /* FIFO polled interrupt */ +#define BA0_HISR_DMAI (1<<18) /* DMA interrupt (half or end) */ +#define BA0_HISR_FIFO(c) (1<<(12+(c))) /* FIFO channel interrupt */ +#define BA0_HISR_DMA(c) (1<<(8+(c))) /* DMA channel interrupt */ +#define BA0_HISR_GPPI (1<<5) /* General Purpose Input (Primary chip) */ +#define BA0_HISR_GPSI (1<<4) /* General Purpose Input (Secondary chip) */ +#define BA0_HISR_GP3I (1<<3) /* GPIO3 pin Interrupt */ +#define BA0_HISR_GP1I (1<<2) /* GPIO1 pin Interrupt */ +#define BA0_HISR_VUPI (1<<1) /* VOLUP pin Interrupt */ +#define BA0_HISR_VDNI (1<<0) /* VOLDN pin Interrupt */ + +#define BA0_HICR 0x0008 /* Host Interrupt Control Register */ +#define BA0_HICR_CHGM (1<<1) /* INTENA Change Mask */ +#define BA0_HICR_IEV (1<<0) /* INTENA Value */ +#define BA0_HICR_EOI (3<<0) /* End of Interrupt command */ + +#define BA0_HIMR 0x000c /* Host Interrupt Mask Register */ + /* Use same contants as for BA0_HISR */ + +#define BA0_IIER 0x0010 /* ISA Interrupt Enable Register */ + +#define BA0_HDSR0 0x00f0 /* Host DMA Engine 0 Status Register */ +#define BA0_HDSR1 0x00f4 /* Host DMA Engine 1 Status Register */ +#define BA0_HDSR2 0x00f8 /* Host DMA Engine 2 Status Register */ +#define BA0_HDSR3 0x00fc /* Host DMA Engine 3 Status Register */ + +#define BA0_HDSR_CH1P (1<<25) /* Channel 1 Pending */ +#define BA0_HDSR_CH2P (1<<24) /* Channel 2 Pending */ +#define BA0_HDSR_DHTC (1<<17) /* DMA Half Terminal Count */ +#define BA0_HDSR_DTC (1<<16) /* DMA Terminal Count */ +#define BA0_HDSR_DRUN (1<<15) /* DMA Running */ +#define BA0_HDSR_RQ (1<<7) /* Pending Request */ + +#define BA0_DCA0 0x0110 /* Host DMA Engine 0 Current Address */ +#define BA0_DCC0 0x0114 /* Host DMA Engine 0 Current Count */ +#define BA0_DBA0 0x0118 /* Host DMA Engine 0 Base Address */ +#define BA0_DBC0 0x011c /* Host DMA Engine 0 Base Count */ +#define BA0_DCA1 0x0120 /* Host DMA Engine 1 Current Address */ +#define BA0_DCC1 0x0124 /* Host DMA Engine 1 Current Count */ +#define BA0_DBA1 0x0128 /* Host DMA Engine 1 Base Address */ +#define BA0_DBC1 0x012c /* Host DMA Engine 1 Base Count */ +#define BA0_DCA2 0x0130 /* Host DMA Engine 2 Current Address */ +#define BA0_DCC2 0x0134 /* Host DMA Engine 2 Current Count */ +#define BA0_DBA2 0x0138 /* Host DMA Engine 2 Base Address */ +#define BA0_DBC2 0x013c /* Host DMA Engine 2 Base Count */ +#define BA0_DCA3 0x0140 /* Host DMA Engine 3 Current Address */ +#define BA0_DCC3 0x0144 /* Host DMA Engine 3 Current Count */ +#define BA0_DBA3 0x0148 /* Host DMA Engine 3 Base Address */ +#define BA0_DBC3 0x014c /* Host DMA Engine 3 Base Count */ +#define BA0_DMR0 0x0150 /* Host DMA Engine 0 Mode */ +#define BA0_DCR0 0x0154 /* Host DMA Engine 0 Command */ +#define BA0_DMR1 0x0158 /* Host DMA Engine 1 Mode */ +#define BA0_DCR1 0x015c /* Host DMA Engine 1 Command */ +#define BA0_DMR2 0x0160 /* Host DMA Engine 2 Mode */ +#define BA0_DCR2 0x0164 /* Host DMA Engine 2 Command */ +#define BA0_DMR3 0x0168 /* Host DMA Engine 3 Mode */ +#define BA0_DCR3 0x016c /* Host DMA Engine 3 Command */ + +#define BA0_DMR_DMA (1<<29) /* Enable DMA mode */ +#define BA0_DMR_POLL (1<<28) /* Enable poll mode */ +#define BA0_DMR_TBC (1<<25) /* Transfer By Channel */ +#define BA0_DMR_CBC (1<<24) /* Count By Channel (0 = frame resolution) */ +#define BA0_DMR_SWAPC (1<<22) /* Swap Left/Right Channels */ +#define BA0_DMR_SIZE20 (1<<20) /* Sample is 20-bit */ +#define BA0_DMR_USIGN (1<<19) /* Unsigned */ +#define BA0_DMR_BEND (1<<18) /* Big Endian */ +#define BA0_DMR_MONO (1<<17) /* Mono */ +#define BA0_DMR_SIZE8 (1<<16) /* Sample is 8-bit */ +#define BA0_DMR_TYPE_DEMAND (0<<6) +#define BA0_DMR_TYPE_SINGLE (1<<6) +#define BA0_DMR_TYPE_BLOCK (2<<6) +#define BA0_DMR_TYPE_CASCADE (3<<6) /* Not supported */ +#define BA0_DMR_DEC (1<<5) /* Access Increment (0) or Decrement (1) */ +#define BA0_DMR_AUTO (1<<4) /* Auto-Initialize */ +#define BA0_DMR_TR_VERIFY (0<<2) /* Verify Transfer */ +#define BA0_DMR_TR_WRITE (1<<2) /* Write Transfer */ +#define BA0_DMR_TR_READ (2<<2) /* Read Transfer */ + +#define BA0_DCR_HTCIE (1<<17) /* Half Terminal Count Interrupt */ +#define BA0_DCR_TCIE (1<<16) /* Terminal Count Interrupt */ +#define BA0_DCR_MSK (1<<0) /* DMA Mask bit */ + +#define BA0_FCR0 0x0180 /* FIFO Control 0 */ +#define BA0_FCR1 0x0184 /* FIFO Control 1 */ +#define BA0_FCR2 0x0188 /* FIFO Control 2 */ +#define BA0_FCR3 0x018c /* FIFO Control 3 */ + +#define BA0_FCR_FEN (1<<31) /* FIFO Enable bit */ +#define BA0_FCR_DACZ (1<<30) /* DAC Zero */ +#define BA0_FCR_PSH (1<<29) /* Previous Sample Hold */ +#define BA0_FCR_RS(x) (((x)&0x1f)<<24) /* Right Slot Mapping */ +#define BA0_FCR_LS(x) (((x)&0x1f)<<16) /* Left Slot Mapping */ +#define BA0_FCR_SZ(x) (((x)&0x7f)<<8) /* FIFO buffer size (in samples) */ +#define BA0_FCR_OF(x) (((x)&0x7f)<<0) /* FIFO starting offset (in samples) */ + +#define BA0_FPDR0 0x0190 /* FIFO Polled Data 0 */ +#define BA0_FPDR1 0x0194 /* FIFO Polled Data 1 */ +#define BA0_FPDR2 0x0198 /* FIFO Polled Data 2 */ +#define BA0_FPDR3 0x019c /* FIFO Polled Data 3 */ + +#define BA0_FCHS 0x020c /* FIFO Channel Status */ +#define BA0_FCHS_RCO(x) (1<<(7+(((x)&3)<<3))) /* Right Channel Out */ +#define BA0_FCHS_LCO(x) (1<<(6+(((x)&3)<<3))) /* Left Channel Out */ +#define BA0_FCHS_MRP(x) (1<<(5+(((x)&3)<<3))) /* Move Read Pointer */ +#define BA0_FCHS_FE(x) (1<<(4+(((x)&3)<<3))) /* FIFO Empty */ +#define BA0_FCHS_FF(x) (1<<(3+(((x)&3)<<3))) /* FIFO Full */ +#define BA0_FCHS_IOR(x) (1<<(2+(((x)&3)<<3))) /* Internal Overrun Flag */ +#define BA0_FCHS_RCI(x) (1<<(1+(((x)&3)<<3))) /* Right Channel In */ +#define BA0_FCHS_LCI(x) (1<<(0+(((x)&3)<<3))) /* Left Channel In */ + +#define BA0_FSIC0 0x0210 /* FIFO Status and Interrupt Control 0 */ +#define BA0_FSIC1 0x0214 /* FIFO Status and Interrupt Control 1 */ +#define BA0_FSIC2 0x0218 /* FIFO Status and Interrupt Control 2 */ +#define BA0_FSIC3 0x021c /* FIFO Status and Interrupt Control 3 */ + +#define BA0_FSIC_FIC(x) (((x)&0x7f)<<24) /* FIFO Interrupt Count */ +#define BA0_FSIC_FORIE (1<<23) /* FIFO OverRun Interrupt Enable */ +#define BA0_FSIC_FURIE (1<<22) /* FIFO UnderRun Interrupt Enable */ +#define BA0_FSIC_FSCIE (1<<16) /* FIFO Sample Count Interrupt Enable */ +#define BA0_FSIC_FSC(x) (((x)&0x7f)<<8) /* FIFO Sample Count */ +#define BA0_FSIC_FOR (1<<7) /* FIFO OverRun */ +#define BA0_FSIC_FUR (1<<6) /* FIFO UnderRun */ +#define BA0_FSIC_FSCR (1<<0) /* FIFO Sample Count Reached */ + +#define BA0_PMCS 0x0344 /* Power Management Control/Status */ +#define BA0_CWPR 0x03e0 /* Configuration Write Protect */ + +#define BA0_EPPMC 0x03e4 /* Extended PCI Power Management Control */ +#define BA0_EPPMC_FPDN (1<<14) /* Full Power DowN */ + +#define BA0_GPIOR 0x03e8 /* GPIO Pin Interface Register */ + +#define BA0_SPMC 0x03ec /* Serial Port Power Management Control (& ASDIN2 enable) */ +#define BA0_SPMC_GIPPEN (1<<15) /* GP INT Primary PME# Enable */ +#define BA0_SPMC_GISPEN (1<<14) /* GP INT Secondary PME# Enable */ +#define BA0_SPMC_EESPD (1<<9) /* EEPROM Serial Port Disable */ +#define BA0_SPMC_ASDI2E (1<<8) /* ASDIN2 Enable */ +#define BA0_SPMC_ASDO (1<<7) /* Asynchronous ASDOUT Assertion */ +#define BA0_SPMC_WUP2 (1<<3) /* Wakeup for Secondary Input */ +#define BA0_SPMC_WUP1 (1<<2) /* Wakeup for Primary Input */ +#define BA0_SPMC_ASYNC (1<<1) /* Asynchronous ASYNC Assertion */ +#define BA0_SPMC_RSTN (1<<0) /* Reset Not! */ + +#define BA0_CFLR 0x03f0 /* Configuration Load Register (EEPROM or BIOS) */ +#define BA0_CFLR_DEFAULT 0x00000001 /* CFLR must be in AC97 link mode */ +#define BA0_IISR 0x03f4 /* ISA Interrupt Select */ +#define BA0_TMS 0x03f8 /* Test Register */ +#define BA0_SSVID 0x03fc /* Subsystem ID register */ + +#define BA0_CLKCR1 0x0400 /* Clock Control Register 1 */ +#define BA0_CLKCR1_CLKON (1<<25) /* Read Only */ +#define BA0_CLKCR1_DLLRDY (1<<24) /* DLL Ready */ +#define BA0_CLKCR1_DLLOS (1<<6) /* DLL Output Select */ +#define BA0_CLKCR1_SWCE (1<<5) /* Clock Enable */ +#define BA0_CLKCR1_DLLP (1<<4) /* DLL PowerUp */ +#define BA0_CLKCR1_DLLSS (((x)&3)<<3) /* DLL Source Select */ + +#define BA0_FRR 0x0410 /* Feature Reporting Register */ +#define BA0_SLT12O 0x041c /* Slot 12 GPIO Output Register for AC-Link */ + +#define BA0_SERMC 0x0420 /* Serial Port Master Control */ +#define BA0_SERMC_FCRN (1<<27) /* Force Codec Ready Not */ +#define BA0_SERMC_ODSEN2 (1<<25) /* On-Demand Support Enable ASDIN2 */ +#define BA0_SERMC_ODSEN1 (1<<24) /* On-Demand Support Enable ASDIN1 */ +#define BA0_SERMC_SXLB (1<<21) /* ASDIN2 to ASDOUT Loopback */ +#define BA0_SERMC_SLB (1<<20) /* ASDOUT to ASDIN2 Loopback */ +#define BA0_SERMC_LOVF (1<<19) /* Loopback Output Valid Frame bit */ +#define BA0_SERMC_TCID(x) (((x)&3)<<16) /* Target Secondary Codec ID */ +#define BA0_SERMC_PXLB (5<<1) /* Primary Port External Loopback */ +#define BA0_SERMC_PLB (4<<1) /* Primary Port Internal Loopback */ +#define BA0_SERMC_PTC (7<<1) /* Port Timing Configuration */ +#define BA0_SERMC_PTC_AC97 (1<<1) /* AC97 mode */ +#define BA0_SERMC_MSPE (1<<0) /* Master Serial Port Enable */ + +#define BA0_SERC1 0x0428 /* Serial Port Configuration 1 */ +#define BA0_SERC1_SO1F(x) (((x)&7)>>1) /* Primary Output Port Format */ +#define BA0_SERC1_AC97 (1<<1) +#define BA0_SERC1_SO1EN (1<<0) /* Primary Output Port Enable */ + +#define BA0_SERC2 0x042c /* Serial Port Configuration 2 */ +#define BA0_SERC2_SI1F(x) (((x)&7)>>1) /* Primary Input Port Format */ +#define BA0_SERC2_AC97 (1<<1) +#define BA0_SERC2_SI1EN (1<<0) /* Primary Input Port Enable */ + +#define BA0_SLT12M 0x045c /* Slot 12 Monitor Register for Primary AC-Link */ + +#define BA0_ACCTL 0x0460 /* AC'97 Control */ +#define BA0_ACCTL_TC (1<<6) /* Target Codec */ +#define BA0_ACCTL_CRW (1<<4) /* 0=Write, 1=Read Command */ +#define BA0_ACCTL_DCV (1<<3) /* Dynamic Command Valid */ +#define BA0_ACCTL_VFRM (1<<2) /* Valid Frame */ +#define BA0_ACCTL_ESYN (1<<1) /* Enable Sync */ + +#define BA0_ACSTS 0x0464 /* AC'97 Status */ +#define BA0_ACSTS_VSTS (1<<1) /* Valid Status */ +#define BA0_ACSTS_CRDY (1<<0) /* Codec Ready */ + +#define BA0_ACOSV 0x0468 /* AC'97 Output Slot Valid */ +#define BA0_ACOSV_SLV(x) (1<<((x)-3)) + +#define BA0_ACCAD 0x046c /* AC'97 Command Address */ +#define BA0_ACCDA 0x0470 /* AC'97 Command Data */ + +#define BA0_ACISV 0x0474 /* AC'97 Input Slot Valid */ +#define BA0_ACISV_SLV(x) (1<<((x)-3)) + +#define BA0_ACSAD 0x0478 /* AC'97 Status Address */ +#define BA0_ACSDA 0x047c /* AC'97 Status Data */ +#define BA0_JSPT 0x0480 /* Joystick poll/trigger */ +#define BA0_JSCTL 0x0484 /* Joystick control */ +#define BA0_JSC1 0x0488 /* Joystick control */ +#define BA0_JSC2 0x048c /* Joystick control */ +#define BA0_JSIO 0x04a0 + +#define BA0_MIDCR 0x0490 /* MIDI Control */ +#define BA0_MIDCR_MRST (1<<5) /* Reset MIDI Interface */ +#define BA0_MIDCR_MLB (1<<4) /* MIDI Loop Back Enable */ +#define BA0_MIDCR_TIE (1<<3) /* MIDI Transmuit Interrupt Enable */ +#define BA0_MIDCR_RIE (1<<2) /* MIDI Receive Interrupt Enable */ +#define BA0_MIDCR_RXE (1<<1) /* MIDI Receive Enable */ +#define BA0_MIDCR_TXE (1<<0) /* MIDI Transmit Enable */ + +#define BA0_MIDCMD 0x0494 /* MIDI Command (wo) */ + +#define BA0_MIDSR 0x0494 /* MIDI Status (ro) */ +#define BA0_MIDSR_RDA (1<<15) /* Sticky bit (RBE 1->0) */ +#define BA0_MIDSR_TBE (1<<14) /* Sticky bit (TBF 0->1) */ +#define BA0_MIDSR_RBE (1<<7) /* Receive Buffer Empty */ +#define BA0_MIDSR_TBF (1<<6) /* Transmit Buffer Full */ + +#define BA0_MIDWP 0x0498 /* MIDI Write */ +#define BA0_MIDRP 0x049c /* MIDI Read (ro) */ + +#define BA0_AODSD1 0x04a8 /* AC'97 On-Demand Slot Disable for primary link (ro) */ +#define BA0_AODSD1_NDS(x) (1<<((x)-3)) + +#define BA0_AODSD2 0x04ac /* AC'97 On-Demand Slot Disable for secondary link (ro) */ +#define BA0_AODSD2_NDS(x) (1<<((x)-3)) + +#define BA0_CFGI 0x04b0 /* Configure Interface (EEPROM interface) */ +#define BA0_SLT12M2 0x04dc /* Slot 12 Monitor Register 2 for secondary AC-link */ +#define BA0_ACSTS2 0x04e4 /* AC'97 Status Register 2 */ +#define BA0_ACISV2 0x04f4 /* AC'97 Input Slot Valid Register 2 */ +#define BA0_ACSAD2 0x04f8 /* AC'97 Status Address Register 2 */ +#define BA0_ACSDA2 0x04fc /* AC'97 Status Data Register 2 */ +#define BA0_FMSR 0x0730 /* FM Synthesis Status (ro) */ +#define BA0_B0AP 0x0730 /* FM Bank 0 Address Port (wo) */ +#define BA0_FMDP 0x0734 /* FM Data Port */ +#define BA0_B1AP 0x0738 /* FM Bank 1 Address Port */ +#define BA0_B1DP 0x073c /* FM Bank 1 Data Port */ + +#define BA0_SSPM 0x0740 /* Sound System Power Management */ +#define BA0_SSPM_MIXEN (1<<6) /* Playback SRC + FM/Wavetable MIX */ +#define BA0_SSPM_CSRCEN (1<<5) /* Capture Sample Rate Converter Enable */ +#define BA0_SSPM_PSRCEN (1<<4) /* Playback Sample Rate Converter Enable */ +#define BA0_SSPM_JSEN (1<<3) /* Joystick Enable */ +#define BA0_SSPM_ACLEN (1<<2) /* Serial Port Engine and AC-Link Enable */ +#define BA0_SSPM_FMEN (1<<1) /* FM Synthesis Block Enable */ + +#define BA0_DACSR 0x0744 /* DAC Sample Rate - Playback SRC */ +#define BA0_ADCSR 0x0748 /* ADC Sample Rate - Capture SRC */ + +#define BA0_SSCR 0x074c /* Sound System Control Register */ +#define BA0_SSCR_HVS1 (1<<23) /* Hardwave Volume Step (0=1,1=2) */ +#define BA0_SSCR_MVCS (1<<19) /* Master Volume Codec Select */ +#define BA0_SSCR_MVLD (1<<18) /* Master Volume Line Out Disable */ +#define BA0_SSCR_MVAD (1<<17) /* Master Volume Alternate Out Disable */ +#define BA0_SSCR_MVMD (1<<16) /* Master Volume Mono Out Disable */ +#define BA0_SSCR_XLPSRC (1<<8) /* External SRC Loopback Mode */ +#define BA0_SSCR_LPSRC (1<<7) /* SRC Loopback Mode */ +#define BA0_SSCR_CDTX (1<<5) /* CD Transfer Data */ +#define BA0_SSCR_HVC (1<<3) /* Harware Volume Control Enable */ + +#define BA0_FMLVC 0x0754 /* FM Synthesis Left Volume Control */ +#define BA0_FMRVC 0x0758 /* FM Synthesis Right Volume Control */ +#define BA0_SRCSA 0x075c /* SRC Slot Assignments */ +#define BA0_PPLVC 0x0760 /* PCM Playback Left Volume Control */ +#define BA0_PPRVC 0x0764 /* PCM Playback Right Volume Control */ +#define BA0_PASR 0x0768 /* playback sample rate */ +#define BA0_CASR 0x076C /* capture sample rate */ + +/* Source Slot Numbers - Playback */ +#define SRCSLOT_LEFT_PCM_PLAYBACK 0 +#define SRCSLOT_RIGHT_PCM_PLAYBACK 1 +#define SRCSLOT_PHONE_LINE_1_DAC 2 +#define SRCSLOT_CENTER_PCM_PLAYBACK 3 +#define SRCSLOT_LEFT_SURROUND_PCM_PLAYBACK 4 +#define SRCSLOT_RIGHT_SURROUND_PCM_PLAYBACK 5 +#define SRCSLOT_LFE_PCM_PLAYBACK 6 +#define SRCSLOT_PHONE_LINE_2_DAC 7 +#define SRCSLOT_HEADSET_DAC 8 +#define SRCSLOT_LEFT_WT 29 /* invalid for BA0_SRCSA */ +#define SRCSLOT_RIGHT_WT 30 /* invalid for BA0_SRCSA */ + +/* Source Slot Numbers - Capture */ +#define SRCSLOT_LEFT_PCM_RECORD 10 +#define SRCSLOT_RIGHT_PCM_RECORD 11 +#define SRCSLOT_PHONE_LINE_1_ADC 12 +#define SRCSLOT_MIC_ADC 13 +#define SRCSLOT_PHONE_LINE_2_ADC 17 +#define SRCSLOT_HEADSET_ADC 18 +#define SRCSLOT_SECONDARY_LEFT_PCM_RECORD 20 +#define SRCSLOT_SECONDARY_RIGHT_PCM_RECORD 21 +#define SRCSLOT_SECONDARY_PHONE_LINE_1_ADC 22 +#define SRCSLOT_SECONDARY_MIC_ADC 23 +#define SRCSLOT_SECONDARY_PHONE_LINE_2_ADC 27 +#define SRCSLOT_SECONDARY_HEADSET_ADC 28 + +/* Source Slot Numbers - Others */ +#define SRCSLOT_POWER_DOWN 31 + +/* MIDI modes */ +#define CS4281_MODE_OUTPUT (1<<0) +#define CS4281_MODE_INPUT (1<<1) + +/* joystick bits */ +/* Bits for JSPT */ +#define JSPT_CAX 0x00000001 +#define JSPT_CAY 0x00000002 +#define JSPT_CBX 0x00000004 +#define JSPT_CBY 0x00000008 +#define JSPT_BA1 0x00000010 +#define JSPT_BA2 0x00000020 +#define JSPT_BB1 0x00000040 +#define JSPT_BB2 0x00000080 + +/* Bits for JSCTL */ +#define JSCTL_SP_MASK 0x00000003 +#define JSCTL_SP_SLOW 0x00000000 +#define JSCTL_SP_MEDIUM_SLOW 0x00000001 +#define JSCTL_SP_MEDIUM_FAST 0x00000002 +#define JSCTL_SP_FAST 0x00000003 +#define JSCTL_ARE 0x00000004 + +/* Data register pairs masks */ +#define JSC1_Y1V_MASK 0x0000FFFF +#define JSC1_X1V_MASK 0xFFFF0000 +#define JSC1_Y1V_SHIFT 0 +#define JSC1_X1V_SHIFT 16 +#define JSC2_Y2V_MASK 0x0000FFFF +#define JSC2_X2V_MASK 0xFFFF0000 +#define JSC2_Y2V_SHIFT 0 +#define JSC2_X2V_SHIFT 16 + +/* JS GPIO */ +#define JSIO_DAX 0x00000001 +#define JSIO_DAY 0x00000002 +#define JSIO_DBX 0x00000004 +#define JSIO_DBY 0x00000008 +#define JSIO_AXOE 0x00000010 +#define JSIO_AYOE 0x00000020 +#define JSIO_BXOE 0x00000040 +#define JSIO_BYOE 0x00000080 + +/* + * + */ + +struct cs4281_dma { + struct snd_pcm_substream *substream; + unsigned int regDBA; /* offset to DBA register */ + unsigned int regDCA; /* offset to DCA register */ + unsigned int regDBC; /* offset to DBC register */ + unsigned int regDCC; /* offset to DCC register */ + unsigned int regDMR; /* offset to DMR register */ + unsigned int regDCR; /* offset to DCR register */ + unsigned int regHDSR; /* offset to HDSR register */ + unsigned int regFCR; /* offset to FCR register */ + unsigned int regFSIC; /* offset to FSIC register */ + unsigned int valDMR; /* DMA mode */ + unsigned int valDCR; /* DMA command */ + unsigned int valFCR; /* FIFO control */ + unsigned int fifo_offset; /* FIFO offset within BA1 */ + unsigned char left_slot; /* FIFO left slot */ + unsigned char right_slot; /* FIFO right slot */ + int frag; /* period number */ +}; + +#define SUSPEND_REGISTERS 20 + +struct cs4281 { + int irq; + + void __iomem *ba0; /* virtual (accessible) address */ + void __iomem *ba1; /* virtual (accessible) address */ + unsigned long ba0_addr; + unsigned long ba1_addr; + + int dual_codec; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + struct snd_ac97 *ac97_secondary; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_input; + struct snd_rawmidi_substream *midi_output; + + struct cs4281_dma dma[4]; + + unsigned char src_left_play_slot; + unsigned char src_right_play_slot; + unsigned char src_left_rec_slot; + unsigned char src_right_rec_slot; + + unsigned int spurious_dhtc_irq; + unsigned int spurious_dtc_irq; + + spinlock_t reg_lock; + unsigned int midcr; + unsigned int uartm; + + struct gameport *gameport; + + u32 suspend_regs[SUSPEND_REGISTERS]; +}; + +static irqreturn_t snd_cs4281_interrupt(int irq, void *dev_id); + +static const struct pci_device_id snd_cs4281_ids[] = { + { PCI_VDEVICE(CIRRUS, 0x6005) }, /* CS4281 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_cs4281_ids); + +/* + * constants + */ + +#define CS4281_FIFO_SIZE 32 + +/* + * common I/O routines + */ + +static inline void snd_cs4281_pokeBA0(struct cs4281 *chip, unsigned long offset, + unsigned int val) +{ + writel(val, chip->ba0 + offset); +} + +static inline unsigned int snd_cs4281_peekBA0(struct cs4281 *chip, unsigned long offset) +{ + return readl(chip->ba0 + offset); +} + +static void snd_cs4281_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + /* + * 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address + * 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 + * 3. Write ACCTL = Control Register = 460h for initiating the write + * 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 07h + * 5. if DCV not cleared, break and return error + */ + struct cs4281 *chip = ac97->private_data; + int count; + + /* + * Setup the AC97 control registers on the CS461x to send the + * appropriate command to the AC97 to perform the read. + * ACCAD = Command Address Register = 46Ch + * ACCDA = Command Data Register = 470h + * ACCTL = Control Register = 460h + * set DCV - will clear when process completed + * reset CRW - Write command + * set VFRM - valid frame enabled + * set ESYN - ASYNC generation enabled + * set RSTN - ARST# inactive, AC97 codec not reset + */ + snd_cs4281_pokeBA0(chip, BA0_ACCAD, reg); + snd_cs4281_pokeBA0(chip, BA0_ACCDA, val); + snd_cs4281_pokeBA0(chip, BA0_ACCTL, BA0_ACCTL_DCV | BA0_ACCTL_VFRM | + BA0_ACCTL_ESYN | (ac97->num ? BA0_ACCTL_TC : 0)); + for (count = 0; count < 2000; count++) { + /* + * First, we want to wait for a short time. + */ + udelay(10); + /* + * Now, check to see if the write has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 07h + */ + if (!(snd_cs4281_peekBA0(chip, BA0_ACCTL) & BA0_ACCTL_DCV)) { + return; + } + } + dev_err(chip->card->dev, + "AC'97 write problem, reg = 0x%x, val = 0x%x\n", reg, val); +} + +static unsigned short snd_cs4281_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct cs4281 *chip = ac97->private_data; + int count; + unsigned short result; + // FIXME: volatile is necessary in the following due to a bug of + // some gcc versions + volatile int ac97_num = ((volatile struct snd_ac97 *)ac97)->num; + + /* + * 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address + * 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 + * 3. Write ACCTL = Control Register = 460h for initiating the write + * 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 17h + * 5. if DCV not cleared, break and return error + * 6. Read ACSTS = Status Register = 464h, check VSTS bit + */ + + snd_cs4281_peekBA0(chip, ac97_num ? BA0_ACSDA2 : BA0_ACSDA); + + /* + * Setup the AC97 control registers on the CS461x to send the + * appropriate command to the AC97 to perform the read. + * ACCAD = Command Address Register = 46Ch + * ACCDA = Command Data Register = 470h + * ACCTL = Control Register = 460h + * set DCV - will clear when process completed + * set CRW - Read command + * set VFRM - valid frame enabled + * set ESYN - ASYNC generation enabled + * set RSTN - ARST# inactive, AC97 codec not reset + */ + + snd_cs4281_pokeBA0(chip, BA0_ACCAD, reg); + snd_cs4281_pokeBA0(chip, BA0_ACCDA, 0); + snd_cs4281_pokeBA0(chip, BA0_ACCTL, BA0_ACCTL_DCV | BA0_ACCTL_CRW | + BA0_ACCTL_VFRM | BA0_ACCTL_ESYN | + (ac97_num ? BA0_ACCTL_TC : 0)); + + + /* + * Wait for the read to occur. + */ + for (count = 0; count < 500; count++) { + /* + * First, we want to wait for a short time. + */ + udelay(10); + /* + * Now, check to see if the read has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 17h + */ + if (!(snd_cs4281_peekBA0(chip, BA0_ACCTL) & BA0_ACCTL_DCV)) + goto __ok1; + } + + dev_err(chip->card->dev, + "AC'97 read problem (ACCTL_DCV), reg = 0x%x\n", reg); + result = 0xffff; + goto __end; + + __ok1: + /* + * Wait for the valid status bit to go active. + */ + for (count = 0; count < 100; count++) { + /* + * Read the AC97 status register. + * ACSTS = Status Register = 464h + * VSTS - Valid Status + */ + if (snd_cs4281_peekBA0(chip, ac97_num ? BA0_ACSTS2 : BA0_ACSTS) & BA0_ACSTS_VSTS) + goto __ok2; + udelay(10); + } + + dev_err(chip->card->dev, + "AC'97 read problem (ACSTS_VSTS), reg = 0x%x\n", reg); + result = 0xffff; + goto __end; + + __ok2: + /* + * Read the data returned from the AC97 register. + * ACSDA = Status Data Register = 474h + */ + result = snd_cs4281_peekBA0(chip, ac97_num ? BA0_ACSDA2 : BA0_ACSDA); + + __end: + return result; +} + +/* + * PCM part + */ + +static int snd_cs4281_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct cs4281_dma *dma = substream->runtime->private_data; + struct cs4281 *chip = snd_pcm_substream_chip(substream); + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dma->valDCR |= BA0_DCR_MSK; + dma->valFCR |= BA0_FCR_FEN; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dma->valDCR &= ~BA0_DCR_MSK; + dma->valFCR &= ~BA0_FCR_FEN; + break; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + snd_cs4281_pokeBA0(chip, dma->regDMR, dma->valDMR & ~BA0_DMR_DMA); + dma->valDMR |= BA0_DMR_DMA; + dma->valDCR &= ~BA0_DCR_MSK; + dma->valFCR |= BA0_FCR_FEN; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + dma->valDMR &= ~(BA0_DMR_DMA|BA0_DMR_POLL); + dma->valDCR |= BA0_DCR_MSK; + dma->valFCR &= ~BA0_FCR_FEN; + /* Leave wave playback FIFO enabled for FM */ + if (dma->regFCR != BA0_FCR0) + dma->valFCR &= ~BA0_FCR_FEN; + break; + default: + return -EINVAL; + } + snd_cs4281_pokeBA0(chip, dma->regDMR, dma->valDMR); + snd_cs4281_pokeBA0(chip, dma->regFCR, dma->valFCR); + snd_cs4281_pokeBA0(chip, dma->regDCR, dma->valDCR); + return 0; +} + +static unsigned int snd_cs4281_rate(unsigned int rate, unsigned int *real_rate) +{ + unsigned int val; + + if (real_rate) + *real_rate = rate; + /* special "hardcoded" rates */ + switch (rate) { + case 8000: return 5; + case 11025: return 4; + case 16000: return 3; + case 22050: return 2; + case 44100: return 1; + case 48000: return 0; + default: + break; + } + val = 1536000 / rate; + if (real_rate) + *real_rate = 1536000 / val; + return val; +} + +static void snd_cs4281_mode(struct cs4281 *chip, struct cs4281_dma *dma, + struct snd_pcm_runtime *runtime, + int capture, int src) +{ + int rec_mono; + + dma->valDMR = BA0_DMR_TYPE_SINGLE | BA0_DMR_AUTO | + (capture ? BA0_DMR_TR_WRITE : BA0_DMR_TR_READ); + if (runtime->channels == 1) + dma->valDMR |= BA0_DMR_MONO; + if (snd_pcm_format_unsigned(runtime->format) > 0) + dma->valDMR |= BA0_DMR_USIGN; + if (snd_pcm_format_big_endian(runtime->format) > 0) + dma->valDMR |= BA0_DMR_BEND; + switch (snd_pcm_format_width(runtime->format)) { + case 8: dma->valDMR |= BA0_DMR_SIZE8; + if (runtime->channels == 1) + dma->valDMR |= BA0_DMR_SWAPC; + break; + case 32: dma->valDMR |= BA0_DMR_SIZE20; break; + } + dma->frag = 0; /* for workaround */ + dma->valDCR = BA0_DCR_TCIE | BA0_DCR_MSK; + if (runtime->buffer_size != runtime->period_size) + dma->valDCR |= BA0_DCR_HTCIE; + /* Initialize DMA */ + snd_cs4281_pokeBA0(chip, dma->regDBA, runtime->dma_addr); + snd_cs4281_pokeBA0(chip, dma->regDBC, runtime->buffer_size - 1); + rec_mono = (chip->dma[1].valDMR & BA0_DMR_MONO) == BA0_DMR_MONO; + snd_cs4281_pokeBA0(chip, BA0_SRCSA, (chip->src_left_play_slot << 0) | + (chip->src_right_play_slot << 8) | + (chip->src_left_rec_slot << 16) | + ((rec_mono ? 31 : chip->src_right_rec_slot) << 24)); + if (!src) + goto __skip_src; + if (!capture) { + if (dma->left_slot == chip->src_left_play_slot) { + unsigned int val = snd_cs4281_rate(runtime->rate, NULL); + snd_BUG_ON(dma->right_slot != chip->src_right_play_slot); + snd_cs4281_pokeBA0(chip, BA0_DACSR, val); + } + } else { + if (dma->left_slot == chip->src_left_rec_slot) { + unsigned int val = snd_cs4281_rate(runtime->rate, NULL); + snd_BUG_ON(dma->right_slot != chip->src_right_rec_slot); + snd_cs4281_pokeBA0(chip, BA0_ADCSR, val); + } + } + __skip_src: + /* Deactivate wave playback FIFO before changing slot assignments */ + if (dma->regFCR == BA0_FCR0) + snd_cs4281_pokeBA0(chip, dma->regFCR, snd_cs4281_peekBA0(chip, dma->regFCR) & ~BA0_FCR_FEN); + /* Initialize FIFO */ + dma->valFCR = BA0_FCR_LS(dma->left_slot) | + BA0_FCR_RS(capture && (dma->valDMR & BA0_DMR_MONO) ? 31 : dma->right_slot) | + BA0_FCR_SZ(CS4281_FIFO_SIZE) | + BA0_FCR_OF(dma->fifo_offset); + snd_cs4281_pokeBA0(chip, dma->regFCR, dma->valFCR | (capture ? BA0_FCR_PSH : 0)); + /* Activate FIFO again for FM playback */ + if (dma->regFCR == BA0_FCR0) + snd_cs4281_pokeBA0(chip, dma->regFCR, dma->valFCR | BA0_FCR_FEN); + /* Clear FIFO Status and Interrupt Control Register */ + snd_cs4281_pokeBA0(chip, dma->regFSIC, 0); +} + +static int snd_cs4281_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct cs4281_dma *dma = runtime->private_data; + struct cs4281 *chip = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&chip->reg_lock); + snd_cs4281_mode(chip, dma, runtime, 0, 1); + return 0; +} + +static int snd_cs4281_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct cs4281_dma *dma = runtime->private_data; + struct cs4281 *chip = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&chip->reg_lock); + snd_cs4281_mode(chip, dma, runtime, 1, 1); + return 0; +} + +static snd_pcm_uframes_t snd_cs4281_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct cs4281_dma *dma = runtime->private_data; + struct cs4281 *chip = snd_pcm_substream_chip(substream); + + /* + dev_dbg(chip->card->dev, + "DCC = 0x%x, buffer_size = 0x%x, jiffies = %li\n", + snd_cs4281_peekBA0(chip, dma->regDCC), runtime->buffer_size, + jiffies); + */ + return runtime->buffer_size - + snd_cs4281_peekBA0(chip, dma->regDCC) - 1; +} + +static const struct snd_pcm_hardware snd_cs4281_playback = +{ + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE, + .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 = (512*1024), + .period_bytes_min = 64, + .period_bytes_max = (512*1024), + .periods_min = 1, + .periods_max = 2, + .fifo_size = CS4281_FIFO_SIZE, +}; + +static const struct snd_pcm_hardware snd_cs4281_capture = +{ + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE, + .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 = (512*1024), + .period_bytes_min = 64, + .period_bytes_max = (512*1024), + .periods_min = 1, + .periods_max = 2, + .fifo_size = CS4281_FIFO_SIZE, +}; + +static int snd_cs4281_playback_open(struct snd_pcm_substream *substream) +{ + struct cs4281 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct cs4281_dma *dma; + + dma = &chip->dma[0]; + dma->substream = substream; + dma->left_slot = 0; + dma->right_slot = 1; + runtime->private_data = dma; + runtime->hw = snd_cs4281_playback; + /* should be detected from the AC'97 layer, but it seems + that although CS4297A rev B reports 18-bit ADC resolution, + samples are 20-bit */ + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 20); + return 0; +} + +static int snd_cs4281_capture_open(struct snd_pcm_substream *substream) +{ + struct cs4281 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct cs4281_dma *dma; + + dma = &chip->dma[1]; + dma->substream = substream; + dma->left_slot = 10; + dma->right_slot = 11; + runtime->private_data = dma; + runtime->hw = snd_cs4281_capture; + /* should be detected from the AC'97 layer, but it seems + that although CS4297A rev B reports 18-bit ADC resolution, + samples are 20-bit */ + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 20); + return 0; +} + +static int snd_cs4281_playback_close(struct snd_pcm_substream *substream) +{ + struct cs4281_dma *dma = substream->runtime->private_data; + + dma->substream = NULL; + return 0; +} + +static int snd_cs4281_capture_close(struct snd_pcm_substream *substream) +{ + struct cs4281_dma *dma = substream->runtime->private_data; + + dma->substream = NULL; + return 0; +} + +static const struct snd_pcm_ops snd_cs4281_playback_ops = { + .open = snd_cs4281_playback_open, + .close = snd_cs4281_playback_close, + .prepare = snd_cs4281_playback_prepare, + .trigger = snd_cs4281_trigger, + .pointer = snd_cs4281_pointer, +}; + +static const struct snd_pcm_ops snd_cs4281_capture_ops = { + .open = snd_cs4281_capture_open, + .close = snd_cs4281_capture_close, + .prepare = snd_cs4281_capture_prepare, + .trigger = snd_cs4281_trigger, + .pointer = snd_cs4281_pointer, +}; + +static int snd_cs4281_pcm(struct cs4281 *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "CS4281", device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs4281_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cs4281_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + strscpy(pcm->name, "CS4281"); + chip->pcm = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + 64*1024, 512*1024); + + return 0; +} + +/* + * Mixer section + */ + +#define CS_VOL_MASK 0x1f + +static int snd_cs4281_info_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 = CS_VOL_MASK; + return 0; +} + +static int snd_cs4281_get_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs4281 *chip = snd_kcontrol_chip(kcontrol); + int regL = (kcontrol->private_value >> 16) & 0xffff; + int regR = kcontrol->private_value & 0xffff; + int volL, volR; + + volL = CS_VOL_MASK - (snd_cs4281_peekBA0(chip, regL) & CS_VOL_MASK); + volR = CS_VOL_MASK - (snd_cs4281_peekBA0(chip, regR) & CS_VOL_MASK); + + ucontrol->value.integer.value[0] = volL; + ucontrol->value.integer.value[1] = volR; + return 0; +} + +static int snd_cs4281_put_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs4281 *chip = snd_kcontrol_chip(kcontrol); + int change = 0; + int regL = (kcontrol->private_value >> 16) & 0xffff; + int regR = kcontrol->private_value & 0xffff; + int volL, volR; + + volL = CS_VOL_MASK - (snd_cs4281_peekBA0(chip, regL) & CS_VOL_MASK); + volR = CS_VOL_MASK - (snd_cs4281_peekBA0(chip, regR) & CS_VOL_MASK); + + if (ucontrol->value.integer.value[0] != volL) { + volL = CS_VOL_MASK - (ucontrol->value.integer.value[0] & CS_VOL_MASK); + snd_cs4281_pokeBA0(chip, regL, volL); + change = 1; + } + if (ucontrol->value.integer.value[1] != volR) { + volR = CS_VOL_MASK - (ucontrol->value.integer.value[1] & CS_VOL_MASK); + snd_cs4281_pokeBA0(chip, regR, volR); + change = 1; + } + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_dsp, -4650, 150, 0); + +static const struct snd_kcontrol_new snd_cs4281_fm_vol = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Synth Playback Volume", + .info = snd_cs4281_info_volume, + .get = snd_cs4281_get_volume, + .put = snd_cs4281_put_volume, + .private_value = ((BA0_FMLVC << 16) | BA0_FMRVC), + .tlv = { .p = db_scale_dsp }, +}; + +static const struct snd_kcontrol_new snd_cs4281_pcm_vol = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Stream Playback Volume", + .info = snd_cs4281_info_volume, + .get = snd_cs4281_get_volume, + .put = snd_cs4281_put_volume, + .private_value = ((BA0_PPLVC << 16) | BA0_PPRVC), + .tlv = { .p = db_scale_dsp }, +}; + +static void snd_cs4281_mixer_free_ac97_bus(struct snd_ac97_bus *bus) +{ + struct cs4281 *chip = bus->private_data; + chip->ac97_bus = NULL; +} + +static void snd_cs4281_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct cs4281 *chip = ac97->private_data; + if (ac97->num) + chip->ac97_secondary = NULL; + else + chip->ac97 = NULL; +} + +static int snd_cs4281_mixer(struct cs4281 *chip) +{ + struct snd_card *card = chip->card; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_cs4281_ac97_write, + .read = snd_cs4281_ac97_read, + }; + + err = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus); + if (err < 0) + return err; + chip->ac97_bus->private_free = snd_cs4281_mixer_free_ac97_bus; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.private_free = snd_cs4281_mixer_free_ac97; + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97); + if (err < 0) + return err; + if (chip->dual_codec) { + ac97.num = 1; + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97_secondary); + if (err < 0) + return err; + } + err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4281_fm_vol, chip)); + if (err < 0) + return err; + err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4281_pcm_vol, chip)); + if (err < 0) + return err; + return 0; +} + + +/* + * proc interface + */ + +static void snd_cs4281_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct cs4281 *chip = entry->private_data; + + snd_iprintf(buffer, "Cirrus Logic CS4281\n\n"); + snd_iprintf(buffer, "Spurious half IRQs : %u\n", chip->spurious_dhtc_irq); + snd_iprintf(buffer, "Spurious end IRQs : %u\n", chip->spurious_dtc_irq); +} + +static ssize_t snd_cs4281_BA0_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) +{ + struct cs4281 *chip = entry->private_data; + + if (copy_to_user_fromio(buf, chip->ba0 + pos, count)) + return -EFAULT; + return count; +} + +static ssize_t snd_cs4281_BA1_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) +{ + struct cs4281 *chip = entry->private_data; + + if (copy_to_user_fromio(buf, chip->ba1 + pos, count)) + return -EFAULT; + return count; +} + +static const struct snd_info_entry_ops snd_cs4281_proc_ops_BA0 = { + .read = snd_cs4281_BA0_read, +}; + +static const struct snd_info_entry_ops snd_cs4281_proc_ops_BA1 = { + .read = snd_cs4281_BA1_read, +}; + +static void snd_cs4281_proc_init(struct cs4281 *chip) +{ + struct snd_info_entry *entry; + + snd_card_ro_proc_new(chip->card, "cs4281", chip, snd_cs4281_proc_read); + if (! snd_card_proc_new(chip->card, "cs4281_BA0", &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = chip; + entry->c.ops = &snd_cs4281_proc_ops_BA0; + entry->size = CS4281_BA0_SIZE; + } + if (! snd_card_proc_new(chip->card, "cs4281_BA1", &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = chip; + entry->c.ops = &snd_cs4281_proc_ops_BA1; + entry->size = CS4281_BA1_SIZE; + } +} + +/* + * joystick support + */ + +#if IS_REACHABLE(CONFIG_GAMEPORT) + +static void snd_cs4281_gameport_trigger(struct gameport *gameport) +{ + struct cs4281 *chip = gameport_get_port_data(gameport); + + if (snd_BUG_ON(!chip)) + return; + snd_cs4281_pokeBA0(chip, BA0_JSPT, 0xff); +} + +static unsigned char snd_cs4281_gameport_read(struct gameport *gameport) +{ + struct cs4281 *chip = gameport_get_port_data(gameport); + + if (snd_BUG_ON(!chip)) + return 0; + return snd_cs4281_peekBA0(chip, BA0_JSPT); +} + +#ifdef COOKED_MODE +static int snd_cs4281_gameport_cooked_read(struct gameport *gameport, + int *axes, int *buttons) +{ + struct cs4281 *chip = gameport_get_port_data(gameport); + unsigned js1, js2, jst; + + if (snd_BUG_ON(!chip)) + return 0; + + js1 = snd_cs4281_peekBA0(chip, BA0_JSC1); + js2 = snd_cs4281_peekBA0(chip, BA0_JSC2); + jst = snd_cs4281_peekBA0(chip, BA0_JSPT); + + *buttons = (~jst >> 4) & 0x0F; + + axes[0] = ((js1 & JSC1_Y1V_MASK) >> JSC1_Y1V_SHIFT) & 0xFFFF; + axes[1] = ((js1 & JSC1_X1V_MASK) >> JSC1_X1V_SHIFT) & 0xFFFF; + axes[2] = ((js2 & JSC2_Y2V_MASK) >> JSC2_Y2V_SHIFT) & 0xFFFF; + axes[3] = ((js2 & JSC2_X2V_MASK) >> JSC2_X2V_SHIFT) & 0xFFFF; + + for (jst = 0; jst < 4; ++jst) + if (axes[jst] == 0xFFFF) axes[jst] = -1; + return 0; +} +#else +#define snd_cs4281_gameport_cooked_read NULL +#endif + +static int snd_cs4281_gameport_open(struct gameport *gameport, int mode) +{ + switch (mode) { +#ifdef COOKED_MODE + case GAMEPORT_MODE_COOKED: + return 0; +#endif + case GAMEPORT_MODE_RAW: + return 0; + default: + return -1; + } + return 0; +} + +static int snd_cs4281_create_gameport(struct cs4281 *chip) +{ + struct gameport *gp; + + chip->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(chip->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "CS4281 Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci)); + gameport_set_dev_parent(gp, &chip->pci->dev); + gp->open = snd_cs4281_gameport_open; + gp->read = snd_cs4281_gameport_read; + gp->trigger = snd_cs4281_gameport_trigger; + gp->cooked_read = snd_cs4281_gameport_cooked_read; + gameport_set_port_data(gp, chip); + + snd_cs4281_pokeBA0(chip, BA0_JSIO, 0xFF); // ? + snd_cs4281_pokeBA0(chip, BA0_JSCTL, JSCTL_SP_MEDIUM_SLOW); + + gameport_register_port(gp); + + return 0; +} + +static void snd_cs4281_free_gameport(struct cs4281 *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } +} +#else +static inline int snd_cs4281_create_gameport(struct cs4281 *chip) { return -ENOSYS; } +static inline void snd_cs4281_free_gameport(struct cs4281 *chip) { } +#endif /* IS_REACHABLE(CONFIG_GAMEPORT) */ + +static void snd_cs4281_free(struct snd_card *card) +{ + struct cs4281 *chip = card->private_data; + + snd_cs4281_free_gameport(chip); + + /* Mask interrupts */ + snd_cs4281_pokeBA0(chip, BA0_HIMR, 0x7fffffff); + /* Stop the DLL Clock logic. */ + snd_cs4281_pokeBA0(chip, BA0_CLKCR1, 0); + /* Sound System Power Management - Turn Everything OFF */ + snd_cs4281_pokeBA0(chip, BA0_SSPM, 0); +} + +static int snd_cs4281_chip_init(struct cs4281 *chip); /* defined below */ + +static int snd_cs4281_create(struct snd_card *card, + struct pci_dev *pci, + int dual_codec) +{ + struct cs4281 *chip = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + spin_lock_init(&chip->reg_lock); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + pci_set_master(pci); + if (dual_codec < 0 || dual_codec > 3) { + dev_err(card->dev, "invalid dual_codec option %d\n", dual_codec); + dual_codec = 0; + } + chip->dual_codec = dual_codec; + + chip->ba0 = pcim_iomap_region(pci, 0, "CS4281"); + if (IS_ERR(chip->ba0)) + return PTR_ERR(chip->ba0); + chip->ba0_addr = pci_resource_start(pci, 0); + + chip->ba1 = pcim_iomap_region(pci, 1, "CS4281"); + if (IS_ERR(chip->ba1)) + return PTR_ERR(chip->ba1); + chip->ba1_addr = pci_resource_start(pci, 1); + + if (devm_request_irq(&pci->dev, pci->irq, snd_cs4281_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -ENOMEM; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_cs4281_free; + + err = snd_cs4281_chip_init(chip); + if (err) + return err; + + snd_cs4281_proc_init(chip); + return 0; +} + +static int snd_cs4281_chip_init(struct cs4281 *chip) +{ + unsigned int tmp; + unsigned long end_time; + int retry_count = 2; + + /* Having EPPMC.FPDN=1 prevent proper chip initialisation */ + tmp = snd_cs4281_peekBA0(chip, BA0_EPPMC); + if (tmp & BA0_EPPMC_FPDN) + snd_cs4281_pokeBA0(chip, BA0_EPPMC, tmp & ~BA0_EPPMC_FPDN); + + __retry: + tmp = snd_cs4281_peekBA0(chip, BA0_CFLR); + if (tmp != BA0_CFLR_DEFAULT) { + snd_cs4281_pokeBA0(chip, BA0_CFLR, BA0_CFLR_DEFAULT); + tmp = snd_cs4281_peekBA0(chip, BA0_CFLR); + if (tmp != BA0_CFLR_DEFAULT) { + dev_err(chip->card->dev, + "CFLR setup failed (0x%x)\n", tmp); + return -EIO; + } + } + + /* Set the 'Configuration Write Protect' register + * to 4281h. Allows vendor-defined configuration + * space between 0e4h and 0ffh to be written. */ + snd_cs4281_pokeBA0(chip, BA0_CWPR, 0x4281); + + tmp = snd_cs4281_peekBA0(chip, BA0_SERC1); + if (tmp != (BA0_SERC1_SO1EN | BA0_SERC1_AC97)) { + dev_err(chip->card->dev, + "SERC1 AC'97 check failed (0x%x)\n", tmp); + return -EIO; + } + tmp = snd_cs4281_peekBA0(chip, BA0_SERC2); + if (tmp != (BA0_SERC2_SI1EN | BA0_SERC2_AC97)) { + dev_err(chip->card->dev, + "SERC2 AC'97 check failed (0x%x)\n", tmp); + return -EIO; + } + + /* Sound System Power Management */ + snd_cs4281_pokeBA0(chip, BA0_SSPM, BA0_SSPM_MIXEN | BA0_SSPM_CSRCEN | + BA0_SSPM_PSRCEN | BA0_SSPM_JSEN | + BA0_SSPM_ACLEN | BA0_SSPM_FMEN); + + /* Serial Port Power Management */ + /* Blast the clock control register to zero so that the + * PLL starts out in a known state, and blast the master serial + * port control register to zero so that the serial ports also + * start out in a known state. */ + snd_cs4281_pokeBA0(chip, BA0_CLKCR1, 0); + snd_cs4281_pokeBA0(chip, BA0_SERMC, 0); + + /* Make ESYN go to zero to turn off + * the Sync pulse on the AC97 link. */ + snd_cs4281_pokeBA0(chip, BA0_ACCTL, 0); + udelay(50); + + /* Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97 + * spec) and then drive it high. This is done for non AC97 modes since + * there might be logic external to the CS4281 that uses the ARST# line + * for a reset. */ + snd_cs4281_pokeBA0(chip, BA0_SPMC, 0); + udelay(50); + snd_cs4281_pokeBA0(chip, BA0_SPMC, BA0_SPMC_RSTN); + msleep(50); + + if (chip->dual_codec) + snd_cs4281_pokeBA0(chip, BA0_SPMC, BA0_SPMC_RSTN | BA0_SPMC_ASDI2E); + + /* + * Set the serial port timing configuration. + */ + snd_cs4281_pokeBA0(chip, BA0_SERMC, + (chip->dual_codec ? BA0_SERMC_TCID(chip->dual_codec) : BA0_SERMC_TCID(1)) | + BA0_SERMC_PTC_AC97 | BA0_SERMC_MSPE); + + /* + * Start the DLL Clock logic. + */ + snd_cs4281_pokeBA0(chip, BA0_CLKCR1, BA0_CLKCR1_DLLP); + msleep(50); + snd_cs4281_pokeBA0(chip, BA0_CLKCR1, BA0_CLKCR1_SWCE | BA0_CLKCR1_DLLP); + + /* + * Wait for the DLL ready signal from the clock logic. + */ + end_time = jiffies + HZ; + do { + /* + * Read the AC97 status register to see if we've seen a CODEC + * signal from the AC97 codec. + */ + if (snd_cs4281_peekBA0(chip, BA0_CLKCR1) & BA0_CLKCR1_DLLRDY) + goto __ok0; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + + dev_err(chip->card->dev, "DLLRDY not seen\n"); + return -EIO; + + __ok0: + + /* + * The first thing we do here is to enable sync generation. As soon + * as we start receiving bit clock, we'll start producing the SYNC + * signal. + */ + snd_cs4281_pokeBA0(chip, BA0_ACCTL, BA0_ACCTL_ESYN); + + /* + * Wait for the codec ready signal from the AC97 codec. + */ + end_time = jiffies + HZ; + do { + /* + * Read the AC97 status register to see if we've seen a CODEC + * signal from the AC97 codec. + */ + if (snd_cs4281_peekBA0(chip, BA0_ACSTS) & BA0_ACSTS_CRDY) + goto __ok1; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + + dev_err(chip->card->dev, + "never read codec ready from AC'97 (0x%x)\n", + snd_cs4281_peekBA0(chip, BA0_ACSTS)); + return -EIO; + + __ok1: + if (chip->dual_codec) { + end_time = jiffies + HZ; + do { + if (snd_cs4281_peekBA0(chip, BA0_ACSTS2) & BA0_ACSTS_CRDY) + goto __codec2_ok; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + dev_info(chip->card->dev, + "secondary codec doesn't respond. disable it...\n"); + chip->dual_codec = 0; + __codec2_ok: ; + } + + /* + * Assert the valid frame signal so that we can start sending commands + * to the AC97 codec. + */ + + snd_cs4281_pokeBA0(chip, BA0_ACCTL, BA0_ACCTL_VFRM | BA0_ACCTL_ESYN); + + /* + * Wait until we've sampled input slots 3 and 4 as valid, meaning that + * the codec is pumping ADC data across the AC-link. + */ + + end_time = jiffies + HZ; + do { + /* + * Read the input slot valid register and see if input slots 3 + * 4 are valid yet. + */ + if ((snd_cs4281_peekBA0(chip, BA0_ACISV) & (BA0_ACISV_SLV(3) | BA0_ACISV_SLV(4))) == (BA0_ACISV_SLV(3) | BA0_ACISV_SLV(4))) + goto __ok2; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + + if (--retry_count > 0) + goto __retry; + dev_err(chip->card->dev, "never read ISV3 and ISV4 from AC'97\n"); + return -EIO; + + __ok2: + + /* + * Now, assert valid frame and the slot 3 and 4 valid bits. This will + * commense the transfer of digital audio data to the AC97 codec. + */ + snd_cs4281_pokeBA0(chip, BA0_ACOSV, BA0_ACOSV_SLV(3) | BA0_ACOSV_SLV(4)); + + /* + * Initialize DMA structures + */ + for (tmp = 0; tmp < 4; tmp++) { + struct cs4281_dma *dma = &chip->dma[tmp]; + dma->regDBA = BA0_DBA0 + (tmp * 0x10); + dma->regDCA = BA0_DCA0 + (tmp * 0x10); + dma->regDBC = BA0_DBC0 + (tmp * 0x10); + dma->regDCC = BA0_DCC0 + (tmp * 0x10); + dma->regDMR = BA0_DMR0 + (tmp * 8); + dma->regDCR = BA0_DCR0 + (tmp * 8); + dma->regHDSR = BA0_HDSR0 + (tmp * 4); + dma->regFCR = BA0_FCR0 + (tmp * 4); + dma->regFSIC = BA0_FSIC0 + (tmp * 4); + dma->fifo_offset = tmp * CS4281_FIFO_SIZE; + snd_cs4281_pokeBA0(chip, dma->regFCR, + BA0_FCR_LS(31) | + BA0_FCR_RS(31) | + BA0_FCR_SZ(CS4281_FIFO_SIZE) | + BA0_FCR_OF(dma->fifo_offset)); + } + + chip->src_left_play_slot = 0; /* AC'97 left PCM playback (3) */ + chip->src_right_play_slot = 1; /* AC'97 right PCM playback (4) */ + chip->src_left_rec_slot = 10; /* AC'97 left PCM record (3) */ + chip->src_right_rec_slot = 11; /* AC'97 right PCM record (4) */ + + /* Activate wave playback FIFO for FM playback */ + chip->dma[0].valFCR = BA0_FCR_FEN | BA0_FCR_LS(0) | + BA0_FCR_RS(1) | + BA0_FCR_SZ(CS4281_FIFO_SIZE) | + BA0_FCR_OF(chip->dma[0].fifo_offset); + snd_cs4281_pokeBA0(chip, chip->dma[0].regFCR, chip->dma[0].valFCR); + snd_cs4281_pokeBA0(chip, BA0_SRCSA, (chip->src_left_play_slot << 0) | + (chip->src_right_play_slot << 8) | + (chip->src_left_rec_slot << 16) | + (chip->src_right_rec_slot << 24)); + + /* Initialize digital volume */ + snd_cs4281_pokeBA0(chip, BA0_PPLVC, 0); + snd_cs4281_pokeBA0(chip, BA0_PPRVC, 0); + + /* Enable IRQs */ + snd_cs4281_pokeBA0(chip, BA0_HICR, BA0_HICR_EOI); + /* Unmask interrupts */ + snd_cs4281_pokeBA0(chip, BA0_HIMR, 0x7fffffff & ~( + BA0_HISR_MIDI | + BA0_HISR_DMAI | + BA0_HISR_DMA(0) | + BA0_HISR_DMA(1) | + BA0_HISR_DMA(2) | + BA0_HISR_DMA(3))); + + return 0; +} + +/* + * MIDI section + */ + +static void snd_cs4281_midi_reset(struct cs4281 *chip) +{ + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr | BA0_MIDCR_MRST); + udelay(100); + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); +} + +static int snd_cs4281_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct cs4281 *chip = substream->rmidi->private_data; + + guard(spinlock_irq)(&chip->reg_lock); + chip->midcr |= BA0_MIDCR_RXE; + chip->midi_input = substream; + if (!(chip->uartm & CS4281_MODE_OUTPUT)) { + snd_cs4281_midi_reset(chip); + } else { + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + return 0; +} + +static int snd_cs4281_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct cs4281 *chip = substream->rmidi->private_data; + + guard(spinlock_irq)(&chip->reg_lock); + chip->midcr &= ~(BA0_MIDCR_RXE | BA0_MIDCR_RIE); + chip->midi_input = NULL; + if (!(chip->uartm & CS4281_MODE_OUTPUT)) { + snd_cs4281_midi_reset(chip); + } else { + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + chip->uartm &= ~CS4281_MODE_INPUT; + return 0; +} + +static int snd_cs4281_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct cs4281 *chip = substream->rmidi->private_data; + + guard(spinlock_irq)(&chip->reg_lock); + chip->uartm |= CS4281_MODE_OUTPUT; + chip->midcr |= BA0_MIDCR_TXE; + chip->midi_output = substream; + if (!(chip->uartm & CS4281_MODE_INPUT)) { + snd_cs4281_midi_reset(chip); + } else { + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + return 0; +} + +static int snd_cs4281_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct cs4281 *chip = substream->rmidi->private_data; + + guard(spinlock_irq)(&chip->reg_lock); + chip->midcr &= ~(BA0_MIDCR_TXE | BA0_MIDCR_TIE); + chip->midi_output = NULL; + if (!(chip->uartm & CS4281_MODE_INPUT)) { + snd_cs4281_midi_reset(chip); + } else { + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + chip->uartm &= ~CS4281_MODE_OUTPUT; + return 0; +} + +static void snd_cs4281_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct cs4281 *chip = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&chip->reg_lock); + if (up) { + if ((chip->midcr & BA0_MIDCR_RIE) == 0) { + chip->midcr |= BA0_MIDCR_RIE; + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } else { + if (chip->midcr & BA0_MIDCR_RIE) { + chip->midcr &= ~BA0_MIDCR_RIE; + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } +} + +static void snd_cs4281_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct cs4281 *chip = substream->rmidi->private_data; + unsigned char byte; + + guard(spinlock_irqsave)(&chip->reg_lock); + if (up) { + if ((chip->midcr & BA0_MIDCR_TIE) == 0) { + chip->midcr |= BA0_MIDCR_TIE; + /* fill UART FIFO buffer at first, and turn Tx interrupts only if necessary */ + while ((chip->midcr & BA0_MIDCR_TIE) && + (snd_cs4281_peekBA0(chip, BA0_MIDSR) & BA0_MIDSR_TBF) == 0) { + if (snd_rawmidi_transmit(substream, &byte, 1) != 1) { + chip->midcr &= ~BA0_MIDCR_TIE; + } else { + snd_cs4281_pokeBA0(chip, BA0_MIDWP, byte); + } + } + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } else { + if (chip->midcr & BA0_MIDCR_TIE) { + chip->midcr &= ~BA0_MIDCR_TIE; + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } +} + +static const struct snd_rawmidi_ops snd_cs4281_midi_output = +{ + .open = snd_cs4281_midi_output_open, + .close = snd_cs4281_midi_output_close, + .trigger = snd_cs4281_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_cs4281_midi_input = +{ + .open = snd_cs4281_midi_input_open, + .close = snd_cs4281_midi_input_close, + .trigger = snd_cs4281_midi_input_trigger, +}; + +static int snd_cs4281_midi(struct cs4281 *chip, int device) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(chip->card, "CS4281", device, 1, 1, &rmidi); + if (err < 0) + return err; + strscpy(rmidi->name, "CS4281"); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_cs4281_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_cs4281_midi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = chip; + chip->rmidi = rmidi; + return 0; +} + +/* + * Interrupt handler + */ + +static irqreturn_t snd_cs4281_interrupt(int irq, void *dev_id) +{ + struct cs4281 *chip = dev_id; + unsigned int status, dma, val; + struct cs4281_dma *cdma; + + if (chip == NULL) + return IRQ_NONE; + status = snd_cs4281_peekBA0(chip, BA0_HISR); + if ((status & 0x7fffffff) == 0) { + snd_cs4281_pokeBA0(chip, BA0_HICR, BA0_HICR_EOI); + return IRQ_NONE; + } + + if (status & (BA0_HISR_DMA(0)|BA0_HISR_DMA(1)|BA0_HISR_DMA(2)|BA0_HISR_DMA(3))) { + for (dma = 0; dma < 4; dma++) { + bool period_elapsed = false; + cdma = &chip->dma[dma]; + + if (status & BA0_HISR_DMA(dma)) { + guard(spinlock)(&chip->reg_lock); + /* ack DMA IRQ */ + val = snd_cs4281_peekBA0(chip, cdma->regHDSR); + /* workaround, sometimes CS4281 acknowledges */ + /* end or middle transfer position twice */ + cdma->frag++; + if ((val & BA0_HDSR_DHTC) && !(cdma->frag & 1)) { + cdma->frag--; + chip->spurious_dhtc_irq++; + continue; + } + if ((val & BA0_HDSR_DTC) && (cdma->frag & 1)) { + cdma->frag--; + chip->spurious_dtc_irq++; + continue; + } + period_elapsed = true; + } + if (period_elapsed) + snd_pcm_period_elapsed(cdma->substream); + } + } + + if ((status & BA0_HISR_MIDI) && chip->rmidi) { + unsigned char c; + + guard(spinlock)(&chip->reg_lock); + while ((snd_cs4281_peekBA0(chip, BA0_MIDSR) & BA0_MIDSR_RBE) == 0) { + c = snd_cs4281_peekBA0(chip, BA0_MIDRP); + if ((chip->midcr & BA0_MIDCR_RIE) == 0) + continue; + snd_rawmidi_receive(chip->midi_input, &c, 1); + } + while ((snd_cs4281_peekBA0(chip, BA0_MIDSR) & BA0_MIDSR_TBF) == 0) { + if ((chip->midcr & BA0_MIDCR_TIE) == 0) + break; + if (snd_rawmidi_transmit(chip->midi_output, &c, 1) != 1) { + chip->midcr &= ~BA0_MIDCR_TIE; + snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr); + break; + } + snd_cs4281_pokeBA0(chip, BA0_MIDWP, c); + } + } + + /* EOI to the PCI part... reenables interrupts */ + snd_cs4281_pokeBA0(chip, BA0_HICR, BA0_HICR_EOI); + + return IRQ_HANDLED; +} + + +/* + * OPL3 command + */ +static void snd_cs4281_opl3_command(struct snd_opl3 *opl3, unsigned short cmd, + unsigned char val) +{ + struct cs4281 *chip = opl3->private_data; + void __iomem *port; + + if (cmd & OPL3_RIGHT) + port = chip->ba0 + BA0_B1AP; /* right port */ + else + port = chip->ba0 + BA0_B0AP; /* left port */ + + guard(spinlock_irqsave)(&opl3->reg_lock); + + writel((unsigned int)cmd, port); + udelay(10); + + writel((unsigned int)val, port + 4); + udelay(30); +} + +static int __snd_cs4281_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct cs4281 *chip; + struct snd_opl3 *opl3; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + err = snd_cs4281_create(card, pci, dual_codec[dev]); + if (err < 0) + return err; + + err = snd_cs4281_mixer(chip); + if (err < 0) + return err; + err = snd_cs4281_pcm(chip, 0); + if (err < 0) + return err; + err = snd_cs4281_midi(chip, 0); + if (err < 0) + return err; + err = snd_opl3_new(card, OPL3_HW_OPL3_CS4281, &opl3); + if (err < 0) + return err; + opl3->private_data = chip; + opl3->command = snd_cs4281_opl3_command; + snd_opl3_init(opl3); + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + return err; + snd_cs4281_create_gameport(chip); + strscpy(card->driver, "CS4281"); + strscpy(card->shortname, "Cirrus Logic CS4281"); + sprintf(card->longname, "%s at 0x%lx, irq %d", + card->shortname, + chip->ba0_addr, + chip->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_cs4281_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_cs4281_probe(pci, pci_id)); +} + +/* + * Power Management + */ +static const int saved_regs[SUSPEND_REGISTERS] = { + BA0_JSCTL, + BA0_GPIOR, + BA0_SSCR, + BA0_MIDCR, + BA0_SRCSA, + BA0_PASR, + BA0_CASR, + BA0_DACSR, + BA0_ADCSR, + BA0_FMLVC, + BA0_FMRVC, + BA0_PPLVC, + BA0_PPRVC, +}; + +#define CLKCR1_CKRA 0x00010000L + +static int cs4281_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct cs4281 *chip = card->private_data; + u32 ulCLK; + unsigned int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(chip->ac97); + snd_ac97_suspend(chip->ac97_secondary); + + ulCLK = snd_cs4281_peekBA0(chip, BA0_CLKCR1); + ulCLK |= CLKCR1_CKRA; + snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK); + + /* Disable interrupts. */ + snd_cs4281_pokeBA0(chip, BA0_HICR, BA0_HICR_CHGM); + + /* remember the status registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + if (saved_regs[i]) + chip->suspend_regs[i] = snd_cs4281_peekBA0(chip, saved_regs[i]); + + /* Turn off the serial ports. */ + snd_cs4281_pokeBA0(chip, BA0_SERMC, 0); + + /* Power off FM, Joystick, AC link, */ + snd_cs4281_pokeBA0(chip, BA0_SSPM, 0); + + /* DLL off. */ + snd_cs4281_pokeBA0(chip, BA0_CLKCR1, 0); + + /* AC link off. */ + snd_cs4281_pokeBA0(chip, BA0_SPMC, 0); + + ulCLK = snd_cs4281_peekBA0(chip, BA0_CLKCR1); + ulCLK &= ~CLKCR1_CKRA; + snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK); + return 0; +} + +static int cs4281_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct cs4281 *chip = card->private_data; + unsigned int i; + u32 ulCLK; + + ulCLK = snd_cs4281_peekBA0(chip, BA0_CLKCR1); + ulCLK |= CLKCR1_CKRA; + snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK); + + snd_cs4281_chip_init(chip); + + /* restore the status registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + if (saved_regs[i]) + snd_cs4281_pokeBA0(chip, saved_regs[i], chip->suspend_regs[i]); + + snd_ac97_resume(chip->ac97); + snd_ac97_resume(chip->ac97_secondary); + + ulCLK = snd_cs4281_peekBA0(chip, BA0_CLKCR1); + ulCLK &= ~CLKCR1_CKRA; + snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(cs4281_pm, cs4281_suspend, cs4281_resume); + +static struct pci_driver cs4281_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_cs4281_ids, + .probe = snd_cs4281_probe, + .driver = { + .pm = &cs4281_pm, + }, +}; + +module_pci_driver(cs4281_driver); diff --git a/sound/pci/cs46xx/Makefile b/sound/pci/cs46xx/Makefile new file mode 100644 index 000000000..3ed2ceb40 --- /dev/null +++ b/sound/pci/cs46xx/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-cs46xx-y := cs46xx.o cs46xx_lib.o +snd-cs46xx-$(CONFIG_SND_CS46XX_NEW_DSP) += dsp_spos.o dsp_spos_scb_lib.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_CS46XX) += snd-cs46xx.o diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c new file mode 100644 index 000000000..0cb7c2a29 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + */ + +/* + NOTES: + - sometimes the sound is metallic and sibilant, unloading and + reloading the module may solve this. +*/ + +#include +#include +#include +#include +#include +#include "cs46xx.h" +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Cirrus Logic Sound Fusion CS46XX"); +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_PNP; /* Enable this card */ +static bool external_amp[SNDRV_CARDS]; +static bool thinkpad[SNDRV_CARDS]; +static bool mmap_valid[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the CS46xx soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the CS46xx soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable CS46xx soundcard."); +module_param_array(external_amp, bool, NULL, 0444); +MODULE_PARM_DESC(external_amp, "Force to enable external amplifier."); +module_param_array(thinkpad, bool, NULL, 0444); +MODULE_PARM_DESC(thinkpad, "Force to enable Thinkpad's CLKRUN control."); +module_param_array(mmap_valid, bool, NULL, 0444); +MODULE_PARM_DESC(mmap_valid, "Support OSS mmap."); + +static const struct pci_device_id snd_cs46xx_ids[] = { + { PCI_VDEVICE(CIRRUS, 0x6001) }, /* CS4280 */ + { PCI_VDEVICE(CIRRUS, 0x6003) }, /* CS4612 */ + { PCI_VDEVICE(CIRRUS, 0x6004) }, /* CS4615 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_cs46xx_ids); + +static int snd_card_cs46xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_cs46xx *chip; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + err = snd_cs46xx_create(card, pci, + external_amp[dev], thinkpad[dev]); + if (err < 0) + goto error; + card->private_data = chip; + chip->accept_valid = mmap_valid[dev]; + err = snd_cs46xx_pcm(chip, 0); + if (err < 0) + goto error; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + err = snd_cs46xx_pcm_rear(chip, 1); + if (err < 0) + goto error; + err = snd_cs46xx_pcm_iec958(chip, 2); + if (err < 0) + goto error; +#endif + err = snd_cs46xx_mixer(chip, 2); + if (err < 0) + goto error; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->nr_ac97_codecs ==2) { + err = snd_cs46xx_pcm_center_lfe(chip, 3); + if (err < 0) + goto error; + } +#endif + err = snd_cs46xx_midi(chip, 0); + if (err < 0) + goto error; + err = snd_cs46xx_start_dsp(chip); + if (err < 0) + goto error; + + snd_cs46xx_gameport(chip); + + strscpy(card->driver, "CS46xx"); + strscpy(card->shortname, "Sound Fusion CS46xx"); + sprintf(card->longname, "%s at 0x%lx/0x%lx, irq %i", + card->shortname, + chip->ba0_addr, + chip->ba1_addr, + chip->irq); + + err = snd_card_register(card); + if (err < 0) + goto error; + + pci_set_drvdata(pci, card); + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +static struct pci_driver cs46xx_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_cs46xx_ids, + .probe = snd_card_cs46xx_probe, +#ifdef CONFIG_PM_SLEEP + .driver = { + .pm = &snd_cs46xx_pm, + }, +#endif +}; + +module_pci_driver(cs46xx_driver); diff --git a/sound/pci/cs46xx/cs46xx.h b/sound/pci/cs46xx/cs46xx.h new file mode 100644 index 000000000..c4f0a0b94 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx.h @@ -0,0 +1,1732 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_CS46XX_H +#define __SOUND_CS46XX_H + +/* + * Copyright (c) by Jaroslav Kysela , + * Cirrus Logic, Inc. + * Definitions for Cirrus Logic CS46xx chips + */ + +#include +#include +#include +#include +#include "cs46xx_dsp_spos.h" + +/* + * Direct registers + */ + +/* + * The following define the offsets of the registers accessed via base address + * register zero on the CS46xx part. + */ +#define BA0_HISR 0x00000000 +#define BA0_HSR0 0x00000004 +#define BA0_HICR 0x00000008 +#define BA0_DMSR 0x00000100 +#define BA0_HSAR 0x00000110 +#define BA0_HDAR 0x00000114 +#define BA0_HDMR 0x00000118 +#define BA0_HDCR 0x0000011C +#define BA0_PFMC 0x00000200 +#define BA0_PFCV1 0x00000204 +#define BA0_PFCV2 0x00000208 +#define BA0_PCICFG00 0x00000300 +#define BA0_PCICFG04 0x00000304 +#define BA0_PCICFG08 0x00000308 +#define BA0_PCICFG0C 0x0000030C +#define BA0_PCICFG10 0x00000310 +#define BA0_PCICFG14 0x00000314 +#define BA0_PCICFG18 0x00000318 +#define BA0_PCICFG1C 0x0000031C +#define BA0_PCICFG20 0x00000320 +#define BA0_PCICFG24 0x00000324 +#define BA0_PCICFG28 0x00000328 +#define BA0_PCICFG2C 0x0000032C +#define BA0_PCICFG30 0x00000330 +#define BA0_PCICFG34 0x00000334 +#define BA0_PCICFG38 0x00000338 +#define BA0_PCICFG3C 0x0000033C +#define BA0_CLKCR1 0x00000400 +#define BA0_CLKCR2 0x00000404 +#define BA0_PLLM 0x00000408 +#define BA0_PLLCC 0x0000040C +#define BA0_FRR 0x00000410 +#define BA0_CFL1 0x00000414 +#define BA0_CFL2 0x00000418 +#define BA0_SERMC1 0x00000420 +#define BA0_SERMC2 0x00000424 +#define BA0_SERC1 0x00000428 +#define BA0_SERC2 0x0000042C +#define BA0_SERC3 0x00000430 +#define BA0_SERC4 0x00000434 +#define BA0_SERC5 0x00000438 +#define BA0_SERBSP 0x0000043C +#define BA0_SERBST 0x00000440 +#define BA0_SERBCM 0x00000444 +#define BA0_SERBAD 0x00000448 +#define BA0_SERBCF 0x0000044C +#define BA0_SERBWP 0x00000450 +#define BA0_SERBRP 0x00000454 +#ifndef NO_CS4612 +#define BA0_ASER_FADDR 0x00000458 +#endif +#define BA0_ACCTL 0x00000460 +#define BA0_ACSTS 0x00000464 +#define BA0_ACOSV 0x00000468 +#define BA0_ACCAD 0x0000046C +#define BA0_ACCDA 0x00000470 +#define BA0_ACISV 0x00000474 +#define BA0_ACSAD 0x00000478 +#define BA0_ACSDA 0x0000047C +#define BA0_JSPT 0x00000480 +#define BA0_JSCTL 0x00000484 +#define BA0_JSC1 0x00000488 +#define BA0_JSC2 0x0000048C +#define BA0_MIDCR 0x00000490 +#define BA0_MIDSR 0x00000494 +#define BA0_MIDWP 0x00000498 +#define BA0_MIDRP 0x0000049C +#define BA0_JSIO 0x000004A0 +#ifndef NO_CS4612 +#define BA0_ASER_MASTER 0x000004A4 +#endif +#define BA0_CFGI 0x000004B0 +#define BA0_SSVID 0x000004B4 +#define BA0_GPIOR 0x000004B8 +#ifndef NO_CS4612 +#define BA0_EGPIODR 0x000004BC +#define BA0_EGPIOPTR 0x000004C0 +#define BA0_EGPIOTR 0x000004C4 +#define BA0_EGPIOWR 0x000004C8 +#define BA0_EGPIOSR 0x000004CC +#define BA0_SERC6 0x000004D0 +#define BA0_SERC7 0x000004D4 +#define BA0_SERACC 0x000004D8 +#define BA0_ACCTL2 0x000004E0 +#define BA0_ACSTS2 0x000004E4 +#define BA0_ACOSV2 0x000004E8 +#define BA0_ACCAD2 0x000004EC +#define BA0_ACCDA2 0x000004F0 +#define BA0_ACISV2 0x000004F4 +#define BA0_ACSAD2 0x000004F8 +#define BA0_ACSDA2 0x000004FC +#define BA0_IOTAC0 0x00000500 +#define BA0_IOTAC1 0x00000504 +#define BA0_IOTAC2 0x00000508 +#define BA0_IOTAC3 0x0000050C +#define BA0_IOTAC4 0x00000510 +#define BA0_IOTAC5 0x00000514 +#define BA0_IOTAC6 0x00000518 +#define BA0_IOTAC7 0x0000051C +#define BA0_IOTAC8 0x00000520 +#define BA0_IOTAC9 0x00000524 +#define BA0_IOTAC10 0x00000528 +#define BA0_IOTAC11 0x0000052C +#define BA0_IOTFR0 0x00000540 +#define BA0_IOTFR1 0x00000544 +#define BA0_IOTFR2 0x00000548 +#define BA0_IOTFR3 0x0000054C +#define BA0_IOTFR4 0x00000550 +#define BA0_IOTFR5 0x00000554 +#define BA0_IOTFR6 0x00000558 +#define BA0_IOTFR7 0x0000055C +#define BA0_IOTFIFO 0x00000580 +#define BA0_IOTRRD 0x00000584 +#define BA0_IOTFP 0x00000588 +#define BA0_IOTCR 0x0000058C +#define BA0_DPCID 0x00000590 +#define BA0_DPCIA 0x00000594 +#define BA0_DPCIC 0x00000598 +#define BA0_PCPCIR 0x00000600 +#define BA0_PCPCIG 0x00000604 +#define BA0_PCPCIEN 0x00000608 +#define BA0_EPCIPMC 0x00000610 +#endif + +/* + * The following define the offsets of the registers and memories accessed via + * base address register one on the CS46xx part. + */ +#define BA1_SP_DMEM0 0x00000000 +#define BA1_SP_DMEM1 0x00010000 +#define BA1_SP_PMEM 0x00020000 +#define BA1_SP_REG 0x00030000 +#define BA1_SPCR 0x00030000 +#define BA1_DREG 0x00030004 +#define BA1_DSRWP 0x00030008 +#define BA1_TWPR 0x0003000C +#define BA1_SPWR 0x00030010 +#define BA1_SPIR 0x00030014 +#define BA1_FGR1 0x00030020 +#define BA1_SPCS 0x00030028 +#define BA1_SDSR 0x0003002C +#define BA1_FRMT 0x00030030 +#define BA1_FRCC 0x00030034 +#define BA1_FRSC 0x00030038 +#define BA1_OMNI_MEM 0x000E0000 + + +/* + * The following defines are for the flags in the host interrupt status + * register. + */ +#define HISR_VC_MASK 0x0000FFFF +#define HISR_VC0 0x00000001 +#define HISR_VC1 0x00000002 +#define HISR_VC2 0x00000004 +#define HISR_VC3 0x00000008 +#define HISR_VC4 0x00000010 +#define HISR_VC5 0x00000020 +#define HISR_VC6 0x00000040 +#define HISR_VC7 0x00000080 +#define HISR_VC8 0x00000100 +#define HISR_VC9 0x00000200 +#define HISR_VC10 0x00000400 +#define HISR_VC11 0x00000800 +#define HISR_VC12 0x00001000 +#define HISR_VC13 0x00002000 +#define HISR_VC14 0x00004000 +#define HISR_VC15 0x00008000 +#define HISR_INT0 0x00010000 +#define HISR_INT1 0x00020000 +#define HISR_DMAI 0x00040000 +#define HISR_FROVR 0x00080000 +#define HISR_MIDI 0x00100000 +#ifdef NO_CS4612 +#define HISR_RESERVED 0x0FE00000 +#else +#define HISR_SBINT 0x00200000 +#define HISR_RESERVED 0x0FC00000 +#endif +#define HISR_H0P 0x40000000 +#define HISR_INTENA 0x80000000 + +/* + * The following defines are for the flags in the host signal register 0. + */ +#define HSR0_VC_MASK 0xFFFFFFFF +#define HSR0_VC16 0x00000001 +#define HSR0_VC17 0x00000002 +#define HSR0_VC18 0x00000004 +#define HSR0_VC19 0x00000008 +#define HSR0_VC20 0x00000010 +#define HSR0_VC21 0x00000020 +#define HSR0_VC22 0x00000040 +#define HSR0_VC23 0x00000080 +#define HSR0_VC24 0x00000100 +#define HSR0_VC25 0x00000200 +#define HSR0_VC26 0x00000400 +#define HSR0_VC27 0x00000800 +#define HSR0_VC28 0x00001000 +#define HSR0_VC29 0x00002000 +#define HSR0_VC30 0x00004000 +#define HSR0_VC31 0x00008000 +#define HSR0_VC32 0x00010000 +#define HSR0_VC33 0x00020000 +#define HSR0_VC34 0x00040000 +#define HSR0_VC35 0x00080000 +#define HSR0_VC36 0x00100000 +#define HSR0_VC37 0x00200000 +#define HSR0_VC38 0x00400000 +#define HSR0_VC39 0x00800000 +#define HSR0_VC40 0x01000000 +#define HSR0_VC41 0x02000000 +#define HSR0_VC42 0x04000000 +#define HSR0_VC43 0x08000000 +#define HSR0_VC44 0x10000000 +#define HSR0_VC45 0x20000000 +#define HSR0_VC46 0x40000000 +#define HSR0_VC47 0x80000000 + +/* + * The following defines are for the flags in the host interrupt control + * register. + */ +#define HICR_IEV 0x00000001 +#define HICR_CHGM 0x00000002 + +/* + * The following defines are for the flags in the DMA status register. + */ +#define DMSR_HP 0x00000001 +#define DMSR_HR 0x00000002 +#define DMSR_SP 0x00000004 +#define DMSR_SR 0x00000008 + +/* + * The following defines are for the flags in the host DMA source address + * register. + */ +#define HSAR_HOST_ADDR_MASK 0xFFFFFFFF +#define HSAR_DSP_ADDR_MASK 0x0000FFFF +#define HSAR_MEMID_MASK 0x000F0000 +#define HSAR_MEMID_SP_DMEM0 0x00000000 +#define HSAR_MEMID_SP_DMEM1 0x00010000 +#define HSAR_MEMID_SP_PMEM 0x00020000 +#define HSAR_MEMID_SP_DEBUG 0x00030000 +#define HSAR_MEMID_OMNI_MEM 0x000E0000 +#define HSAR_END 0x40000000 +#define HSAR_ERR 0x80000000 + +/* + * The following defines are for the flags in the host DMA destination address + * register. + */ +#define HDAR_HOST_ADDR_MASK 0xFFFFFFFF +#define HDAR_DSP_ADDR_MASK 0x0000FFFF +#define HDAR_MEMID_MASK 0x000F0000 +#define HDAR_MEMID_SP_DMEM0 0x00000000 +#define HDAR_MEMID_SP_DMEM1 0x00010000 +#define HDAR_MEMID_SP_PMEM 0x00020000 +#define HDAR_MEMID_SP_DEBUG 0x00030000 +#define HDAR_MEMID_OMNI_MEM 0x000E0000 +#define HDAR_END 0x40000000 +#define HDAR_ERR 0x80000000 + +/* + * The following defines are for the flags in the host DMA control register. + */ +#define HDMR_AC_MASK 0x0000F000 +#define HDMR_AC_8_16 0x00001000 +#define HDMR_AC_M_S 0x00002000 +#define HDMR_AC_B_L 0x00004000 +#define HDMR_AC_S_U 0x00008000 + +/* + * The following defines are for the flags in the host DMA control register. + */ +#define HDCR_COUNT_MASK 0x000003FF +#define HDCR_DONE 0x00004000 +#define HDCR_OPT 0x00008000 +#define HDCR_WBD 0x00400000 +#define HDCR_WBS 0x00800000 +#define HDCR_DMS_MASK 0x07000000 +#define HDCR_DMS_LINEAR 0x00000000 +#define HDCR_DMS_16_DWORDS 0x01000000 +#define HDCR_DMS_32_DWORDS 0x02000000 +#define HDCR_DMS_64_DWORDS 0x03000000 +#define HDCR_DMS_128_DWORDS 0x04000000 +#define HDCR_DMS_256_DWORDS 0x05000000 +#define HDCR_DMS_512_DWORDS 0x06000000 +#define HDCR_DMS_1024_DWORDS 0x07000000 +#define HDCR_DH 0x08000000 +#define HDCR_SMS_MASK 0x70000000 +#define HDCR_SMS_LINEAR 0x00000000 +#define HDCR_SMS_16_DWORDS 0x10000000 +#define HDCR_SMS_32_DWORDS 0x20000000 +#define HDCR_SMS_64_DWORDS 0x30000000 +#define HDCR_SMS_128_DWORDS 0x40000000 +#define HDCR_SMS_256_DWORDS 0x50000000 +#define HDCR_SMS_512_DWORDS 0x60000000 +#define HDCR_SMS_1024_DWORDS 0x70000000 +#define HDCR_SH 0x80000000 +#define HDCR_COUNT_SHIFT 0 + +/* + * The following defines are for the flags in the performance monitor control + * register. + */ +#define PFMC_C1SS_MASK 0x0000001F +#define PFMC_C1EV 0x00000020 +#define PFMC_C1RS 0x00008000 +#define PFMC_C2SS_MASK 0x001F0000 +#define PFMC_C2EV 0x00200000 +#define PFMC_C2RS 0x80000000 +#define PFMC_C1SS_SHIFT 0 +#define PFMC_C2SS_SHIFT 16 +#define PFMC_BUS_GRANT 0 +#define PFMC_GRANT_AFTER_REQ 1 +#define PFMC_TRANSACTION 2 +#define PFMC_DWORD_TRANSFER 3 +#define PFMC_SLAVE_READ 4 +#define PFMC_SLAVE_WRITE 5 +#define PFMC_PREEMPTION 6 +#define PFMC_DISCONNECT_RETRY 7 +#define PFMC_INTERRUPT 8 +#define PFMC_BUS_OWNERSHIP 9 +#define PFMC_TRANSACTION_LAG 10 +#define PFMC_PCI_CLOCK 11 +#define PFMC_SERIAL_CLOCK 12 +#define PFMC_SP_CLOCK 13 + +/* + * The following defines are for the flags in the performance counter value 1 + * register. + */ +#define PFCV1_PC1V_MASK 0xFFFFFFFF +#define PFCV1_PC1V_SHIFT 0 + +/* + * The following defines are for the flags in the performance counter value 2 + * register. + */ +#define PFCV2_PC2V_MASK 0xFFFFFFFF +#define PFCV2_PC2V_SHIFT 0 + +/* + * The following defines are for the flags in the clock control register 1. + */ +#define CLKCR1_OSCS 0x00000001 +#define CLKCR1_OSCP 0x00000002 +#define CLKCR1_PLLSS_MASK 0x0000000C +#define CLKCR1_PLLSS_SERIAL 0x00000000 +#define CLKCR1_PLLSS_CRYSTAL 0x00000004 +#define CLKCR1_PLLSS_PCI 0x00000008 +#define CLKCR1_PLLSS_RESERVED 0x0000000C +#define CLKCR1_PLLP 0x00000010 +#define CLKCR1_SWCE 0x00000020 +#define CLKCR1_PLLOS 0x00000040 + +/* + * The following defines are for the flags in the clock control register 2. + */ +#define CLKCR2_PDIVS_MASK 0x0000000F +#define CLKCR2_PDIVS_1 0x00000001 +#define CLKCR2_PDIVS_2 0x00000002 +#define CLKCR2_PDIVS_4 0x00000004 +#define CLKCR2_PDIVS_7 0x00000007 +#define CLKCR2_PDIVS_8 0x00000008 +#define CLKCR2_PDIVS_16 0x00000000 + +/* + * The following defines are for the flags in the PLL multiplier register. + */ +#define PLLM_MASK 0x000000FF +#define PLLM_SHIFT 0 + +/* + * The following defines are for the flags in the PLL capacitor coefficient + * register. + */ +#define PLLCC_CDR_MASK 0x00000007 +#ifndef NO_CS4610 +#define PLLCC_CDR_240_350_MHZ 0x00000000 +#define PLLCC_CDR_184_265_MHZ 0x00000001 +#define PLLCC_CDR_144_205_MHZ 0x00000002 +#define PLLCC_CDR_111_160_MHZ 0x00000003 +#define PLLCC_CDR_87_123_MHZ 0x00000004 +#define PLLCC_CDR_67_96_MHZ 0x00000005 +#define PLLCC_CDR_52_74_MHZ 0x00000006 +#define PLLCC_CDR_45_58_MHZ 0x00000007 +#endif +#ifndef NO_CS4612 +#define PLLCC_CDR_271_398_MHZ 0x00000000 +#define PLLCC_CDR_227_330_MHZ 0x00000001 +#define PLLCC_CDR_167_239_MHZ 0x00000002 +#define PLLCC_CDR_150_215_MHZ 0x00000003 +#define PLLCC_CDR_107_154_MHZ 0x00000004 +#define PLLCC_CDR_98_140_MHZ 0x00000005 +#define PLLCC_CDR_73_104_MHZ 0x00000006 +#define PLLCC_CDR_63_90_MHZ 0x00000007 +#endif +#define PLLCC_LPF_MASK 0x000000F8 +#ifndef NO_CS4610 +#define PLLCC_LPF_23850_60000_KHZ 0x00000000 +#define PLLCC_LPF_7960_26290_KHZ 0x00000008 +#define PLLCC_LPF_4160_10980_KHZ 0x00000018 +#define PLLCC_LPF_1740_4580_KHZ 0x00000038 +#define PLLCC_LPF_724_1910_KHZ 0x00000078 +#define PLLCC_LPF_317_798_KHZ 0x000000F8 +#endif +#ifndef NO_CS4612 +#define PLLCC_LPF_25580_64530_KHZ 0x00000000 +#define PLLCC_LPF_14360_37270_KHZ 0x00000008 +#define PLLCC_LPF_6100_16020_KHZ 0x00000018 +#define PLLCC_LPF_2540_6690_KHZ 0x00000038 +#define PLLCC_LPF_1050_2780_KHZ 0x00000078 +#define PLLCC_LPF_450_1160_KHZ 0x000000F8 +#endif + +/* + * The following defines are for the flags in the feature reporting register. + */ +#define FRR_FAB_MASK 0x00000003 +#define FRR_MASK_MASK 0x0000001C +#ifdef NO_CS4612 +#define FRR_CFOP_MASK 0x000000E0 +#else +#define FRR_CFOP_MASK 0x00000FE0 +#endif +#define FRR_CFOP_NOT_DVD 0x00000020 +#define FRR_CFOP_A3D 0x00000040 +#define FRR_CFOP_128_PIN 0x00000080 +#ifndef NO_CS4612 +#define FRR_CFOP_CS4280 0x00000800 +#endif +#define FRR_FAB_SHIFT 0 +#define FRR_MASK_SHIFT 2 +#define FRR_CFOP_SHIFT 5 + +/* + * The following defines are for the flags in the configuration load 1 + * register. + */ +#define CFL1_CLOCK_SOURCE_MASK 0x00000003 +#define CFL1_CLOCK_SOURCE_CS423X 0x00000000 +#define CFL1_CLOCK_SOURCE_AC97 0x00000001 +#define CFL1_CLOCK_SOURCE_CRYSTAL 0x00000002 +#define CFL1_CLOCK_SOURCE_DUAL_AC97 0x00000003 +#define CFL1_VALID_DATA_MASK 0x000000FF + +/* + * The following defines are for the flags in the configuration load 2 + * register. + */ +#define CFL2_VALID_DATA_MASK 0x000000FF + +/* + * The following defines are for the flags in the serial port master control + * register 1. + */ +#define SERMC1_MSPE 0x00000001 +#define SERMC1_PTC_MASK 0x0000000E +#define SERMC1_PTC_CS423X 0x00000000 +#define SERMC1_PTC_AC97 0x00000002 +#define SERMC1_PTC_DAC 0x00000004 +#define SERMC1_PLB 0x00000010 +#define SERMC1_XLB 0x00000020 + +/* + * The following defines are for the flags in the serial port master control + * register 2. + */ +#define SERMC2_LROE 0x00000001 +#define SERMC2_MCOE 0x00000002 +#define SERMC2_MCDIV 0x00000004 + +/* + * The following defines are for the flags in the serial port 1 configuration + * register. + */ +#define SERC1_SO1EN 0x00000001 +#define SERC1_SO1F_MASK 0x0000000E +#define SERC1_SO1F_CS423X 0x00000000 +#define SERC1_SO1F_AC97 0x00000002 +#define SERC1_SO1F_DAC 0x00000004 +#define SERC1_SO1F_SPDIF 0x00000006 + +/* + * The following defines are for the flags in the serial port 2 configuration + * register. + */ +#define SERC2_SI1EN 0x00000001 +#define SERC2_SI1F_MASK 0x0000000E +#define SERC2_SI1F_CS423X 0x00000000 +#define SERC2_SI1F_AC97 0x00000002 +#define SERC2_SI1F_ADC 0x00000004 +#define SERC2_SI1F_SPDIF 0x00000006 + +/* + * The following defines are for the flags in the serial port 3 configuration + * register. + */ +#define SERC3_SO2EN 0x00000001 +#define SERC3_SO2F_MASK 0x00000006 +#define SERC3_SO2F_DAC 0x00000000 +#define SERC3_SO2F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port 4 configuration + * register. + */ +#define SERC4_SO3EN 0x00000001 +#define SERC4_SO3F_MASK 0x00000006 +#define SERC4_SO3F_DAC 0x00000000 +#define SERC4_SO3F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port 5 configuration + * register. + */ +#define SERC5_SI2EN 0x00000001 +#define SERC5_SI2F_MASK 0x00000006 +#define SERC5_SI2F_ADC 0x00000000 +#define SERC5_SI2F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor sample + * pointer register. + */ +#define SERBSP_FSP_MASK 0x0000000F +#define SERBSP_FSP_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor status + * register. + */ +#define SERBST_RRDY 0x00000001 +#define SERBST_WBSY 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor command + * register. + */ +#define SERBCM_RDC 0x00000001 +#define SERBCM_WRC 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor address + * register. + */ +#ifdef NO_CS4612 +#define SERBAD_FAD_MASK 0x000000FF +#else +#define SERBAD_FAD_MASK 0x000001FF +#endif +#define SERBAD_FAD_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor + * configuration register. + */ +#define SERBCF_HBP 0x00000001 + +/* + * The following defines are for the flags in the serial port backdoor write + * port register. + */ +#define SERBWP_FWD_MASK 0x000FFFFF +#define SERBWP_FWD_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor read + * port register. + */ +#define SERBRP_FRD_MASK 0x000FFFFF +#define SERBRP_FRD_SHIFT 0 + +/* + * The following defines are for the flags in the async FIFO address register. + */ +#ifndef NO_CS4612 +#define ASER_FADDR_A1_MASK 0x000001FF +#define ASER_FADDR_EN1 0x00008000 +#define ASER_FADDR_A2_MASK 0x01FF0000 +#define ASER_FADDR_EN2 0x80000000 +#define ASER_FADDR_A1_SHIFT 0 +#define ASER_FADDR_A2_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the AC97 control register. + */ +#define ACCTL_RSTN 0x00000001 +#define ACCTL_ESYN 0x00000002 +#define ACCTL_VFRM 0x00000004 +#define ACCTL_DCV 0x00000008 +#define ACCTL_CRW 0x00000010 +#define ACCTL_ASYN 0x00000020 +#ifndef NO_CS4612 +#define ACCTL_TC 0x00000040 +#endif + +/* + * The following defines are for the flags in the AC97 status register. + */ +#define ACSTS_CRDY 0x00000001 +#define ACSTS_VSTS 0x00000002 +#ifndef NO_CS4612 +#define ACSTS_WKUP 0x00000004 +#endif + +/* + * The following defines are for the flags in the AC97 output slot valid + * register. + */ +#define ACOSV_SLV3 0x00000001 +#define ACOSV_SLV4 0x00000002 +#define ACOSV_SLV5 0x00000004 +#define ACOSV_SLV6 0x00000008 +#define ACOSV_SLV7 0x00000010 +#define ACOSV_SLV8 0x00000020 +#define ACOSV_SLV9 0x00000040 +#define ACOSV_SLV10 0x00000080 +#define ACOSV_SLV11 0x00000100 +#define ACOSV_SLV12 0x00000200 + +/* + * The following defines are for the flags in the AC97 command address + * register. + */ +#define ACCAD_CI_MASK 0x0000007F +#define ACCAD_CI_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 command data register. + */ +#define ACCDA_CD_MASK 0x0000FFFF +#define ACCDA_CD_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 input slot valid + * register. + */ +#define ACISV_ISV3 0x00000001 +#define ACISV_ISV4 0x00000002 +#define ACISV_ISV5 0x00000004 +#define ACISV_ISV6 0x00000008 +#define ACISV_ISV7 0x00000010 +#define ACISV_ISV8 0x00000020 +#define ACISV_ISV9 0x00000040 +#define ACISV_ISV10 0x00000080 +#define ACISV_ISV11 0x00000100 +#define ACISV_ISV12 0x00000200 + +/* + * The following defines are for the flags in the AC97 status address + * register. + */ +#define ACSAD_SI_MASK 0x0000007F +#define ACSAD_SI_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 status data register. + */ +#define ACSDA_SD_MASK 0x0000FFFF +#define ACSDA_SD_SHIFT 0 + +/* + * The following defines are for the flags in the joystick poll/trigger + * register. + */ +#define JSPT_CAX 0x00000001 +#define JSPT_CAY 0x00000002 +#define JSPT_CBX 0x00000004 +#define JSPT_CBY 0x00000008 +#define JSPT_BA1 0x00000010 +#define JSPT_BA2 0x00000020 +#define JSPT_BB1 0x00000040 +#define JSPT_BB2 0x00000080 + +/* + * The following defines are for the flags in the joystick control register. + */ +#define JSCTL_SP_MASK 0x00000003 +#define JSCTL_SP_SLOW 0x00000000 +#define JSCTL_SP_MEDIUM_SLOW 0x00000001 +#define JSCTL_SP_MEDIUM_FAST 0x00000002 +#define JSCTL_SP_FAST 0x00000003 +#define JSCTL_ARE 0x00000004 + +/* + * The following defines are for the flags in the joystick coordinate pair 1 + * readback register. + */ +#define JSC1_Y1V_MASK 0x0000FFFF +#define JSC1_X1V_MASK 0xFFFF0000 +#define JSC1_Y1V_SHIFT 0 +#define JSC1_X1V_SHIFT 16 + +/* + * The following defines are for the flags in the joystick coordinate pair 2 + * readback register. + */ +#define JSC2_Y2V_MASK 0x0000FFFF +#define JSC2_X2V_MASK 0xFFFF0000 +#define JSC2_Y2V_SHIFT 0 +#define JSC2_X2V_SHIFT 16 + +/* + * The following defines are for the flags in the MIDI control register. + */ +#define MIDCR_TXE 0x00000001 /* Enable transmitting. */ +#define MIDCR_RXE 0x00000002 /* Enable receiving. */ +#define MIDCR_RIE 0x00000004 /* Interrupt upon tx ready. */ +#define MIDCR_TIE 0x00000008 /* Interrupt upon rx ready. */ +#define MIDCR_MLB 0x00000010 /* Enable midi loopback. */ +#define MIDCR_MRST 0x00000020 /* Reset interface. */ + +/* + * The following defines are for the flags in the MIDI status register. + */ +#define MIDSR_TBF 0x00000001 /* Tx FIFO is full. */ +#define MIDSR_RBE 0x00000002 /* Rx FIFO is empty. */ + +/* + * The following defines are for the flags in the MIDI write port register. + */ +#define MIDWP_MWD_MASK 0x000000FF +#define MIDWP_MWD_SHIFT 0 + +/* + * The following defines are for the flags in the MIDI read port register. + */ +#define MIDRP_MRD_MASK 0x000000FF +#define MIDRP_MRD_SHIFT 0 + +/* + * The following defines are for the flags in the joystick GPIO register. + */ +#define JSIO_DAX 0x00000001 +#define JSIO_DAY 0x00000002 +#define JSIO_DBX 0x00000004 +#define JSIO_DBY 0x00000008 +#define JSIO_AXOE 0x00000010 +#define JSIO_AYOE 0x00000020 +#define JSIO_BXOE 0x00000040 +#define JSIO_BYOE 0x00000080 + +/* + * The following defines are for the flags in the master async/sync serial + * port enable register. + */ +#ifndef NO_CS4612 +#define ASER_MASTER_ME 0x00000001 +#endif + +/* + * The following defines are for the flags in the configuration interface + * register. + */ +#define CFGI_CLK 0x00000001 +#define CFGI_DOUT 0x00000002 +#define CFGI_DIN_EEN 0x00000004 +#define CFGI_EELD 0x00000008 + +/* + * The following defines are for the flags in the subsystem ID and vendor ID + * register. + */ +#define SSVID_VID_MASK 0x0000FFFF +#define SSVID_SID_MASK 0xFFFF0000 +#define SSVID_VID_SHIFT 0 +#define SSVID_SID_SHIFT 16 + +/* + * The following defines are for the flags in the GPIO pin interface register. + */ +#define GPIOR_VOLDN 0x00000001 +#define GPIOR_VOLUP 0x00000002 +#define GPIOR_SI2D 0x00000004 +#define GPIOR_SI2OE 0x00000008 + +/* + * The following defines are for the flags in the extended GPIO pin direction + * register. + */ +#ifndef NO_CS4612 +#define EGPIODR_GPOE0 0x00000001 +#define EGPIODR_GPOE1 0x00000002 +#define EGPIODR_GPOE2 0x00000004 +#define EGPIODR_GPOE3 0x00000008 +#define EGPIODR_GPOE4 0x00000010 +#define EGPIODR_GPOE5 0x00000020 +#define EGPIODR_GPOE6 0x00000040 +#define EGPIODR_GPOE7 0x00000080 +#define EGPIODR_GPOE8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin polarity/ + * type register. + */ +#ifndef NO_CS4612 +#define EGPIOPTR_GPPT0 0x00000001 +#define EGPIOPTR_GPPT1 0x00000002 +#define EGPIOPTR_GPPT2 0x00000004 +#define EGPIOPTR_GPPT3 0x00000008 +#define EGPIOPTR_GPPT4 0x00000010 +#define EGPIOPTR_GPPT5 0x00000020 +#define EGPIOPTR_GPPT6 0x00000040 +#define EGPIOPTR_GPPT7 0x00000080 +#define EGPIOPTR_GPPT8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin sticky + * register. + */ +#ifndef NO_CS4612 +#define EGPIOTR_GPS0 0x00000001 +#define EGPIOTR_GPS1 0x00000002 +#define EGPIOTR_GPS2 0x00000004 +#define EGPIOTR_GPS3 0x00000008 +#define EGPIOTR_GPS4 0x00000010 +#define EGPIOTR_GPS5 0x00000020 +#define EGPIOTR_GPS6 0x00000040 +#define EGPIOTR_GPS7 0x00000080 +#define EGPIOTR_GPS8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO ping wakeup + * register. + */ +#ifndef NO_CS4612 +#define EGPIOWR_GPW0 0x00000001 +#define EGPIOWR_GPW1 0x00000002 +#define EGPIOWR_GPW2 0x00000004 +#define EGPIOWR_GPW3 0x00000008 +#define EGPIOWR_GPW4 0x00000010 +#define EGPIOWR_GPW5 0x00000020 +#define EGPIOWR_GPW6 0x00000040 +#define EGPIOWR_GPW7 0x00000080 +#define EGPIOWR_GPW8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin status + * register. + */ +#ifndef NO_CS4612 +#define EGPIOSR_GPS0 0x00000001 +#define EGPIOSR_GPS1 0x00000002 +#define EGPIOSR_GPS2 0x00000004 +#define EGPIOSR_GPS3 0x00000008 +#define EGPIOSR_GPS4 0x00000010 +#define EGPIOSR_GPS5 0x00000020 +#define EGPIOSR_GPS6 0x00000040 +#define EGPIOSR_GPS7 0x00000080 +#define EGPIOSR_GPS8 0x00000100 +#endif + +/* + * The following defines are for the flags in the serial port 6 configuration + * register. + */ +#ifndef NO_CS4612 +#define SERC6_ASDO2EN 0x00000001 +#endif + +/* + * The following defines are for the flags in the serial port 7 configuration + * register. + */ +#ifndef NO_CS4612 +#define SERC7_ASDI2EN 0x00000001 +#define SERC7_POSILB 0x00000002 +#define SERC7_SIPOLB 0x00000004 +#define SERC7_SOSILB 0x00000008 +#define SERC7_SISOLB 0x00000010 +#endif + +/* + * The following defines are for the flags in the serial port AC link + * configuration register. + */ +#ifndef NO_CS4612 +#define SERACC_CHIP_TYPE_MASK 0x00000001 +#define SERACC_CHIP_TYPE_1_03 0x00000000 +#define SERACC_CHIP_TYPE_2_0 0x00000001 +#define SERACC_TWO_CODECS 0x00000002 +#define SERACC_MDM 0x00000004 +#define SERACC_HSP 0x00000008 +#define SERACC_ODT 0x00000010 /* only CS4630 */ +#endif + +/* + * The following defines are for the flags in the AC97 control register 2. + */ +#ifndef NO_CS4612 +#define ACCTL2_RSTN 0x00000001 +#define ACCTL2_ESYN 0x00000002 +#define ACCTL2_VFRM 0x00000004 +#define ACCTL2_DCV 0x00000008 +#define ACCTL2_CRW 0x00000010 +#define ACCTL2_ASYN 0x00000020 +#endif + +/* + * The following defines are for the flags in the AC97 status register 2. + */ +#ifndef NO_CS4612 +#define ACSTS2_CRDY 0x00000001 +#define ACSTS2_VSTS 0x00000002 +#endif + +/* + * The following defines are for the flags in the AC97 output slot valid + * register 2. + */ +#ifndef NO_CS4612 +#define ACOSV2_SLV3 0x00000001 +#define ACOSV2_SLV4 0x00000002 +#define ACOSV2_SLV5 0x00000004 +#define ACOSV2_SLV6 0x00000008 +#define ACOSV2_SLV7 0x00000010 +#define ACOSV2_SLV8 0x00000020 +#define ACOSV2_SLV9 0x00000040 +#define ACOSV2_SLV10 0x00000080 +#define ACOSV2_SLV11 0x00000100 +#define ACOSV2_SLV12 0x00000200 +#endif + +/* + * The following defines are for the flags in the AC97 command address + * register 2. + */ +#ifndef NO_CS4612 +#define ACCAD2_CI_MASK 0x0000007F +#define ACCAD2_CI_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 command data register + * 2. + */ +#ifndef NO_CS4612 +#define ACCDA2_CD_MASK 0x0000FFFF +#define ACCDA2_CD_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 input slot valid + * register 2. + */ +#ifndef NO_CS4612 +#define ACISV2_ISV3 0x00000001 +#define ACISV2_ISV4 0x00000002 +#define ACISV2_ISV5 0x00000004 +#define ACISV2_ISV6 0x00000008 +#define ACISV2_ISV7 0x00000010 +#define ACISV2_ISV8 0x00000020 +#define ACISV2_ISV9 0x00000040 +#define ACISV2_ISV10 0x00000080 +#define ACISV2_ISV11 0x00000100 +#define ACISV2_ISV12 0x00000200 +#endif + +/* + * The following defines are for the flags in the AC97 status address + * register 2. + */ +#ifndef NO_CS4612 +#define ACSAD2_SI_MASK 0x0000007F +#define ACSAD2_SI_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 status data register 2. + */ +#ifndef NO_CS4612 +#define ACSDA2_SD_MASK 0x0000FFFF +#define ACSDA2_SD_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the I/O trap address and control + * registers (all 12). + */ +#ifndef NO_CS4612 +#define IOTAC_SA_MASK 0x0000FFFF +#define IOTAC_MSK_MASK 0x000F0000 +#define IOTAC_IODC_MASK 0x06000000 +#define IOTAC_IODC_16_BIT 0x00000000 +#define IOTAC_IODC_10_BIT 0x02000000 +#define IOTAC_IODC_12_BIT 0x04000000 +#define IOTAC_WSPI 0x08000000 +#define IOTAC_RSPI 0x10000000 +#define IOTAC_WSE 0x20000000 +#define IOTAC_WE 0x40000000 +#define IOTAC_RE 0x80000000 +#define IOTAC_SA_SHIFT 0 +#define IOTAC_MSK_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap fast read registers + * (all 8). + */ +#ifndef NO_CS4612 +#define IOTFR_D_MASK 0x0000FFFF +#define IOTFR_A_MASK 0x000F0000 +#define IOTFR_R_MASK 0x0F000000 +#define IOTFR_ALL 0x40000000 +#define IOTFR_VL 0x80000000 +#define IOTFR_D_SHIFT 0 +#define IOTFR_A_SHIFT 16 +#define IOTFR_R_SHIFT 24 +#endif + +/* + * The following defines are for the flags in the I/O trap FIFO register. + */ +#ifndef NO_CS4612 +#define IOTFIFO_BA_MASK 0x00003FFF +#define IOTFIFO_S_MASK 0x00FF0000 +#define IOTFIFO_OF 0x40000000 +#define IOTFIFO_SPIOF 0x80000000 +#define IOTFIFO_BA_SHIFT 0 +#define IOTFIFO_S_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap retry read data + * register. + */ +#ifndef NO_CS4612 +#define IOTRRD_D_MASK 0x0000FFFF +#define IOTRRD_RDV 0x80000000 +#define IOTRRD_D_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the I/O trap FIFO pointer + * register. + */ +#ifndef NO_CS4612 +#define IOTFP_CA_MASK 0x00003FFF +#define IOTFP_PA_MASK 0x3FFF0000 +#define IOTFP_CA_SHIFT 0 +#define IOTFP_PA_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap control register. + */ +#ifndef NO_CS4612 +#define IOTCR_ITD 0x00000001 +#define IOTCR_HRV 0x00000002 +#define IOTCR_SRV 0x00000004 +#define IOTCR_DTI 0x00000008 +#define IOTCR_DFI 0x00000010 +#define IOTCR_DDP 0x00000020 +#define IOTCR_JTE 0x00000040 +#define IOTCR_PPE 0x00000080 +#endif + +/* + * The following defines are for the flags in the direct PCI data register. + */ +#ifndef NO_CS4612 +#define DPCID_D_MASK 0xFFFFFFFF +#define DPCID_D_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the direct PCI address register. + */ +#ifndef NO_CS4612 +#define DPCIA_A_MASK 0xFFFFFFFF +#define DPCIA_A_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the direct PCI command register. + */ +#ifndef NO_CS4612 +#define DPCIC_C_MASK 0x0000000F +#define DPCIC_C_IOREAD 0x00000002 +#define DPCIC_C_IOWRITE 0x00000003 +#define DPCIC_BE_MASK 0x000000F0 +#endif + +/* + * The following defines are for the flags in the PC/PCI request register. + */ +#ifndef NO_CS4612 +#define PCPCIR_RDC_MASK 0x00000007 +#define PCPCIR_C_MASK 0x00007000 +#define PCPCIR_REQ 0x00008000 +#define PCPCIR_RDC_SHIFT 0 +#define PCPCIR_C_SHIFT 12 +#endif + +/* + * The following defines are for the flags in the PC/PCI grant register. + */ +#ifndef NO_CS4612 +#define PCPCIG_GDC_MASK 0x00000007 +#define PCPCIG_VL 0x00008000 +#define PCPCIG_GDC_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the PC/PCI master enable + * register. + */ +#ifndef NO_CS4612 +#define PCPCIEN_EN 0x00000001 +#endif + +/* + * The following defines are for the flags in the extended PCI power + * management control register. + */ +#ifndef NO_CS4612 +#define EPCIPMC_GWU 0x00000001 +#define EPCIPMC_FSPC 0x00000002 +#endif + +/* + * The following defines are for the flags in the SP control register. + */ +#define SPCR_RUN 0x00000001 +#define SPCR_STPFR 0x00000002 +#define SPCR_RUNFR 0x00000004 +#define SPCR_TICK 0x00000008 +#define SPCR_DRQEN 0x00000020 +#define SPCR_RSTSP 0x00000040 +#define SPCR_OREN 0x00000080 +#ifndef NO_CS4612 +#define SPCR_PCIINT 0x00000100 +#define SPCR_OINTD 0x00000200 +#define SPCR_CRE 0x00008000 +#endif + +/* + * The following defines are for the flags in the debug index register. + */ +#define DREG_REGID_MASK 0x0000007F +#define DREG_DEBUG 0x00000080 +#define DREG_RGBK_MASK 0x00000700 +#define DREG_TRAP 0x00000800 +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_TRAPX 0x00001000 +#endif +#endif +#define DREG_REGID_SHIFT 0 +#define DREG_RGBK_SHIFT 8 +#define DREG_RGBK_REGID_MASK 0x0000077F +#define DREG_REGID_R0 0x00000010 +#define DREG_REGID_R1 0x00000011 +#define DREG_REGID_R2 0x00000012 +#define DREG_REGID_R3 0x00000013 +#define DREG_REGID_R4 0x00000014 +#define DREG_REGID_R5 0x00000015 +#define DREG_REGID_R6 0x00000016 +#define DREG_REGID_R7 0x00000017 +#define DREG_REGID_R8 0x00000018 +#define DREG_REGID_R9 0x00000019 +#define DREG_REGID_RA 0x0000001A +#define DREG_REGID_RB 0x0000001B +#define DREG_REGID_RC 0x0000001C +#define DREG_REGID_RD 0x0000001D +#define DREG_REGID_RE 0x0000001E +#define DREG_REGID_RF 0x0000001F +#define DREG_REGID_RA_BUS_LOW 0x00000020 +#define DREG_REGID_RA_BUS_HIGH 0x00000038 +#define DREG_REGID_YBUS_LOW 0x00000050 +#define DREG_REGID_YBUS_HIGH 0x00000058 +#define DREG_REGID_TRAP_0 0x00000100 +#define DREG_REGID_TRAP_1 0x00000101 +#define DREG_REGID_TRAP_2 0x00000102 +#define DREG_REGID_TRAP_3 0x00000103 +#define DREG_REGID_TRAP_4 0x00000104 +#define DREG_REGID_TRAP_5 0x00000105 +#define DREG_REGID_TRAP_6 0x00000106 +#define DREG_REGID_TRAP_7 0x00000107 +#define DREG_REGID_INDIRECT_ADDRESS 0x0000010E +#define DREG_REGID_TOP_OF_STACK 0x0000010F +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_8 0x00000110 +#define DREG_REGID_TRAP_9 0x00000111 +#define DREG_REGID_TRAP_10 0x00000112 +#define DREG_REGID_TRAP_11 0x00000113 +#define DREG_REGID_TRAP_12 0x00000114 +#define DREG_REGID_TRAP_13 0x00000115 +#define DREG_REGID_TRAP_14 0x00000116 +#define DREG_REGID_TRAP_15 0x00000117 +#define DREG_REGID_TRAP_16 0x00000118 +#define DREG_REGID_TRAP_17 0x00000119 +#define DREG_REGID_TRAP_18 0x0000011A +#define DREG_REGID_TRAP_19 0x0000011B +#define DREG_REGID_TRAP_20 0x0000011C +#define DREG_REGID_TRAP_21 0x0000011D +#define DREG_REGID_TRAP_22 0x0000011E +#define DREG_REGID_TRAP_23 0x0000011F +#endif +#endif +#define DREG_REGID_RSA0_LOW 0x00000200 +#define DREG_REGID_RSA0_HIGH 0x00000201 +#define DREG_REGID_RSA1_LOW 0x00000202 +#define DREG_REGID_RSA1_HIGH 0x00000203 +#define DREG_REGID_RSA2 0x00000204 +#define DREG_REGID_RSA3 0x00000205 +#define DREG_REGID_RSI0_LOW 0x00000206 +#define DREG_REGID_RSI0_HIGH 0x00000207 +#define DREG_REGID_RSI1 0x00000208 +#define DREG_REGID_RSI2 0x00000209 +#define DREG_REGID_SAGUSTATUS 0x0000020A +#define DREG_REGID_RSCONFIG01_LOW 0x0000020B +#define DREG_REGID_RSCONFIG01_HIGH 0x0000020C +#define DREG_REGID_RSCONFIG23_LOW 0x0000020D +#define DREG_REGID_RSCONFIG23_HIGH 0x0000020E +#define DREG_REGID_RSDMA01E 0x0000020F +#define DREG_REGID_RSDMA23E 0x00000210 +#define DREG_REGID_RSD0_LOW 0x00000211 +#define DREG_REGID_RSD0_HIGH 0x00000212 +#define DREG_REGID_RSD1_LOW 0x00000213 +#define DREG_REGID_RSD1_HIGH 0x00000214 +#define DREG_REGID_RSD2_LOW 0x00000215 +#define DREG_REGID_RSD2_HIGH 0x00000216 +#define DREG_REGID_RSD3_LOW 0x00000217 +#define DREG_REGID_RSD3_HIGH 0x00000218 +#define DREG_REGID_SRAR_HIGH 0x0000021A +#define DREG_REGID_SRAR_LOW 0x0000021B +#define DREG_REGID_DMA_STATE 0x0000021C +#define DREG_REGID_CURRENT_DMA_STREAM 0x0000021D +#define DREG_REGID_NEXT_DMA_STREAM 0x0000021E +#define DREG_REGID_CPU_STATUS 0x00000300 +#define DREG_REGID_MAC_MODE 0x00000301 +#define DREG_REGID_STACK_AND_REPEAT 0x00000302 +#define DREG_REGID_INDEX0 0x00000304 +#define DREG_REGID_INDEX1 0x00000305 +#define DREG_REGID_DMA_STATE_0_3 0x00000400 +#define DREG_REGID_DMA_STATE_4_7 0x00000404 +#define DREG_REGID_DMA_STATE_8_11 0x00000408 +#define DREG_REGID_DMA_STATE_12_15 0x0000040C +#define DREG_REGID_DMA_STATE_16_19 0x00000410 +#define DREG_REGID_DMA_STATE_20_23 0x00000414 +#define DREG_REGID_DMA_STATE_24_27 0x00000418 +#define DREG_REGID_DMA_STATE_28_31 0x0000041C +#define DREG_REGID_DMA_STATE_32_35 0x00000420 +#define DREG_REGID_DMA_STATE_36_39 0x00000424 +#define DREG_REGID_DMA_STATE_40_43 0x00000428 +#define DREG_REGID_DMA_STATE_44_47 0x0000042C +#define DREG_REGID_DMA_STATE_48_51 0x00000430 +#define DREG_REGID_DMA_STATE_52_55 0x00000434 +#define DREG_REGID_DMA_STATE_56_59 0x00000438 +#define DREG_REGID_DMA_STATE_60_63 0x0000043C +#define DREG_REGID_DMA_STATE_64_67 0x00000440 +#define DREG_REGID_DMA_STATE_68_71 0x00000444 +#define DREG_REGID_DMA_STATE_72_75 0x00000448 +#define DREG_REGID_DMA_STATE_76_79 0x0000044C +#define DREG_REGID_DMA_STATE_80_83 0x00000450 +#define DREG_REGID_DMA_STATE_84_87 0x00000454 +#define DREG_REGID_DMA_STATE_88_91 0x00000458 +#define DREG_REGID_DMA_STATE_92_95 0x0000045C +#define DREG_REGID_TRAP_SELECT 0x00000500 +#define DREG_REGID_TRAP_WRITE_0 0x00000500 +#define DREG_REGID_TRAP_WRITE_1 0x00000501 +#define DREG_REGID_TRAP_WRITE_2 0x00000502 +#define DREG_REGID_TRAP_WRITE_3 0x00000503 +#define DREG_REGID_TRAP_WRITE_4 0x00000504 +#define DREG_REGID_TRAP_WRITE_5 0x00000505 +#define DREG_REGID_TRAP_WRITE_6 0x00000506 +#define DREG_REGID_TRAP_WRITE_7 0x00000507 +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_WRITE_8 0x00000510 +#define DREG_REGID_TRAP_WRITE_9 0x00000511 +#define DREG_REGID_TRAP_WRITE_10 0x00000512 +#define DREG_REGID_TRAP_WRITE_11 0x00000513 +#define DREG_REGID_TRAP_WRITE_12 0x00000514 +#define DREG_REGID_TRAP_WRITE_13 0x00000515 +#define DREG_REGID_TRAP_WRITE_14 0x00000516 +#define DREG_REGID_TRAP_WRITE_15 0x00000517 +#define DREG_REGID_TRAP_WRITE_16 0x00000518 +#define DREG_REGID_TRAP_WRITE_17 0x00000519 +#define DREG_REGID_TRAP_WRITE_18 0x0000051A +#define DREG_REGID_TRAP_WRITE_19 0x0000051B +#define DREG_REGID_TRAP_WRITE_20 0x0000051C +#define DREG_REGID_TRAP_WRITE_21 0x0000051D +#define DREG_REGID_TRAP_WRITE_22 0x0000051E +#define DREG_REGID_TRAP_WRITE_23 0x0000051F +#endif +#endif +#define DREG_REGID_MAC0_ACC0_LOW 0x00000600 +#define DREG_REGID_MAC0_ACC1_LOW 0x00000601 +#define DREG_REGID_MAC0_ACC2_LOW 0x00000602 +#define DREG_REGID_MAC0_ACC3_LOW 0x00000603 +#define DREG_REGID_MAC1_ACC0_LOW 0x00000604 +#define DREG_REGID_MAC1_ACC1_LOW 0x00000605 +#define DREG_REGID_MAC1_ACC2_LOW 0x00000606 +#define DREG_REGID_MAC1_ACC3_LOW 0x00000607 +#define DREG_REGID_MAC0_ACC0_MID 0x00000608 +#define DREG_REGID_MAC0_ACC1_MID 0x00000609 +#define DREG_REGID_MAC0_ACC2_MID 0x0000060A +#define DREG_REGID_MAC0_ACC3_MID 0x0000060B +#define DREG_REGID_MAC1_ACC0_MID 0x0000060C +#define DREG_REGID_MAC1_ACC1_MID 0x0000060D +#define DREG_REGID_MAC1_ACC2_MID 0x0000060E +#define DREG_REGID_MAC1_ACC3_MID 0x0000060F +#define DREG_REGID_MAC0_ACC0_HIGH 0x00000610 +#define DREG_REGID_MAC0_ACC1_HIGH 0x00000611 +#define DREG_REGID_MAC0_ACC2_HIGH 0x00000612 +#define DREG_REGID_MAC0_ACC3_HIGH 0x00000613 +#define DREG_REGID_MAC1_ACC0_HIGH 0x00000614 +#define DREG_REGID_MAC1_ACC1_HIGH 0x00000615 +#define DREG_REGID_MAC1_ACC2_HIGH 0x00000616 +#define DREG_REGID_MAC1_ACC3_HIGH 0x00000617 +#define DREG_REGID_RSHOUT_LOW 0x00000620 +#define DREG_REGID_RSHOUT_MID 0x00000628 +#define DREG_REGID_RSHOUT_HIGH 0x00000630 + +/* + * The following defines are for the flags in the DMA stream requestor write + */ +#define DSRWP_DSR_MASK 0x0000000F +#define DSRWP_DSR_BG_RQ 0x00000001 +#define DSRWP_DSR_PRIORITY_MASK 0x00000006 +#define DSRWP_DSR_PRIORITY_0 0x00000000 +#define DSRWP_DSR_PRIORITY_1 0x00000002 +#define DSRWP_DSR_PRIORITY_2 0x00000004 +#define DSRWP_DSR_PRIORITY_3 0x00000006 +#define DSRWP_DSR_RQ_PENDING 0x00000008 + +/* + * The following defines are for the flags in the trap write port register. + */ +#define TWPR_TW_MASK 0x0000FFFF +#define TWPR_TW_SHIFT 0 + +/* + * The following defines are for the flags in the stack pointer write + * register. + */ +#define SPWR_STKP_MASK 0x0000000F +#define SPWR_STKP_SHIFT 0 + +/* + * The following defines are for the flags in the SP interrupt register. + */ +#define SPIR_FRI 0x00000001 +#define SPIR_DOI 0x00000002 +#define SPIR_GPI2 0x00000004 +#define SPIR_GPI3 0x00000008 +#define SPIR_IP0 0x00000010 +#define SPIR_IP1 0x00000020 +#define SPIR_IP2 0x00000040 +#define SPIR_IP3 0x00000080 + +/* + * The following defines are for the flags in the functional group 1 register. + */ +#define FGR1_F1S_MASK 0x0000FFFF +#define FGR1_F1S_SHIFT 0 + +/* + * The following defines are for the flags in the SP clock status register. + */ +#define SPCS_FRI 0x00000001 +#define SPCS_DOI 0x00000002 +#define SPCS_GPI2 0x00000004 +#define SPCS_GPI3 0x00000008 +#define SPCS_IP0 0x00000010 +#define SPCS_IP1 0x00000020 +#define SPCS_IP2 0x00000040 +#define SPCS_IP3 0x00000080 +#define SPCS_SPRUN 0x00000100 +#define SPCS_SLEEP 0x00000200 +#define SPCS_FG 0x00000400 +#define SPCS_ORUN 0x00000800 +#define SPCS_IRQ 0x00001000 +#define SPCS_FGN_MASK 0x0000E000 +#define SPCS_FGN_SHIFT 13 + +/* + * The following defines are for the flags in the SP DMA requestor status + * register. + */ +#define SDSR_DCS_MASK 0x000000FF +#define SDSR_DCS_SHIFT 0 +#define SDSR_DCS_NONE 0x00000007 + +/* + * The following defines are for the flags in the frame timer register. + */ +#define FRMT_FTV_MASK 0x0000FFFF +#define FRMT_FTV_SHIFT 0 + +/* + * The following defines are for the flags in the frame timer current count + * register. + */ +#define FRCC_FCC_MASK 0x0000FFFF +#define FRCC_FCC_SHIFT 0 + +/* + * The following defines are for the flags in the frame timer save count + * register. + */ +#define FRSC_FCS_MASK 0x0000FFFF +#define FRSC_FCS_SHIFT 0 + +/* + * The following define the various flags stored in the scatter/gather + * descriptors. + */ +#define DMA_SG_NEXT_ENTRY_MASK 0x00000FF8 +#define DMA_SG_SAMPLE_END_MASK 0x0FFF0000 +#define DMA_SG_SAMPLE_END_FLAG 0x10000000 +#define DMA_SG_LOOP_END_FLAG 0x20000000 +#define DMA_SG_SIGNAL_END_FLAG 0x40000000 +#define DMA_SG_SIGNAL_PAGE_FLAG 0x80000000 +#define DMA_SG_NEXT_ENTRY_SHIFT 3 +#define DMA_SG_SAMPLE_END_SHIFT 16 + +/* + * The following define the offsets of the fields within the on-chip generic + * DMA requestor. + */ +#define DMA_RQ_CONTROL1 0x00000000 +#define DMA_RQ_CONTROL2 0x00000004 +#define DMA_RQ_SOURCE_ADDR 0x00000008 +#define DMA_RQ_DESTINATION_ADDR 0x0000000C +#define DMA_RQ_NEXT_PAGE_ADDR 0x00000010 +#define DMA_RQ_NEXT_PAGE_SGDESC 0x00000014 +#define DMA_RQ_LOOP_START_ADDR 0x00000018 +#define DMA_RQ_POST_LOOP_ADDR 0x0000001C +#define DMA_RQ_PAGE_MAP_ADDR 0x00000020 + +/* + * The following defines are for the flags in the first control word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_C1_COUNT_MASK 0x000003FF +#define DMA_RQ_C1_DESTINATION_SCATTER 0x00001000 +#define DMA_RQ_C1_SOURCE_GATHER 0x00002000 +#define DMA_RQ_C1_DONE_FLAG 0x00004000 +#define DMA_RQ_C1_OPTIMIZE_STATE 0x00008000 +#define DMA_RQ_C1_SAMPLE_END_STATE_MASK 0x00030000 +#define DMA_RQ_C1_FULL_PAGE 0x00000000 +#define DMA_RQ_C1_BEFORE_SAMPLE_END 0x00010000 +#define DMA_RQ_C1_PAGE_MAP_ERROR 0x00020000 +#define DMA_RQ_C1_AT_SAMPLE_END 0x00030000 +#define DMA_RQ_C1_LOOP_END_STATE_MASK 0x000C0000 +#define DMA_RQ_C1_NOT_LOOP_END 0x00000000 +#define DMA_RQ_C1_BEFORE_LOOP_END 0x00040000 +#define DMA_RQ_C1_2PAGE_LOOP_BEGIN 0x00080000 +#define DMA_RQ_C1_LOOP_BEGIN 0x000C0000 +#define DMA_RQ_C1_PAGE_MAP_MASK 0x00300000 +#define DMA_RQ_C1_PM_NONE_PENDING 0x00000000 +#define DMA_RQ_C1_PM_NEXT_PENDING 0x00100000 +#define DMA_RQ_C1_PM_RESERVED 0x00200000 +#define DMA_RQ_C1_PM_LOOP_NEXT_PENDING 0x00300000 +#define DMA_RQ_C1_WRITEBACK_DEST_FLAG 0x00400000 +#define DMA_RQ_C1_WRITEBACK_SRC_FLAG 0x00800000 +#define DMA_RQ_C1_DEST_SIZE_MASK 0x07000000 +#define DMA_RQ_C1_DEST_LINEAR 0x00000000 +#define DMA_RQ_C1_DEST_MOD16 0x01000000 +#define DMA_RQ_C1_DEST_MOD32 0x02000000 +#define DMA_RQ_C1_DEST_MOD64 0x03000000 +#define DMA_RQ_C1_DEST_MOD128 0x04000000 +#define DMA_RQ_C1_DEST_MOD256 0x05000000 +#define DMA_RQ_C1_DEST_MOD512 0x06000000 +#define DMA_RQ_C1_DEST_MOD1024 0x07000000 +#define DMA_RQ_C1_DEST_ON_HOST 0x08000000 +#define DMA_RQ_C1_SOURCE_SIZE_MASK 0x70000000 +#define DMA_RQ_C1_SOURCE_LINEAR 0x00000000 +#define DMA_RQ_C1_SOURCE_MOD16 0x10000000 +#define DMA_RQ_C1_SOURCE_MOD32 0x20000000 +#define DMA_RQ_C1_SOURCE_MOD64 0x30000000 +#define DMA_RQ_C1_SOURCE_MOD128 0x40000000 +#define DMA_RQ_C1_SOURCE_MOD256 0x50000000 +#define DMA_RQ_C1_SOURCE_MOD512 0x60000000 +#define DMA_RQ_C1_SOURCE_MOD1024 0x70000000 +#define DMA_RQ_C1_SOURCE_ON_HOST 0x80000000 +#define DMA_RQ_C1_COUNT_SHIFT 0 + +/* + * The following defines are for the flags in the second control word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_C2_VIRTUAL_CHANNEL_MASK 0x0000003F +#define DMA_RQ_C2_VIRTUAL_SIGNAL_MASK 0x00000300 +#define DMA_RQ_C2_NO_VIRTUAL_SIGNAL 0x00000000 +#define DMA_RQ_C2_SIGNAL_EVERY_DMA 0x00000100 +#define DMA_RQ_C2_SIGNAL_SOURCE_PINGPONG 0x00000200 +#define DMA_RQ_C2_SIGNAL_DEST_PINGPONG 0x00000300 +#define DMA_RQ_C2_AUDIO_CONVERT_MASK 0x0000F000 +#define DMA_RQ_C2_AC_NONE 0x00000000 +#define DMA_RQ_C2_AC_8_TO_16_BIT 0x00001000 +#define DMA_RQ_C2_AC_MONO_TO_STEREO 0x00002000 +#define DMA_RQ_C2_AC_ENDIAN_CONVERT 0x00004000 +#define DMA_RQ_C2_AC_SIGNED_CONVERT 0x00008000 +#define DMA_RQ_C2_LOOP_END_MASK 0x0FFF0000 +#define DMA_RQ_C2_LOOP_MASK 0x30000000 +#define DMA_RQ_C2_NO_LOOP 0x00000000 +#define DMA_RQ_C2_ONE_PAGE_LOOP 0x10000000 +#define DMA_RQ_C2_TWO_PAGE_LOOP 0x20000000 +#define DMA_RQ_C2_MULTI_PAGE_LOOP 0x30000000 +#define DMA_RQ_C2_SIGNAL_LOOP_BACK 0x40000000 +#define DMA_RQ_C2_SIGNAL_POST_BEGIN_PAGE 0x80000000 +#define DMA_RQ_C2_VIRTUAL_CHANNEL_SHIFT 0 +#define DMA_RQ_C2_LOOP_END_SHIFT 16 + +/* + * The following defines are for the flags in the source and destination words + * of the on-chip generic DMA requestor. + */ +#define DMA_RQ_SD_ADDRESS_MASK 0x0000FFFF +#define DMA_RQ_SD_MEMORY_ID_MASK 0x000F0000 +#define DMA_RQ_SD_SP_PARAM_ADDR 0x00000000 +#define DMA_RQ_SD_SP_SAMPLE_ADDR 0x00010000 +#define DMA_RQ_SD_SP_PROGRAM_ADDR 0x00020000 +#define DMA_RQ_SD_SP_DEBUG_ADDR 0x00030000 +#define DMA_RQ_SD_OMNIMEM_ADDR 0x000E0000 +#define DMA_RQ_SD_END_FLAG 0x40000000 +#define DMA_RQ_SD_ERROR_FLAG 0x80000000 +#define DMA_RQ_SD_ADDRESS_SHIFT 0 + +/* + * The following defines are for the flags in the page map address word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_PMA_LOOP_THIRD_PAGE_ENTRY_MASK 0x00000FF8 +#define DMA_RQ_PMA_PAGE_TABLE_MASK 0xFFFFF000 +#define DMA_RQ_PMA_LOOP_THIRD_PAGE_ENTRY_SHIFT 3 +#define DMA_RQ_PMA_PAGE_TABLE_SHIFT 12 + +#define BA1_VARIDEC_BUF_1 0x000 + +#define BA1_PDTC 0x0c0 /* BA1_PLAY_DMA_TRANSACTION_COUNT_REG */ +#define BA1_PFIE 0x0c4 /* BA1_PLAY_FORMAT_&_INTERRUPT_ENABLE_REG */ +#define BA1_PBA 0x0c8 /* BA1_PLAY_BUFFER_ADDRESS */ +#define BA1_PVOL 0x0f8 /* BA1_PLAY_VOLUME_REG */ +#define BA1_PSRC 0x288 /* BA1_PLAY_SAMPLE_RATE_CORRECTION_REG */ +#define BA1_PCTL 0x2a4 /* BA1_PLAY_CONTROL_REG */ +#define BA1_PPI 0x2b4 /* BA1_PLAY_PHASE_INCREMENT_REG */ + +#define BA1_CCTL 0x064 /* BA1_CAPTURE_CONTROL_REG */ +#define BA1_CIE 0x104 /* BA1_CAPTURE_INTERRUPT_ENABLE_REG */ +#define BA1_CBA 0x10c /* BA1_CAPTURE_BUFFER_ADDRESS */ +#define BA1_CSRC 0x2c8 /* BA1_CAPTURE_SAMPLE_RATE_CORRECTION_REG */ +#define BA1_CCI 0x2d8 /* BA1_CAPTURE_COEFFICIENT_INCREMENT_REG */ +#define BA1_CD 0x2e0 /* BA1_CAPTURE_DELAY_REG */ +#define BA1_CPI 0x2f4 /* BA1_CAPTURE_PHASE_INCREMENT_REG */ +#define BA1_CVOL 0x2f8 /* BA1_CAPTURE_VOLUME_REG */ + +#define BA1_CFG1 0x134 /* BA1_CAPTURE_FRAME_GROUP_1_REG */ +#define BA1_CFG2 0x138 /* BA1_CAPTURE_FRAME_GROUP_2_REG */ +#define BA1_CCST 0x13c /* BA1_CAPTURE_CONSTANT_REG */ +#define BA1_CSPB 0x340 /* BA1_CAPTURE_SPB_ADDRESS */ + +/* + * + */ + +#define CS46XX_MODE_OUTPUT (1<<0) /* MIDI UART - output */ +#define CS46XX_MODE_INPUT (1<<1) /* MIDI UART - input */ + +/* + * + */ + +#define SAVE_REG_MAX 0x10 +#define POWER_DOWN_ALL 0x7f0f + +/* maxinum number of AC97 codecs connected, AC97 2.0 defined 4 */ +#define MAX_NR_AC97 4 +#define CS46XX_PRIMARY_CODEC_INDEX 0 +#define CS46XX_SECONDARY_CODEC_INDEX 1 +#define CS46XX_SECONDARY_CODEC_OFFSET 0x80 +#define CS46XX_DSP_CAPTURE_CHANNEL 1 + +/* capture */ +#define CS46XX_DSP_CAPTURE_CHANNEL 1 + +/* mixer */ +#define CS46XX_MIXER_SPDIF_INPUT_ELEMENT 1 +#define CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT 2 + + +struct snd_cs46xx_pcm { + struct snd_dma_buffer hw_buf; + + unsigned int ctl; + unsigned int shift; /* Shift count to trasform frames in bytes */ + struct snd_pcm_indirect pcm_rec; + struct snd_pcm_substream *substream; + + struct dsp_pcm_channel_descriptor * pcm_channel; + + int pcm_channel_id; /* Fron Rear, Center Lfe ... */ +}; + +struct snd_cs46xx_region { + char name[24]; + unsigned long base; + void __iomem *remap_addr; + unsigned long size; +}; + +struct snd_cs46xx { + int irq; + unsigned long ba0_addr; + unsigned long ba1_addr; + union { + struct { + struct snd_cs46xx_region ba0; + struct snd_cs46xx_region data0; + struct snd_cs46xx_region data1; + struct snd_cs46xx_region pmem; + struct snd_cs46xx_region reg; + } name; + struct snd_cs46xx_region idx[5]; + } region; + + unsigned int mode; + + struct { + struct snd_dma_buffer hw_buf; + + unsigned int ctl; + unsigned int shift; /* Shift count to trasform frames in bytes */ + struct snd_pcm_indirect pcm_rec; + struct snd_pcm_substream *substream; + } capt; + + + int nr_ac97_codecs; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97[MAX_NR_AC97]; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; + + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_input; + struct snd_rawmidi_substream *midi_output; + + spinlock_t reg_lock; + unsigned int midcr; + unsigned int uartm; + + int amplifier; + void (*amplifier_ctrl)(struct snd_cs46xx *, int); + void (*active_ctrl)(struct snd_cs46xx *, int); + void (*mixer_init)(struct snd_cs46xx *); + + int acpi_port; + struct snd_kcontrol *eapd_switch; /* for amplifier hack */ + int accept_valid; /* accept mmap valid (for OSS) */ + int in_suspend; + + struct gameport *gameport; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + struct mutex spos_mutex; + + struct dsp_spos_instance * dsp_spos_instance; + + struct snd_pcm *pcm_rear; + struct snd_pcm *pcm_center_lfe; + struct snd_pcm *pcm_iec958; + +#define CS46XX_DSP_MODULES 5 + struct dsp_module_desc *modules[CS46XX_DSP_MODULES]; +#else /* for compatibility */ + struct snd_cs46xx_pcm *playback_pcm; + unsigned int play_ctl; + + struct ba1_struct *ba1; +#endif + +#ifdef CONFIG_PM_SLEEP + u32 *saved_regs; +#endif +}; + +int snd_cs46xx_create(struct snd_card *card, + struct pci_dev *pci, + int external_amp, int thinkpad); +extern const struct dev_pm_ops snd_cs46xx_pm; + +int snd_cs46xx_pcm(struct snd_cs46xx *chip, int device); +int snd_cs46xx_pcm_rear(struct snd_cs46xx *chip, int device); +int snd_cs46xx_pcm_iec958(struct snd_cs46xx *chip, int device); +int snd_cs46xx_pcm_center_lfe(struct snd_cs46xx *chip, int device); +int snd_cs46xx_mixer(struct snd_cs46xx *chip, int spdif_device); +int snd_cs46xx_midi(struct snd_cs46xx *chip, int device); +int snd_cs46xx_start_dsp(struct snd_cs46xx *chip); +int snd_cs46xx_gameport(struct snd_cs46xx *chip); + +#endif /* __SOUND_CS46XX_H */ diff --git a/sound/pci/cs46xx/cs46xx_dsp_scb_types.h b/sound/pci/cs46xx/cs46xx_dsp_scb_types.h new file mode 100644 index 000000000..7339c3857 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_dsp_scb_types.h @@ -0,0 +1,1198 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + * + * NOTE: comments are copy/paste from cwcemb80.lst + * provided by Tom Woller at Cirrus (my only + * documentation about the SP OS running inside + * the DSP) + */ + +#ifndef __CS46XX_DSP_SCB_TYPES_H__ +#define __CS46XX_DSP_SCB_TYPES_H__ + +#include + +#ifndef ___DSP_DUAL_16BIT_ALLOC +#if defined(__LITTLE_ENDIAN) +#define ___DSP_DUAL_16BIT_ALLOC(a,b) u16 a; u16 b; +#elif defined(__BIG_ENDIAN) +#define ___DSP_DUAL_16BIT_ALLOC(a,b) u16 b; u16 a; +#else +#error Not __LITTLE_ENDIAN and not __BIG_ENDIAN, then what ??? +#endif +#endif + +/* This structs are used internally by the SP */ + +struct dsp_basic_dma_req { + /* DMA Requestor Word 0 (DCW) fields: + + 31 [30-28]27 [26:24] 23 22 21 20 [19:18] [17:16] 15 14 13 12 11 10 9 8 7 6 [5:0] + _______________________________________________________________________________________ + |S| SBT |D| DBT |wb|wb| | | LS | SS |Opt|Do|SSG|DSG| | | | | | | Dword | + |H|_____ |H|_________|S_|D |__|__|______|_______|___|ne|__ |__ |__|__|_|_|_|_|_Count -1| + */ + u32 dcw; /* DMA Control Word */ + u32 dmw; /* DMA Mode Word */ + u32 saw; /* Source Address Word */ + u32 daw; /* Destination Address Word */ +}; + +struct dsp_scatter_gather_ext { + u32 npaw; /* Next-Page Address Word */ + + /* DMA Requestor Word 5 (NPCW) fields: + + 31-30 29 28 [27:16] [15:12] [11:3] [2:0] + _________________________________________________________________________________________ + |SV |LE|SE| Sample-end byte offset | | Page-map entry offset for next | | + |page|__|__| ___________________________|_________|__page, if !sample-end___________|____| + */ + u32 npcw; /* Next-Page Control Word */ + u32 lbaw; /* Loop-Begin Address Word */ + u32 nplbaw; /* Next-Page after Loop-Begin Address Word */ + u32 sgaw; /* Scatter/Gather Address Word */ +}; + +struct dsp_volume_control { + ___DSP_DUAL_16BIT_ALLOC( + rightTarg, /* Target volume for left & right channels */ + leftTarg + ) + ___DSP_DUAL_16BIT_ALLOC( + rightVol, /* Current left & right channel volumes */ + leftVol + ) +}; + +/* Generic stream control block (SCB) structure definition */ +struct dsp_generic_scb { + /* For streaming I/O, the DSP should never alter any words in the DMA + requestor or the scatter/gather extension. Only ad hoc DMA request + streams are free to alter the requestor (currently only occur in the + DOS-based MIDI controller and in debugger-inserted code). + + If an SCB does not have any associated DMA requestor, these 9 ints + may be freed for use by other tasks, but the pointer to the SCB must + still be such that the insOrd:nextSCB appear at offset 9 from the + SCB pointer. + + Basic (non scatter/gather) DMA requestor (4 ints) + */ + + /* Initialized by the host, only modified by DMA + R/O for the DSP task */ + struct dsp_basic_dma_req basic_req; /* Optional */ + + /* Scatter/gather DMA requestor extension (5 ints) + Initialized by the host, only modified by DMA + DSP task never needs to even read these. + */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + + /* Sublist pointer & next stream control block (SCB) link. + Initialized & modified by the host R/O for the DSP task + */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + /* Pointer to this tasks parameter block & stream function pointer + Initialized by the host R/O for the DSP task */ + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + /* rsConfig register for stream buffer (rsDMA reg. + is loaded from basicReq.daw for incoming streams, or + basicReq.saw, for outgoing streams) + + 31 30 29 [28:24] [23:16] 15 14 13 12 11 10 9 8 7 6 5 4 [3:0] + ______________________________________________________________________________ + |DMA |D|maxDMAsize| streamNum|dir|p| | | | | | |ds |shr 1|rev Cy | mod | + |prio |_|__________|__________|___|_|__|__|__|__|_|_|___|_____|_______|_______| + 31 30 29 [28:24] [23:16] 15 14 13 12 11 10 9 8 7 6 5 4 [3:0] + + + Initialized by the host R/O for the DSP task + */ + u32 strm_rs_config; /* REQUIRED */ + // + /* On mixer input streams: indicates mixer input stream configuration + On Tees, this is copied from the stream being snooped + + Stream sample pointer & MAC-unit mode for this stream + + Initialized by the host Updated by the DSP task + */ + u32 strm_buf_ptr; /* REQUIRED */ + + /* On mixer input streams: points to next mixer input and is updated by the + mixer subroutine in the "parent" DSP task + (least-significant 16 bits are preserved, unused) + + On Tees, the pointer is copied from the stream being snooped on + initialization, and, subsequently, it is copied into the + stream being snooped. + + On wavetable/3D voices: the strmBufPtr will use all 32 bits to allow for + fractional phase accumulation + + Fractional increment per output sample in the input sample buffer + + (Not used on mixer input streams & redefined on Tees) + On wavetable/3D voices: this 32-bit word specifies the integer.fractional + increment per output sample. + */ + u32 strmPhiIncr; + + + /* Standard stereo volume control + Initialized by the host (host updates target volumes) + + Current volumes update by the DSP task + On mixer input streams: required & updated by the mixer subroutine in the + "parent" DSP task + + On Tees, both current & target volumes are copied up on initialization, + and, subsequently, the target volume is copied up while the current + volume is copied down. + + These two 32-bit words are redefined for wavetable & 3-D voices. + */ + struct dsp_volume_control vol_ctrl_t; /* Optional */ +}; + + +struct dsp_spos_control_block { + /* WARNING: Certain items in this structure are modified by the host + Any dword that can be modified by the host, must not be + modified by the SP as the host can only do atomic dword + writes, and to do otherwise, even a read modify write, + may lead to corrupted data on the SP. + + This rule does not apply to one off boot time initialisation prior to starting the SP + */ + + + ___DSP_DUAL_16BIT_ALLOC( + /* First element on the Hyper forground task tree */ + hfg_tree_root_ptr, /* HOST */ + /* First 3 dwords are written by the host and read-only on the DSP */ + hfg_stack_base /* HOST */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Point to this data structure to enable easy access */ + spos_cb_ptr, /* SP */ + prev_task_tree_ptr /* SP && HOST */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Currently Unused */ + xxinterval_timer_period, + /* Enable extension of SPOS data structure */ + HFGSPB_ptr + ) + + + ___DSP_DUAL_16BIT_ALLOC( + xxnum_HFG_ticks_thisInterval, + /* Modified by the DSP */ + xxnum_tntervals + ) + + + /* Set by DSP upon encountering a trap (breakpoint) or a spurious + interrupt. The host must clear this dword after reading it + upon receiving spInt1. */ + ___DSP_DUAL_16BIT_ALLOC( + spurious_int_flag, /* (Host & SP) Nature of the spurious interrupt */ + trap_flag /* (Host & SP) Nature of detected Trap */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused2, + invalid_IP_flag /* (Host & SP ) Indicate detection of invalid instruction pointer */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* pointer to forground task tree header for use in next task search */ + fg_task_tree_hdr_ptr, /* HOST */ + /* Data structure for controlling synchronous link update */ + hfg_sync_update_ptr /* HOST */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + begin_foreground_FCNT, /* SP */ + /* Place holder for holding sleep timing */ + last_FCNT_before_sleep /* SP */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused7, /* SP */ + next_task_treePtr /* SP */ + ) + + u32 unused5; + + ___DSP_DUAL_16BIT_ALLOC( + active_flags, /* SP */ + /* State flags, used to assist control of execution of Hyper Forground */ + HFG_flags /* SP */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused9, + unused8 + ) + + /* Space for saving enough context so that we can set up enough + to save some more context. + */ + u32 rFE_save_for_invalid_IP; + u32 r32_save_for_spurious_int; + u32 r32_save_for_trap; + u32 r32_save_for_HFG; +}; + +/* SPB for MIX_TO_OSTREAM algorithm family */ +struct dsp_mix2_ostream_spb +{ + /* 16b.16b integer.frac approximation to the + number of 3 sample triplets to output each + frame. (approximation must be floor, to + insure that the fractional error is always + positive) + */ + u32 outTripletsPerFrame; + + /* 16b.16b integer.frac accumulated number of + output triplets since the start of group + */ + u32 accumOutTriplets; +}; + +/* SCB for Timing master algorithm */ +struct dsp_timing_master_scb { + /* First 12 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Initial values are 0000:xxxx */ + reserved, + extra_sample_accum + ) + + + /* Initial values are xxxx:0000 + hi: Current CODEC output FIFO pointer + (0 to 0x0f) + lo: Flag indicating that the CODEC + FIFO is sync'd (host clears to + resynchronize the FIFO pointer + upon start/restart) + */ + ___DSP_DUAL_16BIT_ALLOC( + codec_FIFO_syncd, + codec_FIFO_ptr + ) + + /* Init. 8000:0005 for 44.1k + 8000:0001 for 48k + hi: Fractional sample accumulator 0.16b + lo: Number of frames remaining to be + processed in the current group of + frames + */ + ___DSP_DUAL_16BIT_ALLOC( + frac_samp_accum_qm1, + TM_frms_left_in_group + ) + + /* Init. 0001:0005 for 44.1k + 0000:0001 for 48k + hi: Fractional sample correction factor 0.16b + to be added every frameGroupLength frames + to correct for truncation error in + nsamp_per_frm_q15 + lo: Number of frames in the group + */ + ___DSP_DUAL_16BIT_ALLOC( + frac_samp_correction_qm1, + TM_frm_group_length + ) + + /* Init. 44.1k*65536/8k = 0x00058333 for 44.1k + 48k*65536/8k = 0x00060000 for 48k + 16b.16b integer.frac approximation to the + number of samples to output each frame. + (approximation must be floor, to insure */ + u32 nsamp_per_frm_q15; +}; + +/* SCB for CODEC output algorithm */ +struct dsp_codec_output_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + + u32 strm_buf_ptr; /* REQUIRED */ + + /* NOTE: The CODEC output task reads samples from the first task on its + sublist at the stream buffer pointer (init. to lag DMA destination + address word). After the required number of samples is transferred, + the CODEC output task advances sub_list_ptr->strm_buf_ptr past the samples + consumed. + */ + + /* Init. 0000:0010 for SDout + 0060:0010 for SDout2 + 0080:0010 for SDout3 + hi: Base IO address of FIFO to which + the left-channel samples are to + be written. + lo: Displacement for the base IO + address for left-channel to obtain + the base IO address for the FIFO + to which the right-channel samples + are to be written. + */ + ___DSP_DUAL_16BIT_ALLOC( + left_chan_base_IO_addr, + right_chan_IO_disp + ) + + + /* Init: 0x0080:0004 for non-AC-97 + Init: 0x0080:0000 for AC-97 + hi: Exponential volume change rate + for input stream + lo: Positive shift count to shift the + 16-bit input sample to obtain the + 32-bit output word + */ + ___DSP_DUAL_16BIT_ALLOC( + CO_scale_shift_count, + CO_exp_vol_change_rate + ) + + /* Pointer to SCB at end of input chain */ + ___DSP_DUAL_16BIT_ALLOC( + reserved, + last_sub_ptr + ) +}; + +/* SCB for CODEC input algorithm */ +struct dsp_codec_input_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + /* NOTE: The CODEC input task reads samples from the hardware FIFO + sublist at the DMA source address word (sub_list_ptr->basic_req.saw). + After the required number of samples is transferred, the CODEC + output task advances sub_list_ptr->basic_req.saw past the samples + consumed. SPuD must initialize the sub_list_ptr->basic_req.saw + to point half-way around from the initial sub_list_ptr->strm_nuf_ptr + to allow for lag/lead. + */ + + /* Init. 0000:0010 for SDout + 0060:0010 for SDout2 + 0080:0010 for SDout3 + hi: Base IO address of FIFO to which + the left-channel samples are to + be written. + lo: Displacement for the base IO + address for left-channel to obtain + the base IO address for the FIFO + to which the right-channel samples + are to be written. + */ + ___DSP_DUAL_16BIT_ALLOC( + rightChanINdisp, + left_chan_base_IN_addr + ) + /* Init. ?:fffc + lo: Negative shift count to shift the + 32-bit input dword to obtain the + 16-bit sample msb-aligned (count + is negative to shift left) + */ + ___DSP_DUAL_16BIT_ALLOC( + scaleShiftCount, + reserver1 + ) + + u32 reserved2; +}; + + +struct dsp_pcm_serial_input_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_buf_ptr; /* REQUIRED */ + u32 strm_rs_config; /* REQUIRED */ + + /* Init. Ptr to CODEC input SCB + hi: Pointer to the SCB containing the + input buffer to which CODEC input + samples are written + lo: Flag indicating the link to the CODEC + input task is to be initialized + */ + ___DSP_DUAL_16BIT_ALLOC( + init_codec_input_link, + codec_input_buf_scb + ) + + /* Initialized by the host (host updates target volumes) */ + struct dsp_volume_control psi_vol_ctrl; + +}; + +struct dsp_src_task_scb { + ___DSP_DUAL_16BIT_ALLOC( + frames_left_in_gof, + gofs_left_in_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + const2_thirds, + num_extra_tnput_samples + ) + + ___DSP_DUAL_16BIT_ALLOC( + cor_per_gof, + correction_per_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + output_buf_producer_ptr, + junk_DMA_MID + ) + + ___DSP_DUAL_16BIT_ALLOC( + gof_length, + gofs_per_sec + ) + + u32 input_buf_strm_config; + + ___DSP_DUAL_16BIT_ALLOC( + reserved_for_SRC_use, + input_buf_consumer_ptr + ) + + u32 accum_phi; + + ___DSP_DUAL_16BIT_ALLOC( + exp_src_vol_change_rate, + input_buf_producer_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + src_next_scb, + src_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + src_entry_point, + src_this_sbp + ) + + u32 src_strm_rs_config; + u32 src_strm_buf_ptr; + + u32 phiIncr6int_26frac; + + struct dsp_volume_control src_vol_ctrl; +}; + +struct dsp_decimate_by_pow2_scb { + /* decimationFactor = 2, 4, or 8 (larger factors waste too much memory + when compared to cascading decimators) + */ + ___DSP_DUAL_16BIT_ALLOC( + dec2_coef_base_ptr, + dec2_coef_increment + ) + + /* coefIncrement = 128 / decimationFactor (for our ROM filter) + coefBasePtr = 0x8000 (for our ROM filter) + */ + ___DSP_DUAL_16BIT_ALLOC( + dec2_in_samples_per_out_triplet, + dec2_extra_in_samples + ) + /* extraInSamples: # of accumulated, unused input samples (init. to 0) + inSamplesPerOutTriplet = 3 * decimationFactor + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_const2_thirds, + dec2_half_num_taps_mp5 + ) + /* halfNumTapsM5: (1/2 number of taps in decimation filter) minus 5 + const2thirds: constant 2/3 in 16Q0 format (sign.15) + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_output_buf_producer_ptr, + dec2_junkdma_mid + ) + + u32 dec2_reserved2; + + u32 dec2_input_nuf_strm_config; + /* inputBufStrmConfig: rsConfig for the input buffer to the decimator + (buffer size = decimationFactor * 32 dwords) + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_phi_incr, + dec2_input_buf_consumer_ptr + ) + /* inputBufConsumerPtr: Input buffer read pointer (into SRC filter) + phiIncr = decimationFactor * 4 + */ + + u32 dec2_reserved3; + + ___DSP_DUAL_16BIT_ALLOC( + dec2_exp_vol_change_rate, + dec2_input_buf_producer_ptr + ) + /* inputBufProducerPtr: Input buffer write pointer + expVolChangeRate: Exponential volume change rate for possible + future mixer on input streams + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_next_scb, + dec2_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + dec2_entry_point, + dec2_this_spb + ) + + u32 dec2_strm_rs_config; + u32 dec2_strm_buf_ptr; + + u32 dec2_reserved4; + + struct dsp_volume_control dec2_vol_ctrl; /* Not used! */ +}; + +struct dsp_vari_decimate_scb { + ___DSP_DUAL_16BIT_ALLOC( + vdec_frames_left_in_gof, + vdec_gofs_left_in_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + vdec_const2_thirds, + vdec_extra_in_samples + ) + /* extraInSamples: # of accumulated, unused input samples (init. to 0) + const2thirds: constant 2/3 in 16Q0 format (sign.15) */ + + ___DSP_DUAL_16BIT_ALLOC( + vdec_cor_per_gof, + vdec_correction_per_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + vdec_output_buf_producer_ptr, + vdec_input_buf_consumer_ptr + ) + /* inputBufConsumerPtr: Input buffer read pointer (into SRC filter) */ + ___DSP_DUAL_16BIT_ALLOC( + vdec_gof_length, + vdec_gofs_per_sec + ) + + u32 vdec_input_buf_strm_config; + /* inputBufStrmConfig: rsConfig for the input buffer to the decimator + (buffer size = 64 dwords) */ + u32 vdec_coef_increment; + /* coefIncrement = - 128.0 / decimationFactor (as a 32Q15 number) */ + + u32 vdec_accumphi; + /* accumPhi: accumulated fractional phase increment (6.26) */ + + ___DSP_DUAL_16BIT_ALLOC( + vdec_exp_vol_change_rate, + vdec_input_buf_producer_ptr + ) + /* inputBufProducerPtr: Input buffer write pointer + expVolChangeRate: Exponential volume change rate for possible + future mixer on input streams */ + + ___DSP_DUAL_16BIT_ALLOC( + vdec_next_scb, + vdec_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + vdec_entry_point, + vdec_this_spb + ) + + u32 vdec_strm_rs_config; + u32 vdec_strm_buf_ptr; + + u32 vdec_phi_incr_6int_26frac; + + struct dsp_volume_control vdec_vol_ctrl; +}; + + +/* SCB for MIX_TO_OSTREAM algorithm family */ +struct dsp_mix2_ostream_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + + /* hi: Number of mixed-down input triplets + computed since start of group + lo: Number of frames remaining to be + processed in the current group of + frames + */ + ___DSP_DUAL_16BIT_ALLOC( + frames_left_in_group, + accum_input_triplets + ) + + /* hi: Exponential volume change rate + for mixer on input streams + lo: Number of frames in the group + */ + ___DSP_DUAL_16BIT_ALLOC( + frame_group_length, + exp_vol_change_rate + ) + + ___DSP_DUAL_16BIT_ALLOC( + const_FFFF, + const_zero + ) +}; + + +/* SCB for S16_MIX algorithm */ +struct dsp_mix_only_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + u32 reserved; + struct dsp_volume_control vol_ctrl; +}; + +/* SCB for the async. CODEC input algorithm */ +struct dsp_async_codec_input_scb { + u32 io_free2; + + u32 io_current_total; + u32 io_previous_total; + + u16 io_count; + u16 io_count_limit; + + u16 o_fifo_base_addr; + u16 ost_mo_format; + /* 1 = stereo; 0 = mono + xxx for ASER 1 (not allowed); 118 for ASER2 */ + + u32 ostrm_rs_config; + u32 ostrm_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + io_sclks_per_lr_clk, + io_io_enable + ) + + u32 io_free4; + + ___DSP_DUAL_16BIT_ALLOC( + io_next_scb, + io_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + io_entry_point, + io_this_spb + ) + + u32 istrm_rs_config; + u32 istrm_buf_ptr; + + /* Init. 0000:8042: for ASER1 + 0000:8044: for ASER2 */ + ___DSP_DUAL_16BIT_ALLOC( + io_stat_reg_addr, + iofifo_pointer + ) + + /* Init 1 stero:100 ASER1 + Init 0 mono:110 ASER2 + */ + ___DSP_DUAL_16BIT_ALLOC( + ififo_base_addr, + ist_mo_format + ) + + u32 i_free; +}; + + +/* SCB for the SP/DIF CODEC input and output */ +struct dsp_spdifiscb { + ___DSP_DUAL_16BIT_ALLOC( + status_ptr, + status_start_ptr + ) + + u32 current_total; + u32 previous_total; + + ___DSP_DUAL_16BIT_ALLOC( + count, + count_limit + ) + + u32 status_data; + + ___DSP_DUAL_16BIT_ALLOC( + status, + free4 + ) + + u32 free3; + + ___DSP_DUAL_16BIT_ALLOC( + free2, + bit_count + ) + + u32 temp_status; + + ___DSP_DUAL_16BIT_ALLOC( + next_SCB, + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_spb + ) + + u32 strm_rs_config; + u32 strm_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + stat_reg_addr, + fifo_pointer + ) + + ___DSP_DUAL_16BIT_ALLOC( + fifo_base_addr, + st_mo_format + ) + + u32 free1; +}; + + +/* SCB for the SP/DIF CODEC input and output */ +struct dsp_spdifoscb { + + u32 free2; + + u32 free3[4]; + + /* Need to be here for compatibility with AsynchFGTxCode */ + u32 strm_rs_config; + + u32 strm_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + status, + free5 + ) + + u32 free4; + + ___DSP_DUAL_16BIT_ALLOC( + next_scb, + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_spb + ) + + u32 free6[2]; + + ___DSP_DUAL_16BIT_ALLOC( + stat_reg_addr, + fifo_pointer + ) + + ___DSP_DUAL_16BIT_ALLOC( + fifo_base_addr, + st_mo_format + ) + + u32 free1; +}; + + +struct dsp_asynch_fg_rx_scb { + ___DSP_DUAL_16BIT_ALLOC( + bot_buf_mask, + buf_Mask + ) + + ___DSP_DUAL_16BIT_ALLOC( + max, + min + ) + + ___DSP_DUAL_16BIT_ALLOC( + old_producer_pointer, + hfg_scb_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + delta, + adjust_count + ) + + u32 unused2[5]; + + ___DSP_DUAL_16BIT_ALLOC( + sibling_ptr, + child_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + code_ptr, + this_ptr + ) + + u32 strm_rs_config; + + u32 strm_buf_ptr; + + u32 unused_phi_incr; + + ___DSP_DUAL_16BIT_ALLOC( + right_targ, + left_targ + ) + + ___DSP_DUAL_16BIT_ALLOC( + right_vol, + left_vol + ) +}; + + +struct dsp_asynch_fg_tx_scb { + ___DSP_DUAL_16BIT_ALLOC( + not_buf_mask, + buf_mask + ) + + ___DSP_DUAL_16BIT_ALLOC( + max, + min + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused1, + hfg_scb_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + delta, + adjust_count + ) + + u32 accum_phi; + + ___DSP_DUAL_16BIT_ALLOC( + unused2, + const_one_third + ) + + u32 unused3[3]; + + ___DSP_DUAL_16BIT_ALLOC( + sibling_ptr, + child_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + codePtr, + this_ptr + ) + + u32 strm_rs_config; + + u32 strm_buf_ptr; + + u32 phi_incr; + + ___DSP_DUAL_16BIT_ALLOC( + unused_right_targ, + unused_left_targ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused_right_vol, + unused_left_vol + ) +}; + + +struct dsp_output_snoop_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + ___DSP_DUAL_16BIT_ALLOC( + init_snoop_input_link, + snoop_child_input_scb + ) + + u32 snoop_input_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + reserved, + input_scb + ) +}; + +struct dsp_spio_write_scb { + ___DSP_DUAL_16BIT_ALLOC( + address1, + address2 + ) + + u32 data1; + + u32 data2; + + ___DSP_DUAL_16BIT_ALLOC( + address3, + address4 + ) + + u32 data3; + + u32 data4; + + ___DSP_DUAL_16BIT_ALLOC( + unused1, + data_ptr + ) + + u32 unused2[2]; + + ___DSP_DUAL_16BIT_ALLOC( + sibling_ptr, + child_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_ptr + ) + + u32 unused3[5]; +}; + +struct dsp_magic_snoop_task { + u32 i0; + u32 i1; + + u32 strm_buf_ptr1; + + u16 i2; + u16 snoop_scb; + + u32 i3; + u32 i4; + u32 i5; + u32 i6; + + u32 i7; + + ___DSP_DUAL_16BIT_ALLOC( + next_scb, + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_ptr + ) + + u32 strm_buf_config; + u32 strm_buf_ptr2; + + u32 i8; + + struct dsp_volume_control vdec_vol_ctrl; +}; + + +struct dsp_filter_scb { + ___DSP_DUAL_16BIT_ALLOC( + a0_right, /* 0x00 */ + a0_left + ) + ___DSP_DUAL_16BIT_ALLOC( + a1_right, /* 0x01 */ + a1_left + ) + ___DSP_DUAL_16BIT_ALLOC( + a2_right, /* 0x02 */ + a2_left + ) + ___DSP_DUAL_16BIT_ALLOC( + output_buf_ptr, /* 0x03 */ + init + ) + + ___DSP_DUAL_16BIT_ALLOC( + filter_unused3, /* 0x04 */ + filter_unused2 + ) + + u32 prev_sample_output1; /* 0x05 */ + u32 prev_sample_output2; /* 0x06 */ + u32 prev_sample_input1; /* 0x07 */ + u32 prev_sample_input2; /* 0x08 */ + + ___DSP_DUAL_16BIT_ALLOC( + next_scb_ptr, /* 0x09 */ + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* 0x0A */ + spb_ptr + ) + + u32 strm_rs_config; /* 0x0B */ + u32 strm_buf_ptr; /* 0x0C */ + + ___DSP_DUAL_16BIT_ALLOC( + b0_right, /* 0x0D */ + b0_left + ) + ___DSP_DUAL_16BIT_ALLOC( + b1_right, /* 0x0E */ + b1_left + ) + ___DSP_DUAL_16BIT_ALLOC( + b2_right, /* 0x0F */ + b2_left + ) +}; +#endif /* __DSP_SCB_TYPES_H__ */ diff --git a/sound/pci/cs46xx/cs46xx_dsp_spos.h b/sound/pci/cs46xx/cs46xx_dsp_spos.h new file mode 100644 index 000000000..2fa9c7d6a --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_dsp_spos.h @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + */ + +#ifndef __CS46XX_DSP_SPOS_H__ +#define __CS46XX_DSP_SPOS_H__ + +#include "cs46xx_dsp_scb_types.h" +#include "cs46xx_dsp_task_types.h" + +#define SYMBOL_CONSTANT 0x0 +#define SYMBOL_SAMPLE 0x1 +#define SYMBOL_PARAMETER 0x2 +#define SYMBOL_CODE 0x3 + +#define SEGTYPE_SP_PROGRAM 0x00000001 +#define SEGTYPE_SP_PARAMETER 0x00000002 +#define SEGTYPE_SP_SAMPLE 0x00000003 +#define SEGTYPE_SP_COEFFICIENT 0x00000004 + +#define DSP_SPOS_UU 0x0deadul /* unused */ +#define DSP_SPOS_DC 0x0badul /* don't care */ +#define DSP_SPOS_DC_DC 0x0bad0badul /* don't care */ +#define DSP_SPOS_UUUU 0xdeadc0edul /* unused */ +#define DSP_SPOS_UUHI 0xdeadul +#define DSP_SPOS_UULO 0xc0edul +#define DSP_SPOS_DCDC 0x0badf1d0ul /* don't care */ +#define DSP_SPOS_DCDCHI 0x0badul +#define DSP_SPOS_DCDCLO 0xf1d0ul + +#define DSP_MAX_TASK_NAME 60 +#define DSP_MAX_SYMBOL_NAME 100 +#define DSP_MAX_SCB_NAME 60 +#define DSP_MAX_SCB_DESC 200 +#define DSP_MAX_TASK_DESC 50 + +#define DSP_MAX_PCM_CHANNELS 32 +#define DSP_MAX_SRC_NR 14 + +#define DSP_PCM_MAIN_CHANNEL 1 +#define DSP_PCM_REAR_CHANNEL 2 +#define DSP_PCM_CENTER_LFE_CHANNEL 3 +#define DSP_PCM_S71_CHANNEL 4 /* surround 7.1 */ +#define DSP_IEC958_CHANNEL 5 + +#define DSP_SPDIF_STATUS_OUTPUT_ENABLED 1 +#define DSP_SPDIF_STATUS_PLAYBACK_OPEN 2 +#define DSP_SPDIF_STATUS_HW_ENABLED 4 +#define DSP_SPDIF_STATUS_INPUT_CTRL_ENABLED 8 + +struct dsp_symbol_entry { + u32 address; + char symbol_name[DSP_MAX_SYMBOL_NAME]; + int symbol_type; + + /* initialized by driver */ + struct dsp_module_desc * module; + int deleted; +}; + +struct dsp_symbol_desc { + int nsymbols; + + struct dsp_symbol_entry *symbols; + + /* initialized by driver */ + int highest_frag_index; +}; + +struct dsp_segment_desc { + int segment_type; + u32 offset; + u32 size; + u32 * data; +}; + +struct dsp_module_desc { + char * module_name; + struct dsp_symbol_desc symbol_table; + int nsegments; + struct dsp_segment_desc * segments; + + /* initialized by driver */ + u32 overlay_begin_address; + u32 load_address; + int nfixups; +}; + +struct dsp_scb_descriptor { + char scb_name[DSP_MAX_SCB_NAME]; + u32 address; + int index; + u32 *data; + + struct dsp_scb_descriptor * sub_list_ptr; + struct dsp_scb_descriptor * next_scb_ptr; + struct dsp_scb_descriptor * parent_scb_ptr; + + struct dsp_symbol_entry * task_entry; + struct dsp_symbol_entry * scb_symbol; + + struct snd_info_entry *proc_info; + int ref_count; + + u16 volume[2]; + unsigned int deleted :1; + unsigned int updated :1; + unsigned int volume_set :1; +}; + +struct dsp_task_descriptor { + char task_name[DSP_MAX_TASK_NAME]; + int size; + u32 address; + int index; + u32 *data; +}; + +struct dsp_pcm_channel_descriptor { + int active; + int src_slot; + int pcm_slot; + u32 sample_rate; + u32 unlinked; + struct dsp_scb_descriptor * pcm_reader_scb; + struct dsp_scb_descriptor * src_scb; + struct dsp_scb_descriptor * mixer_scb; + + void * private_data; +}; + +struct dsp_spos_instance { + struct dsp_symbol_desc symbol_table; /* currently available loaded symbols in SP */ + + int nmodules; + struct dsp_module_desc * modules; /* modules loaded into SP */ + + struct dsp_segment_desc code; + + /* Main PCM playback mixer */ + struct dsp_scb_descriptor * master_mix_scb; + u16 dac_volume_right; + u16 dac_volume_left; + + /* Rear/surround PCM playback mixer */ + struct dsp_scb_descriptor * rear_mix_scb; + + /* Center/LFE mixer */ + struct dsp_scb_descriptor * center_lfe_mix_scb; + + int npcm_channels; + int nsrc_scb; + struct dsp_pcm_channel_descriptor pcm_channels[DSP_MAX_PCM_CHANNELS]; + int src_scb_slots[DSP_MAX_SRC_NR]; + + /* cache this symbols */ + struct dsp_symbol_entry * null_algorithm; /* used by PCMreaderSCB's */ + struct dsp_symbol_entry * s16_up; /* used by SRCtaskSCB's */ + + /* proc fs */ + struct snd_card *snd_card; + struct snd_info_entry * proc_dsp_dir; + + /* SCB's descriptors */ + int nscb; + int scb_highest_frag_index; + struct dsp_scb_descriptor scbs[DSP_MAX_SCB_DESC]; + struct dsp_scb_descriptor * the_null_scb; + + /* Task's descriptors */ + int ntask; + struct dsp_task_descriptor tasks[DSP_MAX_TASK_DESC]; + + /* SPDIF status */ + int spdif_status_out; + int spdif_status_in; + u16 spdif_input_volume_right; + u16 spdif_input_volume_left; + /* spdif channel status, + left right and user validity bits */ + unsigned int spdif_csuv_default; + unsigned int spdif_csuv_stream; + + /* SPDIF input sample rate converter */ + struct dsp_scb_descriptor * spdif_in_src; + /* SPDIF input asynch. receiver */ + struct dsp_scb_descriptor * asynch_rx_scb; + + /* Capture record mixer SCB */ + struct dsp_scb_descriptor * record_mixer_scb; + + /* CODEC input SCB */ + struct dsp_scb_descriptor * codec_in_scb; + + /* reference snooper */ + struct dsp_scb_descriptor * ref_snoop_scb; + + /* SPDIF output PCM reference */ + struct dsp_scb_descriptor * spdif_pcm_input_scb; + + /* asynch TX task */ + struct dsp_scb_descriptor * asynch_tx_scb; + + /* record sources */ + struct dsp_scb_descriptor * pcm_input; + struct dsp_scb_descriptor * adc_input; + + int spdif_in_sample_rate; +}; + +#endif /* __DSP_SPOS_H__ */ diff --git a/sound/pci/cs46xx/cs46xx_dsp_task_types.h b/sound/pci/cs46xx/cs46xx_dsp_task_types.h new file mode 100644 index 000000000..fcbd31e40 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_dsp_task_types.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + * + * NOTE: comments are copy/paste from cwcemb80.lst + * provided by Tom Woller at Cirrus (my only + * documentation about the SP OS running inside + * the DSP) + */ + +#ifndef __CS46XX_DSP_TASK_TYPES_H__ +#define __CS46XX_DSP_TASK_TYPES_H__ + +#include "cs46xx_dsp_scb_types.h" + +/********************************************************************************************* +Example hierarchy of stream control blocks in the SP + +hfgTree +Ptr____Call (c) + \ + -------+------ ------------- ------------- ------------- ----- +| SBlaster IF |______\| Foreground |___\| Middlegr'nd |___\| Background |___\| Nul | +| |Goto /| tree header |g /| tree header |g /| tree header |g /| SCB |r + -------------- (g) ------------- ------------- ------------- ----- + |c |c |c |c + | | | | + \/ ------------- ------------- ------------- + | Foreground |_\ | Middlegr'nd |_\ | Background |_\ + | tree |g/ | tree |g/ | tree |g/ + ------------- ------------- ------------- + |c |c |c + | | | + \/ \/ \/ + +*********************************************************************************************/ + +#define HFG_FIRST_EXECUTE_MODE 0x0001 +#define HFG_FIRST_EXECUTE_MODE_BIT 0 +#define HFG_CONTEXT_SWITCH_MODE 0x0002 +#define HFG_CONTEXT_SWITCH_MODE_BIT 1 + +#define MAX_FG_STACK_SIZE 32 /* THESE NEED TO BE COMPUTED PROPERLY */ +#define MAX_MG_STACK_SIZE 16 +#define MAX_BG_STACK_SIZE 9 +#define MAX_HFG_STACK_SIZE 4 + +#define SLEEP_ACTIVE_INCREMENT 0 /* Enable task tree thread to go to sleep + This should only ever be used on the Background thread */ +#define STANDARD_ACTIVE_INCREMENT 1 /* Task tree thread normal operation */ +#define SUSPEND_ACTIVE_INCREMENT 2 /* Cause execution to suspend in the task tree thread + This should only ever be used on the Background thread */ + +#define HOSTFLAGS_DISABLE_BG_SLEEP 0 /* Host-controlled flag that determines whether we go to sleep + at the end of BG */ + +/* Minimal context save area for Hyper Forground */ +struct dsp_hf_save_area { + u32 r10_save; + u32 r54_save; + u32 r98_save; + + ___DSP_DUAL_16BIT_ALLOC( + status_save, + ind_save + ) + + ___DSP_DUAL_16BIT_ALLOC( + rci1_save, + rci0_save + ) + + u32 r32_save; + u32 r76_save; + u32 rsd2_save; + + ___DSP_DUAL_16BIT_ALLOC( + rsi2_save, /* See TaskTreeParameterBlock for + remainder of registers */ + rsa2Save + ) + /* saved as part of HFG context */ +}; + + +/* Task link data structure */ +struct dsp_tree_link { + ___DSP_DUAL_16BIT_ALLOC( + /* Pointer to sibling task control block */ + next_scb, + /* Pointer to child task control block */ + sub_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Pointer to code entry point */ + entry_point, + /* Pointer to local data */ + this_spb + ) +}; + + +struct dsp_task_tree_data { + ___DSP_DUAL_16BIT_ALLOC( + /* Initial tock count; controls task tree execution rate */ + tock_count_limit, + /* Tock down counter */ + tock_count + ) + + /* Add to ActiveCount when TockCountLimit reached: + Subtract on task tree termination */ + ___DSP_DUAL_16BIT_ALLOC( + active_tncrement, + /* Number of pending activations for task tree */ + active_count + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* BitNumber to enable modification of correct bit in ActiveTaskFlags */ + active_bit, + /* Pointer to OS location for indicating current activity on task level */ + active_task_flags_ptr + ) + + /* Data structure for controlling movement of memory blocks:- + currently unused */ + ___DSP_DUAL_16BIT_ALLOC( + mem_upd_ptr, + /* Data structure for controlling synchronous link update */ + link_upd_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Save area for remainder of full context. */ + save_area, + /* Address of start of local stack for data storage */ + data_stack_base_ptr + ) + +}; + + +struct dsp_interval_timer_data +{ + /* These data items have the same relative locations to those */ + ___DSP_DUAL_16BIT_ALLOC( + interval_timer_period, + itd_unused + ) + + /* used for this data in the SPOS control block for SPOS 1.0 */ + ___DSP_DUAL_16BIT_ALLOC( + num_FG_ticks_this_interval, + num_intervals + ) +}; + + +/* This structure contains extra storage for the task tree + Currently, this additional data is related only to a full context save */ +struct dsp_task_tree_context_block { + /* Up to 10 values are saved onto the stack. 8 for the task tree, 1 for + The access to the context switch (call or interrupt), and 1 spare that + users should never use. This last may be required by the system */ + ___DSP_DUAL_16BIT_ALLOC( + stack1, + stack0 + ) + ___DSP_DUAL_16BIT_ALLOC( + stack3, + stack2 + ) + ___DSP_DUAL_16BIT_ALLOC( + stack5, + stack4 + ) + ___DSP_DUAL_16BIT_ALLOC( + stack7, + stack6 + ) + ___DSP_DUAL_16BIT_ALLOC( + stack9, + stack8 + ) + + u32 saverfe; + + /* Value may be overwritten by stack save algorithm. + Retain the size of the stack data saved here if used */ + ___DSP_DUAL_16BIT_ALLOC( + reserved1, + stack_size + ) + u32 saverba; /* (HFG) */ + u32 saverdc; + u32 savers_config_23; /* (HFG) */ + u32 savers_DMA23; /* (HFG) */ + u32 saversa0; + u32 saversi0; + u32 saversa1; + u32 saversi1; + u32 saversa3; + u32 saversd0; + u32 saversd1; + u32 saversd3; + u32 savers_config01; + u32 savers_DMA01; + u32 saveacc0hl; + u32 saveacc1hl; + u32 saveacc0xacc1x; + u32 saveacc2hl; + u32 saveacc3hl; + u32 saveacc2xacc3x; + u32 saveaux0hl; + u32 saveaux1hl; + u32 saveaux0xaux1x; + u32 saveaux2hl; + u32 saveaux3hl; + u32 saveaux2xaux3x; + u32 savershouthl; + u32 savershoutxmacmode; +}; + + +struct dsp_task_tree_control_block { + struct dsp_hf_save_area context; + struct dsp_tree_link links; + struct dsp_task_tree_data data; + struct dsp_task_tree_context_block context_blk; + struct dsp_interval_timer_data int_timer; +}; + + +#endif /* __DSP_TASK_TYPES_H__ */ diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c new file mode 100644 index 000000000..19a6927c0 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -0,0 +1,3914 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Abramo Bagnara + * Cirrus Logic, Inc. + * Routines for control of Cirrus Logic CS461x chips + * + * KNOWN BUGS: + * - Sometimes the SPDIF input DSP tasks get's unsynchronized + * and the SPDIF get somewhat "distorcionated", or/and left right channel + * are swapped. To get around this problem when it happens, mute and unmute + * the SPDIF input mixer control. + * - On the Hercules Game Theater XP the amplifier are sometimes turned + * off on inadecuate moments which causes distorcions on sound. + * + * TODO: + * - Secondary CODEC on some soundcards + * - SPDIF input support for other sample rates then 48khz + * - Posibility to mix the SPDIF output with analog sources. + * - PCM channels for Center and LFE on secondary codec + * + * NOTE: with CONFIG_SND_CS46XX_NEW_DSP unset uses old DSP image (which + * is default configuration), no SPDIF, no secondary codec, no + * multi channel PCM. But known to work. + * + * FINALLY: A credit to the developers Tom and Jordan + * at Cirrus for have helping me out with the DSP, however we + * still don't have sufficient documentation and technical + * references to be able to implement all fancy feutures + * supported by the cs46xx DSP's. + * Benny + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "cs46xx.h" + +#include "cs46xx_lib.h" +#include "dsp_spos.h" + +static void amp_voyetra(struct snd_cs46xx *chip, int change); + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +static const struct snd_pcm_ops snd_cs46xx_playback_rear_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_rear_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_clfe_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_clfe_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_iec958_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_iec958_ops; +#endif + +static const struct snd_pcm_ops snd_cs46xx_playback_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_ops; +static const struct snd_pcm_ops snd_cs46xx_capture_ops; +static const struct snd_pcm_ops snd_cs46xx_capture_indirect_ops; + +static unsigned short snd_cs46xx_codec_read(struct snd_cs46xx *chip, + unsigned short reg, + int codec_index) +{ + int count; + unsigned short result,tmp; + u32 offset = 0; + + if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX && + codec_index != CS46XX_SECONDARY_CODEC_INDEX)) + return 0xffff; + + chip->active_ctrl(chip, 1); + + if (codec_index == CS46XX_SECONDARY_CODEC_INDEX) + offset = CS46XX_SECONDARY_CODEC_OFFSET; + + /* + * 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address + * 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 + * 3. Write ACCTL = Control Register = 460h for initiating the write7---55 + * 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 17h + * 5. if DCV not cleared, break and return error + * 6. Read ACSTS = Status Register = 464h, check VSTS bit + */ + + snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset); + + tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL); + if ((tmp & ACCTL_VFRM) == 0) { + dev_warn(chip->card->dev, "ACCTL_VFRM not set 0x%x\n", tmp); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, (tmp & (~ACCTL_ESYN)) | ACCTL_VFRM ); + msleep(50); + tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL + offset); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, tmp | ACCTL_ESYN | ACCTL_VFRM ); + + } + + /* + * Setup the AC97 control registers on the CS461x to send the + * appropriate command to the AC97 to perform the read. + * ACCAD = Command Address Register = 46Ch + * ACCDA = Command Data Register = 470h + * ACCTL = Control Register = 460h + * set DCV - will clear when process completed + * set CRW - Read command + * set VFRM - valid frame enabled + * set ESYN - ASYNC generation enabled + * set RSTN - ARST# inactive, AC97 codec not reset + */ + + snd_cs46xx_pokeBA0(chip, BA0_ACCAD, reg); + snd_cs46xx_pokeBA0(chip, BA0_ACCDA, 0); + if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) { + snd_cs46xx_pokeBA0(chip, BA0_ACCTL,/* clear ACCTL_DCV */ ACCTL_CRW | + ACCTL_VFRM | ACCTL_ESYN | + ACCTL_RSTN); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW | + ACCTL_VFRM | ACCTL_ESYN | + ACCTL_RSTN); + } else { + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC | + ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN | + ACCTL_RSTN); + } + + /* + * Wait for the read to occur. + */ + for (count = 0; count < 1000; count++) { + /* + * First, we want to wait for a short time. + */ + udelay(10); + /* + * Now, check to see if the read has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 17h + */ + if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV)) + goto ok1; + } + + dev_err(chip->card->dev, + "AC'97 read problem (ACCTL_DCV), reg = 0x%x\n", reg); + result = 0xffff; + goto end; + + ok1: + /* + * Wait for the valid status bit to go active. + */ + for (count = 0; count < 100; count++) { + /* + * Read the AC97 status register. + * ACSTS = Status Register = 464h + * VSTS - Valid Status + */ + if (snd_cs46xx_peekBA0(chip, BA0_ACSTS + offset) & ACSTS_VSTS) + goto ok2; + udelay(10); + } + + dev_err(chip->card->dev, + "AC'97 read problem (ACSTS_VSTS), codec_index %d, reg = 0x%x\n", + codec_index, reg); + result = 0xffff; + goto end; + + ok2: + /* + * Read the data returned from the AC97 register. + * ACSDA = Status Data Register = 474h + */ +#if 0 + dev_dbg(chip->card->dev, + "e) reg = 0x%x, val = 0x%x, BA0_ACCAD = 0x%x\n", reg, + snd_cs46xx_peekBA0(chip, BA0_ACSDA), + snd_cs46xx_peekBA0(chip, BA0_ACCAD)); +#endif + + //snd_cs46xx_peekBA0(chip, BA0_ACCAD); + result = snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset); + end: + chip->active_ctrl(chip, -1); + return result; +} + +static unsigned short snd_cs46xx_ac97_read(struct snd_ac97 * ac97, + unsigned short reg) +{ + struct snd_cs46xx *chip = ac97->private_data; + unsigned short val; + int codec_index = ac97->num; + + if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX && + codec_index != CS46XX_SECONDARY_CODEC_INDEX)) + return 0xffff; + + val = snd_cs46xx_codec_read(chip, reg, codec_index); + + return val; +} + + +static void snd_cs46xx_codec_write(struct snd_cs46xx *chip, + unsigned short reg, + unsigned short val, + int codec_index) +{ + int count; + + if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX && + codec_index != CS46XX_SECONDARY_CODEC_INDEX)) + return; + + chip->active_ctrl(chip, 1); + + /* + * 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address + * 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 + * 3. Write ACCTL = Control Register = 460h for initiating the write + * 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 07h + * 5. if DCV not cleared, break and return error + */ + + /* + * Setup the AC97 control registers on the CS461x to send the + * appropriate command to the AC97 to perform the read. + * ACCAD = Command Address Register = 46Ch + * ACCDA = Command Data Register = 470h + * ACCTL = Control Register = 460h + * set DCV - will clear when process completed + * reset CRW - Write command + * set VFRM - valid frame enabled + * set ESYN - ASYNC generation enabled + * set RSTN - ARST# inactive, AC97 codec not reset + */ + snd_cs46xx_pokeBA0(chip, BA0_ACCAD , reg); + snd_cs46xx_pokeBA0(chip, BA0_ACCDA , val); + snd_cs46xx_peekBA0(chip, BA0_ACCTL); + + if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) { + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, /* clear ACCTL_DCV */ ACCTL_VFRM | + ACCTL_ESYN | ACCTL_RSTN); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM | + ACCTL_ESYN | ACCTL_RSTN); + } else { + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC | + ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); + } + + for (count = 0; count < 4000; count++) { + /* + * First, we want to wait for a short time. + */ + udelay(10); + /* + * Now, check to see if the write has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 07h + */ + if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV)) { + goto end; + } + } + dev_err(chip->card->dev, + "AC'97 write problem, codec_index = %d, reg = 0x%x, val = 0x%x\n", + codec_index, reg, val); + end: + chip->active_ctrl(chip, -1); +} + +static void snd_cs46xx_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct snd_cs46xx *chip = ac97->private_data; + int codec_index = ac97->num; + + if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX && + codec_index != CS46XX_SECONDARY_CODEC_INDEX)) + return; + + snd_cs46xx_codec_write(chip, reg, val, codec_index); +} + + +/* + * Chip initialization + */ + +int snd_cs46xx_download(struct snd_cs46xx *chip, + u32 *src, + unsigned long offset, + unsigned long len) +{ + void __iomem *dst; + unsigned int bank = offset >> 16; + offset = offset & 0xffff; + + if (snd_BUG_ON((offset & 3) || (len & 3))) + return -EINVAL; + dst = chip->region.idx[bank+1].remap_addr + offset; + len /= sizeof(u32); + + /* writel already converts 32-bit value to right endianess */ + while (len-- > 0) { + writel(*src++, dst); + dst += sizeof(u32); + } + return 0; +} + +static inline void memcpy_le32(void *dst, const void *src, unsigned int len) +{ +#ifdef __LITTLE_ENDIAN + memcpy(dst, src, len); +#else + u32 *_dst = dst; + const __le32 *_src = src; + len /= 4; + while (len-- > 0) + *_dst++ = le32_to_cpu(*_src++); +#endif +} + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + +static const char *module_names[CS46XX_DSP_MODULES] = { + "cwc4630", "cwcasync", "cwcsnoop", "cwcbinhack", "cwcdma" +}; + +MODULE_FIRMWARE("cs46xx/cwc4630"); +MODULE_FIRMWARE("cs46xx/cwcasync"); +MODULE_FIRMWARE("cs46xx/cwcsnoop"); +MODULE_FIRMWARE("cs46xx/cwcbinhack"); +MODULE_FIRMWARE("cs46xx/cwcdma"); + +static void free_module_desc(struct dsp_module_desc *module) +{ + if (!module) + return; + kfree(module->module_name); + kfree(module->symbol_table.symbols); + if (module->segments) { + int i; + for (i = 0; i < module->nsegments; i++) + kfree(module->segments[i].data); + kfree(module->segments); + } + kfree(module); +} + +/* firmware binary format: + * le32 nsymbols; + * struct { + * le32 address; + * char symbol_name[DSP_MAX_SYMBOL_NAME]; + * le32 symbol_type; + * } symbols[nsymbols]; + * le32 nsegments; + * struct { + * le32 segment_type; + * le32 offset; + * le32 size; + * le32 data[size]; + * } segments[nsegments]; + */ + +static int load_firmware(struct snd_cs46xx *chip, + struct dsp_module_desc **module_ret, + const char *fw_name) +{ + int i, err; + unsigned int nums, fwlen, fwsize; + const __le32 *fwdat; + struct dsp_module_desc *module = NULL; + const struct firmware *fw __free(firmware) = NULL; + char fw_path[32]; + + sprintf(fw_path, "cs46xx/%s", fw_name); + err = request_firmware(&fw, fw_path, &chip->pci->dev); + if (err < 0) + return err; + fwsize = fw->size / 4; + if (fwsize < 2) + return -EINVAL; + + err = -ENOMEM; + module = kzalloc_obj(*module); + if (!module) + goto error; + module->module_name = kstrdup(fw_name, GFP_KERNEL); + if (!module->module_name) + goto error; + + fwlen = 0; + fwdat = (const __le32 *)fw->data; + nums = module->symbol_table.nsymbols = le32_to_cpu(fwdat[fwlen++]); + if (nums >= 40) + goto error_inval; + module->symbol_table.symbols = + kzalloc_objs(struct dsp_symbol_entry, nums); + if (!module->symbol_table.symbols) + goto error; + for (i = 0; i < nums; i++) { + struct dsp_symbol_entry *entry = + &module->symbol_table.symbols[i]; + if (fwlen + 2 + DSP_MAX_SYMBOL_NAME / 4 > fwsize) + goto error_inval; + entry->address = le32_to_cpu(fwdat[fwlen++]); + memcpy(entry->symbol_name, &fwdat[fwlen], DSP_MAX_SYMBOL_NAME - 1); + fwlen += DSP_MAX_SYMBOL_NAME / 4; + entry->symbol_type = le32_to_cpu(fwdat[fwlen++]); + } + + if (fwlen >= fwsize) + goto error_inval; + nums = module->nsegments = le32_to_cpu(fwdat[fwlen++]); + if (nums > 10) + goto error_inval; + module->segments = + kzalloc_objs(struct dsp_segment_desc, nums); + if (!module->segments) + goto error; + for (i = 0; i < nums; i++) { + struct dsp_segment_desc *entry = &module->segments[i]; + if (fwlen + 3 > fwsize) + goto error_inval; + entry->segment_type = le32_to_cpu(fwdat[fwlen++]); + entry->offset = le32_to_cpu(fwdat[fwlen++]); + entry->size = le32_to_cpu(fwdat[fwlen++]); + if (fwlen + entry->size > fwsize) + goto error_inval; + entry->data = kmalloc_array(entry->size, 4, GFP_KERNEL); + if (!entry->data) + goto error; + memcpy_le32(entry->data, &fwdat[fwlen], entry->size * 4); + fwlen += entry->size; + } + + *module_ret = module; + return 0; + + error_inval: + err = -EINVAL; + error: + free_module_desc(module); + return err; +} + +int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip, + unsigned long offset, + unsigned long len) +{ + void __iomem *dst; + unsigned int bank = offset >> 16; + offset = offset & 0xffff; + + if (snd_BUG_ON((offset & 3) || (len & 3))) + return -EINVAL; + dst = chip->region.idx[bank+1].remap_addr + offset; + len /= sizeof(u32); + + /* writel already converts 32-bit value to right endianess */ + while (len-- > 0) { + writel(0, dst); + dst += sizeof(u32); + } + return 0; +} + +#else /* old DSP image */ + +struct ba1_struct { + struct { + u32 offset; + u32 size; + } memory[BA1_MEMORY_COUNT]; + u32 map[BA1_DWORD_SIZE]; +}; + +MODULE_FIRMWARE("cs46xx/ba1"); + +static int load_firmware(struct snd_cs46xx *chip) +{ + const struct firmware *fw __free(firmware) = NULL; + int i, size, err; + + err = request_firmware(&fw, "cs46xx/ba1", &chip->pci->dev); + if (err < 0) + return err; + if (fw->size != sizeof(*chip->ba1)) + return -EINVAL; + + chip->ba1 = vmalloc(sizeof(*chip->ba1)); + if (!chip->ba1) + return -ENOMEM; + + memcpy_le32(chip->ba1, fw->data, sizeof(*chip->ba1)); + + /* sanity check */ + size = 0; + for (i = 0; i < BA1_MEMORY_COUNT; i++) + size += chip->ba1->memory[i].size; + if (size > BA1_DWORD_SIZE * 4) + return -EINVAL; + + return 0; +} + +static __maybe_unused int snd_cs46xx_download_image(struct snd_cs46xx *chip) +{ + int idx, err; + unsigned int offset = 0; + struct ba1_struct *ba1 = chip->ba1; + + for (idx = 0; idx < BA1_MEMORY_COUNT; idx++) { + err = snd_cs46xx_download(chip, + &ba1->map[offset], + ba1->memory[idx].offset, + ba1->memory[idx].size); + if (err < 0) + return err; + offset += ba1->memory[idx].size >> 2; + } + return 0; +} +#endif /* CONFIG_SND_CS46XX_NEW_DSP */ + +/* + * Chip reset + */ + +static void snd_cs46xx_reset(struct snd_cs46xx *chip) +{ + int idx; + + /* + * Write the reset bit of the SP control register. + */ + snd_cs46xx_poke(chip, BA1_SPCR, SPCR_RSTSP); + + /* + * Write the control register. + */ + snd_cs46xx_poke(chip, BA1_SPCR, SPCR_DRQEN); + + /* + * Clear the trap registers. + */ + for (idx = 0; idx < 8; idx++) { + snd_cs46xx_poke(chip, BA1_DREG, DREG_REGID_TRAP_SELECT + idx); + snd_cs46xx_poke(chip, BA1_TWPR, 0xFFFF); + } + snd_cs46xx_poke(chip, BA1_DREG, 0); + + /* + * Set the frame timer to reflect the number of cycles per frame. + */ + snd_cs46xx_poke(chip, BA1_FRMT, 0xadf); +} + +static int cs46xx_wait_for_fifo(struct snd_cs46xx * chip,int retry_timeout) +{ + u32 i, status = 0; + /* + * Make sure the previous FIFO write operation has completed. + */ + for(i = 0; i < 50; i++){ + status = snd_cs46xx_peekBA0(chip, BA0_SERBST); + + if( !(status & SERBST_WBSY) ) + break; + + mdelay(retry_timeout); + } + + if(status & SERBST_WBSY) { + dev_err(chip->card->dev, + "failure waiting for FIFO command to complete\n"); + return -EINVAL; + } + + return 0; +} + +static void snd_cs46xx_clear_serial_FIFOs(struct snd_cs46xx *chip) +{ + int idx, powerdown = 0; + unsigned int tmp; + + /* + * See if the devices are powered down. If so, we must power them up first + * or they will not respond. + */ + tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1); + if (!(tmp & CLKCR1_SWCE)) { + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp | CLKCR1_SWCE); + powerdown = 1; + } + + /* + * We want to clear out the serial port FIFOs so we don't end up playing + * whatever random garbage happens to be in them. We fill the sample FIFOS + * with zero (silence). + */ + snd_cs46xx_pokeBA0(chip, BA0_SERBWP, 0); + + /* + * Fill all 256 sample FIFO locations. + */ + for (idx = 0; idx < 0xFF; idx++) { + /* + * Make sure the previous FIFO write operation has completed. + */ + if (cs46xx_wait_for_fifo(chip,1)) { + dev_dbg(chip->card->dev, + "failed waiting for FIFO at addr (%02X)\n", + idx); + + if (powerdown) + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp); + + break; + } + /* + * Write the serial port FIFO index. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERBAD, idx); + /* + * Tell the serial port to load the new value into the FIFO location. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERBCM, SERBCM_WRC); + } + /* + * Now, if we powered up the devices, then power them back down again. + * This is kinda ugly, but should never happen. + */ + if (powerdown) + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp); +} + +static void snd_cs46xx_proc_start(struct snd_cs46xx *chip) +{ + int cnt; + + /* + * Set the frame timer to reflect the number of cycles per frame. + */ + snd_cs46xx_poke(chip, BA1_FRMT, 0xadf); + /* + * Turn on the run, run at frame, and DMA enable bits in the local copy of + * the SP control register. + */ + snd_cs46xx_poke(chip, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN); + /* + * Wait until the run at frame bit resets itself in the SP control + * register. + */ + for (cnt = 0; cnt < 25; cnt++) { + udelay(50); + if (!(snd_cs46xx_peek(chip, BA1_SPCR) & SPCR_RUNFR)) + break; + } + + if (snd_cs46xx_peek(chip, BA1_SPCR) & SPCR_RUNFR) + dev_err(chip->card->dev, "SPCR_RUNFR never reset\n"); +} + +static void snd_cs46xx_proc_stop(struct snd_cs46xx *chip) +{ + /* + * Turn off the run, run at frame, and DMA enable bits in the local copy of + * the SP control register. + */ + snd_cs46xx_poke(chip, BA1_SPCR, 0); +} + +/* + * Sample rate routines + */ + +#define GOF_PER_SEC 200 + +static void snd_cs46xx_set_play_sample_rate(struct snd_cs46xx *chip, unsigned int rate) +{ + unsigned int tmp1, tmp2; + unsigned int phiIncr; + unsigned int correctionPerGOF, correctionPerSec; + + /* + * Compute the values used to drive the actual sample rate conversion. + * The following formulas are being computed, using inline assembly + * since we need to use 64 bit arithmetic to compute the values: + * + * phiIncr = floor((Fs,in * 2^26) / Fs,out) + * correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) / + * GOF_PER_SEC) + * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -M + * GOF_PER_SEC * correctionPerGOF + * + * i.e. + * + * phiIncr:other = dividend:remainder((Fs,in * 2^26) / Fs,out) + * correctionPerGOF:correctionPerSec = + * dividend:remainder(ulOther / GOF_PER_SEC) + */ + tmp1 = rate << 16; + phiIncr = tmp1 / 48000; + tmp1 -= phiIncr * 48000; + tmp1 <<= 10; + phiIncr <<= 10; + tmp2 = tmp1 / 48000; + phiIncr += tmp2; + tmp1 -= tmp2 * 48000; + correctionPerGOF = tmp1 / GOF_PER_SEC; + tmp1 -= correctionPerGOF * GOF_PER_SEC; + correctionPerSec = tmp1; + + /* + * Fill in the SampleRateConverter control block. + */ + guard(spinlock_irqsave)(&chip->reg_lock); + snd_cs46xx_poke(chip, BA1_PSRC, + ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF)); + snd_cs46xx_poke(chip, BA1_PPI, phiIncr); +} + +static void snd_cs46xx_set_capture_sample_rate(struct snd_cs46xx *chip, unsigned int rate) +{ + unsigned int phiIncr, coeffIncr, tmp1, tmp2; + unsigned int correctionPerGOF, correctionPerSec, initialDelay; + unsigned int frameGroupLength, cnt; + + /* + * We can only decimate by up to a factor of 1/9th the hardware rate. + * Correct the value if an attempt is made to stray outside that limit. + */ + if ((rate * 9) < 48000) + rate = 48000 / 9; + + /* + * We can not capture at a rate greater than the Input Rate (48000). + * Return an error if an attempt is made to stray outside that limit. + */ + if (rate > 48000) + rate = 48000; + + /* + * Compute the values used to drive the actual sample rate conversion. + * The following formulas are being computed, using inline assembly + * since we need to use 64 bit arithmetic to compute the values: + * + * coeffIncr = -floor((Fs,out * 2^23) / Fs,in) + * phiIncr = floor((Fs,in * 2^26) / Fs,out) + * correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) / + * GOF_PER_SEC) + * correctionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - + * GOF_PER_SEC * correctionPerGOF + * initialDelay = ceil((24 * Fs,in) / Fs,out) + * + * i.e. + * + * coeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in)) + * phiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) + * correctionPerGOF:correctionPerSec = + * dividend:remainder(ulOther / GOF_PER_SEC) + * initialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out) + */ + + tmp1 = rate << 16; + coeffIncr = tmp1 / 48000; + tmp1 -= coeffIncr * 48000; + tmp1 <<= 7; + coeffIncr <<= 7; + coeffIncr += tmp1 / 48000; + coeffIncr ^= 0xFFFFFFFF; + coeffIncr++; + tmp1 = 48000 << 16; + phiIncr = tmp1 / rate; + tmp1 -= phiIncr * rate; + tmp1 <<= 10; + phiIncr <<= 10; + tmp2 = tmp1 / rate; + phiIncr += tmp2; + tmp1 -= tmp2 * rate; + correctionPerGOF = tmp1 / GOF_PER_SEC; + tmp1 -= correctionPerGOF * GOF_PER_SEC; + correctionPerSec = tmp1; + initialDelay = DIV_ROUND_UP(48000 * 24, rate); + + /* + * Fill in the VariDecimate control block. + */ + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + snd_cs46xx_poke(chip, BA1_CSRC, + ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF)); + snd_cs46xx_poke(chip, BA1_CCI, coeffIncr); + snd_cs46xx_poke(chip, BA1_CD, + (((BA1_VARIDEC_BUF_1 + (initialDelay << 2)) << 16) & 0xFFFF0000) | 0x80); + snd_cs46xx_poke(chip, BA1_CPI, phiIncr); + } + + /* + * Figure out the frame group length for the write back task. Basically, + * this is just the factors of 24000 (2^6*3*5^3) that are not present in + * the output sample rate. + */ + frameGroupLength = 1; + for (cnt = 2; cnt <= 64; cnt *= 2) { + if (((rate / cnt) * cnt) != rate) + frameGroupLength *= 2; + } + if (((rate / 3) * 3) != rate) { + frameGroupLength *= 3; + } + for (cnt = 5; cnt <= 125; cnt *= 5) { + if (((rate / cnt) * cnt) != rate) + frameGroupLength *= 5; + } + + /* + * Fill in the WriteBack control block. + */ + guard(spinlock_irqsave)(&chip->reg_lock); + snd_cs46xx_poke(chip, BA1_CFG1, frameGroupLength); + snd_cs46xx_poke(chip, BA1_CFG2, (0x00800000 | frameGroupLength)); + snd_cs46xx_poke(chip, BA1_CCST, 0x0000FFFF); + snd_cs46xx_poke(chip, BA1_CSPB, ((65536 * rate) / 24000)); + snd_cs46xx_poke(chip, (BA1_CSPB + 4), 0x0000FFFF); +} + +/* + * PCM part + */ + +static void snd_cs46xx_pb_trans_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect *rec, size_t bytes) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_cs46xx_pcm * cpcm = runtime->private_data; + memcpy(cpcm->hw_buf.area + rec->hw_data, runtime->dma_area + rec->sw_data, bytes); +} + +static int snd_cs46xx_playback_transfer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_cs46xx_pcm * cpcm = runtime->private_data; + return snd_pcm_indirect_playback_transfer(substream, &cpcm->pcm_rec, + snd_cs46xx_pb_trans_copy); +} + +static void snd_cs46xx_cp_trans_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect *rec, size_t bytes) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + memcpy(runtime->dma_area + rec->sw_data, + chip->capt.hw_buf.area + rec->hw_data, bytes); +} + +static int snd_cs46xx_capture_transfer(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + return snd_pcm_indirect_capture_transfer(substream, &chip->capt.pcm_rec, + snd_cs46xx_cp_trans_copy); +} + +static snd_pcm_uframes_t snd_cs46xx_playback_direct_pointer(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + size_t ptr; + struct snd_cs46xx_pcm *cpcm = substream->runtime->private_data; + + if (snd_BUG_ON(!cpcm->pcm_channel)) + return -ENXIO; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + ptr = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 2) << 2); +#else + ptr = snd_cs46xx_peek(chip, BA1_PBA); +#endif + ptr -= cpcm->hw_buf.addr; + return ptr >> cpcm->shift; +} + +static snd_pcm_uframes_t snd_cs46xx_playback_indirect_pointer(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + size_t ptr; + struct snd_cs46xx_pcm *cpcm = substream->runtime->private_data; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (snd_BUG_ON(!cpcm->pcm_channel)) + return -ENXIO; + ptr = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 2) << 2); +#else + ptr = snd_cs46xx_peek(chip, BA1_PBA); +#endif + ptr -= cpcm->hw_buf.addr; + return snd_pcm_indirect_playback_pointer(substream, &cpcm->pcm_rec, ptr); +} + +static snd_pcm_uframes_t snd_cs46xx_capture_direct_pointer(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + size_t ptr = snd_cs46xx_peek(chip, BA1_CBA) - chip->capt.hw_buf.addr; + return ptr >> chip->capt.shift; +} + +static snd_pcm_uframes_t snd_cs46xx_capture_indirect_pointer(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + size_t ptr = snd_cs46xx_peek(chip, BA1_CBA) - chip->capt.hw_buf.addr; + return snd_pcm_indirect_capture_pointer(substream, &chip->capt.pcm_rec, ptr); +} + +static int snd_cs46xx_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + /*struct snd_pcm_runtime *runtime = substream->runtime;*/ + int result = 0; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + struct snd_cs46xx_pcm *cpcm = substream->runtime->private_data; + if (! cpcm->pcm_channel) { + return -ENXIO; + } +#endif + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: +#ifdef CONFIG_SND_CS46XX_NEW_DSP + /* magic value to unmute PCM stream playback volume */ + snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + + SCBVolumeCtrl) << 2, 0x80008000); + + if (cpcm->pcm_channel->unlinked) + cs46xx_dsp_pcm_link(chip,cpcm->pcm_channel); + + if (substream->runtime->periods != CS46XX_FRAGS) + snd_cs46xx_playback_transfer(substream); +#else + scoped_guard(spinlock, &chip->reg_lock) { + unsigned int tmp; + if (substream->runtime->periods != CS46XX_FRAGS) + snd_cs46xx_playback_transfer(substream); + tmp = snd_cs46xx_peek(chip, BA1_PCTL); + tmp &= 0x0000ffff; + snd_cs46xx_poke(chip, BA1_PCTL, chip->play_ctl | tmp); + } +#endif + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: +#ifdef CONFIG_SND_CS46XX_NEW_DSP + /* magic mute channel */ + snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + + SCBVolumeCtrl) << 2, 0xffffffff); + + if (!cpcm->pcm_channel->unlinked) + cs46xx_dsp_pcm_unlink(chip,cpcm->pcm_channel); +#else + scoped_guard(spinlock, &chip->reg_lock) { + unsigned int tmp; + tmp = snd_cs46xx_peek(chip, BA1_PCTL); + tmp &= 0x0000ffff; + snd_cs46xx_poke(chip, BA1_PCTL, tmp); + } +#endif + break; + default: + result = -EINVAL; + break; + } + + return result; +} + +static int snd_cs46xx_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + unsigned int tmp; + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + tmp = snd_cs46xx_peek(chip, BA1_CCTL); + tmp &= 0xffff0000; + snd_cs46xx_poke(chip, BA1_CCTL, chip->capt.ctl | tmp); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + tmp = snd_cs46xx_peek(chip, BA1_CCTL); + tmp &= 0xffff0000; + snd_cs46xx_poke(chip, BA1_CCTL, tmp); + break; + default: + return -EINVAL; + } + return 0; +} + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +static int _cs46xx_adjust_sample_rate (struct snd_cs46xx *chip, struct snd_cs46xx_pcm *cpcm, + int sample_rate) +{ + + /* If PCMReaderSCB and SrcTaskSCB not created yet ... */ + if ( cpcm->pcm_channel == NULL) { + cpcm->pcm_channel = cs46xx_dsp_create_pcm_channel (chip, sample_rate, + cpcm, cpcm->hw_buf.addr,cpcm->pcm_channel_id); + if (cpcm->pcm_channel == NULL) { + dev_err(chip->card->dev, + "failed to create virtual PCM channel\n"); + return -ENOMEM; + } + cpcm->pcm_channel->sample_rate = sample_rate; + } else + /* if sample rate is changed */ + if ((int)cpcm->pcm_channel->sample_rate != sample_rate) { + int unlinked = cpcm->pcm_channel->unlinked; + cs46xx_dsp_destroy_pcm_channel (chip,cpcm->pcm_channel); + + cpcm->pcm_channel = cs46xx_dsp_create_pcm_channel(chip, sample_rate, cpcm, + cpcm->hw_buf.addr, + cpcm->pcm_channel_id); + if (!cpcm->pcm_channel) { + dev_err(chip->card->dev, + "failed to re-create virtual PCM channel\n"); + return -ENOMEM; + } + + if (!unlinked) cs46xx_dsp_pcm_link (chip,cpcm->pcm_channel); + cpcm->pcm_channel->sample_rate = sample_rate; + } + + return 0; +} +#endif + + +static int snd_cs46xx_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_cs46xx_pcm *cpcm; + int err; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + int sample_rate = params_rate(hw_params); + int period_size = params_period_bytes(hw_params); +#endif + cpcm = runtime->private_data; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (snd_BUG_ON(!sample_rate)) + return -ENXIO; + + guard(mutex)(&chip->spos_mutex); + + if (_cs46xx_adjust_sample_rate(chip, cpcm, sample_rate)) + return -ENXIO; + + snd_BUG_ON(!cpcm->pcm_channel); + if (!cpcm->pcm_channel) + return -ENXIO; + + if (cs46xx_dsp_pcm_channel_set_period(chip, cpcm->pcm_channel, period_size)) + return -EINVAL; + + dev_dbg(chip->card->dev, + "period_size (%d), periods (%d) buffer_size(%d)\n", + period_size, params_periods(hw_params), + params_buffer_bytes(hw_params)); +#endif + + if (params_periods(hw_params) == CS46XX_FRAGS) { + if (runtime->dma_area != cpcm->hw_buf.area) + snd_pcm_lib_free_pages(substream); + snd_pcm_set_runtime_buffer(substream, &cpcm->hw_buf); + + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (cpcm->pcm_channel_id == DSP_PCM_MAIN_CHANNEL) { + substream->ops = &snd_cs46xx_playback_ops; + } else if (cpcm->pcm_channel_id == DSP_PCM_REAR_CHANNEL) { + substream->ops = &snd_cs46xx_playback_rear_ops; + } else if (cpcm->pcm_channel_id == DSP_PCM_CENTER_LFE_CHANNEL) { + substream->ops = &snd_cs46xx_playback_clfe_ops; + } else if (cpcm->pcm_channel_id == DSP_IEC958_CHANNEL) { + substream->ops = &snd_cs46xx_playback_iec958_ops; + } else { + snd_BUG(); + } +#else + substream->ops = &snd_cs46xx_playback_ops; +#endif + + } else { + if (runtime->dma_area == cpcm->hw_buf.area) + snd_pcm_set_runtime_buffer(substream, NULL); + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (err < 0) + return err; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (cpcm->pcm_channel_id == DSP_PCM_MAIN_CHANNEL) { + substream->ops = &snd_cs46xx_playback_indirect_ops; + } else if (cpcm->pcm_channel_id == DSP_PCM_REAR_CHANNEL) { + substream->ops = &snd_cs46xx_playback_indirect_rear_ops; + } else if (cpcm->pcm_channel_id == DSP_PCM_CENTER_LFE_CHANNEL) { + substream->ops = &snd_cs46xx_playback_indirect_clfe_ops; + } else if (cpcm->pcm_channel_id == DSP_IEC958_CHANNEL) { + substream->ops = &snd_cs46xx_playback_indirect_iec958_ops; + } else { + snd_BUG(); + } +#else + substream->ops = &snd_cs46xx_playback_indirect_ops; +#endif + + } + + return 0; +} + +static int snd_cs46xx_playback_hw_free(struct snd_pcm_substream *substream) +{ + /*struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);*/ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_cs46xx_pcm *cpcm; + + cpcm = runtime->private_data; + + /* if play_back open fails, then this function + is called and cpcm can actually be NULL here */ + if (!cpcm) return -ENXIO; + + if (runtime->dma_area != cpcm->hw_buf.area) + snd_pcm_lib_free_pages(substream); + + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static int snd_cs46xx_playback_prepare(struct snd_pcm_substream *substream) +{ + unsigned int tmp; + unsigned int pfie; + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_cs46xx_pcm *cpcm; + + cpcm = runtime->private_data; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (snd_BUG_ON(!cpcm->pcm_channel)) + return -ENXIO; + + pfie = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 1) << 2 ); + pfie &= ~0x0000f03f; +#else + /* old dsp */ + pfie = snd_cs46xx_peek(chip, BA1_PFIE); + pfie &= ~0x0000f03f; +#endif + + cpcm->shift = 2; + /* if to convert from stereo to mono */ + if (runtime->channels == 1) { + cpcm->shift--; + pfie |= 0x00002000; + } + /* if to convert from 8 bit to 16 bit */ + if (snd_pcm_format_width(runtime->format) == 8) { + cpcm->shift--; + pfie |= 0x00001000; + } + /* if to convert to unsigned */ + if (snd_pcm_format_unsigned(runtime->format)) + pfie |= 0x00008000; + + /* Never convert byte order when sample stream is 8 bit */ + if (snd_pcm_format_width(runtime->format) != 8) { + /* convert from big endian to little endian */ + if (snd_pcm_format_big_endian(runtime->format)) + pfie |= 0x00004000; + } + + memset(&cpcm->pcm_rec, 0, sizeof(cpcm->pcm_rec)); + cpcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream); + cpcm->pcm_rec.hw_buffer_size = runtime->period_size * CS46XX_FRAGS << cpcm->shift; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + + tmp = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address) << 2); + tmp &= ~0x000003ff; + tmp |= (4 << cpcm->shift) - 1; + /* playback transaction count register */ + snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address) << 2, tmp); + + /* playback format && interrupt enable */ + snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 1) << 2, pfie | cpcm->pcm_channel->pcm_slot); +#else + snd_cs46xx_poke(chip, BA1_PBA, cpcm->hw_buf.addr); + tmp = snd_cs46xx_peek(chip, BA1_PDTC); + tmp &= ~0x000003ff; + tmp |= (4 << cpcm->shift) - 1; + snd_cs46xx_poke(chip, BA1_PDTC, tmp); + snd_cs46xx_poke(chip, BA1_PFIE, pfie); + snd_cs46xx_set_play_sample_rate(chip, runtime->rate); +#endif + + return 0; +} + +static int snd_cs46xx_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + cs46xx_dsp_pcm_ostream_set_period (chip, params_period_bytes(hw_params)); +#endif + if (runtime->periods == CS46XX_FRAGS) { + if (runtime->dma_area != chip->capt.hw_buf.area) + snd_pcm_lib_free_pages(substream); + snd_pcm_set_runtime_buffer(substream, &chip->capt.hw_buf); + substream->ops = &snd_cs46xx_capture_ops; + } else { + if (runtime->dma_area == chip->capt.hw_buf.area) + snd_pcm_set_runtime_buffer(substream, NULL); + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (err < 0) + return err; + substream->ops = &snd_cs46xx_capture_indirect_ops; + } + + return 0; +} + +static int snd_cs46xx_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (runtime->dma_area != chip->capt.hw_buf.area) + snd_pcm_lib_free_pages(substream); + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static int snd_cs46xx_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_cs46xx_poke(chip, BA1_CBA, chip->capt.hw_buf.addr); + chip->capt.shift = 2; + memset(&chip->capt.pcm_rec, 0, sizeof(chip->capt.pcm_rec)); + chip->capt.pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream); + chip->capt.pcm_rec.hw_buffer_size = runtime->period_size * CS46XX_FRAGS << 2; + snd_cs46xx_set_capture_sample_rate(chip, runtime->rate); + + return 0; +} + +static irqreturn_t snd_cs46xx_interrupt(int irq, void *dev_id) +{ + struct snd_cs46xx *chip = dev_id; + u32 status1; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + u32 status2; + int i; + struct snd_cs46xx_pcm *cpcm = NULL; +#endif + + /* + * Read the Interrupt Status Register to clear the interrupt + */ + status1 = snd_cs46xx_peekBA0(chip, BA0_HISR); + if ((status1 & 0x7fffffff) == 0) { + snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_CHGM | HICR_IEV); + return IRQ_NONE; + } + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + status2 = snd_cs46xx_peekBA0(chip, BA0_HSR0); + + for (i = 0; i < DSP_MAX_PCM_CHANNELS; ++i) { + if (i <= 15) { + if ( status1 & (1 << i) ) { + if (i == CS46XX_DSP_CAPTURE_CHANNEL) { + if (chip->capt.substream) + snd_pcm_period_elapsed(chip->capt.substream); + } else { + if (ins->pcm_channels[i].active && + ins->pcm_channels[i].private_data && + !ins->pcm_channels[i].unlinked) { + cpcm = ins->pcm_channels[i].private_data; + snd_pcm_period_elapsed(cpcm->substream); + } + } + } + } else { + if ( status2 & (1 << (i - 16))) { + if (ins->pcm_channels[i].active && + ins->pcm_channels[i].private_data && + !ins->pcm_channels[i].unlinked) { + cpcm = ins->pcm_channels[i].private_data; + snd_pcm_period_elapsed(cpcm->substream); + } + } + } + } + +#else + /* old dsp */ + if ((status1 & HISR_VC0) && chip->playback_pcm) { + if (chip->playback_pcm->substream) + snd_pcm_period_elapsed(chip->playback_pcm->substream); + } + if ((status1 & HISR_VC1) && chip->pcm) { + if (chip->capt.substream) + snd_pcm_period_elapsed(chip->capt.substream); + } +#endif + + if ((status1 & HISR_MIDI) && chip->rmidi) { + unsigned char c; + + guard(spinlock)(&chip->reg_lock); + while ((snd_cs46xx_peekBA0(chip, BA0_MIDSR) & MIDSR_RBE) == 0) { + c = snd_cs46xx_peekBA0(chip, BA0_MIDRP); + if ((chip->midcr & MIDCR_RIE) == 0) + continue; + snd_rawmidi_receive(chip->midi_input, &c, 1); + } + while ((snd_cs46xx_peekBA0(chip, BA0_MIDSR) & MIDSR_TBF) == 0) { + if ((chip->midcr & MIDCR_TIE) == 0) + break; + if (snd_rawmidi_transmit(chip->midi_output, &c, 1) != 1) { + chip->midcr &= ~MIDCR_TIE; + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + break; + } + snd_cs46xx_pokeBA0(chip, BA0_MIDWP, c); + } + } + /* + * EOI to the PCI part....reenables interrupts + */ + snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_CHGM | HICR_IEV); + + return IRQ_HANDLED; +} + +static const struct snd_pcm_hardware snd_cs46xx_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER /*|*/ + /*SNDRV_PCM_INFO_RESUME*/ | + SNDRV_PCM_INFO_SYNC_APPLPTR), + .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5500, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (256 * 1024), + .period_bytes_min = CS46XX_MIN_PERIOD_SIZE, + .period_bytes_max = CS46XX_MAX_PERIOD_SIZE, + .periods_min = CS46XX_FRAGS, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_cs46xx_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER /*|*/ + /*SNDRV_PCM_INFO_RESUME*/ | + SNDRV_PCM_INFO_SYNC_APPLPTR), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5500, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (256 * 1024), + .period_bytes_min = CS46XX_MIN_PERIOD_SIZE, + .period_bytes_max = CS46XX_MAX_PERIOD_SIZE, + .periods_min = CS46XX_FRAGS, + .periods_max = 1024, + .fifo_size = 0, +}; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + +static const unsigned int period_sizes[] = { 32, 64, 128, 256, 512, 1024, 2048 }; + +static const struct snd_pcm_hw_constraint_list hw_constraints_period_sizes = { + .count = ARRAY_SIZE(period_sizes), + .list = period_sizes, + .mask = 0 +}; + +#endif + +static void snd_cs46xx_pcm_free_substream(struct snd_pcm_runtime *runtime) +{ + kfree(runtime->private_data); +} + +static int _cs46xx_playback_open_channel (struct snd_pcm_substream *substream,int pcm_channel_id) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + struct snd_cs46xx_pcm * cpcm; + struct snd_pcm_runtime *runtime = substream->runtime; + + cpcm = kzalloc(sizeof(*cpcm), GFP_KERNEL); + if (cpcm == NULL) + return -ENOMEM; + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + PAGE_SIZE, &cpcm->hw_buf) < 0) { + kfree(cpcm); + return -ENOMEM; + } + + runtime->hw = snd_cs46xx_playback; + runtime->private_data = cpcm; + runtime->private_free = snd_cs46xx_pcm_free_substream; + + cpcm->substream = substream; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + scoped_guard(mutex, &chip->spos_mutex) { + cpcm->pcm_channel = NULL; + cpcm->pcm_channel_id = pcm_channel_id; + } + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &hw_constraints_period_sizes); +#else + chip->playback_pcm = cpcm; /* HACK */ +#endif + + if (chip->accept_valid) + substream->runtime->hw.info |= SNDRV_PCM_INFO_MMAP_VALID; + chip->active_ctrl(chip, 1); + + return 0; +} + +static int snd_cs46xx_playback_open(struct snd_pcm_substream *substream) +{ + dev_dbg(substream->pcm->card->dev, "open front channel\n"); + return _cs46xx_playback_open_channel(substream,DSP_PCM_MAIN_CHANNEL); +} + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +static int snd_cs46xx_playback_open_rear(struct snd_pcm_substream *substream) +{ + dev_dbg(substream->pcm->card->dev, "open rear channel\n"); + return _cs46xx_playback_open_channel(substream,DSP_PCM_REAR_CHANNEL); +} + +static int snd_cs46xx_playback_open_clfe(struct snd_pcm_substream *substream) +{ + dev_dbg(substream->pcm->card->dev, "open center - LFE channel\n"); + return _cs46xx_playback_open_channel(substream,DSP_PCM_CENTER_LFE_CHANNEL); +} + +static int snd_cs46xx_playback_open_iec958(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + + dev_dbg(chip->card->dev, "open raw iec958 channel\n"); + + scoped_guard(mutex, &chip->spos_mutex) { + cs46xx_iec958_pre_open(chip); + } + + return _cs46xx_playback_open_channel(substream,DSP_IEC958_CHANNEL); +} + +static int snd_cs46xx_playback_close(struct snd_pcm_substream *substream); + +static int snd_cs46xx_playback_close_iec958(struct snd_pcm_substream *substream) +{ + int err; + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + + dev_dbg(chip->card->dev, "close raw iec958 channel\n"); + + err = snd_cs46xx_playback_close(substream); + + scoped_guard(mutex, &chip->spos_mutex) { + cs46xx_iec958_post_close(chip); + } + + return err; +} +#endif + +static int snd_cs46xx_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + PAGE_SIZE, &chip->capt.hw_buf) < 0) + return -ENOMEM; + chip->capt.substream = substream; + substream->runtime->hw = snd_cs46xx_capture; + + if (chip->accept_valid) + substream->runtime->hw.info |= SNDRV_PCM_INFO_MMAP_VALID; + + chip->active_ctrl(chip, 1); + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &hw_constraints_period_sizes); +#endif + return 0; +} + +static int snd_cs46xx_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_cs46xx_pcm * cpcm; + + cpcm = runtime->private_data; + + /* when playback_open fails, then cpcm can be NULL */ + if (!cpcm) return -ENXIO; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + scoped_guard(mutex, &chip->spos_mutex) { + if (cpcm->pcm_channel) { + cs46xx_dsp_destroy_pcm_channel(chip, cpcm->pcm_channel); + cpcm->pcm_channel = NULL; + } + } +#else + chip->playback_pcm = NULL; +#endif + + cpcm->substream = NULL; + snd_dma_free_pages(&cpcm->hw_buf); + chip->active_ctrl(chip, -1); + + return 0; +} + +static int snd_cs46xx_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_cs46xx *chip = snd_pcm_substream_chip(substream); + + chip->capt.substream = NULL; + snd_dma_free_pages(&chip->capt.hw_buf); + chip->active_ctrl(chip, -1); + + return 0; +} + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +static const struct snd_pcm_ops snd_cs46xx_playback_rear_ops = { + .open = snd_cs46xx_playback_open_rear, + .close = snd_cs46xx_playback_close, + .hw_params = snd_cs46xx_playback_hw_params, + .hw_free = snd_cs46xx_playback_hw_free, + .prepare = snd_cs46xx_playback_prepare, + .trigger = snd_cs46xx_playback_trigger, + .pointer = snd_cs46xx_playback_direct_pointer, +}; + +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_rear_ops = { + .open = snd_cs46xx_playback_open_rear, + .close = snd_cs46xx_playback_close, + .hw_params = snd_cs46xx_playback_hw_params, + .hw_free = snd_cs46xx_playback_hw_free, + .prepare = snd_cs46xx_playback_prepare, + .trigger = snd_cs46xx_playback_trigger, + .pointer = snd_cs46xx_playback_indirect_pointer, + .ack = snd_cs46xx_playback_transfer, +}; + +static const struct snd_pcm_ops snd_cs46xx_playback_clfe_ops = { + .open = snd_cs46xx_playback_open_clfe, + .close = snd_cs46xx_playback_close, + .hw_params = snd_cs46xx_playback_hw_params, + .hw_free = snd_cs46xx_playback_hw_free, + .prepare = snd_cs46xx_playback_prepare, + .trigger = snd_cs46xx_playback_trigger, + .pointer = snd_cs46xx_playback_direct_pointer, +}; + +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_clfe_ops = { + .open = snd_cs46xx_playback_open_clfe, + .close = snd_cs46xx_playback_close, + .hw_params = snd_cs46xx_playback_hw_params, + .hw_free = snd_cs46xx_playback_hw_free, + .prepare = snd_cs46xx_playback_prepare, + .trigger = snd_cs46xx_playback_trigger, + .pointer = snd_cs46xx_playback_indirect_pointer, + .ack = snd_cs46xx_playback_transfer, +}; + +static const struct snd_pcm_ops snd_cs46xx_playback_iec958_ops = { + .open = snd_cs46xx_playback_open_iec958, + .close = snd_cs46xx_playback_close_iec958, + .hw_params = snd_cs46xx_playback_hw_params, + .hw_free = snd_cs46xx_playback_hw_free, + .prepare = snd_cs46xx_playback_prepare, + .trigger = snd_cs46xx_playback_trigger, + .pointer = snd_cs46xx_playback_direct_pointer, +}; + +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_iec958_ops = { + .open = snd_cs46xx_playback_open_iec958, + .close = snd_cs46xx_playback_close_iec958, + .hw_params = snd_cs46xx_playback_hw_params, + .hw_free = snd_cs46xx_playback_hw_free, + .prepare = snd_cs46xx_playback_prepare, + .trigger = snd_cs46xx_playback_trigger, + .pointer = snd_cs46xx_playback_indirect_pointer, + .ack = snd_cs46xx_playback_transfer, +}; + +#endif + +static const struct snd_pcm_ops snd_cs46xx_playback_ops = { + .open = snd_cs46xx_playback_open, + .close = snd_cs46xx_playback_close, + .hw_params = snd_cs46xx_playback_hw_params, + .hw_free = snd_cs46xx_playback_hw_free, + .prepare = snd_cs46xx_playback_prepare, + .trigger = snd_cs46xx_playback_trigger, + .pointer = snd_cs46xx_playback_direct_pointer, +}; + +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_ops = { + .open = snd_cs46xx_playback_open, + .close = snd_cs46xx_playback_close, + .hw_params = snd_cs46xx_playback_hw_params, + .hw_free = snd_cs46xx_playback_hw_free, + .prepare = snd_cs46xx_playback_prepare, + .trigger = snd_cs46xx_playback_trigger, + .pointer = snd_cs46xx_playback_indirect_pointer, + .ack = snd_cs46xx_playback_transfer, +}; + +static const struct snd_pcm_ops snd_cs46xx_capture_ops = { + .open = snd_cs46xx_capture_open, + .close = snd_cs46xx_capture_close, + .hw_params = snd_cs46xx_capture_hw_params, + .hw_free = snd_cs46xx_capture_hw_free, + .prepare = snd_cs46xx_capture_prepare, + .trigger = snd_cs46xx_capture_trigger, + .pointer = snd_cs46xx_capture_direct_pointer, +}; + +static const struct snd_pcm_ops snd_cs46xx_capture_indirect_ops = { + .open = snd_cs46xx_capture_open, + .close = snd_cs46xx_capture_close, + .hw_params = snd_cs46xx_capture_hw_params, + .hw_free = snd_cs46xx_capture_hw_free, + .prepare = snd_cs46xx_capture_prepare, + .trigger = snd_cs46xx_capture_trigger, + .pointer = snd_cs46xx_capture_indirect_pointer, + .ack = snd_cs46xx_capture_transfer, +}; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +#define MAX_PLAYBACK_CHANNELS (DSP_MAX_PCM_CHANNELS - 1) +#else +#define MAX_PLAYBACK_CHANNELS 1 +#endif + +int snd_cs46xx_pcm(struct snd_cs46xx *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "CS46xx", device, MAX_PLAYBACK_CHANNELS, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cs46xx_capture_ops); + + /* global setup */ + pcm->info_flags = 0; + strscpy(pcm->name, "CS46xx"); + chip->pcm = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64*1024, 256*1024); + + return 0; +} + + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +int snd_cs46xx_pcm_rear(struct snd_cs46xx *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "CS46xx - Rear", device, MAX_PLAYBACK_CHANNELS, 0, &pcm); + if (err < 0) + return err; + + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_rear_ops); + + /* global setup */ + pcm->info_flags = 0; + strscpy(pcm->name, "CS46xx - Rear"); + chip->pcm_rear = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64*1024, 256*1024); + + return 0; +} + +int snd_cs46xx_pcm_center_lfe(struct snd_cs46xx *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "CS46xx - Center LFE", device, MAX_PLAYBACK_CHANNELS, 0, &pcm); + if (err < 0) + return err; + + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_clfe_ops); + + /* global setup */ + pcm->info_flags = 0; + strscpy(pcm->name, "CS46xx - Center LFE"); + chip->pcm_center_lfe = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64*1024, 256*1024); + + return 0; +} + +int snd_cs46xx_pcm_iec958(struct snd_cs46xx *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "CS46xx - IEC958", device, 1, 0, &pcm); + if (err < 0) + return err; + + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_iec958_ops); + + /* global setup */ + pcm->info_flags = 0; + strscpy(pcm->name, "CS46xx - IEC958"); + chip->pcm_iec958 = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + 64*1024, 256*1024); + + return 0; +} +#endif + +/* + * Mixer routines + */ +static void snd_cs46xx_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct snd_cs46xx *chip = ac97->private_data; + + if (snd_BUG_ON(ac97 != chip->ac97[CS46XX_PRIMARY_CODEC_INDEX] && + ac97 != chip->ac97[CS46XX_SECONDARY_CODEC_INDEX])) + return; + + if (ac97 == chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]) { + chip->ac97[CS46XX_PRIMARY_CODEC_INDEX] = NULL; + chip->eapd_switch = NULL; + } + else + chip->ac97[CS46XX_SECONDARY_CODEC_INDEX] = NULL; +} + +static int snd_cs46xx_vol_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 = 0x7fff; + return 0; +} + +static int snd_cs46xx_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value; + unsigned int val = snd_cs46xx_peek(chip, reg); + ucontrol->value.integer.value[0] = 0xffff - (val >> 16); + ucontrol->value.integer.value[1] = 0xffff - (val & 0xffff); + return 0; +} + +static int snd_cs46xx_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value; + unsigned int val = ((0xffff - ucontrol->value.integer.value[0]) << 16 | + (0xffff - ucontrol->value.integer.value[1])); + unsigned int old = snd_cs46xx_peek(chip, reg); + int change = (old != val); + + if (change) { + snd_cs46xx_poke(chip, reg, val); + } + + return change; +} + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + +static int snd_cs46xx_vol_dac_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = chip->dsp_spos_instance->dac_volume_left; + ucontrol->value.integer.value[1] = chip->dsp_spos_instance->dac_volume_right; + + return 0; +} + +static int snd_cs46xx_vol_dac_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + int change = 0; + + if (chip->dsp_spos_instance->dac_volume_right != ucontrol->value.integer.value[0] || + chip->dsp_spos_instance->dac_volume_left != ucontrol->value.integer.value[1]) { + cs46xx_dsp_set_dac_volume(chip, + ucontrol->value.integer.value[0], + ucontrol->value.integer.value[1]); + change = 1; + } + + return change; +} + +#if 0 +static int snd_cs46xx_vol_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = chip->dsp_spos_instance->spdif_input_volume_left; + ucontrol->value.integer.value[1] = chip->dsp_spos_instance->spdif_input_volume_right; + return 0; +} + +static int snd_cs46xx_vol_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + int change = 0; + + if (chip->dsp_spos_instance->spdif_input_volume_left != ucontrol->value.integer.value[0] || + chip->dsp_spos_instance->spdif_input_volume_right!= ucontrol->value.integer.value[1]) { + cs46xx_dsp_set_iec958_volume (chip, + ucontrol->value.integer.value[0], + ucontrol->value.integer.value[1]); + change = 1; + } + + return change; +} +#endif + +#define snd_mixer_boolean_info snd_ctl_boolean_mono_info + +static int snd_cs46xx_iec958_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value; + + if (reg == CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT) + ucontrol->value.integer.value[0] = (chip->dsp_spos_instance->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED); + else + ucontrol->value.integer.value[0] = chip->dsp_spos_instance->spdif_status_in; + + return 0; +} + +static int snd_cs46xx_iec958_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + int change, res; + + switch (kcontrol->private_value) { + case CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT: + scoped_guard(mutex, &chip->spos_mutex) { + change = (chip->dsp_spos_instance->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED); + if (ucontrol->value.integer.value[0] && !change) + cs46xx_dsp_enable_spdif_out(chip); + else if (change && !ucontrol->value.integer.value[0]) + cs46xx_dsp_disable_spdif_out(chip); + + res = (change != (chip->dsp_spos_instance->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED)); + } + break; + case CS46XX_MIXER_SPDIF_INPUT_ELEMENT: + change = chip->dsp_spos_instance->spdif_status_in; + if (ucontrol->value.integer.value[0] && !change) { + cs46xx_dsp_enable_spdif_in(chip); + /* restore volume */ + } + else if (change && !ucontrol->value.integer.value[0]) + cs46xx_dsp_disable_spdif_in(chip); + + res = (change != chip->dsp_spos_instance->spdif_status_in); + break; + default: + res = -EINVAL; + snd_BUG(); /* should never happen ... */ + } + + return res; +} + +static int snd_cs46xx_adc_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (ins->adc_input != NULL) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int snd_cs46xx_adc_capture_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int change = 0; + + if (ucontrol->value.integer.value[0] && !ins->adc_input) { + cs46xx_dsp_enable_adc_capture(chip); + change = 1; + } else if (!ucontrol->value.integer.value[0] && ins->adc_input) { + cs46xx_dsp_disable_adc_capture(chip); + change = 1; + } + return change; +} + +static int snd_cs46xx_pcm_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (ins->pcm_input != NULL) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + + +static int snd_cs46xx_pcm_capture_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int change = 0; + + if (ucontrol->value.integer.value[0] && !ins->pcm_input) { + cs46xx_dsp_enable_pcm_capture(chip); + change = 1; + } else if (!ucontrol->value.integer.value[0] && ins->pcm_input) { + cs46xx_dsp_disable_pcm_capture(chip); + change = 1; + } + + return change; +} + +static int snd_herc_spdif_select_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + + int val1 = snd_cs46xx_peekBA0(chip, BA0_EGPIODR); + + if (val1 & EGPIODR_GPOE0) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +/* + * Game Theatre XP card - EGPIO[0] is used to select SPDIF input optical or coaxial. + */ +static int snd_herc_spdif_select_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + int val1 = snd_cs46xx_peekBA0(chip, BA0_EGPIODR); + int val2 = snd_cs46xx_peekBA0(chip, BA0_EGPIOPTR); + + if (ucontrol->value.integer.value[0]) { + /* optical is default */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, + EGPIODR_GPOE0 | val1); /* enable EGPIO0 output */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, + EGPIOPTR_GPPT0 | val2); /* open-drain on output */ + } else { + /* coaxial */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, val1 & ~EGPIODR_GPOE0); /* disable */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, val2 & ~EGPIOPTR_GPPT0); /* disable */ + } + + /* checking diff from the EGPIO direction register + should be enough */ + return (val1 != (int)snd_cs46xx_peekBA0(chip, BA0_EGPIODR)); +} + + +static int snd_cs46xx_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_cs46xx_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + guard(mutex)(&chip->spos_mutex); + ucontrol->value.iec958.status[0] = _wrap_all_bits((ins->spdif_csuv_default >> 24) & 0xff); + ucontrol->value.iec958.status[1] = _wrap_all_bits((ins->spdif_csuv_default >> 16) & 0xff); + ucontrol->value.iec958.status[2] = 0; + ucontrol->value.iec958.status[3] = _wrap_all_bits((ins->spdif_csuv_default) & 0xff); + + return 0; +} + +static int snd_cs46xx_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx * chip = snd_kcontrol_chip(kcontrol); + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + unsigned int val; + int change; + + guard(mutex)(&chip->spos_mutex); + val = ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[0]) << 24) | + ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[2]) << 16) | + ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[3])) | + /* left and right validity bit */ + (1 << 13) | (1 << 12); + + + change = (unsigned int)ins->spdif_csuv_default != val; + ins->spdif_csuv_default = val; + + if ( !(ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) ) + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV,val); + + return change; +} + +static int snd_cs46xx_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0x00; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static int snd_cs46xx_spdif_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + guard(mutex)(&chip->spos_mutex); + ucontrol->value.iec958.status[0] = _wrap_all_bits((ins->spdif_csuv_stream >> 24) & 0xff); + ucontrol->value.iec958.status[1] = _wrap_all_bits((ins->spdif_csuv_stream >> 16) & 0xff); + ucontrol->value.iec958.status[2] = 0; + ucontrol->value.iec958.status[3] = _wrap_all_bits((ins->spdif_csuv_stream) & 0xff); + + return 0; +} + +static int snd_cs46xx_spdif_stream_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx * chip = snd_kcontrol_chip(kcontrol); + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + unsigned int val; + int change; + + guard(mutex)(&chip->spos_mutex); + val = ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[0]) << 24) | + ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[1]) << 16) | + ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[3])) | + /* left and right validity bit */ + (1 << 13) | (1 << 12); + + + change = ins->spdif_csuv_stream != val; + ins->spdif_csuv_stream = val; + + if ( ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN ) + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV,val); + + return change; +} + +#endif /* CONFIG_SND_CS46XX_NEW_DSP */ + + +static const struct snd_kcontrol_new snd_cs46xx_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Volume", + .info = snd_cs46xx_vol_info, +#ifndef CONFIG_SND_CS46XX_NEW_DSP + .get = snd_cs46xx_vol_get, + .put = snd_cs46xx_vol_put, + .private_value = BA1_PVOL, +#else + .get = snd_cs46xx_vol_dac_get, + .put = snd_cs46xx_vol_dac_put, +#endif +}, + +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Volume", + .info = snd_cs46xx_vol_info, + .get = snd_cs46xx_vol_get, + .put = snd_cs46xx_vol_put, +#ifndef CONFIG_SND_CS46XX_NEW_DSP + .private_value = BA1_CVOL, +#else + .private_value = (VARIDECIMATE_SCB_ADDR + 0xE) << 2, +#endif +}, +#ifdef CONFIG_SND_CS46XX_NEW_DSP +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Capture Switch", + .info = snd_mixer_boolean_info, + .get = snd_cs46xx_adc_capture_get, + .put = snd_cs46xx_adc_capture_put +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Capture Switch", + .info = snd_mixer_boolean_info, + .get = snd_cs46xx_pcm_capture_get, + .put = snd_cs46xx_pcm_capture_put +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH), + .info = snd_mixer_boolean_info, + .get = snd_cs46xx_iec958_get, + .put = snd_cs46xx_iec958_put, + .private_value = CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("Input ",NONE,SWITCH), + .info = snd_mixer_boolean_info, + .get = snd_cs46xx_iec958_get, + .put = snd_cs46xx_iec958_put, + .private_value = CS46XX_MIXER_SPDIF_INPUT_ELEMENT, +}, +#if 0 +/* Input IEC958 volume does not work for the moment. (Benny) */ +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("Input ",NONE,VOLUME), + .info = snd_cs46xx_vol_info, + .get = snd_cs46xx_vol_iec958_get, + .put = snd_cs46xx_vol_iec958_put, + .private_value = (ASYNCRX_SCB_ADDR + 0xE) << 2, +}, +#endif +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_cs46xx_spdif_info, + .get = snd_cs46xx_spdif_default_get, + .put = snd_cs46xx_spdif_default_put, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = snd_cs46xx_spdif_info, + .get = snd_cs46xx_spdif_mask_get, + .access = SNDRV_CTL_ELEM_ACCESS_READ +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .info = snd_cs46xx_spdif_info, + .get = snd_cs46xx_spdif_stream_get, + .put = snd_cs46xx_spdif_stream_put +}, + +#endif +}; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +/* set primary cs4294 codec into Extended Audio Mode */ +static int snd_cs46xx_front_dup_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + unsigned short val; + val = snd_ac97_read(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX], AC97_CSR_ACMODE); + ucontrol->value.integer.value[0] = (val & 0x200) ? 0 : 1; + return 0; +} + +static int snd_cs46xx_front_dup_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol); + return snd_ac97_update_bits(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX], + AC97_CSR_ACMODE, 0x200, + ucontrol->value.integer.value[0] ? 0 : 0x200); +} + +static const struct snd_kcontrol_new snd_cs46xx_front_dup_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Duplicate Front", + .info = snd_mixer_boolean_info, + .get = snd_cs46xx_front_dup_get, + .put = snd_cs46xx_front_dup_put, +}; +#endif + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +/* Only available on the Hercules Game Theater XP soundcard */ +static const struct snd_kcontrol_new snd_hercules_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Optical/Coaxial SPDIF Input Switch", + .info = snd_mixer_boolean_info, + .get = snd_herc_spdif_select_get, + .put = snd_herc_spdif_select_put, +}, +}; + + +static void snd_cs46xx_codec_reset (struct snd_ac97 * ac97) +{ + unsigned long end_time; + int err; + + /* reset to defaults */ + snd_ac97_write(ac97, AC97_RESET, 0); + + /* set the desired CODEC mode */ + if (ac97->num == CS46XX_PRIMARY_CODEC_INDEX) { + dev_dbg(ac97->bus->card->dev, "CODEC1 mode %04x\n", 0x0); + snd_cs46xx_ac97_write(ac97, AC97_CSR_ACMODE, 0x0); + } else if (ac97->num == CS46XX_SECONDARY_CODEC_INDEX) { + dev_dbg(ac97->bus->card->dev, "CODEC2 mode %04x\n", 0x3); + snd_cs46xx_ac97_write(ac97, AC97_CSR_ACMODE, 0x3); + } else { + snd_BUG(); /* should never happen ... */ + } + + udelay(50); + + /* it's necessary to wait awhile until registers are accessible after RESET */ + /* because the PCM or MASTER volume registers can be modified, */ + /* the REC_GAIN register is used for tests */ + end_time = jiffies + HZ; + do { + unsigned short ext_mid; + + /* use preliminary reads to settle the communication */ + snd_ac97_read(ac97, AC97_RESET); + snd_ac97_read(ac97, AC97_VENDOR_ID1); + snd_ac97_read(ac97, AC97_VENDOR_ID2); + /* modem? */ + ext_mid = snd_ac97_read(ac97, AC97_EXTENDED_MID); + if (ext_mid != 0xffff && (ext_mid & 1) != 0) + return; + + /* test if we can write to the record gain volume register */ + snd_ac97_write(ac97, AC97_REC_GAIN, 0x8a05); + err = snd_ac97_read(ac97, AC97_REC_GAIN); + if (err == 0x8a05) + return; + + msleep(10); + } while (time_after_eq(end_time, jiffies)); + + dev_err(ac97->bus->card->dev, + "CS46xx secondary codec doesn't respond!\n"); +} +#endif + +static int cs46xx_detect_codec(struct snd_cs46xx *chip, int codec) +{ + int idx, err; + struct snd_ac97_template ac97; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.private_free = snd_cs46xx_mixer_free_ac97; + ac97.num = codec; + if (chip->amplifier_ctrl == amp_voyetra) + ac97.scaps = AC97_SCAP_INV_EAPD; + + if (codec == CS46XX_SECONDARY_CODEC_INDEX) { + snd_cs46xx_codec_write(chip, AC97_RESET, 0, codec); + udelay(10); + if (snd_cs46xx_codec_read(chip, AC97_RESET, codec) & 0x8000) { + dev_dbg(chip->card->dev, + "secondary codec not present\n"); + return -ENXIO; + } + } + + snd_cs46xx_codec_write(chip, AC97_MASTER, 0x8000, codec); + for (idx = 0; idx < 100; ++idx) { + if (snd_cs46xx_codec_read(chip, AC97_MASTER, codec) == 0x8000) { + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97[codec]); + return err; + } + msleep(10); + } + dev_dbg(chip->card->dev, "codec %d detection timeout\n", codec); + return -ENXIO; +} + +int snd_cs46xx_mixer(struct snd_cs46xx *chip, int spdif_device) +{ + struct snd_card *card = chip->card; + int err; + unsigned int idx; + static const struct snd_ac97_bus_ops ops = { +#ifdef CONFIG_SND_CS46XX_NEW_DSP + .reset = snd_cs46xx_codec_reset, +#endif + .write = snd_cs46xx_ac97_write, + .read = snd_cs46xx_ac97_read, + }; + + /* detect primary codec */ + chip->nr_ac97_codecs = 0; + dev_dbg(chip->card->dev, "detecting primary codec\n"); + err = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus); + if (err < 0) + return err; + + if (cs46xx_detect_codec(chip, CS46XX_PRIMARY_CODEC_INDEX) < 0) + return -ENXIO; + chip->nr_ac97_codecs = 1; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + dev_dbg(chip->card->dev, "detecting secondary codec\n"); + /* try detect a secondary codec */ + if (! cs46xx_detect_codec(chip, CS46XX_SECONDARY_CODEC_INDEX)) + chip->nr_ac97_codecs = 2; +#endif /* CONFIG_SND_CS46XX_NEW_DSP */ + + /* add cs4630 mixer controls */ + for (idx = 0; idx < ARRAY_SIZE(snd_cs46xx_controls); idx++) { + struct snd_kcontrol *kctl; + kctl = snd_ctl_new1(&snd_cs46xx_controls[idx], chip); + if (kctl && kctl->id.iface == SNDRV_CTL_ELEM_IFACE_PCM) + kctl->id.device = spdif_device; + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + + /* get EAPD mixer switch (for voyetra hack) */ + chip->eapd_switch = snd_ctl_find_id_mixer(chip->card, + "External Amplifier"); + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->nr_ac97_codecs == 1) { + unsigned int id2 = chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]->id & 0xffff; + if ((id2 & 0xfff0) == 0x5920) { /* CS4294 and CS4298 */ + err = snd_ctl_add(card, snd_ctl_new1(&snd_cs46xx_front_dup_ctl, chip)); + if (err < 0) + return err; + snd_ac97_write_cache(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX], + AC97_CSR_ACMODE, 0x200); + } + } + /* do soundcard specific mixer setup */ + if (chip->mixer_init) { + dev_dbg(chip->card->dev, "calling chip->mixer_init(chip);\n"); + chip->mixer_init(chip); + } +#endif + + /* turn on amplifier */ + chip->amplifier_ctrl(chip, 1); + + return 0; +} + +/* + * RawMIDI interface + */ + +static void snd_cs46xx_midi_reset(struct snd_cs46xx *chip) +{ + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, MIDCR_MRST); + udelay(100); + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); +} + +static int snd_cs46xx_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + + chip->active_ctrl(chip, 1); + guard(spinlock_irq)(&chip->reg_lock); + chip->uartm |= CS46XX_MODE_INPUT; + chip->midcr |= MIDCR_RXE; + chip->midi_input = substream; + if (!(chip->uartm & CS46XX_MODE_OUTPUT)) { + snd_cs46xx_midi_reset(chip); + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + return 0; +} + +static int snd_cs46xx_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + + scoped_guard(spinlock_irq, &chip->reg_lock) { + chip->midcr &= ~(MIDCR_RXE | MIDCR_RIE); + chip->midi_input = NULL; + if (!(chip->uartm & CS46XX_MODE_OUTPUT)) { + snd_cs46xx_midi_reset(chip); + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + chip->uartm &= ~CS46XX_MODE_INPUT; + } + chip->active_ctrl(chip, -1); + return 0; +} + +static int snd_cs46xx_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + + chip->active_ctrl(chip, 1); + + guard(spinlock_irq)(&chip->reg_lock); + chip->uartm |= CS46XX_MODE_OUTPUT; + chip->midcr |= MIDCR_TXE; + chip->midi_output = substream; + if (!(chip->uartm & CS46XX_MODE_INPUT)) { + snd_cs46xx_midi_reset(chip); + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + return 0; +} + +static int snd_cs46xx_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + + scoped_guard(spinlock_irq, &chip->reg_lock) { + chip->midcr &= ~(MIDCR_TXE | MIDCR_TIE); + chip->midi_output = NULL; + if (!(chip->uartm & CS46XX_MODE_INPUT)) { + snd_cs46xx_midi_reset(chip); + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + chip->uartm &= ~CS46XX_MODE_OUTPUT; + } + chip->active_ctrl(chip, -1); + return 0; +} + +static void snd_cs46xx_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&chip->reg_lock); + if (up) { + if ((chip->midcr & MIDCR_RIE) == 0) { + chip->midcr |= MIDCR_RIE; + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } else { + if (chip->midcr & MIDCR_RIE) { + chip->midcr &= ~MIDCR_RIE; + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } +} + +static void snd_cs46xx_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_cs46xx *chip = substream->rmidi->private_data; + unsigned char byte; + + guard(spinlock_irqsave)(&chip->reg_lock); + if (up) { + if ((chip->midcr & MIDCR_TIE) == 0) { + chip->midcr |= MIDCR_TIE; + /* fill UART FIFO buffer at first, and turn Tx interrupts only if necessary */ + while ((chip->midcr & MIDCR_TIE) && + (snd_cs46xx_peekBA0(chip, BA0_MIDSR) & MIDSR_TBF) == 0) { + if (snd_rawmidi_transmit(substream, &byte, 1) != 1) { + chip->midcr &= ~MIDCR_TIE; + } else { + snd_cs46xx_pokeBA0(chip, BA0_MIDWP, byte); + } + } + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } else { + if (chip->midcr & MIDCR_TIE) { + chip->midcr &= ~MIDCR_TIE; + snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr); + } + } +} + +static const struct snd_rawmidi_ops snd_cs46xx_midi_output = +{ + .open = snd_cs46xx_midi_output_open, + .close = snd_cs46xx_midi_output_close, + .trigger = snd_cs46xx_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_cs46xx_midi_input = +{ + .open = snd_cs46xx_midi_input_open, + .close = snd_cs46xx_midi_input_close, + .trigger = snd_cs46xx_midi_input_trigger, +}; + +int snd_cs46xx_midi(struct snd_cs46xx *chip, int device) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(chip->card, "CS46XX", device, 1, 1, &rmidi); + if (err < 0) + return err; + strscpy(rmidi->name, "CS46XX"); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_cs46xx_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_cs46xx_midi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = chip; + chip->rmidi = rmidi; + return 0; +} + + +/* + * gameport interface + */ + +#if IS_REACHABLE(CONFIG_GAMEPORT) + +static void snd_cs46xx_gameport_trigger(struct gameport *gameport) +{ + struct snd_cs46xx *chip = gameport_get_port_data(gameport); + + if (snd_BUG_ON(!chip)) + return; + snd_cs46xx_pokeBA0(chip, BA0_JSPT, 0xFF); //outb(gameport->io, 0xFF); +} + +static unsigned char snd_cs46xx_gameport_read(struct gameport *gameport) +{ + struct snd_cs46xx *chip = gameport_get_port_data(gameport); + + if (snd_BUG_ON(!chip)) + return 0; + return snd_cs46xx_peekBA0(chip, BA0_JSPT); //inb(gameport->io); +} + +static int snd_cs46xx_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + struct snd_cs46xx *chip = gameport_get_port_data(gameport); + unsigned js1, js2, jst; + + if (snd_BUG_ON(!chip)) + return 0; + + js1 = snd_cs46xx_peekBA0(chip, BA0_JSC1); + js2 = snd_cs46xx_peekBA0(chip, BA0_JSC2); + jst = snd_cs46xx_peekBA0(chip, BA0_JSPT); + + *buttons = (~jst >> 4) & 0x0F; + + axes[0] = ((js1 & JSC1_Y1V_MASK) >> JSC1_Y1V_SHIFT) & 0xFFFF; + axes[1] = ((js1 & JSC1_X1V_MASK) >> JSC1_X1V_SHIFT) & 0xFFFF; + axes[2] = ((js2 & JSC2_Y2V_MASK) >> JSC2_Y2V_SHIFT) & 0xFFFF; + axes[3] = ((js2 & JSC2_X2V_MASK) >> JSC2_X2V_SHIFT) & 0xFFFF; + + for(jst=0;jst<4;++jst) + if(axes[jst]==0xFFFF) axes[jst] = -1; + return 0; +} + +static int snd_cs46xx_gameport_open(struct gameport *gameport, int mode) +{ + switch (mode) { + case GAMEPORT_MODE_COOKED: + return 0; + case GAMEPORT_MODE_RAW: + return 0; + default: + return -1; + } + return 0; +} + +int snd_cs46xx_gameport(struct snd_cs46xx *chip) +{ + struct gameport *gp; + + chip->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(chip->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "CS46xx Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci)); + gameport_set_dev_parent(gp, &chip->pci->dev); + gameport_set_port_data(gp, chip); + + gp->open = snd_cs46xx_gameport_open; + gp->read = snd_cs46xx_gameport_read; + gp->trigger = snd_cs46xx_gameport_trigger; + gp->cooked_read = snd_cs46xx_gameport_cooked_read; + + snd_cs46xx_pokeBA0(chip, BA0_JSIO, 0xFF); // ? + snd_cs46xx_pokeBA0(chip, BA0_JSCTL, JSCTL_SP_MEDIUM_SLOW); + + gameport_register_port(gp); + + return 0; +} + +static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } +} +#else +int snd_cs46xx_gameport(struct snd_cs46xx *chip) { return -ENOSYS; } +static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip) { } +#endif /* CONFIG_GAMEPORT */ + +#ifdef CONFIG_SND_PROC_FS +/* + * proc interface + */ + +static ssize_t snd_cs46xx_io_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) +{ + struct snd_cs46xx_region *region = entry->private_data; + + if (copy_to_user_fromio(buf, region->remap_addr + pos, count)) + return -EFAULT; + return count; +} + +static const struct snd_info_entry_ops snd_cs46xx_proc_io_ops = { + .read = snd_cs46xx_io_read, +}; + +static int snd_cs46xx_proc_init(struct snd_card *card, struct snd_cs46xx *chip) +{ + struct snd_info_entry *entry; + int idx; + + for (idx = 0; idx < 5; idx++) { + struct snd_cs46xx_region *region = &chip->region.idx[idx]; + if (! snd_card_proc_new(card, region->name, &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = chip; + entry->c.ops = &snd_cs46xx_proc_io_ops; + entry->size = region->size; + entry->mode = S_IFREG | 0400; + } + } +#ifdef CONFIG_SND_CS46XX_NEW_DSP + cs46xx_dsp_proc_init(card, chip); +#endif + return 0; +} + +static int snd_cs46xx_proc_done(struct snd_cs46xx *chip) +{ +#ifdef CONFIG_SND_CS46XX_NEW_DSP + cs46xx_dsp_proc_done(chip); +#endif + return 0; +} +#else /* !CONFIG_SND_PROC_FS */ +#define snd_cs46xx_proc_init(card, chip) +#define snd_cs46xx_proc_done(chip) +#endif + +/* + * stop the h/w + */ +static void snd_cs46xx_hw_stop(struct snd_cs46xx *chip) +{ + unsigned int tmp; + + tmp = snd_cs46xx_peek(chip, BA1_PFIE); + tmp &= ~0x0000f03f; + tmp |= 0x00000010; + snd_cs46xx_poke(chip, BA1_PFIE, tmp); /* playback interrupt disable */ + + tmp = snd_cs46xx_peek(chip, BA1_CIE); + tmp &= ~0x0000003f; + tmp |= 0x00000011; + snd_cs46xx_poke(chip, BA1_CIE, tmp); /* capture interrupt disable */ + + /* + * Stop playback DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_PCTL); + snd_cs46xx_poke(chip, BA1_PCTL, tmp & 0x0000ffff); + + /* + * Stop capture DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_CCTL); + snd_cs46xx_poke(chip, BA1_CCTL, tmp & 0xffff0000); + + /* + * Reset the processor. + */ + snd_cs46xx_reset(chip); + + snd_cs46xx_proc_stop(chip); + + /* + * Power down the PLL. + */ + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, 0); + + /* + * Turn off the Processor by turning off the software clock enable flag in + * the clock control register. + */ + tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1) & ~CLKCR1_SWCE; + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp); +} + + +static void snd_cs46xx_free(struct snd_card *card) +{ + struct snd_cs46xx *chip = card->private_data; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + int idx; +#endif + + if (chip->active_ctrl) + chip->active_ctrl(chip, 1); + + snd_cs46xx_remove_gameport(chip); + + if (chip->amplifier_ctrl) + chip->amplifier_ctrl(chip, -chip->amplifier); /* force to off */ + + snd_cs46xx_proc_done(chip); + + snd_cs46xx_hw_stop(chip); + + if (chip->active_ctrl) + chip->active_ctrl(chip, -chip->amplifier); + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->dsp_spos_instance) { + cs46xx_dsp_spos_destroy(chip); + chip->dsp_spos_instance = NULL; + } + for (idx = 0; idx < CS46XX_DSP_MODULES; idx++) + free_module_desc(chip->modules[idx]); +#else + vfree(chip->ba1); +#endif +} + +/* + * initialize chip + */ +static int snd_cs46xx_chip_init(struct snd_cs46xx *chip) +{ + int timeout; + + /* + * First, blast the clock control register to zero so that the PLL starts + * out in a known state, and blast the master serial port control register + * to zero so that the serial ports also start out in a known state. + */ + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, 0); + snd_cs46xx_pokeBA0(chip, BA0_SERMC1, 0); + + /* + * If we are in AC97 mode, then we must set the part to a host controlled + * AC-link. Otherwise, we won't be able to bring up the link. + */ +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_SERACC, SERACC_HSP | SERACC_CHIP_TYPE_2_0 | + SERACC_TWO_CODECS); /* 2.00 dual codecs */ + /* snd_cs46xx_pokeBA0(chip, BA0_SERACC, SERACC_HSP | SERACC_CHIP_TYPE_2_0); */ /* 2.00 codec */ +#else + snd_cs46xx_pokeBA0(chip, BA0_SERACC, SERACC_HSP | SERACC_CHIP_TYPE_1_03); /* 1.03 codec */ +#endif + + /* + * Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97 + * spec) and then drive it high. This is done for non AC97 modes since + * there might be logic external to the CS461x that uses the ARST# line + * for a reset. + */ + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, 0); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, 0); +#endif + udelay(50); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_RSTN); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, ACCTL_RSTN); +#endif + + /* + * The first thing we do here is to enable sync generation. As soon + * as we start receiving bit clock, we'll start producing the SYNC + * signal. + */ + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_ESYN | ACCTL_RSTN); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, ACCTL_ESYN | ACCTL_RSTN); +#endif + + /* + * Now wait for a short while to allow the AC97 part to start + * generating bit clock (so we don't try to start the PLL without an + * input clock). + */ + mdelay(10); + + /* + * Set the serial port timing configuration, so that + * the clock control circuit gets its clock from the correct place. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERMC1, SERMC1_PTC_AC97); + + /* + * Write the selected clock control setup to the hardware. Do not turn on + * SWCE yet (if requested), so that the devices clocked by the output of + * PLL are not clocked until the PLL is stable. + */ + snd_cs46xx_pokeBA0(chip, BA0_PLLCC, PLLCC_LPF_1050_2780_KHZ | PLLCC_CDR_73_104_MHZ); + snd_cs46xx_pokeBA0(chip, BA0_PLLM, 0x3a); + snd_cs46xx_pokeBA0(chip, BA0_CLKCR2, CLKCR2_PDIVS_8); + + /* + * Power up the PLL. + */ + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, CLKCR1_PLLP); + + /* + * Wait until the PLL has stabilized. + */ + msleep(100); + + /* + * Turn on clocking of the core so that we can setup the serial ports. + */ + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, CLKCR1_PLLP | CLKCR1_SWCE); + + /* + * Enable FIFO Host Bypass + */ + snd_cs46xx_pokeBA0(chip, BA0_SERBCF, SERBCF_HBP); + + /* + * Fill the serial port FIFOs with silence. + */ + snd_cs46xx_clear_serial_FIFOs(chip); + + /* + * Set the serial port FIFO pointer to the first sample in the FIFO. + */ + /* snd_cs46xx_pokeBA0(chip, BA0_SERBSP, 0); */ + + /* + * Write the serial port configuration to the part. The master + * enable bit is not set until all other values have been written. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERC1, SERC1_SO1F_AC97 | SERC1_SO1EN); + snd_cs46xx_pokeBA0(chip, BA0_SERC2, SERC2_SI1F_AC97 | SERC1_SO1EN); + snd_cs46xx_pokeBA0(chip, BA0_SERMC1, SERMC1_PTC_AC97 | SERMC1_MSPE); + + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_SERC7, SERC7_ASDI2EN); + snd_cs46xx_pokeBA0(chip, BA0_SERC3, 0); + snd_cs46xx_pokeBA0(chip, BA0_SERC4, 0); + snd_cs46xx_pokeBA0(chip, BA0_SERC5, 0); + snd_cs46xx_pokeBA0(chip, BA0_SERC6, 1); +#endif + + mdelay(5); + + + /* + * Wait for the codec ready signal from the AC97 codec. + */ + timeout = 150; + while (timeout-- > 0) { + /* + * Read the AC97 status register to see if we've seen a CODEC READY + * signal from the AC97 codec. + */ + if (snd_cs46xx_peekBA0(chip, BA0_ACSTS) & ACSTS_CRDY) + goto ok1; + msleep(10); + } + + + dev_err(chip->card->dev, + "create - never read codec ready from AC'97\n"); + dev_err(chip->card->dev, + "it is not probably bug, try to use CS4236 driver\n"); + return -EIO; + ok1: +#ifdef CONFIG_SND_CS46XX_NEW_DSP + { + int count; + for (count = 0; count < 150; count++) { + /* First, we want to wait for a short time. */ + udelay(25); + + if (snd_cs46xx_peekBA0(chip, BA0_ACSTS2) & ACSTS_CRDY) + break; + } + + /* + * Make sure CODEC is READY. + */ + if (!(snd_cs46xx_peekBA0(chip, BA0_ACSTS2) & ACSTS_CRDY)) + dev_dbg(chip->card->dev, + "never read card ready from secondary AC'97\n"); + } +#endif + + /* + * Assert the vaid frame signal so that we can start sending commands + * to the AC97 codec. + */ + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); +#endif + + + /* + * Wait until we've sampled input slots 3 and 4 as valid, meaning that + * the codec is pumping ADC data across the AC-link. + */ + timeout = 150; + while (timeout-- > 0) { + /* + * Read the input slot valid register and see if input slots 3 and + * 4 are valid yet. + */ + if ((snd_cs46xx_peekBA0(chip, BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) == (ACISV_ISV3 | ACISV_ISV4)) + goto ok2; + msleep(10); + } + +#ifndef CONFIG_SND_CS46XX_NEW_DSP + dev_err(chip->card->dev, + "create - never read ISV3 & ISV4 from AC'97\n"); + return -EIO; +#else + /* This may happen on a cold boot with a Terratec SiXPack 5.1. + Reloading the driver may help, if there's other soundcards + with the same problem I would like to know. (Benny) */ + + dev_err(chip->card->dev, "never read ISV3 & ISV4 from AC'97\n"); + dev_err(chip->card->dev, + "Try reloading the ALSA driver, if you find something\n"); + dev_err(chip->card->dev, + "broken or not working on your soundcard upon\n"); + dev_err(chip->card->dev, + "this message please report to alsa-devel@alsa-project.org\n"); + + return -EIO; +#endif + ok2: + + /* + * Now, assert valid frame and the slot 3 and 4 valid bits. This will + * commense the transfer of digital audio data to the AC97 codec. + */ + + snd_cs46xx_pokeBA0(chip, BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4); + + + /* + * Power down the DAC and ADC. We will power them up (if) when we need + * them. + */ + /* snd_cs46xx_pokeBA0(chip, BA0_AC97_POWERDOWN, 0x300); */ + + /* + * Turn off the Processor by turning off the software clock enable flag in + * the clock control register. + */ + /* tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1) & ~CLKCR1_SWCE; */ + /* snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp); */ + + return 0; +} + +/* + * start and load DSP + */ + +static void cs46xx_enable_stream_irqs(struct snd_cs46xx *chip) +{ + unsigned int tmp; + + snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_IEV | HICR_CHGM); + + tmp = snd_cs46xx_peek(chip, BA1_PFIE); + tmp &= ~0x0000f03f; + snd_cs46xx_poke(chip, BA1_PFIE, tmp); /* playback interrupt enable */ + + tmp = snd_cs46xx_peek(chip, BA1_CIE); + tmp &= ~0x0000003f; + tmp |= 0x00000001; + snd_cs46xx_poke(chip, BA1_CIE, tmp); /* capture interrupt enable */ +} + +int snd_cs46xx_start_dsp(struct snd_cs46xx *chip) +{ + unsigned int tmp; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + int i; +#endif + int err; + + /* + * Reset the processor. + */ + snd_cs46xx_reset(chip); + /* + * Download the image to the processor. + */ +#ifdef CONFIG_SND_CS46XX_NEW_DSP + for (i = 0; i < CS46XX_DSP_MODULES; i++) { + err = load_firmware(chip, &chip->modules[i], module_names[i]); + if (err < 0) { + dev_err(chip->card->dev, "firmware load error [%s]\n", + module_names[i]); + return err; + } + err = cs46xx_dsp_load_module(chip, chip->modules[i]); + if (err < 0) { + dev_err(chip->card->dev, "image download error [%s]\n", + module_names[i]); + return err; + } + } + + if (cs46xx_dsp_scb_and_task_init(chip) < 0) + return -EIO; +#else + err = load_firmware(chip); + if (err < 0) + return err; + + /* old image */ + err = snd_cs46xx_download_image(chip); + if (err < 0) { + dev_err(chip->card->dev, "image download error\n"); + return err; + } + + /* + * Stop playback DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_PCTL); + chip->play_ctl = tmp & 0xffff0000; + snd_cs46xx_poke(chip, BA1_PCTL, tmp & 0x0000ffff); +#endif + + /* + * Stop capture DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_CCTL); + chip->capt.ctl = tmp & 0x0000ffff; + snd_cs46xx_poke(chip, BA1_CCTL, tmp & 0xffff0000); + + mdelay(5); + + snd_cs46xx_set_play_sample_rate(chip, 8000); + snd_cs46xx_set_capture_sample_rate(chip, 8000); + + snd_cs46xx_proc_start(chip); + + cs46xx_enable_stream_irqs(chip); + +#ifndef CONFIG_SND_CS46XX_NEW_DSP + /* set the attenuation to 0dB */ + snd_cs46xx_poke(chip, BA1_PVOL, 0x80008000); + snd_cs46xx_poke(chip, BA1_CVOL, 0x80008000); +#endif + + return 0; +} + + +/* + * AMP control - null AMP + */ + +static void amp_none(struct snd_cs46xx *chip, int change) +{ +} + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +static int voyetra_setup_eapd_slot(struct snd_cs46xx *chip) +{ + + u32 idx, valid_slots,tmp,powerdown = 0; + u16 modem_power,pin_config,logic_type; + + dev_dbg(chip->card->dev, "cs46xx_setup_eapd_slot()+\n"); + + /* + * See if the devices are powered down. If so, we must power them up first + * or they will not respond. + */ + tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1); + + if (!(tmp & CLKCR1_SWCE)) { + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp | CLKCR1_SWCE); + powerdown = 1; + } + + /* + * Clear PRA. The Bonzo chip will be used for GPIO not for modem + * stuff. + */ + if(chip->nr_ac97_codecs != 2) { + dev_err(chip->card->dev, + "cs46xx_setup_eapd_slot() - no secondary codec configured\n"); + return -EINVAL; + } + + modem_power = snd_cs46xx_codec_read (chip, + AC97_EXTENDED_MSTATUS, + CS46XX_SECONDARY_CODEC_INDEX); + modem_power &=0xFEFF; + + snd_cs46xx_codec_write(chip, + AC97_EXTENDED_MSTATUS, modem_power, + CS46XX_SECONDARY_CODEC_INDEX); + + /* + * Set GPIO pin's 7 and 8 so that they are configured for output. + */ + pin_config = snd_cs46xx_codec_read (chip, + AC97_GPIO_CFG, + CS46XX_SECONDARY_CODEC_INDEX); + pin_config &=0x27F; + + snd_cs46xx_codec_write(chip, + AC97_GPIO_CFG, pin_config, + CS46XX_SECONDARY_CODEC_INDEX); + + /* + * Set GPIO pin's 7 and 8 so that they are compatible with CMOS logic. + */ + + logic_type = snd_cs46xx_codec_read(chip, AC97_GPIO_POLARITY, + CS46XX_SECONDARY_CODEC_INDEX); + logic_type &=0x27F; + + snd_cs46xx_codec_write (chip, AC97_GPIO_POLARITY, logic_type, + CS46XX_SECONDARY_CODEC_INDEX); + + valid_slots = snd_cs46xx_peekBA0(chip, BA0_ACOSV); + valid_slots |= 0x200; + snd_cs46xx_pokeBA0(chip, BA0_ACOSV, valid_slots); + + if ( cs46xx_wait_for_fifo(chip,1) ) { + dev_dbg(chip->card->dev, "FIFO is busy\n"); + + return -EINVAL; + } + + /* + * Fill slots 12 with the correct value for the GPIO pins. + */ + for(idx = 0x90; idx <= 0x9F; idx++) { + /* + * Initialize the fifo so that bits 7 and 8 are on. + * + * Remember that the GPIO pins in bonzo are shifted by 4 bits to + * the left. 0x1800 corresponds to bits 7 and 8. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERBWP, 0x1800); + + /* + * Wait for command to complete + */ + if ( cs46xx_wait_for_fifo(chip,200) ) { + dev_dbg(chip->card->dev, + "failed waiting for FIFO at addr (%02X)\n", + idx); + + return -EINVAL; + } + + /* + * Write the serial port FIFO index. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERBAD, idx); + + /* + * Tell the serial port to load the new value into the FIFO location. + */ + snd_cs46xx_pokeBA0(chip, BA0_SERBCM, SERBCM_WRC); + } + + /* wait for last command to complete */ + cs46xx_wait_for_fifo(chip,200); + + /* + * Now, if we powered up the devices, then power them back down again. + * This is kinda ugly, but should never happen. + */ + if (powerdown) + snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp); + + return 0; +} +#endif + +/* + * Crystal EAPD mode + */ + +static void amp_voyetra(struct snd_cs46xx *chip, int change) +{ + /* Manage the EAPD bit on the Crystal 4297 + and the Analog AD1885 */ + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + int old = chip->amplifier; +#endif + int oval, val; + + chip->amplifier += change; + oval = snd_cs46xx_codec_read(chip, AC97_POWERDOWN, + CS46XX_PRIMARY_CODEC_INDEX); + val = oval; + if (chip->amplifier) { + /* Turn the EAPD amp on */ + val |= 0x8000; + } else { + /* Turn the EAPD amp off */ + val &= ~0x8000; + } + if (val != oval) { + snd_cs46xx_codec_write(chip, AC97_POWERDOWN, val, + CS46XX_PRIMARY_CODEC_INDEX); + if (chip->eapd_switch) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->eapd_switch->id); + } + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->amplifier && !old) { + voyetra_setup_eapd_slot(chip); + } +#endif +} + +static void hercules_init(struct snd_cs46xx *chip) +{ + /* default: AMP off, and SPDIF input optical */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, EGPIODR_GPOE0); + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, EGPIODR_GPOE0); +} + + +/* + * Game Theatre XP card - EGPIO[2] is used to enable the external amp. + */ +static void amp_hercules(struct snd_cs46xx *chip, int change) +{ + int old = chip->amplifier; + int val1 = snd_cs46xx_peekBA0(chip, BA0_EGPIODR); + int val2 = snd_cs46xx_peekBA0(chip, BA0_EGPIOPTR); + + chip->amplifier += change; + if (chip->amplifier && !old) { + dev_dbg(chip->card->dev, "Hercules amplifier ON\n"); + + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, + EGPIODR_GPOE2 | val1); /* enable EGPIO2 output */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, + EGPIOPTR_GPPT2 | val2); /* open-drain on output */ + } else if (old && !chip->amplifier) { + dev_dbg(chip->card->dev, "Hercules amplifier OFF\n"); + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, val1 & ~EGPIODR_GPOE2); /* disable */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, val2 & ~EGPIOPTR_GPPT2); /* disable */ + } +} + +static void voyetra_mixer_init (struct snd_cs46xx *chip) +{ + dev_dbg(chip->card->dev, "initializing Voyetra mixer\n"); + + /* Enable SPDIF out */ + snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, EGPIODR_GPOE0); + snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, EGPIODR_GPOE0); +} + +static void hercules_mixer_init (struct snd_cs46xx *chip) +{ +#ifdef CONFIG_SND_CS46XX_NEW_DSP + unsigned int idx; + int err; + struct snd_card *card = chip->card; +#endif + + /* set EGPIO to default */ + hercules_init(chip); + + dev_dbg(chip->card->dev, "initializing Hercules mixer\n"); + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + if (chip->in_suspend) + return; + + for (idx = 0 ; idx < ARRAY_SIZE(snd_hercules_controls); idx++) { + struct snd_kcontrol *kctl; + + kctl = snd_ctl_new1(&snd_hercules_controls[idx], chip); + err = snd_ctl_add(card, kctl); + if (err < 0) { + dev_err(card->dev, + "failed to initialize Hercules mixer (%d)\n", + err); + break; + } + } +#endif +} + + +#if 0 +/* + * Untested + */ + +static void amp_voyetra_4294(struct snd_cs46xx *chip, int change) +{ + chip->amplifier += change; + + if (chip->amplifier) { + /* Switch the GPIO pins 7 and 8 to open drain */ + snd_cs46xx_codec_write(chip, 0x4C, + snd_cs46xx_codec_read(chip, 0x4C) & 0xFE7F); + snd_cs46xx_codec_write(chip, 0x4E, + snd_cs46xx_codec_read(chip, 0x4E) | 0x0180); + /* Now wake the AMP (this might be backwards) */ + snd_cs46xx_codec_write(chip, 0x54, + snd_cs46xx_codec_read(chip, 0x54) & ~0x0180); + } else { + snd_cs46xx_codec_write(chip, 0x54, + snd_cs46xx_codec_read(chip, 0x54) | 0x0180); + } +} +#endif + + +/* + * Handle the CLKRUN on a thinkpad. We must disable CLKRUN support + * whenever we need to beat on the chip. + * + * The original idea and code for this hack comes from David Kaiser at + * Linuxcare. Perhaps one day Crystal will document their chips well + * enough to make them useful. + */ + +static void clkrun_hack(struct snd_cs46xx *chip, int change) +{ + u16 control, nval; + + if (!chip->acpi_port) + return; + + chip->amplifier += change; + + /* Read ACPI port */ + nval = control = inw(chip->acpi_port + 0x10); + + /* Flip CLKRUN off while running */ + if (! chip->amplifier) + nval |= 0x2000; + else + nval &= ~0x2000; + if (nval != control) + outw(nval, chip->acpi_port + 0x10); +} + + +/* + * detect intel piix4 + */ +static void clkrun_init(struct snd_cs46xx *chip) +{ + struct pci_dev *pdev; + u8 pp; + + chip->acpi_port = 0; + + pdev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82371AB_3, NULL); + if (pdev == NULL) + return; /* Not a thinkpad thats for sure */ + + /* Find the control port */ + pci_read_config_byte(pdev, 0x41, &pp); + chip->acpi_port = pp << 8; + pci_dev_put(pdev); +} + + +/* + * Card subid table + */ + +struct cs_card_type +{ + u16 vendor; + u16 id; + char *name; + void (*init)(struct snd_cs46xx *); + void (*amp)(struct snd_cs46xx *, int); + void (*active)(struct snd_cs46xx *, int); + void (*mixer_init)(struct snd_cs46xx *); +}; + +static struct cs_card_type cards[] = { + { + .vendor = 0x1489, + .id = 0x7001, + .name = "Genius Soundmaker 128 value", + /* nothing special */ + }, + { + .vendor = 0x5053, + .id = 0x3357, + .name = "Voyetra", + .amp = amp_voyetra, + .mixer_init = voyetra_mixer_init, + }, + { + .vendor = 0x1071, + .id = 0x6003, + .name = "Mitac MI6020/21", + .amp = amp_voyetra, + }, + /* Hercules Game Theatre XP */ + { + .vendor = 0x14af, /* Guillemot Corporation */ + .id = 0x0050, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + { + .vendor = 0x1681, + .id = 0x0050, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + { + .vendor = 0x1681, + .id = 0x0051, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + + }, + { + .vendor = 0x1681, + .id = 0x0052, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + { + .vendor = 0x1681, + .id = 0x0053, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + { + .vendor = 0x1681, + .id = 0x0054, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + .mixer_init = hercules_mixer_init, + }, + /* Herculess Fortissimo */ + { + .vendor = 0x1681, + .id = 0xa010, + .name = "Hercules Gamesurround Fortissimo II", + }, + { + .vendor = 0x1681, + .id = 0xa011, + .name = "Hercules Gamesurround Fortissimo III 7.1", + }, + /* Teratec */ + { + .vendor = 0x153b, + .id = 0x112e, + .name = "Terratec DMX XFire 1024", + }, + { + .vendor = 0x153b, + .id = 0x1136, + .name = "Terratec SiXPack 5.1", + }, + /* Not sure if the 570 needs the clkrun hack */ + { + .vendor = PCI_VENDOR_ID_IBM, + .id = 0x0132, + .name = "Thinkpad 570", + .init = clkrun_init, + .active = clkrun_hack, + }, + { + .vendor = PCI_VENDOR_ID_IBM, + .id = 0x0153, + .name = "Thinkpad 600X/A20/T20", + .init = clkrun_init, + .active = clkrun_hack, + }, + { + .vendor = PCI_VENDOR_ID_IBM, + .id = 0x1010, + .name = "Thinkpad 600E (unsupported)", + }, + {} /* terminator */ +}; + + +/* + * APM support + */ +#ifdef CONFIG_PM_SLEEP +static const unsigned int saved_regs[] = { + BA0_ACOSV, + /*BA0_ASER_FADDR,*/ + BA0_ASER_MASTER, + BA1_PVOL, + BA1_CVOL, +}; + +static int snd_cs46xx_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_cs46xx *chip = card->private_data; + int i, amp_saved; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + chip->in_suspend = 1; + // chip->ac97_powerdown = snd_cs46xx_codec_read(chip, AC97_POWER_CONTROL); + // chip->ac97_general_purpose = snd_cs46xx_codec_read(chip, BA0_AC97_GENERAL_PURPOSE); + + snd_ac97_suspend(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]); + snd_ac97_suspend(chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]); + + /* save some registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + chip->saved_regs[i] = snd_cs46xx_peekBA0(chip, saved_regs[i]); + + amp_saved = chip->amplifier; + /* turn off amp */ + chip->amplifier_ctrl(chip, -chip->amplifier); + snd_cs46xx_hw_stop(chip); + /* disable CLKRUN */ + chip->active_ctrl(chip, -chip->amplifier); + chip->amplifier = amp_saved; /* restore the status */ + return 0; +} + +static int snd_cs46xx_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_cs46xx *chip = card->private_data; + int amp_saved; +#ifdef CONFIG_SND_CS46XX_NEW_DSP + int i; +#endif + unsigned int tmp; + + amp_saved = chip->amplifier; + chip->amplifier = 0; + chip->active_ctrl(chip, 1); /* force to on */ + + snd_cs46xx_chip_init(chip); + + snd_cs46xx_reset(chip); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + cs46xx_dsp_resume(chip); + /* restore some registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + snd_cs46xx_pokeBA0(chip, saved_regs[i], chip->saved_regs[i]); +#else + snd_cs46xx_download_image(chip); +#endif + +#if 0 + snd_cs46xx_codec_write(chip, BA0_AC97_GENERAL_PURPOSE, + chip->ac97_general_purpose); + snd_cs46xx_codec_write(chip, AC97_POWER_CONTROL, + chip->ac97_powerdown); + mdelay(10); + snd_cs46xx_codec_write(chip, BA0_AC97_POWERDOWN, + chip->ac97_powerdown); + mdelay(5); +#endif + + snd_ac97_resume(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]); + snd_ac97_resume(chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]); + + /* + * Stop capture DMA. + */ + tmp = snd_cs46xx_peek(chip, BA1_CCTL); + chip->capt.ctl = tmp & 0x0000ffff; + snd_cs46xx_poke(chip, BA1_CCTL, tmp & 0xffff0000); + + mdelay(5); + + /* reset playback/capture */ + snd_cs46xx_set_play_sample_rate(chip, 8000); + snd_cs46xx_set_capture_sample_rate(chip, 8000); + snd_cs46xx_proc_start(chip); + + cs46xx_enable_stream_irqs(chip); + + if (amp_saved) + chip->amplifier_ctrl(chip, 1); /* turn amp on */ + else + chip->active_ctrl(chip, -1); /* disable CLKRUN */ + chip->amplifier = amp_saved; + chip->in_suspend = 0; + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +SIMPLE_DEV_PM_OPS(snd_cs46xx_pm, snd_cs46xx_suspend, snd_cs46xx_resume); +#endif /* CONFIG_PM_SLEEP */ + + +/* + */ + +int snd_cs46xx_create(struct snd_card *card, + struct pci_dev *pci, + int external_amp, int thinkpad) +{ + struct snd_cs46xx *chip = card->private_data; + int err, idx; + struct snd_cs46xx_region *region; + struct cs_card_type *cp; + u16 ss_card, ss_vendor; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + mutex_init(&chip->spos_mutex); +#endif + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + err = pcim_request_all_regions(pci, "CS46xx"); + if (err < 0) + return err; + chip->ba0_addr = pci_resource_start(pci, 0); + chip->ba1_addr = pci_resource_start(pci, 1); + if (chip->ba0_addr == 0 || chip->ba0_addr == (unsigned long)~0 || + chip->ba1_addr == 0 || chip->ba1_addr == (unsigned long)~0) { + dev_err(chip->card->dev, + "wrong address(es) - ba0 = 0x%lx, ba1 = 0x%lx\n", + chip->ba0_addr, chip->ba1_addr); + return -ENOMEM; + } + + region = &chip->region.name.ba0; + strscpy(region->name, "CS46xx_BA0"); + region->base = chip->ba0_addr; + region->size = CS46XX_BA0_SIZE; + + region = &chip->region.name.data0; + strscpy(region->name, "CS46xx_BA1_data0"); + region->base = chip->ba1_addr + BA1_SP_DMEM0; + region->size = CS46XX_BA1_DATA0_SIZE; + + region = &chip->region.name.data1; + strscpy(region->name, "CS46xx_BA1_data1"); + region->base = chip->ba1_addr + BA1_SP_DMEM1; + region->size = CS46XX_BA1_DATA1_SIZE; + + region = &chip->region.name.pmem; + strscpy(region->name, "CS46xx_BA1_pmem"); + region->base = chip->ba1_addr + BA1_SP_PMEM; + region->size = CS46XX_BA1_PRG_SIZE; + + region = &chip->region.name.reg; + strscpy(region->name, "CS46xx_BA1_reg"); + region->base = chip->ba1_addr + BA1_SP_REG; + region->size = CS46XX_BA1_REG_SIZE; + + /* set up amp and clkrun hack */ + pci_read_config_word(pci, PCI_SUBSYSTEM_VENDOR_ID, &ss_vendor); + pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &ss_card); + + for (cp = &cards[0]; cp->name; cp++) { + if (cp->vendor == ss_vendor && cp->id == ss_card) { + dev_dbg(chip->card->dev, "hack for %s enabled\n", + cp->name); + + chip->amplifier_ctrl = cp->amp; + chip->active_ctrl = cp->active; + chip->mixer_init = cp->mixer_init; + + if (cp->init) + cp->init(chip); + break; + } + } + + if (external_amp) { + dev_info(chip->card->dev, + "Crystal EAPD support forced on.\n"); + chip->amplifier_ctrl = amp_voyetra; + } + + if (thinkpad) { + dev_info(chip->card->dev, + "Activating CLKRUN hack for Thinkpad.\n"); + chip->active_ctrl = clkrun_hack; + clkrun_init(chip); + } + + if (chip->amplifier_ctrl == NULL) + chip->amplifier_ctrl = amp_none; + if (chip->active_ctrl == NULL) + chip->active_ctrl = amp_none; + + chip->active_ctrl(chip, 1); /* enable CLKRUN */ + + pci_set_master(pci); + + for (idx = 0; idx < 5; idx++) { + region = &chip->region.idx[idx]; + region->remap_addr = devm_ioremap(&pci->dev, region->base, + region->size); + if (region->remap_addr == NULL) { + dev_err(chip->card->dev, + "%s ioremap problem\n", region->name); + return -ENOMEM; + } + } + + if (devm_request_irq(&pci->dev, pci->irq, snd_cs46xx_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(chip->card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_cs46xx_free; + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + chip->dsp_spos_instance = cs46xx_dsp_spos_create(chip); + if (!chip->dsp_spos_instance) + return -ENOMEM; +#endif + + err = snd_cs46xx_chip_init(chip); + if (err < 0) + return err; + + snd_cs46xx_proc_init(card, chip); + +#ifdef CONFIG_PM_SLEEP + chip->saved_regs = devm_kmalloc_array(&pci->dev, + ARRAY_SIZE(saved_regs), + sizeof(*chip->saved_regs), + GFP_KERNEL); + if (!chip->saved_regs) + return -ENOMEM; +#endif + + chip->active_ctrl(chip, -1); /* disable CLKRUN */ + return 0; +} diff --git a/sound/pci/cs46xx/cs46xx_lib.h b/sound/pci/cs46xx/cs46xx_lib.h new file mode 100644 index 000000000..6bcf2b636 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_lib.h @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + */ + +#ifndef __CS46XX_LIB_H__ +#define __CS46XX_LIB_H__ + +/* + * constants + */ + +#define CS46XX_BA0_SIZE 0x1000 +#define CS46XX_BA1_DATA0_SIZE 0x3000 +#define CS46XX_BA1_DATA1_SIZE 0x3800 +#define CS46XX_BA1_PRG_SIZE 0x7000 +#define CS46XX_BA1_REG_SIZE 0x0100 + + + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +#define CS46XX_MIN_PERIOD_SIZE 64 +#define CS46XX_MAX_PERIOD_SIZE 1024*1024 +#else +#define CS46XX_MIN_PERIOD_SIZE 2048 +#define CS46XX_MAX_PERIOD_SIZE 2048 +#endif + +#define CS46XX_FRAGS 2 +/* #define CS46XX_BUFFER_SIZE CS46XX_MAX_PERIOD_SIZE * CS46XX_FRAGS */ + +#define SCB_NO_PARENT 0 +#define SCB_ON_PARENT_NEXT_SCB 1 +#define SCB_ON_PARENT_SUBLIST_SCB 2 + +/* 3*1024 parameter, 3.5*1024 sample, 2*3.5*1024 code */ +#define BA1_DWORD_SIZE (13 * 1024 + 512) +#define BA1_MEMORY_COUNT 3 + +/* + * common I/O routines + */ + +static inline void snd_cs46xx_poke(struct snd_cs46xx *chip, unsigned long reg, unsigned int val) +{ + unsigned int bank = reg >> 16; + unsigned int offset = reg & 0xffff; + + /* + if (bank == 0) + printk(KERN_DEBUG "snd_cs46xx_poke: %04X - %08X\n", + reg >> 2,val); + */ + writel(val, chip->region.idx[bank+1].remap_addr + offset); +} + +static inline unsigned int snd_cs46xx_peek(struct snd_cs46xx *chip, unsigned long reg) +{ + unsigned int bank = reg >> 16; + unsigned int offset = reg & 0xffff; + return readl(chip->region.idx[bank+1].remap_addr + offset); +} + +static inline void snd_cs46xx_pokeBA0(struct snd_cs46xx *chip, unsigned long offset, unsigned int val) +{ + writel(val, chip->region.name.ba0.remap_addr + offset); +} + +static inline unsigned int snd_cs46xx_peekBA0(struct snd_cs46xx *chip, unsigned long offset) +{ + return readl(chip->region.name.ba0.remap_addr + offset); +} + +struct dsp_spos_instance *cs46xx_dsp_spos_create (struct snd_cs46xx * chip); +void cs46xx_dsp_spos_destroy (struct snd_cs46xx * chip); +int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * module); +#ifdef CONFIG_PM_SLEEP +int cs46xx_dsp_resume(struct snd_cs46xx * chip); +#endif +struct dsp_symbol_entry *cs46xx_dsp_lookup_symbol (struct snd_cs46xx * chip, char * symbol_name, + int symbol_type); +#ifdef CONFIG_SND_PROC_FS +int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip); +int cs46xx_dsp_proc_done (struct snd_cs46xx *chip); +#else +#define cs46xx_dsp_proc_init(card, chip) +#define cs46xx_dsp_proc_done(chip) +#endif +int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip); +int snd_cs46xx_download (struct snd_cs46xx *chip, u32 *src, unsigned long offset, + unsigned long len); +int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip, unsigned long offset, unsigned long len); +int cs46xx_dsp_enable_spdif_out (struct snd_cs46xx *chip); +int cs46xx_dsp_enable_spdif_hw (struct snd_cs46xx *chip); +int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip); +int cs46xx_dsp_enable_spdif_in (struct snd_cs46xx *chip); +int cs46xx_dsp_disable_spdif_in (struct snd_cs46xx *chip); +int cs46xx_dsp_enable_pcm_capture (struct snd_cs46xx *chip); +int cs46xx_dsp_disable_pcm_capture (struct snd_cs46xx *chip); +int cs46xx_dsp_enable_adc_capture (struct snd_cs46xx *chip); +int cs46xx_dsp_disable_adc_capture (struct snd_cs46xx *chip); +int cs46xx_poke_via_dsp (struct snd_cs46xx *chip, u32 address, u32 data); +struct dsp_scb_descriptor * cs46xx_dsp_create_scb (struct snd_cs46xx *chip, char * name, + u32 * scb_data, u32 dest); +#ifdef CONFIG_SND_PROC_FS +void cs46xx_dsp_proc_free_scb_desc (struct dsp_scb_descriptor * scb); +void cs46xx_dsp_proc_register_scb_desc (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * scb); +#else +#define cs46xx_dsp_proc_free_scb_desc(scb) +#define cs46xx_dsp_proc_register_scb_desc(chip, scb) +#endif +struct dsp_scb_descriptor * cs46xx_dsp_create_timing_master_scb (struct snd_cs46xx *chip); +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_out_scb(struct snd_cs46xx * chip, + char * codec_name, u16 channel_disp, u16 fifo_addr, + u16 child_scb_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name, + u16 channel_disp, u16 fifo_addr, + u16 sample_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * scb); +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name, + u16 channel_disp, u16 fifo_addr, + u16 sample_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_src_task_scb(struct snd_cs46xx * chip, char * scb_name, + int sample_rate, u16 src_buffer_addr, + u16 src_delay_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type, int pass_through); +struct dsp_scb_descriptor * +cs46xx_dsp_create_mix_only_scb(struct snd_cs46xx * chip, char * scb_name, + u16 mix_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); + +struct dsp_scb_descriptor * +cs46xx_dsp_create_vari_decimate_scb(struct snd_cs46xx * chip, char * scb_name, + u16 vari_buffer_addr0, u16 vari_buffer_addr1, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_asynch_fg_rx_scb(struct snd_cs46xx * chip, char * scb_name, + u32 dest, u16 hfg_scb_address, u16 asynch_buffer_address, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_spio_write_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_mix_to_ostream_scb(struct snd_cs46xx * chip, char * scb_name, + u16 mix_buffer_addr, u16 writeback_spb, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_scb_descriptor * +cs46xx_dsp_create_magic_snoop_scb(struct snd_cs46xx * chip, char * scb_name, + u32 dest, u16 snoop_buffer_address, + struct dsp_scb_descriptor * snoop_scb, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type); +struct dsp_pcm_channel_descriptor * +cs46xx_dsp_create_pcm_channel (struct snd_cs46xx * chip, u32 sample_rate, + void * private_data, u32 hw_dma_addr, + int pcm_channel_id); +void cs46xx_dsp_destroy_pcm_channel (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel); +int cs46xx_dsp_pcm_unlink (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel); +int cs46xx_dsp_pcm_link (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel); +struct dsp_scb_descriptor * +cs46xx_add_record_source (struct snd_cs46xx *chip, struct dsp_scb_descriptor * source, + u16 addr, char * scb_name); +int cs46xx_src_unlink(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src); +int cs46xx_src_link(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src); +int cs46xx_iec958_pre_open (struct snd_cs46xx *chip); +int cs46xx_iec958_post_close (struct snd_cs46xx *chip); +int cs46xx_dsp_pcm_channel_set_period (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel, + int period_size); +int cs46xx_dsp_pcm_ostream_set_period (struct snd_cs46xx * chip, int period_size); +int cs46xx_dsp_set_dac_volume (struct snd_cs46xx * chip, u16 left, u16 right); +int cs46xx_dsp_set_iec958_volume (struct snd_cs46xx * chip, u16 left, u16 right); +#endif /* __CS46XX_LIB_H__ */ diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c new file mode 100644 index 000000000..6fd4f629d --- /dev/null +++ b/sound/pci/cs46xx/dsp_spos.c @@ -0,0 +1,1965 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* + * 2002-07 Benny Sjostrand benny@hostmobility.com + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "cs46xx.h" + +#include "cs46xx_lib.h" +#include "dsp_spos.h" + +static int cs46xx_dsp_async_init (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * fg_entry); + +static const enum wide_opcode wide_opcodes[] = { + WIDE_FOR_BEGIN_LOOP, + WIDE_FOR_BEGIN_LOOP2, + WIDE_COND_GOTO_ADDR, + WIDE_COND_GOTO_CALL, + WIDE_TBEQ_COND_GOTO_ADDR, + WIDE_TBEQ_COND_CALL_ADDR, + WIDE_TBEQ_NCOND_GOTO_ADDR, + WIDE_TBEQ_NCOND_CALL_ADDR, + WIDE_TBEQ_COND_GOTO1_ADDR, + WIDE_TBEQ_COND_CALL1_ADDR, + WIDE_TBEQ_NCOND_GOTOI_ADDR, + WIDE_TBEQ_NCOND_CALL1_ADDR +}; + +static int shadow_and_reallocate_code (struct snd_cs46xx * chip, u32 * data, u32 size, + u32 overlay_begin_address) +{ + unsigned int i = 0, j, nreallocated = 0; + u32 hival,loval,address; + u32 mop_operands,mop_type,wide_op; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(size %2)) + return -EINVAL; + + while (i < size) { + loval = data[i++]; + hival = data[i++]; + + if (ins->code.offset > 0) { + mop_operands = (hival >> 6) & 0x03fff; + mop_type = mop_operands >> 10; + + /* check for wide type instruction */ + if (mop_type == 0 && + (mop_operands & WIDE_LADD_INSTR_MASK) == 0 && + (mop_operands & WIDE_INSTR_MASK) != 0) { + wide_op = loval & 0x7f; + for (j = 0;j < ARRAY_SIZE(wide_opcodes); ++j) { + if (wide_opcodes[j] == wide_op) { + /* need to reallocate instruction */ + address = (hival & 0x00FFF) << 5; + address |= loval >> 15; + + dev_dbg(chip->card->dev, + "handle_wideop[1]: %05x:%05x addr %04x\n", + hival, loval, address); + + if ( !(address & 0x8000) ) { + address += (ins->code.offset / 2) - overlay_begin_address; + } else { + dev_dbg(chip->card->dev, + "handle_wideop[1]: ROM symbol not reallocated\n"); + } + + hival &= 0xFF000; + loval &= 0x07FFF; + + hival |= ( (address >> 5) & 0x00FFF); + loval |= ( (address << 15) & 0xF8000); + + address = (hival & 0x00FFF) << 5; + address |= loval >> 15; + + dev_dbg(chip->card->dev, + "handle_wideop:[2] %05x:%05x addr %04x\n", + hival, loval, address); + nreallocated++; + } /* wide_opcodes[j] == wide_op */ + } /* for */ + } /* mod_type == 0 ... */ + } /* ins->code.offset > 0 */ + + ins->code.data[ins->code.size++] = loval; + ins->code.data[ins->code.size++] = hival; + } + + dev_dbg(chip->card->dev, + "dsp_spos: %d instructions reallocated\n", nreallocated); + return nreallocated; +} + +static struct dsp_segment_desc * get_segment_desc (struct dsp_module_desc * module, int seg_type) +{ + int i; + for (i = 0;i < module->nsegments; ++i) { + if (module->segments[i].segment_type == seg_type) { + return (module->segments + i); + } + } + + return NULL; +}; + +static int find_free_symbol_index (struct dsp_spos_instance * ins) +{ + int index = ins->symbol_table.nsymbols,i; + + for (i = ins->symbol_table.highest_frag_index; i < ins->symbol_table.nsymbols; ++i) { + if (ins->symbol_table.symbols[i].deleted) { + index = i; + break; + } + } + + return index; +} + +static int add_symbols (struct snd_cs46xx * chip, struct dsp_module_desc * module) +{ + int i; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (module->symbol_table.nsymbols > 0) { + if (!strcmp(module->symbol_table.symbols[0].symbol_name, "OVERLAYBEGINADDRESS") && + module->symbol_table.symbols[0].symbol_type == SYMBOL_CONSTANT ) { + module->overlay_begin_address = module->symbol_table.symbols[0].address; + } + } + + for (i = 0;i < module->symbol_table.nsymbols; ++i) { + if (ins->symbol_table.nsymbols == (DSP_MAX_SYMBOLS - 1)) { + dev_err(chip->card->dev, + "dsp_spos: symbol table is full\n"); + return -ENOMEM; + } + + + if (cs46xx_dsp_lookup_symbol(chip, + module->symbol_table.symbols[i].symbol_name, + module->symbol_table.symbols[i].symbol_type) == NULL) { + + ins->symbol_table.symbols[ins->symbol_table.nsymbols] = module->symbol_table.symbols[i]; + ins->symbol_table.symbols[ins->symbol_table.nsymbols].address += ((ins->code.offset / 2) - module->overlay_begin_address); + ins->symbol_table.symbols[ins->symbol_table.nsymbols].module = module; + ins->symbol_table.symbols[ins->symbol_table.nsymbols].deleted = 0; + + if (ins->symbol_table.nsymbols > ins->symbol_table.highest_frag_index) + ins->symbol_table.highest_frag_index = ins->symbol_table.nsymbols; + + ins->symbol_table.nsymbols++; + } else { +#if 0 + dev_dbg(chip->card->dev, + "dsp_spos: symbol <%s> duplicated, probably nothing wrong with that (Cirrus?)\n", + module->symbol_table.symbols[i].symbol_name); */ +#endif + } + } + + return 0; +} + +static struct dsp_symbol_entry * +add_symbol (struct snd_cs46xx * chip, char * symbol_name, u32 address, int type) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_symbol_entry * symbol = NULL; + int index; + + if (ins->symbol_table.nsymbols == (DSP_MAX_SYMBOLS - 1)) { + dev_err(chip->card->dev, "dsp_spos: symbol table is full\n"); + return NULL; + } + + if (cs46xx_dsp_lookup_symbol(chip, + symbol_name, + type) != NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol <%s> duplicated\n", symbol_name); + return NULL; + } + + index = find_free_symbol_index (ins); + + strscpy (ins->symbol_table.symbols[index].symbol_name, symbol_name); + ins->symbol_table.symbols[index].address = address; + ins->symbol_table.symbols[index].symbol_type = type; + ins->symbol_table.symbols[index].module = NULL; + ins->symbol_table.symbols[index].deleted = 0; + symbol = (ins->symbol_table.symbols + index); + + if (index > ins->symbol_table.highest_frag_index) + ins->symbol_table.highest_frag_index = index; + + if (index == ins->symbol_table.nsymbols) + ins->symbol_table.nsymbols++; /* no frag. in list */ + + return symbol; +} + +struct dsp_spos_instance *cs46xx_dsp_spos_create (struct snd_cs46xx * chip) +{ + struct dsp_spos_instance * ins = kzalloc_obj(struct dsp_spos_instance); + + if (ins == NULL) + return NULL; + + /* better to use vmalloc for this big table */ + ins->symbol_table.symbols = + vmalloc(array_size(DSP_MAX_SYMBOLS, + sizeof(struct dsp_symbol_entry))); + ins->code.data = kmalloc(DSP_CODE_BYTE_SIZE, GFP_KERNEL); + ins->modules = kmalloc_objs(struct dsp_module_desc, DSP_MAX_MODULES); + if (!ins->symbol_table.symbols || !ins->code.data || !ins->modules) { + cs46xx_dsp_spos_destroy(chip); + goto error; + } + ins->symbol_table.nsymbols = 0; + ins->symbol_table.highest_frag_index = 0; + ins->code.offset = 0; + ins->code.size = 0; + ins->nscb = 0; + ins->ntask = 0; + ins->nmodules = 0; + + /* default SPDIF input sample rate + to 48000 khz */ + ins->spdif_in_sample_rate = 48000; + + /* maximize volume */ + ins->dac_volume_right = 0x8000; + ins->dac_volume_left = 0x8000; + ins->spdif_input_volume_right = 0x8000; + ins->spdif_input_volume_left = 0x8000; + + /* set left and right validity bits and + default channel status */ + ins->spdif_csuv_default = + ins->spdif_csuv_stream = + /* byte 0 */ ((unsigned int)_wrap_all_bits( (SNDRV_PCM_DEFAULT_CON_SPDIF & 0xff)) << 24) | + /* byte 1 */ ((unsigned int)_wrap_all_bits( ((SNDRV_PCM_DEFAULT_CON_SPDIF >> 8) & 0xff)) << 16) | + /* byte 3 */ (unsigned int)_wrap_all_bits( (SNDRV_PCM_DEFAULT_CON_SPDIF >> 24) & 0xff) | + /* left and right validity bits */ (1 << 13) | (1 << 12); + + return ins; + +error: + kfree(ins->modules); + kfree(ins->code.data); + vfree(ins->symbol_table.symbols); + kfree(ins); + return NULL; +} + +void cs46xx_dsp_spos_destroy (struct snd_cs46xx * chip) +{ + int i; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins)) + return; + + guard(mutex)(&chip->spos_mutex); + for (i = 0; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) continue; + + cs46xx_dsp_proc_free_scb_desc ( (ins->scbs + i) ); +#ifdef CONFIG_PM_SLEEP + kfree(ins->scbs[i].data); +#endif + } + + kfree(ins->code.data); + vfree(ins->symbol_table.symbols); + kfree(ins->modules); + kfree(ins); +} + +static int dsp_load_parameter(struct snd_cs46xx *chip, + struct dsp_segment_desc *parameter) +{ + u32 doffset, dsize; + + if (!parameter) { + dev_dbg(chip->card->dev, + "dsp_spos: module got no parameter segment\n"); + return 0; + } + + doffset = (parameter->offset * 4 + DSP_PARAMETER_BYTE_OFFSET); + dsize = parameter->size * 4; + + dev_dbg(chip->card->dev, + "dsp_spos: downloading parameter data to chip (%08x-%08x)\n", + doffset,doffset + dsize); + if (snd_cs46xx_download (chip, parameter->data, doffset, dsize)) { + dev_err(chip->card->dev, + "dsp_spos: failed to download parameter data to DSP\n"); + return -EINVAL; + } + return 0; +} + +static int dsp_load_sample(struct snd_cs46xx *chip, + struct dsp_segment_desc *sample) +{ + u32 doffset, dsize; + + if (!sample) { + dev_dbg(chip->card->dev, + "dsp_spos: module got no sample segment\n"); + return 0; + } + + doffset = (sample->offset * 4 + DSP_SAMPLE_BYTE_OFFSET); + dsize = sample->size * 4; + + dev_dbg(chip->card->dev, + "dsp_spos: downloading sample data to chip (%08x-%08x)\n", + doffset,doffset + dsize); + + if (snd_cs46xx_download (chip,sample->data,doffset,dsize)) { + dev_err(chip->card->dev, + "dsp_spos: failed to sample data to DSP\n"); + return -EINVAL; + } + return 0; +} + +int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * module) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_segment_desc * code = get_segment_desc (module,SEGTYPE_SP_PROGRAM); + u32 doffset, dsize; + int err; + + if (ins->nmodules == DSP_MAX_MODULES - 1) { + dev_err(chip->card->dev, + "dsp_spos: to many modules loaded into DSP\n"); + return -ENOMEM; + } + + dev_dbg(chip->card->dev, + "dsp_spos: loading module %s into DSP\n", module->module_name); + + if (ins->nmodules == 0) { + dev_dbg(chip->card->dev, "dsp_spos: clearing parameter area\n"); + snd_cs46xx_clear_BA1(chip, DSP_PARAMETER_BYTE_OFFSET, DSP_PARAMETER_BYTE_SIZE); + } + + err = dsp_load_parameter(chip, get_segment_desc(module, + SEGTYPE_SP_PARAMETER)); + if (err < 0) + return err; + + if (ins->nmodules == 0) { + dev_dbg(chip->card->dev, "dsp_spos: clearing sample area\n"); + snd_cs46xx_clear_BA1(chip, DSP_SAMPLE_BYTE_OFFSET, DSP_SAMPLE_BYTE_SIZE); + } + + err = dsp_load_sample(chip, get_segment_desc(module, + SEGTYPE_SP_SAMPLE)); + if (err < 0) + return err; + + if (ins->nmodules == 0) { + dev_dbg(chip->card->dev, "dsp_spos: clearing code area\n"); + snd_cs46xx_clear_BA1(chip, DSP_CODE_BYTE_OFFSET, DSP_CODE_BYTE_SIZE); + } + + if (code == NULL) { + dev_dbg(chip->card->dev, + "dsp_spos: module got no code segment\n"); + } else { + if (ins->code.offset + code->size > DSP_CODE_BYTE_SIZE) { + dev_err(chip->card->dev, + "dsp_spos: no space available in DSP\n"); + return -ENOMEM; + } + + module->load_address = ins->code.offset; + module->overlay_begin_address = 0x000; + + /* if module has a code segment it must have + symbol table */ + if (snd_BUG_ON(!module->symbol_table.symbols)) + return -ENOMEM; + if (add_symbols(chip,module)) { + dev_err(chip->card->dev, + "dsp_spos: failed to load symbol table\n"); + return -ENOMEM; + } + + doffset = (code->offset * 4 + ins->code.offset * 4 + DSP_CODE_BYTE_OFFSET); + dsize = code->size * 4; + dev_dbg(chip->card->dev, + "dsp_spos: downloading code to chip (%08x-%08x)\n", + doffset,doffset + dsize); + + module->nfixups = shadow_and_reallocate_code(chip,code->data,code->size,module->overlay_begin_address); + + if (snd_cs46xx_download (chip,(ins->code.data + ins->code.offset),doffset,dsize)) { + dev_err(chip->card->dev, + "dsp_spos: failed to download code to DSP\n"); + return -EINVAL; + } + + ins->code.offset += code->size; + } + + /* NOTE: module segments and symbol table must be + statically allocated. Case that module data is + not generated by the ospparser */ + ins->modules[ins->nmodules] = *module; + ins->nmodules++; + + return 0; +} + +struct dsp_symbol_entry * +cs46xx_dsp_lookup_symbol (struct snd_cs46xx * chip, char * symbol_name, int symbol_type) +{ + int i; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + for ( i = 0; i < ins->symbol_table.nsymbols; ++i ) { + + if (ins->symbol_table.symbols[i].deleted) + continue; + + if (!strcmp(ins->symbol_table.symbols[i].symbol_name,symbol_name) && + ins->symbol_table.symbols[i].symbol_type == symbol_type) { + return (ins->symbol_table.symbols + i); + } + } + +#if 0 + dev_err(chip->card->dev, "dsp_spos: symbol <%s> type %02x not found\n", + symbol_name,symbol_type); +#endif + + return NULL; +} + + +#ifdef CONFIG_SND_PROC_FS +static struct dsp_symbol_entry * +cs46xx_dsp_lookup_symbol_addr (struct snd_cs46xx * chip, u32 address, int symbol_type) +{ + int i; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + for ( i = 0; i < ins->symbol_table.nsymbols; ++i ) { + + if (ins->symbol_table.symbols[i].deleted) + continue; + + if (ins->symbol_table.symbols[i].address == address && + ins->symbol_table.symbols[i].symbol_type == symbol_type) { + return (ins->symbol_table.symbols + i); + } + } + + + return NULL; +} + + +static void cs46xx_dsp_proc_symbol_table_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i; + + snd_iprintf(buffer, "SYMBOLS:\n"); + for ( i = 0; i < ins->symbol_table.nsymbols; ++i ) { + char *module_str = "system"; + + if (ins->symbol_table.symbols[i].deleted) + continue; + + if (ins->symbol_table.symbols[i].module != NULL) { + module_str = ins->symbol_table.symbols[i].module->module_name; + } + + + snd_iprintf(buffer, "%04X <%02X> %s [%s]\n", + ins->symbol_table.symbols[i].address, + ins->symbol_table.symbols[i].symbol_type, + ins->symbol_table.symbols[i].symbol_name, + module_str); + } +} + + +static void cs46xx_dsp_proc_modules_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i,j; + + guard(mutex)(&chip->spos_mutex); + snd_iprintf(buffer, "MODULES:\n"); + for ( i = 0; i < ins->nmodules; ++i ) { + snd_iprintf(buffer, "\n%s:\n", ins->modules[i].module_name); + snd_iprintf(buffer, " %d symbols\n", ins->modules[i].symbol_table.nsymbols); + snd_iprintf(buffer, " %d fixups\n", ins->modules[i].nfixups); + + for (j = 0; j < ins->modules[i].nsegments; ++ j) { + struct dsp_segment_desc * desc = (ins->modules[i].segments + j); + snd_iprintf(buffer, " segment %02x offset %08x size %08x\n", + desc->segment_type,desc->offset, desc->size); + } + } +} + +static void cs46xx_dsp_proc_task_tree_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i, j, col; + void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET; + + guard(mutex)(&chip->spos_mutex); + snd_iprintf(buffer, "TASK TREES:\n"); + for ( i = 0; i < ins->ntask; ++i) { + snd_iprintf(buffer,"\n%04x %s:\n",ins->tasks[i].address,ins->tasks[i].task_name); + + for (col = 0,j = 0;j < ins->tasks[i].size; j++,col++) { + u32 val; + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + val = readl(dst + (ins->tasks[i].address + j) * sizeof(u32)); + snd_iprintf(buffer,"%08x ",val); + } + } + + snd_iprintf(buffer,"\n"); +} + +static void cs46xx_dsp_proc_scb_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i; + + guard(mutex)(&chip->spos_mutex); + snd_iprintf(buffer, "SCB's:\n"); + for ( i = 0; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) + continue; + snd_iprintf(buffer,"\n%04x %s:\n\n",ins->scbs[i].address,ins->scbs[i].scb_name); + + if (ins->scbs[i].parent_scb_ptr != NULL) { + snd_iprintf(buffer,"parent [%s:%04x] ", + ins->scbs[i].parent_scb_ptr->scb_name, + ins->scbs[i].parent_scb_ptr->address); + } else snd_iprintf(buffer,"parent [none] "); + + snd_iprintf(buffer,"sub_list_ptr [%s:%04x]\nnext_scb_ptr [%s:%04x] task_entry [%s:%04x]\n", + ins->scbs[i].sub_list_ptr->scb_name, + ins->scbs[i].sub_list_ptr->address, + ins->scbs[i].next_scb_ptr->scb_name, + ins->scbs[i].next_scb_ptr->address, + ins->scbs[i].task_entry->symbol_name, + ins->scbs[i].task_entry->address); + } + + snd_iprintf(buffer,"\n"); +} + +static void cs46xx_dsp_proc_parameter_dump_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + /*struct dsp_spos_instance * ins = chip->dsp_spos_instance; */ + unsigned int i, col = 0; + void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET; + struct dsp_symbol_entry * symbol; + + for (i = 0;i < DSP_PARAMETER_BYTE_SIZE; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + symbol = cs46xx_dsp_lookup_symbol_addr(chip, i / sizeof(u32), SYMBOL_PARAMETER); + if (symbol) { + col = 0; + snd_iprintf (buffer,"\n%s:\n",symbol->symbol_name); + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ", i / (unsigned int)sizeof(u32)); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } +} + +static void cs46xx_dsp_proc_sample_dump_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_cs46xx *chip = entry->private_data; + int i,col = 0; + void __iomem *dst = chip->region.idx[2].remap_addr; + + snd_iprintf(buffer,"PCMREADER:\n"); + for (i = PCM_READER_BUF1;i < PCM_READER_BUF1 + 0x30; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + snd_iprintf(buffer,"\nMIX_SAMPLE_BUF1:\n"); + + col = 0; + for (i = MIX_SAMPLE_BUF1;i < MIX_SAMPLE_BUF1 + 0x40; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + snd_iprintf(buffer,"\nSRC_TASK_SCB1:\n"); + col = 0; + for (i = 0x2480 ; i < 0x2480 + 0x40 ; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + + snd_iprintf(buffer,"\nSPDIFO_BUFFER:\n"); + col = 0; + for (i = SPDIFO_IP_OUTPUT_BUFFER1;i < SPDIFO_IP_OUTPUT_BUFFER1 + 0x30; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + snd_iprintf(buffer,"\n...\n"); + col = 0; + + for (i = SPDIFO_IP_OUTPUT_BUFFER1+0xD0;i < SPDIFO_IP_OUTPUT_BUFFER1 + 0x110; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + + snd_iprintf(buffer,"\nOUTPUT_SNOOP:\n"); + col = 0; + for (i = OUTPUT_SNOOP_BUFFER;i < OUTPUT_SNOOP_BUFFER + 0x40; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + + snd_iprintf(buffer,"\nCODEC_INPUT_BUF1: \n"); + col = 0; + for (i = CODEC_INPUT_BUF1;i < CODEC_INPUT_BUF1 + 0x40; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } +#if 0 + snd_iprintf(buffer,"\nWRITE_BACK_BUF1: \n"); + col = 0; + for (i = WRITE_BACK_BUF1;i < WRITE_BACK_BUF1 + 0x40; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } +#endif + + snd_iprintf(buffer,"\nSPDIFI_IP_OUTPUT_BUFFER1: \n"); + col = 0; + for (i = SPDIFI_IP_OUTPUT_BUFFER1;i < SPDIFI_IP_OUTPUT_BUFFER1 + 0x80; i += sizeof(u32),col ++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + + if (col == 0) { + snd_iprintf(buffer, "%04X ",i); + } + + snd_iprintf(buffer,"%08X ",readl(dst + i)); + } + snd_iprintf(buffer,"\n"); +} + +int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip) +{ + struct snd_info_entry *entry; + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i; + + ins->snd_card = card; + + entry = snd_info_create_card_entry(card, "dsp", card->proc_root); + if (entry) + entry->mode = S_IFDIR | 0555; + ins->proc_dsp_dir = entry; + + if (!ins->proc_dsp_dir) + return -ENOMEM; + + entry = snd_info_create_card_entry(card, "spos_symbols", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_symbol_table_read); + + entry = snd_info_create_card_entry(card, "spos_modules", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_modules_read); + + entry = snd_info_create_card_entry(card, "parameter", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_parameter_dump_read); + + entry = snd_info_create_card_entry(card, "sample", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_sample_dump_read); + + entry = snd_info_create_card_entry(card, "task_tree", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_task_tree_read); + + entry = snd_info_create_card_entry(card, "scb_info", + ins->proc_dsp_dir); + if (entry) + snd_info_set_text_ops(entry, chip, + cs46xx_dsp_proc_scb_read); + + guard(mutex)(&chip->spos_mutex); + /* register/update SCB's entries on proc */ + for (i = 0; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) continue; + + cs46xx_dsp_proc_register_scb_desc (chip, (ins->scbs + i)); + } + + return 0; +} + +int cs46xx_dsp_proc_done (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i; + + if (!ins) + return 0; + + scoped_guard(mutex, &chip->spos_mutex) { + for (i = 0; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) + continue; + cs46xx_dsp_proc_free_scb_desc((ins->scbs + i)); + } + } + + snd_info_free_entry(ins->proc_dsp_dir); + ins->proc_dsp_dir = NULL; + + return 0; +} +#endif /* CONFIG_SND_PROC_FS */ + +static void _dsp_create_task_tree (struct snd_cs46xx *chip, u32 * task_data, + u32 dest, int size) +{ + void __iomem *spdst = chip->region.idx[1].remap_addr + + DSP_PARAMETER_BYTE_OFFSET + dest * sizeof(u32); + int i; + + for (i = 0; i < size; ++i) { + dev_dbg(chip->card->dev, "addr %p, val %08x\n", + spdst, task_data[i]); + writel(task_data[i],spdst); + spdst += sizeof(u32); + } +} + +static void _dsp_create_scb (struct snd_cs46xx *chip, u32 * scb_data, u32 dest) +{ + void __iomem *spdst = chip->region.idx[1].remap_addr + + DSP_PARAMETER_BYTE_OFFSET + dest * sizeof(u32); + int i; + + for (i = 0; i < 0x10; ++i) { + dev_dbg(chip->card->dev, "addr %p, val %08x\n", + spdst, scb_data[i]); + writel(scb_data[i],spdst); + spdst += sizeof(u32); + } +} + +static int find_free_scb_index (struct dsp_spos_instance * ins) +{ + int index = ins->nscb, i; + + for (i = ins->scb_highest_frag_index; i < ins->nscb; ++i) { + if (ins->scbs[i].deleted) { + index = i; + break; + } + } + + return index; +} + +static struct dsp_scb_descriptor * _map_scb (struct snd_cs46xx *chip, char * name, u32 dest) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * desc = NULL; + int index; + + if (ins->nscb == DSP_MAX_SCB_DESC - 1) { + dev_err(chip->card->dev, + "dsp_spos: got no place for other SCB\n"); + return NULL; + } + + index = find_free_scb_index (ins); + + memset(&ins->scbs[index], 0, sizeof(ins->scbs[index])); + strscpy(ins->scbs[index].scb_name, name); + ins->scbs[index].address = dest; + ins->scbs[index].index = index; + ins->scbs[index].ref_count = 1; + + desc = (ins->scbs + index); + ins->scbs[index].scb_symbol = add_symbol (chip, name, dest, SYMBOL_PARAMETER); + + if (index > ins->scb_highest_frag_index) + ins->scb_highest_frag_index = index; + + if (index == ins->nscb) + ins->nscb++; + + return desc; +} + +static struct dsp_task_descriptor * +_map_task_tree (struct snd_cs46xx *chip, char * name, u32 dest, u32 size) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_task_descriptor * desc = NULL; + + if (ins->ntask == DSP_MAX_TASK_DESC - 1) { + dev_err(chip->card->dev, + "dsp_spos: got no place for other TASK\n"); + return NULL; + } + + if (name) + strscpy(ins->tasks[ins->ntask].task_name, name); + else + strscpy(ins->tasks[ins->ntask].task_name, "(NULL)"); + ins->tasks[ins->ntask].address = dest; + ins->tasks[ins->ntask].size = size; + + /* quick find in list */ + ins->tasks[ins->ntask].index = ins->ntask; + desc = (ins->tasks + ins->ntask); + ins->ntask++; + + if (name) + add_symbol (chip,name,dest,SYMBOL_PARAMETER); + return desc; +} + +#define SCB_BYTES (0x10 * 4) + +struct dsp_scb_descriptor * +cs46xx_dsp_create_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, u32 dest) +{ + struct dsp_scb_descriptor * desc; + +#ifdef CONFIG_PM_SLEEP + /* copy the data for resume */ + scb_data = kmemdup(scb_data, SCB_BYTES, GFP_KERNEL); + if (!scb_data) + return NULL; +#endif + + desc = _map_scb (chip,name,dest); + if (desc) { + desc->data = scb_data; + _dsp_create_scb(chip,scb_data,dest); + } else { + dev_err(chip->card->dev, "dsp_spos: failed to map SCB\n"); +#ifdef CONFIG_PM_SLEEP + kfree(scb_data); +#endif + } + + return desc; +} + + +static struct dsp_task_descriptor * +cs46xx_dsp_create_task_tree (struct snd_cs46xx *chip, char * name, u32 * task_data, + u32 dest, int size) +{ + struct dsp_task_descriptor * desc; + + desc = _map_task_tree (chip,name,dest,size); + if (desc) { + desc->data = task_data; + _dsp_create_task_tree(chip,task_data,dest,size); + } else { + dev_err(chip->card->dev, "dsp_spos: failed to map TASK\n"); + } + + return desc; +} + +int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_symbol_entry * fg_task_tree_header_code; + struct dsp_symbol_entry * task_tree_header_code; + struct dsp_symbol_entry * task_tree_thread; + struct dsp_symbol_entry * null_algorithm; + struct dsp_symbol_entry * magic_snoop_task; + + struct dsp_scb_descriptor * timing_master_scb; + struct dsp_scb_descriptor * codec_out_scb; + struct dsp_scb_descriptor * codec_in_scb; + struct dsp_scb_descriptor * src_task_scb; + struct dsp_scb_descriptor * master_mix_scb; + struct dsp_scb_descriptor * rear_mix_scb; + struct dsp_scb_descriptor * record_mix_scb; + struct dsp_scb_descriptor * write_back_scb; + struct dsp_scb_descriptor * vari_decimate_scb; + struct dsp_scb_descriptor * rear_codec_out_scb; + struct dsp_scb_descriptor * clfe_codec_out_scb; + struct dsp_scb_descriptor * magic_snoop_scb; + + int fifo_addr, fifo_span, valid_slots; + + static const struct dsp_spos_control_block sposcb = { + /* 0 */ HFG_TREE_SCB,HFG_STACK, + /* 1 */ SPOSCB_ADDR,BG_TREE_SCB_ADDR, + /* 2 */ DSP_SPOS_DC,0, + /* 3 */ DSP_SPOS_DC,DSP_SPOS_DC, + /* 4 */ 0,0, + /* 5 */ DSP_SPOS_UU,0, + /* 6 */ FG_TASK_HEADER_ADDR,0, + /* 7 */ 0,0, + /* 8 */ DSP_SPOS_UU,DSP_SPOS_DC, + /* 9 */ 0, + /* A */ 0,HFG_FIRST_EXECUTE_MODE, + /* B */ DSP_SPOS_UU,DSP_SPOS_UU, + /* C */ DSP_SPOS_DC_DC, + /* D */ DSP_SPOS_DC_DC, + /* E */ DSP_SPOS_DC_DC, + /* F */ DSP_SPOS_DC_DC + }; + + cs46xx_dsp_create_task_tree(chip, "sposCB", (u32 *)&sposcb, SPOSCB_ADDR, 0x10); + + null_algorithm = cs46xx_dsp_lookup_symbol(chip, "NULLALGORITHM", SYMBOL_CODE); + if (null_algorithm == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol NULLALGORITHM not found\n"); + return -EIO; + } + + fg_task_tree_header_code = cs46xx_dsp_lookup_symbol(chip, "FGTASKTREEHEADERCODE", SYMBOL_CODE); + if (fg_task_tree_header_code == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol FGTASKTREEHEADERCODE not found\n"); + return -EIO; + } + + task_tree_header_code = cs46xx_dsp_lookup_symbol(chip, "TASKTREEHEADERCODE", SYMBOL_CODE); + if (task_tree_header_code == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol TASKTREEHEADERCODE not found\n"); + return -EIO; + } + + task_tree_thread = cs46xx_dsp_lookup_symbol(chip, "TASKTREETHREAD", SYMBOL_CODE); + if (task_tree_thread == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol TASKTREETHREAD not found\n"); + return -EIO; + } + + magic_snoop_task = cs46xx_dsp_lookup_symbol(chip, "MAGICSNOOPTASK", SYMBOL_CODE); + if (magic_snoop_task == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol MAGICSNOOPTASK not found\n"); + return -EIO; + } + + { + /* create the null SCB */ + static struct dsp_generic_scb null_scb = { + { 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + NULL_SCB_ADDR, NULL_SCB_ADDR, + 0, 0, 0, 0, 0, + { + 0,0, + 0,0, + } + }; + + null_scb.entry_point = null_algorithm->address; + ins->the_null_scb = cs46xx_dsp_create_scb(chip, "nullSCB", (u32 *)&null_scb, NULL_SCB_ADDR); + ins->the_null_scb->task_entry = null_algorithm; + ins->the_null_scb->sub_list_ptr = ins->the_null_scb; + ins->the_null_scb->next_scb_ptr = ins->the_null_scb; + ins->the_null_scb->parent_scb_ptr = NULL; + cs46xx_dsp_proc_register_scb_desc (chip,ins->the_null_scb); + } + + { + /* setup foreground task tree */ + static struct dsp_task_tree_control_block fg_task_tree_hdr = { + { FG_TASK_HEADER_ADDR | (DSP_SPOS_DC << 0x10), + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + 0x0000,DSP_SPOS_DC, + DSP_SPOS_DC, DSP_SPOS_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC,DSP_SPOS_DC }, + + { + BG_TREE_SCB_ADDR,TIMINGMASTER_SCB_ADDR, + 0, + FG_TASK_HEADER_ADDR + TCBData, + }, + + { + 4,0, + 1,0, + 2,SPOSCB_ADDR + HFGFlags, + 0,0, + FG_TASK_HEADER_ADDR + TCBContextBlk,FG_STACK + }, + + { + DSP_SPOS_DC,0, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DCDC, + DSP_SPOS_UU,1, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC + }, + { + FG_INTERVAL_TIMER_PERIOD,DSP_SPOS_UU, + 0,0 + } + }; + + fg_task_tree_hdr.links.entry_point = fg_task_tree_header_code->address; + fg_task_tree_hdr.context_blk.stack0 = task_tree_thread->address; + cs46xx_dsp_create_task_tree(chip,"FGtaskTreeHdr",(u32 *)&fg_task_tree_hdr,FG_TASK_HEADER_ADDR,0x35); + } + + + { + /* setup foreground task tree */ + static struct dsp_task_tree_control_block bg_task_tree_hdr = { + { DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC, DSP_SPOS_DC, + DSP_SPOS_DC, DSP_SPOS_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC_DC, + DSP_SPOS_DC,DSP_SPOS_DC }, + + { + NULL_SCB_ADDR,NULL_SCB_ADDR, /* Set up the background to do nothing */ + 0, + BG_TREE_SCB_ADDR + TCBData, + }, + + { + 9999,0, + 0,1, + 0,SPOSCB_ADDR + HFGFlags, + 0,0, + BG_TREE_SCB_ADDR + TCBContextBlk,BG_STACK + }, + + { + DSP_SPOS_DC,0, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DC,DSP_SPOS_DC, + DSP_SPOS_DCDC, + DSP_SPOS_UU,1, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC, + DSP_SPOS_DCDC + }, + { + BG_INTERVAL_TIMER_PERIOD,DSP_SPOS_UU, + 0,0 + } + }; + + bg_task_tree_hdr.links.entry_point = task_tree_header_code->address; + bg_task_tree_hdr.context_blk.stack0 = task_tree_thread->address; + cs46xx_dsp_create_task_tree(chip,"BGtaskTreeHdr",(u32 *)&bg_task_tree_hdr,BG_TREE_SCB_ADDR,0x35); + } + + /* create timing master SCB */ + timing_master_scb = cs46xx_dsp_create_timing_master_scb(chip); + + /* create the CODEC output task */ + codec_out_scb = cs46xx_dsp_create_codec_out_scb(chip,"CodecOutSCB_I",0x0010,0x0000, + MASTERMIX_SCB_ADDR, + CODECOUT_SCB_ADDR,timing_master_scb, + SCB_ON_PARENT_SUBLIST_SCB); + + if (!codec_out_scb) goto _fail_end; + /* create the master mix SCB */ + master_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"MasterMixSCB", + MIX_SAMPLE_BUF1,MASTERMIX_SCB_ADDR, + codec_out_scb, + SCB_ON_PARENT_SUBLIST_SCB); + ins->master_mix_scb = master_mix_scb; + + if (!master_mix_scb) goto _fail_end; + + /* create codec in */ + codec_in_scb = cs46xx_dsp_create_codec_in_scb(chip,"CodecInSCB",0x0010,0x00A0, + CODEC_INPUT_BUF1, + CODECIN_SCB_ADDR,codec_out_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!codec_in_scb) goto _fail_end; + ins->codec_in_scb = codec_in_scb; + + /* create write back scb */ + write_back_scb = cs46xx_dsp_create_mix_to_ostream_scb(chip,"WriteBackSCB", + WRITE_BACK_BUF1,WRITE_BACK_SPB, + WRITEBACK_SCB_ADDR, + timing_master_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!write_back_scb) goto _fail_end; + + { + static struct dsp_mix2_ostream_spb mix2_ostream_spb = { + 0x00020000, + 0x0000ffff + }; + + if (!cs46xx_dsp_create_task_tree(chip, NULL, + (u32 *)&mix2_ostream_spb, + WRITE_BACK_SPB, 2)) + goto _fail_end; + } + + /* input sample converter */ + vari_decimate_scb = cs46xx_dsp_create_vari_decimate_scb(chip,"VariDecimateSCB", + VARI_DECIMATE_BUF0, + VARI_DECIMATE_BUF1, + VARIDECIMATE_SCB_ADDR, + write_back_scb, + SCB_ON_PARENT_SUBLIST_SCB); + if (!vari_decimate_scb) goto _fail_end; + + /* create the record mixer SCB */ + record_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"RecordMixerSCB", + MIX_SAMPLE_BUF2, + RECORD_MIXER_SCB_ADDR, + vari_decimate_scb, + SCB_ON_PARENT_SUBLIST_SCB); + ins->record_mixer_scb = record_mix_scb; + + if (!record_mix_scb) goto _fail_end; + + valid_slots = snd_cs46xx_peekBA0(chip, BA0_ACOSV); + + if (snd_BUG_ON(chip->nr_ac97_codecs != 1 && chip->nr_ac97_codecs != 2)) + goto _fail_end; + + if (chip->nr_ac97_codecs == 1) { + /* output on slot 5 and 11 + on primary CODEC */ + fifo_addr = 0x20; + fifo_span = 0x60; + + /* enable slot 5 and 11 */ + valid_slots |= ACOSV_SLV5 | ACOSV_SLV11; + } else { + /* output on slot 7 and 8 + on secondary CODEC */ + fifo_addr = 0x40; + fifo_span = 0x10; + + /* enable slot 7 and 8 */ + valid_slots |= ACOSV_SLV7 | ACOSV_SLV8; + } + /* create CODEC tasklet for rear speakers output*/ + rear_codec_out_scb = cs46xx_dsp_create_codec_out_scb(chip,"CodecOutSCB_Rear",fifo_span,fifo_addr, + REAR_MIXER_SCB_ADDR, + REAR_CODECOUT_SCB_ADDR,codec_in_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!rear_codec_out_scb) goto _fail_end; + + + /* create the rear PCM channel mixer SCB */ + rear_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"RearMixerSCB", + MIX_SAMPLE_BUF3, + REAR_MIXER_SCB_ADDR, + rear_codec_out_scb, + SCB_ON_PARENT_SUBLIST_SCB); + ins->rear_mix_scb = rear_mix_scb; + if (!rear_mix_scb) goto _fail_end; + + if (chip->nr_ac97_codecs == 2) { + /* create CODEC tasklet for rear Center/LFE output + slot 6 and 9 on secondary CODEC */ + clfe_codec_out_scb = cs46xx_dsp_create_codec_out_scb(chip,"CodecOutSCB_CLFE",0x0030,0x0030, + CLFE_MIXER_SCB_ADDR, + CLFE_CODEC_SCB_ADDR, + rear_codec_out_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!clfe_codec_out_scb) goto _fail_end; + + + /* create the rear PCM channel mixer SCB */ + ins->center_lfe_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"CLFEMixerSCB", + MIX_SAMPLE_BUF4, + CLFE_MIXER_SCB_ADDR, + clfe_codec_out_scb, + SCB_ON_PARENT_SUBLIST_SCB); + if (!ins->center_lfe_mix_scb) goto _fail_end; + + /* enable slot 6 and 9 */ + valid_slots |= ACOSV_SLV6 | ACOSV_SLV9; + } else { + clfe_codec_out_scb = rear_codec_out_scb; + ins->center_lfe_mix_scb = rear_mix_scb; + } + + /* enable slots depending on CODEC configuration */ + snd_cs46xx_pokeBA0(chip, BA0_ACOSV, valid_slots); + + /* the magic snooper */ + magic_snoop_scb = cs46xx_dsp_create_magic_snoop_scb (chip,"MagicSnoopSCB_I",OUTPUTSNOOP_SCB_ADDR, + OUTPUT_SNOOP_BUFFER, + codec_out_scb, + clfe_codec_out_scb, + SCB_ON_PARENT_NEXT_SCB); + + + if (!magic_snoop_scb) goto _fail_end; + ins->ref_snoop_scb = magic_snoop_scb; + + /* SP IO access */ + if (!cs46xx_dsp_create_spio_write_scb(chip,"SPIOWriteSCB",SPIOWRITE_SCB_ADDR, + magic_snoop_scb, + SCB_ON_PARENT_NEXT_SCB)) + goto _fail_end; + + /* SPDIF input sampel rate converter */ + src_task_scb = cs46xx_dsp_create_src_task_scb(chip,"SrcTaskSCB_SPDIFI", + ins->spdif_in_sample_rate, + SRC_OUTPUT_BUF1, + SRC_DELAY_BUF1,SRCTASK_SCB_ADDR, + master_mix_scb, + SCB_ON_PARENT_SUBLIST_SCB,1); + + if (!src_task_scb) goto _fail_end; + cs46xx_src_unlink(chip,src_task_scb); + + /* NOTE: when we now how to detect the SPDIF input + sample rate we will use this SRC to adjust it */ + ins->spdif_in_src = src_task_scb; + + cs46xx_dsp_async_init(chip,timing_master_scb); + return 0; + + _fail_end: + dev_err(chip->card->dev, "dsp_spos: failed to setup SCB's in DSP\n"); + return -EINVAL; +} + +static int cs46xx_dsp_async_init (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * fg_entry) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_symbol_entry * s16_async_codec_input_task; + struct dsp_symbol_entry * spdifo_task; + struct dsp_symbol_entry * spdifi_task; + struct dsp_scb_descriptor * spdifi_scb_desc, * spdifo_scb_desc, * async_codec_scb_desc; + + s16_async_codec_input_task = cs46xx_dsp_lookup_symbol(chip, "S16_ASYNCCODECINPUTTASK", SYMBOL_CODE); + if (s16_async_codec_input_task == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol S16_ASYNCCODECINPUTTASK not found\n"); + return -EIO; + } + spdifo_task = cs46xx_dsp_lookup_symbol(chip, "SPDIFOTASK", SYMBOL_CODE); + if (spdifo_task == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol SPDIFOTASK not found\n"); + return -EIO; + } + + spdifi_task = cs46xx_dsp_lookup_symbol(chip, "SPDIFITASK", SYMBOL_CODE); + if (spdifi_task == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol SPDIFITASK not found\n"); + return -EIO; + } + + { + /* 0xBC0 */ + struct dsp_spdifoscb spdifo_scb = { + /* 0 */ DSP_SPOS_UUUU, + { + /* 1 */ 0xb0, + /* 2 */ 0, + /* 3 */ 0, + /* 4 */ 0, + }, + /* NOTE: the SPDIF output task read samples in mono + format, the AsynchFGTxSCB task writes to buffer + in stereo format + */ + /* 5 */ RSCONFIG_SAMPLE_16MONO + RSCONFIG_MODULO_256, + /* 6 */ ( SPDIFO_IP_OUTPUT_BUFFER1 << 0x10 ) | 0xFFFC, + /* 7 */ 0,0, + /* 8 */ 0, + /* 9 */ FG_TASK_HEADER_ADDR, NULL_SCB_ADDR, + /* A */ spdifo_task->address, + SPDIFO_SCB_INST + SPDIFOFIFOPointer, + { + /* B */ 0x0040, /*DSP_SPOS_UUUU,*/ + /* C */ 0x20ff, /*DSP_SPOS_UUUU,*/ + }, + /* D */ 0x804c,0, /* SPDIFOFIFOPointer:SPDIFOStatRegAddr; */ + /* E */ 0x0108,0x0001, /* SPDIFOStMoFormat:SPDIFOFIFOBaseAddr; */ + /* F */ DSP_SPOS_UUUU /* SPDIFOFree; */ + }; + + /* 0xBB0 */ + struct dsp_spdifiscb spdifi_scb = { + /* 0 */ DSP_SPOS_UULO,DSP_SPOS_UUHI, + /* 1 */ 0, + /* 2 */ 0, + /* 3 */ 1,4000, /* SPDIFICountLimit SPDIFICount */ + /* 4 */ DSP_SPOS_UUUU, /* SPDIFIStatusData */ + /* 5 */ 0,DSP_SPOS_UUHI, /* StatusData, Free4 */ + /* 6 */ DSP_SPOS_UUUU, /* Free3 */ + /* 7 */ DSP_SPOS_UU,DSP_SPOS_DC, /* Free2 BitCount*/ + /* 8 */ DSP_SPOS_UUUU, /* TempStatus */ + /* 9 */ SPDIFO_SCB_INST, NULL_SCB_ADDR, + /* A */ spdifi_task->address, + SPDIFI_SCB_INST + SPDIFIFIFOPointer, + /* NOTE: The SPDIF input task write the sample in mono + format from the HW FIFO, the AsynchFGRxSCB task reads + them in stereo + */ + /* B */ RSCONFIG_SAMPLE_16MONO + RSCONFIG_MODULO_128, + /* C */ (SPDIFI_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC, + /* D */ 0x8048,0, + /* E */ 0x01f0,0x0001, + /* F */ DSP_SPOS_UUUU /* SPDIN_STATUS monitor */ + }; + + /* 0xBA0 */ + struct dsp_async_codec_input_scb async_codec_input_scb = { + /* 0 */ DSP_SPOS_UUUU, + /* 1 */ 0, + /* 2 */ 0, + /* 3 */ 1,4000, + /* 4 */ 0x0118,0x0001, + /* 5 */ RSCONFIG_SAMPLE_16MONO + RSCONFIG_MODULO_64, + /* 6 */ (ASYNC_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC, + /* 7 */ DSP_SPOS_UU,0x3, + /* 8 */ DSP_SPOS_UUUU, + /* 9 */ SPDIFI_SCB_INST,NULL_SCB_ADDR, + /* A */ s16_async_codec_input_task->address, + HFG_TREE_SCB + AsyncCIOFIFOPointer, + + /* B */ RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64, + /* C */ (ASYNC_IP_OUTPUT_BUFFER1 << 0x10), /*(ASYNC_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC,*/ + +#ifdef UseASER1Input + /* short AsyncCIFIFOPointer:AsyncCIStatRegAddr; + Init. 0000:8042: for ASER1 + 0000:8044: for ASER2 */ + /* D */ 0x8042,0, + + /* short AsyncCIStMoFormat:AsyncCIFIFOBaseAddr; + Init 1 stero:8050 ASER1 + Init 0 mono:8070 ASER2 + Init 1 Stereo : 0100 ASER1 (Set by script) */ + /* E */ 0x0100,0x0001, + +#endif + +#ifdef UseASER2Input + /* short AsyncCIFIFOPointer:AsyncCIStatRegAddr; + Init. 0000:8042: for ASER1 + 0000:8044: for ASER2 */ + /* D */ 0x8044,0, + + /* short AsyncCIStMoFormat:AsyncCIFIFOBaseAddr; + Init 1 stero:8050 ASER1 + Init 0 mono:8070 ASER2 + Init 1 Stereo : 0100 ASER1 (Set by script) */ + /* E */ 0x0110,0x0001, + +#endif + + /* short AsyncCIOutputBufModulo:AsyncCIFree; + AsyncCIOutputBufModulo: The modulo size for + the output buffer of this task */ + /* F */ 0, /* DSP_SPOS_UUUU */ + }; + + spdifo_scb_desc = cs46xx_dsp_create_scb(chip,"SPDIFOSCB",(u32 *)&spdifo_scb,SPDIFO_SCB_INST); + + if (snd_BUG_ON(!spdifo_scb_desc)) + return -EIO; + spdifi_scb_desc = cs46xx_dsp_create_scb(chip,"SPDIFISCB",(u32 *)&spdifi_scb,SPDIFI_SCB_INST); + if (snd_BUG_ON(!spdifi_scb_desc)) + return -EIO; + async_codec_scb_desc = cs46xx_dsp_create_scb(chip,"AsynCodecInputSCB",(u32 *)&async_codec_input_scb, HFG_TREE_SCB); + if (snd_BUG_ON(!async_codec_scb_desc)) + return -EIO; + + async_codec_scb_desc->parent_scb_ptr = NULL; + async_codec_scb_desc->next_scb_ptr = spdifi_scb_desc; + async_codec_scb_desc->sub_list_ptr = ins->the_null_scb; + async_codec_scb_desc->task_entry = s16_async_codec_input_task; + + spdifi_scb_desc->parent_scb_ptr = async_codec_scb_desc; + spdifi_scb_desc->next_scb_ptr = spdifo_scb_desc; + spdifi_scb_desc->sub_list_ptr = ins->the_null_scb; + spdifi_scb_desc->task_entry = spdifi_task; + + spdifo_scb_desc->parent_scb_ptr = spdifi_scb_desc; + spdifo_scb_desc->next_scb_ptr = fg_entry; + spdifo_scb_desc->sub_list_ptr = ins->the_null_scb; + spdifo_scb_desc->task_entry = spdifo_task; + + /* this one is faked, as the parnet of SPDIFO task + is the FG task tree */ + fg_entry->parent_scb_ptr = spdifo_scb_desc; + + /* for proc fs */ + cs46xx_dsp_proc_register_scb_desc (chip,spdifo_scb_desc); + cs46xx_dsp_proc_register_scb_desc (chip,spdifi_scb_desc); + cs46xx_dsp_proc_register_scb_desc (chip,async_codec_scb_desc); + + /* Async MASTER ENABLE, affects both SPDIF input and output */ + snd_cs46xx_pokeBA0(chip, BA0_ASER_MASTER, 0x1 ); + } + + return 0; +} + +static void cs46xx_dsp_disable_spdif_hw (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + /* set SPDIF output FIFO slot */ + snd_cs46xx_pokeBA0(chip, BA0_ASER_FADDR, 0); + + /* SPDIF output MASTER ENABLE */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CONTROL, 0); + + /* right and left validate bit */ + /*cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_default);*/ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, 0x0); + + /* clear fifo pointer */ + cs46xx_poke_via_dsp (chip,SP_SPDIN_FIFOPTR, 0x0); + + /* monitor state */ + ins->spdif_status_out &= ~DSP_SPDIF_STATUS_HW_ENABLED; +} + +int cs46xx_dsp_enable_spdif_hw (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + /* if hw-ctrl already enabled, turn off to reset logic ... */ + cs46xx_dsp_disable_spdif_hw (chip); + udelay(50); + + /* set SPDIF output FIFO slot */ + snd_cs46xx_pokeBA0(chip, BA0_ASER_FADDR, ( 0x8000 | ((SP_SPDOUT_FIFO >> 4) << 4) )); + + /* SPDIF output MASTER ENABLE */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CONTROL, 0x80000000); + + /* right and left validate bit */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_default); + + /* monitor state */ + ins->spdif_status_out |= DSP_SPDIF_STATUS_HW_ENABLED; + + return 0; +} + +int cs46xx_dsp_enable_spdif_in (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + /* turn on amplifier */ + chip->active_ctrl(chip, 1); + chip->amplifier_ctrl(chip, 1); + + if (snd_BUG_ON(ins->asynch_rx_scb)) + return -EINVAL; + if (snd_BUG_ON(!ins->spdif_in_src)) + return -EINVAL; + + guard(mutex)(&chip->spos_mutex); + + if ( ! (ins->spdif_status_out & DSP_SPDIF_STATUS_INPUT_CTRL_ENABLED) ) { + /* time countdown enable */ + cs46xx_poke_via_dsp (chip,SP_ASER_COUNTDOWN, 0x80000005); + /* NOTE: 80000005 value is just magic. With all values + that I've tested this one seem to give the best result. + Got no explication why. (Benny) */ + + /* SPDIF input MASTER ENABLE */ + cs46xx_poke_via_dsp (chip,SP_SPDIN_CONTROL, 0x800003ff); + + ins->spdif_status_out |= DSP_SPDIF_STATUS_INPUT_CTRL_ENABLED; + } + + /* create and start the asynchronous receiver SCB */ + ins->asynch_rx_scb = cs46xx_dsp_create_asynch_fg_rx_scb(chip,"AsynchFGRxSCB", + ASYNCRX_SCB_ADDR, + SPDIFI_SCB_INST, + SPDIFI_IP_OUTPUT_BUFFER1, + ins->spdif_in_src, + SCB_ON_PARENT_SUBLIST_SCB); + + guard(spinlock_irq)(&chip->reg_lock); + + /* reset SPDIF input sample buffer pointer */ + /*snd_cs46xx_poke (chip, (SPDIFI_SCB_INST + 0x0c) << 2, + (SPDIFI_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC);*/ + + /* reset FIFO ptr */ + /*cs46xx_poke_via_dsp (chip,SP_SPDIN_FIFOPTR, 0x0);*/ + cs46xx_src_link(chip,ins->spdif_in_src); + + /* unmute SRC volume */ + cs46xx_dsp_scb_set_volume (chip,ins->spdif_in_src,0x7fff,0x7fff); + + /* set SPDIF input sample rate and unmute + NOTE: only 48khz support for SPDIF input this time */ + /* cs46xx_dsp_set_src_sample_rate(chip,ins->spdif_in_src,48000); */ + + /* monitor state */ + ins->spdif_status_in = 1; + + return 0; +} + +int cs46xx_dsp_disable_spdif_in (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins->asynch_rx_scb)) + return -EINVAL; + if (snd_BUG_ON(!ins->spdif_in_src)) + return -EINVAL; + + scoped_guard(mutex, &chip->spos_mutex) { + /* Remove the asynchronous receiver SCB */ + cs46xx_dsp_remove_scb(chip, ins->asynch_rx_scb); + ins->asynch_rx_scb = NULL; + + cs46xx_src_unlink(chip, ins->spdif_in_src); + + /* monitor state */ + ins->spdif_status_in = 0; + } + + /* restore amplifier */ + chip->active_ctrl(chip, -1); + chip->amplifier_ctrl(chip, -1); + + return 0; +} + +int cs46xx_dsp_enable_pcm_capture (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(ins->pcm_input)) + return -EINVAL; + if (snd_BUG_ON(!ins->ref_snoop_scb)) + return -EINVAL; + + guard(mutex)(&chip->spos_mutex); + ins->pcm_input = cs46xx_add_record_source(chip,ins->ref_snoop_scb,PCMSERIALIN_PCM_SCB_ADDR, + "PCMSerialInput_Wave"); + + return 0; +} + +int cs46xx_dsp_disable_pcm_capture (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins->pcm_input)) + return -EINVAL; + + guard(mutex)(&chip->spos_mutex); + cs46xx_dsp_remove_scb (chip,ins->pcm_input); + ins->pcm_input = NULL; + + return 0; +} + +int cs46xx_dsp_enable_adc_capture (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(ins->adc_input)) + return -EINVAL; + if (snd_BUG_ON(!ins->codec_in_scb)) + return -EINVAL; + + guard(mutex)(&chip->spos_mutex); + ins->adc_input = cs46xx_add_record_source(chip,ins->codec_in_scb,PCMSERIALIN_SCB_ADDR, + "PCMSerialInput_ADC"); + + return 0; +} + +int cs46xx_dsp_disable_adc_capture (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins->adc_input)) + return -EINVAL; + + guard(mutex)(&chip->spos_mutex); + cs46xx_dsp_remove_scb (chip,ins->adc_input); + ins->adc_input = NULL; + + return 0; +} + +int cs46xx_poke_via_dsp (struct snd_cs46xx *chip, u32 address, u32 data) +{ + u32 temp; + int i; + + /* santiy check the parameters. (These numbers are not 100% correct. They are + a rough guess from looking at the controller spec.) */ + if (address < 0x8000 || address >= 0x9000) + return -EINVAL; + + /* initialize the SP_IO_WRITE SCB with the data. */ + temp = ( address << 16 ) | ( address & 0x0000FFFF); /* offset 0 <-- address2 : address1 */ + + snd_cs46xx_poke(chip,( SPIOWRITE_SCB_ADDR << 2), temp); + snd_cs46xx_poke(chip,((SPIOWRITE_SCB_ADDR + 1) << 2), data); /* offset 1 <-- data1 */ + snd_cs46xx_poke(chip,((SPIOWRITE_SCB_ADDR + 2) << 2), data); /* offset 1 <-- data2 */ + + /* Poke this location to tell the task to start */ + snd_cs46xx_poke(chip,((SPIOWRITE_SCB_ADDR + 6) << 2), SPIOWRITE_SCB_ADDR << 0x10); + + /* Verify that the task ran */ + for (i=0; i<25; i++) { + udelay(125); + + temp = snd_cs46xx_peek(chip,((SPIOWRITE_SCB_ADDR + 6) << 2)); + if (temp == 0x00000000) + break; + } + + if (i == 25) { + dev_err(chip->card->dev, + "dsp_spos: SPIOWriteTask not responding\n"); + return -EBUSY; + } + + return 0; +} + +int cs46xx_dsp_set_dac_volume (struct snd_cs46xx * chip, u16 left, u16 right) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + + guard(mutex)(&chip->spos_mutex); + + /* main output */ + scb = ins->master_mix_scb->sub_list_ptr; + while (scb != ins->the_null_scb) { + cs46xx_dsp_scb_set_volume (chip,scb,left,right); + scb = scb->next_scb_ptr; + } + + /* rear output */ + scb = ins->rear_mix_scb->sub_list_ptr; + while (scb != ins->the_null_scb) { + cs46xx_dsp_scb_set_volume (chip,scb,left,right); + scb = scb->next_scb_ptr; + } + + ins->dac_volume_left = left; + ins->dac_volume_right = right; + + return 0; +} + +int cs46xx_dsp_set_iec958_volume (struct snd_cs46xx * chip, u16 left, u16 right) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + guard(mutex)(&chip->spos_mutex); + + if (ins->asynch_rx_scb != NULL) + cs46xx_dsp_scb_set_volume (chip,ins->asynch_rx_scb, + left,right); + + ins->spdif_input_volume_left = left; + ins->spdif_input_volume_right = right; + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +int cs46xx_dsp_resume(struct snd_cs46xx * chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i, err; + + /* clear parameter, sample and code areas */ + snd_cs46xx_clear_BA1(chip, DSP_PARAMETER_BYTE_OFFSET, + DSP_PARAMETER_BYTE_SIZE); + snd_cs46xx_clear_BA1(chip, DSP_SAMPLE_BYTE_OFFSET, + DSP_SAMPLE_BYTE_SIZE); + snd_cs46xx_clear_BA1(chip, DSP_CODE_BYTE_OFFSET, DSP_CODE_BYTE_SIZE); + + for (i = 0; i < ins->nmodules; i++) { + struct dsp_module_desc *module = &ins->modules[i]; + struct dsp_segment_desc *seg; + u32 doffset, dsize; + + seg = get_segment_desc(module, SEGTYPE_SP_PARAMETER); + err = dsp_load_parameter(chip, seg); + if (err < 0) + return err; + + seg = get_segment_desc(module, SEGTYPE_SP_SAMPLE); + err = dsp_load_sample(chip, seg); + if (err < 0) + return err; + + seg = get_segment_desc(module, SEGTYPE_SP_PROGRAM); + if (!seg) + continue; + + doffset = seg->offset * 4 + module->load_address * 4 + + DSP_CODE_BYTE_OFFSET; + dsize = seg->size * 4; + err = snd_cs46xx_download(chip, + ins->code.data + module->load_address, + doffset, dsize); + if (err < 0) + return err; + } + + for (i = 0; i < ins->ntask; i++) { + struct dsp_task_descriptor *t = &ins->tasks[i]; + _dsp_create_task_tree(chip, t->data, t->address, t->size); + } + + for (i = 0; i < ins->nscb; i++) { + struct dsp_scb_descriptor *s = &ins->scbs[i]; + if (s->deleted) + continue; + _dsp_create_scb(chip, s->data, s->address); + } + for (i = 0; i < ins->nscb; i++) { + struct dsp_scb_descriptor *s = &ins->scbs[i]; + if (s->deleted) + continue; + if (s->updated) + cs46xx_dsp_spos_update_scb(chip, s); + if (s->volume_set) + cs46xx_dsp_scb_set_volume(chip, s, + s->volume[0], s->volume[1]); + } + if (ins->spdif_status_out & DSP_SPDIF_STATUS_HW_ENABLED) { + cs46xx_dsp_enable_spdif_hw(chip); + snd_cs46xx_poke(chip, (ins->ref_snoop_scb->address + 2) << 2, + (OUTPUT_SNOOP_BUFFER + 0x10) << 0x10); + if (ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) + cs46xx_poke_via_dsp(chip, SP_SPDOUT_CSUV, + ins->spdif_csuv_stream); + } + if (chip->dsp_spos_instance->spdif_status_in) { + cs46xx_poke_via_dsp(chip, SP_ASER_COUNTDOWN, 0x80000005); + cs46xx_poke_via_dsp(chip, SP_SPDIN_CONTROL, 0x800003ff); + } + return 0; +} +#endif diff --git a/sound/pci/cs46xx/dsp_spos.h b/sound/pci/cs46xx/dsp_spos.h new file mode 100644 index 000000000..a4853c748 --- /dev/null +++ b/sound/pci/cs46xx/dsp_spos.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + */ + +/* + * 2002-07 Benny Sjostrand benny@hostmobility.com + */ + +#ifdef CONFIG_SND_CS46XX_NEW_DSP /* hack ... */ +#ifndef __DSP_SPOS_H__ +#define __DSP_SPOS_H__ + +#define DSP_MAX_SYMBOLS 1024 +#define DSP_MAX_MODULES 64 + +#define DSP_CODE_BYTE_SIZE 0x00007000UL +#define DSP_PARAMETER_BYTE_SIZE 0x00003000UL +#define DSP_SAMPLE_BYTE_SIZE 0x00003800UL +#define DSP_PARAMETER_BYTE_OFFSET 0x00000000UL +#define DSP_SAMPLE_BYTE_OFFSET 0x00010000UL +#define DSP_CODE_BYTE_OFFSET 0x00020000UL + +#define WIDE_INSTR_MASK 0x0040 +#define WIDE_LADD_INSTR_MASK 0x0380 + +/* this instruction types + needs to be reallocated when load + code into DSP */ +enum wide_opcode { + WIDE_FOR_BEGIN_LOOP = 0x20, + WIDE_FOR_BEGIN_LOOP2, + + WIDE_COND_GOTO_ADDR = 0x30, + WIDE_COND_GOTO_CALL, + + WIDE_TBEQ_COND_GOTO_ADDR = 0x70, + WIDE_TBEQ_COND_CALL_ADDR, + WIDE_TBEQ_NCOND_GOTO_ADDR, + WIDE_TBEQ_NCOND_CALL_ADDR, + WIDE_TBEQ_COND_GOTO1_ADDR, + WIDE_TBEQ_COND_CALL1_ADDR, + WIDE_TBEQ_NCOND_GOTOI_ADDR, + WIDE_TBEQ_NCOND_CALL1_ADDR, +}; + +/* SAMPLE segment */ +#define VARI_DECIMATE_BUF1 0x0000 +#define WRITE_BACK_BUF1 0x0400 +#define CODEC_INPUT_BUF1 0x0500 +#define PCM_READER_BUF1 0x0600 +#define SRC_DELAY_BUF1 0x0680 +#define VARI_DECIMATE_BUF0 0x0780 +#define SRC_OUTPUT_BUF1 0x07A0 +#define ASYNC_IP_OUTPUT_BUFFER1 0x0A00 +#define OUTPUT_SNOOP_BUFFER 0x0B00 +#define SPDIFI_IP_OUTPUT_BUFFER1 0x0E00 +#define SPDIFO_IP_OUTPUT_BUFFER1 0x1000 +#define MIX_SAMPLE_BUF1 0x1400 +#define MIX_SAMPLE_BUF2 0x2E80 +#define MIX_SAMPLE_BUF3 0x2F00 +#define MIX_SAMPLE_BUF4 0x2F80 +#define MIX_SAMPLE_BUF5 0x3000 + +/* Task stack address */ +#define HFG_STACK 0x066A +#define FG_STACK 0x066E +#define BG_STACK 0x068E + +/* SCB's addresses */ +#define SPOSCB_ADDR 0x070 +#define BG_TREE_SCB_ADDR 0x635 +#define NULL_SCB_ADDR 0x000 +#define TIMINGMASTER_SCB_ADDR 0x010 +#define CODECOUT_SCB_ADDR 0x020 +#define PCMREADER_SCB_ADDR 0x030 +#define WRITEBACK_SCB_ADDR 0x040 +#define CODECIN_SCB_ADDR 0x080 +#define MASTERMIX_SCB_ADDR 0x090 +#define SRCTASK_SCB_ADDR 0x0A0 +#define VARIDECIMATE_SCB_ADDR 0x0B0 +#define PCMSERIALIN_SCB_ADDR 0x0C0 +#define FG_TASK_HEADER_ADDR 0x600 +#define ASYNCTX_SCB_ADDR 0x0E0 +#define ASYNCRX_SCB_ADDR 0x0F0 +#define SRCTASKII_SCB_ADDR 0x100 +#define OUTPUTSNOOP_SCB_ADDR 0x110 +#define PCMSERIALINII_SCB_ADDR 0x120 +#define SPIOWRITE_SCB_ADDR 0x130 +#define REAR_CODECOUT_SCB_ADDR 0x140 +#define OUTPUTSNOOPII_SCB_ADDR 0x150 +#define PCMSERIALIN_PCM_SCB_ADDR 0x160 +#define RECORD_MIXER_SCB_ADDR 0x170 +#define REAR_MIXER_SCB_ADDR 0x180 +#define CLFE_MIXER_SCB_ADDR 0x190 +#define CLFE_CODEC_SCB_ADDR 0x1A0 + +/* hyperforground SCB's*/ +#define HFG_TREE_SCB 0xBA0 +#define SPDIFI_SCB_INST 0xBB0 +#define SPDIFO_SCB_INST 0xBC0 +#define WRITE_BACK_SPB 0x0D0 + +/* offsets */ +#define AsyncCIOFIFOPointer 0xd +#define SPDIFOFIFOPointer 0xd +#define SPDIFIFIFOPointer 0xd +#define TCBData 0xb +#define HFGFlags 0xa +#define TCBContextBlk 0x10 +#define AFGTxAccumPhi 0x4 +#define SCBsubListPtr 0x9 +#define SCBfuncEntryPtr 0xA +#define SRCCorPerGof 0x2 +#define SRCPhiIncr6Int26Frac 0xd +#define SCBVolumeCtrl 0xe + +/* conf */ +#define UseASER1Input 1 + + + +/* + * The following defines are for the flags in the rsConfig01/23 registers of + * the SP. + */ + +#define RSCONFIG_MODULO_SIZE_MASK 0x0000000FL +#define RSCONFIG_MODULO_16 0x00000001L +#define RSCONFIG_MODULO_32 0x00000002L +#define RSCONFIG_MODULO_64 0x00000003L +#define RSCONFIG_MODULO_128 0x00000004L +#define RSCONFIG_MODULO_256 0x00000005L +#define RSCONFIG_MODULO_512 0x00000006L +#define RSCONFIG_MODULO_1024 0x00000007L +#define RSCONFIG_MODULO_4 0x00000008L +#define RSCONFIG_MODULO_8 0x00000009L +#define RSCONFIG_SAMPLE_SIZE_MASK 0x000000C0L +#define RSCONFIG_SAMPLE_8MONO 0x00000000L +#define RSCONFIG_SAMPLE_8STEREO 0x00000040L +#define RSCONFIG_SAMPLE_16MONO 0x00000080L +#define RSCONFIG_SAMPLE_16STEREO 0x000000C0L +#define RSCONFIG_UNDERRUN_ZERO 0x00004000L +#define RSCONFIG_DMA_TO_HOST 0x00008000L +#define RSCONFIG_STREAM_NUM_MASK 0x00FF0000L +#define RSCONFIG_MAX_DMA_SIZE_MASK 0x1F000000L +#define RSCONFIG_DMA_ENABLE 0x20000000L +#define RSCONFIG_PRIORITY_MASK 0xC0000000L +#define RSCONFIG_PRIORITY_HIGH 0x00000000L +#define RSCONFIG_PRIORITY_MEDIUM_HIGH 0x40000000L +#define RSCONFIG_PRIORITY_MEDIUM_LOW 0x80000000L +#define RSCONFIG_PRIORITY_LOW 0xC0000000L +#define RSCONFIG_STREAM_NUM_SHIFT 16L +#define RSCONFIG_MAX_DMA_SIZE_SHIFT 24L + +/* SP constants */ +#define FG_INTERVAL_TIMER_PERIOD 0x0051 +#define BG_INTERVAL_TIMER_PERIOD 0x0100 + + +/* Only SP accessible registers */ +#define SP_ASER_COUNTDOWN 0x8040 +#define SP_SPDOUT_FIFO 0x0108 +#define SP_SPDIN_MI_FIFO 0x01E0 +#define SP_SPDIN_D_FIFO 0x01F0 +#define SP_SPDIN_STATUS 0x8048 +#define SP_SPDIN_CONTROL 0x8049 +#define SP_SPDIN_FIFOPTR 0x804A +#define SP_SPDOUT_STATUS 0x804C +#define SP_SPDOUT_CONTROL 0x804D +#define SP_SPDOUT_CSUV 0x808E + +static inline u8 _wrap_all_bits (u8 val) +{ + u8 wrapped; + + /* wrap all 8 bits */ + wrapped = + ((val & 0x1 ) << 7) | + ((val & 0x2 ) << 5) | + ((val & 0x4 ) << 3) | + ((val & 0x8 ) << 1) | + ((val & 0x10) >> 1) | + ((val & 0x20) >> 3) | + ((val & 0x40) >> 5) | + ((val & 0x80) >> 7); + + return wrapped; +} + +static inline void cs46xx_dsp_spos_update_scb (struct snd_cs46xx * chip, + struct dsp_scb_descriptor * scb) +{ + /* update nextSCB and subListPtr in SCB */ + snd_cs46xx_poke(chip, + (scb->address + SCBsubListPtr) << 2, + (scb->sub_list_ptr->address << 0x10) | + (scb->next_scb_ptr->address)); + scb->updated = 1; +} + +static inline void cs46xx_dsp_scb_set_volume (struct snd_cs46xx * chip, + struct dsp_scb_descriptor * scb, + u16 left, u16 right) +{ + unsigned int val = ((0xffff - left) << 16 | (0xffff - right)); + + snd_cs46xx_poke(chip, (scb->address + SCBVolumeCtrl) << 2, val); + snd_cs46xx_poke(chip, (scb->address + SCBVolumeCtrl + 1) << 2, val); + scb->volume_set = 1; + scb->volume[0] = left; + scb->volume[1] = right; +} +#endif /* __DSP_SPOS_H__ */ +#endif /* CONFIG_SND_CS46XX_NEW_DSP */ diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c new file mode 100644 index 000000000..fd1936502 --- /dev/null +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -0,0 +1,1749 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* + * 2002-07 Benny Sjostrand benny@hostmobility.com + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "cs46xx.h" + +#include "cs46xx_lib.h" +#include "dsp_spos.h" + +struct proc_scb_info { + struct dsp_scb_descriptor * scb_desc; + struct snd_cs46xx *chip; +}; + +static void remove_symbol (struct snd_cs46xx * chip, struct dsp_symbol_entry * symbol) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int symbol_index = (int)(symbol - ins->symbol_table.symbols); + + if (snd_BUG_ON(ins->symbol_table.nsymbols <= 0)) + return; + if (snd_BUG_ON(symbol_index < 0 || + symbol_index >= ins->symbol_table.nsymbols)) + return; + + ins->symbol_table.symbols[symbol_index].deleted = 1; + + if (symbol_index < ins->symbol_table.highest_frag_index) { + ins->symbol_table.highest_frag_index = symbol_index; + } + + if (symbol_index == ins->symbol_table.nsymbols - 1) + ins->symbol_table.nsymbols --; + + if (ins->symbol_table.highest_frag_index > ins->symbol_table.nsymbols) { + ins->symbol_table.highest_frag_index = ins->symbol_table.nsymbols; + } + +} + +#ifdef CONFIG_SND_PROC_FS +static void cs46xx_dsp_proc_scb_info_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct proc_scb_info * scb_info = entry->private_data; + struct dsp_scb_descriptor * scb = scb_info->scb_desc; + struct snd_cs46xx *chip = scb_info->chip; + int j,col; + void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET; + + guard(mutex)(&chip->spos_mutex); + snd_iprintf(buffer,"%04x %s:\n",scb->address,scb->scb_name); + + for (col = 0,j = 0;j < 0x10; j++,col++) { + if (col == 4) { + snd_iprintf(buffer,"\n"); + col = 0; + } + snd_iprintf(buffer,"%08x ",readl(dst + (scb->address + j) * sizeof(u32))); + } + + snd_iprintf(buffer,"\n"); + + if (scb->parent_scb_ptr != NULL) { + snd_iprintf(buffer,"parent [%s:%04x] ", + scb->parent_scb_ptr->scb_name, + scb->parent_scb_ptr->address); + } else snd_iprintf(buffer,"parent [none] "); + + snd_iprintf(buffer,"sub_list_ptr [%s:%04x]\nnext_scb_ptr [%s:%04x] task_entry [%s:%04x]\n", + scb->sub_list_ptr->scb_name, + scb->sub_list_ptr->address, + scb->next_scb_ptr->scb_name, + scb->next_scb_ptr->address, + scb->task_entry->symbol_name, + scb->task_entry->address); + + snd_iprintf(buffer,"index [%d] ref_count [%d]\n",scb->index,scb->ref_count); +} +#endif + +static void _dsp_unlink_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * scb) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if ( scb->parent_scb_ptr ) { + /* unlink parent SCB */ + if (snd_BUG_ON(scb->parent_scb_ptr->sub_list_ptr != scb && + scb->parent_scb_ptr->next_scb_ptr != scb)) + return; + + if (scb->parent_scb_ptr->sub_list_ptr == scb) { + + if (scb->next_scb_ptr == ins->the_null_scb) { + /* last and only node in parent sublist */ + scb->parent_scb_ptr->sub_list_ptr = scb->sub_list_ptr; + + if (scb->sub_list_ptr != ins->the_null_scb) { + scb->sub_list_ptr->parent_scb_ptr = scb->parent_scb_ptr; + } + scb->sub_list_ptr = ins->the_null_scb; + } else { + /* first node in parent sublist */ + scb->parent_scb_ptr->sub_list_ptr = scb->next_scb_ptr; + + if (scb->next_scb_ptr != ins->the_null_scb) { + /* update next node parent ptr. */ + scb->next_scb_ptr->parent_scb_ptr = scb->parent_scb_ptr; + } + scb->next_scb_ptr = ins->the_null_scb; + } + } else { + scb->parent_scb_ptr->next_scb_ptr = scb->next_scb_ptr; + + if (scb->next_scb_ptr != ins->the_null_scb) { + /* update next node parent ptr. */ + scb->next_scb_ptr->parent_scb_ptr = scb->parent_scb_ptr; + } + scb->next_scb_ptr = ins->the_null_scb; + } + + /* update parent first entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip,scb->parent_scb_ptr); + + /* then update entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip,scb); + + scb->parent_scb_ptr = NULL; + } +} + +static void _dsp_clear_sample_buffer (struct snd_cs46xx *chip, u32 sample_buffer_addr, + int dword_count) +{ + void __iomem *dst = chip->region.idx[2].remap_addr + sample_buffer_addr; + int i; + + for (i = 0; i < dword_count ; ++i ) { + writel(0, dst); + dst += 4; + } +} + +void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * scb) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + /* check integrety */ + if (snd_BUG_ON(scb->index < 0 || + scb->index >= ins->nscb || + (ins->scbs + scb->index) != scb)) + return; + +#if 0 + /* can't remove a SCB with childs before + removing childs first */ + if (snd_BUG_ON(scb->sub_list_ptr != ins->the_null_scb || + scb->next_scb_ptr != ins->the_null_scb)) + goto _end; +#endif + + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + _dsp_unlink_scb(chip, scb); + } + + cs46xx_dsp_proc_free_scb_desc(scb); + if (snd_BUG_ON(!scb->scb_symbol)) + return; + remove_symbol (chip,scb->scb_symbol); + + ins->scbs[scb->index].deleted = 1; +#ifdef CONFIG_PM_SLEEP + kfree(ins->scbs[scb->index].data); + ins->scbs[scb->index].data = NULL; +#endif + + if (scb->index < ins->scb_highest_frag_index) + ins->scb_highest_frag_index = scb->index; + + if (scb->index == ins->nscb - 1) { + ins->nscb --; + } + + if (ins->scb_highest_frag_index > ins->nscb) { + ins->scb_highest_frag_index = ins->nscb; + } +} + + +#ifdef CONFIG_SND_PROC_FS +void cs46xx_dsp_proc_free_scb_desc (struct dsp_scb_descriptor * scb) +{ + if (scb->proc_info) { + struct proc_scb_info * scb_info = scb->proc_info->private_data; + struct snd_cs46xx *chip = scb_info->chip; + + dev_dbg(chip->card->dev, + "cs46xx_dsp_proc_free_scb_desc: freeing %s\n", + scb->scb_name); + + snd_info_free_entry(scb->proc_info); + scb->proc_info = NULL; + + kfree (scb_info); + } +} + +void cs46xx_dsp_proc_register_scb_desc (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * scb) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct snd_info_entry * entry; + struct proc_scb_info * scb_info; + + /* register to proc */ + if (ins->snd_card != NULL && ins->proc_dsp_dir != NULL && + scb->proc_info == NULL) { + + entry = snd_info_create_card_entry(ins->snd_card, scb->scb_name, + ins->proc_dsp_dir); + if (entry) { + scb_info = kmalloc_obj(struct proc_scb_info); + if (!scb_info) { + snd_info_free_entry(entry); + entry = NULL; + goto out; + } + + scb_info->chip = chip; + scb_info->scb_desc = scb; + snd_info_set_text_ops(entry, scb_info, + cs46xx_dsp_proc_scb_info_read); + } +out: + scb->proc_info = entry; + } +} +#endif /* CONFIG_SND_PROC_FS */ + +static struct dsp_scb_descriptor * +_dsp_create_generic_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, u32 dest, + struct dsp_symbol_entry * task_entry, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + + if (snd_BUG_ON(!ins->the_null_scb)) + return NULL; + + /* fill the data that will be wroten to DSP */ + scb_data[SCBsubListPtr] = + (ins->the_null_scb->address << 0x10) | ins->the_null_scb->address; + + scb_data[SCBfuncEntryPtr] &= 0xFFFF0000; + scb_data[SCBfuncEntryPtr] |= task_entry->address; + + dev_dbg(chip->card->dev, "dsp_spos: creating SCB <%s>\n", name); + + scb = cs46xx_dsp_create_scb(chip,name,scb_data,dest); + + + scb->sub_list_ptr = ins->the_null_scb; + scb->next_scb_ptr = ins->the_null_scb; + + scb->parent_scb_ptr = parent_scb; + scb->task_entry = task_entry; + + + /* update parent SCB */ + if (scb->parent_scb_ptr) { +#if 0 + dev_dbg(chip->card->dev, + "scb->parent_scb_ptr = %s\n", + scb->parent_scb_ptr->scb_name); + dev_dbg(chip->card->dev, + "scb->parent_scb_ptr->next_scb_ptr = %s\n", + scb->parent_scb_ptr->next_scb_ptr->scb_name); + dev_dbg(chip->card->dev, + "scb->parent_scb_ptr->sub_list_ptr = %s\n", + scb->parent_scb_ptr->sub_list_ptr->scb_name); +#endif + /* link to parent SCB */ + if (scb_child_type == SCB_ON_PARENT_NEXT_SCB) { + if (snd_BUG_ON(scb->parent_scb_ptr->next_scb_ptr != + ins->the_null_scb)) + return NULL; + + scb->parent_scb_ptr->next_scb_ptr = scb; + + } else if (scb_child_type == SCB_ON_PARENT_SUBLIST_SCB) { + if (snd_BUG_ON(scb->parent_scb_ptr->sub_list_ptr != + ins->the_null_scb)) + return NULL; + + scb->parent_scb_ptr->sub_list_ptr = scb; + } else { + snd_BUG(); + } + + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + /* update entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip, scb->parent_scb_ptr); + } + } + + + cs46xx_dsp_proc_register_scb_desc (chip,scb); + + return scb; +} + +static struct dsp_scb_descriptor * +cs46xx_dsp_create_generic_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, + u32 dest, char * task_entry_name, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_symbol_entry * task_entry; + + task_entry = cs46xx_dsp_lookup_symbol (chip,task_entry_name, + SYMBOL_CODE); + + if (task_entry == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol %s not found\n", task_entry_name); + return NULL; + } + + return _dsp_create_generic_scb (chip,name,scb_data,dest,task_entry, + parent_scb,scb_child_type); +} + +struct dsp_scb_descriptor * +cs46xx_dsp_create_timing_master_scb (struct snd_cs46xx *chip) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_timing_master_scb timing_master_scb = { + { 0, + 0, + 0, + 0 + }, + { 0, + 0, + 0, + 0, + 0 + }, + 0,0, + 0,NULL_SCB_ADDR, + 0,0, /* extraSampleAccum:TMreserved */ + 0,0, /* codecFIFOptr:codecFIFOsyncd */ + 0x0001,0x8000, /* fracSampAccumQm1:TMfrmsLeftInGroup */ + 0x0001,0x0000, /* fracSampCorrectionQm1:TMfrmGroupLength */ + 0x00060000 /* nSampPerFrmQ15 */ + }; + + scb = cs46xx_dsp_create_generic_scb(chip,"TimingMasterSCBInst",(u32 *)&timing_master_scb, + TIMINGMASTER_SCB_ADDR, + "TIMINGMASTER",NULL,SCB_NO_PARENT); + + return scb; +} + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_out_scb(struct snd_cs46xx * chip, char * codec_name, + u16 channel_disp, u16 fifo_addr, u16 child_scb_addr, + u32 dest, struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_codec_output_scb codec_out_scb = { + { 0, + 0, + 0, + 0 + }, + { + 0, + 0, + 0, + 0, + 0 + }, + 0,0, + 0,NULL_SCB_ADDR, + 0, /* COstrmRsConfig */ + 0, /* COstrmBufPtr */ + channel_disp,fifo_addr, /* leftChanBaseIOaddr:rightChanIOdisp */ + 0x0000,0x0080, /* (!AC97!) COexpVolChangeRate:COscaleShiftCount */ + 0,child_scb_addr /* COreserved - need child scb to work with rom code */ + }; + + + scb = cs46xx_dsp_create_generic_scb(chip,codec_name,(u32 *)&codec_out_scb, + dest,"S16_CODECOUTPUTTASK",parent_scb, + scb_child_type); + + return scb; +} + +struct dsp_scb_descriptor * +cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name, + u16 channel_disp, u16 fifo_addr, u16 sample_buffer_addr, + u32 dest, struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + struct dsp_codec_input_scb codec_input_scb = { + { 0, + 0, + 0, + 0 + }, + { + 0, + 0, + 0, + 0, + 0 + }, + +#if 0 /* cs4620 */ + SyncIOSCB,NULL_SCB_ADDR +#else + 0 , 0, +#endif + 0,0, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64, /* strmRsConfig */ + sample_buffer_addr << 0x10, /* strmBufPtr; defined as a dword ptr, used as a byte ptr */ + channel_disp,fifo_addr, /* (!AC97!) leftChanBaseINaddr=AC97primary + link input slot 3 :rightChanINdisp=""slot 4 */ + 0x0000,0x0000, /* (!AC97!) ????:scaleShiftCount; no shift needed + because AC97 is already 20 bits */ + 0x80008000 /* ??clw cwcgame.scb has 0 */ + }; + + scb = cs46xx_dsp_create_generic_scb(chip,codec_name,(u32 *)&codec_input_scb, + dest,"S16_CODECINPUTTASK",parent_scb, + scb_child_type); + return scb; +} + + +static struct dsp_scb_descriptor * +cs46xx_dsp_create_pcm_reader_scb(struct snd_cs46xx * chip, char * scb_name, + u16 sample_buffer_addr, u32 dest, + int virtual_channel, u32 playback_hw_addr, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + + struct dsp_generic_scb pcm_reader_scb = { + + /* + Play DMA Task xfers data from host buffer to SP buffer + init/runtime variables: + PlayAC: Play Audio Data Conversion - SCB loc: 2nd dword, mask: 0x0000F000L + DATA_FMT_16BIT_ST_LTLEND(0x00000000L) from 16-bit stereo, little-endian + DATA_FMT_8_BIT_ST_SIGNED(0x00001000L) from 8-bit stereo, signed + DATA_FMT_16BIT_MN_LTLEND(0x00002000L) from 16-bit mono, little-endian + DATA_FMT_8_BIT_MN_SIGNED(0x00003000L) from 8-bit mono, signed + DATA_FMT_16BIT_ST_BIGEND(0x00004000L) from 16-bit stereo, big-endian + DATA_FMT_16BIT_MN_BIGEND(0x00006000L) from 16-bit mono, big-endian + DATA_FMT_8_BIT_ST_UNSIGNED(0x00009000L) from 8-bit stereo, unsigned + DATA_FMT_8_BIT_MN_UNSIGNED(0x0000b000L) from 8-bit mono, unsigned + ? Other combinations possible from: + DMA_RQ_C2_AUDIO_CONVERT_MASK 0x0000F000L + DMA_RQ_C2_AC_NONE 0x00000000L + DMA_RQ_C2_AC_8_TO_16_BIT 0x00001000L + DMA_RQ_C2_AC_MONO_TO_STEREO 0x00002000L + DMA_RQ_C2_AC_ENDIAN_CONVERT 0x00004000L + DMA_RQ_C2_AC_SIGNED_CONVERT 0x00008000L + + HostBuffAddr: Host Buffer Physical Byte Address - SCB loc:3rd dword, Mask: 0xFFFFFFFFL + aligned to dword boundary + */ + /* Basic (non scatter/gather) DMA requestor (4 ints) */ + { DMA_RQ_C1_SOURCE_ON_HOST + /* source buffer is on the host */ + DMA_RQ_C1_SOURCE_MOD1024 + /* source buffer is 1024 dwords (4096 bytes) */ + DMA_RQ_C1_DEST_MOD32 + /* dest buffer(PCMreaderBuf) is 32 dwords*/ + DMA_RQ_C1_WRITEBACK_SRC_FLAG + /* ?? */ + DMA_RQ_C1_WRITEBACK_DEST_FLAG + /* ?? */ + 15, /* DwordCount-1: picked 16 for DwordCount because Jim */ + /* Barnette said that is what we should use since */ + /* we are not running in optimized mode? */ + DMA_RQ_C2_AC_NONE + + DMA_RQ_C2_SIGNAL_SOURCE_PINGPONG + /* set play interrupt (bit0) in HISR when source */ + /* buffer (on host) crosses half-way point */ + virtual_channel, /* Play DMA channel arbitrarily set to 0 */ + playback_hw_addr, /* HostBuffAddr (source) */ + DMA_RQ_SD_SP_SAMPLE_ADDR + /* destination buffer is in SP Sample Memory */ + sample_buffer_addr /* SP Buffer Address (destination) */ + }, + /* Scatter/gather DMA requestor extension (5 ints) */ + { + 0, + 0, + 0, + 0, + 0 + }, + /* Sublist pointer & next stream control block (SCB) link. */ + NULL_SCB_ADDR,NULL_SCB_ADDR, + /* Pointer to this tasks parameter block & stream function pointer */ + 0,NULL_SCB_ADDR, + /* rsConfig register for stream buffer (rsDMA reg. is loaded from basicReq.daw */ + /* for incoming streams, or basicReq.saw, for outgoing streams) */ + RSCONFIG_DMA_ENABLE + /* enable DMA */ + (19 << RSCONFIG_MAX_DMA_SIZE_SHIFT) + /* MAX_DMA_SIZE picked to be 19 since SPUD */ + /* uses it for some reason */ + ((dest >> 4) << RSCONFIG_STREAM_NUM_SHIFT) + /* stream number = SCBaddr/16 */ + RSCONFIG_SAMPLE_16STEREO + + RSCONFIG_MODULO_32, /* dest buffer(PCMreaderBuf) is 32 dwords (256 bytes) */ + /* Stream sample pointer & MAC-unit mode for this stream */ + (sample_buffer_addr << 0x10), + /* Fractional increment per output sample in the input sample buffer */ + 0, + { + /* Standard stereo volume control + default muted */ + 0xffff,0xffff, + 0xffff,0xffff + } + }; + + if (ins->null_algorithm == NULL) { + ins->null_algorithm = cs46xx_dsp_lookup_symbol (chip,"NULLALGORITHM", + SYMBOL_CODE); + + if (ins->null_algorithm == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol NULLALGORITHM not found\n"); + return NULL; + } + } + + scb = _dsp_create_generic_scb(chip,scb_name,(u32 *)&pcm_reader_scb, + dest,ins->null_algorithm,parent_scb, + scb_child_type); + + return scb; +} + +#define GOF_PER_SEC 200 + +struct dsp_scb_descriptor * +cs46xx_dsp_create_src_task_scb(struct snd_cs46xx * chip, char * scb_name, + int rate, + u16 src_buffer_addr, + u16 src_delay_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type, + int pass_through) +{ + + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + unsigned int tmp1, tmp2; + unsigned int phiIncr; + unsigned int correctionPerGOF, correctionPerSec; + + dev_dbg(chip->card->dev, "dsp_spos: setting %s rate to %u\n", + scb_name, rate); + + /* + * Compute the values used to drive the actual sample rate conversion. + * The following formulas are being computed, using inline assembly + * since we need to use 64 bit arithmetic to compute the values: + * + * phiIncr = floor((Fs,in * 2^26) / Fs,out) + * correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) / + * GOF_PER_SEC) + * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -M + * GOF_PER_SEC * correctionPerGOF + * + * i.e. + * + * phiIncr:other = dividend:remainder((Fs,in * 2^26) / Fs,out) + * correctionPerGOF:correctionPerSec = + * dividend:remainder(ulOther / GOF_PER_SEC) + */ + tmp1 = rate << 16; + phiIncr = tmp1 / 48000; + tmp1 -= phiIncr * 48000; + tmp1 <<= 10; + phiIncr <<= 10; + tmp2 = tmp1 / 48000; + phiIncr += tmp2; + tmp1 -= tmp2 * 48000; + correctionPerGOF = tmp1 / GOF_PER_SEC; + tmp1 -= correctionPerGOF * GOF_PER_SEC; + correctionPerSec = tmp1; + + { + struct dsp_src_task_scb src_task_scb = { + 0x0028,0x00c8, + 0x5555,0x0000, + 0x0000,0x0000, + src_buffer_addr,1, + correctionPerGOF,correctionPerSec, + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_32, + 0x0000,src_delay_buffer_addr, + 0x0, + 0x080,(src_delay_buffer_addr + (24 * 4)), + 0,0, /* next_scb, sub_list_ptr */ + 0,0, /* entry, this_spb */ + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_8, + src_buffer_addr << 0x10, + phiIncr, + { + 0xffff - ins->dac_volume_right,0xffff - ins->dac_volume_left, + 0xffff - ins->dac_volume_right,0xffff - ins->dac_volume_left + } + }; + + if (ins->s16_up == NULL) { + ins->s16_up = cs46xx_dsp_lookup_symbol (chip,"S16_UPSRC", + SYMBOL_CODE); + + if (ins->s16_up == NULL) { + dev_err(chip->card->dev, + "dsp_spos: symbol S16_UPSRC not found\n"); + return NULL; + } + } + + /* clear buffers */ + _dsp_clear_sample_buffer (chip,src_buffer_addr,8); + _dsp_clear_sample_buffer (chip,src_delay_buffer_addr,32); + + if (pass_through) { + /* wont work with any other rate than + the native DSP rate */ + snd_BUG_ON(rate != 48000); + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&src_task_scb, + dest,"DMAREADER",parent_scb, + scb_child_type); + } else { + scb = _dsp_create_generic_scb(chip,scb_name,(u32 *)&src_task_scb, + dest,ins->s16_up,parent_scb, + scb_child_type); + } + + + } + + return scb; +} + +#if 0 /* not used */ +struct dsp_scb_descriptor * +cs46xx_dsp_create_filter_scb(struct snd_cs46xx * chip, char * scb_name, + u16 buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) { + struct dsp_scb_descriptor * scb; + + struct dsp_filter_scb filter_scb = { + .a0_right = 0x41a9, + .a0_left = 0x41a9, + .a1_right = 0xb8e4, + .a1_left = 0xb8e4, + .a2_right = 0x3e55, + .a2_left = 0x3e55, + + .filter_unused3 = 0x0000, + .filter_unused2 = 0x0000, + + .output_buf_ptr = buffer_addr, + .init = 0x000, + + .prev_sample_output1 = 0x00000000, + .prev_sample_output2 = 0x00000000, + + .prev_sample_input1 = 0x00000000, + .prev_sample_input2 = 0x00000000, + + .next_scb_ptr = 0x0000, + .sub_list_ptr = 0x0000, + + .entry_point = 0x0000, + .spb_ptr = 0x0000, + + .b0_right = 0x0e38, + .b0_left = 0x0e38, + .b1_right = 0x1c71, + .b1_left = 0x1c71, + .b2_right = 0x0e38, + .b2_left = 0x0e38, + }; + + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&filter_scb, + dest,"FILTERTASK",parent_scb, + scb_child_type); + + return scb; +} +#endif /* not used */ + +struct dsp_scb_descriptor * +cs46xx_dsp_create_mix_only_scb(struct snd_cs46xx * chip, char * scb_name, + u16 mix_buffer_addr, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_mix_only_scb master_mix_scb = { + /* 0 */ { 0, + /* 1 */ 0, + /* 2 */ mix_buffer_addr, + /* 3 */ 0 + /* */ }, + { + /* 4 */ 0, + /* 5 */ 0, + /* 6 */ 0, + /* 7 */ 0, + /* 8 */ 0x00000080 + }, + /* 9 */ 0,0, + /* A */ 0,0, + /* B */ RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_32, + /* C */ (mix_buffer_addr + (16 * 4)) << 0x10, + /* D */ 0, + { + /* E */ 0x8000,0x8000, + /* F */ 0x8000,0x8000 + } + }; + + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&master_mix_scb, + dest,"S16_MIX",parent_scb, + scb_child_type); + return scb; +} + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_mix_to_ostream_scb(struct snd_cs46xx * chip, char * scb_name, + u16 mix_buffer_addr, u16 writeback_spb, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_mix2_ostream_scb mix2_ostream_scb = { + /* Basic (non scatter/gather) DMA requestor (4 ints) */ + { + DMA_RQ_C1_SOURCE_MOD64 + + DMA_RQ_C1_DEST_ON_HOST + + DMA_RQ_C1_DEST_MOD1024 + + DMA_RQ_C1_WRITEBACK_SRC_FLAG + + DMA_RQ_C1_WRITEBACK_DEST_FLAG + + 15, + + DMA_RQ_C2_AC_NONE + + DMA_RQ_C2_SIGNAL_DEST_PINGPONG + + + CS46XX_DSP_CAPTURE_CHANNEL, + DMA_RQ_SD_SP_SAMPLE_ADDR + + mix_buffer_addr, + 0x0 + }, + + { 0, 0, 0, 0, 0, }, + 0,0, + 0,writeback_spb, + + RSCONFIG_DMA_ENABLE + + (19 << RSCONFIG_MAX_DMA_SIZE_SHIFT) + + + ((dest >> 4) << RSCONFIG_STREAM_NUM_SHIFT) + + RSCONFIG_DMA_TO_HOST + + RSCONFIG_SAMPLE_16STEREO + + RSCONFIG_MODULO_64, + (mix_buffer_addr + (32 * 4)) << 0x10, + 1,0, + 0x0001,0x0080, + 0xFFFF,0 + }; + + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&mix2_ostream_scb, + + dest,"S16_MIX_TO_OSTREAM",parent_scb, + scb_child_type); + + return scb; +} + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_vari_decimate_scb(struct snd_cs46xx * chip,char * scb_name, + u16 vari_buffer_addr0, + u16 vari_buffer_addr1, + u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + + struct dsp_vari_decimate_scb vari_decimate_scb = { + 0x0028,0x00c8, + 0x5555,0x0000, + 0x0000,0x0000, + vari_buffer_addr0,vari_buffer_addr1, + + 0x0028,0x00c8, + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_256, + + 0xFF800000, + 0, + 0x0080,vari_buffer_addr1 + (25 * 4), + + 0,0, + 0,0, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_8, + vari_buffer_addr0 << 0x10, + 0x04000000, + { + 0x8000,0x8000, + 0xFFFF,0xFFFF + } + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&vari_decimate_scb, + dest,"VARIDECIMATE",parent_scb, + scb_child_type); + + return scb; +} + + +static struct dsp_scb_descriptor * +cs46xx_dsp_create_pcm_serial_input_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + struct dsp_scb_descriptor * input_scb, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + + + struct dsp_pcm_serial_input_scb pcm_serial_input_scb = { + { 0, + 0, + 0, + 0 + }, + { + 0, + 0, + 0, + 0, + 0 + }, + + 0,0, + 0,0, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_16, + 0, + /* 0xD */ 0,input_scb->address, + { + /* 0xE */ 0x8000,0x8000, + /* 0xF */ 0x8000,0x8000 + } + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&pcm_serial_input_scb, + dest,"PCMSERIALINPUTTASK",parent_scb, + scb_child_type); + return scb; +} + + +static struct dsp_scb_descriptor * +cs46xx_dsp_create_asynch_fg_tx_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + u16 hfg_scb_address, + u16 asynch_buffer_address, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + + struct dsp_asynch_fg_tx_scb asynch_fg_tx_scb = { + 0xfc00,0x03ff, /* Prototype sample buffer size of 256 dwords */ + 0x0058,0x0028, /* Min Delta 7 dwords == 28 bytes */ + /* : Max delta 25 dwords == 100 bytes */ + 0,hfg_scb_address, /* Point to HFG task SCB */ + 0,0, /* Initialize current Delta and Consumer ptr adjustment count */ + 0, /* Initialize accumulated Phi to 0 */ + 0,0x2aab, /* Const 1/3 */ + + { + 0, /* Define the unused elements */ + 0, + 0 + }, + + 0,0, + 0,dest + AFGTxAccumPhi, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_256, /* Stereo, 256 dword */ + (asynch_buffer_address) << 0x10, /* This should be automagically synchronized + to the producer pointer */ + + /* There is no correct initial value, it will depend upon the detected + rate etc */ + 0x18000000, /* Phi increment for approx 32k operation */ + 0x8000,0x8000, /* Volume controls are unused at this time */ + 0x8000,0x8000 + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&asynch_fg_tx_scb, + dest,"ASYNCHFGTXCODE",parent_scb, + scb_child_type); + + return scb; +} + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_asynch_fg_rx_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + u16 hfg_scb_address, + u16 asynch_buffer_address, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb; + + struct dsp_asynch_fg_rx_scb asynch_fg_rx_scb = { + 0xfe00,0x01ff, /* Prototype sample buffer size of 128 dwords */ + 0x0064,0x001c, /* Min Delta 7 dwords == 28 bytes */ + /* : Max delta 25 dwords == 100 bytes */ + 0,hfg_scb_address, /* Point to HFG task SCB */ + 0,0, /* Initialize current Delta and Consumer ptr adjustment count */ + { + 0, /* Define the unused elements */ + 0, + 0, + 0, + 0 + }, + + 0,0, + 0,dest, + + RSCONFIG_MODULO_128 | + RSCONFIG_SAMPLE_16STEREO, /* Stereo, 128 dword */ + ( (asynch_buffer_address + (16 * 4)) << 0x10), /* This should be automagically + synchrinized to the producer pointer */ + + /* There is no correct initial value, it will depend upon the detected + rate etc */ + 0x18000000, + + /* Set IEC958 input volume */ + 0xffff - ins->spdif_input_volume_right,0xffff - ins->spdif_input_volume_left, + 0xffff - ins->spdif_input_volume_right,0xffff - ins->spdif_input_volume_left, + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&asynch_fg_rx_scb, + dest,"ASYNCHFGRXCODE",parent_scb, + scb_child_type); + + return scb; +} + + +#if 0 /* not used */ +struct dsp_scb_descriptor * +cs46xx_dsp_create_output_snoop_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + u16 snoop_buffer_address, + struct dsp_scb_descriptor * snoop_scb, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + + struct dsp_scb_descriptor * scb; + + struct dsp_output_snoop_scb output_snoop_scb = { + { 0, /* not used. Zero */ + 0, + 0, + 0, + }, + { + 0, /* not used. Zero */ + 0, + 0, + 0, + 0 + }, + + 0,0, + 0,0, + + RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64, + snoop_buffer_address << 0x10, + 0,0, + 0, + 0,snoop_scb->address + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&output_snoop_scb, + dest,"OUTPUTSNOOP",parent_scb, + scb_child_type); + return scb; +} +#endif /* not used */ + + +struct dsp_scb_descriptor * +cs46xx_dsp_create_spio_write_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_spio_write_scb spio_write_scb = { + 0,0, /* SPIOWAddress2:SPIOWAddress1; */ + 0, /* SPIOWData1; */ + 0, /* SPIOWData2; */ + 0,0, /* SPIOWAddress4:SPIOWAddress3; */ + 0, /* SPIOWData3; */ + 0, /* SPIOWData4; */ + 0,0, /* SPIOWDataPtr:Unused1; */ + { 0,0 }, /* Unused2[2]; */ + + 0,0, /* SPIOWChildPtr:SPIOWSiblingPtr; */ + 0,0, /* SPIOWThisPtr:SPIOWEntryPoint; */ + + { + 0, + 0, + 0, + 0, + 0 /* Unused3[5]; */ + } + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&spio_write_scb, + dest,"SPIOWRITE",parent_scb, + scb_child_type); + + return scb; +} + +struct dsp_scb_descriptor * +cs46xx_dsp_create_magic_snoop_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest, + u16 snoop_buffer_address, + struct dsp_scb_descriptor * snoop_scb, + struct dsp_scb_descriptor * parent_scb, + int scb_child_type) +{ + struct dsp_scb_descriptor * scb; + + struct dsp_magic_snoop_task magic_snoop_scb = { + /* 0 */ 0, /* i0 */ + /* 1 */ 0, /* i1 */ + /* 2 */ snoop_buffer_address << 0x10, + /* 3 */ 0,snoop_scb->address, + /* 4 */ 0, /* i3 */ + /* 5 */ 0, /* i4 */ + /* 6 */ 0, /* i5 */ + /* 7 */ 0, /* i6 */ + /* 8 */ 0, /* i7 */ + /* 9 */ 0,0, /* next_scb, sub_list_ptr */ + /* A */ 0,0, /* entry_point, this_ptr */ + /* B */ RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64, + /* C */ snoop_buffer_address << 0x10, + /* D */ 0, + /* E */ { 0x8000,0x8000, + /* F */ 0xffff,0xffff + } + }; + + scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&magic_snoop_scb, + dest,"MAGICSNOOPTASK",parent_scb, + scb_child_type); + + return scb; +} + +static struct dsp_scb_descriptor * +find_next_free_scb (struct snd_cs46xx * chip, struct dsp_scb_descriptor * from) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * scb = from; + + while (scb->next_scb_ptr != ins->the_null_scb) { + if (snd_BUG_ON(!scb->next_scb_ptr)) + return NULL; + + scb = scb->next_scb_ptr; + } + + return scb; +} + +static const u32 pcm_reader_buffer_addr[DSP_MAX_PCM_CHANNELS] = { + 0x0600, /* 1 */ + 0x1500, /* 2 */ + 0x1580, /* 3 */ + 0x1600, /* 4 */ + 0x1680, /* 5 */ + 0x1700, /* 6 */ + 0x1780, /* 7 */ + 0x1800, /* 8 */ + 0x1880, /* 9 */ + 0x1900, /* 10 */ + 0x1980, /* 11 */ + 0x1A00, /* 12 */ + 0x1A80, /* 13 */ + 0x1B00, /* 14 */ + 0x1B80, /* 15 */ + 0x1C00, /* 16 */ + 0x1C80, /* 17 */ + 0x1D00, /* 18 */ + 0x1D80, /* 19 */ + 0x1E00, /* 20 */ + 0x1E80, /* 21 */ + 0x1F00, /* 22 */ + 0x1F80, /* 23 */ + 0x2000, /* 24 */ + 0x2080, /* 25 */ + 0x2100, /* 26 */ + 0x2180, /* 27 */ + 0x2200, /* 28 */ + 0x2280, /* 29 */ + 0x2300, /* 30 */ + 0x2380, /* 31 */ + 0x2400, /* 32 */ +}; + +static const u32 src_output_buffer_addr[DSP_MAX_SRC_NR] = { + 0x2B80, + 0x2BA0, + 0x2BC0, + 0x2BE0, + 0x2D00, + 0x2D20, + 0x2D40, + 0x2D60, + 0x2D80, + 0x2DA0, + 0x2DC0, + 0x2DE0, + 0x2E00, + 0x2E20 +}; + +static const u32 src_delay_buffer_addr[DSP_MAX_SRC_NR] = { + 0x2480, + 0x2500, + 0x2580, + 0x2600, + 0x2680, + 0x2700, + 0x2780, + 0x2800, + 0x2880, + 0x2900, + 0x2980, + 0x2A00, + 0x2A80, + 0x2B00 +}; + +struct dsp_pcm_channel_descriptor * +cs46xx_dsp_create_pcm_channel (struct snd_cs46xx * chip, + u32 sample_rate, void * private_data, + u32 hw_dma_addr, + int pcm_channel_id) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * src_scb = NULL, * pcm_scb, * mixer_scb = NULL; + struct dsp_scb_descriptor * src_parent_scb = NULL; + + /* struct dsp_scb_descriptor * pcm_parent_scb; */ + char scb_name[DSP_MAX_SCB_NAME]; + int i, pcm_index = -1, insert_point, src_index = -1, pass_through = 0; + + switch (pcm_channel_id) { + case DSP_PCM_MAIN_CHANNEL: + mixer_scb = ins->master_mix_scb; + break; + case DSP_PCM_REAR_CHANNEL: + mixer_scb = ins->rear_mix_scb; + break; + case DSP_PCM_CENTER_LFE_CHANNEL: + mixer_scb = ins->center_lfe_mix_scb; + break; + case DSP_PCM_S71_CHANNEL: + /* TODO */ + snd_BUG(); + break; + case DSP_IEC958_CHANNEL: + if (snd_BUG_ON(!ins->asynch_tx_scb)) + return NULL; + mixer_scb = ins->asynch_tx_scb; + + /* if sample rate is set to 48khz we pass + the Sample Rate Converted (which could + alter the raw data stream ...) */ + if (sample_rate == 48000) { + dev_dbg(chip->card->dev, "IEC958 pass through\n"); + /* Hack to bypass creating a new SRC */ + pass_through = 1; + } + break; + default: + snd_BUG(); + return NULL; + } + /* default sample rate is 44100 */ + if (!sample_rate) sample_rate = 44100; + + /* search for a already created SRC SCB with the same sample rate */ + for (i = 0; i < DSP_MAX_PCM_CHANNELS && + (pcm_index == -1 || src_scb == NULL); ++i) { + + /* virtual channel reserved + for capture */ + if (i == CS46XX_DSP_CAPTURE_CHANNEL) continue; + + if (ins->pcm_channels[i].active) { + if (!src_scb && + ins->pcm_channels[i].sample_rate == sample_rate && + ins->pcm_channels[i].mixer_scb == mixer_scb) { + src_scb = ins->pcm_channels[i].src_scb; + ins->pcm_channels[i].src_scb->ref_count ++; + src_index = ins->pcm_channels[i].src_slot; + } + } else if (pcm_index == -1) { + pcm_index = i; + } + } + + if (pcm_index == -1) { + dev_err(chip->card->dev, "dsp_spos: no free PCM channel\n"); + return NULL; + } + + if (src_scb == NULL) { + if (ins->nsrc_scb >= DSP_MAX_SRC_NR) { + dev_err(chip->card->dev, + "dsp_spos: too many SRC instances\n!"); + return NULL; + } + + /* find a free slot */ + for (i = 0; i < DSP_MAX_SRC_NR; ++i) { + if (ins->src_scb_slots[i] == 0) { + src_index = i; + ins->src_scb_slots[i] = 1; + break; + } + } + if (snd_BUG_ON(src_index == -1)) + return NULL; + + /* we need to create a new SRC SCB */ + if (mixer_scb->sub_list_ptr == ins->the_null_scb) { + src_parent_scb = mixer_scb; + insert_point = SCB_ON_PARENT_SUBLIST_SCB; + } else { + src_parent_scb = find_next_free_scb(chip,mixer_scb->sub_list_ptr); + insert_point = SCB_ON_PARENT_NEXT_SCB; + } + + snprintf (scb_name,DSP_MAX_SCB_NAME,"SrcTask_SCB%d",src_index); + + dev_dbg(chip->card->dev, + "dsp_spos: creating SRC \"%s\"\n", scb_name); + src_scb = cs46xx_dsp_create_src_task_scb(chip,scb_name, + sample_rate, + src_output_buffer_addr[src_index], + src_delay_buffer_addr[src_index], + /* 0x400 - 0x600 source SCBs */ + 0x400 + (src_index * 0x10) , + src_parent_scb, + insert_point, + pass_through); + + if (!src_scb) { + dev_err(chip->card->dev, + "dsp_spos: failed to create SRCtaskSCB\n"); + return NULL; + } + + /* cs46xx_dsp_set_src_sample_rate(chip,src_scb,sample_rate); */ + + ins->nsrc_scb ++; + } + + + snprintf (scb_name,DSP_MAX_SCB_NAME,"PCMReader_SCB%d",pcm_index); + + dev_dbg(chip->card->dev, "dsp_spos: creating PCM \"%s\" (%d)\n", + scb_name, pcm_channel_id); + + pcm_scb = cs46xx_dsp_create_pcm_reader_scb(chip,scb_name, + pcm_reader_buffer_addr[pcm_index], + /* 0x200 - 400 PCMreader SCBs */ + (pcm_index * 0x10) + 0x200, + pcm_index, /* virtual channel 0-31 */ + hw_dma_addr, /* pcm hw addr */ + NULL, /* parent SCB ptr */ + 0 /* insert point */ + ); + + if (!pcm_scb) { + dev_err(chip->card->dev, + "dsp_spos: failed to create PCMreaderSCB\n"); + return NULL; + } + + guard(spinlock_irqsave)(&chip->reg_lock); + ins->pcm_channels[pcm_index].sample_rate = sample_rate; + ins->pcm_channels[pcm_index].pcm_reader_scb = pcm_scb; + ins->pcm_channels[pcm_index].src_scb = src_scb; + ins->pcm_channels[pcm_index].unlinked = 1; + ins->pcm_channels[pcm_index].private_data = private_data; + ins->pcm_channels[pcm_index].src_slot = src_index; + ins->pcm_channels[pcm_index].active = 1; + ins->pcm_channels[pcm_index].pcm_slot = pcm_index; + ins->pcm_channels[pcm_index].mixer_scb = mixer_scb; + ins->npcm_channels ++; + + return (ins->pcm_channels + pcm_index); +} + +int cs46xx_dsp_pcm_channel_set_period (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel, + int period_size) +{ + u32 temp = snd_cs46xx_peek (chip,pcm_channel->pcm_reader_scb->address << 2); + temp &= ~DMA_RQ_C1_SOURCE_SIZE_MASK; + + switch (period_size) { + case 2048: + temp |= DMA_RQ_C1_SOURCE_MOD1024; + break; + case 1024: + temp |= DMA_RQ_C1_SOURCE_MOD512; + break; + case 512: + temp |= DMA_RQ_C1_SOURCE_MOD256; + break; + case 256: + temp |= DMA_RQ_C1_SOURCE_MOD128; + break; + case 128: + temp |= DMA_RQ_C1_SOURCE_MOD64; + break; + case 64: + temp |= DMA_RQ_C1_SOURCE_MOD32; + break; + case 32: + temp |= DMA_RQ_C1_SOURCE_MOD16; + break; + default: + dev_dbg(chip->card->dev, + "period size (%d) not supported by HW\n", period_size); + return -EINVAL; + } + + snd_cs46xx_poke (chip,pcm_channel->pcm_reader_scb->address << 2,temp); + + return 0; +} + +int cs46xx_dsp_pcm_ostream_set_period (struct snd_cs46xx * chip, + int period_size) +{ + u32 temp = snd_cs46xx_peek (chip,WRITEBACK_SCB_ADDR << 2); + temp &= ~DMA_RQ_C1_DEST_SIZE_MASK; + + switch (period_size) { + case 2048: + temp |= DMA_RQ_C1_DEST_MOD1024; + break; + case 1024: + temp |= DMA_RQ_C1_DEST_MOD512; + break; + case 512: + temp |= DMA_RQ_C1_DEST_MOD256; + break; + case 256: + temp |= DMA_RQ_C1_DEST_MOD128; + break; + case 128: + temp |= DMA_RQ_C1_DEST_MOD64; + break; + case 64: + temp |= DMA_RQ_C1_DEST_MOD32; + break; + case 32: + temp |= DMA_RQ_C1_DEST_MOD16; + break; + default: + dev_dbg(chip->card->dev, + "period size (%d) not supported by HW\n", period_size); + return -EINVAL; + } + + snd_cs46xx_poke (chip,WRITEBACK_SCB_ADDR << 2,temp); + + return 0; +} + +void cs46xx_dsp_destroy_pcm_channel (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!pcm_channel->active || + ins->npcm_channels <= 0 || + pcm_channel->src_scb->ref_count <= 0)) + return; + + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + pcm_channel->unlinked = 1; + pcm_channel->active = 0; + pcm_channel->private_data = NULL; + pcm_channel->src_scb->ref_count--; + ins->npcm_channels--; + } + + cs46xx_dsp_remove_scb(chip,pcm_channel->pcm_reader_scb); + + if (!pcm_channel->src_scb->ref_count) { + cs46xx_dsp_remove_scb(chip,pcm_channel->src_scb); + + if (snd_BUG_ON(pcm_channel->src_slot < 0 || + pcm_channel->src_slot >= DSP_MAX_SRC_NR)) + return; + + ins->src_scb_slots[pcm_channel->src_slot] = 0; + ins->nsrc_scb --; + } +} + +int cs46xx_dsp_pcm_unlink (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel) +{ + if (snd_BUG_ON(!pcm_channel->active || + chip->dsp_spos_instance->npcm_channels <= 0)) + return -EIO; + + guard(spinlock_irqsave)(&chip->reg_lock); + if (pcm_channel->unlinked) + return -EIO; + + pcm_channel->unlinked = 1; + + _dsp_unlink_scb (chip,pcm_channel->pcm_reader_scb); + + return 0; +} + +int cs46xx_dsp_pcm_link (struct snd_cs46xx * chip, + struct dsp_pcm_channel_descriptor * pcm_channel) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * parent_scb; + struct dsp_scb_descriptor * src_scb = pcm_channel->src_scb; + + guard(spinlock_irqsave)(&chip->reg_lock); + + if (pcm_channel->unlinked == 0) + return -EIO; + + parent_scb = src_scb; + + if (src_scb->sub_list_ptr != ins->the_null_scb) { + src_scb->sub_list_ptr->parent_scb_ptr = pcm_channel->pcm_reader_scb; + pcm_channel->pcm_reader_scb->next_scb_ptr = src_scb->sub_list_ptr; + } + + src_scb->sub_list_ptr = pcm_channel->pcm_reader_scb; + + snd_BUG_ON(pcm_channel->pcm_reader_scb->parent_scb_ptr); + pcm_channel->pcm_reader_scb->parent_scb_ptr = parent_scb; + + /* update SCB entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip,pcm_channel->pcm_reader_scb); + + /* update parent SCB entry */ + cs46xx_dsp_spos_update_scb(chip,parent_scb); + + pcm_channel->unlinked = 0; + return 0; +} + +struct dsp_scb_descriptor * +cs46xx_add_record_source (struct snd_cs46xx *chip, struct dsp_scb_descriptor * source, + u16 addr, char * scb_name) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * parent; + struct dsp_scb_descriptor * pcm_input; + int insert_point; + + if (snd_BUG_ON(!ins->record_mixer_scb)) + return NULL; + + if (ins->record_mixer_scb->sub_list_ptr != ins->the_null_scb) { + parent = find_next_free_scb (chip,ins->record_mixer_scb->sub_list_ptr); + insert_point = SCB_ON_PARENT_NEXT_SCB; + } else { + parent = ins->record_mixer_scb; + insert_point = SCB_ON_PARENT_SUBLIST_SCB; + } + + pcm_input = cs46xx_dsp_create_pcm_serial_input_scb(chip,scb_name,addr, + source, parent, + insert_point); + + return pcm_input; +} + +int cs46xx_src_unlink(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src) +{ + if (snd_BUG_ON(!src->parent_scb_ptr)) + return -EINVAL; + + /* mute SCB */ + cs46xx_dsp_scb_set_volume (chip,src,0,0); + + guard(spinlock_irqsave)(&chip->reg_lock); + _dsp_unlink_scb (chip,src); + + return 0; +} + +int cs46xx_src_link(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + struct dsp_scb_descriptor * parent_scb; + + if (snd_BUG_ON(src->parent_scb_ptr)) + return -EINVAL; + if (snd_BUG_ON(!ins->master_mix_scb)) + return -EINVAL; + + if (ins->master_mix_scb->sub_list_ptr != ins->the_null_scb) { + parent_scb = find_next_free_scb (chip,ins->master_mix_scb->sub_list_ptr); + parent_scb->next_scb_ptr = src; + } else { + parent_scb = ins->master_mix_scb; + parent_scb->sub_list_ptr = src; + } + + src->parent_scb_ptr = parent_scb; + + /* update entry in DSP RAM */ + cs46xx_dsp_spos_update_scb(chip,parent_scb); + + return 0; +} + +int cs46xx_dsp_enable_spdif_out (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if ( ! (ins->spdif_status_out & DSP_SPDIF_STATUS_HW_ENABLED) ) { + cs46xx_dsp_enable_spdif_hw (chip); + } + + /* dont touch anything if SPDIF is open */ + if ( ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) { + /* when cs46xx_iec958_post_close(...) is called it + will call this function if necessary depending on + this bit */ + ins->spdif_status_out |= DSP_SPDIF_STATUS_OUTPUT_ENABLED; + + return -EBUSY; + } + + if (snd_BUG_ON(ins->asynch_tx_scb)) + return -EINVAL; + if (snd_BUG_ON(ins->master_mix_scb->next_scb_ptr != + ins->the_null_scb)) + return -EINVAL; + + /* reset output snooper sample buffer pointer */ + snd_cs46xx_poke (chip, (ins->ref_snoop_scb->address + 2) << 2, + (OUTPUT_SNOOP_BUFFER + 0x10) << 0x10 ); + + /* The asynch. transfer task */ + ins->asynch_tx_scb = cs46xx_dsp_create_asynch_fg_tx_scb(chip,"AsynchFGTxSCB",ASYNCTX_SCB_ADDR, + SPDIFO_SCB_INST, + SPDIFO_IP_OUTPUT_BUFFER1, + ins->master_mix_scb, + SCB_ON_PARENT_NEXT_SCB); + if (!ins->asynch_tx_scb) return -ENOMEM; + + ins->spdif_pcm_input_scb = cs46xx_dsp_create_pcm_serial_input_scb(chip,"PCMSerialInput_II", + PCMSERIALINII_SCB_ADDR, + ins->ref_snoop_scb, + ins->asynch_tx_scb, + SCB_ON_PARENT_SUBLIST_SCB); + + + if (!ins->spdif_pcm_input_scb) return -ENOMEM; + + /* monitor state */ + ins->spdif_status_out |= DSP_SPDIF_STATUS_OUTPUT_ENABLED; + + return 0; +} + +int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + /* dont touch anything if SPDIF is open */ + if ( ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) { + ins->spdif_status_out &= ~DSP_SPDIF_STATUS_OUTPUT_ENABLED; + return -EBUSY; + } + + /* check integrety */ + if (snd_BUG_ON(!ins->asynch_tx_scb)) + return -EINVAL; + if (snd_BUG_ON(!ins->spdif_pcm_input_scb)) + return -EINVAL; + if (snd_BUG_ON(ins->master_mix_scb->next_scb_ptr != ins->asynch_tx_scb)) + return -EINVAL; + if (snd_BUG_ON(ins->asynch_tx_scb->parent_scb_ptr != + ins->master_mix_scb)) + return -EINVAL; + + cs46xx_dsp_remove_scb (chip,ins->spdif_pcm_input_scb); + cs46xx_dsp_remove_scb (chip,ins->asynch_tx_scb); + + ins->spdif_pcm_input_scb = NULL; + ins->asynch_tx_scb = NULL; + + /* clear buffer to prevent any undesired noise */ + _dsp_clear_sample_buffer(chip,SPDIFO_IP_OUTPUT_BUFFER1,256); + + /* monitor state */ + ins->spdif_status_out &= ~DSP_SPDIF_STATUS_OUTPUT_ENABLED; + + + return 0; +} + +int cs46xx_iec958_pre_open (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if ( ins->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED ) { + /* remove AsynchFGTxSCB and PCMSerialInput_II */ + cs46xx_dsp_disable_spdif_out (chip); + + /* save state */ + ins->spdif_status_out |= DSP_SPDIF_STATUS_OUTPUT_ENABLED; + } + + /* if not enabled already */ + if ( !(ins->spdif_status_out & DSP_SPDIF_STATUS_HW_ENABLED) ) { + cs46xx_dsp_enable_spdif_hw (chip); + } + + /* Create the asynch. transfer task for playback */ + ins->asynch_tx_scb = cs46xx_dsp_create_asynch_fg_tx_scb(chip,"AsynchFGTxSCB",ASYNCTX_SCB_ADDR, + SPDIFO_SCB_INST, + SPDIFO_IP_OUTPUT_BUFFER1, + ins->master_mix_scb, + SCB_ON_PARENT_NEXT_SCB); + + + /* set spdif channel status value for streaming */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_stream); + + ins->spdif_status_out |= DSP_SPDIF_STATUS_PLAYBACK_OPEN; + + return 0; +} + +int cs46xx_iec958_post_close (struct snd_cs46xx *chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + + if (snd_BUG_ON(!ins->asynch_tx_scb)) + return -EINVAL; + + ins->spdif_status_out &= ~DSP_SPDIF_STATUS_PLAYBACK_OPEN; + + /* restore settings */ + cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_default); + + /* deallocate stuff */ + if (ins->spdif_pcm_input_scb != NULL) { + cs46xx_dsp_remove_scb (chip,ins->spdif_pcm_input_scb); + ins->spdif_pcm_input_scb = NULL; + } + + cs46xx_dsp_remove_scb (chip,ins->asynch_tx_scb); + ins->asynch_tx_scb = NULL; + + /* clear buffer to prevent any undesired noise */ + _dsp_clear_sample_buffer(chip,SPDIFO_IP_OUTPUT_BUFFER1,256); + + /* restore state */ + if ( ins->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED ) { + cs46xx_dsp_enable_spdif_out (chip); + } + + return 0; +} diff --git a/sound/pci/cs5530.c b/sound/pci/cs5530.c new file mode 100644 index 000000000..292b65aa7 --- /dev/null +++ b/sound/pci/cs5530.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cs5530.c - Initialisation code for Cyrix/NatSemi VSA1 softaudio + * + * (C) Copyright 2007 Ash Willis + * (C) Copyright 2003 Red Hat Inc + * + * This driver was ported (shamelessly ripped ;) from oss/kahlua.c but I did + * mess with it a bit. The chip seems to have to have trouble with full duplex + * mode. If we're recording in 8bit 8000kHz, say, and we then attempt to + * simultaneously play back audio at 16bit 44100kHz, the device actually plays + * back in the same format in which it is capturing. By forcing the chip to + * always play/capture in 16/44100, we can let alsa-lib convert the samples and + * that way we can hack up some full duplex audio. + * + * XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems. + * The older version (VSA1) provides fairly good soundblaster emulation + * although there are a couple of bugs: large DMA buffers break record, + * and the MPU event handling seems suspect. VSA2 allows the native driver + * to control the AC97 audio engine directly and requires a different driver. + * + * Thanks to National Semiconductor for providing the needed information + * on the XpressAudio(tm) internals. + * + * TO DO: + * Investigate whether we can portably support Cognac (5520) in the + * same manner. + */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Ash Willis"); +MODULE_DESCRIPTION("CS5530 Audio"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for CS5530 Audio driver."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for CS5530 Audio driver."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable CS5530 Audio driver."); + +struct snd_cs5530 { + struct snd_card *card; + struct pci_dev *pci; + struct snd_sb *sb; + unsigned long pci_base; +}; + +static const struct pci_device_id snd_cs5530_ids[] = { + {PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO, PCI_ANY_ID, + PCI_ANY_ID, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, snd_cs5530_ids); + +static u8 snd_cs5530_mixer_read(unsigned long io, u8 reg) +{ + outb(reg, io + 4); + udelay(20); + reg = inb(io + 5); + udelay(20); + return reg; +} + +static int snd_cs5530_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct snd_cs5530 *chip = card->private_data; + unsigned long sb_base; + u8 irq, dma8, dma16 = 0; + u16 map; + void __iomem *mem; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + chip->card = card; + chip->pci = pci; + + mem = pcim_iomap_region(pci, 0, "CS5530"); + if (IS_ERR(mem)) + return PTR_ERR(mem); + chip->pci_base = pci_resource_start(pci, 0); + map = readw(mem + 0x18); + + /* Map bits + 0:1 * 0x20 + 0x200 = sb base + 2 sb enable + 3 adlib enable + 5 MPU enable 0x330 + 6 MPU enable 0x300 + + The other bits may be used internally so must be masked */ + + sb_base = 0x220 + 0x20 * (map & 3); + + if (map & (1<<2)) + dev_info(card->dev, "XpressAudio at 0x%lx\n", sb_base); + else { + dev_err(card->dev, "Could not find XpressAudio!\n"); + return -ENODEV; + } + + if (map & (1<<5)) + dev_info(card->dev, "MPU at 0x300\n"); + else if (map & (1<<6)) + dev_info(card->dev, "MPU at 0x330\n"); + + irq = snd_cs5530_mixer_read(sb_base, 0x80) & 0x0F; + dma8 = snd_cs5530_mixer_read(sb_base, 0x81); + + if (dma8 & 0x20) + dma16 = 5; + else if (dma8 & 0x40) + dma16 = 6; + else if (dma8 & 0x80) + dma16 = 7; + else { + dev_err(card->dev, "No 16bit DMA enabled\n"); + return -ENODEV; + } + + if (dma8 & 0x01) + dma8 = 0; + else if (dma8 & 02) + dma8 = 1; + else if (dma8 & 0x08) + dma8 = 3; + else { + dev_err(card->dev, "No 8bit DMA enabled\n"); + return -ENODEV; + } + + if (irq & 1) + irq = 9; + else if (irq & 2) + irq = 5; + else if (irq & 4) + irq = 7; + else if (irq & 8) + irq = 10; + else { + dev_err(card->dev, "SoundBlaster IRQ not set\n"); + return -ENODEV; + } + + dev_info(card->dev, "IRQ: %d DMA8: %d DMA16: %d\n", irq, dma8, dma16); + + err = snd_sbdsp_create(card, sb_base, irq, snd_sb16dsp_interrupt, dma8, + dma16, SB_HW_CS5530, &chip->sb); + if (err < 0) { + dev_err(card->dev, "Could not create SoundBlaster\n"); + return err; + } + + err = snd_sb16dsp_pcm(chip->sb, 0); + if (err < 0) { + dev_err(card->dev, "Could not create PCM\n"); + return err; + } + + err = snd_sbmixer_new(chip->sb); + if (err < 0) { + dev_err(card->dev, "Could not create Mixer\n"); + return err; + } + + return 0; +} + +static int snd_cs5530_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_cs5530 *chip; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + err = snd_cs5530_create(card, pci); + if (err < 0) + return err; + + strscpy(card->driver, "CS5530"); + strscpy(card->shortname, "CS5530 Audio"); + sprintf(card->longname, "%s at 0x%lx", card->shortname, chip->pci_base); + + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static struct pci_driver cs5530_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_cs5530_ids, + .probe = snd_cs5530_probe, +}; + +module_pci_driver(cs5530_driver); diff --git a/sound/pci/cs5535audio/Makefile b/sound/pci/cs5535audio/Makefile new file mode 100644 index 000000000..447e62875 --- /dev/null +++ b/sound/pci/cs5535audio/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for cs5535audio +# + +snd-cs5535audio-y := cs5535audio.o cs5535audio_pcm.o +snd-cs5535audio-$(CONFIG_PM_SLEEP) += cs5535audio_pm.o +snd-cs5535audio-$(CONFIG_OLPC) += cs5535audio_olpc.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_CS5535AUDIO) += snd-cs5535audio.o diff --git a/sound/pci/cs5535audio/cs5535audio.c b/sound/pci/cs5535audio/cs5535audio.c new file mode 100644 index 000000000..0ebf6c02b --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for audio on multifunction CS5535/6 companion device + * Copyright (C) Jaya Kumar + * + * Based on Jaroslav Kysela and Takashi Iwai's examples. + * This work was sponsored by CIS(M) Sdn Bhd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs5535audio.h" + +#define DRIVER_NAME "cs5535audio" + +static char *ac97_quirk; +module_param(ac97_quirk, charp, 0444); +MODULE_PARM_DESC(ac97_quirk, "AC'97 board specific workarounds."); + +static const struct ac97_quirk ac97_quirks[] = { +#if 0 /* Not yet confirmed if all 5536 boards are HP only */ + { + .subvendor = PCI_VENDOR_ID_AMD, + .subdevice = PCI_DEVICE_ID_AMD_CS5536_AUDIO, + .name = "AMD RDK", + .type = AC97_TUNE_HP_ONLY + }, +#endif + {} +}; + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " DRIVER_NAME); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " DRIVER_NAME); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " DRIVER_NAME); + +static const struct pci_device_id snd_cs5535audio_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_AUDIO) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_AUDIO) }, + {} +}; + +MODULE_DEVICE_TABLE(pci, snd_cs5535audio_ids); + +static void wait_till_cmd_acked(struct cs5535audio *cs5535au, unsigned long timeout) +{ + unsigned int tmp; + do { + tmp = cs_readl(cs5535au, ACC_CODEC_CNTL); + if (!(tmp & CMD_NEW)) + break; + udelay(1); + } while (--timeout); + if (!timeout) + dev_err(cs5535au->card->dev, + "Failure writing to cs5535 codec\n"); +} + +static unsigned short snd_cs5535audio_codec_read(struct cs5535audio *cs5535au, + unsigned short reg) +{ + unsigned int regdata; + unsigned int timeout; + unsigned int val; + + regdata = ((unsigned int) reg) << 24; + regdata |= ACC_CODEC_CNTL_RD_CMD; + regdata |= CMD_NEW; + + cs_writel(cs5535au, ACC_CODEC_CNTL, regdata); + wait_till_cmd_acked(cs5535au, 50); + + timeout = 50; + do { + val = cs_readl(cs5535au, ACC_CODEC_STATUS); + if ((val & STS_NEW) && reg == (val >> 24)) + break; + udelay(1); + } while (--timeout); + if (!timeout) + dev_err(cs5535au->card->dev, + "Failure reading codec reg 0x%x, Last value=0x%x\n", + reg, val); + + return (unsigned short) val; +} + +static void snd_cs5535audio_codec_write(struct cs5535audio *cs5535au, + unsigned short reg, unsigned short val) +{ + unsigned int regdata; + + regdata = ((unsigned int) reg) << 24; + regdata |= val; + regdata &= CMD_MASK; + regdata |= CMD_NEW; + regdata &= ACC_CODEC_CNTL_WR_CMD; + + cs_writel(cs5535au, ACC_CODEC_CNTL, regdata); + wait_till_cmd_acked(cs5535au, 50); +} + +static void snd_cs5535audio_ac97_codec_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + struct cs5535audio *cs5535au = ac97->private_data; + snd_cs5535audio_codec_write(cs5535au, reg, val); +} + +static unsigned short snd_cs5535audio_ac97_codec_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct cs5535audio *cs5535au = ac97->private_data; + return snd_cs5535audio_codec_read(cs5535au, reg); +} + +static int snd_cs5535audio_mixer(struct cs5535audio *cs5535au) +{ + struct snd_card *card = cs5535au->card; + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_cs5535audio_ac97_codec_write, + .read = snd_cs5535audio_ac97_codec_read, + }; + + err = snd_ac97_bus(card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM + | AC97_SCAP_POWER_SAVE; + ac97.private_data = cs5535au; + ac97.pci = cs5535au->pci; + + /* set any OLPC-specific scaps */ + olpc_prequirks(card, &ac97); + + err = snd_ac97_mixer(pbus, &ac97, &cs5535au->ac97); + if (err < 0) { + dev_err(card->dev, "mixer failed\n"); + return err; + } + + snd_ac97_tune_hardware(cs5535au->ac97, ac97_quirks, ac97_quirk); + + err = olpc_quirks(card, cs5535au->ac97); + if (err < 0) { + dev_err(card->dev, "olpc quirks failed\n"); + return err; + } + + return 0; +} + +static void process_bm0_irq(struct cs5535audio *cs5535au) +{ + u8 bm_stat; + + scoped_guard(spinlock, &cs5535au->reg_lock) { + bm_stat = cs_readb(cs5535au, ACC_BM0_STATUS); + } + if (bm_stat & EOP) { + snd_pcm_period_elapsed(cs5535au->playback_substream); + } else { + dev_err(cs5535au->card->dev, + "unexpected bm0 irq src, bm_stat=%x\n", + bm_stat); + } +} + +static void process_bm1_irq(struct cs5535audio *cs5535au) +{ + u8 bm_stat; + + scoped_guard(spinlock, &cs5535au->reg_lock) { + bm_stat = cs_readb(cs5535au, ACC_BM1_STATUS); + } + if (bm_stat & EOP) + snd_pcm_period_elapsed(cs5535au->capture_substream); +} + +static irqreturn_t snd_cs5535audio_interrupt(int irq, void *dev_id) +{ + u16 acc_irq_stat; + unsigned char count; + struct cs5535audio *cs5535au = dev_id; + + if (cs5535au == NULL) + return IRQ_NONE; + + acc_irq_stat = cs_readw(cs5535au, ACC_IRQ_STATUS); + + if (!acc_irq_stat) + return IRQ_NONE; + for (count = 0; count < 4; count++) { + if (acc_irq_stat & (1 << count)) { + switch (count) { + case IRQ_STS: + cs_readl(cs5535au, ACC_GPIO_STATUS); + break; + case WU_IRQ_STS: + cs_readl(cs5535au, ACC_GPIO_STATUS); + break; + case BM0_IRQ_STS: + process_bm0_irq(cs5535au); + break; + case BM1_IRQ_STS: + process_bm1_irq(cs5535au); + break; + default: + dev_err(cs5535au->card->dev, + "Unexpected irq src: 0x%x\n", + acc_irq_stat); + break; + } + } + } + return IRQ_HANDLED; +} + +static void snd_cs5535audio_free(struct snd_card *card) +{ + olpc_quirks_cleanup(); +} + +static int snd_cs5535audio_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct cs5535audio *cs5535au = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32))) { + dev_warn(card->dev, "unable to get 32bit dma\n"); + return -ENXIO; + } + + spin_lock_init(&cs5535au->reg_lock); + cs5535au->card = card; + cs5535au->pci = pci; + cs5535au->irq = -1; + + err = pcim_request_all_regions(pci, "CS5535 Audio"); + if (err < 0) + return err; + + cs5535au->port = pci_resource_start(pci, 0); + + if (devm_request_irq(&pci->dev, pci->irq, snd_cs5535audio_interrupt, + IRQF_SHARED, KBUILD_MODNAME, cs5535au)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + + cs5535au->irq = pci->irq; + card->sync_irq = cs5535au->irq; + pci_set_master(pci); + + return 0; +} + +static int __snd_cs5535audio_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct cs5535audio *cs5535au; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*cs5535au), &card); + if (err < 0) + return err; + cs5535au = card->private_data; + card->private_free = snd_cs5535audio_free; + + err = snd_cs5535audio_create(card, pci); + if (err < 0) + return err; + + err = snd_cs5535audio_mixer(cs5535au); + if (err < 0) + return err; + + err = snd_cs5535audio_pcm(cs5535au); + if (err < 0) + return err; + + strscpy(card->driver, DRIVER_NAME); + + strscpy(card->shortname, "CS5535 Audio"); + sprintf(card->longname, "%s %s at 0x%lx, irq %i", + card->shortname, card->driver, + cs5535au->port, cs5535au->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_cs5535audio_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_cs5535audio_probe(pci, pci_id)); +} + +static struct pci_driver cs5535audio_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_cs5535audio_ids, + .probe = snd_cs5535audio_probe, +#ifdef CONFIG_PM_SLEEP + .driver = { + .pm = &snd_cs5535audio_pm, + }, +#endif +}; + +module_pci_driver(cs5535audio_driver); + +MODULE_AUTHOR("Jaya Kumar"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CS5535 Audio"); diff --git a/sound/pci/cs5535audio/cs5535audio.h b/sound/pci/cs5535audio/cs5535audio.h new file mode 100644 index 000000000..d84620a0c --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_CS5535AUDIO_H +#define __SOUND_CS5535AUDIO_H + +#define cs_writel(cs5535au, reg, val) outl(val, (cs5535au)->port + reg) +#define cs_writeb(cs5535au, reg, val) outb(val, (cs5535au)->port + reg) +#define cs_readl(cs5535au, reg) inl((cs5535au)->port + reg) +#define cs_readw(cs5535au, reg) inw((cs5535au)->port + reg) +#define cs_readb(cs5535au, reg) inb((cs5535au)->port + reg) + +#define CS5535AUDIO_MAX_DESCRIPTORS 128 + +/* acc_codec bar0 reg addrs */ +#define ACC_GPIO_STATUS 0x00 +#define ACC_CODEC_STATUS 0x08 +#define ACC_CODEC_CNTL 0x0C +#define ACC_IRQ_STATUS 0x12 +#define ACC_BM0_CMD 0x20 +#define ACC_BM1_CMD 0x28 +#define ACC_BM0_PRD 0x24 +#define ACC_BM1_PRD 0x2C +#define ACC_BM0_STATUS 0x21 +#define ACC_BM1_STATUS 0x29 +#define ACC_BM0_PNTR 0x60 +#define ACC_BM1_PNTR 0x64 + +/* acc_codec bar0 reg bits */ +/* ACC_IRQ_STATUS */ +#define IRQ_STS 0 +#define WU_IRQ_STS 1 +#define BM0_IRQ_STS 2 +#define BM1_IRQ_STS 3 +/* ACC_BMX_STATUS */ +#define EOP (1<<0) +#define BM_EOP_ERR (1<<1) +/* ACC_BMX_CTL */ +#define BM_CTL_EN 0x01 +#define BM_CTL_PAUSE 0x03 +#define BM_CTL_DIS 0x00 +#define BM_CTL_BYTE_ORD_LE 0x00 +#define BM_CTL_BYTE_ORD_BE 0x04 +/* cs5535 specific ac97 codec register defines */ +#define CMD_MASK 0xFF00FFFF +#define CMD_NEW 0x00010000 +#define STS_NEW 0x00020000 +#define PRM_RDY_STS 0x00800000 +#define ACC_CODEC_CNTL_WR_CMD (~0x80000000) +#define ACC_CODEC_CNTL_RD_CMD 0x80000000 +#define ACC_CODEC_CNTL_LNK_SHUTDOWN 0x00040000 +#define ACC_CODEC_CNTL_LNK_WRM_RST 0x00020000 +#define PRD_JMP 0x2000 +#define PRD_EOP 0x4000 +#define PRD_EOT 0x8000 + +enum { CS5535AUDIO_DMA_PLAYBACK, CS5535AUDIO_DMA_CAPTURE, NUM_CS5535AUDIO_DMAS }; + +struct cs5535audio; + +struct cs5535audio_dma_ops { + int type; + void (*enable_dma)(struct cs5535audio *cs5535au); + void (*disable_dma)(struct cs5535audio *cs5535au); + void (*pause_dma)(struct cs5535audio *cs5535au); + void (*setup_prd)(struct cs5535audio *cs5535au, u32 prd_addr); + u32 (*read_prd)(struct cs5535audio *cs5535au); + u32 (*read_dma_pntr)(struct cs5535audio *cs5535au); +}; + +struct cs5535audio_dma_desc { + __le32 addr; + __le16 size; + __le16 ctlreserved; +}; + +struct cs5535audio_dma { + const struct cs5535audio_dma_ops *ops; + struct snd_dma_buffer desc_buf; + struct snd_pcm_substream *substream; + unsigned int buf_addr, buf_bytes; + unsigned int period_bytes, periods; + u32 saved_prd; + int pcm_open_flag; +}; + +struct cs5535audio { + struct snd_card *card; + struct snd_ac97 *ac97; + struct snd_pcm *pcm; + int irq; + struct pci_dev *pci; + unsigned long port; + spinlock_t reg_lock; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + struct cs5535audio_dma dmas[NUM_CS5535AUDIO_DMAS]; +}; + +extern const struct dev_pm_ops snd_cs5535audio_pm; + +#ifdef CONFIG_OLPC +void olpc_prequirks(struct snd_card *card, + struct snd_ac97_template *ac97); +int olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97); +void olpc_quirks_cleanup(void); +void olpc_analog_input(struct snd_ac97 *ac97, int on); +void olpc_mic_bias(struct snd_ac97 *ac97, int on); + +static inline void olpc_capture_open(struct snd_ac97 *ac97) +{ + /* default to Analog Input off */ + olpc_analog_input(ac97, 0); + /* enable MIC Bias for recording */ + olpc_mic_bias(ac97, 1); +} + +static inline void olpc_capture_close(struct snd_ac97 *ac97) +{ + /* disable Analog Input */ + olpc_analog_input(ac97, 0); + /* disable the MIC Bias (so the recording LED turns off) */ + olpc_mic_bias(ac97, 0); +} +#else +static inline void olpc_prequirks(struct snd_card *card, + struct snd_ac97_template *ac97) { } +static inline int olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97) +{ + return 0; +} +static inline void olpc_quirks_cleanup(void) { } +static inline void olpc_analog_input(struct snd_ac97 *ac97, int on) { } +static inline void olpc_mic_bias(struct snd_ac97 *ac97, int on) { } +static inline void olpc_capture_open(struct snd_ac97 *ac97) { } +static inline void olpc_capture_close(struct snd_ac97 *ac97) { } +#endif + +int snd_cs5535audio_pcm(struct cs5535audio *cs5535audio); + +#endif /* __SOUND_CS5535AUDIO_H */ + diff --git a/sound/pci/cs5535audio/cs5535audio_olpc.c b/sound/pci/cs5535audio/cs5535audio_olpc.c new file mode 100644 index 000000000..cfdcc5bf4 --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio_olpc.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OLPC XO-1 additional sound features + * + * Copyright © 2006 Jaya Kumar + * Copyright © 2007-2008 Andres Salomon + */ +#include +#include +#include +#include +#include + +#include +#include "cs5535audio.h" + +#define DRV_NAME "cs5535audio-olpc" + +/* + * OLPC has an additional feature on top of the regular AD1888 codec features. + * It has an Analog Input mode that is switched into (after disabling the + * High Pass Filter) via GPIO. It is supported on B2 and later models. + */ +void olpc_analog_input(struct snd_ac97 *ac97, int on) +{ + int err; + + if (!machine_is_olpc()) + return; + + /* update the High Pass Filter (via AC97_AD_TEST2) */ + err = snd_ac97_update_bits(ac97, AC97_AD_TEST2, + 1 << AC97_AD_HPFD_SHIFT, on << AC97_AD_HPFD_SHIFT); + if (err < 0) { + dev_err(ac97->bus->card->dev, + "setting High Pass Filter - %d\n", err); + return; + } + + /* set Analog Input through GPIO */ + gpio_set_value(OLPC_GPIO_MIC_AC, on); +} + +/* + * OLPC XO-1's V_REFOUT is a mic bias enable. + */ +void olpc_mic_bias(struct snd_ac97 *ac97, int on) +{ + int err; + + if (!machine_is_olpc()) + return; + + on = on ? 0 : 1; + err = snd_ac97_update_bits(ac97, AC97_AD_MISC, + 1 << AC97_AD_VREFD_SHIFT, on << AC97_AD_VREFD_SHIFT); + if (err < 0) + dev_err(ac97->bus->card->dev, "setting MIC Bias - %d\n", err); +} + +static int olpc_dc_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int olpc_dc_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) +{ + v->value.integer.value[0] = gpio_get_value(OLPC_GPIO_MIC_AC); + return 0; +} + +static int olpc_dc_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) +{ + struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); + + olpc_analog_input(cs5535au->ac97, v->value.integer.value[0]); + return 1; +} + +static int olpc_mic_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int olpc_mic_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) +{ + struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); + struct snd_ac97 *ac97 = cs5535au->ac97; + int i; + + i = (snd_ac97_read(ac97, AC97_AD_MISC) >> AC97_AD_VREFD_SHIFT) & 0x1; + v->value.integer.value[0] = i ? 0 : 1; + return 0; +} + +static int olpc_mic_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) +{ + struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); + + olpc_mic_bias(cs5535au->ac97, v->value.integer.value[0]); + return 1; +} + +static const struct snd_kcontrol_new olpc_cs5535audio_ctls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DC Mode Enable", + .info = olpc_dc_info, + .get = olpc_dc_get, + .put = olpc_dc_put, + .private_value = 0, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "MIC Bias Enable", + .info = olpc_mic_info, + .get = olpc_mic_get, + .put = olpc_mic_put, + .private_value = 0, +}, +}; + +void olpc_prequirks(struct snd_card *card, + struct snd_ac97_template *ac97) +{ + if (!machine_is_olpc()) + return; + + /* invert EAPD if on an OLPC B3 or higher */ + if (olpc_board_at_least(olpc_board_pre(0xb3))) + ac97->scaps |= AC97_SCAP_INV_EAPD; +} + +int olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97) +{ + struct snd_ctl_elem_id elem; + int i, err; + + if (!machine_is_olpc()) + return 0; + + if (gpio_request(OLPC_GPIO_MIC_AC, DRV_NAME)) { + dev_err(card->dev, "unable to allocate MIC GPIO\n"); + return -EIO; + } + gpio_direction_output(OLPC_GPIO_MIC_AC, 0); + + /* drop the original AD1888 HPF control */ + memset(&elem, 0, sizeof(elem)); + elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(elem.name, "High Pass Filter Enable", sizeof(elem.name)); + snd_ctl_remove_id(card, &elem); + + /* drop the original V_REFOUT control */ + memset(&elem, 0, sizeof(elem)); + elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(elem.name, "V_REFOUT Enable", sizeof(elem.name)); + snd_ctl_remove_id(card, &elem); + + /* add the OLPC-specific controls */ + for (i = 0; i < ARRAY_SIZE(olpc_cs5535audio_ctls); i++) { + err = snd_ctl_add(card, snd_ctl_new1(&olpc_cs5535audio_ctls[i], + ac97->private_data)); + if (err < 0) + return err; + } + + /* turn off the mic by default */ + olpc_mic_bias(ac97, 0); + return 0; +} + +void olpc_quirks_cleanup(void) +{ + if (machine_is_olpc()) + gpio_free(OLPC_GPIO_MIC_AC); +} diff --git a/sound/pci/cs5535audio/cs5535audio_pcm.c b/sound/pci/cs5535audio/cs5535audio_pcm.c new file mode 100644 index 000000000..48b99a07e --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio_pcm.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for audio on multifunction CS5535 companion device + * Copyright (C) Jaya Kumar + * + * Based on Jaroslav Kysela and Takashi Iwai's examples. + * This work was sponsored by CIS(M) Sdn Bhd. + * + * todo: add be fmt support, spdif, pm + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs5535audio.h" + +static const struct snd_pcm_hardware snd_cs5535audio_playback = +{ + .info = ( + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME + ), + .formats = ( + SNDRV_PCM_FMTBIT_S16_LE + ), + .rates = ( + SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_48000 + ), + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (64*1024 - 16), + .periods_min = 1, + .periods_max = CS5535AUDIO_MAX_DESCRIPTORS, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_cs5535audio_capture = +{ + .info = ( + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID + ), + .formats = ( + SNDRV_PCM_FMTBIT_S16_LE + ), + .rates = ( + SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_48000 + ), + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (64*1024 - 16), + .periods_min = 1, + .periods_max = CS5535AUDIO_MAX_DESCRIPTORS, + .fifo_size = 0, +}; + +static int snd_cs5535audio_playback_open(struct snd_pcm_substream *substream) +{ + int err; + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_cs5535audio_playback; + runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_FRONT_DAC]; + snd_pcm_limit_hw_rates(runtime); + cs5535au->playback_substream = substream; + runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK]); + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + return 0; +} + +static int snd_cs5535audio_playback_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +#define CS5535AUDIO_DESC_LIST_SIZE \ + PAGE_ALIGN(CS5535AUDIO_MAX_DESCRIPTORS * sizeof(struct cs5535audio_dma_desc)) + +static int cs5535audio_build_dma_packets(struct cs5535audio *cs5535au, + struct cs5535audio_dma *dma, + struct snd_pcm_substream *substream, + unsigned int periods, + unsigned int period_bytes) +{ + unsigned int i; + u32 addr, jmpprd_addr; + struct cs5535audio_dma_desc *lastdesc; + + if (periods > CS5535AUDIO_MAX_DESCRIPTORS) + return -ENOMEM; + + if (dma->desc_buf.area == NULL) { + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + &cs5535au->pci->dev, + CS5535AUDIO_DESC_LIST_SIZE+1, + &dma->desc_buf) < 0) + return -ENOMEM; + dma->period_bytes = dma->periods = 0; + } + + if (dma->periods == periods && dma->period_bytes == period_bytes) + return 0; + + /* the u32 cast is okay because in snd*create we successfully told + pci alloc that we're only 32 bit capable so the upper will be 0 */ + addr = (u32) substream->runtime->dma_addr; + for (i = 0; i < periods; i++) { + struct cs5535audio_dma_desc *desc = + &((struct cs5535audio_dma_desc *) dma->desc_buf.area)[i]; + desc->addr = cpu_to_le32(addr); + desc->size = cpu_to_le16(period_bytes); + desc->ctlreserved = cpu_to_le16(PRD_EOP); + addr += period_bytes; + } + /* we reserved one dummy descriptor at the end to do the PRD jump */ + lastdesc = &((struct cs5535audio_dma_desc *) dma->desc_buf.area)[periods]; + lastdesc->addr = cpu_to_le32((u32) dma->desc_buf.addr); + lastdesc->size = 0; + lastdesc->ctlreserved = cpu_to_le16(PRD_JMP); + jmpprd_addr = (u32)dma->desc_buf.addr + + sizeof(struct cs5535audio_dma_desc) * periods; + + dma->substream = substream; + dma->period_bytes = period_bytes; + dma->periods = periods; + guard(spinlock_irq)(&cs5535au->reg_lock); + dma->ops->disable_dma(cs5535au); + dma->ops->setup_prd(cs5535au, jmpprd_addr); + return 0; +} + +static void cs5535audio_playback_enable_dma(struct cs5535audio *cs5535au) +{ + cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_EN); +} + +static void cs5535audio_playback_disable_dma(struct cs5535audio *cs5535au) +{ + cs_writeb(cs5535au, ACC_BM0_CMD, 0); +} + +static void cs5535audio_playback_pause_dma(struct cs5535audio *cs5535au) +{ + cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE); +} + +static void cs5535audio_playback_setup_prd(struct cs5535audio *cs5535au, + u32 prd_addr) +{ + cs_writel(cs5535au, ACC_BM0_PRD, prd_addr); +} + +static u32 cs5535audio_playback_read_prd(struct cs5535audio *cs5535au) +{ + return cs_readl(cs5535au, ACC_BM0_PRD); +} + +static u32 cs5535audio_playback_read_dma_pntr(struct cs5535audio *cs5535au) +{ + return cs_readl(cs5535au, ACC_BM0_PNTR); +} + +static void cs5535audio_capture_enable_dma(struct cs5535audio *cs5535au) +{ + cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_EN); +} + +static void cs5535audio_capture_disable_dma(struct cs5535audio *cs5535au) +{ + cs_writeb(cs5535au, ACC_BM1_CMD, 0); +} + +static void cs5535audio_capture_pause_dma(struct cs5535audio *cs5535au) +{ + cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE); +} + +static void cs5535audio_capture_setup_prd(struct cs5535audio *cs5535au, + u32 prd_addr) +{ + cs_writel(cs5535au, ACC_BM1_PRD, prd_addr); +} + +static u32 cs5535audio_capture_read_prd(struct cs5535audio *cs5535au) +{ + return cs_readl(cs5535au, ACC_BM1_PRD); +} + +static u32 cs5535audio_capture_read_dma_pntr(struct cs5535audio *cs5535au) +{ + return cs_readl(cs5535au, ACC_BM1_PNTR); +} + +static void cs5535audio_clear_dma_packets(struct cs5535audio *cs5535au, + struct cs5535audio_dma *dma, + struct snd_pcm_substream *substream) +{ + snd_dma_free_pages(&dma->desc_buf); + dma->desc_buf.area = NULL; + dma->substream = NULL; +} + +static int snd_cs5535audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + struct cs5535audio_dma *dma = substream->runtime->private_data; + int err; + + dma->buf_addr = substream->runtime->dma_addr; + dma->buf_bytes = params_buffer_bytes(hw_params); + + err = cs5535audio_build_dma_packets(cs5535au, dma, substream, + params_periods(hw_params), + params_period_bytes(hw_params)); + if (!err) + dma->pcm_open_flag = 1; + + return err; +} + +static int snd_cs5535audio_hw_free(struct snd_pcm_substream *substream) +{ + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + struct cs5535audio_dma *dma = substream->runtime->private_data; + + if (dma->pcm_open_flag) { + if (substream == cs5535au->playback_substream) + snd_ac97_update_power(cs5535au->ac97, + AC97_PCM_FRONT_DAC_RATE, 0); + else + snd_ac97_update_power(cs5535au->ac97, + AC97_PCM_LR_ADC_RATE, 0); + dma->pcm_open_flag = 0; + } + cs5535audio_clear_dma_packets(cs5535au, dma, substream); + return 0; +} + +static int snd_cs5535audio_playback_prepare(struct snd_pcm_substream *substream) +{ + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE, + substream->runtime->rate); +} + +static int snd_cs5535audio_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + struct cs5535audio_dma *dma = substream->runtime->private_data; + + guard(spinlock)(&cs5535au->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dma->ops->pause_dma(cs5535au); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dma->ops->enable_dma(cs5535au); + break; + case SNDRV_PCM_TRIGGER_START: + dma->ops->enable_dma(cs5535au); + break; + case SNDRV_PCM_TRIGGER_RESUME: + dma->ops->enable_dma(cs5535au); + break; + case SNDRV_PCM_TRIGGER_STOP: + dma->ops->disable_dma(cs5535au); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + dma->ops->disable_dma(cs5535au); + break; + default: + dev_err(cs5535au->card->dev, "unhandled trigger\n"); + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t snd_cs5535audio_pcm_pointer(struct snd_pcm_substream + *substream) +{ + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + u32 curdma; + struct cs5535audio_dma *dma; + + dma = substream->runtime->private_data; + curdma = dma->ops->read_dma_pntr(cs5535au); + if (curdma < dma->buf_addr) { + dev_err(cs5535au->card->dev, "curdma=%x < %x bufaddr.\n", + curdma, dma->buf_addr); + return 0; + } + curdma -= dma->buf_addr; + if (curdma >= dma->buf_bytes) { + dev_err(cs5535au->card->dev, "diff=%x >= %x buf_bytes.\n", + curdma, dma->buf_bytes); + return 0; + } + return bytes_to_frames(substream->runtime, curdma); +} + +static int snd_cs5535audio_capture_open(struct snd_pcm_substream *substream) +{ + int err; + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_cs5535audio_capture; + runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_ADC]; + snd_pcm_limit_hw_rates(runtime); + cs5535au->capture_substream = substream; + runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE]); + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + olpc_capture_open(cs5535au->ac97); + return 0; +} + +static int snd_cs5535audio_capture_close(struct snd_pcm_substream *substream) +{ + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + olpc_capture_close(cs5535au->ac97); + return 0; +} + +static int snd_cs5535audio_capture_prepare(struct snd_pcm_substream *substream) +{ + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_LR_ADC_RATE, + substream->runtime->rate); +} + +static const struct snd_pcm_ops snd_cs5535audio_playback_ops = { + .open = snd_cs5535audio_playback_open, + .close = snd_cs5535audio_playback_close, + .hw_params = snd_cs5535audio_hw_params, + .hw_free = snd_cs5535audio_hw_free, + .prepare = snd_cs5535audio_playback_prepare, + .trigger = snd_cs5535audio_trigger, + .pointer = snd_cs5535audio_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_cs5535audio_capture_ops = { + .open = snd_cs5535audio_capture_open, + .close = snd_cs5535audio_capture_close, + .hw_params = snd_cs5535audio_hw_params, + .hw_free = snd_cs5535audio_hw_free, + .prepare = snd_cs5535audio_capture_prepare, + .trigger = snd_cs5535audio_trigger, + .pointer = snd_cs5535audio_pcm_pointer, +}; + +static const struct cs5535audio_dma_ops snd_cs5535audio_playback_dma_ops = { + .type = CS5535AUDIO_DMA_PLAYBACK, + .enable_dma = cs5535audio_playback_enable_dma, + .disable_dma = cs5535audio_playback_disable_dma, + .setup_prd = cs5535audio_playback_setup_prd, + .read_prd = cs5535audio_playback_read_prd, + .pause_dma = cs5535audio_playback_pause_dma, + .read_dma_pntr = cs5535audio_playback_read_dma_pntr, +}; + +static const struct cs5535audio_dma_ops snd_cs5535audio_capture_dma_ops = { + .type = CS5535AUDIO_DMA_CAPTURE, + .enable_dma = cs5535audio_capture_enable_dma, + .disable_dma = cs5535audio_capture_disable_dma, + .setup_prd = cs5535audio_capture_setup_prd, + .read_prd = cs5535audio_capture_read_prd, + .pause_dma = cs5535audio_capture_pause_dma, + .read_dma_pntr = cs5535audio_capture_read_dma_pntr, +}; + +int snd_cs5535audio_pcm(struct cs5535audio *cs5535au) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(cs5535au->card, "CS5535 Audio", 0, 1, 1, &pcm); + if (err < 0) + return err; + + cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK].ops = + &snd_cs5535audio_playback_dma_ops; + cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE].ops = + &snd_cs5535audio_capture_dma_ops; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_cs5535audio_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_cs5535audio_capture_ops); + + pcm->private_data = cs5535au; + pcm->info_flags = 0; + strscpy(pcm->name, "CS5535 Audio"); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &cs5535au->pci->dev, + 64*1024, 128*1024); + cs5535au->pcm = pcm; + + return 0; +} + diff --git a/sound/pci/cs5535audio/cs5535audio_pm.c b/sound/pci/cs5535audio/cs5535audio_pm.c new file mode 100644 index 000000000..90fb73a9d --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio_pm.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Power management for audio on multifunction CS5535 companion device + * Copyright (C) Jaya Kumar + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs5535audio.h" + +static void snd_cs5535audio_stop_hardware(struct cs5535audio *cs5535au) +{ + /* + we depend on snd_ac97_suspend to tell the + AC97 codec to shutdown. the amd spec suggests + that the LNK_SHUTDOWN be done at the same time + that the codec power-down is issued. instead, + we do it just after rather than at the same + time. excluding codec specific build_ops->suspend + ac97 powerdown hits: + 0x8000 EAPD + 0x4000 Headphone amplifier + 0x0300 ADC & DAC + 0x0400 Analog Mixer powerdown (Vref on) + I am not sure if this is the best that we can do. + The remainder to be investigated are: + - analog mixer (vref off) 0x0800 + - AC-link powerdown 0x1000 + - codec internal clock 0x2000 + */ + + /* set LNK_SHUTDOWN to shutdown AC link */ + cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_SHUTDOWN); + +} + +static int __maybe_unused snd_cs5535audio_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct cs5535audio *cs5535au = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(cs5535au->ac97); + for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) { + struct cs5535audio_dma *dma = &cs5535au->dmas[i]; + if (dma && dma->substream) + dma->saved_prd = dma->ops->read_prd(cs5535au); + } + /* save important regs, then disable aclink in hw */ + snd_cs5535audio_stop_hardware(cs5535au); + return 0; +} + +static int __maybe_unused snd_cs5535audio_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct cs5535audio *cs5535au = card->private_data; + u32 tmp; + int timeout; + int i; + + /* set LNK_WRM_RST to reset AC link */ + cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_WRM_RST); + + timeout = 50; + do { + tmp = cs_readl(cs5535au, ACC_CODEC_STATUS); + if (tmp & PRM_RDY_STS) + break; + udelay(1); + } while (--timeout); + + if (!timeout) + dev_err(cs5535au->card->dev, "Failure getting AC Link ready\n"); + + /* set up rate regs, dma. actual initiation is done in trig */ + for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) { + struct cs5535audio_dma *dma = &cs5535au->dmas[i]; + if (dma && dma->substream) { + dma->substream->ops->prepare(dma->substream); + dma->ops->setup_prd(cs5535au, dma->saved_prd); + } + } + + /* we depend on ac97 to perform the codec power up */ + snd_ac97_resume(cs5535au->ac97); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} + +SIMPLE_DEV_PM_OPS(snd_cs5535audio_pm, snd_cs5535audio_suspend, snd_cs5535audio_resume); diff --git a/sound/pci/ctxfi/Makefile b/sound/pci/ctxfi/Makefile new file mode 100644 index 000000000..ff2b1cba3 --- /dev/null +++ b/sound/pci/ctxfi/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-ctxfi-y := xfi.o ctatc.o ctvmem.o ctpcm.o ctmixer.o ctresource.o \ + ctsrc.o ctamixer.o ctdaio.o ctimap.o cthardware.o cttimer.o \ + cthw20k2.o cthw20k1.o + +obj-$(CONFIG_SND_CTXFI) += snd-ctxfi.o diff --git a/sound/pci/ctxfi/ct20k1reg.h b/sound/pci/ctxfi/ct20k1reg.h new file mode 100644 index 000000000..05bb006c0 --- /dev/null +++ b/sound/pci/ctxfi/ct20k1reg.h @@ -0,0 +1,631 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + */ + +#ifndef CT20K1REG_H +#define CT20K1REG_H + +/* 20k1 registers */ +#define DSPXRAM_START 0x000000 +#define DSPXRAM_END 0x013FFC +#define DSPAXRAM_START 0x020000 +#define DSPAXRAM_END 0x023FFC +#define DSPYRAM_START 0x040000 +#define DSPYRAM_END 0x04FFFC +#define DSPAYRAM_START 0x020000 +#define DSPAYRAM_END 0x063FFC +#define DSPMICRO_START 0x080000 +#define DSPMICRO_END 0x0B3FFC +#define DSP0IO_START 0x100000 +#define DSP0IO_END 0x101FFC +#define AUDIORINGIPDSP0_START 0x100000 +#define AUDIORINGIPDSP0_END 0x1003FC +#define AUDIORINGOPDSP0_START 0x100400 +#define AUDIORINGOPDSP0_END 0x1007FC +#define AUDPARARINGIODSP0_START 0x100800 +#define AUDPARARINGIODSP0_END 0x100BFC +#define DSP0LOCALHWREG_START 0x100C00 +#define DSP0LOCALHWREG_END 0x100C3C +#define DSP0XYRAMAGINDEX_START 0x100C40 +#define DSP0XYRAMAGINDEX_END 0x100C5C +#define DSP0XYRAMAGMDFR_START 0x100C60 +#define DSP0XYRAMAGMDFR_END 0x100C7C +#define DSP0INTCONTLVEC_START 0x100C80 +#define DSP0INTCONTLVEC_END 0x100CD8 +#define INTCONTLGLOBALREG_START 0x100D1C +#define INTCONTLGLOBALREG_END 0x100D3C +#define HOSTINTFPORTADDRCONTDSP0 0x100D40 +#define HOSTINTFPORTDATADSP0 0x100D44 +#define TIME0PERENBDSP0 0x100D60 +#define TIME0COUNTERDSP0 0x100D64 +#define TIME1PERENBDSP0 0x100D68 +#define TIME1COUNTERDSP0 0x100D6C +#define TIME2PERENBDSP0 0x100D70 +#define TIME2COUNTERDSP0 0x100D74 +#define TIME3PERENBDSP0 0x100D78 +#define TIME3COUNTERDSP0 0x100D7C +#define XRAMINDOPERREFNOUP_STARTDSP0 0x100D80 +#define XRAMINDOPERREFNOUP_ENDDSP0 0x100D9C +#define XRAMINDOPERREFUP_STARTDSP0 0x100DA0 +#define XRAMINDOPERREFUP_ENDDSP0 0x100DBC +#define YRAMINDOPERREFNOUP_STARTDSP0 0x100DC0 +#define YRAMINDOPERREFNOUP_ENDDSP0 0x100DDC +#define YRAMINDOPERREFUP_STARTDSP0 0x100DE0 +#define YRAMINDOPERREFUP_ENDDSP0 0x100DFC +#define DSP0CONDCODE 0x100E00 +#define DSP0STACKFLAG 0x100E04 +#define DSP0PROGCOUNTSTACKPTREG 0x100E08 +#define DSP0PROGCOUNTSTACKDATAREG 0x100E0C +#define DSP0CURLOOPADDRREG 0x100E10 +#define DSP0CURLOOPCOUNT 0x100E14 +#define DSP0TOPLOOPCOUNTSTACK 0x100E18 +#define DSP0TOPLOOPADDRSTACK 0x100E1C +#define DSP0LOOPSTACKPTR 0x100E20 +#define DSP0STASSTACKDATAREG 0x100E24 +#define DSP0STASSTACKPTR 0x100E28 +#define DSP0PROGCOUNT 0x100E2C +#define GLOBDSPDEBGREG 0x100E30 +#define GLOBDSPBREPTRREG 0x100E30 +#define DSP0XYRAMBASE_START 0x100EA0 +#define DSP0XYRAMBASE_END 0x100EBC +#define DSP0XYRAMLENG_START 0x100EC0 +#define DSP0XYRAMLENG_END 0x100EDC +#define SEMAPHOREREGDSP0 0x100EE0 +#define DSP0INTCONTMASKREG 0x100EE4 +#define DSP0INTCONTPENDREG 0x100EE8 +#define DSP0INTCONTSERVINT 0x100EEC +#define DSPINTCONTEXTINTMODREG 0x100EEC +#define GPIODSP0 0x100EFC +#define DMADSPBASEADDRREG_STARTDSP0 0x100F00 +#define DMADSPBASEADDRREG_ENDDSP0 0x100F1C +#define DMAHOSTBASEADDRREG_STARTDSP0 0x100F20 +#define DMAHOSTBASEADDRREG_ENDDSP0 0x100F3C +#define DMADSPCURADDRREG_STARTDSP0 0x100F40 +#define DMADSPCURADDRREG_ENDDSP0 0x100F5C +#define DMAHOSTCURADDRREG_STARTDSP0 0x100F60 +#define DMAHOSTCURADDRREG_ENDDSP0 0x100F7C +#define DMATANXCOUNTREG_STARTDSP0 0x100F80 +#define DMATANXCOUNTREG_ENDDSP0 0x100F9C +#define DMATIMEBUGREG_STARTDSP0 0x100FA0 +#define DMATIMEBUGREG_ENDDSP0 0x100FAC +#define DMACNTLMODFREG_STARTDSP0 0x100FA0 +#define DMACNTLMODFREG_ENDDSP0 0x100FAC + +#define DMAGLOBSTATSREGDSP0 0x100FEC +#define DSP0XGPRAM_START 0x101000 +#define DSP0XGPRAM_END 0x1017FC +#define DSP0YGPRAM_START 0x101800 +#define DSP0YGPRAM_END 0x101FFC + + + + +#define AUDIORINGIPDSP1_START 0x102000 +#define AUDIORINGIPDSP1_END 0x1023FC +#define AUDIORINGOPDSP1_START 0x102400 +#define AUDIORINGOPDSP1_END 0x1027FC +#define AUDPARARINGIODSP1_START 0x102800 +#define AUDPARARINGIODSP1_END 0x102BFC +#define DSP1LOCALHWREG_START 0x102C00 +#define DSP1LOCALHWREG_END 0x102C3C +#define DSP1XYRAMAGINDEX_START 0x102C40 +#define DSP1XYRAMAGINDEX_END 0x102C5C +#define DSP1XYRAMAGMDFR_START 0x102C60 +#define DSP1XYRAMAGMDFR_END 0x102C7C +#define DSP1INTCONTLVEC_START 0x102C80 +#define DSP1INTCONTLVEC_END 0x102CD8 +#define HOSTINTFPORTADDRCONTDSP1 0x102D40 +#define HOSTINTFPORTDATADSP1 0x102D44 +#define TIME0PERENBDSP1 0x102D60 +#define TIME0COUNTERDSP1 0x102D64 +#define TIME1PERENBDSP1 0x102D68 +#define TIME1COUNTERDSP1 0x102D6C +#define TIME2PERENBDSP1 0x102D70 +#define TIME2COUNTERDSP1 0x102D74 +#define TIME3PERENBDSP1 0x102D78 +#define TIME3COUNTERDSP1 0x102D7C +#define XRAMINDOPERREFNOUP_STARTDSP1 0x102D80 +#define XRAMINDOPERREFNOUP_ENDDSP1 0x102D9C +#define XRAMINDOPERREFUP_STARTDSP1 0x102DA0 +#define XRAMINDOPERREFUP_ENDDSP1 0x102DBC +#define YRAMINDOPERREFNOUP_STARTDSP1 0x102DC0 +#define YRAMINDOPERREFNOUP_ENDDSP1 0x102DDC +#define YRAMINDOPERREFUP_STARTDSP1 0x102DE0 +#define YRAMINDOPERREFUP_ENDDSP1 0x102DFC + +#define DSP1CONDCODE 0x102E00 +#define DSP1STACKFLAG 0x102E04 +#define DSP1PROGCOUNTSTACKPTREG 0x102E08 +#define DSP1PROGCOUNTSTACKDATAREG 0x102E0C +#define DSP1CURLOOPADDRREG 0x102E10 +#define DSP1CURLOOPCOUNT 0x102E14 +#define DSP1TOPLOOPCOUNTSTACK 0x102E18 +#define DSP1TOPLOOPADDRSTACK 0x102E1C +#define DSP1LOOPSTACKPTR 0x102E20 +#define DSP1STASSTACKDATAREG 0x102E24 +#define DSP1STASSTACKPTR 0x102E28 +#define DSP1PROGCOUNT 0x102E2C +#define DSP1XYRAMBASE_START 0x102EA0 +#define DSP1XYRAMBASE_END 0x102EBC +#define DSP1XYRAMLENG_START 0x102EC0 +#define DSP1XYRAMLENG_END 0x102EDC +#define SEMAPHOREREGDSP1 0x102EE0 +#define DSP1INTCONTMASKREG 0x102EE4 +#define DSP1INTCONTPENDREG 0x102EE8 +#define DSP1INTCONTSERVINT 0x102EEC +#define GPIODSP1 0x102EFC +#define DMADSPBASEADDRREG_STARTDSP1 0x102F00 +#define DMADSPBASEADDRREG_ENDDSP1 0x102F1C +#define DMAHOSTBASEADDRREG_STARTDSP1 0x102F20 +#define DMAHOSTBASEADDRREG_ENDDSP1 0x102F3C +#define DMADSPCURADDRREG_STARTDSP1 0x102F40 +#define DMADSPCURADDRREG_ENDDSP1 0x102F5C +#define DMAHOSTCURADDRREG_STARTDSP1 0x102F60 +#define DMAHOSTCURADDRREG_ENDDSP1 0x102F7C +#define DMATANXCOUNTREG_STARTDSP1 0x102F80 +#define DMATANXCOUNTREG_ENDDSP1 0x102F9C +#define DMATIMEBUGREG_STARTDSP1 0x102FA0 +#define DMATIMEBUGREG_ENDDSP1 0x102FAC +#define DMACNTLMODFREG_STARTDSP1 0x102FA0 +#define DMACNTLMODFREG_ENDDSP1 0x102FAC + +#define DMAGLOBSTATSREGDSP1 0x102FEC +#define DSP1XGPRAM_START 0x103000 +#define DSP1XGPRAM_END 0x1033FC +#define DSP1YGPRAM_START 0x103400 +#define DSP1YGPRAM_END 0x1037FC + + + +#define AUDIORINGIPDSP2_START 0x104000 +#define AUDIORINGIPDSP2_END 0x1043FC +#define AUDIORINGOPDSP2_START 0x104400 +#define AUDIORINGOPDSP2_END 0x1047FC +#define AUDPARARINGIODSP2_START 0x104800 +#define AUDPARARINGIODSP2_END 0x104BFC +#define DSP2LOCALHWREG_START 0x104C00 +#define DSP2LOCALHWREG_END 0x104C3C +#define DSP2XYRAMAGINDEX_START 0x104C40 +#define DSP2XYRAMAGINDEX_END 0x104C5C +#define DSP2XYRAMAGMDFR_START 0x104C60 +#define DSP2XYRAMAGMDFR_END 0x104C7C +#define DSP2INTCONTLVEC_START 0x104C80 +#define DSP2INTCONTLVEC_END 0x104CD8 +#define HOSTINTFPORTADDRCONTDSP2 0x104D40 +#define HOSTINTFPORTDATADSP2 0x104D44 +#define TIME0PERENBDSP2 0x104D60 +#define TIME0COUNTERDSP2 0x104D64 +#define TIME1PERENBDSP2 0x104D68 +#define TIME1COUNTERDSP2 0x104D6C +#define TIME2PERENBDSP2 0x104D70 +#define TIME2COUNTERDSP2 0x104D74 +#define TIME3PERENBDSP2 0x104D78 +#define TIME3COUNTERDSP2 0x104D7C +#define XRAMINDOPERREFNOUP_STARTDSP2 0x104D80 +#define XRAMINDOPERREFNOUP_ENDDSP2 0x104D9C +#define XRAMINDOPERREFUP_STARTDSP2 0x104DA0 +#define XRAMINDOPERREFUP_ENDDSP2 0x104DBC +#define YRAMINDOPERREFNOUP_STARTDSP2 0x104DC0 +#define YRAMINDOPERREFNOUP_ENDDSP2 0x104DDC +#define YRAMINDOPERREFUP_STARTDSP2 0x104DE0 +#define YRAMINDOPERREFUP_ENDDSP2 0x104DFC +#define DSP2CONDCODE 0x104E00 +#define DSP2STACKFLAG 0x104E04 +#define DSP2PROGCOUNTSTACKPTREG 0x104E08 +#define DSP2PROGCOUNTSTACKDATAREG 0x104E0C +#define DSP2CURLOOPADDRREG 0x104E10 +#define DSP2CURLOOPCOUNT 0x104E14 +#define DSP2TOPLOOPCOUNTSTACK 0x104E18 +#define DSP2TOPLOOPADDRSTACK 0x104E1C +#define DSP2LOOPSTACKPTR 0x104E20 +#define DSP2STASSTACKDATAREG 0x104E24 +#define DSP2STASSTACKPTR 0x104E28 +#define DSP2PROGCOUNT 0x104E2C +#define DSP2XYRAMBASE_START 0x104EA0 +#define DSP2XYRAMBASE_END 0x104EBC +#define DSP2XYRAMLENG_START 0x104EC0 +#define DSP2XYRAMLENG_END 0x104EDC +#define SEMAPHOREREGDSP2 0x104EE0 +#define DSP2INTCONTMASKREG 0x104EE4 +#define DSP2INTCONTPENDREG 0x104EE8 +#define DSP2INTCONTSERVINT 0x104EEC +#define GPIODSP2 0x104EFC +#define DMADSPBASEADDRREG_STARTDSP2 0x104F00 +#define DMADSPBASEADDRREG_ENDDSP2 0x104F1C +#define DMAHOSTBASEADDRREG_STARTDSP2 0x104F20 +#define DMAHOSTBASEADDRREG_ENDDSP2 0x104F3C +#define DMADSPCURADDRREG_STARTDSP2 0x104F40 +#define DMADSPCURADDRREG_ENDDSP2 0x104F5C +#define DMAHOSTCURADDRREG_STARTDSP2 0x104F60 +#define DMAHOSTCURADDRREG_ENDDSP2 0x104F7C +#define DMATANXCOUNTREG_STARTDSP2 0x104F80 +#define DMATANXCOUNTREG_ENDDSP2 0x104F9C +#define DMATIMEBUGREG_STARTDSP2 0x104FA0 +#define DMATIMEBUGREG_ENDDSP2 0x104FAC +#define DMACNTLMODFREG_STARTDSP2 0x104FA0 +#define DMACNTLMODFREG_ENDDSP2 0x104FAC + +#define DMAGLOBSTATSREGDSP2 0x104FEC +#define DSP2XGPRAM_START 0x105000 +#define DSP2XGPRAM_END 0x1051FC +#define DSP2YGPRAM_START 0x105800 +#define DSP2YGPRAM_END 0x1059FC + + + +#define AUDIORINGIPDSP3_START 0x106000 +#define AUDIORINGIPDSP3_END 0x1063FC +#define AUDIORINGOPDSP3_START 0x106400 +#define AUDIORINGOPDSP3_END 0x1067FC +#define AUDPARARINGIODSP3_START 0x106800 +#define AUDPARARINGIODSP3_END 0x106BFC +#define DSP3LOCALHWREG_START 0x106C00 +#define DSP3LOCALHWREG_END 0x106C3C +#define DSP3XYRAMAGINDEX_START 0x106C40 +#define DSP3XYRAMAGINDEX_END 0x106C5C +#define DSP3XYRAMAGMDFR_START 0x106C60 +#define DSP3XYRAMAGMDFR_END 0x106C7C +#define DSP3INTCONTLVEC_START 0x106C80 +#define DSP3INTCONTLVEC_END 0x106CD8 +#define HOSTINTFPORTADDRCONTDSP3 0x106D40 +#define HOSTINTFPORTDATADSP3 0x106D44 +#define TIME0PERENBDSP3 0x106D60 +#define TIME0COUNTERDSP3 0x106D64 +#define TIME1PERENBDSP3 0x106D68 +#define TIME1COUNTERDSP3 0x106D6C +#define TIME2PERENBDSP3 0x106D70 +#define TIME2COUNTERDSP3 0x106D74 +#define TIME3PERENBDSP3 0x106D78 +#define TIME3COUNTERDSP3 0x106D7C +#define XRAMINDOPERREFNOUP_STARTDSP3 0x106D80 +#define XRAMINDOPERREFNOUP_ENDDSP3 0x106D9C +#define XRAMINDOPERREFUP_STARTDSP3 0x106DA0 +#define XRAMINDOPERREFUP_ENDDSP3 0x106DBC +#define YRAMINDOPERREFNOUP_STARTDSP3 0x106DC0 +#define YRAMINDOPERREFNOUP_ENDDSP3 0x106DDC +#define YRAMINDOPERREFUP_STARTDSP3 0x106DE0 +#define YRAMINDOPERREFUP_ENDDSP3 0x100DFC + +#define DSP3CONDCODE 0x106E00 +#define DSP3STACKFLAG 0x106E04 +#define DSP3PROGCOUNTSTACKPTREG 0x106E08 +#define DSP3PROGCOUNTSTACKDATAREG 0x106E0C +#define DSP3CURLOOPADDRREG 0x106E10 +#define DSP3CURLOOPCOUNT 0x106E14 +#define DSP3TOPLOOPCOUNTSTACK 0x106E18 +#define DSP3TOPLOOPADDRSTACK 0x106E1C +#define DSP3LOOPSTACKPTR 0x106E20 +#define DSP3STASSTACKDATAREG 0x106E24 +#define DSP3STASSTACKPTR 0x106E28 +#define DSP3PROGCOUNT 0x106E2C +#define DSP3XYRAMBASE_START 0x106EA0 +#define DSP3XYRAMBASE_END 0x106EBC +#define DSP3XYRAMLENG_START 0x106EC0 +#define DSP3XYRAMLENG_END 0x106EDC +#define SEMAPHOREREGDSP3 0x106EE0 +#define DSP3INTCONTMASKREG 0x106EE4 +#define DSP3INTCONTPENDREG 0x106EE8 +#define DSP3INTCONTSERVINT 0x106EEC +#define GPIODSP3 0x106EFC +#define DMADSPBASEADDRREG_STARTDSP3 0x106F00 +#define DMADSPBASEADDRREG_ENDDSP3 0x106F1C +#define DMAHOSTBASEADDRREG_STARTDSP3 0x106F20 +#define DMAHOSTBASEADDRREG_ENDDSP3 0x106F3C +#define DMADSPCURADDRREG_STARTDSP3 0x106F40 +#define DMADSPCURADDRREG_ENDDSP3 0x106F5C +#define DMAHOSTCURADDRREG_STARTDSP3 0x106F60 +#define DMAHOSTCURADDRREG_ENDDSP3 0x106F7C +#define DMATANXCOUNTREG_STARTDSP3 0x106F80 +#define DMATANXCOUNTREG_ENDDSP3 0x106F9C +#define DMATIMEBUGREG_STARTDSP3 0x106FA0 +#define DMATIMEBUGREG_ENDDSP3 0x106FAC +#define DMACNTLMODFREG_STARTDSP3 0x106FA0 +#define DMACNTLMODFREG_ENDDSP3 0x106FAC + +#define DMAGLOBSTATSREGDSP3 0x106FEC +#define DSP3XGPRAM_START 0x107000 +#define DSP3XGPRAM_END 0x1071FC +#define DSP3YGPRAM_START 0x107800 +#define DSP3YGPRAM_END 0x1079FC + +/* end of DSP reg definitions */ + +#define DSPAIMAP_START 0x108000 +#define DSPAIMAP_END 0x1083FC +#define DSPPIMAP_START 0x108400 +#define DSPPIMAP_END 0x1087FC +#define DSPPOMAP_START 0x108800 +#define DSPPOMAP_END 0x108BFC +#define DSPPOCTL 0x108C00 +#define TKCTL_START 0x110000 +#define TKCTL_END 0x110FFC +#define TKCC_START 0x111000 +#define TKCC_END 0x111FFC +#define TKIMAP_START 0x112000 +#define TKIMAP_END 0x112FFC +#define TKDCTR16 0x113000 +#define TKPB16 0x113004 +#define TKBS16 0x113008 +#define TKDCTR32 0x11300C +#define TKPB32 0x113010 +#define TKBS32 0x113014 +#define ICDCTR16 0x113018 +#define ITBS16 0x11301C +#define ICDCTR32 0x113020 +#define ITBS32 0x113024 +#define ITSTART 0x113028 +#define TKSQ 0x11302C + +#define TKSCCTL_START 0x114000 +#define TKSCCTL_END 0x11403C +#define TKSCADR_START 0x114100 +#define TKSCADR_END 0x11413C +#define TKSCDATAX_START 0x114800 +#define TKSCDATAX_END 0x1149FC +#define TKPCDATAX_START 0x120000 +#define TKPCDATAX_END 0x12FFFC + +#define MALSA 0x130000 +#define MAPPHA 0x130004 +#define MAPPLA 0x130008 +#define MALSB 0x130010 +#define MAPPHB 0x130014 +#define MAPPLB 0x130018 + +#define TANSPORTMAPABREGS_START 0x130020 +#define TANSPORTMAPABREGS_END 0x13A2FC + +#define PTPAHX 0x13B000 +#define PTPALX 0x13B004 + +#define TANSPPAGETABLEPHYADDR015_START 0x13B008 +#define TANSPPAGETABLEPHYADDR015_END 0x13B07C +#define TRNQADRX_START 0x13B100 +#define TRNQADRX_END 0x13B13C +#define TRNQTIMX_START 0x13B200 +#define TRNQTIMX_END 0x13B23C +#define TRNQAPARMX_START 0x13B300 +#define TRNQAPARMX_END 0x13B33C + +#define TRNQCNT 0x13B400 +#define TRNCTL 0x13B404 +#define TRNIS 0x13B408 +#define TRNCURTS 0x13B40C + +#define AMOP_START 0x140000 +#define AMOPLO 0x140000 +#define AMOPHI 0x140004 +#define AMOP_END 0x147FFC +#define PMOP_START 0x148000 +#define PMOPLO 0x148000 +#define PMOPHI 0x148004 +#define PMOP_END 0x14FFFC +#define PCURR_START 0x150000 +#define PCURR_END 0x153FFC +#define PTRAG_START 0x154000 +#define PTRAG_END 0x157FFC +#define PSR_START 0x158000 +#define PSR_END 0x15BFFC + +#define PFSTAT4SEG_START 0x160000 +#define PFSTAT4SEG_END 0x160BFC +#define PFSTAT2SEG_START 0x160C00 +#define PFSTAT2SEG_END 0x1617FC +#define PFTARG4SEG_START 0x164000 +#define PFTARG4SEG_END 0x164BFC +#define PFTARG2SEG_START 0x164C00 +#define PFTARG2SEG_END 0x1657FC +#define PFSR4SEG_START 0x168000 +#define PFSR4SEG_END 0x168BFC +#define PFSR2SEG_START 0x168C00 +#define PFSR2SEG_END 0x1697FC +#define PCURRMS4SEG_START 0x16C000 +#define PCURRMS4SEG_END 0x16CCFC +#define PCURRMS2SEG_START 0x16CC00 +#define PCURRMS2SEG_END 0x16D7FC +#define PTARGMS4SEG_START 0x170000 +#define PTARGMS4SEG_END 0x172FFC +#define PTARGMS2SEG_START 0x173000 +#define PTARGMS2SEG_END 0x1747FC +#define PSRMS4SEG_START 0x170000 +#define PSRMS4SEG_END 0x172FFC +#define PSRMS2SEG_START 0x173000 +#define PSRMS2SEG_END 0x1747FC + +#define PRING_LO_START 0x190000 +#define PRING_LO_END 0x193FFC +#define PRING_HI_START 0x194000 +#define PRING_HI_END 0x197FFC +#define PRING_LO_HI_START 0x198000 +#define PRING_LO_HI 0x198000 +#define PRING_LO_HI_END 0x19BFFC + +#define PINTFIFO 0x1A0000 +#define SRCCTL 0x1B0000 +#define SRCCCR 0x1B0004 +#define SRCIMAP 0x1B0008 +#define SRCODDC 0x1B000C +#define SRCCA 0x1B0010 +#define SRCCF 0x1B0014 +#define SRCSA 0x1B0018 +#define SRCLA 0x1B001C +#define SRCCTLSWR 0x1B0020 + +/* SRC HERE */ +#define SRCALBA 0x1B002C +#define SRCMCTL 0x1B012C +#define SRCCERR 0x1B022C +#define SRCITB 0x1B032C +#define SRCIPM 0x1B082C +#define SRCIP 0x1B102C +#define SRCENBSTAT 0x1B202C +#define SRCENBLO 0x1B212C +#define SRCENBHI 0x1B222C +#define SRCENBS 0x1B232C +#define SRCENB 0x1B282C +#define SRCENB07 0x1B282C +#define SRCENBS07 0x1B302C + +#define SRCDN0Z 0x1B0030 +#define SRCDN0Z0 0x1B0030 +#define SRCDN0Z1 0x1B0034 +#define SRCDN0Z2 0x1B0038 +#define SRCDN0Z3 0x1B003C +#define SRCDN1Z 0x1B0040 +#define SRCDN1Z0 0x1B0040 +#define SRCDN1Z1 0x1B0044 +#define SRCDN1Z2 0x1B0048 +#define SRCDN1Z3 0x1B004C +#define SRCDN1Z4 0x1B0050 +#define SRCDN1Z5 0x1B0054 +#define SRCDN1Z6 0x1B0058 +#define SRCDN1Z7 0x1B005C +#define SRCUPZ 0x1B0060 +#define SRCUPZ0 0x1B0060 +#define SRCUPZ1 0x1B0064 +#define SRCUPZ2 0x1B0068 +#define SRCUPZ3 0x1B006C +#define SRCUPZ4 0x1B0070 +#define SRCUPZ5 0x1B0074 +#define SRCUPZ6 0x1B0078 +#define SRCUPZ7 0x1B007C +#define SRCCD0 0x1B0080 +#define SRCCD1 0x1B0084 +#define SRCCD2 0x1B0088 +#define SRCCD3 0x1B008C +#define SRCCD4 0x1B0090 +#define SRCCD5 0x1B0094 +#define SRCCD6 0x1B0098 +#define SRCCD7 0x1B009C +#define SRCCD8 0x1B00A0 +#define SRCCD9 0x1B00A4 +#define SRCCDA 0x1B00A8 +#define SRCCDB 0x1B00AC +#define SRCCDC 0x1B00B0 +#define SRCCDD 0x1B00B4 +#define SRCCDE 0x1B00B8 +#define SRCCDF 0x1B00BC +#define SRCCD10 0x1B00C0 +#define SRCCD11 0x1B00C4 +#define SRCCD12 0x1B00C8 +#define SRCCD13 0x1B00CC +#define SRCCD14 0x1B00D0 +#define SRCCD15 0x1B00D4 +#define SRCCD16 0x1B00D8 +#define SRCCD17 0x1B00DC +#define SRCCD18 0x1B00E0 +#define SRCCD19 0x1B00E4 +#define SRCCD1A 0x1B00E8 +#define SRCCD1B 0x1B00EC +#define SRCCD1C 0x1B00F0 +#define SRCCD1D 0x1B00F4 +#define SRCCD1E 0x1B00F8 +#define SRCCD1F 0x1B00FC + +#define SRCCONTRBLOCK_START 0x1B0100 +#define SRCCONTRBLOCK_END 0x1BFFFC +#define FILTOP_START 0x1C0000 +#define FILTOP_END 0x1C05FC +#define FILTIMAP_START 0x1C0800 +#define FILTIMAP_END 0x1C0DFC +#define FILTZ1_START 0x1C1000 +#define FILTZ1_END 0x1C15FC +#define FILTZ2_START 0x1C1800 +#define FILTZ2_END 0x1C1DFC +#define DAOIMAP_START 0x1C5000 +#define DAOIMAP 0x1C5000 +#define DAOIMAP_END 0x1C5124 + +#define AC97D 0x1C5400 +#define AC97A 0x1C5404 +#define AC97CTL 0x1C5408 +#define I2SCTL 0x1C5420 + +#define SPOS 0x1C5440 +#define SPOSA 0x1C5440 +#define SPOSB 0x1C5444 +#define SPOSC 0x1C5448 +#define SPOSD 0x1C544C + +#define SPISA 0x1C5450 +#define SPISB 0x1C5454 +#define SPISC 0x1C5458 +#define SPISD 0x1C545C + +#define SPFSCTL 0x1C5460 + +#define SPFS0 0x1C5468 +#define SPFS1 0x1C546C +#define SPFS2 0x1C5470 +#define SPFS3 0x1C5474 +#define SPFS4 0x1C5478 +#define SPFS5 0x1C547C + +#define SPOCTL 0x1C5480 +#define SPICTL 0x1C5484 +#define SPISTS 0x1C5488 +#define SPINTP 0x1C548C +#define SPINTE 0x1C5490 +#define SPUTCTLAB 0x1C5494 +#define SPUTCTLCD 0x1C5498 + +#define SRTSPA 0x1C54C0 +#define SRTSPB 0x1C54C4 +#define SRTSPC 0x1C54C8 +#define SRTSPD 0x1C54CC + +#define SRTSCTL 0x1C54D0 +#define SRTSCTLA 0x1C54D0 +#define SRTSCTLB 0x1C54D4 +#define SRTSCTLC 0x1C54D8 +#define SRTSCTLD 0x1C54DC + +#define SRTI2S 0x1C54E0 +#define SRTICTL 0x1C54F0 + +#define WC 0x1C6000 +#define TIMR 0x1C6004 +# define TIMR_IE (1<<15) +# define TIMR_IP (1<<14) + +#define GIP 0x1C6010 +#define GIE 0x1C6014 +#define DIE 0x1C6018 +#define DIC 0x1C601C +#define GPIO 0x1C6020 +#define GPIOCTL 0x1C6024 +#define GPIP 0x1C6028 +#define GPIE 0x1C602C +#define DSPINT0 0x1C6030 +#define DSPEIOC 0x1C6034 +#define MUADAT 0x1C6040 +#define MUACMD 0x1C6044 +#define MUASTAT 0x1C6044 +#define MUBDAT 0x1C6048 +#define MUBCMD 0x1C604C +#define MUBSTAT 0x1C604C +#define UARTCMA 0x1C6050 +#define UARTCMB 0x1C6054 +#define UARTIP 0x1C6058 +#define UARTIE 0x1C605C +#define PLLCTL 0x1C6060 +#define PLLDCD 0x1C6064 +#define GCTL 0x1C6070 +#define ID0 0x1C6080 +#define ID1 0x1C6084 +#define ID2 0x1C6088 +#define ID3 0x1C608C +#define SDRCTL 0x1C7000 + + +#define I2SA_L 0x0L +#define I2SA_R 0x1L +#define I2SB_L 0x8L +#define I2SB_R 0x9L +#define I2SC_L 0x10L +#define I2SC_R 0x11L +#define I2SD_L 0x18L +#define I2SD_R 0x19L + +#endif /* CT20K1REG_H */ diff --git a/sound/pci/ctxfi/ct20k2reg.h b/sound/pci/ctxfi/ct20k2reg.h new file mode 100644 index 000000000..02f67828e --- /dev/null +++ b/sound/pci/ctxfi/ct20k2reg.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + */ + +#ifndef _20K2REGISTERS_H_ +#define _20K2REGISTERS_H_ + + +/* Timer Registers */ +#define WC 0x1b7000 +#define TIMR 0x1b7004 +# define TIMR_IE (1<<15) +# define TIMR_IP (1<<14) +#define GIP 0x1b7010 +#define GIE 0x1b7014 + +/* I2C Registers */ +#define I2C_IF_ADDRESS 0x1B9000 +#define I2C_IF_WDATA 0x1B9004 +#define I2C_IF_RDATA 0x1B9008 +#define I2C_IF_STATUS 0x1B900C +#define I2C_IF_WLOCK 0x1B9010 + +/* Global Control Registers */ +#define GLOBAL_CNTL_GCTL 0x1B7090 + +/* PLL Registers */ +#define PLL_CTL 0x1B7080 +#define PLL_STAT 0x1B7084 +#define PLL_ENB 0x1B7088 + +/* SRC Registers */ +#define SRC_CTL 0x1A0000 /* 0x1A0000 + (256 * Chn) */ +#define SRC_CCR 0x1A0004 /* 0x1A0004 + (256 * Chn) */ +#define SRC_IMAP 0x1A0008 /* 0x1A0008 + (256 * Chn) */ +#define SRC_CA 0x1A0010 /* 0x1A0010 + (256 * Chn) */ +#define SRC_CF 0x1A0014 /* 0x1A0014 + (256 * Chn) */ +#define SRC_SA 0x1A0018 /* 0x1A0018 + (256 * Chn) */ +#define SRC_LA 0x1A001C /* 0x1A001C + (256 * Chn) */ +#define SRC_CTLSWR 0x1A0020 /* 0x1A0020 + (256 * Chn) */ +#define SRC_CD 0x1A0080 /* 0x1A0080 + (256 * Chn) + (4 * Regn) */ +#define SRC_MCTL 0x1A012C +#define SRC_IP 0x1A102C /* 0x1A102C + (256 * Regn) */ +#define SRC_ENB 0x1A282C /* 0x1A282C + (256 * Regn) */ +#define SRC_ENBSTAT 0x1A202C +#define SRC_ENBSA 0x1A232C +#define SRC_DN0Z 0x1A0030 +#define SRC_DN1Z 0x1A0040 +#define SRC_UPZ 0x1A0060 + +/* GPIO Registers */ +#define GPIO_DATA 0x1B7020 +#define GPIO_CTRL 0x1B7024 +#define GPIO_EXT_DATA 0x1B70A0 + +/* Virtual memory registers */ +#define VMEM_PTPAL 0x1C6300 /* 0x1C6300 + (16 * Chn) */ +#define VMEM_PTPAH 0x1C6304 /* 0x1C6304 + (16 * Chn) */ +#define VMEM_CTL 0x1C7000 + +/* Transport Registers */ +#define TRANSPORT_ENB 0x1B6000 +#define TRANSPORT_CTL 0x1B6004 +#define TRANSPORT_INT 0x1B6008 + +/* Audio IO */ +#define AUDIO_IO_AIM 0x1B5000 /* 0x1B5000 + (0x04 * Chn) */ +#define AUDIO_IO_TX_CTL 0x1B5400 /* 0x1B5400 + (0x40 * Chn) */ +#define AUDIO_IO_TX_CSTAT_L 0x1B5408 /* 0x1B5408 + (0x40 * Chn) */ +#define AUDIO_IO_TX_CSTAT_H 0x1B540C /* 0x1B540C + (0x40 * Chn) */ +#define AUDIO_IO_RX_CTL 0x1B5410 /* 0x1B5410 + (0x40 * Chn) */ +#define AUDIO_IO_RX_SRT_CTL 0x1B5420 /* 0x1B5420 + (0x40 * Chn) */ +#define AUDIO_IO_MCLK 0x1B5600 +#define AUDIO_IO_TX_BLRCLK 0x1B5604 +#define AUDIO_IO_RX_BLRCLK 0x1B5608 + +/* Mixer */ +#define MIXER_AMOPLO 0x130000 /* 0x130000 + (8 * Chn) [4095 : 0] */ +#define MIXER_AMOPHI 0x130004 /* 0x130004 + (8 * Chn) [4095 : 0] */ +#define MIXER_PRING_LO_HI 0x188000 /* 0x188000 + (4 * Chn) [4095 : 0] */ +#define MIXER_PMOPLO 0x138000 /* 0x138000 + (8 * Chn) [4095 : 0] */ +#define MIXER_PMOPHI 0x138004 /* 0x138004 + (8 * Chn) [4095 : 0] */ +#define MIXER_AR_ENABLE 0x19000C + +#endif diff --git a/sound/pci/ctxfi/ctamixer.c b/sound/pci/ctxfi/ctamixer.c new file mode 100644 index 000000000..5fc1c9226 --- /dev/null +++ b/sound/pci/ctxfi/ctamixer.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctamixer.c + * + * @Brief + * This file contains the implementation of the Audio Mixer + * resource management object. + * + * @Author Liu Chun + * @Date May 21 2008 + */ + +#include "ctamixer.h" +#include "cthardware.h" +#include + +#define AMIXER_RESOURCE_NUM 256 +#define SUM_RESOURCE_NUM 256 + +#define AMIXER_Y_IMMEDIATE 1 + +#define BLANK_SLOT 4094 + +static void amixer_master(struct rsc *rsc) +{ + rsc->conj = 0; + rsc->idx = container_of(rsc, struct amixer, rsc)->idx[0]; +} + +static void amixer_next_conj(struct rsc *rsc) +{ + rsc->conj++; +} + +static int amixer_index(const struct rsc *rsc) +{ + return container_of(rsc, struct amixer, rsc)->idx[rsc->conj]; +} + +static int amixer_output_slot(const struct rsc *rsc) +{ + return (amixer_index(rsc) << 4) + 0x4; +} + +static const struct rsc_ops amixer_basic_rsc_ops = { + .master = amixer_master, + .next_conj = amixer_next_conj, + .index = amixer_index, + .output_slot = amixer_output_slot, +}; + +static int amixer_set_input(struct amixer *amixer, struct rsc *rsc) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + hw->amixer_set_mode(amixer->rsc.ctrl_blk, AMIXER_Y_IMMEDIATE); + amixer->input = rsc; + if (!rsc) + hw->amixer_set_x(amixer->rsc.ctrl_blk, BLANK_SLOT); + else + hw->amixer_set_x(amixer->rsc.ctrl_blk, + rsc->ops->output_slot(rsc)); + + return 0; +} + +/* y is a 14-bit immediate constant */ +static int amixer_set_y(struct amixer *amixer, unsigned int y) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + hw->amixer_set_y(amixer->rsc.ctrl_blk, y); + + return 0; +} + +static int amixer_set_invalid_squash(struct amixer *amixer, unsigned int iv) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + hw->amixer_set_iv(amixer->rsc.ctrl_blk, iv); + + return 0; +} + +static int amixer_set_sum(struct amixer *amixer, struct sum *sum) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + amixer->sum = sum; + if (!sum) { + hw->amixer_set_se(amixer->rsc.ctrl_blk, 0); + } else { + hw->amixer_set_se(amixer->rsc.ctrl_blk, 1); + hw->amixer_set_sadr(amixer->rsc.ctrl_blk, + sum->rsc.ops->index(&sum->rsc)); + } + + return 0; +} + +static int amixer_commit_write(struct amixer *amixer) +{ + struct hw *hw; + unsigned int index; + int i; + struct rsc *input; + struct sum *sum; + + hw = amixer->rsc.hw; + input = amixer->input; + sum = amixer->sum; + + /* Program master and conjugate resources */ + amixer->rsc.ops->master(&amixer->rsc); + if (input) + input->ops->master(input); + + if (sum) + sum->rsc.ops->master(&sum->rsc); + + for (i = 0; i < amixer->rsc.msr; i++) { + hw->amixer_set_dirty_all(amixer->rsc.ctrl_blk); + if (input) { + hw->amixer_set_x(amixer->rsc.ctrl_blk, + input->ops->output_slot(input)); + input->ops->next_conj(input); + } + if (sum) { + hw->amixer_set_sadr(amixer->rsc.ctrl_blk, + sum->rsc.ops->index(&sum->rsc)); + sum->rsc.ops->next_conj(&sum->rsc); + } + index = amixer->rsc.ops->output_slot(&amixer->rsc); + hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk); + amixer->rsc.ops->next_conj(&amixer->rsc); + } + amixer->rsc.ops->master(&amixer->rsc); + if (input) + input->ops->master(input); + + if (sum) + sum->rsc.ops->master(&sum->rsc); + + return 0; +} + +static int amixer_commit_raw_write(struct amixer *amixer) +{ + struct hw *hw; + unsigned int index; + + hw = amixer->rsc.hw; + index = amixer->rsc.ops->output_slot(&amixer->rsc); + hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk); + + return 0; +} + +static int amixer_get_y(struct amixer *amixer) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + return hw->amixer_get_y(amixer->rsc.ctrl_blk); +} + +static int amixer_setup(struct amixer *amixer, struct rsc *input, + unsigned int scale, struct sum *sum) +{ + amixer_set_input(amixer, input); + amixer_set_y(amixer, scale); + amixer_set_sum(amixer, sum); + amixer_commit_write(amixer); + return 0; +} + +static const struct amixer_rsc_ops amixer_ops = { + .set_input = amixer_set_input, + .set_invalid_squash = amixer_set_invalid_squash, + .set_scale = amixer_set_y, + .set_sum = amixer_set_sum, + .commit_write = amixer_commit_write, + .commit_raw_write = amixer_commit_raw_write, + .setup = amixer_setup, + .get_scale = amixer_get_y, +}; + +static int amixer_rsc_init(struct amixer *amixer, + const struct amixer_desc *desc, + struct amixer_mgr *mgr) +{ + int err; + + err = rsc_init(&amixer->rsc, amixer->idx[0], + AMIXER, desc->msr, mgr->mgr.hw); + if (err) + return err; + + /* Set amixer specific operations */ + amixer->rsc.ops = &amixer_basic_rsc_ops; + amixer->rsc.conj = 0; + amixer->ops = &amixer_ops; + amixer->input = NULL; + amixer->sum = NULL; + + amixer_setup(amixer, NULL, 0, NULL); + + return 0; +} + +static int amixer_rsc_uninit(struct amixer *amixer) +{ + amixer_setup(amixer, NULL, 0, NULL); + rsc_uninit(&amixer->rsc); + amixer->ops = NULL; + amixer->input = NULL; + amixer->sum = NULL; + return 0; +} + +static int get_amixer_rsc(struct amixer_mgr *mgr, + const struct amixer_desc *desc, + struct amixer **ramixer) +{ + int err, i; + unsigned int idx; + struct amixer *amixer; + + *ramixer = NULL; + + /* Allocate mem for amixer resource */ + amixer = kzalloc(sizeof(*amixer), GFP_KERNEL); + if (!amixer) + return -ENOMEM; + + /* Check whether there are sufficient + * amixer resources to meet request. */ + err = 0; + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + for (i = 0; i < desc->msr; i++) { + err = mgr_get_resource(&mgr->mgr, 1, &idx); + if (err) + break; + + amixer->idx[i] = idx; + } + } + if (err) { + dev_err(mgr->card->dev, + "Can't meet AMIXER resource request!\n"); + goto error; + } + + err = amixer_rsc_init(amixer, desc, mgr); + if (err) + goto error; + + *ramixer = amixer; + + return 0; + +error: + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + for (i--; i >= 0; i--) + mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]); + } + + kfree(amixer); + return err; +} + +static int put_amixer_rsc(struct amixer_mgr *mgr, struct amixer *amixer) +{ + int i; + + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + for (i = 0; i < amixer->rsc.msr; i++) + mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]); + } + amixer_rsc_uninit(amixer); + kfree(amixer); + + return 0; +} + +int amixer_mgr_create(struct hw *hw, void **ramixer_mgr) +{ + int err; + struct amixer_mgr *amixer_mgr; + + *ramixer_mgr = NULL; + amixer_mgr = kzalloc_obj(*amixer_mgr); + if (!amixer_mgr) + return -ENOMEM; + + err = rsc_mgr_init(&amixer_mgr->mgr, AMIXER, AMIXER_RESOURCE_NUM, hw); + if (err) + goto error; + + spin_lock_init(&amixer_mgr->mgr_lock); + + amixer_mgr->get_amixer = get_amixer_rsc; + amixer_mgr->put_amixer = put_amixer_rsc; + amixer_mgr->card = hw->card; + + *ramixer_mgr = amixer_mgr; + + return 0; + +error: + kfree(amixer_mgr); + return err; +} + +int amixer_mgr_destroy(void *ptr) +{ + struct amixer_mgr *amixer_mgr = ptr; + rsc_mgr_uninit(&amixer_mgr->mgr); + kfree(amixer_mgr); + return 0; +} + +/* SUM resource management */ + +static void sum_master(struct rsc *rsc) +{ + rsc->conj = 0; + rsc->idx = container_of(rsc, struct sum, rsc)->idx[0]; +} + +static void sum_next_conj(struct rsc *rsc) +{ + rsc->conj++; +} + +static int sum_index(const struct rsc *rsc) +{ + return container_of(rsc, struct sum, rsc)->idx[rsc->conj]; +} + +static int sum_output_slot(const struct rsc *rsc) +{ + return (sum_index(rsc) << 4) + 0xc; +} + +static const struct rsc_ops sum_basic_rsc_ops = { + .master = sum_master, + .next_conj = sum_next_conj, + .index = sum_index, + .output_slot = sum_output_slot, +}; + +static int sum_rsc_init(struct sum *sum, + const struct sum_desc *desc, + struct sum_mgr *mgr) +{ + int err; + + err = rsc_init(&sum->rsc, sum->idx[0], SUM, desc->msr, mgr->mgr.hw); + if (err) + return err; + + sum->rsc.ops = &sum_basic_rsc_ops; + sum->rsc.conj = 0; + + return 0; +} + +static int sum_rsc_uninit(struct sum *sum) +{ + rsc_uninit(&sum->rsc); + return 0; +} + +static int get_sum_rsc(struct sum_mgr *mgr, + const struct sum_desc *desc, + struct sum **rsum) +{ + int err, i; + unsigned int idx; + struct sum *sum; + + *rsum = NULL; + + /* Allocate mem for sum resource */ + sum = kzalloc(sizeof(*sum), GFP_KERNEL); + if (!sum) + return -ENOMEM; + + /* Check whether there are sufficient sum resources to meet request. */ + err = 0; + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + for (i = 0; i < desc->msr; i++) { + err = mgr_get_resource(&mgr->mgr, 1, &idx); + if (err) + break; + + sum->idx[i] = idx; + } + } + if (err) { + dev_err(mgr->card->dev, + "Can't meet SUM resource request!\n"); + goto error; + } + + err = sum_rsc_init(sum, desc, mgr); + if (err) + goto error; + + *rsum = sum; + + return 0; + +error: + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + for (i--; i >= 0; i--) + mgr_put_resource(&mgr->mgr, 1, sum->idx[i]); + } + kfree(sum); + return err; +} + +static int put_sum_rsc(struct sum_mgr *mgr, struct sum *sum) +{ + int i; + + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + for (i = 0; i < sum->rsc.msr; i++) + mgr_put_resource(&mgr->mgr, 1, sum->idx[i]); + } + sum_rsc_uninit(sum); + kfree(sum); + + return 0; +} + +int sum_mgr_create(struct hw *hw, void **rsum_mgr) +{ + int err; + struct sum_mgr *sum_mgr; + + *rsum_mgr = NULL; + sum_mgr = kzalloc_obj(*sum_mgr); + if (!sum_mgr) + return -ENOMEM; + + err = rsc_mgr_init(&sum_mgr->mgr, SUM, SUM_RESOURCE_NUM, hw); + if (err) + goto error; + + spin_lock_init(&sum_mgr->mgr_lock); + + sum_mgr->get_sum = get_sum_rsc; + sum_mgr->put_sum = put_sum_rsc; + sum_mgr->card = hw->card; + + *rsum_mgr = sum_mgr; + + return 0; + +error: + kfree(sum_mgr); + return err; +} + +int sum_mgr_destroy(void *ptr) +{ + struct sum_mgr *sum_mgr = ptr; + rsc_mgr_uninit(&sum_mgr->mgr); + kfree(sum_mgr); + return 0; +} + diff --git a/sound/pci/ctxfi/ctamixer.h b/sound/pci/ctxfi/ctamixer.h new file mode 100644 index 000000000..8fc017da6 --- /dev/null +++ b/sound/pci/ctxfi/ctamixer.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctamixer.h + * + * @Brief + * This file contains the definition of the Audio Mixer + * resource management object. + * + * @Author Liu Chun + * @Date May 21 2008 + */ + +#ifndef CTAMIXER_H +#define CTAMIXER_H + +#include "ctresource.h" +#include +#include + +/* Define the descriptor of a summation node resource */ +struct sum { + struct rsc rsc; /* Basic resource info */ + unsigned char idx[8]; +}; + +/* Define sum resource request description info */ +struct sum_desc { + unsigned int msr; +}; + +struct sum_mgr { + struct rsc_mgr mgr; /* Basic resource manager info */ + struct snd_card *card; /* pointer to this card */ + spinlock_t mgr_lock; + + /* request one sum resource */ + int (*get_sum)(struct sum_mgr *mgr, + const struct sum_desc *desc, struct sum **rsum); + /* return one sum resource */ + int (*put_sum)(struct sum_mgr *mgr, struct sum *sum); +}; + +/* Constructor and destructor of daio resource manager */ +int sum_mgr_create(struct hw *hw, void **ptr); +int sum_mgr_destroy(void *ptr); + +/* Define the descriptor of a amixer resource */ +struct amixer_rsc_ops; + +struct amixer { + struct rsc rsc; /* Basic resource info */ + unsigned char idx[8]; + struct rsc *input; /* pointer to a resource acting as source */ + struct sum *sum; /* Put amixer output to this summation node */ + const struct amixer_rsc_ops *ops; /* AMixer specific operations */ +}; + +struct amixer_rsc_ops { + int (*set_input)(struct amixer *amixer, struct rsc *rsc); + int (*set_scale)(struct amixer *amixer, unsigned int scale); + int (*set_invalid_squash)(struct amixer *amixer, unsigned int iv); + int (*set_sum)(struct amixer *amixer, struct sum *sum); + int (*commit_write)(struct amixer *amixer); + /* Only for interleaved recording */ + int (*commit_raw_write)(struct amixer *amixer); + int (*setup)(struct amixer *amixer, struct rsc *input, + unsigned int scale, struct sum *sum); + int (*get_scale)(struct amixer *amixer); +}; + +/* Define amixer resource request description info */ +struct amixer_desc { + unsigned int msr; +}; + +struct amixer_mgr { + struct rsc_mgr mgr; /* Basic resource manager info */ + struct snd_card *card; /* pointer to this card */ + spinlock_t mgr_lock; + + /* request one amixer resource */ + int (*get_amixer)(struct amixer_mgr *mgr, + const struct amixer_desc *desc, + struct amixer **ramixer); + /* return one amixer resource */ + int (*put_amixer)(struct amixer_mgr *mgr, struct amixer *amixer); +}; + +/* Constructor and destructor of amixer resource manager */ +int amixer_mgr_create(struct hw *hw, void **ramixer_mgr); +int amixer_mgr_destroy(void *amixer_mgr); + +#endif /* CTAMIXER_H */ diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c new file mode 100644 index 000000000..02fe09330 --- /dev/null +++ b/sound/pci/ctxfi/ctatc.c @@ -0,0 +1,1795 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctatc.c + * + * @Brief + * This file contains the implementation of the device resource management + * object. + * + * @Author Liu Chun + * @Date Mar 28 2008 + */ + +#include "ctatc.h" +#include "ctpcm.h" +#include "ctmixer.h" +#include "ctsrc.h" +#include "ctamixer.h" +#include "ctdaio.h" +#include "cttimer.h" +#include +#include +#include +#include +#include + +#define NUM_ATC_SRCS 6 +#define NUM_ATC_PCM (2 * 4) + +#define MONO_SUM_SCALE 0x19a8 /* 2^(-0.5) in 14-bit floating format */ +#define MAX_MULTI_CHN 8 + +#define IEC958_DEFAULT_CON ((IEC958_AES0_NONAUDIO \ + | IEC958_AES0_CON_NOT_COPYRIGHT) \ + | ((IEC958_AES1_CON_MIXER \ + | IEC958_AES1_CON_ORIGINAL) << 8) \ + | (0x10 << 16) \ + | ((IEC958_AES3_CON_FS_48000) << 24)) + +static const struct snd_pci_quirk subsys_20k1_list[] = { + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0021, "SB046x", CTSB046X), + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0022, "SB055x", CTSB055X), + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x002f, "SB055x", CTSB055X), + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0029, "SB073x", CTSB073X), + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0031, "SB073x", CTSB073X), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_CREATIVE, 0xf000, 0x6000, + "UAA", CTUAA), + { } /* terminator */ +}; + +static const struct snd_pci_quirk subsys_20k2_list[] = { + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB0760, + "SB0760", CTSB0760), + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB08801, + "SB0880", CTSB0880), + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB08802, + "SB0880", CTSB0880), + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB08803, + "SB0880", CTSB0880), + SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB1270, + "SB1270", CTSB1270), + SND_PCI_QUIRK(0x160b, 0x0101, "OK0010", CTOK0010), + SND_PCI_QUIRK(0x160b, 0x0102, "OK0010", CTOK0010), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_CREATIVE, 0xf000, + PCI_SUBDEVICE_ID_CREATIVE_HENDRIX, "HENDRIX", + CTHENDRIX), + { } /* terminator */ +}; + +static const char *ct_subsys_name[NUM_CTCARDS] = { + /* 20k1 models */ + [CTSB046X] = "SB046x", + [CTSB055X] = "SB055x", + [CTSB073X] = "SB073x", + [CTUAA] = "UAA", + [CT20K1_UNKNOWN] = "Unknown", + /* 20k2 models */ + [CTSB0760] = "SB076x", + [CTHENDRIX] = "Hendrix", + [CTSB0880] = "SB0880", + [CTSB1270] = "SB1270", + [CTOK0010] = "OK0010", + [CT20K2_UNKNOWN] = "Unknown", +}; + +static struct { + int (*create)(struct ct_atc *atc, + enum CTALSADEVS device, const char *device_name); + int (*destroy)(void *alsa_dev); + const char *public_name; +} alsa_dev_funcs[NUM_CTALSADEVS] = { + [FRONT] = { .create = ct_alsa_pcm_create, + .destroy = NULL, + .public_name = "Front/WaveIn"}, + [SURROUND] = { .create = ct_alsa_pcm_create, + .destroy = NULL, + .public_name = "Surround"}, + [CLFE] = { .create = ct_alsa_pcm_create, + .destroy = NULL, + .public_name = "Center/LFE"}, + [SIDE] = { .create = ct_alsa_pcm_create, + .destroy = NULL, + .public_name = "Side"}, + [IEC958] = { .create = ct_alsa_pcm_create, + .destroy = NULL, + .public_name = "IEC958 Non-audio"}, + + [MIXER] = { .create = ct_alsa_mix_create, + .destroy = NULL, + .public_name = "Mixer"} +}; + +static struct { + int (*create)(struct hw *hw, void **rmgr); + int (*destroy)(void *mgr); +} rsc_mgr_funcs[NUM_RSCTYP] = { + [SRC] = { .create = src_mgr_create, + .destroy = src_mgr_destroy }, + [SRCIMP] = { .create = srcimp_mgr_create, + .destroy = srcimp_mgr_destroy }, + [AMIXER] = { .create = amixer_mgr_create, + .destroy = amixer_mgr_destroy }, + [SUM] = { .create = sum_mgr_create, + .destroy = sum_mgr_destroy }, + [DAIO] = { .create = daio_mgr_create, + .destroy = daio_mgr_destroy } +}; + +static int +atc_pcm_release_resources(struct ct_atc *atc, struct ct_atc_pcm *apcm); + +/* * + * Only mono and interleaved modes are supported now. + * Always allocates a contiguous channel block. + * */ + +static int ct_map_audio_buffer(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct snd_pcm_runtime *runtime; + struct ct_vm *vm; + + if (!apcm->substream) + return 0; + + runtime = apcm->substream->runtime; + vm = atc->vm; + + apcm->vm_block = vm->map(vm, apcm->substream, runtime->dma_bytes); + + if (!apcm->vm_block) + return -ENOENT; + + return 0; +} + +static void ct_unmap_audio_buffer(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct ct_vm *vm; + + if (!apcm->vm_block) + return; + + vm = atc->vm; + + vm->unmap(vm, apcm->vm_block); + + apcm->vm_block = NULL; +} + +static unsigned long atc_get_ptp_phys(struct ct_atc *atc, int index) +{ + return atc->vm->get_ptp_phys(atc->vm, index); +} + +static unsigned int convert_format(snd_pcm_format_t snd_format, + struct snd_card *card) +{ + switch (snd_format) { + case SNDRV_PCM_FORMAT_U8: + return SRC_SF_U8; + case SNDRV_PCM_FORMAT_S16_LE: + return SRC_SF_S16; + case SNDRV_PCM_FORMAT_S24_3LE: + return SRC_SF_S24; + case SNDRV_PCM_FORMAT_S32_LE: + return SRC_SF_S32; + case SNDRV_PCM_FORMAT_FLOAT_LE: + return SRC_SF_F32; + default: + dev_err(card->dev, "not recognized snd format is %d\n", + snd_format); + return SRC_SF_S16; + } +} + +static unsigned int +atc_get_pitch(unsigned int input_rate, unsigned int output_rate) +{ + unsigned int pitch; + int b; + + /* get pitch and convert to fixed-point 8.24 format. */ + pitch = (input_rate / output_rate) << 24; + input_rate %= output_rate; + input_rate /= 100; + output_rate /= 100; + for (b = 31; ((b >= 0) && !(input_rate >> b)); ) + b--; + + if (b >= 0) { + input_rate <<= (31 - b); + input_rate /= output_rate; + b = 24 - (31 - b); + if (b >= 0) + input_rate <<= b; + else + input_rate >>= -b; + + pitch |= input_rate; + } + + return pitch; +} + +static int select_rom(unsigned int pitch) +{ + if (pitch > 0x00428f5c && pitch < 0x01b851ec) { + /* 0.26 <= pitch <= 1.72 */ + return 1; + } else if (pitch == 0x01d66666 || pitch == 0x01d66667) { + /* pitch == 1.8375 */ + return 2; + } else if (pitch == 0x02000000) { + /* pitch == 2 */ + return 3; + } else if (pitch <= 0x08000000) { + /* 0 <= pitch <= 8 */ + return 0; + } else { + return -ENOENT; + } +} + +static int atc_pcm_playback_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct src_mgr *src_mgr = atc->rsc_mgrs[SRC]; + struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER]; + struct src_desc desc = {0}; + struct amixer_desc mix_dsc = {0}; + struct src *src; + struct amixer *amixer; + int err; + int n_amixer = apcm->substream->runtime->channels, i = 0; + int device = apcm->substream->pcm->device; + unsigned int pitch; + + /* first release old resources */ + atc_pcm_release_resources(atc, apcm); + + /* Get SRC resource */ + desc.multi = apcm->substream->runtime->channels; + desc.msr = atc->msr; + desc.mode = MEMRD; + err = src_mgr->get_src(src_mgr, &desc, (struct src **)&apcm->src); + if (err) + goto error1; + + pitch = atc_get_pitch(apcm->substream->runtime->rate, + (atc->rsr * atc->msr)); + src = apcm->src; + src->ops->set_pitch(src, pitch); + src->ops->set_rom(src, select_rom(pitch)); + src->ops->set_sf(src, convert_format(apcm->substream->runtime->format, + atc->card)); + src->ops->set_pm(src, (src->ops->next_interleave(src) != NULL)); + + /* Get AMIXER resource */ + n_amixer = (n_amixer < 2) ? 2 : n_amixer; + apcm->amixers = kcalloc(n_amixer, sizeof(void *), GFP_KERNEL); + if (!apcm->amixers) { + err = -ENOMEM; + goto error1; + } + mix_dsc.msr = atc->msr; + for (i = 0, apcm->n_amixer = 0; i < n_amixer; i++) { + err = amixer_mgr->get_amixer(amixer_mgr, &mix_dsc, + (struct amixer **)&apcm->amixers[i]); + if (err) + goto error1; + + apcm->n_amixer++; + } + + /* Set up device virtual mem map */ + err = ct_map_audio_buffer(atc, apcm); + if (err < 0) + goto error1; + + /* Connect resources */ + src = apcm->src; + for (i = 0; i < n_amixer; i++) { + amixer = apcm->amixers[i]; + scoped_guard(mutex, &atc->atc_mutex) { + amixer->ops->setup(amixer, &src->rsc, + INIT_VOL, atc->pcm[i+device*2]); + } + src = src->ops->next_interleave(src); + if (!src) + src = apcm->src; + } + + ct_timer_prepare(apcm->timer); + + return 0; + +error1: + atc_pcm_release_resources(atc, apcm); + return err; +} + +static int +atc_pcm_release_resources(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct src_mgr *src_mgr = atc->rsc_mgrs[SRC]; + struct srcimp_mgr *srcimp_mgr = atc->rsc_mgrs[SRCIMP]; + struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER]; + struct sum_mgr *sum_mgr = atc->rsc_mgrs[SUM]; + struct srcimp *srcimp; + int i; + + if (apcm->srcimps) { + for (i = 0; i < apcm->n_srcimp; i++) { + srcimp = apcm->srcimps[i]; + srcimp->ops->unmap(srcimp); + srcimp_mgr->put_srcimp(srcimp_mgr, srcimp); + apcm->srcimps[i] = NULL; + } + kfree(apcm->srcimps); + apcm->srcimps = NULL; + } + + if (apcm->srccs) { + for (i = 0; i < apcm->n_srcc; i++) { + src_mgr->put_src(src_mgr, apcm->srccs[i]); + apcm->srccs[i] = NULL; + } + kfree(apcm->srccs); + apcm->srccs = NULL; + } + + if (apcm->amixers) { + for (i = 0; i < apcm->n_amixer; i++) { + amixer_mgr->put_amixer(amixer_mgr, apcm->amixers[i]); + apcm->amixers[i] = NULL; + } + kfree(apcm->amixers); + apcm->amixers = NULL; + } + + if (apcm->mono) { + sum_mgr->put_sum(sum_mgr, apcm->mono); + apcm->mono = NULL; + } + + if (apcm->src) { + src_mgr->put_src(src_mgr, apcm->src); + apcm->src = NULL; + } + + if (apcm->vm_block) { + /* Undo device virtual mem map */ + ct_unmap_audio_buffer(atc, apcm); + apcm->vm_block = NULL; + } + + return 0; +} + +static int atc_pcm_playback_start(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + unsigned int max_cisz; + struct src *src = apcm->src; + + if (apcm->started) + return 0; + apcm->started = 1; + + max_cisz = src->multi * src->rsc.msr; + max_cisz = 0x80 * (max_cisz < 8 ? max_cisz : 8); + + src->ops->set_sa(src, apcm->vm_block->addr); + src->ops->set_la(src, apcm->vm_block->addr + apcm->vm_block->size); + src->ops->set_ca(src, apcm->vm_block->addr + max_cisz); + src->ops->set_cisz(src, max_cisz); + + src->ops->set_bm(src, 1); + src->ops->set_state(src, SRC_STATE_INIT); + src->ops->commit_write(src); + + ct_timer_start(apcm->timer); + return 0; +} + +static int atc_pcm_stop(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct src *src; + int i; + + ct_timer_stop(apcm->timer); + + src = apcm->src; + src->ops->set_bm(src, 0); + src->ops->set_state(src, SRC_STATE_OFF); + src->ops->commit_write(src); + + if (apcm->srccs) { + for (i = 0; i < apcm->n_srcc; i++) { + src = apcm->srccs[i]; + src->ops->set_bm(src, 0); + src->ops->set_state(src, SRC_STATE_OFF); + src->ops->commit_write(src); + } + } + + apcm->started = 0; + + return 0; +} + +static int +atc_pcm_playback_position(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct src *src = apcm->src; + u32 size, max_cisz; + int position; + + if (!src) + return 0; + position = src->ops->get_ca(src); + + if (position < apcm->vm_block->addr) { + dev_dbg(atc->card->dev, + "bad ca - ca=0x%08x, vba=0x%08x, vbs=0x%08x\n", + position, apcm->vm_block->addr, apcm->vm_block->size); + position = apcm->vm_block->addr; + } + + size = apcm->vm_block->size; + max_cisz = src->multi * src->rsc.msr; + max_cisz = 128 * (max_cisz < 8 ? max_cisz : 8); + + return (position + size - max_cisz - apcm->vm_block->addr) % size; +} + +struct src_node_conf_t { + unsigned int pitch; + unsigned int msr:8; + unsigned int mix_msr:8; + unsigned int imp_msr:8; + unsigned int vo:1; +}; + +static void setup_src_node_conf(struct ct_atc *atc, struct ct_atc_pcm *apcm, + struct src_node_conf_t *conf, int *n_srcc) +{ + unsigned int pitch; + + /* get pitch and convert to fixed-point 8.24 format. */ + pitch = atc_get_pitch((atc->rsr * atc->msr), + apcm->substream->runtime->rate); + *n_srcc = 0; + + if (1 == atc->msr) { /* FIXME: do we really need SRC here if pitch==1 */ + *n_srcc = apcm->substream->runtime->channels; + conf[0].pitch = pitch; + conf[0].mix_msr = conf[0].imp_msr = conf[0].msr = 1; + conf[0].vo = 1; + } else if (2 <= atc->msr) { + if (0x8000000 < pitch) { + /* Need two-stage SRCs, SRCIMPs and + * AMIXERs for converting format */ + conf[0].pitch = (atc->msr << 24); + conf[0].msr = conf[0].mix_msr = 1; + conf[0].imp_msr = atc->msr; + conf[0].vo = 0; + conf[1].pitch = atc_get_pitch(atc->rsr, + apcm->substream->runtime->rate); + conf[1].msr = conf[1].mix_msr = conf[1].imp_msr = 1; + conf[1].vo = 1; + *n_srcc = apcm->substream->runtime->channels * 2; + } else if (0x1000000 < pitch) { + /* Need one-stage SRCs, SRCIMPs and + * AMIXERs for converting format */ + conf[0].pitch = pitch; + conf[0].msr = conf[0].mix_msr + = conf[0].imp_msr = atc->msr; + conf[0].vo = 1; + *n_srcc = apcm->substream->runtime->channels; + } + } +} + +static int +atc_pcm_capture_get_resources(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct src_mgr *src_mgr = atc->rsc_mgrs[SRC]; + struct srcimp_mgr *srcimp_mgr = atc->rsc_mgrs[SRCIMP]; + struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER]; + struct sum_mgr *sum_mgr = atc->rsc_mgrs[SUM]; + struct src_desc src_dsc = {0}; + struct src *src; + struct srcimp_desc srcimp_dsc = {0}; + struct srcimp *srcimp; + struct amixer_desc mix_dsc = {0}; + struct sum_desc sum_dsc = {0}; + unsigned int pitch; + int multi, err, i; + int n_srcimp, n_amixer, n_srcc, n_sum; + struct src_node_conf_t src_node_conf[2] = {{0} }; + + /* first release old resources */ + atc_pcm_release_resources(atc, apcm); + + /* The numbers of converting SRCs and SRCIMPs should be determined + * by pitch value. */ + + multi = apcm->substream->runtime->channels; + + /* get pitch and convert to fixed-point 8.24 format. */ + pitch = atc_get_pitch((atc->rsr * atc->msr), + apcm->substream->runtime->rate); + + setup_src_node_conf(atc, apcm, src_node_conf, &n_srcc); + n_sum = (1 == multi) ? 1 : 0; + n_amixer = n_sum * 2 + n_srcc; + n_srcimp = n_srcc; + if ((multi > 1) && (0x8000000 >= pitch)) { + /* Need extra AMIXERs and SRCIMPs for special treatment + * of interleaved recording of conjugate channels */ + n_amixer += multi * atc->msr; + n_srcimp += multi * atc->msr; + } else { + n_srcimp += multi; + } + + if (n_srcc) { + apcm->srccs = kcalloc(n_srcc, sizeof(void *), GFP_KERNEL); + if (!apcm->srccs) + return -ENOMEM; + } + if (n_amixer) { + apcm->amixers = kcalloc(n_amixer, sizeof(void *), GFP_KERNEL); + if (!apcm->amixers) { + err = -ENOMEM; + goto error1; + } + } + apcm->srcimps = kcalloc(n_srcimp, sizeof(void *), GFP_KERNEL); + if (!apcm->srcimps) { + err = -ENOMEM; + goto error1; + } + + /* Allocate SRCs for sample rate conversion if needed */ + src_dsc.multi = 1; + src_dsc.mode = ARCRW; + for (i = 0, apcm->n_srcc = 0; i < n_srcc; i++) { + src_dsc.msr = src_node_conf[i/multi].msr; + err = src_mgr->get_src(src_mgr, &src_dsc, + (struct src **)&apcm->srccs[i]); + if (err) + goto error1; + + src = apcm->srccs[i]; + pitch = src_node_conf[i/multi].pitch; + src->ops->set_pitch(src, pitch); + src->ops->set_rom(src, select_rom(pitch)); + src->ops->set_vo(src, src_node_conf[i/multi].vo); + + apcm->n_srcc++; + } + + /* Allocate AMIXERs for routing SRCs of conversion if needed */ + for (i = 0, apcm->n_amixer = 0; i < n_amixer; i++) { + if (i < (n_sum*2)) + mix_dsc.msr = atc->msr; + else if (i < (n_sum*2+n_srcc)) + mix_dsc.msr = src_node_conf[(i-n_sum*2)/multi].mix_msr; + else + mix_dsc.msr = 1; + + err = amixer_mgr->get_amixer(amixer_mgr, &mix_dsc, + (struct amixer **)&apcm->amixers[i]); + if (err) + goto error1; + + apcm->n_amixer++; + } + + /* Allocate a SUM resource to mix all input channels together */ + sum_dsc.msr = atc->msr; + err = sum_mgr->get_sum(sum_mgr, &sum_dsc, (struct sum **)&apcm->mono); + if (err) + goto error1; + + pitch = atc_get_pitch((atc->rsr * atc->msr), + apcm->substream->runtime->rate); + /* Allocate SRCIMP resources */ + for (i = 0, apcm->n_srcimp = 0; i < n_srcimp; i++) { + if (i < (n_srcc)) + srcimp_dsc.msr = src_node_conf[i/multi].imp_msr; + else if (1 == multi) + srcimp_dsc.msr = (pitch <= 0x8000000) ? atc->msr : 1; + else + srcimp_dsc.msr = 1; + + err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc, &srcimp); + if (err) + goto error1; + + apcm->srcimps[i] = srcimp; + apcm->n_srcimp++; + } + + /* Allocate a SRC for writing data to host memory */ + src_dsc.multi = apcm->substream->runtime->channels; + src_dsc.msr = 1; + src_dsc.mode = MEMWR; + err = src_mgr->get_src(src_mgr, &src_dsc, (struct src **)&apcm->src); + if (err) + goto error1; + + src = apcm->src; + src->ops->set_pitch(src, pitch); + + /* Set up device virtual mem map */ + err = ct_map_audio_buffer(atc, apcm); + if (err < 0) + goto error1; + + return 0; + +error1: + atc_pcm_release_resources(atc, apcm); + return err; +} + +static int atc_pcm_capture_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct src *src; + struct amixer *amixer; + struct srcimp *srcimp; + struct ct_mixer *mixer = atc->mixer; + struct sum *mono; + struct rsc *out_ports[8] = {NULL}; + int err, i, j, n_sum, multi; + unsigned int pitch; + int mix_base = 0, imp_base = 0; + + atc_pcm_release_resources(atc, apcm); + + /* Get needed resources. */ + err = atc_pcm_capture_get_resources(atc, apcm); + if (err) + return err; + + /* Connect resources */ + mixer->get_output_ports(mixer, MIX_PCMO_FRONT, + &out_ports[0], &out_ports[1]); + + multi = apcm->substream->runtime->channels; + if (1 == multi) { + mono = apcm->mono; + for (i = 0; i < 2; i++) { + amixer = apcm->amixers[i]; + amixer->ops->setup(amixer, out_ports[i], + MONO_SUM_SCALE, mono); + } + out_ports[0] = &mono->rsc; + n_sum = 1; + mix_base = n_sum * 2; + } + + for (i = 0; i < apcm->n_srcc; i++) { + src = apcm->srccs[i]; + srcimp = apcm->srcimps[imp_base+i]; + amixer = apcm->amixers[mix_base+i]; + srcimp->ops->map(srcimp, src, out_ports[i%multi]); + amixer->ops->setup(amixer, &src->rsc, INIT_VOL, NULL); + out_ports[i%multi] = &amixer->rsc; + } + + pitch = atc_get_pitch((atc->rsr * atc->msr), + apcm->substream->runtime->rate); + + if ((multi > 1) && (pitch <= 0x8000000)) { + /* Special connection for interleaved + * recording with conjugate channels */ + for (i = 0; i < multi; i++) { + out_ports[i]->ops->master(out_ports[i]); + for (j = 0; j < atc->msr; j++) { + amixer = apcm->amixers[apcm->n_srcc+j*multi+i]; + amixer->ops->set_input(amixer, out_ports[i]); + amixer->ops->set_scale(amixer, INIT_VOL); + amixer->ops->set_sum(amixer, NULL); + amixer->ops->commit_raw_write(amixer); + out_ports[i]->ops->next_conj(out_ports[i]); + + srcimp = apcm->srcimps[apcm->n_srcc+j*multi+i]; + srcimp->ops->map(srcimp, apcm->src, + &amixer->rsc); + } + } + } else { + for (i = 0; i < multi; i++) { + srcimp = apcm->srcimps[apcm->n_srcc+i]; + srcimp->ops->map(srcimp, apcm->src, out_ports[i]); + } + } + + ct_timer_prepare(apcm->timer); + + return 0; +} + +static int atc_pcm_capture_start(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct src *src; + struct src_mgr *src_mgr = atc->rsc_mgrs[SRC]; + int i, multi; + + if (apcm->started) + return 0; + + apcm->started = 1; + multi = apcm->substream->runtime->channels; + /* Set up converting SRCs */ + for (i = 0; i < apcm->n_srcc; i++) { + src = apcm->srccs[i]; + src->ops->set_pm(src, ((i%multi) != (multi-1))); + src_mgr->src_disable(src_mgr, src); + } + + /* Set up recording SRC */ + src = apcm->src; + src->ops->set_sf(src, convert_format(apcm->substream->runtime->format, + atc->card)); + src->ops->set_sa(src, apcm->vm_block->addr); + src->ops->set_la(src, apcm->vm_block->addr + apcm->vm_block->size); + src->ops->set_ca(src, apcm->vm_block->addr); + src_mgr->src_disable(src_mgr, src); + + /* Disable relevant SRCs firstly */ + src_mgr->commit_write(src_mgr); + + /* Enable SRCs respectively */ + for (i = 0; i < apcm->n_srcc; i++) { + src = apcm->srccs[i]; + src->ops->set_state(src, SRC_STATE_RUN); + src->ops->commit_write(src); + src_mgr->src_enable_s(src_mgr, src); + } + src = apcm->src; + src->ops->set_bm(src, 1); + src->ops->set_state(src, SRC_STATE_RUN); + src->ops->commit_write(src); + src_mgr->src_enable_s(src_mgr, src); + + /* Enable relevant SRCs synchronously */ + src_mgr->commit_write(src_mgr); + + ct_timer_start(apcm->timer); + return 0; +} + +static int +atc_pcm_capture_position(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct src *src = apcm->src; + + if (!src) + return 0; + return src->ops->get_ca(src) - apcm->vm_block->addr; +} + +static int spdif_passthru_playback_get_resources(struct ct_atc *atc, + struct ct_atc_pcm *apcm) +{ + struct src_mgr *src_mgr = atc->rsc_mgrs[SRC]; + struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER]; + struct src_desc desc = {0}; + struct amixer_desc mix_dsc = {0}; + struct src *src; + int err; + int n_amixer = apcm->substream->runtime->channels, i; + unsigned int pitch; + unsigned int rsr = atc->pll_rate ? atc->pll_rate : atc->rsr; + + /* first release old resources */ + atc_pcm_release_resources(atc, apcm); + + /* Get SRC resource */ + desc.multi = apcm->substream->runtime->channels; + desc.msr = 1; + while (apcm->substream->runtime->rate > (rsr * desc.msr)) + desc.msr <<= 1; + + desc.mode = MEMRD; + err = src_mgr->get_src(src_mgr, &desc, (struct src **)&apcm->src); + if (err) + goto error1; + + pitch = atc_get_pitch(apcm->substream->runtime->rate, (rsr * desc.msr)); + src = apcm->src; + src->ops->set_pitch(src, pitch); + src->ops->set_rom(src, select_rom(pitch)); + src->ops->set_sf(src, convert_format(apcm->substream->runtime->format, + atc->card)); + src->ops->set_pm(src, (src->ops->next_interleave(src) != NULL)); + src->ops->set_bp(src, 1); + + /* Get AMIXER resource */ + n_amixer = (n_amixer < 2) ? 2 : n_amixer; + apcm->amixers = kcalloc(n_amixer, sizeof(void *), GFP_KERNEL); + if (!apcm->amixers) { + err = -ENOMEM; + goto error1; + } + mix_dsc.msr = desc.msr; + for (i = 0, apcm->n_amixer = 0; i < n_amixer; i++) { + err = amixer_mgr->get_amixer(amixer_mgr, &mix_dsc, + (struct amixer **)&apcm->amixers[i]); + if (err) + goto error1; + + apcm->n_amixer++; + } + + /* Set up device virtual mem map */ + err = ct_map_audio_buffer(atc, apcm); + if (err < 0) + goto error1; + + return 0; + +error1: + atc_pcm_release_resources(atc, apcm); + return err; +} + +static int atc_pll_init(struct ct_atc *atc, int rate) +{ + struct hw *hw = atc->hw; + int err; + err = hw->pll_init(hw, rate); + atc->pll_rate = err ? 0 : rate; + return err; +} + +static int +spdif_passthru_playback_setup(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct dao *dao = container_of(atc->daios[SPDIFOO], struct dao, daio); + unsigned int rate = apcm->substream->runtime->rate; + unsigned int status; + int err = 0; + unsigned char iec958_con_fs; + + switch (rate) { + case 48000: + iec958_con_fs = IEC958_AES3_CON_FS_48000; + break; + case 44100: + iec958_con_fs = IEC958_AES3_CON_FS_44100; + break; + case 32000: + iec958_con_fs = IEC958_AES3_CON_FS_32000; + break; + default: + return -ENOENT; + } + + guard(mutex)(&atc->atc_mutex); + dao->ops->get_spos(dao, &status); + if (((status >> 24) & IEC958_AES3_CON_FS) != iec958_con_fs) { + status &= ~(IEC958_AES3_CON_FS << 24); + status |= (iec958_con_fs << 24); + dao->ops->set_spos(dao, status); + dao->ops->commit_write(dao); + } + if ((rate != atc->pll_rate) && (32000 != rate)) + err = atc_pll_init(atc, rate); + + return err; +} + +static int +spdif_passthru_playback_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm) +{ + struct src *src; + struct amixer *amixer; + struct dao *dao; + int err; + int i; + + atc_pcm_release_resources(atc, apcm); + + /* Configure SPDIFOO and PLL to passthrough mode; + * determine pll_rate. */ + err = spdif_passthru_playback_setup(atc, apcm); + if (err) + return err; + + /* Get needed resources. */ + err = spdif_passthru_playback_get_resources(atc, apcm); + if (err) + return err; + + /* Connect resources */ + src = apcm->src; + for (i = 0; i < apcm->n_amixer; i++) { + amixer = apcm->amixers[i]; + amixer->ops->setup(amixer, &src->rsc, INIT_VOL, NULL); + src = src->ops->next_interleave(src); + if (!src) + src = apcm->src; + } + /* Connect to SPDIFOO */ + scoped_guard(mutex, &atc->atc_mutex) { + dao = container_of(atc->daios[SPDIFOO], struct dao, daio); + amixer = apcm->amixers[0]; + dao->ops->set_left_input(dao, &amixer->rsc); + amixer = apcm->amixers[1]; + dao->ops->set_right_input(dao, &amixer->rsc); + } + + ct_timer_prepare(apcm->timer); + + return 0; +} + +static int atc_select_line_in(struct ct_atc *atc) +{ + struct hw *hw = atc->hw; + struct ct_mixer *mixer = atc->mixer; + struct src *src; + + if (hw->is_adc_source_selected(hw, ADC_LINEIN)) + return 0; + + mixer->set_input_left(mixer, MIX_MIC_IN, NULL); + mixer->set_input_right(mixer, MIX_MIC_IN, NULL); + + hw->select_adc_source(hw, ADC_LINEIN); + + src = atc->srcs[2]; + mixer->set_input_left(mixer, MIX_LINE_IN, &src->rsc); + src = atc->srcs[3]; + mixer->set_input_right(mixer, MIX_LINE_IN, &src->rsc); + + return 0; +} + +static int atc_select_mic_in(struct ct_atc *atc) +{ + struct hw *hw = atc->hw; + struct ct_mixer *mixer = atc->mixer; + struct src *src; + + if (hw->is_adc_source_selected(hw, ADC_MICIN)) + return 0; + + mixer->set_input_left(mixer, MIX_LINE_IN, NULL); + mixer->set_input_right(mixer, MIX_LINE_IN, NULL); + + hw->select_adc_source(hw, ADC_MICIN); + + src = atc->srcs[2]; + mixer->set_input_left(mixer, MIX_MIC_IN, &src->rsc); + src = atc->srcs[3]; + mixer->set_input_right(mixer, MIX_MIC_IN, &src->rsc); + + return 0; +} + +static inline enum DAIOTYP atc_spdif_in_type(struct ct_atc *atc) +{ + return (atc->model == CTSB073X) ? SPDIFI_BAY : SPDIFIO; +} + +static struct capabilities atc_capabilities(struct ct_atc *atc) +{ + struct hw *hw = atc->hw; + + return hw->capabilities(hw); +} + +static void atc_dedicated_rca_select(struct ct_atc *atc) +{ + struct dao *dao; + struct ct_mixer *mixer = atc->mixer; + struct rsc *rscs[2] = {NULL}; + + dao = container_of(atc->daios[atc->rca_state ? RCA : LINEO1], + struct dao, daio); + dao->ops->clear_left_input(dao); + dao->ops->clear_right_input(dao); + + mixer->get_output_ports(mixer, MIX_WAVE_FRONT, &rscs[0], &rscs[1]); + dao = container_of(atc->daios[atc->rca_state ? LINEO1 : RCA], + struct dao, daio); + dao->ops->set_left_input(dao, rscs[0]); + dao->ops->set_right_input(dao, rscs[1]); +} + +static int atc_output_switch_get(struct ct_atc *atc) +{ + struct hw *hw = atc->hw; + + return hw->output_switch_get(hw); +} + +static int atc_output_switch_put(struct ct_atc *atc, int position) +{ + struct hw *hw = atc->hw; + + return hw->output_switch_put(hw, position); +} + +static int atc_mic_source_switch_get(struct ct_atc *atc) +{ + struct hw *hw = atc->hw; + + return hw->mic_source_switch_get(hw); +} + +static int atc_mic_source_switch_put(struct ct_atc *atc, int position) +{ + struct hw *hw = atc->hw; + + return hw->mic_source_switch_put(hw, position); +} + +static int atc_select_digit_io(struct ct_atc *atc) +{ + struct hw *hw = atc->hw; + + if (hw->is_adc_source_selected(hw, ADC_NONE)) + return 0; + + hw->select_adc_source(hw, ADC_NONE); + + return 0; +} + +static int atc_daio_unmute(struct ct_atc *atc, unsigned char state, int type) +{ + struct daio_mgr *daio_mgr = atc->rsc_mgrs[DAIO]; + + if (state) + daio_mgr->daio_enable(daio_mgr, atc->daios[type]); + else + daio_mgr->daio_disable(daio_mgr, atc->daios[type]); + + daio_mgr->commit_write(daio_mgr); + + return 0; +} + +static int +atc_dao_get_status(struct ct_atc *atc, unsigned int *status, int type) +{ + struct dao *dao = container_of(atc->daios[type], struct dao, daio); + return dao->ops->get_spos(dao, status); +} + +static int +atc_dao_set_status(struct ct_atc *atc, unsigned int status, int type) +{ + struct dao *dao = container_of(atc->daios[type], struct dao, daio); + + dao->ops->set_spos(dao, status); + dao->ops->commit_write(dao); + return 0; +} + +static int atc_line_front_unmute(struct ct_atc *atc, unsigned char state) +{ + return atc_daio_unmute(atc, state, LINEO1); +} + +static int atc_line_surround_unmute(struct ct_atc *atc, unsigned char state) +{ + return atc_daio_unmute(atc, state, LINEO2); +} + +static int atc_line_clfe_unmute(struct ct_atc *atc, unsigned char state) +{ + return atc_daio_unmute(atc, state, LINEO3); +} + +static int atc_line_rear_unmute(struct ct_atc *atc, unsigned char state) +{ + return atc_daio_unmute(atc, state, LINEO4); +} + +static int atc_line_in_unmute(struct ct_atc *atc, unsigned char state) +{ + return atc_daio_unmute(atc, state, LINEIM); +} + +static int atc_mic_unmute(struct ct_atc *atc, unsigned char state) +{ + return atc_daio_unmute(atc, state, MIC); +} + +static int atc_rca_unmute(struct ct_atc *atc, unsigned char state) +{ + return atc_daio_unmute(atc, state, RCA); +} + +static int atc_spdif_out_unmute(struct ct_atc *atc, unsigned char state) +{ + return atc_daio_unmute(atc, state, SPDIFOO); +} + +static int atc_spdif_in_unmute(struct ct_atc *atc, unsigned char state) +{ + return atc_daio_unmute(atc, state, atc_spdif_in_type(atc)); +} + +static int atc_spdif_out_get_status(struct ct_atc *atc, unsigned int *status) +{ + return atc_dao_get_status(atc, status, SPDIFOO); +} + +static int atc_spdif_out_set_status(struct ct_atc *atc, unsigned int status) +{ + return atc_dao_set_status(atc, status, SPDIFOO); +} + +static int atc_spdif_out_passthru(struct ct_atc *atc, unsigned char state) +{ + struct dao_desc da_dsc = {0}; + struct dao *dao; + int err; + struct ct_mixer *mixer = atc->mixer; + struct rsc *rscs[2] = {NULL}; + unsigned int spos = 0; + + guard(mutex)(&atc->atc_mutex); + dao = container_of(atc->daios[SPDIFOO], struct dao, daio); + da_dsc.msr = state ? 1 : atc->msr; + da_dsc.passthru = state ? 1 : 0; + err = dao->ops->reinit(dao, &da_dsc); + if (state) { + spos = IEC958_DEFAULT_CON; + } else { + mixer->get_output_ports(mixer, MIX_SPDIF_OUT, + &rscs[0], &rscs[1]); + dao->ops->set_left_input(dao, rscs[0]); + dao->ops->set_right_input(dao, rscs[1]); + /* Restore PLL to atc->rsr if needed. */ + if (atc->pll_rate != atc->rsr) + err = atc_pll_init(atc, atc->rsr); + } + dao->ops->set_spos(dao, spos); + dao->ops->commit_write(dao); + + return err; +} + +static int atc_release_resources(struct ct_atc *atc) +{ + int i; + struct daio_mgr *daio_mgr = NULL; + struct dao *dao = NULL; + struct daio *daio = NULL; + struct sum_mgr *sum_mgr = NULL; + struct src_mgr *src_mgr = NULL; + struct srcimp_mgr *srcimp_mgr = NULL; + struct srcimp *srcimp = NULL; + struct ct_mixer *mixer = NULL; + + /* disconnect internal mixer objects */ + if (atc->mixer) { + mixer = atc->mixer; + mixer->set_input_left(mixer, MIX_LINE_IN, NULL); + mixer->set_input_right(mixer, MIX_LINE_IN, NULL); + mixer->set_input_left(mixer, MIX_MIC_IN, NULL); + mixer->set_input_right(mixer, MIX_MIC_IN, NULL); + mixer->set_input_left(mixer, MIX_SPDIF_IN, NULL); + mixer->set_input_right(mixer, MIX_SPDIF_IN, NULL); + } + + if (atc->daios) { + daio_mgr = (struct daio_mgr *)atc->rsc_mgrs[DAIO]; + for (i = 0; i < NUM_DAIOTYP; i++) { + daio = atc->daios[i]; + if (!daio) + continue; + if (daio->output) { + dao = container_of(daio, struct dao, daio); + dao->ops->clear_left_input(dao); + dao->ops->clear_right_input(dao); + } + daio_mgr->put_daio(daio_mgr, daio); + } + kfree(atc->daios); + atc->daios = NULL; + } + + if (atc->pcm) { + sum_mgr = atc->rsc_mgrs[SUM]; + for (i = 0; i < NUM_ATC_PCM; i++) + if (atc->pcm[i]) + sum_mgr->put_sum(sum_mgr, atc->pcm[i]); + + kfree(atc->pcm); + atc->pcm = NULL; + } + + if (atc->srcs) { + src_mgr = atc->rsc_mgrs[SRC]; + for (i = 0; i < NUM_ATC_SRCS; i++) + if (atc->srcs[i]) + src_mgr->put_src(src_mgr, atc->srcs[i]); + + kfree(atc->srcs); + atc->srcs = NULL; + } + + if (atc->srcimps) { + srcimp_mgr = atc->rsc_mgrs[SRCIMP]; + for (i = 0; i < NUM_ATC_SRCS; i++) { + if (!atc->srcimps[i]) + continue; + srcimp = atc->srcimps[i]; + srcimp->ops->unmap(srcimp); + srcimp_mgr->put_srcimp(srcimp_mgr, atc->srcimps[i]); + } + kfree(atc->srcimps); + atc->srcimps = NULL; + } + + return 0; +} + +static int ct_atc_destroy(struct ct_atc *atc) +{ + int i = 0; + + if (!atc) + return 0; + + if (atc->timer) { + ct_timer_free(atc->timer); + atc->timer = NULL; + } + + atc_release_resources(atc); + + /* Destroy internal mixer objects */ + if (atc->mixer) + ct_mixer_destroy(atc->mixer); + + for (i = 0; i < NUM_RSCTYP; i++) { + if (rsc_mgr_funcs[i].destroy && atc->rsc_mgrs[i]) + rsc_mgr_funcs[i].destroy(atc->rsc_mgrs[i]); + + } + + if (atc->hw) + destroy_hw_obj(atc->hw); + + /* Destroy device virtual memory manager object */ + if (atc->vm) { + ct_vm_destroy(atc->vm); + atc->vm = NULL; + } + + kfree(atc); + + return 0; +} + +static int atc_dev_free(struct snd_device *dev) +{ + struct ct_atc *atc = dev->device_data; + return ct_atc_destroy(atc); +} + +static int atc_identify_card(struct ct_atc *atc, unsigned int ssid) +{ + const struct snd_pci_quirk *p; + const struct snd_pci_quirk *list; + u16 vendor_id, device_id; + + switch (atc->chip_type) { + case ATC20K1: + atc->chip_name = "20K1"; + list = subsys_20k1_list; + break; + case ATC20K2: + atc->chip_name = "20K2"; + list = subsys_20k2_list; + break; + default: + return -ENOENT; + } + if (ssid) { + vendor_id = ssid >> 16; + device_id = ssid & 0xffff; + } else { + vendor_id = atc->pci->subsystem_vendor; + device_id = atc->pci->subsystem_device; + } + p = snd_pci_quirk_lookup_id(vendor_id, device_id, list); + if (p) { + if (p->value < 0) { + dev_err(atc->card->dev, + "Device %04x:%04x is on the denylist\n", + vendor_id, device_id); + return -ENOENT; + } + atc->model = p->value; + } else { + if (atc->chip_type == ATC20K1) + atc->model = CT20K1_UNKNOWN; + else + atc->model = CT20K2_UNKNOWN; + } + atc->model_name = ct_subsys_name[atc->model]; + dev_info(atc->card->dev, "chip %s model %s (%04x:%04x) is found\n", + atc->chip_name, atc->model_name, + vendor_id, device_id); + atc->rca_state = 0; + return 0; +} + +int ct_atc_create_alsa_devs(struct ct_atc *atc) +{ + enum CTALSADEVS i; + int err; + + alsa_dev_funcs[MIXER].public_name = atc->chip_name; + + for (i = 0; i < NUM_CTALSADEVS; i++) { + if (!alsa_dev_funcs[i].create) + continue; + + err = alsa_dev_funcs[i].create(atc, i, + alsa_dev_funcs[i].public_name); + if (err) { + dev_err(atc->card->dev, + "Creating alsa device %d failed!\n", i); + return err; + } + } + + return 0; +} + +static int atc_create_hw_devs(struct ct_atc *atc) +{ + struct hw *hw; + struct card_conf info = {0}; + int i, err; + + err = create_hw_obj(atc->pci, atc->chip_type, atc->model, &hw); + if (err) { + dev_err(atc->card->dev, "Failed to create hw obj!!!\n"); + return err; + } + hw->card = atc->card; + atc->hw = hw; + + /* Initialize card hardware. */ + info.rsr = atc->rsr; + info.msr = atc->msr; + info.vm_pgt_phys = atc_get_ptp_phys(atc, 0); + err = hw->card_init(hw, &info); + if (err < 0) + return err; + + for (i = 0; i < NUM_RSCTYP; i++) { + if (!rsc_mgr_funcs[i].create) + continue; + + err = rsc_mgr_funcs[i].create(atc->hw, &atc->rsc_mgrs[i]); + if (err) { + dev_err(atc->card->dev, + "Failed to create rsc_mgr %d!!!\n", i); + return err; + } + } + + return 0; +} + +static int atc_get_resources(struct ct_atc *atc) +{ + struct daio_desc da_desc = {0}; + struct daio_mgr *daio_mgr; + struct src_desc src_dsc = {0}; + struct src_mgr *src_mgr; + struct srcimp_desc srcimp_dsc = {0}; + struct srcimp_mgr *srcimp_mgr; + struct sum_desc sum_dsc = {0}; + struct sum_mgr *sum_mgr; + struct capabilities cap; + int atc_srcs_limit; + int err, i; + + cap = atc->capabilities(atc); + atc_srcs_limit = cap.dedicated_mic ? NUM_ATC_SRCS : 4; + + atc->daios = kcalloc(NUM_DAIOTYP, sizeof(void *), GFP_KERNEL); + if (!atc->daios) + return -ENOMEM; + + atc->srcs = kcalloc(NUM_ATC_SRCS, sizeof(void *), GFP_KERNEL); + if (!atc->srcs) + return -ENOMEM; + + atc->srcimps = kcalloc(NUM_ATC_SRCS, sizeof(void *), GFP_KERNEL); + if (!atc->srcimps) + return -ENOMEM; + + atc->pcm = kcalloc(NUM_ATC_PCM, sizeof(void *), GFP_KERNEL); + if (!atc->pcm) + return -ENOMEM; + + daio_mgr = (struct daio_mgr *)atc->rsc_mgrs[DAIO]; + da_desc.msr = atc->msr; + for (i = 0; i < NUM_DAIOTYP; i++) { + if (((i == SPDIFIO) && (atc->model == CTSB073X)) || + ((i == SPDIFI_BAY) && (atc->model != CTSB073X)) || + ((i == MIC) && !cap.dedicated_mic) || + ((i == RCA) && !cap.dedicated_rca)) + continue; + da_desc.type = i; + da_desc.output = (i < LINEIM) || (i == RCA); + err = daio_mgr->get_daio(daio_mgr, &da_desc, + (struct daio **)&atc->daios[i]); + if (err) { + dev_err(atc->card->dev, + "Failed to get DAIO resource %d!!!\n", + i); + return err; + } + } + + src_mgr = atc->rsc_mgrs[SRC]; + src_dsc.multi = 1; + src_dsc.msr = atc->msr; + src_dsc.mode = ARCRW; + for (i = 0; i < atc_srcs_limit; i++) { + err = src_mgr->get_src(src_mgr, &src_dsc, + (struct src **)&atc->srcs[i]); + if (err) + return err; + } + + srcimp_mgr = atc->rsc_mgrs[SRCIMP]; + srcimp_dsc.msr = 8; + for (i = 0; i < atc_srcs_limit; i++) { + err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc, + (struct srcimp **)&atc->srcimps[i]); + if (err) + return err; + } + + sum_mgr = atc->rsc_mgrs[SUM]; + sum_dsc.msr = atc->msr; + for (i = 0; i < NUM_ATC_PCM; i++) { + err = sum_mgr->get_sum(sum_mgr, &sum_dsc, + (struct sum **)&atc->pcm[i]); + if (err) + return err; + } + + return 0; +} + +static void +atc_connect_dai(struct src_mgr *src_mgr, struct dai *dai, + struct src **srcs, struct srcimp **srcimps) +{ + struct rsc *rscs[2] = {NULL}; + struct src *src; + struct srcimp *srcimp; + int i = 0; + + rscs[0] = &dai->daio.rscl; + rscs[1] = &dai->daio.rscr; + for (i = 0; i < 2; i++) { + src = srcs[i]; + srcimp = srcimps[i]; + srcimp->ops->map(srcimp, src, rscs[i]); + src_mgr->src_disable(src_mgr, src); + } + + src_mgr->commit_write(src_mgr); /* Actually disable SRCs */ + + src = srcs[0]; + src->ops->set_pm(src, 1); + for (i = 0; i < 2; i++) { + src = srcs[i]; + src->ops->set_state(src, SRC_STATE_RUN); + src->ops->commit_write(src); + src_mgr->src_enable_s(src_mgr, src); + } + + dai->ops->set_srt_srcl(dai, &(srcs[0]->rsc)); + dai->ops->set_srt_srcr(dai, &(srcs[1]->rsc)); + + dai->ops->set_enb_src(dai, 1); + dai->ops->set_enb_srt(dai, 1); + dai->ops->commit_write(dai); + + src_mgr->commit_write(src_mgr); /* Synchronously enable SRCs */ +} + +static void atc_connect_resources(struct ct_atc *atc) +{ + struct dai *dai; + struct dao *dao; + struct src *src; + struct sum *sum; + struct ct_mixer *mixer; + struct rsc *rscs[2] = {NULL}; + struct capabilities cap; + int i, j; + + mixer = atc->mixer; + cap = atc->capabilities(atc); + + for (i = MIX_WAVE_FRONT, j = LINEO1; i <= MIX_SPDIF_OUT; i++, j++) { + mixer->get_output_ports(mixer, i, &rscs[0], &rscs[1]); + dao = container_of(atc->daios[j], struct dao, daio); + dao->ops->set_left_input(dao, rscs[0]); + dao->ops->set_right_input(dao, rscs[1]); + } + + if (cap.dedicated_rca) { + /* SE-300PCIE has a dedicated DAC for the RCA. */ + atc_dedicated_rca_select(atc); + } + + dai = container_of(atc->daios[LINEIM], struct dai, daio); + atc_connect_dai(atc->rsc_mgrs[SRC], dai, + (struct src **)&atc->srcs[2], + (struct srcimp **)&atc->srcimps[2]); + src = atc->srcs[2]; + mixer->set_input_left(mixer, MIX_LINE_IN, &src->rsc); + src = atc->srcs[3]; + mixer->set_input_right(mixer, MIX_LINE_IN, &src->rsc); + + if (cap.dedicated_mic) { + /* Titanium HD has a dedicated ADC for the Mic. */ + /* SE-300PCIE has a 4-channel ADC. */ + dai = container_of(atc->daios[MIC], struct dai, daio); + atc_connect_dai(atc->rsc_mgrs[SRC], dai, + (struct src **)&atc->srcs[4], + (struct srcimp **)&atc->srcimps[4]); + src = atc->srcs[4]; + mixer->set_input_left(mixer, MIX_MIC_IN, &src->rsc); + src = atc->srcs[5]; + mixer->set_input_right(mixer, MIX_MIC_IN, &src->rsc); + } + + dai = container_of(atc->daios[atc_spdif_in_type(atc)], struct dai, daio); + atc_connect_dai(atc->rsc_mgrs[SRC], dai, + (struct src **)&atc->srcs[0], + (struct srcimp **)&atc->srcimps[0]); + + src = atc->srcs[0]; + mixer->set_input_left(mixer, MIX_SPDIF_IN, &src->rsc); + src = atc->srcs[1]; + mixer->set_input_right(mixer, MIX_SPDIF_IN, &src->rsc); + + for (i = MIX_PCMI_FRONT, j = 0; i <= MIX_PCMI_SURROUND; i++, j += 2) { + sum = atc->pcm[j]; + mixer->set_input_left(mixer, i, &sum->rsc); + sum = atc->pcm[j+1]; + mixer->set_input_right(mixer, i, &sum->rsc); + } +} + +#ifdef CONFIG_PM_SLEEP +static int atc_suspend(struct ct_atc *atc) +{ + struct hw *hw = atc->hw; + + snd_power_change_state(atc->card, SNDRV_CTL_POWER_D3hot); + + atc_release_resources(atc); + + hw->suspend(hw); + + return 0; +} + +static int atc_hw_resume(struct ct_atc *atc) +{ + struct hw *hw = atc->hw; + struct card_conf info = {0}; + + /* Re-initialize card hardware. */ + info.rsr = atc->rsr; + info.msr = atc->msr; + info.vm_pgt_phys = atc_get_ptp_phys(atc, 0); + return hw->resume(hw, &info); +} + +static int atc_resources_resume(struct ct_atc *atc) +{ + struct ct_mixer *mixer; + int err = 0; + + /* Get resources */ + err = atc_get_resources(atc); + if (err < 0) { + atc_release_resources(atc); + return err; + } + + /* Build topology */ + atc_connect_resources(atc); + + mixer = atc->mixer; + mixer->resume(mixer); + + return 0; +} + +static int atc_resume(struct ct_atc *atc) +{ + int err = 0; + + /* Do hardware resume. */ + err = atc_hw_resume(atc); + if (err < 0) { + dev_err(atc->card->dev, + "pci_enable_device failed, disabling device\n"); + snd_card_disconnect(atc->card); + return err; + } + + err = atc_resources_resume(atc); + if (err < 0) + return err; + + snd_power_change_state(atc->card, SNDRV_CTL_POWER_D0); + + return 0; +} +#endif + +static const struct ct_atc atc_preset = { + .map_audio_buffer = ct_map_audio_buffer, + .unmap_audio_buffer = ct_unmap_audio_buffer, + .pcm_playback_prepare = atc_pcm_playback_prepare, + .pcm_release_resources = atc_pcm_release_resources, + .pcm_playback_start = atc_pcm_playback_start, + .pcm_playback_stop = atc_pcm_stop, + .pcm_playback_position = atc_pcm_playback_position, + .pcm_capture_prepare = atc_pcm_capture_prepare, + .pcm_capture_start = atc_pcm_capture_start, + .pcm_capture_stop = atc_pcm_stop, + .pcm_capture_position = atc_pcm_capture_position, + .spdif_passthru_playback_prepare = spdif_passthru_playback_prepare, + .get_ptp_phys = atc_get_ptp_phys, + .select_line_in = atc_select_line_in, + .select_mic_in = atc_select_mic_in, + .select_digit_io = atc_select_digit_io, + .line_front_unmute = atc_line_front_unmute, + .line_surround_unmute = atc_line_surround_unmute, + .line_clfe_unmute = atc_line_clfe_unmute, + .line_rear_unmute = atc_line_rear_unmute, + .line_in_unmute = atc_line_in_unmute, + .mic_unmute = atc_mic_unmute, + .rca_unmute = atc_rca_unmute, + .spdif_out_unmute = atc_spdif_out_unmute, + .spdif_in_unmute = atc_spdif_in_unmute, + .spdif_out_get_status = atc_spdif_out_get_status, + .spdif_out_set_status = atc_spdif_out_set_status, + .spdif_out_passthru = atc_spdif_out_passthru, + .capabilities = atc_capabilities, + .dedicated_rca_select = atc_dedicated_rca_select, + .output_switch_get = atc_output_switch_get, + .output_switch_put = atc_output_switch_put, + .mic_source_switch_get = atc_mic_source_switch_get, + .mic_source_switch_put = atc_mic_source_switch_put, +#ifdef CONFIG_PM_SLEEP + .suspend = atc_suspend, + .resume = atc_resume, +#endif +}; + +/** + * ct_atc_create - create and initialize a hardware manager + * @card: corresponding alsa card object + * @pci: corresponding kernel pci device object + * @rsr: reference sampling rate + * @msr: master sampling rate + * @chip_type: CHIPTYP enum values + * @ssid: vendor ID (upper 16 bits) and device ID (lower 16 bits) + * @ratc: return created object address in it + * + * Creates and initializes a hardware manager. + * + * Creates kmallocated ct_atc structure. Initializes hardware. + * Returns 0 if succeeds, or negative error code if fails. + */ + +int ct_atc_create(struct snd_card *card, struct pci_dev *pci, + unsigned int rsr, unsigned int msr, + int chip_type, unsigned int ssid, + struct ct_atc **ratc) +{ + struct ct_atc *atc; + static const struct snd_device_ops ops = { + .dev_free = atc_dev_free, + }; + int err; + + *ratc = NULL; + + atc = kzalloc_obj(*atc); + if (!atc) + return -ENOMEM; + + /* Set operations */ + *atc = atc_preset; + + atc->card = card; + atc->pci = pci; + atc->rsr = rsr; + atc->msr = msr; + atc->chip_type = chip_type; + + mutex_init(&atc->atc_mutex); + + /* Find card model */ + err = atc_identify_card(atc, ssid); + if (err < 0) { + dev_err(card->dev, "ctatc: Card not recognised\n"); + goto error1; + } + + /* Set up device virtual memory management object */ + err = ct_vm_create(&atc->vm, pci); + if (err < 0) + goto error1; + + /* Create all atc hw devices */ + err = atc_create_hw_devs(atc); + if (err < 0) + goto error1; + + err = ct_mixer_create(atc, (struct ct_mixer **)&atc->mixer); + if (err) { + dev_err(card->dev, "Failed to create mixer obj!!!\n"); + goto error1; + } + + /* Get resources */ + err = atc_get_resources(atc); + if (err < 0) + goto error1; + + /* Build topology */ + atc_connect_resources(atc); + + atc->timer = ct_timer_new(atc); + if (!atc->timer) { + err = -ENOMEM; + goto error1; + } + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, atc, &ops); + if (err < 0) + goto error1; + + *ratc = atc; + return 0; + +error1: + ct_atc_destroy(atc); + dev_err(card->dev, "Something wrong!!!\n"); + return err; +} diff --git a/sound/pci/ctxfi/ctatc.h b/sound/pci/ctxfi/ctatc.h new file mode 100644 index 000000000..ca0a9d5b8 --- /dev/null +++ b/sound/pci/ctxfi/ctatc.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctatc.h + * + * @Brief + * This file contains the definition of the device resource management object. + * + * @Author Liu Chun + * @Date Mar 28 2008 + */ + +#ifndef CTATC_H +#define CTATC_H + +#include +#include +#include +#include +#include + +#include "ctvmem.h" +#include "cthardware.h" +#include "ctresource.h" + +enum CTALSADEVS { /* Types of alsa devices */ + FRONT, + SURROUND, + CLFE, + SIDE, + IEC958, + MIXER, + NUM_CTALSADEVS /* This should always be the last */ +}; + +struct ct_atc_chip_sub_details { + u16 subsys; + const char *nm_model; +}; + +struct ct_atc_chip_details { + u16 vendor; + u16 device; + const struct ct_atc_chip_sub_details *sub_details; + const char *nm_card; +}; + +struct ct_atc; +struct ct_timer; +struct ct_timer_instance; + +/* alsa pcm stream descriptor */ +struct ct_atc_pcm { + struct snd_pcm_substream *substream; + void (*interrupt)(struct ct_atc_pcm *apcm); + struct ct_timer_instance *timer; + unsigned int started:1; + + /* Only mono and interleaved modes are supported now. */ + struct ct_vm_block *vm_block; + void *src; /* SRC for interacting with host memory */ + void **srccs; /* SRCs for sample rate conversion */ + void **srcimps; /* SRC Input Mappers */ + void **amixers; /* AMIXERs for routing converted data */ + void *mono; /* A SUM resource for mixing chs to one */ + unsigned char n_srcc; /* Number of converting SRCs */ + unsigned char n_srcimp; /* Number of SRC Input Mappers */ + unsigned char n_amixer; /* Number of AMIXERs */ +}; + +/* Chip resource management object */ +struct ct_atc { + struct pci_dev *pci; + struct snd_card *card; + unsigned int rsr; /* reference sample rate in Hz */ + unsigned int msr; /* master sample rate in rsr */ + unsigned int pll_rate; /* current rate of Phase Lock Loop */ + + int chip_type; + int model; + const char *chip_name; + const char *model_name; + + unsigned char rca_state; /* 0 = dedicated RCA, 1 = 7.1ch Front */ + + struct ct_vm *vm; /* device virtual memory manager for this card */ + int (*map_audio_buffer)(struct ct_atc *atc, struct ct_atc_pcm *apcm); + void (*unmap_audio_buffer)(struct ct_atc *atc, struct ct_atc_pcm *apcm); + unsigned long (*get_ptp_phys)(struct ct_atc *atc, int index); + + struct mutex atc_mutex; + + int (*pcm_playback_prepare)(struct ct_atc *atc, + struct ct_atc_pcm *apcm); + int (*pcm_playback_start)(struct ct_atc *atc, struct ct_atc_pcm *apcm); + int (*pcm_playback_stop)(struct ct_atc *atc, struct ct_atc_pcm *apcm); + int (*pcm_playback_position)(struct ct_atc *atc, + struct ct_atc_pcm *apcm); + int (*spdif_passthru_playback_prepare)(struct ct_atc *atc, + struct ct_atc_pcm *apcm); + int (*pcm_capture_prepare)(struct ct_atc *atc, struct ct_atc_pcm *apcm); + int (*pcm_capture_start)(struct ct_atc *atc, struct ct_atc_pcm *apcm); + int (*pcm_capture_stop)(struct ct_atc *atc, struct ct_atc_pcm *apcm); + int (*pcm_capture_position)(struct ct_atc *atc, + struct ct_atc_pcm *apcm); + int (*pcm_release_resources)(struct ct_atc *atc, + struct ct_atc_pcm *apcm); + int (*select_line_in)(struct ct_atc *atc); + int (*select_mic_in)(struct ct_atc *atc); + int (*select_digit_io)(struct ct_atc *atc); + int (*line_front_unmute)(struct ct_atc *atc, unsigned char state); + int (*line_surround_unmute)(struct ct_atc *atc, unsigned char state); + int (*line_clfe_unmute)(struct ct_atc *atc, unsigned char state); + int (*line_rear_unmute)(struct ct_atc *atc, unsigned char state); + int (*line_in_unmute)(struct ct_atc *atc, unsigned char state); + int (*mic_unmute)(struct ct_atc *atc, unsigned char state); + int (*rca_unmute)(struct ct_atc *atc, unsigned char state); + int (*spdif_out_unmute)(struct ct_atc *atc, unsigned char state); + int (*spdif_in_unmute)(struct ct_atc *atc, unsigned char state); + int (*spdif_out_get_status)(struct ct_atc *atc, unsigned int *status); + int (*spdif_out_set_status)(struct ct_atc *atc, unsigned int status); + int (*spdif_out_passthru)(struct ct_atc *atc, unsigned char state); + struct capabilities (*capabilities)(struct ct_atc *atc); + void (*dedicated_rca_select)(struct ct_atc *atc); + int (*output_switch_get)(struct ct_atc *atc); + int (*output_switch_put)(struct ct_atc *atc, int position); + int (*mic_source_switch_get)(struct ct_atc *atc); + int (*mic_source_switch_put)(struct ct_atc *atc, int position); + + /* Don't touch! Used for internal object. */ + void *rsc_mgrs[NUM_RSCTYP]; /* chip resource managers */ + void *mixer; /* internal mixer object */ + struct hw *hw; /* chip specific hardware access object */ + void **daios; /* digital audio io resources */ + void **pcm; /* SUMs for collecting all pcm stream */ + void **srcs; /* Sample Rate Converters for input signal */ + void **srcimps; /* input mappers for SRCs */ + + struct ct_timer *timer; + +#ifdef CONFIG_PM_SLEEP + int (*suspend)(struct ct_atc *atc); + int (*resume)(struct ct_atc *atc); +#define NUM_PCMS (NUM_CTALSADEVS - 1) + struct snd_pcm *pcms[NUM_PCMS]; +#endif +}; + + +int ct_atc_create(struct snd_card *card, struct pci_dev *pci, + unsigned int rsr, unsigned int msr, int chip_type, + unsigned int subsysid, struct ct_atc **ratc); +int ct_atc_create_alsa_devs(struct ct_atc *atc); + +#endif /* CTATC_H */ diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c new file mode 100644 index 000000000..69aacd067 --- /dev/null +++ b/sound/pci/ctxfi/ctdaio.c @@ -0,0 +1,743 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctdaio.c + * + * @Brief + * This file contains the implementation of Digital Audio Input Output + * resource management object. + * + * @Author Liu Chun + * @Date May 23 2008 + */ + +#include "ctdaio.h" +#include "cthardware.h" +#include "ctimap.h" +#include +#include + +struct daio_usage { + unsigned short data; +}; + +struct daio_rsc_idx { + unsigned short left; + unsigned short right; +}; + +static const struct daio_rsc_idx idx_20k1[NUM_DAIOTYP] = { + [LINEO1] = {.left = 0x00, .right = 0x01}, + [LINEO2] = {.left = 0x18, .right = 0x19}, + [LINEO3] = {.left = 0x08, .right = 0x09}, + [LINEO4] = {.left = 0x10, .right = 0x11}, + [LINEIM] = {.left = 0x1b5, .right = 0x1bd}, + [SPDIFOO] = {.left = 0x20, .right = 0x21}, + [SPDIFIO] = {.left = 0x15, .right = 0x1d}, + [SPDIFI_BAY] = {.left = 0x95, .right = 0x9d}, +}; + +static const struct daio_rsc_idx idx_20k2[NUM_DAIOTYP] = { + [LINEO1] = {.left = 0x40, .right = 0x41}, + [LINEO2] = {.left = 0x60, .right = 0x61}, + [LINEO3] = {.left = 0x50, .right = 0x51}, + [LINEO4] = {.left = 0x70, .right = 0x71}, + [LINEIM] = {.left = 0x45, .right = 0xc5}, + [MIC] = {.left = 0x55, .right = 0xd5}, + [RCA] = {.left = 0x30, .right = 0x31}, + [SPDIFOO] = {.left = 0x00, .right = 0x01}, + [SPDIFIO] = {.left = 0x05, .right = 0x85}, +}; + +static void daio_master(struct rsc *rsc) +{ + /* Actually, this is not the resource index of DAIO. + * For DAO, it is the input mapper index. And, for DAI, + * it is the output time-slot index. */ + rsc->conj = rsc->idx; +} + +static int daio_index(const struct rsc *rsc) +{ + return rsc->conj; +} + +static void daio_out_next_conj(struct rsc *rsc) +{ + rsc->conj += 2; +} + +static void daio_in_next_conj_20k1(struct rsc *rsc) +{ + rsc->conj += 0x200; +} + +static void daio_in_next_conj_20k2(struct rsc *rsc) +{ + rsc->conj += 0x100; +} + +static const struct rsc_ops daio_out_rsc_ops = { + .master = daio_master, + .next_conj = daio_out_next_conj, + .index = daio_index, + .output_slot = NULL, +}; + +static const struct rsc_ops daio_in_rsc_ops_20k1 = { + .master = daio_master, + .next_conj = daio_in_next_conj_20k1, + .index = NULL, + .output_slot = daio_index, +}; + +static const struct rsc_ops daio_in_rsc_ops_20k2 = { + .master = daio_master, + .next_conj = daio_in_next_conj_20k2, + .index = NULL, + .output_slot = daio_index, +}; + +static int daio_device_index(enum DAIOTYP type, struct hw *hw) +{ + switch (hw->chip_type) { + case ATC20K1: + switch (type) { + case SPDIFOO: return 0; + case SPDIFIO: return 0; + case SPDIFI_BAY: return 1; + case LINEO1: return 4; + case LINEO2: return 7; + case LINEO3: return 5; + case LINEO4: return 6; + case LINEIM: return 7; + default: + pr_err("ctxfi: Invalid type %d for hw20k1\n", type); + return -EINVAL; + } + case ATC20K2: + switch (type) { + case SPDIFOO: return 0; + case SPDIFIO: return 0; + case LINEO1: return 4; + case LINEO2: return 7; + case LINEO3: return 5; + case LINEO4: return 6; + case LINEIM: return 4; + case MIC: return 5; + case RCA: return 3; + default: + pr_err("ctxfi: Invalid type %d for hw20k2\n", type); + return -EINVAL; + } + default: + pr_err("ctxfi: Invalid chip type %d\n", hw->chip_type); + return -EINVAL; + } +} + +static int dao_rsc_reinit(struct dao *dao, const struct dao_desc *desc); + +static int dao_spdif_get_spos(struct dao *dao, unsigned int *spos) +{ + dao->hw->dao_get_spos(dao->ctrl_blk, spos); + return 0; +} + +static int dao_spdif_set_spos(struct dao *dao, unsigned int spos) +{ + dao->hw->dao_set_spos(dao->ctrl_blk, spos); + return 0; +} + +static int dao_commit_write(struct dao *dao) +{ + int idx = daio_device_index(dao->daio.type, dao->hw); + + if (idx < 0) + return idx; + dao->hw->dao_commit_write(dao->hw, idx, dao->ctrl_blk); + return 0; +} + +static int dao_set_left_input(struct dao *dao, struct rsc *input) +{ + struct imapper *entry; + struct daio *daio = &dao->daio; + int i; + + entry = kzalloc_objs(*entry, daio->rscl.msr); + if (!entry) + return -ENOMEM; + + dao->ops->clear_left_input(dao); + /* Program master and conjugate resources */ + input->ops->master(input); + daio->rscl.ops->master(&daio->rscl); + for (i = 0; i < daio->rscl.msr; i++, entry++) { + entry->slot = input->ops->output_slot(input); + entry->user = entry->addr = daio->rscl.ops->index(&daio->rscl); + dao->mgr->imap_add(dao->mgr, entry); + dao->imappers[i] = entry; + + input->ops->next_conj(input); + daio->rscl.ops->next_conj(&daio->rscl); + } + input->ops->master(input); + daio->rscl.ops->master(&daio->rscl); + + return 0; +} + +static int dao_set_right_input(struct dao *dao, struct rsc *input) +{ + struct imapper *entry; + struct daio *daio = &dao->daio; + int i; + + entry = kzalloc_objs(*entry, daio->rscr.msr); + if (!entry) + return -ENOMEM; + + dao->ops->clear_right_input(dao); + /* Program master and conjugate resources */ + input->ops->master(input); + daio->rscr.ops->master(&daio->rscr); + for (i = 0; i < daio->rscr.msr; i++, entry++) { + entry->slot = input->ops->output_slot(input); + entry->user = entry->addr = daio->rscr.ops->index(&daio->rscr); + dao->mgr->imap_add(dao->mgr, entry); + dao->imappers[daio->rscl.msr + i] = entry; + + input->ops->next_conj(input); + daio->rscr.ops->next_conj(&daio->rscr); + } + input->ops->master(input); + daio->rscr.ops->master(&daio->rscr); + + return 0; +} + +static int dao_clear_input(struct dao *dao, unsigned int start, unsigned int end) +{ + unsigned int i; + + if (!dao->imappers[start]) + return 0; + for (i = start; i < end; i++) { + dao->mgr->imap_delete(dao->mgr, dao->imappers[i]); + dao->imappers[i] = NULL; + } + + return 0; +} + + +static int dao_clear_left_input(struct dao *dao) +{ + return dao_clear_input(dao, 0, dao->daio.rscl.msr); +} + +static int dao_clear_right_input(struct dao *dao) +{ + return dao_clear_input(dao, dao->daio.rscl.msr, + dao->daio.rscl.msr + dao->daio.rscr.msr); +} + +static const struct dao_rsc_ops dao_ops = { + .set_spos = dao_spdif_set_spos, + .commit_write = dao_commit_write, + .get_spos = dao_spdif_get_spos, + .reinit = dao_rsc_reinit, + .set_left_input = dao_set_left_input, + .set_right_input = dao_set_right_input, + .clear_left_input = dao_clear_left_input, + .clear_right_input = dao_clear_right_input, +}; + +static int dai_set_srt_srcl(struct dai *dai, struct rsc *src) +{ + src->ops->master(src); + dai->hw->dai_srt_set_srcm(dai->ctrl_blk, src->ops->index(src)); + return 0; +} + +static int dai_set_srt_srcr(struct dai *dai, struct rsc *src) +{ + src->ops->master(src); + dai->hw->dai_srt_set_srco(dai->ctrl_blk, src->ops->index(src)); + return 0; +} + +static int dai_set_srt_msr(struct dai *dai, unsigned int msr) +{ + unsigned int rsr; + + for (rsr = 0; msr > 1; msr >>= 1) + rsr++; + + dai->hw->dai_srt_set_rsr(dai->ctrl_blk, rsr); + return 0; +} + +static int dai_set_enb_src(struct dai *dai, unsigned int enb) +{ + dai->hw->dai_srt_set_ec(dai->ctrl_blk, enb); + return 0; +} + +static int dai_set_enb_srt(struct dai *dai, unsigned int enb) +{ + dai->hw->dai_srt_set_et(dai->ctrl_blk, enb); + return 0; +} + +static int dai_commit_write(struct dai *dai) +{ + int idx = daio_device_index(dai->daio.type, dai->hw); + + if (idx < 0) + return idx; + dai->hw->dai_commit_write(dai->hw, idx, dai->ctrl_blk); + return 0; +} + +static const struct dai_rsc_ops dai_ops = { + .set_srt_srcl = dai_set_srt_srcl, + .set_srt_srcr = dai_set_srt_srcr, + .set_srt_msr = dai_set_srt_msr, + .set_enb_src = dai_set_enb_src, + .set_enb_srt = dai_set_enb_srt, + .commit_write = dai_commit_write, +}; + +static int daio_rsc_init(struct daio *daio, + const struct daio_desc *desc, + struct hw *hw) +{ + int err; + unsigned int idx_l, idx_r; + + switch (hw->chip_type) { + case ATC20K1: + idx_l = idx_20k1[desc->type].left; + idx_r = idx_20k1[desc->type].right; + break; + case ATC20K2: + idx_l = idx_20k2[desc->type].left; + idx_r = idx_20k2[desc->type].right; + break; + default: + return -EINVAL; + } + err = rsc_init(&daio->rscl, idx_l, DAIO, desc->msr, hw); + if (err) + return err; + + err = rsc_init(&daio->rscr, idx_r, DAIO, desc->msr, hw); + if (err) + goto error1; + + /* Set daio->rscl/r->ops to daio specific ones */ + if (desc->output) { + daio->rscl.ops = daio->rscr.ops = &daio_out_rsc_ops; + } else { + switch (hw->chip_type) { + case ATC20K1: + daio->rscl.ops = daio->rscr.ops = &daio_in_rsc_ops_20k1; + break; + case ATC20K2: + daio->rscl.ops = daio->rscr.ops = &daio_in_rsc_ops_20k2; + break; + default: + break; + } + } + daio->type = desc->type; + daio->output = desc->output; + + return 0; + +error1: + rsc_uninit(&daio->rscl); + return err; +} + +static int daio_rsc_uninit(struct daio *daio) +{ + rsc_uninit(&daio->rscl); + rsc_uninit(&daio->rscr); + + return 0; +} + +static int dao_rsc_init(struct dao *dao, + const struct daio_desc *desc, + struct daio_mgr *mgr) +{ + struct hw *hw = mgr->mgr.hw; + unsigned int conf; + int idx, err; + + err = daio_rsc_init(&dao->daio, desc, mgr->mgr.hw); + if (err) + return err; + + dao->imappers = kzalloc(array3_size(sizeof(void *), desc->msr, 2), + GFP_KERNEL); + if (!dao->imappers) { + err = -ENOMEM; + goto error1; + } + dao->ops = &dao_ops; + dao->mgr = mgr; + dao->hw = hw; + err = hw->dao_get_ctrl_blk(&dao->ctrl_blk); + if (err) + goto error2; + + idx = daio_device_index(dao->daio.type, hw); + if (idx < 0) { + err = idx; + goto error2; + } + + hw->daio_mgr_dsb_dao(mgr->mgr.ctrl_blk, idx); + hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk); + + conf = (desc->msr & 0x7) | (desc->passthru << 3); + hw->daio_mgr_dao_init(hw, mgr->mgr.ctrl_blk, idx, conf); + hw->daio_mgr_enb_dao(mgr->mgr.ctrl_blk, idx); + hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk); + + return 0; + +error2: + kfree(dao->imappers); + dao->imappers = NULL; +error1: + daio_rsc_uninit(&dao->daio); + return err; +} + +static int dao_rsc_uninit(struct dao *dao) +{ + if (dao->imappers) { + if (dao->imappers[0]) + dao_clear_left_input(dao); + + if (dao->imappers[dao->daio.rscl.msr]) + dao_clear_right_input(dao); + + kfree(dao->imappers); + dao->imappers = NULL; + } + dao->hw->dao_put_ctrl_blk(dao->ctrl_blk); + dao->hw = dao->ctrl_blk = NULL; + daio_rsc_uninit(&dao->daio); + + return 0; +} + +static int dao_rsc_reinit(struct dao *dao, const struct dao_desc *desc) +{ + struct daio_mgr *mgr = dao->mgr; + struct daio_desc dsc = {0}; + + dsc.type = dao->daio.type; + dsc.msr = desc->msr; + dsc.passthru = desc->passthru; + dsc.output = dao->daio.output; + dao_rsc_uninit(dao); + return dao_rsc_init(dao, &dsc, mgr); +} + +static int dai_rsc_init(struct dai *dai, + const struct daio_desc *desc, + struct daio_mgr *mgr) +{ + int idx, err; + struct hw *hw = mgr->mgr.hw; + unsigned int rsr, msr; + + err = daio_rsc_init(&dai->daio, desc, mgr->mgr.hw); + if (err) + return err; + + dai->ops = &dai_ops; + dai->hw = mgr->mgr.hw; + err = hw->dai_get_ctrl_blk(&dai->ctrl_blk); + if (err) + goto error1; + + idx = daio_device_index(dai->daio.type, dai->hw); + if (idx < 0) { + err = idx; + goto error1; + } + + for (rsr = 0, msr = desc->msr; msr > 1; msr >>= 1) + rsr++; + + hw->dai_srt_set_rsr(dai->ctrl_blk, rsr); + hw->dai_srt_set_drat(dai->ctrl_blk, 0); + /* default to disabling control of a SRC */ + hw->dai_srt_set_ec(dai->ctrl_blk, 0); + hw->dai_srt_set_et(dai->ctrl_blk, 0); /* default to disabling SRT */ + hw->dai_commit_write(hw, idx, dai->ctrl_blk); + + return 0; + +error1: + daio_rsc_uninit(&dai->daio); + return err; +} + +static int dai_rsc_uninit(struct dai *dai) +{ + dai->hw->dai_put_ctrl_blk(dai->ctrl_blk); + dai->hw = dai->ctrl_blk = NULL; + daio_rsc_uninit(&dai->daio); + return 0; +} + +static int daio_mgr_get_rsc(struct rsc_mgr *mgr, enum DAIOTYP type) +{ + if (((struct daio_usage *)mgr->rscs)->data & (0x1 << type)) + return -ENOENT; + + ((struct daio_usage *)mgr->rscs)->data |= (0x1 << type); + + return 0; +} + +static int daio_mgr_put_rsc(struct rsc_mgr *mgr, enum DAIOTYP type) +{ + ((struct daio_usage *)mgr->rscs)->data &= ~(0x1 << type); + + return 0; +} + +static int get_daio_rsc(struct daio_mgr *mgr, + const struct daio_desc *desc, + struct daio **rdaio) +{ + int err; + + *rdaio = NULL; + + /* Check whether there are sufficient daio resources to meet request. */ + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + err = daio_mgr_get_rsc(&mgr->mgr, desc->type); + } + if (err) { + dev_err(mgr->card->dev, + "Can't meet DAIO resource request!\n"); + return err; + } + + err = -ENOMEM; + /* Allocate mem for daio resource */ + if (desc->output) { + struct dao *dao = kzalloc(sizeof(*dao), GFP_KERNEL); + if (!dao) + goto error; + + err = dao_rsc_init(dao, desc, mgr); + if (err) { + kfree(dao); + goto error; + } + + *rdaio = &dao->daio; + } else { + struct dai *dai = kzalloc(sizeof(*dai), GFP_KERNEL); + if (!dai) + goto error; + + err = dai_rsc_init(dai, desc, mgr); + if (err) { + kfree(dai); + goto error; + } + + *rdaio = &dai->daio; + } + + mgr->daio_enable(mgr, *rdaio); + mgr->commit_write(mgr); + + return 0; + +error: + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + daio_mgr_put_rsc(&mgr->mgr, desc->type); + } + return err; +} + +static int put_daio_rsc(struct daio_mgr *mgr, struct daio *daio) +{ + mgr->daio_disable(mgr, daio); + mgr->commit_write(mgr); + + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + daio_mgr_put_rsc(&mgr->mgr, daio->type); + } + + if (daio->output) { + dao_rsc_uninit(container_of(daio, struct dao, daio)); + kfree(container_of(daio, struct dao, daio)); + } else { + dai_rsc_uninit(container_of(daio, struct dai, daio)); + kfree(container_of(daio, struct dai, daio)); + } + + return 0; +} + +static int daio_mgr_enb_daio(struct daio_mgr *mgr, struct daio *daio) +{ + struct hw *hw = mgr->mgr.hw; + int idx = daio_device_index(daio->type, hw); + + if (idx < 0) + return idx; + if (daio->output) + hw->daio_mgr_enb_dao(mgr->mgr.ctrl_blk, idx); + else + hw->daio_mgr_enb_dai(mgr->mgr.ctrl_blk, idx); + return 0; +} + +static int daio_mgr_dsb_daio(struct daio_mgr *mgr, struct daio *daio) +{ + struct hw *hw = mgr->mgr.hw; + int idx = daio_device_index(daio->type, hw); + + if (idx < 0) + return idx; + if (daio->output) + hw->daio_mgr_dsb_dao(mgr->mgr.ctrl_blk, idx); + else + hw->daio_mgr_dsb_dai(mgr->mgr.ctrl_blk, idx); + return 0; +} + +static int daio_map_op(void *data, struct imapper *entry) +{ + struct rsc_mgr *mgr = &((struct daio_mgr *)data)->mgr; + struct hw *hw = mgr->hw; + + hw->daio_mgr_set_imaparc(mgr->ctrl_blk, entry->slot); + hw->daio_mgr_set_imapnxt(mgr->ctrl_blk, entry->next); + hw->daio_mgr_set_imapaddr(mgr->ctrl_blk, entry->addr); + hw->daio_mgr_commit_write(mgr->hw, mgr->ctrl_blk); + + return 0; +} + +static int daio_imap_add(struct daio_mgr *mgr, struct imapper *entry) +{ + guard(spinlock_irqsave)(&mgr->imap_lock); + if (!entry->addr && mgr->init_imap_added) { + input_mapper_delete(&mgr->imappers, mgr->init_imap, + daio_map_op, mgr); + mgr->init_imap_added = 0; + } + return input_mapper_add(&mgr->imappers, entry, daio_map_op, mgr); +} + +static int daio_imap_delete(struct daio_mgr *mgr, struct imapper *entry) +{ + int err; + + guard(spinlock_irqsave)(&mgr->imap_lock); + err = input_mapper_delete(&mgr->imappers, entry, daio_map_op, mgr); + if (list_empty(&mgr->imappers)) { + input_mapper_add(&mgr->imappers, mgr->init_imap, + daio_map_op, mgr); + mgr->init_imap_added = 1; + } + + return err; +} + +static int daio_mgr_commit_write(struct daio_mgr *mgr) +{ + struct hw *hw = mgr->mgr.hw; + + hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk); + return 0; +} + +int daio_mgr_create(struct hw *hw, void **rdaio_mgr) +{ + int err, i; + struct daio_mgr *daio_mgr; + struct imapper *entry; + + *rdaio_mgr = NULL; + daio_mgr = kzalloc_obj(*daio_mgr); + if (!daio_mgr) + return -ENOMEM; + + err = rsc_mgr_init(&daio_mgr->mgr, DAIO, NUM_DAIOTYP, hw); + if (err) + goto error1; + + spin_lock_init(&daio_mgr->mgr_lock); + spin_lock_init(&daio_mgr->imap_lock); + INIT_LIST_HEAD(&daio_mgr->imappers); + entry = kzalloc_obj(*entry); + if (!entry) { + err = -ENOMEM; + goto error2; + } + entry->slot = entry->addr = entry->next = entry->user = 0; + list_add(&entry->list, &daio_mgr->imappers); + daio_mgr->init_imap = entry; + daio_mgr->init_imap_added = 1; + + daio_mgr->get_daio = get_daio_rsc; + daio_mgr->put_daio = put_daio_rsc; + daio_mgr->daio_enable = daio_mgr_enb_daio; + daio_mgr->daio_disable = daio_mgr_dsb_daio; + daio_mgr->imap_add = daio_imap_add; + daio_mgr->imap_delete = daio_imap_delete; + daio_mgr->commit_write = daio_mgr_commit_write; + daio_mgr->card = hw->card; + + for (i = 0; i < 8; i++) { + hw->daio_mgr_dsb_dao(daio_mgr->mgr.ctrl_blk, i); + hw->daio_mgr_dsb_dai(daio_mgr->mgr.ctrl_blk, i); + } + hw->daio_mgr_commit_write(hw, daio_mgr->mgr.ctrl_blk); + + *rdaio_mgr = daio_mgr; + + return 0; + +error2: + rsc_mgr_uninit(&daio_mgr->mgr); +error1: + kfree(daio_mgr); + return err; +} + +int daio_mgr_destroy(void *ptr) +{ + struct daio_mgr *daio_mgr = ptr; + + /* free daio input mapper list */ + scoped_guard(spinlock_irqsave, &daio_mgr->imap_lock) { + free_input_mapper_list(&daio_mgr->imappers); + } + + rsc_mgr_uninit(&daio_mgr->mgr); + kfree(daio_mgr); + + return 0; +} + diff --git a/sound/pci/ctxfi/ctdaio.h b/sound/pci/ctxfi/ctdaio.h new file mode 100644 index 000000000..c9f6207fe --- /dev/null +++ b/sound/pci/ctxfi/ctdaio.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctdaio.h + * + * @Brief + * This file contains the definition of Digital Audio Input Output + * resource management object. + * + * @Author Liu Chun + * @Date May 23 2008 + */ + +#ifndef CTDAIO_H +#define CTDAIO_H + +#include "ctresource.h" +#include "ctimap.h" +#include +#include +#include + +/* Define the descriptor of a daio resource */ +enum DAIOTYP { + LINEO1, + LINEO2, + LINEO3, + LINEO4, + SPDIFOO, /* S/PDIF Out (Flexijack/Optical) */ + LINEIM, + SPDIFIO, /* S/PDIF In (Flexijack/Optical) on the card */ + MIC, /* Dedicated mic on Titanium HD */ + RCA, /* Dedicated RCA on SE-300PCIE */ + SPDIFI_BAY, /* S/PDIF In on internal drive bay */ + NUM_DAIOTYP +}; + +struct dao_rsc_ops; +struct dai_rsc_ops; +struct daio_mgr; + +struct daio { + struct rsc rscl; /* Basic resource info for left TX/RX */ + struct rsc rscr; /* Basic resource info for right TX/RX */ + enum DAIOTYP type; + unsigned char output; +}; + +struct dao { + struct daio daio; + const struct dao_rsc_ops *ops; /* DAO specific operations */ + struct imapper **imappers; + struct daio_mgr *mgr; + struct hw *hw; + void *ctrl_blk; +}; + +struct dai { + struct daio daio; + const struct dai_rsc_ops *ops; /* DAI specific operations */ + struct hw *hw; + void *ctrl_blk; +}; + +struct dao_desc { + unsigned int msr:4; + unsigned int passthru:1; +}; + +struct dao_rsc_ops { + int (*set_spos)(struct dao *dao, unsigned int spos); + int (*commit_write)(struct dao *dao); + int (*get_spos)(struct dao *dao, unsigned int *spos); + int (*reinit)(struct dao *dao, const struct dao_desc *desc); + int (*set_left_input)(struct dao *dao, struct rsc *input); + int (*set_right_input)(struct dao *dao, struct rsc *input); + int (*clear_left_input)(struct dao *dao); + int (*clear_right_input)(struct dao *dao); +}; + +struct dai_rsc_ops { + int (*set_srt_srcl)(struct dai *dai, struct rsc *src); + int (*set_srt_srcr)(struct dai *dai, struct rsc *src); + int (*set_srt_msr)(struct dai *dai, unsigned int msr); + int (*set_enb_src)(struct dai *dai, unsigned int enb); + int (*set_enb_srt)(struct dai *dai, unsigned int enb); + int (*commit_write)(struct dai *dai); +}; + +/* Define daio resource request description info */ +struct daio_desc { + unsigned int type:4; + unsigned int msr:4; + unsigned int passthru:1; + unsigned int output:1; +}; + +struct daio_mgr { + struct rsc_mgr mgr; /* Basic resource manager info */ + struct snd_card *card; /* pointer to this card */ + spinlock_t mgr_lock; + spinlock_t imap_lock; + struct list_head imappers; + struct imapper *init_imap; + unsigned int init_imap_added; + + /* request one daio resource */ + int (*get_daio)(struct daio_mgr *mgr, + const struct daio_desc *desc, struct daio **rdaio); + /* return one daio resource */ + int (*put_daio)(struct daio_mgr *mgr, struct daio *daio); + int (*daio_enable)(struct daio_mgr *mgr, struct daio *daio); + int (*daio_disable)(struct daio_mgr *mgr, struct daio *daio); + int (*imap_add)(struct daio_mgr *mgr, struct imapper *entry); + int (*imap_delete)(struct daio_mgr *mgr, struct imapper *entry); + int (*commit_write)(struct daio_mgr *mgr); +}; + +/* Constructor and destructor of daio resource manager */ +int daio_mgr_create(struct hw *hw, void **ptr); +int daio_mgr_destroy(void *ptr); + +#endif /* CTDAIO_H */ diff --git a/sound/pci/ctxfi/cthardware.c b/sound/pci/ctxfi/cthardware.c new file mode 100644 index 000000000..1d5064486 --- /dev/null +++ b/sound/pci/ctxfi/cthardware.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File cthardware.c + * + * @Brief + * This file contains the implementation of hardware access methord. + * + * @Author Liu Chun + * @Date Jun 26 2008 + */ + +#include "cthardware.h" +#include "cthw20k1.h" +#include "cthw20k2.h" +#include + +int create_hw_obj(struct pci_dev *pci, enum CHIPTYP chip_type, + enum CTCARDS model, struct hw **rhw) +{ + int err; + + switch (chip_type) { + case ATC20K1: + err = create_20k1_hw_obj(rhw); + break; + case ATC20K2: + err = create_20k2_hw_obj(rhw); + break; + default: + err = -ENODEV; + break; + } + if (err) + return err; + + (*rhw)->pci = pci; + (*rhw)->chip_type = chip_type; + (*rhw)->model = model; + + return 0; +} + +int destroy_hw_obj(struct hw *hw) +{ + int err; + + switch (hw->pci->device) { + case 0x0005: /* 20k1 device */ + err = destroy_20k1_hw_obj(hw); + break; + case 0x000B: /* 20k2 device */ + err = destroy_20k2_hw_obj(hw); + break; + default: + err = -ENODEV; + break; + } + + return err; +} + +unsigned int get_field(unsigned int data, unsigned int field) +{ + int i; + + if (WARN_ON(!field)) + return 0; + /* @field should always be greater than 0 */ + for (i = 0; !(field & (1 << i)); ) + i++; + + return (data & field) >> i; +} + +void set_field(unsigned int *data, unsigned int field, unsigned int value) +{ + int i; + + if (WARN_ON(!field)) + return; + /* @field should always be greater than 0 */ + for (i = 0; !(field & (1 << i)); ) + i++; + + *data = (*data & (~field)) | ((value << i) & field); +} + diff --git a/sound/pci/ctxfi/cthardware.h b/sound/pci/ctxfi/cthardware.h new file mode 100644 index 000000000..a3051fdd3 --- /dev/null +++ b/sound/pci/ctxfi/cthardware.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File cthardware.h + * + * @Brief + * This file contains the definition of hardware access methord. + * + * @Author Liu Chun + * @Date May 13 2008 + */ + +#ifndef CTHARDWARE_H +#define CTHARDWARE_H + +#include +#include +#include + +enum CHIPTYP { + ATC20K1, + ATC20K2, + ATCNONE +}; + +enum CTCARDS { + /* 20k1 models */ + CTSB046X, + CT20K1_MODEL_FIRST = CTSB046X, + CTSB055X, + CTSB073X, + CTUAA, + CT20K1_UNKNOWN, + /* 20k2 models */ + CTSB0760, + CT20K2_MODEL_FIRST = CTSB0760, + CTHENDRIX, + CTSB0880, + CTSB1270, + CTOK0010, + CT20K2_UNKNOWN, + NUM_CTCARDS /* This should always be the last */ +}; + +/* Type of input source for ADC */ +enum ADCSRC{ + ADC_MICIN, + ADC_LINEIN, + ADC_VIDEO, + ADC_AUX, + ADC_NONE /* Switch to digital input */ +}; + +struct card_conf { + /* device virtual mem page table page physical addr + * (supporting one page table page now) */ + unsigned long vm_pgt_phys; + unsigned int rsr; /* reference sample rate in Hzs*/ + unsigned int msr; /* master sample rate in rsrs */ +}; + +struct capabilities { + unsigned int digit_io_switch:1; + unsigned int dedicated_mic:1; + unsigned int dedicated_rca:1; + unsigned int output_switch:1; + unsigned int mic_source_switch:1; +}; + +struct hw { + int (*card_init)(struct hw *hw, struct card_conf *info); + int (*card_stop)(struct hw *hw); + int (*pll_init)(struct hw *hw, unsigned int rsr); +#ifdef CONFIG_PM_SLEEP + int (*suspend)(struct hw *hw); + int (*resume)(struct hw *hw, struct card_conf *info); +#endif + int (*is_adc_source_selected)(struct hw *hw, enum ADCSRC source); + int (*select_adc_source)(struct hw *hw, enum ADCSRC source); + struct capabilities (*capabilities)(struct hw *hw); + int (*output_switch_get)(struct hw *hw); + int (*output_switch_put)(struct hw *hw, int position); + int (*mic_source_switch_get)(struct hw *hw); + int (*mic_source_switch_put)(struct hw *hw, int position); + + /* SRC operations */ + int (*src_rsc_get_ctrl_blk)(void **rblk); + int (*src_rsc_put_ctrl_blk)(void *blk); + int (*src_set_state)(void *blk, unsigned int state); + int (*src_set_bm)(void *blk, unsigned int bm); + int (*src_set_rsr)(void *blk, unsigned int rsr); + int (*src_set_sf)(void *blk, unsigned int sf); + int (*src_set_wr)(void *blk, unsigned int wr); + int (*src_set_pm)(void *blk, unsigned int pm); + int (*src_set_rom)(void *blk, unsigned int rom); + int (*src_set_vo)(void *blk, unsigned int vo); + int (*src_set_st)(void *blk, unsigned int st); + int (*src_set_ie)(void *blk, unsigned int ie); + int (*src_set_ilsz)(void *blk, unsigned int ilsz); + int (*src_set_bp)(void *blk, unsigned int bp); + int (*src_set_cisz)(void *blk, unsigned int cisz); + int (*src_set_ca)(void *blk, unsigned int ca); + int (*src_set_sa)(void *blk, unsigned int sa); + int (*src_set_la)(void *blk, unsigned int la); + int (*src_set_pitch)(void *blk, unsigned int pitch); + int (*src_set_clear_zbufs)(void *blk, unsigned int clear); + int (*src_set_dirty)(void *blk, unsigned int flags); + int (*src_set_dirty_all)(void *blk); + int (*src_commit_write)(struct hw *hw, unsigned int idx, void *blk); + int (*src_get_ca)(struct hw *hw, unsigned int idx, void *blk); + unsigned int (*src_get_dirty)(void *blk); + unsigned int (*src_dirty_conj_mask)(void); + int (*src_mgr_get_ctrl_blk)(void **rblk); + int (*src_mgr_put_ctrl_blk)(void *blk); + /* syncly enable src @idx */ + int (*src_mgr_enbs_src)(void *blk, unsigned int idx); + /* enable src @idx */ + int (*src_mgr_enb_src)(void *blk, unsigned int idx); + /* disable src @idx */ + int (*src_mgr_dsb_src)(void *blk, unsigned int idx); + int (*src_mgr_commit_write)(struct hw *hw, void *blk); + + /* SRC Input Mapper operations */ + int (*srcimp_mgr_get_ctrl_blk)(void **rblk); + int (*srcimp_mgr_put_ctrl_blk)(void *blk); + int (*srcimp_mgr_set_imaparc)(void *blk, unsigned int slot); + int (*srcimp_mgr_set_imapuser)(void *blk, unsigned int user); + int (*srcimp_mgr_set_imapnxt)(void *blk, unsigned int next); + int (*srcimp_mgr_set_imapaddr)(void *blk, unsigned int addr); + int (*srcimp_mgr_commit_write)(struct hw *hw, void *blk); + + /* AMIXER operations */ + int (*amixer_rsc_get_ctrl_blk)(void **rblk); + int (*amixer_rsc_put_ctrl_blk)(void *blk); + int (*amixer_mgr_get_ctrl_blk)(void **rblk); + int (*amixer_mgr_put_ctrl_blk)(void *blk); + int (*amixer_set_mode)(void *blk, unsigned int mode); + int (*amixer_set_iv)(void *blk, unsigned int iv); + int (*amixer_set_x)(void *blk, unsigned int x); + int (*amixer_set_y)(void *blk, unsigned int y); + int (*amixer_set_sadr)(void *blk, unsigned int sadr); + int (*amixer_set_se)(void *blk, unsigned int se); + int (*amixer_set_dirty)(void *blk, unsigned int flags); + int (*amixer_set_dirty_all)(void *blk); + int (*amixer_commit_write)(struct hw *hw, unsigned int idx, void *blk); + int (*amixer_get_y)(void *blk); + unsigned int (*amixer_get_dirty)(void *blk); + + /* DAIO operations */ + int (*dai_get_ctrl_blk)(void **rblk); + int (*dai_put_ctrl_blk)(void *blk); + int (*dai_srt_set_srco)(void *blk, unsigned int src); + int (*dai_srt_set_srcm)(void *blk, unsigned int src); + int (*dai_srt_set_rsr)(void *blk, unsigned int rsr); + int (*dai_srt_set_drat)(void *blk, unsigned int drat); + int (*dai_srt_set_ec)(void *blk, unsigned int ec); + int (*dai_srt_set_et)(void *blk, unsigned int et); + int (*dai_commit_write)(struct hw *hw, unsigned int idx, void *blk); + int (*dao_get_ctrl_blk)(void **rblk); + int (*dao_put_ctrl_blk)(void *blk); + int (*dao_set_spos)(void *blk, unsigned int spos); + int (*dao_commit_write)(struct hw *hw, unsigned int idx, void *blk); + int (*dao_get_spos)(void *blk, unsigned int *spos); + + int (*daio_mgr_get_ctrl_blk)(struct hw *hw, void **rblk); + int (*daio_mgr_put_ctrl_blk)(void *blk); + int (*daio_mgr_enb_dai)(void *blk, unsigned int idx); + int (*daio_mgr_dsb_dai)(void *blk, unsigned int idx); + int (*daio_mgr_enb_dao)(void *blk, unsigned int idx); + int (*daio_mgr_dsb_dao)(void *blk, unsigned int idx); + int (*daio_mgr_dao_init)(struct hw *hw, void *blk, unsigned int idx, + unsigned int conf); + int (*daio_mgr_set_imaparc)(void *blk, unsigned int slot); + int (*daio_mgr_set_imapnxt)(void *blk, unsigned int next); + int (*daio_mgr_set_imapaddr)(void *blk, unsigned int addr); + int (*daio_mgr_commit_write)(struct hw *hw, void *blk); + + int (*set_timer_irq)(struct hw *hw, int enable); + int (*set_timer_tick)(struct hw *hw, unsigned int tick); + unsigned int (*get_wc)(struct hw *hw); + + void (*irq_callback)(void *data, unsigned int bit); + void *irq_callback_data; + + struct pci_dev *pci; /* the pci kernel structure of this card */ + struct snd_card *card; /* pointer to this card */ + int irq; + unsigned long io_base; + void __iomem *mem_base; + + enum CHIPTYP chip_type; + enum CTCARDS model; +}; + +int create_hw_obj(struct pci_dev *pci, enum CHIPTYP chip_type, + enum CTCARDS model, struct hw **rhw); +int destroy_hw_obj(struct hw *hw); + +unsigned int get_field(unsigned int data, unsigned int field); +void set_field(unsigned int *data, unsigned int field, unsigned int value); + +/* IRQ bits */ +#define PLL_INT (1 << 10) /* PLL input-clock out-of-range */ +#define FI_INT (1 << 9) /* forced interrupt */ +#define IT_INT (1 << 8) /* timer interrupt */ +#define PCI_INT (1 << 7) /* PCI bus error pending */ +#define URT_INT (1 << 6) /* UART Tx/Rx */ +#define GPI_INT (1 << 5) /* GPI pin */ +#define MIX_INT (1 << 4) /* mixer parameter segment FIFO channels */ +#define DAI_INT (1 << 3) /* DAI (SR-tracker or SPDIF-receiver) */ +#define TP_INT (1 << 2) /* transport priority queue */ +#define DSP_INT (1 << 1) /* DSP */ +#define SRC_INT (1 << 0) /* SRC channels */ + +#endif /* CTHARDWARE_H */ diff --git a/sound/pci/ctxfi/cthw20k1.c b/sound/pci/ctxfi/cthw20k1.c new file mode 100644 index 000000000..06753f555 --- /dev/null +++ b/sound/pci/ctxfi/cthw20k1.c @@ -0,0 +1,2251 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File cthw20k1.c + * + * @Brief + * This file contains the implementation of hardware access methord for 20k1. + * + * @Author Liu Chun + * @Date Jun 24 2008 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cthw20k1.h" +#include "ct20k1reg.h" + +struct hw20k1 { + struct hw hw; + spinlock_t reg_20k1_lock; + spinlock_t reg_pci_lock; +}; + +static u32 hw_read_20kx(struct hw *hw, u32 reg); +static void hw_write_20kx(struct hw *hw, u32 reg, u32 data); +static u32 hw_read_pci(struct hw *hw, u32 reg); +static void hw_write_pci(struct hw *hw, u32 reg, u32 data); + +/* + * Type definition block. + * The layout of control structures can be directly applied on 20k2 chip. + */ + +/* + * SRC control block definitions. + */ + +/* SRC resource control block */ +#define SRCCTL_STATE 0x00000007 +#define SRCCTL_BM 0x00000008 +#define SRCCTL_RSR 0x00000030 +#define SRCCTL_SF 0x000001C0 +#define SRCCTL_WR 0x00000200 +#define SRCCTL_PM 0x00000400 +#define SRCCTL_ROM 0x00001800 +#define SRCCTL_VO 0x00002000 +#define SRCCTL_ST 0x00004000 +#define SRCCTL_IE 0x00008000 +#define SRCCTL_ILSZ 0x000F0000 +#define SRCCTL_BP 0x00100000 + +#define SRCCCR_CISZ 0x000007FF +#define SRCCCR_CWA 0x001FF800 +#define SRCCCR_D 0x00200000 +#define SRCCCR_RS 0x01C00000 +#define SRCCCR_NAL 0x3E000000 +#define SRCCCR_RA 0xC0000000 + +#define SRCCA_CA 0x03FFFFFF +#define SRCCA_RS 0x1C000000 +#define SRCCA_NAL 0xE0000000 + +#define SRCSA_SA 0x03FFFFFF + +#define SRCLA_LA 0x03FFFFFF + +/* Mixer Parameter Ring ram Low and Hight register. + * Fixed-point value in 8.24 format for parameter channel */ +#define MPRLH_PITCH 0xFFFFFFFF + +/* SRC resource register dirty flags */ +union src_dirty { + struct { + u16 ctl:1; + u16 ccr:1; + u16 sa:1; + u16 la:1; + u16 ca:1; + u16 mpr:1; + u16 czbfs:1; /* Clear Z-Buffers */ + u16 rsv:9; + } bf; + u16 data; +}; + +struct src_rsc_ctrl_blk { + unsigned int ctl; + unsigned int ccr; + unsigned int ca; + unsigned int sa; + unsigned int la; + unsigned int mpr; + union src_dirty dirty; +}; + +/* SRC manager control block */ +union src_mgr_dirty { + struct { + u16 enb0:1; + u16 enb1:1; + u16 enb2:1; + u16 enb3:1; + u16 enb4:1; + u16 enb5:1; + u16 enb6:1; + u16 enb7:1; + u16 enbsa:1; + u16 rsv:7; + } bf; + u16 data; +}; + +struct src_mgr_ctrl_blk { + unsigned int enbsa; + unsigned int enb[8]; + union src_mgr_dirty dirty; +}; + +/* SRCIMP manager control block */ +#define SRCAIM_ARC 0x00000FFF +#define SRCAIM_NXT 0x00FF0000 +#define SRCAIM_SRC 0xFF000000 + +struct srcimap { + unsigned int srcaim; + unsigned int idx; +}; + +/* SRCIMP manager register dirty flags */ +union srcimp_mgr_dirty { + struct { + u16 srcimap:1; + u16 rsv:15; + } bf; + u16 data; +}; + +struct srcimp_mgr_ctrl_blk { + struct srcimap srcimap; + union srcimp_mgr_dirty dirty; +}; + +/* + * Function implementation block. + */ + +static int src_get_rsc_ctrl_blk(void **rblk) +{ + struct src_rsc_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int src_put_rsc_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int src_set_state(void *blk, unsigned int state) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_STATE, state); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_bm(void *blk, unsigned int bm) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_BM, bm); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_rsr(void *blk, unsigned int rsr) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_RSR, rsr); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_sf(void *blk, unsigned int sf) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_SF, sf); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_wr(void *blk, unsigned int wr) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_WR, wr); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_pm(void *blk, unsigned int pm) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_PM, pm); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_rom(void *blk, unsigned int rom) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_ROM, rom); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_vo(void *blk, unsigned int vo) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_VO, vo); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_st(void *blk, unsigned int st) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_ST, st); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_ie(void *blk, unsigned int ie) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_IE, ie); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_ilsz(void *blk, unsigned int ilsz) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_ILSZ, ilsz); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_bp(void *blk, unsigned int bp) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_BP, bp); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_cisz(void *blk, unsigned int cisz) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ccr, SRCCCR_CISZ, cisz); + ctl->dirty.bf.ccr = 1; + return 0; +} + +static int src_set_ca(void *blk, unsigned int ca) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ca, SRCCA_CA, ca); + ctl->dirty.bf.ca = 1; + return 0; +} + +static int src_set_sa(void *blk, unsigned int sa) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->sa, SRCSA_SA, sa); + ctl->dirty.bf.sa = 1; + return 0; +} + +static int src_set_la(void *blk, unsigned int la) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->la, SRCLA_LA, la); + ctl->dirty.bf.la = 1; + return 0; +} + +static int src_set_pitch(void *blk, unsigned int pitch) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->mpr, MPRLH_PITCH, pitch); + ctl->dirty.bf.mpr = 1; + return 0; +} + +static int src_set_clear_zbufs(void *blk, unsigned int clear) +{ + ((struct src_rsc_ctrl_blk *)blk)->dirty.bf.czbfs = (clear ? 1 : 0); + return 0; +} + +static int src_set_dirty(void *blk, unsigned int flags) +{ + ((struct src_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff); + return 0; +} + +static int src_set_dirty_all(void *blk) +{ + ((struct src_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0); + return 0; +} + +#define AR_SLOT_SIZE 4096 +#define AR_SLOT_BLOCK_SIZE 16 +#define AR_PTS_PITCH 6 +#define AR_PARAM_SRC_OFFSET 0x60 + +static unsigned int src_param_pitch_mixer(unsigned int src_idx) +{ + return ((src_idx << 4) + AR_PTS_PITCH + AR_SLOT_SIZE + - AR_PARAM_SRC_OFFSET) % AR_SLOT_SIZE; + +} + +static int src_commit_write(struct hw *hw, unsigned int idx, void *blk) +{ + struct src_rsc_ctrl_blk *ctl = blk; + int i; + + if (ctl->dirty.bf.czbfs) { + /* Clear Z-Buffer registers */ + for (i = 0; i < 8; i++) + hw_write_20kx(hw, SRCUPZ+idx*0x100+i*0x4, 0); + + for (i = 0; i < 4; i++) + hw_write_20kx(hw, SRCDN0Z+idx*0x100+i*0x4, 0); + + for (i = 0; i < 8; i++) + hw_write_20kx(hw, SRCDN1Z+idx*0x100+i*0x4, 0); + + ctl->dirty.bf.czbfs = 0; + } + if (ctl->dirty.bf.mpr) { + /* Take the parameter mixer resource in the same group as that + * the idx src is in for simplicity. Unlike src, all conjugate + * parameter mixer resources must be programmed for + * corresponding conjugate src resources. */ + unsigned int pm_idx = src_param_pitch_mixer(idx); + hw_write_20kx(hw, PRING_LO_HI+4*pm_idx, ctl->mpr); + hw_write_20kx(hw, PMOPLO+8*pm_idx, 0x3); + hw_write_20kx(hw, PMOPHI+8*pm_idx, 0x0); + ctl->dirty.bf.mpr = 0; + } + if (ctl->dirty.bf.sa) { + hw_write_20kx(hw, SRCSA+idx*0x100, ctl->sa); + ctl->dirty.bf.sa = 0; + } + if (ctl->dirty.bf.la) { + hw_write_20kx(hw, SRCLA+idx*0x100, ctl->la); + ctl->dirty.bf.la = 0; + } + if (ctl->dirty.bf.ca) { + hw_write_20kx(hw, SRCCA+idx*0x100, ctl->ca); + ctl->dirty.bf.ca = 0; + } + + /* Write srccf register */ + hw_write_20kx(hw, SRCCF+idx*0x100, 0x0); + + if (ctl->dirty.bf.ccr) { + hw_write_20kx(hw, SRCCCR+idx*0x100, ctl->ccr); + ctl->dirty.bf.ccr = 0; + } + if (ctl->dirty.bf.ctl) { + hw_write_20kx(hw, SRCCTL+idx*0x100, ctl->ctl); + ctl->dirty.bf.ctl = 0; + } + + return 0; +} + +static int src_get_ca(struct hw *hw, unsigned int idx, void *blk) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + ctl->ca = hw_read_20kx(hw, SRCCA+idx*0x100); + ctl->dirty.bf.ca = 0; + + return get_field(ctl->ca, SRCCA_CA); +} + +static unsigned int src_get_dirty(void *blk) +{ + return ((struct src_rsc_ctrl_blk *)blk)->dirty.data; +} + +static unsigned int src_dirty_conj_mask(void) +{ + return 0x20; +} + +static int src_mgr_enbs_src(void *blk, unsigned int idx) +{ + ((struct src_mgr_ctrl_blk *)blk)->enbsa = ~(0x0); + ((struct src_mgr_ctrl_blk *)blk)->dirty.bf.enbsa = 1; + ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32)); + return 0; +} + +static int src_mgr_enb_src(void *blk, unsigned int idx) +{ + ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32)); + ((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32)); + return 0; +} + +static int src_mgr_dsb_src(void *blk, unsigned int idx) +{ + ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] &= ~(0x1 << (idx%32)); + ((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32)); + return 0; +} + +static int src_mgr_commit_write(struct hw *hw, void *blk) +{ + struct src_mgr_ctrl_blk *ctl = blk; + int i; + unsigned int ret; + + if (ctl->dirty.bf.enbsa) { + do { + ret = hw_read_20kx(hw, SRCENBSTAT); + } while (ret & 0x1); + hw_write_20kx(hw, SRCENBS, ctl->enbsa); + ctl->dirty.bf.enbsa = 0; + } + for (i = 0; i < 8; i++) { + if ((ctl->dirty.data & (0x1 << i))) { + hw_write_20kx(hw, SRCENB+(i*0x100), ctl->enb[i]); + ctl->dirty.data &= ~(0x1 << i); + } + } + + return 0; +} + +static int src_mgr_get_ctrl_blk(void **rblk) +{ + struct src_mgr_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int src_mgr_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int srcimp_mgr_get_ctrl_blk(void **rblk) +{ + struct srcimp_mgr_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int srcimp_mgr_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int srcimp_mgr_set_imaparc(void *blk, unsigned int slot) +{ + struct srcimp_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->srcimap.srcaim, SRCAIM_ARC, slot); + ctl->dirty.bf.srcimap = 1; + return 0; +} + +static int srcimp_mgr_set_imapuser(void *blk, unsigned int user) +{ + struct srcimp_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->srcimap.srcaim, SRCAIM_SRC, user); + ctl->dirty.bf.srcimap = 1; + return 0; +} + +static int srcimp_mgr_set_imapnxt(void *blk, unsigned int next) +{ + struct srcimp_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->srcimap.srcaim, SRCAIM_NXT, next); + ctl->dirty.bf.srcimap = 1; + return 0; +} + +static int srcimp_mgr_set_imapaddr(void *blk, unsigned int addr) +{ + struct srcimp_mgr_ctrl_blk *ctl = blk; + + ctl->srcimap.idx = addr; + ctl->dirty.bf.srcimap = 1; + return 0; +} + +static int srcimp_mgr_commit_write(struct hw *hw, void *blk) +{ + struct srcimp_mgr_ctrl_blk *ctl = blk; + + if (ctl->dirty.bf.srcimap) { + hw_write_20kx(hw, SRCIMAP+ctl->srcimap.idx*0x100, + ctl->srcimap.srcaim); + ctl->dirty.bf.srcimap = 0; + } + + return 0; +} + +/* + * AMIXER control block definitions. + */ + +#define AMOPLO_M 0x00000003 +#define AMOPLO_X 0x0003FFF0 +#define AMOPLO_Y 0xFFFC0000 + +#define AMOPHI_SADR 0x000000FF +#define AMOPHI_SE 0x80000000 + +/* AMIXER resource register dirty flags */ +union amixer_dirty { + struct { + u16 amoplo:1; + u16 amophi:1; + u16 rsv:14; + } bf; + u16 data; +}; + +/* AMIXER resource control block */ +struct amixer_rsc_ctrl_blk { + unsigned int amoplo; + unsigned int amophi; + union amixer_dirty dirty; +}; + +static int amixer_set_mode(void *blk, unsigned int mode) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amoplo, AMOPLO_M, mode); + ctl->dirty.bf.amoplo = 1; + return 0; +} + +static int amixer_set_iv(void *blk, unsigned int iv) +{ + /* 20k1 amixer does not have this field */ + return 0; +} + +static int amixer_set_x(void *blk, unsigned int x) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amoplo, AMOPLO_X, x); + ctl->dirty.bf.amoplo = 1; + return 0; +} + +static int amixer_set_y(void *blk, unsigned int y) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amoplo, AMOPLO_Y, y); + ctl->dirty.bf.amoplo = 1; + return 0; +} + +static int amixer_set_sadr(void *blk, unsigned int sadr) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amophi, AMOPHI_SADR, sadr); + ctl->dirty.bf.amophi = 1; + return 0; +} + +static int amixer_set_se(void *blk, unsigned int se) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amophi, AMOPHI_SE, se); + ctl->dirty.bf.amophi = 1; + return 0; +} + +static int amixer_set_dirty(void *blk, unsigned int flags) +{ + ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff); + return 0; +} + +static int amixer_set_dirty_all(void *blk) +{ + ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0); + return 0; +} + +static int amixer_commit_write(struct hw *hw, unsigned int idx, void *blk) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + if (ctl->dirty.bf.amoplo || ctl->dirty.bf.amophi) { + hw_write_20kx(hw, AMOPLO+idx*8, ctl->amoplo); + ctl->dirty.bf.amoplo = 0; + hw_write_20kx(hw, AMOPHI+idx*8, ctl->amophi); + ctl->dirty.bf.amophi = 0; + } + + return 0; +} + +static int amixer_get_y(void *blk) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + return get_field(ctl->amoplo, AMOPLO_Y); +} + +static unsigned int amixer_get_dirty(void *blk) +{ + return ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data; +} + +static int amixer_rsc_get_ctrl_blk(void **rblk) +{ + struct amixer_rsc_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int amixer_rsc_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int amixer_mgr_get_ctrl_blk(void **rblk) +{ + /*amixer_mgr_ctrl_blk_t *blk;*/ + + *rblk = NULL; + /*blk = kzalloc(sizeof(*blk), GFP_KERNEL); + if (!blk) + return -ENOMEM; + + *rblk = blk;*/ + + return 0; +} + +static int amixer_mgr_put_ctrl_blk(void *blk) +{ + /*kfree((amixer_mgr_ctrl_blk_t *)blk);*/ + + return 0; +} + +/* + * DAIO control block definitions. + */ + +/* Receiver Sample Rate Tracker Control register */ +#define SRTCTL_SRCR 0x000000FF +#define SRTCTL_SRCL 0x0000FF00 +#define SRTCTL_RSR 0x00030000 +#define SRTCTL_DRAT 0x000C0000 +#define SRTCTL_RLE 0x10000000 +#define SRTCTL_RLP 0x20000000 +#define SRTCTL_EC 0x40000000 +#define SRTCTL_ET 0x80000000 + +/* DAIO Receiver register dirty flags */ +union dai_dirty { + struct { + u16 srtctl:1; + u16 rsv:15; + } bf; + u16 data; +}; + +/* DAIO Receiver control block */ +struct dai_ctrl_blk { + unsigned int srtctl; + union dai_dirty dirty; +}; + +/* S/PDIF Transmitter register dirty flags */ +union dao_dirty { + struct { + u16 spos:1; + u16 rsv:15; + } bf; + u16 data; +}; + +/* S/PDIF Transmitter control block */ +struct dao_ctrl_blk { + unsigned int spos; /* S/PDIF Output Channel Status Register */ + union dao_dirty dirty; +}; + +/* Audio Input Mapper RAM */ +#define AIM_ARC 0x00000FFF +#define AIM_NXT 0x007F0000 + +struct daoimap { + unsigned int aim; + unsigned int idx; +}; + +/* I2S Transmitter/Receiver Control register */ +#define I2SCTL_EA 0x00000004 +#define I2SCTL_EI 0x00000010 + +/* S/PDIF Transmitter Control register */ +#define SPOCTL_OE 0x00000001 +#define SPOCTL_OS 0x0000000E +#define SPOCTL_RIV 0x00000010 +#define SPOCTL_LIV 0x00000020 +#define SPOCTL_SR 0x000000C0 + +/* S/PDIF Receiver Control register */ +#define SPICTL_EN 0x00000001 +#define SPICTL_I24 0x00000002 +#define SPICTL_IB 0x00000004 +#define SPICTL_SM 0x00000008 +#define SPICTL_VM 0x00000010 + +/* DAIO manager register dirty flags */ +union daio_mgr_dirty { + struct { + u32 i2soctl:4; + u32 i2sictl:4; + u32 spoctl:4; + u32 spictl:4; + u32 daoimap:1; + u32 rsv:15; + } bf; + u32 data; +}; + +/* DAIO manager control block */ +struct daio_mgr_ctrl_blk { + unsigned int i2sctl; + unsigned int spoctl; + unsigned int spictl; + struct daoimap daoimap; + union daio_mgr_dirty dirty; +}; + +static int dai_srt_set_srcr(void *blk, unsigned int src) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srtctl, SRTCTL_SRCR, src); + ctl->dirty.bf.srtctl = 1; + return 0; +} + +static int dai_srt_set_srcl(void *blk, unsigned int src) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srtctl, SRTCTL_SRCL, src); + ctl->dirty.bf.srtctl = 1; + return 0; +} + +static int dai_srt_set_rsr(void *blk, unsigned int rsr) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srtctl, SRTCTL_RSR, rsr); + ctl->dirty.bf.srtctl = 1; + return 0; +} + +static int dai_srt_set_drat(void *blk, unsigned int drat) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srtctl, SRTCTL_DRAT, drat); + ctl->dirty.bf.srtctl = 1; + return 0; +} + +static int dai_srt_set_ec(void *blk, unsigned int ec) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srtctl, SRTCTL_EC, ec ? 1 : 0); + ctl->dirty.bf.srtctl = 1; + return 0; +} + +static int dai_srt_set_et(void *blk, unsigned int et) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srtctl, SRTCTL_ET, et ? 1 : 0); + ctl->dirty.bf.srtctl = 1; + return 0; +} + +static int dai_commit_write(struct hw *hw, unsigned int idx, void *blk) +{ + struct dai_ctrl_blk *ctl = blk; + + if (ctl->dirty.bf.srtctl) { + if (idx < 4) { + /* S/PDIF SRTs */ + hw_write_20kx(hw, SRTSCTL+0x4*idx, ctl->srtctl); + } else { + /* I2S SRT */ + hw_write_20kx(hw, SRTICTL, ctl->srtctl); + } + ctl->dirty.bf.srtctl = 0; + } + + return 0; +} + +static int dai_get_ctrl_blk(void **rblk) +{ + struct dai_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int dai_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int dao_set_spos(void *blk, unsigned int spos) +{ + ((struct dao_ctrl_blk *)blk)->spos = spos; + ((struct dao_ctrl_blk *)blk)->dirty.bf.spos = 1; + return 0; +} + +static int dao_commit_write(struct hw *hw, unsigned int idx, void *blk) +{ + struct dao_ctrl_blk *ctl = blk; + + if (ctl->dirty.bf.spos) { + if (idx < 4) { + /* S/PDIF SPOSx */ + hw_write_20kx(hw, SPOS+0x4*idx, ctl->spos); + } + ctl->dirty.bf.spos = 0; + } + + return 0; +} + +static int dao_get_spos(void *blk, unsigned int *spos) +{ + *spos = ((struct dao_ctrl_blk *)blk)->spos; + return 0; +} + +static int dao_get_ctrl_blk(void **rblk) +{ + struct dao_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int dao_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int daio_mgr_enb_dai(void *blk, unsigned int idx) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + if (idx < 4) { + /* S/PDIF input */ + set_field(&ctl->spictl, SPICTL_EN << (idx*8), 1); + ctl->dirty.bf.spictl |= (0x1 << idx); + } else { + /* I2S input */ + idx %= 4; + set_field(&ctl->i2sctl, I2SCTL_EI << (idx*8), 1); + ctl->dirty.bf.i2sictl |= (0x1 << idx); + } + return 0; +} + +static int daio_mgr_dsb_dai(void *blk, unsigned int idx) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + if (idx < 4) { + /* S/PDIF input */ + set_field(&ctl->spictl, SPICTL_EN << (idx*8), 0); + ctl->dirty.bf.spictl |= (0x1 << idx); + } else { + /* I2S input */ + idx %= 4; + set_field(&ctl->i2sctl, I2SCTL_EI << (idx*8), 0); + ctl->dirty.bf.i2sictl |= (0x1 << idx); + } + return 0; +} + +static int daio_mgr_enb_dao(void *blk, unsigned int idx) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + if (idx < 4) { + /* S/PDIF output */ + set_field(&ctl->spoctl, SPOCTL_OE << (idx*8), 1); + ctl->dirty.bf.spoctl |= (0x1 << idx); + } else { + /* I2S output */ + idx %= 4; + set_field(&ctl->i2sctl, I2SCTL_EA << (idx*8), 1); + ctl->dirty.bf.i2soctl |= (0x1 << idx); + } + return 0; +} + +static int daio_mgr_dsb_dao(void *blk, unsigned int idx) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + if (idx < 4) { + /* S/PDIF output */ + set_field(&ctl->spoctl, SPOCTL_OE << (idx*8), 0); + ctl->dirty.bf.spoctl |= (0x1 << idx); + } else { + /* I2S output */ + idx %= 4; + set_field(&ctl->i2sctl, I2SCTL_EA << (idx*8), 0); + ctl->dirty.bf.i2soctl |= (0x1 << idx); + } + return 0; +} + +static int daio_mgr_dao_init(struct hw *hw __maybe_unused, void *blk, unsigned int idx, unsigned int conf) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + if (idx < 4) { + /* S/PDIF output */ + switch ((conf & 0x7)) { + case 0: + set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 3); + break; /* CDIF */ + case 1: + set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 0); + break; + case 2: + set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 1); + break; + case 4: + set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 2); + break; + default: + break; + } + set_field(&ctl->spoctl, SPOCTL_LIV << (idx*8), + (conf >> 4) & 0x1); /* Non-audio */ + set_field(&ctl->spoctl, SPOCTL_RIV << (idx*8), + (conf >> 4) & 0x1); /* Non-audio */ + set_field(&ctl->spoctl, SPOCTL_OS << (idx*8), + ((conf >> 3) & 0x1) ? 2 : 2); /* Raw */ + + ctl->dirty.bf.spoctl |= (0x1 << idx); + } else { + /* I2S output */ + /*idx %= 4; */ + } + return 0; +} + +static int daio_mgr_set_imaparc(void *blk, unsigned int slot) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->daoimap.aim, AIM_ARC, slot); + ctl->dirty.bf.daoimap = 1; + return 0; +} + +static int daio_mgr_set_imapnxt(void *blk, unsigned int next) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->daoimap.aim, AIM_NXT, next); + ctl->dirty.bf.daoimap = 1; + return 0; +} + +static int daio_mgr_set_imapaddr(void *blk, unsigned int addr) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + ctl->daoimap.idx = addr; + ctl->dirty.bf.daoimap = 1; + return 0; +} + +static int daio_mgr_commit_write(struct hw *hw, void *blk) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + int i; + + if (ctl->dirty.bf.i2sictl || ctl->dirty.bf.i2soctl) { + for (i = 0; i < 4; i++) { + if ((ctl->dirty.bf.i2sictl & (0x1 << i))) + ctl->dirty.bf.i2sictl &= ~(0x1 << i); + + if ((ctl->dirty.bf.i2soctl & (0x1 << i))) + ctl->dirty.bf.i2soctl &= ~(0x1 << i); + } + hw_write_20kx(hw, I2SCTL, ctl->i2sctl); + mdelay(1); + } + if (ctl->dirty.bf.spoctl) { + for (i = 0; i < 4; i++) { + if ((ctl->dirty.bf.spoctl & (0x1 << i))) + ctl->dirty.bf.spoctl &= ~(0x1 << i); + } + hw_write_20kx(hw, SPOCTL, ctl->spoctl); + mdelay(1); + } + if (ctl->dirty.bf.spictl) { + for (i = 0; i < 4; i++) { + if ((ctl->dirty.bf.spictl & (0x1 << i))) + ctl->dirty.bf.spictl &= ~(0x1 << i); + } + hw_write_20kx(hw, SPICTL, ctl->spictl); + mdelay(1); + } + if (ctl->dirty.bf.daoimap) { + hw_write_20kx(hw, DAOIMAP+ctl->daoimap.idx*4, + ctl->daoimap.aim); + ctl->dirty.bf.daoimap = 0; + } + + return 0; +} + +static int daio_mgr_get_ctrl_blk(struct hw *hw, void **rblk) +{ + struct daio_mgr_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + blk->i2sctl = hw_read_20kx(hw, I2SCTL); + blk->spoctl = hw_read_20kx(hw, SPOCTL); + blk->spictl = hw_read_20kx(hw, SPICTL); + + *rblk = blk; + + return 0; +} + +static int daio_mgr_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +/* Timer interrupt */ +static int set_timer_irq(struct hw *hw, int enable) +{ + hw_write_20kx(hw, GIE, enable ? IT_INT : 0); + return 0; +} + +static int set_timer_tick(struct hw *hw, unsigned int ticks) +{ + if (ticks) + ticks |= TIMR_IE | TIMR_IP; + hw_write_20kx(hw, TIMR, ticks); + return 0; +} + +static unsigned int get_wc(struct hw *hw) +{ + return hw_read_20kx(hw, WC); +} + +/* Card hardware initialization block */ +struct dac_conf { + unsigned int msr; /* master sample rate in rsrs */ +}; + +struct adc_conf { + unsigned int msr; /* master sample rate in rsrs */ + unsigned char input; /* the input source of ADC */ + unsigned char mic20db; /* boost mic by 20db if input is microphone */ +}; + +struct daio_conf { + unsigned int msr; /* master sample rate in rsrs */ +}; + +struct trn_conf { + unsigned long vm_pgt_phys; +}; + +static int hw_daio_init(struct hw *hw, const struct daio_conf *info) +{ + u32 i2sorg; + u32 spdorg; + + /* Read I2S CTL. Keep original value. */ + /*i2sorg = hw_read_20kx(hw, I2SCTL);*/ + i2sorg = 0x94040404; /* enable all audio out and I2S-D input */ + /* Program I2S with proper master sample rate and enable + * the correct I2S channel. */ + i2sorg &= 0xfffffffc; + + /* Enable S/PDIF-out-A in fixed 24-bit data + * format and default to 48kHz. */ + /* Disable all before doing any changes. */ + hw_write_20kx(hw, SPOCTL, 0x0); + spdorg = 0x05; + + switch (info->msr) { + case 1: + i2sorg |= 1; + spdorg |= (0x0 << 6); + break; + case 2: + i2sorg |= 2; + spdorg |= (0x1 << 6); + break; + case 4: + i2sorg |= 3; + spdorg |= (0x2 << 6); + break; + default: + i2sorg |= 1; + break; + } + + hw_write_20kx(hw, I2SCTL, i2sorg); + hw_write_20kx(hw, SPOCTL, spdorg); + + /* Enable S/PDIF-in-A in fixed 24-bit data format. */ + /* Disable all before doing any changes. */ + hw_write_20kx(hw, SPICTL, 0x0); + mdelay(1); + spdorg = 0x0a0a0a0a; + hw_write_20kx(hw, SPICTL, spdorg); + mdelay(1); + + return 0; +} + +/* TRANSPORT operations */ +static int hw_trn_init(struct hw *hw, const struct trn_conf *info) +{ + u32 trnctl; + u32 ptp_phys_low, ptp_phys_high; + + /* Set up device page table */ + if ((~0UL) == info->vm_pgt_phys) { + dev_err(hw->card->dev, + "Wrong device page table page address!\n"); + return -1; + } + + trnctl = 0x13; /* 32-bit, 4k-size page */ + ptp_phys_low = (u32)info->vm_pgt_phys; + ptp_phys_high = upper_32_bits(info->vm_pgt_phys); + if (sizeof(void *) == 8) /* 64bit address */ + trnctl |= (1 << 2); +#if 0 /* Only 4k h/w pages for simplicitiy */ +#if PAGE_SIZE == 8192 + trnctl |= (1<<5); +#endif +#endif + hw_write_20kx(hw, PTPALX, ptp_phys_low); + hw_write_20kx(hw, PTPAHX, ptp_phys_high); + hw_write_20kx(hw, TRNCTL, trnctl); + hw_write_20kx(hw, TRNIS, 0x200c01); /* really needed? */ + + return 0; +} + +/* Card initialization */ +#define GCTL_EAC 0x00000001 +#define GCTL_EAI 0x00000002 +#define GCTL_BEP 0x00000004 +#define GCTL_BES 0x00000008 +#define GCTL_DSP 0x00000010 +#define GCTL_DBP 0x00000020 +#define GCTL_ABP 0x00000040 +#define GCTL_TBP 0x00000080 +#define GCTL_SBP 0x00000100 +#define GCTL_FBP 0x00000200 +#define GCTL_XA 0x00000400 +#define GCTL_ET 0x00000800 +#define GCTL_PR 0x00001000 +#define GCTL_MRL 0x00002000 +#define GCTL_SDE 0x00004000 +#define GCTL_SDI 0x00008000 +#define GCTL_SM 0x00010000 +#define GCTL_SR 0x00020000 +#define GCTL_SD 0x00040000 +#define GCTL_SE 0x00080000 +#define GCTL_AID 0x00100000 + +static int hw_pll_init(struct hw *hw, unsigned int rsr) +{ + unsigned int pllctl; + int i; + + pllctl = (48000 == rsr) ? 0x1480a001 : 0x1480a731; + for (i = 0; i < 3; i++) { + if (hw_read_20kx(hw, PLLCTL) == pllctl) + break; + + hw_write_20kx(hw, PLLCTL, pllctl); + msleep(40); + } + if (i >= 3) { + dev_alert(hw->card->dev, "PLL initialization failed!!!\n"); + return -EBUSY; + } + + return 0; +} + +static int hw_auto_init(struct hw *hw) +{ + unsigned int gctl; + int i; + + gctl = hw_read_20kx(hw, GCTL); + set_field(&gctl, GCTL_EAI, 0); + hw_write_20kx(hw, GCTL, gctl); + set_field(&gctl, GCTL_EAI, 1); + hw_write_20kx(hw, GCTL, gctl); + mdelay(10); + for (i = 0; i < 400000; i++) { + gctl = hw_read_20kx(hw, GCTL); + if (get_field(gctl, GCTL_AID)) + break; + } + if (!get_field(gctl, GCTL_AID)) { + dev_alert(hw->card->dev, "Card Auto-init failed!!!\n"); + return -EBUSY; + } + + return 0; +} + +static int i2c_unlock(struct hw *hw) +{ + if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa) + return 0; + + hw_write_pci(hw, 0xcc, 0x8c); + hw_write_pci(hw, 0xcc, 0x0e); + if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa) + return 0; + + hw_write_pci(hw, 0xcc, 0xee); + hw_write_pci(hw, 0xcc, 0xaa); + if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa) + return 0; + + return -1; +} + +static void i2c_lock(struct hw *hw) +{ + if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa) + hw_write_pci(hw, 0xcc, 0x00); +} + +static void i2c_write(struct hw *hw, u32 device, u32 addr, u32 data) +{ + unsigned int ret; + + do { + ret = hw_read_pci(hw, 0xEC); + } while (!(ret & 0x800000)); + hw_write_pci(hw, 0xE0, device); + hw_write_pci(hw, 0xE4, (data << 8) | (addr & 0xff)); +} + +/* DAC operations */ + +static int hw_reset_dac(struct hw *hw) +{ + u32 i; + u16 gpioorg; + unsigned int ret; + + if (i2c_unlock(hw)) + return -1; + + do { + ret = hw_read_pci(hw, 0xEC); + } while (!(ret & 0x800000)); + hw_write_pci(hw, 0xEC, 0x05); /* write to i2c status control */ + + /* To be effective, need to reset the DAC twice. */ + for (i = 0; i < 2; i++) { + /* set gpio */ + msleep(100); + gpioorg = (u16)hw_read_20kx(hw, GPIO); + gpioorg &= 0xfffd; + hw_write_20kx(hw, GPIO, gpioorg); + mdelay(1); + hw_write_20kx(hw, GPIO, gpioorg | 0x2); + } + + i2c_write(hw, 0x00180080, 0x01, 0x80); + i2c_write(hw, 0x00180080, 0x02, 0x10); + + i2c_lock(hw); + + return 0; +} + +static int hw_dac_init(struct hw *hw, const struct dac_conf *info) +{ + u32 data; + u16 gpioorg; + unsigned int ret; + + if (hw->model == CTSB055X) { + /* SB055x, unmute outputs */ + gpioorg = (u16)hw_read_20kx(hw, GPIO); + gpioorg &= 0xffbf; /* set GPIO6 to low */ + gpioorg |= 2; /* set GPIO1 to high */ + hw_write_20kx(hw, GPIO, gpioorg); + return 0; + } + + /* mute outputs */ + gpioorg = (u16)hw_read_20kx(hw, GPIO); + gpioorg &= 0xffbf; + hw_write_20kx(hw, GPIO, gpioorg); + + hw_reset_dac(hw); + + if (i2c_unlock(hw)) + return -1; + + hw_write_pci(hw, 0xEC, 0x05); /* write to i2c status control */ + do { + ret = hw_read_pci(hw, 0xEC); + } while (!(ret & 0x800000)); + + switch (info->msr) { + case 1: + data = 0x24; + break; + case 2: + data = 0x25; + break; + case 4: + data = 0x26; + break; + default: + data = 0x24; + break; + } + + i2c_write(hw, 0x00180080, 0x06, data); + i2c_write(hw, 0x00180080, 0x09, data); + i2c_write(hw, 0x00180080, 0x0c, data); + i2c_write(hw, 0x00180080, 0x0f, data); + + i2c_lock(hw); + + /* unmute outputs */ + gpioorg = (u16)hw_read_20kx(hw, GPIO); + gpioorg = gpioorg | 0x40; + hw_write_20kx(hw, GPIO, gpioorg); + + return 0; +} + +/* ADC operations */ + +static int is_adc_input_selected_SB055x(struct hw *hw, enum ADCSRC type) +{ + return 0; +} + +static int is_adc_input_selected_SBx(struct hw *hw, enum ADCSRC type) +{ + u32 data; + + data = hw_read_20kx(hw, GPIO); + switch (type) { + case ADC_MICIN: + data = ((data & (0x1<<7)) && (data & (0x1<<8))); + break; + case ADC_LINEIN: + data = (!(data & (0x1<<7)) && (data & (0x1<<8))); + break; + case ADC_NONE: /* Digital I/O */ + data = (!(data & (0x1<<8))); + break; + default: + data = 0; + } + return data; +} + +static int is_adc_input_selected_hendrix(struct hw *hw, enum ADCSRC type) +{ + u32 data; + + data = hw_read_20kx(hw, GPIO); + switch (type) { + case ADC_MICIN: + data = (data & (0x1 << 7)) ? 1 : 0; + break; + case ADC_LINEIN: + data = (data & (0x1 << 7)) ? 0 : 1; + break; + default: + data = 0; + } + return data; +} + +static int hw_is_adc_input_selected(struct hw *hw, enum ADCSRC type) +{ + switch (hw->model) { + case CTSB055X: + return is_adc_input_selected_SB055x(hw, type); + case CTSB073X: + return is_adc_input_selected_hendrix(hw, type); + case CTUAA: + return is_adc_input_selected_hendrix(hw, type); + default: + return is_adc_input_selected_SBx(hw, type); + } +} + +static int +adc_input_select_SB055x(struct hw *hw, enum ADCSRC type, unsigned char boost) +{ + u32 data; + + /* + * check and set the following GPIO bits accordingly + * ADC_Gain = GPIO2 + * DRM_off = GPIO3 + * Mic_Pwr_on = GPIO7 + * Digital_IO_Sel = GPIO8 + * Mic_Sw = GPIO9 + * Aux/MicLine_Sw = GPIO12 + */ + data = hw_read_20kx(hw, GPIO); + data &= 0xec73; + switch (type) { + case ADC_MICIN: + data |= (0x1<<7) | (0x1<<8) | (0x1<<9) ; + data |= boost ? (0x1<<2) : 0; + break; + case ADC_LINEIN: + data |= (0x1<<8); + break; + case ADC_AUX: + data |= (0x1<<8) | (0x1<<12); + break; + case ADC_NONE: + data |= (0x1<<12); /* set to digital */ + break; + default: + return -1; + } + + hw_write_20kx(hw, GPIO, data); + + return 0; +} + + +static int +adc_input_select_SBx(struct hw *hw, enum ADCSRC type, unsigned char boost) +{ + u32 data; + u32 i2c_data; + unsigned int ret; + + if (i2c_unlock(hw)) + return -1; + + do { + ret = hw_read_pci(hw, 0xEC); + } while (!(ret & 0x800000)); /* i2c ready poll */ + /* set i2c access mode as Direct Control */ + hw_write_pci(hw, 0xEC, 0x05); + + data = hw_read_20kx(hw, GPIO); + switch (type) { + case ADC_MICIN: + data |= ((0x1 << 7) | (0x1 << 8)); + i2c_data = 0x1; /* Mic-in */ + break; + case ADC_LINEIN: + data &= ~(0x1 << 7); + data |= (0x1 << 8); + i2c_data = 0x2; /* Line-in */ + break; + case ADC_NONE: + data &= ~(0x1 << 8); + i2c_data = 0x0; /* set to Digital */ + break; + default: + i2c_lock(hw); + return -1; + } + hw_write_20kx(hw, GPIO, data); + i2c_write(hw, 0x001a0080, 0x2a, i2c_data); + if (boost) { + i2c_write(hw, 0x001a0080, 0x1c, 0xe7); /* +12dB boost */ + i2c_write(hw, 0x001a0080, 0x1e, 0xe7); /* +12dB boost */ + } else { + i2c_write(hw, 0x001a0080, 0x1c, 0xcf); /* No boost */ + i2c_write(hw, 0x001a0080, 0x1e, 0xcf); /* No boost */ + } + + i2c_lock(hw); + + return 0; +} + +static int +adc_input_select_hendrix(struct hw *hw, enum ADCSRC type, unsigned char boost) +{ + u32 data; + u32 i2c_data; + unsigned int ret; + + if (i2c_unlock(hw)) + return -1; + + do { + ret = hw_read_pci(hw, 0xEC); + } while (!(ret & 0x800000)); /* i2c ready poll */ + /* set i2c access mode as Direct Control */ + hw_write_pci(hw, 0xEC, 0x05); + + data = hw_read_20kx(hw, GPIO); + switch (type) { + case ADC_MICIN: + data |= (0x1 << 7); + i2c_data = 0x1; /* Mic-in */ + break; + case ADC_LINEIN: + data &= ~(0x1 << 7); + i2c_data = 0x2; /* Line-in */ + break; + default: + i2c_lock(hw); + return -1; + } + hw_write_20kx(hw, GPIO, data); + i2c_write(hw, 0x001a0080, 0x2a, i2c_data); + if (boost) { + i2c_write(hw, 0x001a0080, 0x1c, 0xe7); /* +12dB boost */ + i2c_write(hw, 0x001a0080, 0x1e, 0xe7); /* +12dB boost */ + } else { + i2c_write(hw, 0x001a0080, 0x1c, 0xcf); /* No boost */ + i2c_write(hw, 0x001a0080, 0x1e, 0xcf); /* No boost */ + } + + i2c_lock(hw); + + return 0; +} + +static int hw_adc_input_select(struct hw *hw, enum ADCSRC type) +{ + int state = type == ADC_MICIN; + + switch (hw->model) { + case CTSB055X: + return adc_input_select_SB055x(hw, type, state); + case CTSB073X: + return adc_input_select_hendrix(hw, type, state); + case CTUAA: + return adc_input_select_hendrix(hw, type, state); + default: + return adc_input_select_SBx(hw, type, state); + } +} + +static int adc_init_SB055x(struct hw *hw, int input, int mic20db) +{ + return adc_input_select_SB055x(hw, input, mic20db); +} + +static int adc_init_SBx(struct hw *hw, int input, int mic20db) +{ + u16 gpioorg; + u16 input_source; + u32 adcdata; + unsigned int ret; + + input_source = 0x100; /* default to analog */ + switch (input) { + case ADC_MICIN: + adcdata = 0x1; + input_source = 0x180; /* set GPIO7 to select Mic */ + break; + case ADC_LINEIN: + adcdata = 0x2; + break; + case ADC_VIDEO: + adcdata = 0x4; + break; + case ADC_AUX: + adcdata = 0x8; + break; + case ADC_NONE: + adcdata = 0x0; + input_source = 0x0; /* set to Digital */ + break; + default: + adcdata = 0x0; + break; + } + + if (i2c_unlock(hw)) + return -1; + + do { + ret = hw_read_pci(hw, 0xEC); + } while (!(ret & 0x800000)); /* i2c ready poll */ + hw_write_pci(hw, 0xEC, 0x05); /* write to i2c status control */ + + i2c_write(hw, 0x001a0080, 0x0e, 0x08); + i2c_write(hw, 0x001a0080, 0x18, 0x0a); + i2c_write(hw, 0x001a0080, 0x28, 0x86); + i2c_write(hw, 0x001a0080, 0x2a, adcdata); + + if (mic20db) { + i2c_write(hw, 0x001a0080, 0x1c, 0xf7); + i2c_write(hw, 0x001a0080, 0x1e, 0xf7); + } else { + i2c_write(hw, 0x001a0080, 0x1c, 0xcf); + i2c_write(hw, 0x001a0080, 0x1e, 0xcf); + } + + if (!(hw_read_20kx(hw, ID0) & 0x100)) + i2c_write(hw, 0x001a0080, 0x16, 0x26); + + i2c_lock(hw); + + gpioorg = (u16)hw_read_20kx(hw, GPIO); + gpioorg &= 0xfe7f; + gpioorg |= input_source; + hw_write_20kx(hw, GPIO, gpioorg); + + return 0; +} + +static int hw_adc_init(struct hw *hw, const struct adc_conf *info) +{ + if (hw->model == CTSB055X) + return adc_init_SB055x(hw, info->input, info->mic20db); + else + return adc_init_SBx(hw, info->input, info->mic20db); +} + +static struct capabilities hw_capabilities(struct hw *hw) +{ + struct capabilities cap; + + /* SB073x and Vista compatible cards have no digit IO switch */ + cap.digit_io_switch = !(hw->model == CTSB073X || hw->model == CTUAA); + cap.dedicated_mic = 0; + cap.dedicated_rca = 0; + cap.output_switch = 0; + cap.mic_source_switch = 0; + + return cap; +} + +#define CTLBITS(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d)) + +#define UAA_CFG_PWRSTATUS 0x44 +#define UAA_CFG_SPACE_FLAG 0xA0 +#define UAA_CORE_CHANGE 0x3FFC +static int uaa_to_xfi(struct pci_dev *pci) +{ + unsigned int bar0, bar1, bar2, bar3, bar4, bar5; + unsigned int cmd, irq, cl_size, l_timer, pwr; + unsigned int is_uaa; + unsigned int data[4] = {0}; + unsigned int io_base; + void __iomem *mem_base; + int i; + const u32 CTLX = CTLBITS('C', 'T', 'L', 'X'); + const u32 CTL_ = CTLBITS('C', 'T', 'L', '-'); + const u32 CTLF = CTLBITS('C', 'T', 'L', 'F'); + const u32 CTLi = CTLBITS('C', 'T', 'L', 'i'); + const u32 CTLA = CTLBITS('C', 'T', 'L', 'A'); + const u32 CTLZ = CTLBITS('C', 'T', 'L', 'Z'); + const u32 CTLL = CTLBITS('C', 'T', 'L', 'L'); + + /* By default, Hendrix card UAA Bar0 should be using memory... */ + io_base = pci_resource_start(pci, 0); + mem_base = ioremap(io_base, pci_resource_len(pci, 0)); + if (!mem_base) + return -ENOENT; + + /* Read current mode from Mode Change Register */ + for (i = 0; i < 4; i++) + data[i] = readl(mem_base + UAA_CORE_CHANGE); + + /* Determine current mode... */ + if (data[0] == CTLA) { + is_uaa = ((data[1] == CTLZ && data[2] == CTLL + && data[3] == CTLA) || (data[1] == CTLA + && data[2] == CTLZ && data[3] == CTLL)); + } else if (data[0] == CTLZ) { + is_uaa = (data[1] == CTLL + && data[2] == CTLA && data[3] == CTLA); + } else if (data[0] == CTLL) { + is_uaa = (data[1] == CTLA + && data[2] == CTLA && data[3] == CTLZ); + } else { + is_uaa = 0; + } + + if (!is_uaa) { + /* Not in UAA mode currently. Return directly. */ + iounmap(mem_base); + return 0; + } + + pci_read_config_dword(pci, PCI_BASE_ADDRESS_0, &bar0); + pci_read_config_dword(pci, PCI_BASE_ADDRESS_1, &bar1); + pci_read_config_dword(pci, PCI_BASE_ADDRESS_2, &bar2); + pci_read_config_dword(pci, PCI_BASE_ADDRESS_3, &bar3); + pci_read_config_dword(pci, PCI_BASE_ADDRESS_4, &bar4); + pci_read_config_dword(pci, PCI_BASE_ADDRESS_5, &bar5); + pci_read_config_dword(pci, PCI_INTERRUPT_LINE, &irq); + pci_read_config_dword(pci, PCI_CACHE_LINE_SIZE, &cl_size); + pci_read_config_dword(pci, PCI_LATENCY_TIMER, &l_timer); + pci_read_config_dword(pci, UAA_CFG_PWRSTATUS, &pwr); + pci_read_config_dword(pci, PCI_COMMAND, &cmd); + + /* Set up X-Fi core PCI configuration space. */ + /* Switch to X-Fi config space with BAR0 exposed. */ + pci_write_config_dword(pci, UAA_CFG_SPACE_FLAG, 0x87654321); + /* Copy UAA's BAR5 into X-Fi BAR0 */ + pci_write_config_dword(pci, PCI_BASE_ADDRESS_0, bar5); + /* Switch to X-Fi config space without BAR0 exposed. */ + pci_write_config_dword(pci, UAA_CFG_SPACE_FLAG, 0x12345678); + pci_write_config_dword(pci, PCI_BASE_ADDRESS_1, bar1); + pci_write_config_dword(pci, PCI_BASE_ADDRESS_2, bar2); + pci_write_config_dword(pci, PCI_BASE_ADDRESS_3, bar3); + pci_write_config_dword(pci, PCI_BASE_ADDRESS_4, bar4); + pci_write_config_dword(pci, PCI_INTERRUPT_LINE, irq); + pci_write_config_dword(pci, PCI_CACHE_LINE_SIZE, cl_size); + pci_write_config_dword(pci, PCI_LATENCY_TIMER, l_timer); + pci_write_config_dword(pci, UAA_CFG_PWRSTATUS, pwr); + pci_write_config_dword(pci, PCI_COMMAND, cmd); + + /* Switch to X-Fi mode */ + writel(CTLX, (mem_base + UAA_CORE_CHANGE)); + writel(CTL_, (mem_base + UAA_CORE_CHANGE)); + writel(CTLF, (mem_base + UAA_CORE_CHANGE)); + writel(CTLi, (mem_base + UAA_CORE_CHANGE)); + + iounmap(mem_base); + + return 0; +} + +static irqreturn_t ct_20k1_interrupt(int irq, void *dev_id) +{ + struct hw *hw = dev_id; + unsigned int status; + + status = hw_read_20kx(hw, GIP); + if (!status) + return IRQ_NONE; + + if (hw->irq_callback) + hw->irq_callback(hw->irq_callback_data, status); + + hw_write_20kx(hw, GIP, status); + return IRQ_HANDLED; +} + +static int hw_card_start(struct hw *hw) +{ + int err; + struct pci_dev *pci = hw->pci; + const unsigned int dma_bits = BITS_PER_LONG; + + err = pci_enable_device(pci); + if (err < 0) + return err; + + /* Set DMA transfer mask */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(dma_bits))) + dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32)); + + if (!hw->io_base) { + err = pci_request_regions(pci, "XFi"); + if (err < 0) + goto error1; + + if (hw->model == CTUAA) + hw->io_base = pci_resource_start(pci, 5); + else + hw->io_base = pci_resource_start(pci, 0); + + } + + /* Switch to X-Fi mode from UAA mode if needed */ + if (hw->model == CTUAA) { + err = uaa_to_xfi(pci); + if (err) + goto error2; + + } + + if (hw->irq < 0) { + err = request_irq(pci->irq, ct_20k1_interrupt, IRQF_SHARED, + KBUILD_MODNAME, hw); + if (err < 0) { + dev_err(hw->card->dev, + "XFi: Cannot get irq %d\n", pci->irq); + goto error2; + } + hw->irq = pci->irq; + hw->card->sync_irq = hw->irq; + } + + pci_set_master(pci); + + return 0; + +error2: + pci_release_regions(pci); + hw->io_base = 0; +error1: + pci_disable_device(pci); + return err; +} + +static int hw_card_stop(struct hw *hw) +{ + unsigned int data; + + /* disable transport bus master and queueing of request */ + hw_write_20kx(hw, TRNCTL, 0x00); + + /* disable pll */ + data = hw_read_20kx(hw, PLLCTL); + hw_write_20kx(hw, PLLCTL, (data & (~(0x0F<<12)))); + + return 0; +} + +static int hw_card_shutdown(struct hw *hw) +{ + if (hw->irq >= 0) + free_irq(hw->irq, hw); + + hw->irq = -1; + iounmap(hw->mem_base); + hw->mem_base = NULL; + + if (hw->io_base) + pci_release_regions(hw->pci); + + hw->io_base = 0; + + pci_disable_device(hw->pci); + + return 0; +} + +static int hw_card_init(struct hw *hw, struct card_conf *info) +{ + int err; + unsigned int gctl; + u32 data; + struct dac_conf dac_info = {0}; + struct adc_conf adc_info = {0}; + struct daio_conf daio_info = {0}; + struct trn_conf trn_info = {0}; + + /* Get PCI io port base address and do Hendrix switch if needed. */ + err = hw_card_start(hw); + if (err) + return err; + + /* PLL init */ + err = hw_pll_init(hw, info->rsr); + if (err < 0) + return err; + + /* kick off auto-init */ + err = hw_auto_init(hw); + if (err < 0) + return err; + + /* Enable audio ring */ + gctl = hw_read_20kx(hw, GCTL); + set_field(&gctl, GCTL_EAC, 1); + set_field(&gctl, GCTL_DBP, 1); + set_field(&gctl, GCTL_TBP, 1); + set_field(&gctl, GCTL_FBP, 1); + set_field(&gctl, GCTL_ET, 1); + hw_write_20kx(hw, GCTL, gctl); + mdelay(10); + + /* Reset all global pending interrupts */ + hw_write_20kx(hw, GIE, 0); + /* Reset all SRC pending interrupts */ + hw_write_20kx(hw, SRCIP, 0); + msleep(30); + + /* Detect the card ID and configure GPIO accordingly. */ + switch (hw->model) { + case CTSB055X: + hw_write_20kx(hw, GPIOCTL, 0x13fe); + break; + case CTSB073X: + hw_write_20kx(hw, GPIOCTL, 0x00e6); + break; + case CTUAA: + hw_write_20kx(hw, GPIOCTL, 0x00c2); + break; + default: + hw_write_20kx(hw, GPIOCTL, 0x01e6); + break; + } + + trn_info.vm_pgt_phys = info->vm_pgt_phys; + err = hw_trn_init(hw, &trn_info); + if (err < 0) + return err; + + daio_info.msr = info->msr; + err = hw_daio_init(hw, &daio_info); + if (err < 0) + return err; + + dac_info.msr = info->msr; + err = hw_dac_init(hw, &dac_info); + if (err < 0) + return err; + + adc_info.msr = info->msr; + adc_info.input = ADC_LINEIN; + adc_info.mic20db = 0; + err = hw_adc_init(hw, &adc_info); + if (err < 0) + return err; + + data = hw_read_20kx(hw, SRCMCTL); + data |= 0x1; /* Enables input from the audio ring */ + hw_write_20kx(hw, SRCMCTL, data); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int hw_suspend(struct hw *hw) +{ + struct pci_dev *pci = hw->pci; + + hw_card_stop(hw); + + if (hw->model == CTUAA) { + /* Switch to UAA config space. */ + pci_write_config_dword(pci, UAA_CFG_SPACE_FLAG, 0x0); + } + + return 0; +} + +static int hw_resume(struct hw *hw, struct card_conf *info) +{ + /* Re-initialize card hardware. */ + return hw_card_init(hw, info); +} +#endif + +static u32 hw_read_20kx(struct hw *hw, u32 reg) +{ + guard(spinlock_irqsave)(&container_of(hw, struct hw20k1, hw)->reg_20k1_lock); + outl(reg, hw->io_base + 0x0); + return inl(hw->io_base + 0x4); +} + +static void hw_write_20kx(struct hw *hw, u32 reg, u32 data) +{ + guard(spinlock_irqsave)(&container_of(hw, struct hw20k1, hw)->reg_20k1_lock); + outl(reg, hw->io_base + 0x0); + outl(data, hw->io_base + 0x4); +} + +static u32 hw_read_pci(struct hw *hw, u32 reg) +{ + guard(spinlock_irqsave)(&container_of(hw, struct hw20k1, hw)->reg_pci_lock); + outl(reg, hw->io_base + 0x10); + return inl(hw->io_base + 0x14); +} + +static void hw_write_pci(struct hw *hw, u32 reg, u32 data) +{ + guard(spinlock_irqsave)(&container_of(hw, struct hw20k1, hw)->reg_pci_lock); + outl(reg, hw->io_base + 0x10); + outl(data, hw->io_base + 0x14); +} + +static const struct hw ct20k1_preset = { + .irq = -1, + + .card_init = hw_card_init, + .card_stop = hw_card_stop, + .pll_init = hw_pll_init, + .is_adc_source_selected = hw_is_adc_input_selected, + .select_adc_source = hw_adc_input_select, + .capabilities = hw_capabilities, +#ifdef CONFIG_PM_SLEEP + .suspend = hw_suspend, + .resume = hw_resume, +#endif + + .src_rsc_get_ctrl_blk = src_get_rsc_ctrl_blk, + .src_rsc_put_ctrl_blk = src_put_rsc_ctrl_blk, + .src_mgr_get_ctrl_blk = src_mgr_get_ctrl_blk, + .src_mgr_put_ctrl_blk = src_mgr_put_ctrl_blk, + .src_set_state = src_set_state, + .src_set_bm = src_set_bm, + .src_set_rsr = src_set_rsr, + .src_set_sf = src_set_sf, + .src_set_wr = src_set_wr, + .src_set_pm = src_set_pm, + .src_set_rom = src_set_rom, + .src_set_vo = src_set_vo, + .src_set_st = src_set_st, + .src_set_ie = src_set_ie, + .src_set_ilsz = src_set_ilsz, + .src_set_bp = src_set_bp, + .src_set_cisz = src_set_cisz, + .src_set_ca = src_set_ca, + .src_set_sa = src_set_sa, + .src_set_la = src_set_la, + .src_set_pitch = src_set_pitch, + .src_set_dirty = src_set_dirty, + .src_set_clear_zbufs = src_set_clear_zbufs, + .src_set_dirty_all = src_set_dirty_all, + .src_commit_write = src_commit_write, + .src_get_ca = src_get_ca, + .src_get_dirty = src_get_dirty, + .src_dirty_conj_mask = src_dirty_conj_mask, + .src_mgr_enbs_src = src_mgr_enbs_src, + .src_mgr_enb_src = src_mgr_enb_src, + .src_mgr_dsb_src = src_mgr_dsb_src, + .src_mgr_commit_write = src_mgr_commit_write, + + .srcimp_mgr_get_ctrl_blk = srcimp_mgr_get_ctrl_blk, + .srcimp_mgr_put_ctrl_blk = srcimp_mgr_put_ctrl_blk, + .srcimp_mgr_set_imaparc = srcimp_mgr_set_imaparc, + .srcimp_mgr_set_imapuser = srcimp_mgr_set_imapuser, + .srcimp_mgr_set_imapnxt = srcimp_mgr_set_imapnxt, + .srcimp_mgr_set_imapaddr = srcimp_mgr_set_imapaddr, + .srcimp_mgr_commit_write = srcimp_mgr_commit_write, + + .amixer_rsc_get_ctrl_blk = amixer_rsc_get_ctrl_blk, + .amixer_rsc_put_ctrl_blk = amixer_rsc_put_ctrl_blk, + .amixer_mgr_get_ctrl_blk = amixer_mgr_get_ctrl_blk, + .amixer_mgr_put_ctrl_blk = amixer_mgr_put_ctrl_blk, + .amixer_set_mode = amixer_set_mode, + .amixer_set_iv = amixer_set_iv, + .amixer_set_x = amixer_set_x, + .amixer_set_y = amixer_set_y, + .amixer_set_sadr = amixer_set_sadr, + .amixer_set_se = amixer_set_se, + .amixer_set_dirty = amixer_set_dirty, + .amixer_set_dirty_all = amixer_set_dirty_all, + .amixer_commit_write = amixer_commit_write, + .amixer_get_y = amixer_get_y, + .amixer_get_dirty = amixer_get_dirty, + + .dai_get_ctrl_blk = dai_get_ctrl_blk, + .dai_put_ctrl_blk = dai_put_ctrl_blk, + .dai_srt_set_srco = dai_srt_set_srcr, + .dai_srt_set_srcm = dai_srt_set_srcl, + .dai_srt_set_rsr = dai_srt_set_rsr, + .dai_srt_set_drat = dai_srt_set_drat, + .dai_srt_set_ec = dai_srt_set_ec, + .dai_srt_set_et = dai_srt_set_et, + .dai_commit_write = dai_commit_write, + + .dao_get_ctrl_blk = dao_get_ctrl_blk, + .dao_put_ctrl_blk = dao_put_ctrl_blk, + .dao_set_spos = dao_set_spos, + .dao_commit_write = dao_commit_write, + .dao_get_spos = dao_get_spos, + + .daio_mgr_get_ctrl_blk = daio_mgr_get_ctrl_blk, + .daio_mgr_put_ctrl_blk = daio_mgr_put_ctrl_blk, + .daio_mgr_enb_dai = daio_mgr_enb_dai, + .daio_mgr_dsb_dai = daio_mgr_dsb_dai, + .daio_mgr_enb_dao = daio_mgr_enb_dao, + .daio_mgr_dsb_dao = daio_mgr_dsb_dao, + .daio_mgr_dao_init = daio_mgr_dao_init, + .daio_mgr_set_imaparc = daio_mgr_set_imaparc, + .daio_mgr_set_imapnxt = daio_mgr_set_imapnxt, + .daio_mgr_set_imapaddr = daio_mgr_set_imapaddr, + .daio_mgr_commit_write = daio_mgr_commit_write, + + .set_timer_irq = set_timer_irq, + .set_timer_tick = set_timer_tick, + .get_wc = get_wc, +}; + +int create_20k1_hw_obj(struct hw **rhw) +{ + struct hw20k1 *hw20k1; + + *rhw = NULL; + hw20k1 = kzalloc_obj(*hw20k1); + if (!hw20k1) + return -ENOMEM; + + spin_lock_init(&hw20k1->reg_20k1_lock); + spin_lock_init(&hw20k1->reg_pci_lock); + + hw20k1->hw = ct20k1_preset; + + *rhw = &hw20k1->hw; + + return 0; +} + +int destroy_20k1_hw_obj(struct hw *hw) +{ + if (hw->io_base) + hw_card_shutdown(hw); + + kfree(container_of(hw, struct hw20k1, hw)); + return 0; +} diff --git a/sound/pci/ctxfi/cthw20k1.h b/sound/pci/ctxfi/cthw20k1.h new file mode 100644 index 000000000..ffb019abf --- /dev/null +++ b/sound/pci/ctxfi/cthw20k1.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File cthw20k1.h + * + * @Brief + * This file contains the definition of hardware access methord. + * + * @Author Liu Chun + * @Date May 13 2008 + */ + +#ifndef CTHW20K1_H +#define CTHW20K1_H + +#include "cthardware.h" + +int create_20k1_hw_obj(struct hw **rhw); +int destroy_20k1_hw_obj(struct hw *hw); + +#endif /* CTHW20K1_H */ diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c new file mode 100644 index 000000000..07e1490a6 --- /dev/null +++ b/sound/pci/ctxfi/cthw20k2.c @@ -0,0 +1,2389 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File cthw20k2.c + * + * @Brief + * This file contains the implementation of hardware access method for 20k2. + * + * @Author Liu Chun + * @Date May 14 2008 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "cthw20k2.h" +#include "ct20k2reg.h" + +struct hw20k2 { + struct hw hw; + /* for i2c */ + unsigned char dev_id; + unsigned char addr_size; + unsigned char data_size; + + int mic_source; +}; + +static u32 hw_read_20kx(struct hw *hw, u32 reg); +static void hw_write_20kx(struct hw *hw, u32 reg, u32 data); + +/* + * Type definition block. + * The layout of control structures can be directly applied on 20k2 chip. + */ + +/* + * SRC control block definitions. + */ + +/* SRC resource control block */ +#define SRCCTL_STATE 0x00000007 +#define SRCCTL_BM 0x00000008 +#define SRCCTL_RSR 0x00000030 +#define SRCCTL_SF 0x000001C0 +#define SRCCTL_WR 0x00000200 +#define SRCCTL_PM 0x00000400 +#define SRCCTL_ROM 0x00001800 +#define SRCCTL_VO 0x00002000 +#define SRCCTL_ST 0x00004000 +#define SRCCTL_IE 0x00008000 +#define SRCCTL_ILSZ 0x000F0000 +#define SRCCTL_BP 0x00100000 + +#define SRCCCR_CISZ 0x000007FF +#define SRCCCR_CWA 0x001FF800 +#define SRCCCR_D 0x00200000 +#define SRCCCR_RS 0x01C00000 +#define SRCCCR_NAL 0x3E000000 +#define SRCCCR_RA 0xC0000000 + +#define SRCCA_CA 0x0FFFFFFF +#define SRCCA_RS 0xE0000000 + +#define SRCSA_SA 0x0FFFFFFF + +#define SRCLA_LA 0x0FFFFFFF + +/* Mixer Parameter Ring ram Low and Hight register. + * Fixed-point value in 8.24 format for parameter channel */ +#define MPRLH_PITCH 0xFFFFFFFF + +/* SRC resource register dirty flags */ +union src_dirty { + struct { + u16 ctl:1; + u16 ccr:1; + u16 sa:1; + u16 la:1; + u16 ca:1; + u16 mpr:1; + u16 czbfs:1; /* Clear Z-Buffers */ + u16 rsv:9; + } bf; + u16 data; +}; + +struct src_rsc_ctrl_blk { + unsigned int ctl; + unsigned int ccr; + unsigned int ca; + unsigned int sa; + unsigned int la; + unsigned int mpr; + union src_dirty dirty; +}; + +/* SRC manager control block */ +union src_mgr_dirty { + struct { + u16 enb0:1; + u16 enb1:1; + u16 enb2:1; + u16 enb3:1; + u16 enb4:1; + u16 enb5:1; + u16 enb6:1; + u16 enb7:1; + u16 enbsa:1; + u16 rsv:7; + } bf; + u16 data; +}; + +struct src_mgr_ctrl_blk { + unsigned int enbsa; + unsigned int enb[8]; + union src_mgr_dirty dirty; +}; + +/* SRCIMP manager control block */ +#define SRCAIM_ARC 0x00000FFF +#define SRCAIM_NXT 0x00FF0000 +#define SRCAIM_SRC 0xFF000000 + +struct srcimap { + unsigned int srcaim; + unsigned int idx; +}; + +/* SRCIMP manager register dirty flags */ +union srcimp_mgr_dirty { + struct { + u16 srcimap:1; + u16 rsv:15; + } bf; + u16 data; +}; + +struct srcimp_mgr_ctrl_blk { + struct srcimap srcimap; + union srcimp_mgr_dirty dirty; +}; + +/* + * Function implementation block. + */ + +static int src_get_rsc_ctrl_blk(void **rblk) +{ + struct src_rsc_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int src_put_rsc_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int src_set_state(void *blk, unsigned int state) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_STATE, state); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_bm(void *blk, unsigned int bm) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_BM, bm); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_rsr(void *blk, unsigned int rsr) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_RSR, rsr); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_sf(void *blk, unsigned int sf) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_SF, sf); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_wr(void *blk, unsigned int wr) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_WR, wr); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_pm(void *blk, unsigned int pm) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_PM, pm); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_rom(void *blk, unsigned int rom) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_ROM, rom); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_vo(void *blk, unsigned int vo) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_VO, vo); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_st(void *blk, unsigned int st) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_ST, st); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_ie(void *blk, unsigned int ie) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_IE, ie); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_ilsz(void *blk, unsigned int ilsz) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_ILSZ, ilsz); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_bp(void *blk, unsigned int bp) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ctl, SRCCTL_BP, bp); + ctl->dirty.bf.ctl = 1; + return 0; +} + +static int src_set_cisz(void *blk, unsigned int cisz) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ccr, SRCCCR_CISZ, cisz); + ctl->dirty.bf.ccr = 1; + return 0; +} + +static int src_set_ca(void *blk, unsigned int ca) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->ca, SRCCA_CA, ca); + ctl->dirty.bf.ca = 1; + return 0; +} + +static int src_set_sa(void *blk, unsigned int sa) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->sa, SRCSA_SA, sa); + ctl->dirty.bf.sa = 1; + return 0; +} + +static int src_set_la(void *blk, unsigned int la) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->la, SRCLA_LA, la); + ctl->dirty.bf.la = 1; + return 0; +} + +static int src_set_pitch(void *blk, unsigned int pitch) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->mpr, MPRLH_PITCH, pitch); + ctl->dirty.bf.mpr = 1; + return 0; +} + +static int src_set_clear_zbufs(void *blk, unsigned int clear) +{ + ((struct src_rsc_ctrl_blk *)blk)->dirty.bf.czbfs = (clear ? 1 : 0); + return 0; +} + +static int src_set_dirty(void *blk, unsigned int flags) +{ + ((struct src_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff); + return 0; +} + +static int src_set_dirty_all(void *blk) +{ + ((struct src_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0); + return 0; +} + +#define AR_SLOT_SIZE 4096 +#define AR_SLOT_BLOCK_SIZE 16 +#define AR_PTS_PITCH 6 +#define AR_PARAM_SRC_OFFSET 0x60 + +static unsigned int src_param_pitch_mixer(unsigned int src_idx) +{ + return ((src_idx << 4) + AR_PTS_PITCH + AR_SLOT_SIZE + - AR_PARAM_SRC_OFFSET) % AR_SLOT_SIZE; + +} + +static int src_commit_write(struct hw *hw, unsigned int idx, void *blk) +{ + struct src_rsc_ctrl_blk *ctl = blk; + int i; + + if (ctl->dirty.bf.czbfs) { + /* Clear Z-Buffer registers */ + for (i = 0; i < 8; i++) + hw_write_20kx(hw, SRC_UPZ+idx*0x100+i*0x4, 0); + + for (i = 0; i < 4; i++) + hw_write_20kx(hw, SRC_DN0Z+idx*0x100+i*0x4, 0); + + for (i = 0; i < 8; i++) + hw_write_20kx(hw, SRC_DN1Z+idx*0x100+i*0x4, 0); + + ctl->dirty.bf.czbfs = 0; + } + if (ctl->dirty.bf.mpr) { + /* Take the parameter mixer resource in the same group as that + * the idx src is in for simplicity. Unlike src, all conjugate + * parameter mixer resources must be programmed for + * corresponding conjugate src resources. */ + unsigned int pm_idx = src_param_pitch_mixer(idx); + hw_write_20kx(hw, MIXER_PRING_LO_HI+4*pm_idx, ctl->mpr); + hw_write_20kx(hw, MIXER_PMOPLO+8*pm_idx, 0x3); + hw_write_20kx(hw, MIXER_PMOPHI+8*pm_idx, 0x0); + ctl->dirty.bf.mpr = 0; + } + if (ctl->dirty.bf.sa) { + hw_write_20kx(hw, SRC_SA+idx*0x100, ctl->sa); + ctl->dirty.bf.sa = 0; + } + if (ctl->dirty.bf.la) { + hw_write_20kx(hw, SRC_LA+idx*0x100, ctl->la); + ctl->dirty.bf.la = 0; + } + if (ctl->dirty.bf.ca) { + hw_write_20kx(hw, SRC_CA+idx*0x100, ctl->ca); + ctl->dirty.bf.ca = 0; + } + + /* Write srccf register */ + hw_write_20kx(hw, SRC_CF+idx*0x100, 0x0); + + if (ctl->dirty.bf.ccr) { + hw_write_20kx(hw, SRC_CCR+idx*0x100, ctl->ccr); + ctl->dirty.bf.ccr = 0; + } + if (ctl->dirty.bf.ctl) { + hw_write_20kx(hw, SRC_CTL+idx*0x100, ctl->ctl); + ctl->dirty.bf.ctl = 0; + } + + return 0; +} + +static int src_get_ca(struct hw *hw, unsigned int idx, void *blk) +{ + struct src_rsc_ctrl_blk *ctl = blk; + + ctl->ca = hw_read_20kx(hw, SRC_CA+idx*0x100); + ctl->dirty.bf.ca = 0; + + return get_field(ctl->ca, SRCCA_CA); +} + +static unsigned int src_get_dirty(void *blk) +{ + return ((struct src_rsc_ctrl_blk *)blk)->dirty.data; +} + +static unsigned int src_dirty_conj_mask(void) +{ + return 0x20; +} + +static int src_mgr_enbs_src(void *blk, unsigned int idx) +{ + ((struct src_mgr_ctrl_blk *)blk)->enbsa |= (0x1 << ((idx%128)/4)); + ((struct src_mgr_ctrl_blk *)blk)->dirty.bf.enbsa = 1; + ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32)); + return 0; +} + +static int src_mgr_enb_src(void *blk, unsigned int idx) +{ + ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32)); + ((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32)); + return 0; +} + +static int src_mgr_dsb_src(void *blk, unsigned int idx) +{ + ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] &= ~(0x1 << (idx%32)); + ((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32)); + return 0; +} + +static int src_mgr_commit_write(struct hw *hw, void *blk) +{ + struct src_mgr_ctrl_blk *ctl = blk; + int i; + unsigned int ret; + + if (ctl->dirty.bf.enbsa) { + do { + ret = hw_read_20kx(hw, SRC_ENBSTAT); + } while (ret & 0x1); + hw_write_20kx(hw, SRC_ENBSA, ctl->enbsa); + ctl->dirty.bf.enbsa = 0; + } + for (i = 0; i < 8; i++) { + if ((ctl->dirty.data & (0x1 << i))) { + hw_write_20kx(hw, SRC_ENB+(i*0x100), ctl->enb[i]); + ctl->dirty.data &= ~(0x1 << i); + } + } + + return 0; +} + +static int src_mgr_get_ctrl_blk(void **rblk) +{ + struct src_mgr_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int src_mgr_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int srcimp_mgr_get_ctrl_blk(void **rblk) +{ + struct srcimp_mgr_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int srcimp_mgr_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int srcimp_mgr_set_imaparc(void *blk, unsigned int slot) +{ + struct srcimp_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->srcimap.srcaim, SRCAIM_ARC, slot); + ctl->dirty.bf.srcimap = 1; + return 0; +} + +static int srcimp_mgr_set_imapuser(void *blk, unsigned int user) +{ + struct srcimp_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->srcimap.srcaim, SRCAIM_SRC, user); + ctl->dirty.bf.srcimap = 1; + return 0; +} + +static int srcimp_mgr_set_imapnxt(void *blk, unsigned int next) +{ + struct srcimp_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->srcimap.srcaim, SRCAIM_NXT, next); + ctl->dirty.bf.srcimap = 1; + return 0; +} + +static int srcimp_mgr_set_imapaddr(void *blk, unsigned int addr) +{ + ((struct srcimp_mgr_ctrl_blk *)blk)->srcimap.idx = addr; + ((struct srcimp_mgr_ctrl_blk *)blk)->dirty.bf.srcimap = 1; + return 0; +} + +static int srcimp_mgr_commit_write(struct hw *hw, void *blk) +{ + struct srcimp_mgr_ctrl_blk *ctl = blk; + + if (ctl->dirty.bf.srcimap) { + hw_write_20kx(hw, SRC_IMAP+ctl->srcimap.idx*0x100, + ctl->srcimap.srcaim); + ctl->dirty.bf.srcimap = 0; + } + + return 0; +} + +/* + * AMIXER control block definitions. + */ + +#define AMOPLO_M 0x00000003 +#define AMOPLO_IV 0x00000004 +#define AMOPLO_X 0x0003FFF0 +#define AMOPLO_Y 0xFFFC0000 + +#define AMOPHI_SADR 0x000000FF +#define AMOPHI_SE 0x80000000 + +/* AMIXER resource register dirty flags */ +union amixer_dirty { + struct { + u16 amoplo:1; + u16 amophi:1; + u16 rsv:14; + } bf; + u16 data; +}; + +/* AMIXER resource control block */ +struct amixer_rsc_ctrl_blk { + unsigned int amoplo; + unsigned int amophi; + union amixer_dirty dirty; +}; + +static int amixer_set_mode(void *blk, unsigned int mode) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amoplo, AMOPLO_M, mode); + ctl->dirty.bf.amoplo = 1; + return 0; +} + +static int amixer_set_iv(void *blk, unsigned int iv) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amoplo, AMOPLO_IV, iv); + ctl->dirty.bf.amoplo = 1; + return 0; +} + +static int amixer_set_x(void *blk, unsigned int x) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amoplo, AMOPLO_X, x); + ctl->dirty.bf.amoplo = 1; + return 0; +} + +static int amixer_set_y(void *blk, unsigned int y) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amoplo, AMOPLO_Y, y); + ctl->dirty.bf.amoplo = 1; + return 0; +} + +static int amixer_set_sadr(void *blk, unsigned int sadr) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amophi, AMOPHI_SADR, sadr); + ctl->dirty.bf.amophi = 1; + return 0; +} + +static int amixer_set_se(void *blk, unsigned int se) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + set_field(&ctl->amophi, AMOPHI_SE, se); + ctl->dirty.bf.amophi = 1; + return 0; +} + +static int amixer_set_dirty(void *blk, unsigned int flags) +{ + ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff); + return 0; +} + +static int amixer_set_dirty_all(void *blk) +{ + ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0); + return 0; +} + +static int amixer_commit_write(struct hw *hw, unsigned int idx, void *blk) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + if (ctl->dirty.bf.amoplo || ctl->dirty.bf.amophi) { + hw_write_20kx(hw, MIXER_AMOPLO+idx*8, ctl->amoplo); + ctl->dirty.bf.amoplo = 0; + hw_write_20kx(hw, MIXER_AMOPHI+idx*8, ctl->amophi); + ctl->dirty.bf.amophi = 0; + } + + return 0; +} + +static int amixer_get_y(void *blk) +{ + struct amixer_rsc_ctrl_blk *ctl = blk; + + return get_field(ctl->amoplo, AMOPLO_Y); +} + +static unsigned int amixer_get_dirty(void *blk) +{ + return ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data; +} + +static int amixer_rsc_get_ctrl_blk(void **rblk) +{ + struct amixer_rsc_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int amixer_rsc_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int amixer_mgr_get_ctrl_blk(void **rblk) +{ + *rblk = NULL; + + return 0; +} + +static int amixer_mgr_put_ctrl_blk(void *blk) +{ + return 0; +} + +/* + * DAIO control block definitions. + */ + +/* Receiver Sample Rate Tracker Control register */ +#define SRTCTL_SRCO 0x000000FF +#define SRTCTL_SRCM 0x0000FF00 +#define SRTCTL_RSR 0x00030000 +#define SRTCTL_DRAT 0x00300000 +#define SRTCTL_EC 0x01000000 +#define SRTCTL_ET 0x10000000 + +/* DAIO Receiver register dirty flags */ +union dai_dirty { + struct { + u16 srt:1; + u16 rsv:15; + } bf; + u16 data; +}; + +/* DAIO Receiver control block */ +struct dai_ctrl_blk { + unsigned int srt; + union dai_dirty dirty; +}; + +/* Audio Input Mapper RAM */ +#define AIM_ARC 0x00000FFF +#define AIM_NXT 0x007F0000 + +struct daoimap { + unsigned int aim; + unsigned int idx; +}; + +/* Audio Transmitter Control and Status register */ +#define ATXCTL_EN 0x00000001 +#define ATXCTL_MODE 0x00000010 +#define ATXCTL_CD 0x00000020 +#define ATXCTL_RAW 0x00000100 +#define ATXCTL_MT 0x00000200 +#define ATXCTL_NUC 0x00003000 +#define ATXCTL_BEN 0x00010000 +#define ATXCTL_BMUX 0x00700000 +#define ATXCTL_B24 0x01000000 +#define ATXCTL_CPF 0x02000000 +#define ATXCTL_RIV 0x10000000 +#define ATXCTL_LIV 0x20000000 +#define ATXCTL_RSAT 0x40000000 +#define ATXCTL_LSAT 0x80000000 + +/* XDIF Transmitter register dirty flags */ +union dao_dirty { + struct { + u16 atxcsl:1; + u16 rsv:15; + } bf; + u16 data; +}; + +/* XDIF Transmitter control block */ +struct dao_ctrl_blk { + /* XDIF Transmitter Channel Status Low Register */ + unsigned int atxcsl; + union dao_dirty dirty; +}; + +/* Audio Receiver Control register */ +#define ARXCTL_EN 0x00000001 + +/* DAIO manager register dirty flags */ +union daio_mgr_dirty { + struct { + u32 atxctl:8; + u32 arxctl:8; + u32 daoimap:1; + u32 rsv:15; + } bf; + u32 data; +}; + +/* DAIO manager control block */ +struct daio_mgr_ctrl_blk { + struct daoimap daoimap; + unsigned int txctl[8]; + unsigned int rxctl[8]; + union daio_mgr_dirty dirty; +}; + +static int dai_srt_set_srco(void *blk, unsigned int src) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srt, SRTCTL_SRCO, src); + ctl->dirty.bf.srt = 1; + return 0; +} + +static int dai_srt_set_srcm(void *blk, unsigned int src) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srt, SRTCTL_SRCM, src); + ctl->dirty.bf.srt = 1; + return 0; +} + +static int dai_srt_set_rsr(void *blk, unsigned int rsr) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srt, SRTCTL_RSR, rsr); + ctl->dirty.bf.srt = 1; + return 0; +} + +static int dai_srt_set_drat(void *blk, unsigned int drat) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srt, SRTCTL_DRAT, drat); + ctl->dirty.bf.srt = 1; + return 0; +} + +static int dai_srt_set_ec(void *blk, unsigned int ec) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srt, SRTCTL_EC, ec ? 1 : 0); + ctl->dirty.bf.srt = 1; + return 0; +} + +static int dai_srt_set_et(void *blk, unsigned int et) +{ + struct dai_ctrl_blk *ctl = blk; + + set_field(&ctl->srt, SRTCTL_ET, et ? 1 : 0); + ctl->dirty.bf.srt = 1; + return 0; +} + +static int dai_commit_write(struct hw *hw, unsigned int idx, void *blk) +{ + struct dai_ctrl_blk *ctl = blk; + + if (ctl->dirty.bf.srt) { + hw_write_20kx(hw, AUDIO_IO_RX_SRT_CTL+0x40*idx, ctl->srt); + ctl->dirty.bf.srt = 0; + } + + return 0; +} + +static int dai_get_ctrl_blk(void **rblk) +{ + struct dai_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int dai_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int dao_set_spos(void *blk, unsigned int spos) +{ + ((struct dao_ctrl_blk *)blk)->atxcsl = spos; + ((struct dao_ctrl_blk *)blk)->dirty.bf.atxcsl = 1; + return 0; +} + +static int dao_commit_write(struct hw *hw, unsigned int idx, void *blk) +{ + struct dao_ctrl_blk *ctl = blk; + + if (ctl->dirty.bf.atxcsl) { + if ((idx < 4) && ((hw->model != CTOK0010) || (idx < 3))) { + /* S/PDIF SPOSx */ + hw_write_20kx(hw, AUDIO_IO_TX_CSTAT_L+0x40*idx, + ctl->atxcsl); + } + ctl->dirty.bf.atxcsl = 0; + } + + return 0; +} + +static int dao_get_spos(void *blk, unsigned int *spos) +{ + *spos = ((struct dao_ctrl_blk *)blk)->atxcsl; + return 0; +} + +static int dao_get_ctrl_blk(void **rblk) +{ + struct dao_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int dao_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +static int daio_mgr_enb_dai(void *blk, unsigned int idx) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->rxctl[idx], ARXCTL_EN, 1); + ctl->dirty.bf.arxctl |= (0x1 << idx); + return 0; +} + +static int daio_mgr_dsb_dai(void *blk, unsigned int idx) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->rxctl[idx], ARXCTL_EN, 0); + + ctl->dirty.bf.arxctl |= (0x1 << idx); + return 0; +} + +static int daio_mgr_enb_dao(void *blk, unsigned int idx) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->txctl[idx], ATXCTL_EN, 1); + ctl->dirty.bf.atxctl |= (0x1 << idx); + return 0; +} + +static int daio_mgr_dsb_dao(void *blk, unsigned int idx) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->txctl[idx], ATXCTL_EN, 0); + ctl->dirty.bf.atxctl |= (0x1 << idx); + return 0; +} + +static int daio_mgr_dao_init(struct hw *hw, void *blk, unsigned int idx, unsigned int conf) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + /* Port 3 is dedicated to RCA on SE-300PCIE */ + if ((idx < 4) && ((hw->model != CTOK0010) || (idx < 3))) { + /* S/PDIF output */ + switch ((conf & 0xf)) { + case 1: + set_field(&ctl->txctl[idx], ATXCTL_NUC, 0); + break; + case 2: + set_field(&ctl->txctl[idx], ATXCTL_NUC, 1); + break; + case 4: + set_field(&ctl->txctl[idx], ATXCTL_NUC, 2); + break; + case 8: + set_field(&ctl->txctl[idx], ATXCTL_NUC, 3); + break; + default: + break; + } + /* CDIF */ + set_field(&ctl->txctl[idx], ATXCTL_CD, (!(conf & 0x7))); + /* Non-audio */ + set_field(&ctl->txctl[idx], ATXCTL_LIV, (conf >> 4) & 0x1); + /* Non-audio */ + set_field(&ctl->txctl[idx], ATXCTL_RIV, (conf >> 4) & 0x1); + set_field(&ctl->txctl[idx], ATXCTL_RAW, + ((conf >> 3) & 0x1) ? 0 : 0); + ctl->dirty.bf.atxctl |= (0x1 << idx); + } else { + /* I2S output */ + /*idx %= 4; */ + } + return 0; +} + +static int daio_mgr_set_imaparc(void *blk, unsigned int slot) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->daoimap.aim, AIM_ARC, slot); + ctl->dirty.bf.daoimap = 1; + return 0; +} + +static int daio_mgr_set_imapnxt(void *blk, unsigned int next) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + + set_field(&ctl->daoimap.aim, AIM_NXT, next); + ctl->dirty.bf.daoimap = 1; + return 0; +} + +static int daio_mgr_set_imapaddr(void *blk, unsigned int addr) +{ + ((struct daio_mgr_ctrl_blk *)blk)->daoimap.idx = addr; + ((struct daio_mgr_ctrl_blk *)blk)->dirty.bf.daoimap = 1; + return 0; +} + +static int daio_mgr_commit_write(struct hw *hw, void *blk) +{ + struct daio_mgr_ctrl_blk *ctl = blk; + unsigned int data; + int i; + + for (i = 0; i < 8; i++) { + if ((ctl->dirty.bf.atxctl & (0x1 << i))) { + data = ctl->txctl[i]; + hw_write_20kx(hw, (AUDIO_IO_TX_CTL+(0x40*i)), data); + ctl->dirty.bf.atxctl &= ~(0x1 << i); + mdelay(1); + } + if ((ctl->dirty.bf.arxctl & (0x1 << i))) { + data = ctl->rxctl[i]; + hw_write_20kx(hw, (AUDIO_IO_RX_CTL+(0x40*i)), data); + ctl->dirty.bf.arxctl &= ~(0x1 << i); + mdelay(1); + } + } + if (ctl->dirty.bf.daoimap) { + hw_write_20kx(hw, AUDIO_IO_AIM+ctl->daoimap.idx*4, + ctl->daoimap.aim); + ctl->dirty.bf.daoimap = 0; + } + + return 0; +} + +static int daio_mgr_get_ctrl_blk(struct hw *hw, void **rblk) +{ + struct daio_mgr_ctrl_blk *blk; + int i; + + *rblk = NULL; + blk = kzalloc_obj(*blk); + if (!blk) + return -ENOMEM; + + for (i = 0; i < 8; i++) { + blk->txctl[i] = hw_read_20kx(hw, AUDIO_IO_TX_CTL+(0x40*i)); + blk->rxctl[i] = hw_read_20kx(hw, AUDIO_IO_RX_CTL+(0x40*i)); + } + + *rblk = blk; + + return 0; +} + +static int daio_mgr_put_ctrl_blk(void *blk) +{ + kfree(blk); + + return 0; +} + +/* Timer interrupt */ +static int set_timer_irq(struct hw *hw, int enable) +{ + hw_write_20kx(hw, GIE, enable ? IT_INT : 0); + return 0; +} + +static int set_timer_tick(struct hw *hw, unsigned int ticks) +{ + if (ticks) + ticks |= TIMR_IE | TIMR_IP; + hw_write_20kx(hw, TIMR, ticks); + return 0; +} + +static unsigned int get_wc(struct hw *hw) +{ + return hw_read_20kx(hw, WC); +} + +/* Card hardware initialization block */ +struct dac_conf { + unsigned int msr; /* master sample rate in rsrs */ +}; + +struct adc_conf { + unsigned int msr; /* master sample rate in rsrs */ + unsigned char input; /* the input source of ADC */ + unsigned char mic20db; /* boost mic by 20db if input is microphone */ +}; + +struct daio_conf { + unsigned int msr; /* master sample rate in rsrs */ +}; + +struct trn_conf { + unsigned long vm_pgt_phys; +}; + +static int hw_daio_init(struct hw *hw, const struct daio_conf *info) +{ + u32 data; + int i; + + /* Program I2S with proper sample rate and enable the correct I2S + * channel. ED(0/8/16/24): Enable all I2S/I2X master clock output */ + if (1 == info->msr) { + hw_write_20kx(hw, AUDIO_IO_MCLK, 0x01010101); + hw_write_20kx(hw, AUDIO_IO_TX_BLRCLK, 0x01010101); + hw_write_20kx(hw, AUDIO_IO_RX_BLRCLK, 0); + } else if (2 == info->msr) { + if (hw->model != CTSB1270) { + hw_write_20kx(hw, AUDIO_IO_MCLK, 0x11111111); + } else { + /* PCM4220 on Titanium HD is different. */ + hw_write_20kx(hw, AUDIO_IO_MCLK, 0x11011111); + } + /* Specify all playing 96khz + * EA [0] - Enabled + * RTA [4:5] - 96kHz + * EB [8] - Enabled + * RTB [12:13] - 96kHz + * EC [16] - Enabled + * RTC [20:21] - 96kHz + * ED [24] - Enabled + * RTD [28:29] - 96kHz */ + hw_write_20kx(hw, AUDIO_IO_TX_BLRCLK, 0x11111111); + hw_write_20kx(hw, AUDIO_IO_RX_BLRCLK, 0); + } else if ((4 == info->msr) && (hw->model == CTSB1270)) { + hw_write_20kx(hw, AUDIO_IO_MCLK, 0x21011111); + hw_write_20kx(hw, AUDIO_IO_TX_BLRCLK, 0x21212121); + hw_write_20kx(hw, AUDIO_IO_RX_BLRCLK, 0); + } else if ((4 == info->msr) && (hw->model == CTOK0010)) { + hw_write_20kx(hw, AUDIO_IO_MCLK, 0x21212121); + hw_write_20kx(hw, AUDIO_IO_TX_BLRCLK, 0x21212121); + hw_write_20kx(hw, AUDIO_IO_RX_BLRCLK, 0); + } else { + dev_alert(hw->card->dev, + "ERROR!!! Invalid sampling rate!!!\n"); + return -EINVAL; + } + + for (i = 0; i < 8; i++) { + /* Port 3 is configured as I2S on SE-300PCIE */ + if ((i < 4) && ((hw->model != CTOK0010) || (i < 3))) { + /* This comment looks wrong since loop is over 4 */ + /* channels and emu20k2 supports 4 spdif IOs. */ + /* 1st 3 channels are SPDIFs (SB0960) */ + if (i == 3) + data = 0x1001001; + else + data = 0x1000001; + + hw_write_20kx(hw, (AUDIO_IO_TX_CTL+(0x40*i)), data); + hw_write_20kx(hw, (AUDIO_IO_RX_CTL+(0x40*i)), data); + + /* Initialize the SPDIF Out Channel status registers. + * The value specified here is based on the typical + * values provided in the specification, namely: Clock + * Accuracy of 1000ppm, Sample Rate of 48KHz, + * unspecified source number, Generation status = 1, + * Category code = 0x12 (Digital Signal Mixer), + * Mode = 0, Emph = 0, Copy Permitted, AN = 0 + * (indicating that we're transmitting digital audio, + * and the Professional Use bit is 0. */ + + hw_write_20kx(hw, AUDIO_IO_TX_CSTAT_L+(0x40*i), + 0x02109204); /* Default to 48kHz */ + + hw_write_20kx(hw, AUDIO_IO_TX_CSTAT_H+(0x40*i), 0x0B); + } else { + /* Again, loop is over 4 channels not 5. */ + /* Next 5 channels are I2S (SB0960) */ + data = 0x11; + hw_write_20kx(hw, AUDIO_IO_RX_CTL+(0x40*i), data); + if (2 == info->msr) { + /* Four channels per sample period */ + data |= 0x1000; + } else if (4 == info->msr) { + /* FIXME: check this against the chip spec */ + data |= 0x2000; + } + hw_write_20kx(hw, AUDIO_IO_TX_CTL+(0x40*i), data); + } + } + + return 0; +} + +/* TRANSPORT operations */ +static int hw_trn_init(struct hw *hw, const struct trn_conf *info) +{ + u32 vmctl, data; + u32 ptp_phys_low, ptp_phys_high; + int i; + + /* Set up device page table */ + if ((~0UL) == info->vm_pgt_phys) { + dev_alert(hw->card->dev, + "Wrong device page table page address!!!\n"); + return -1; + } + + vmctl = 0x80000C0F; /* 32-bit, 4k-size page */ + ptp_phys_low = (u32)info->vm_pgt_phys; + ptp_phys_high = upper_32_bits(info->vm_pgt_phys); + if (sizeof(void *) == 8) /* 64bit address */ + vmctl |= (3 << 8); + /* Write page table physical address to all PTPAL registers */ + for (i = 0; i < 64; i++) { + hw_write_20kx(hw, VMEM_PTPAL+(16*i), ptp_phys_low); + hw_write_20kx(hw, VMEM_PTPAH+(16*i), ptp_phys_high); + } + /* Enable virtual memory transfer */ + hw_write_20kx(hw, VMEM_CTL, vmctl); + /* Enable transport bus master and queueing of request */ + hw_write_20kx(hw, TRANSPORT_CTL, 0x03); + hw_write_20kx(hw, TRANSPORT_INT, 0x200c01); + /* Enable transport ring */ + data = hw_read_20kx(hw, TRANSPORT_ENB); + hw_write_20kx(hw, TRANSPORT_ENB, (data | 0x03)); + + return 0; +} + +/* Card initialization */ +#define GCTL_AIE 0x00000001 +#define GCTL_UAA 0x00000002 +#define GCTL_DPC 0x00000004 +#define GCTL_DBP 0x00000008 +#define GCTL_ABP 0x00000010 +#define GCTL_TBP 0x00000020 +#define GCTL_SBP 0x00000040 +#define GCTL_FBP 0x00000080 +#define GCTL_ME 0x00000100 +#define GCTL_AID 0x00001000 + +#define PLLCTL_SRC 0x00000007 +#define PLLCTL_SPE 0x00000008 +#define PLLCTL_RD 0x000000F0 +#define PLLCTL_FD 0x0001FF00 +#define PLLCTL_OD 0x00060000 +#define PLLCTL_B 0x00080000 +#define PLLCTL_AS 0x00100000 +#define PLLCTL_LF 0x03E00000 +#define PLLCTL_SPS 0x1C000000 +#define PLLCTL_AD 0x60000000 + +#define PLLSTAT_CCS 0x00000007 +#define PLLSTAT_SPL 0x00000008 +#define PLLSTAT_CRD 0x000000F0 +#define PLLSTAT_CFD 0x0001FF00 +#define PLLSTAT_SL 0x00020000 +#define PLLSTAT_FAS 0x00040000 +#define PLLSTAT_B 0x00080000 +#define PLLSTAT_PD 0x00100000 +#define PLLSTAT_OCA 0x00200000 +#define PLLSTAT_NCA 0x00400000 + +static int hw_pll_init(struct hw *hw, unsigned int rsr) +{ + unsigned int pllenb; + unsigned int pllctl; + unsigned int pllstat; + int i; + + pllenb = 0xB; + hw_write_20kx(hw, PLL_ENB, pllenb); + pllctl = 0x20C00000; + set_field(&pllctl, PLLCTL_B, 0); + set_field(&pllctl, PLLCTL_FD, 48000 == rsr ? 16 - 4 : 147 - 4); + set_field(&pllctl, PLLCTL_RD, 48000 == rsr ? 1 - 1 : 10 - 1); + hw_write_20kx(hw, PLL_CTL, pllctl); + msleep(40); + + pllctl = hw_read_20kx(hw, PLL_CTL); + set_field(&pllctl, PLLCTL_FD, 48000 == rsr ? 16 - 2 : 147 - 2); + hw_write_20kx(hw, PLL_CTL, pllctl); + msleep(40); + + for (i = 0; i < 1000; i++) { + pllstat = hw_read_20kx(hw, PLL_STAT); + if (get_field(pllstat, PLLSTAT_PD)) + continue; + + if (get_field(pllstat, PLLSTAT_B) != + get_field(pllctl, PLLCTL_B)) + continue; + + if (get_field(pllstat, PLLSTAT_CCS) != + get_field(pllctl, PLLCTL_SRC)) + continue; + + if (get_field(pllstat, PLLSTAT_CRD) != + get_field(pllctl, PLLCTL_RD)) + continue; + + if (get_field(pllstat, PLLSTAT_CFD) != + get_field(pllctl, PLLCTL_FD)) + continue; + + break; + } + if (i >= 1000) { + dev_alert(hw->card->dev, + "PLL initialization failed!!!\n"); + return -EBUSY; + } + + return 0; +} + +static int hw_auto_init(struct hw *hw) +{ + unsigned int gctl; + int i; + + gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL); + set_field(&gctl, GCTL_AIE, 0); + hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl); + set_field(&gctl, GCTL_AIE, 1); + hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl); + mdelay(10); + for (i = 0; i < 400000; i++) { + gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL); + if (get_field(gctl, GCTL_AID)) + break; + } + if (!get_field(gctl, GCTL_AID)) { + dev_alert(hw->card->dev, "Card Auto-init failed!!!\n"); + return -EBUSY; + } + + return 0; +} + +/* DAC operations */ + +#define CS4382_MC1 0x1 +#define CS4382_MC2 0x2 +#define CS4382_MC3 0x3 +#define CS4382_FC 0x4 +#define CS4382_IC 0x5 +#define CS4382_XC1 0x6 +#define CS4382_VCA1 0x7 +#define CS4382_VCB1 0x8 +#define CS4382_XC2 0x9 +#define CS4382_VCA2 0xA +#define CS4382_VCB2 0xB +#define CS4382_XC3 0xC +#define CS4382_VCA3 0xD +#define CS4382_VCB3 0xE +#define CS4382_XC4 0xF +#define CS4382_VCA4 0x10 +#define CS4382_VCB4 0x11 +#define CS4382_CREV 0x12 + +/* I2C status */ +#define STATE_LOCKED 0x00 +#define STATE_UNLOCKED 0xAA +#define DATA_READY 0x800000 /* Used with I2C_IF_STATUS */ +#define DATA_ABORT 0x10000 /* Used with I2C_IF_STATUS */ + +#define I2C_STATUS_DCM 0x00000001 +#define I2C_STATUS_BC 0x00000006 +#define I2C_STATUS_APD 0x00000008 +#define I2C_STATUS_AB 0x00010000 +#define I2C_STATUS_DR 0x00800000 + +#define I2C_ADDRESS_PTAD 0x0000FFFF +#define I2C_ADDRESS_SLAD 0x007F0000 + +struct regs_cs4382 { + u32 mode_control_1; + u32 mode_control_2; + u32 mode_control_3; + + u32 filter_control; + u32 invert_control; + + u32 mix_control_P1; + u32 vol_control_A1; + u32 vol_control_B1; + + u32 mix_control_P2; + u32 vol_control_A2; + u32 vol_control_B2; + + u32 mix_control_P3; + u32 vol_control_A3; + u32 vol_control_B3; + + u32 mix_control_P4; + u32 vol_control_A4; + u32 vol_control_B4; +}; + +static int hw20k2_i2c_unlock_full_access(struct hw *hw) +{ + u8 UnlockKeySequence_FLASH_FULLACCESS_MODE[2] = {0xB3, 0xD4}; + + /* Send keys for forced BIOS mode */ + hw_write_20kx(hw, I2C_IF_WLOCK, + UnlockKeySequence_FLASH_FULLACCESS_MODE[0]); + hw_write_20kx(hw, I2C_IF_WLOCK, + UnlockKeySequence_FLASH_FULLACCESS_MODE[1]); + /* Check whether the chip is unlocked */ + if (hw_read_20kx(hw, I2C_IF_WLOCK) == STATE_UNLOCKED) + return 0; + + return -1; +} + +static int hw20k2_i2c_lock_chip(struct hw *hw) +{ + /* Write twice */ + hw_write_20kx(hw, I2C_IF_WLOCK, STATE_LOCKED); + hw_write_20kx(hw, I2C_IF_WLOCK, STATE_LOCKED); + if (hw_read_20kx(hw, I2C_IF_WLOCK) == STATE_LOCKED) + return 0; + + return -1; +} + +static int hw20k2_i2c_init(struct hw *hw, u8 dev_id, u8 addr_size, u8 data_size) +{ + struct hw20k2 *hw20k2 = (struct hw20k2 *)hw; + int err; + unsigned int i2c_status; + unsigned int i2c_addr; + + err = hw20k2_i2c_unlock_full_access(hw); + if (err < 0) + return err; + + hw20k2->addr_size = addr_size; + hw20k2->data_size = data_size; + hw20k2->dev_id = dev_id; + + i2c_addr = 0; + set_field(&i2c_addr, I2C_ADDRESS_SLAD, dev_id); + + hw_write_20kx(hw, I2C_IF_ADDRESS, i2c_addr); + + i2c_status = hw_read_20kx(hw, I2C_IF_STATUS); + + set_field(&i2c_status, I2C_STATUS_DCM, 1); /* Direct control mode */ + + hw_write_20kx(hw, I2C_IF_STATUS, i2c_status); + + return 0; +} + +static int hw20k2_i2c_uninit(struct hw *hw) +{ + unsigned int i2c_status; + unsigned int i2c_addr; + + i2c_addr = 0; + set_field(&i2c_addr, I2C_ADDRESS_SLAD, 0x57); /* I2C id */ + + hw_write_20kx(hw, I2C_IF_ADDRESS, i2c_addr); + + i2c_status = hw_read_20kx(hw, I2C_IF_STATUS); + + set_field(&i2c_status, I2C_STATUS_DCM, 0); /* I2C mode */ + + hw_write_20kx(hw, I2C_IF_STATUS, i2c_status); + + return hw20k2_i2c_lock_chip(hw); +} + +static int hw20k2_i2c_wait_data_ready(struct hw *hw) +{ + int i = 0x400000; + unsigned int ret; + + do { + ret = hw_read_20kx(hw, I2C_IF_STATUS); + } while ((!(ret & DATA_READY)) && --i); + + return i; +} + +static int hw20k2_i2c_read(struct hw *hw, u16 addr, u32 *datap) +{ + struct hw20k2 *hw20k2 = (struct hw20k2 *)hw; + unsigned int i2c_status; + + i2c_status = hw_read_20kx(hw, I2C_IF_STATUS); + set_field(&i2c_status, I2C_STATUS_BC, + (4 == hw20k2->addr_size) ? 0 : hw20k2->addr_size); + hw_write_20kx(hw, I2C_IF_STATUS, i2c_status); + if (!hw20k2_i2c_wait_data_ready(hw)) + return -1; + + hw_write_20kx(hw, I2C_IF_WDATA, addr); + if (!hw20k2_i2c_wait_data_ready(hw)) + return -1; + + /* Force a read operation */ + hw_write_20kx(hw, I2C_IF_RDATA, 0); + if (!hw20k2_i2c_wait_data_ready(hw)) + return -1; + + *datap = hw_read_20kx(hw, I2C_IF_RDATA); + + return 0; +} + +static int hw20k2_i2c_write(struct hw *hw, u16 addr, u32 data) +{ + struct hw20k2 *hw20k2 = (struct hw20k2 *)hw; + unsigned int i2c_data = (data << (hw20k2->addr_size * 8)) | addr; + unsigned int i2c_status; + + i2c_status = hw_read_20kx(hw, I2C_IF_STATUS); + + set_field(&i2c_status, I2C_STATUS_BC, + (4 == (hw20k2->addr_size + hw20k2->data_size)) ? + 0 : (hw20k2->addr_size + hw20k2->data_size)); + + hw_write_20kx(hw, I2C_IF_STATUS, i2c_status); + hw20k2_i2c_wait_data_ready(hw); + /* Dummy write to trigger the write operation */ + hw_write_20kx(hw, I2C_IF_WDATA, 0); + hw20k2_i2c_wait_data_ready(hw); + + /* This is the real data */ + hw_write_20kx(hw, I2C_IF_WDATA, i2c_data); + hw20k2_i2c_wait_data_ready(hw); + + return 0; +} + +static void hw_dac_stop(struct hw *hw) +{ + u32 data; + data = hw_read_20kx(hw, GPIO_DATA); + data &= 0xFFFFFFFD; + hw_write_20kx(hw, GPIO_DATA, data); + usleep_range(10000, 11000); +} + +static void hw_dac_start(struct hw *hw) +{ + u32 data; + data = hw_read_20kx(hw, GPIO_DATA); + data |= 0x2; + hw_write_20kx(hw, GPIO_DATA, data); + msleep(50); +} + +static void hw_dac_reset(struct hw *hw) +{ + hw_dac_stop(hw); + hw_dac_start(hw); +} + +static int hw_dac_init(struct hw *hw, const struct dac_conf *info) +{ + int err; + u32 data; + int i; + struct regs_cs4382 cs_read = {0}; + struct regs_cs4382 cs_def = { + .mode_control_1 = 0x00000001, /* Mode Control 1 */ + .mode_control_2 = 0x00000000, /* Mode Control 2 */ + .mode_control_3 = 0x00000084, /* Mode Control 3 */ + .filter_control = 0x00000000, /* Filter Control */ + .invert_control = 0x00000000, /* Invert Control */ + .mix_control_P1 = 0x00000024, /* Mixing Control Pair 1 */ + .vol_control_A1 = 0x00000000, /* Vol Control A1 */ + .vol_control_B1 = 0x00000000, /* Vol Control B1 */ + .mix_control_P2 = 0x00000024, /* Mixing Control Pair 2 */ + .vol_control_A2 = 0x00000000, /* Vol Control A2 */ + .vol_control_B2 = 0x00000000, /* Vol Control B2 */ + .mix_control_P3 = 0x00000024, /* Mixing Control Pair 3 */ + .vol_control_A3 = 0x00000000, /* Vol Control A3 */ + .vol_control_B3 = 0x00000000, /* Vol Control B3 */ + .mix_control_P4 = 0x00000024, /* Mixing Control Pair 4 */ + .vol_control_A4 = 0x00000000, /* Vol Control A4 */ + .vol_control_B4 = 0x00000000 /* Vol Control B4 */ + }; + + if (hw->model == CTSB1270) { + hw_dac_stop(hw); + data = hw_read_20kx(hw, GPIO_DATA); + data &= ~0x0600; + if (1 == info->msr) + data |= 0x0000; /* Single Speed Mode 0-50kHz */ + else if (2 == info->msr) + data |= 0x0200; /* Double Speed Mode 50-100kHz */ + else + data |= 0x0600; /* Quad Speed Mode 100-200kHz */ + hw_write_20kx(hw, GPIO_DATA, data); + hw_dac_start(hw); + return 0; + } else if (hw->model == CTOK0010) { + hw_dac_stop(hw); + data = hw_read_20kx(hw, GPIO_DATA); + data |= 0x1000; + hw_write_20kx(hw, GPIO_DATA, data); + hw_dac_start(hw); + return 0; + } + + /* Set DAC reset bit as output */ + data = hw_read_20kx(hw, GPIO_CTRL); + data |= 0x02; + hw_write_20kx(hw, GPIO_CTRL, data); + + err = hw20k2_i2c_init(hw, 0x18, 1, 1); + if (err < 0) + goto End; + + for (i = 0; i < 2; i++) { + /* Reset DAC twice just in-case the chip + * didn't initialized properly */ + hw_dac_reset(hw); + hw_dac_reset(hw); + + if (hw20k2_i2c_read(hw, CS4382_MC1, &cs_read.mode_control_1)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_MC2, &cs_read.mode_control_2)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_MC3, &cs_read.mode_control_3)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_FC, &cs_read.filter_control)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_IC, &cs_read.invert_control)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_XC1, &cs_read.mix_control_P1)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_VCA1, &cs_read.vol_control_A1)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_VCB1, &cs_read.vol_control_B1)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_XC2, &cs_read.mix_control_P2)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_VCA2, &cs_read.vol_control_A2)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_VCB2, &cs_read.vol_control_B2)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_XC3, &cs_read.mix_control_P3)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_VCA3, &cs_read.vol_control_A3)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_VCB3, &cs_read.vol_control_B3)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_XC4, &cs_read.mix_control_P4)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_VCA4, &cs_read.vol_control_A4)) + continue; + + if (hw20k2_i2c_read(hw, CS4382_VCB4, &cs_read.vol_control_B4)) + continue; + + if (memcmp(&cs_read, &cs_def, sizeof(cs_read))) + continue; + else + break; + } + + if (i >= 2) + goto End; + + /* Note: Every I2C write must have some delay. + * This is not a requirement but the delay works here... */ + hw20k2_i2c_write(hw, CS4382_MC1, 0x80); + hw20k2_i2c_write(hw, CS4382_MC2, 0x10); + if (1 == info->msr) { + hw20k2_i2c_write(hw, CS4382_XC1, 0x24); + hw20k2_i2c_write(hw, CS4382_XC2, 0x24); + hw20k2_i2c_write(hw, CS4382_XC3, 0x24); + hw20k2_i2c_write(hw, CS4382_XC4, 0x24); + } else if (2 == info->msr) { + hw20k2_i2c_write(hw, CS4382_XC1, 0x25); + hw20k2_i2c_write(hw, CS4382_XC2, 0x25); + hw20k2_i2c_write(hw, CS4382_XC3, 0x25); + hw20k2_i2c_write(hw, CS4382_XC4, 0x25); + } else { + hw20k2_i2c_write(hw, CS4382_XC1, 0x26); + hw20k2_i2c_write(hw, CS4382_XC2, 0x26); + hw20k2_i2c_write(hw, CS4382_XC3, 0x26); + hw20k2_i2c_write(hw, CS4382_XC4, 0x26); + } + + return 0; +End: + + hw20k2_i2c_uninit(hw); + return -1; +} + +/* ADC operations */ +#define MAKE_WM8775_ADDR(addr, data) (u32)(((addr<<1)&0xFE)|((data>>8)&0x1)) +#define MAKE_WM8775_DATA(data) (u32)(data&0xFF) + +#define WM8775_IC 0x0B +#define WM8775_MMC 0x0C +#define WM8775_AADCL 0x0E +#define WM8775_AADCR 0x0F +#define WM8775_ADCMC 0x15 +#define WM8775_RESET 0x17 + +static int hw_is_adc_input_selected(struct hw *hw, enum ADCSRC type) +{ + u32 data; + if ((hw->model == CTSB1270) || (hw->model == CTOK0010)) { + /* Titanium HD has two ADC chips, one for line in and one */ + /* for MIC. Also, SE-300PCIE has a single ADC chip that */ + /* simultaneously supports 4-channel input. We don't need */ + /* to switch the ADC input. */ + return 1; + } + data = hw_read_20kx(hw, GPIO_DATA); + switch (type) { + case ADC_MICIN: + data = (data & (0x1 << 14)) ? 1 : 0; + break; + case ADC_LINEIN: + data = (data & (0x1 << 14)) ? 0 : 1; + break; + default: + data = 0; + } + return data; +} + +#define MIC_BOOST_0DB 0xCF +#define MIC_BOOST_STEPS_PER_DB 2 + +static void hw_wm8775_input_select(struct hw *hw, u8 input, s8 gain_in_db) +{ + u32 adcmc, gain; + + if (input > 3) + input = 3; + + adcmc = ((u32)1 << input) | 0x100; /* Link L+R gain... */ + + hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_ADCMC, adcmc), + MAKE_WM8775_DATA(adcmc)); + + if (gain_in_db < -103) + gain_in_db = -103; + if (gain_in_db > 24) + gain_in_db = 24; + + gain = gain_in_db * MIC_BOOST_STEPS_PER_DB + MIC_BOOST_0DB; + + hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCL, gain), + MAKE_WM8775_DATA(gain)); + /* ...so there should be no need for the following. */ + hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCR, gain), + MAKE_WM8775_DATA(gain)); +} + +static int hw_adc_input_select(struct hw *hw, enum ADCSRC type) +{ + u32 data; + data = hw_read_20kx(hw, GPIO_DATA); + switch (type) { + case ADC_MICIN: + data |= (0x1 << 14); + hw_write_20kx(hw, GPIO_DATA, data); + hw_wm8775_input_select(hw, 0, 20); /* Mic, 20dB */ + break; + case ADC_LINEIN: + data &= ~(0x1 << 14); + hw_write_20kx(hw, GPIO_DATA, data); + hw_wm8775_input_select(hw, 1, 0); /* Line-in, 0dB */ + break; + default: + break; + } + + return 0; +} + +static void hw_adc_stop(struct hw *hw) +{ + u32 data; + /* Reset the ADC (reset is active low). */ + data = hw_read_20kx(hw, GPIO_DATA); + data &= ~(0x1 << 15); + hw_write_20kx(hw, GPIO_DATA, data); + usleep_range(10000, 11000); +} + +static void hw_adc_start(struct hw *hw) +{ + u32 data; + /* Return the ADC to normal operation. */ + data = hw_read_20kx(hw, GPIO_DATA); + data |= (0x1 << 15); + hw_write_20kx(hw, GPIO_DATA, data); + msleep(50); +} + +static void hw_adc_reset(struct hw *hw) +{ + hw_adc_stop(hw); + hw_adc_start(hw); +} + +static int hw_adc_init(struct hw *hw, const struct adc_conf *info) +{ + int err; + u32 data, ctl; + + /* Set ADC reset bit as output */ + data = hw_read_20kx(hw, GPIO_CTRL); + data |= (0x1 << 15); + hw_write_20kx(hw, GPIO_CTRL, data); + + if (hw->model == CTOK0010) { + /* Manual ADC setup for SE-300PCIE is not needed. */ + hw_adc_reset(hw); + return 0; + } + + /* Initialize I2C */ + err = hw20k2_i2c_init(hw, 0x1A, 1, 1); + if (err < 0) { + dev_alert(hw->card->dev, "Failure to acquire I2C!!!\n"); + goto error; + } + + hw_adc_stop(hw); + + if (hw->model == CTSB1270) { + /* Set up the PCM4220 ADC on Titanium HD */ + data &= ~0x0C; + if (1 == info->msr) + data |= 0x00; /* Single Speed Mode 32-50kHz */ + else if (2 == info->msr) + data |= 0x08; /* Double Speed Mode 50-108kHz */ + else + data |= 0x04; /* Quad Speed Mode 108kHz-216kHz */ + hw_write_20kx(hw, GPIO_DATA, data); + } + + hw_adc_start(hw); + + /* I2C write to register offset 0x0B to set ADC LRCLK polarity */ + /* invert bit, interface format to I2S, word length to 24-bit, */ + /* enable ADC high pass filter. Fixes bug 5323? */ + hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_IC, 0x26), + MAKE_WM8775_DATA(0x26)); + + /* Set the master mode (256fs) */ + if (1 == info->msr) { + /* slave mode, 128x oversampling 256fs */ + hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_MMC, 0x02), + MAKE_WM8775_DATA(0x02)); + } else if ((2 == info->msr) || (4 == info->msr)) { + /* slave mode, 64x oversampling, 256fs */ + hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_MMC, 0x0A), + MAKE_WM8775_DATA(0x0A)); + } else { + dev_alert(hw->card->dev, + "Invalid master sampling rate (msr %d)!!!\n", + info->msr); + err = -EINVAL; + goto error; + } + + if (hw->model != CTSB1270) { + /* Configure GPIO bit 14 change to line-in/mic-in */ + ctl = hw_read_20kx(hw, GPIO_CTRL); + ctl |= 0x1 << 14; + hw_write_20kx(hw, GPIO_CTRL, ctl); + hw_adc_input_select(hw, ADC_LINEIN); + } else { + hw_wm8775_input_select(hw, 0, 0); + } + + return 0; +error: + hw20k2_i2c_uninit(hw); + return err; +} + +static struct capabilities hw_capabilities(struct hw *hw) +{ + struct capabilities cap; + + cap.digit_io_switch = 0; + cap.dedicated_mic = (hw->model == CTSB1270) || (hw->model == CTOK0010); + cap.dedicated_rca = hw->model == CTOK0010; + cap.output_switch = hw->model == CTSB1270; + cap.mic_source_switch = hw->model == CTSB1270; + + return cap; +} + +static int hw_output_switch_get(struct hw *hw) +{ + u32 data = hw_read_20kx(hw, GPIO_EXT_DATA); + + switch (data & 0x30) { + case 0x00: + return 0; + case 0x10: + return 1; + case 0x20: + return 2; + default: + return 3; + } +} + +static int hw_output_switch_put(struct hw *hw, int position) +{ + u32 data; + + if (position == hw_output_switch_get(hw)) + return 0; + + /* Mute line and headphones (intended for anti-pop). */ + data = hw_read_20kx(hw, GPIO_DATA); + data |= (0x03 << 11); + hw_write_20kx(hw, GPIO_DATA, data); + + data = hw_read_20kx(hw, GPIO_EXT_DATA) & ~0x30; + switch (position) { + case 0: + break; + case 1: + data |= 0x10; + break; + default: + data |= 0x20; + } + hw_write_20kx(hw, GPIO_EXT_DATA, data); + + /* Unmute line and headphones. */ + data = hw_read_20kx(hw, GPIO_DATA); + data &= ~(0x03 << 11); + hw_write_20kx(hw, GPIO_DATA, data); + + return 1; +} + +static int hw_mic_source_switch_get(struct hw *hw) +{ + struct hw20k2 *hw20k2 = (struct hw20k2 *)hw; + + return hw20k2->mic_source; +} + +static int hw_mic_source_switch_put(struct hw *hw, int position) +{ + struct hw20k2 *hw20k2 = (struct hw20k2 *)hw; + + if (position == hw20k2->mic_source) + return 0; + + switch (position) { + case 0: + hw_wm8775_input_select(hw, 0, 0); /* Mic, 0dB */ + break; + case 1: + hw_wm8775_input_select(hw, 1, 0); /* FP Mic, 0dB */ + break; + case 2: + hw_wm8775_input_select(hw, 3, 0); /* Aux Ext, 0dB */ + break; + default: + return 0; + } + + hw20k2->mic_source = position; + + return 1; +} + +static irqreturn_t ct_20k2_interrupt(int irq, void *dev_id) +{ + struct hw *hw = dev_id; + unsigned int status; + + status = hw_read_20kx(hw, GIP); + if (!status) + return IRQ_NONE; + + if (hw->irq_callback) + hw->irq_callback(hw->irq_callback_data, status); + + hw_write_20kx(hw, GIP, status); + return IRQ_HANDLED; +} + +static int hw_card_start(struct hw *hw) +{ + int err = 0; + struct pci_dev *pci = hw->pci; + unsigned int gctl; + const unsigned int dma_bits = BITS_PER_LONG; + + err = pci_enable_device(pci); + if (err < 0) + return err; + + /* Set DMA transfer mask */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(dma_bits))) + dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32)); + + if (!hw->io_base) { + err = pci_request_regions(pci, "XFi"); + if (err < 0) + goto error1; + + hw->io_base = pci_resource_start(hw->pci, 2); + hw->mem_base = ioremap(hw->io_base, + pci_resource_len(hw->pci, 2)); + if (!hw->mem_base) { + err = -ENOENT; + goto error2; + } + } + + /* Switch to 20k2 mode from UAA mode. */ + gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL); + set_field(&gctl, GCTL_UAA, 0); + hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl); + + if (hw->irq < 0) { + err = request_irq(pci->irq, ct_20k2_interrupt, IRQF_SHARED, + KBUILD_MODNAME, hw); + if (err < 0) { + dev_err(hw->card->dev, + "XFi: Cannot get irq %d\n", pci->irq); + goto error2; + } + hw->irq = pci->irq; + hw->card->sync_irq = hw->irq; + } + + pci_set_master(pci); + + return 0; + +/*error3: + iounmap((void *)hw->mem_base); + hw->mem_base = (unsigned long)NULL;*/ +error2: + pci_release_regions(pci); + hw->io_base = 0; +error1: + pci_disable_device(pci); + return err; +} + +static int hw_card_stop(struct hw *hw) +{ + unsigned int data; + + /* disable transport bus master and queueing of request */ + hw_write_20kx(hw, TRANSPORT_CTL, 0x00); + + /* disable pll */ + data = hw_read_20kx(hw, PLL_ENB); + hw_write_20kx(hw, PLL_ENB, (data & (~0x07))); + + /* TODO: Disable interrupt and so on... */ + return 0; +} + +static int hw_card_shutdown(struct hw *hw) +{ + if (hw->irq >= 0) + free_irq(hw->irq, hw); + + hw->irq = -1; + iounmap(hw->mem_base); + hw->mem_base = NULL; + + if (hw->io_base) + pci_release_regions(hw->pci); + + hw->io_base = 0; + + pci_disable_device(hw->pci); + + return 0; +} + +static int hw_card_init(struct hw *hw, struct card_conf *info) +{ + int err; + unsigned int gctl; + u32 data = 0; + struct dac_conf dac_info = {0}; + struct adc_conf adc_info = {0}; + struct daio_conf daio_info = {0}; + struct trn_conf trn_info = {0}; + + /* Get PCI io port/memory base address and + * do 20kx core switch if needed. */ + err = hw_card_start(hw); + if (err) + return err; + + /* PLL init */ + err = hw_pll_init(hw, info->rsr); + if (err < 0) + return err; + + /* kick off auto-init */ + err = hw_auto_init(hw); + if (err < 0) + return err; + + gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL); + set_field(&gctl, GCTL_DBP, 1); + set_field(&gctl, GCTL_TBP, 1); + set_field(&gctl, GCTL_FBP, 1); + set_field(&gctl, GCTL_DPC, 0); + hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl); + + /* Reset all global pending interrupts */ + hw_write_20kx(hw, GIE, 0); + /* Reset all SRC pending interrupts */ + hw_write_20kx(hw, SRC_IP, 0); + + if (hw->model == CTSB1270) { + hw_write_20kx(hw, GPIO_CTRL, 0x9E5F); + } else if (hw->model == CTOK0010) { + hw_write_20kx(hw, GPIO_CTRL, 0x9902); + } else { + /* TODO: detect the card ID and configure GPIO accordingly. */ + /* Configures GPIO (0xD802 0x98028) */ + /*hw_write_20kx(hw, GPIO_CTRL, 0x7F07);*/ + /* Configures GPIO (SB0880) */ + /*hw_write_20kx(hw, GPIO_CTRL, 0xFF07);*/ + hw_write_20kx(hw, GPIO_CTRL, 0xD802); + } + /* Enable audio ring */ + hw_write_20kx(hw, MIXER_AR_ENABLE, 0x01); + + trn_info.vm_pgt_phys = info->vm_pgt_phys; + err = hw_trn_init(hw, &trn_info); + if (err < 0) + return err; + + daio_info.msr = info->msr; + err = hw_daio_init(hw, &daio_info); + if (err < 0) + return err; + + dac_info.msr = info->msr; + err = hw_dac_init(hw, &dac_info); + if (err < 0) + return err; + + adc_info.msr = info->msr; + adc_info.input = ADC_LINEIN; + adc_info.mic20db = 0; + err = hw_adc_init(hw, &adc_info); + if (err < 0) + return err; + + data = hw_read_20kx(hw, SRC_MCTL); + data |= 0x1; /* Enables input from the audio ring */ + hw_write_20kx(hw, SRC_MCTL, data); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int hw_suspend(struct hw *hw) +{ + hw_card_stop(hw); + return 0; +} + +static int hw_resume(struct hw *hw, struct card_conf *info) +{ + /* Re-initialize card hardware. */ + return hw_card_init(hw, info); +} +#endif + +static u32 hw_read_20kx(struct hw *hw, u32 reg) +{ + return readl(hw->mem_base + reg); +} + +static void hw_write_20kx(struct hw *hw, u32 reg, u32 data) +{ + writel(data, hw->mem_base + reg); +} + +static const struct hw ct20k2_preset = { + .irq = -1, + + .card_init = hw_card_init, + .card_stop = hw_card_stop, + .pll_init = hw_pll_init, + .is_adc_source_selected = hw_is_adc_input_selected, + .select_adc_source = hw_adc_input_select, + .capabilities = hw_capabilities, + .output_switch_get = hw_output_switch_get, + .output_switch_put = hw_output_switch_put, + .mic_source_switch_get = hw_mic_source_switch_get, + .mic_source_switch_put = hw_mic_source_switch_put, +#ifdef CONFIG_PM_SLEEP + .suspend = hw_suspend, + .resume = hw_resume, +#endif + + .src_rsc_get_ctrl_blk = src_get_rsc_ctrl_blk, + .src_rsc_put_ctrl_blk = src_put_rsc_ctrl_blk, + .src_mgr_get_ctrl_blk = src_mgr_get_ctrl_blk, + .src_mgr_put_ctrl_blk = src_mgr_put_ctrl_blk, + .src_set_state = src_set_state, + .src_set_bm = src_set_bm, + .src_set_rsr = src_set_rsr, + .src_set_sf = src_set_sf, + .src_set_wr = src_set_wr, + .src_set_pm = src_set_pm, + .src_set_rom = src_set_rom, + .src_set_vo = src_set_vo, + .src_set_st = src_set_st, + .src_set_ie = src_set_ie, + .src_set_ilsz = src_set_ilsz, + .src_set_bp = src_set_bp, + .src_set_cisz = src_set_cisz, + .src_set_ca = src_set_ca, + .src_set_sa = src_set_sa, + .src_set_la = src_set_la, + .src_set_pitch = src_set_pitch, + .src_set_dirty = src_set_dirty, + .src_set_clear_zbufs = src_set_clear_zbufs, + .src_set_dirty_all = src_set_dirty_all, + .src_commit_write = src_commit_write, + .src_get_ca = src_get_ca, + .src_get_dirty = src_get_dirty, + .src_dirty_conj_mask = src_dirty_conj_mask, + .src_mgr_enbs_src = src_mgr_enbs_src, + .src_mgr_enb_src = src_mgr_enb_src, + .src_mgr_dsb_src = src_mgr_dsb_src, + .src_mgr_commit_write = src_mgr_commit_write, + + .srcimp_mgr_get_ctrl_blk = srcimp_mgr_get_ctrl_blk, + .srcimp_mgr_put_ctrl_blk = srcimp_mgr_put_ctrl_blk, + .srcimp_mgr_set_imaparc = srcimp_mgr_set_imaparc, + .srcimp_mgr_set_imapuser = srcimp_mgr_set_imapuser, + .srcimp_mgr_set_imapnxt = srcimp_mgr_set_imapnxt, + .srcimp_mgr_set_imapaddr = srcimp_mgr_set_imapaddr, + .srcimp_mgr_commit_write = srcimp_mgr_commit_write, + + .amixer_rsc_get_ctrl_blk = amixer_rsc_get_ctrl_blk, + .amixer_rsc_put_ctrl_blk = amixer_rsc_put_ctrl_blk, + .amixer_mgr_get_ctrl_blk = amixer_mgr_get_ctrl_blk, + .amixer_mgr_put_ctrl_blk = amixer_mgr_put_ctrl_blk, + .amixer_set_mode = amixer_set_mode, + .amixer_set_iv = amixer_set_iv, + .amixer_set_x = amixer_set_x, + .amixer_set_y = amixer_set_y, + .amixer_set_sadr = amixer_set_sadr, + .amixer_set_se = amixer_set_se, + .amixer_set_dirty = amixer_set_dirty, + .amixer_set_dirty_all = amixer_set_dirty_all, + .amixer_commit_write = amixer_commit_write, + .amixer_get_y = amixer_get_y, + .amixer_get_dirty = amixer_get_dirty, + + .dai_get_ctrl_blk = dai_get_ctrl_blk, + .dai_put_ctrl_blk = dai_put_ctrl_blk, + .dai_srt_set_srco = dai_srt_set_srco, + .dai_srt_set_srcm = dai_srt_set_srcm, + .dai_srt_set_rsr = dai_srt_set_rsr, + .dai_srt_set_drat = dai_srt_set_drat, + .dai_srt_set_ec = dai_srt_set_ec, + .dai_srt_set_et = dai_srt_set_et, + .dai_commit_write = dai_commit_write, + + .dao_get_ctrl_blk = dao_get_ctrl_blk, + .dao_put_ctrl_blk = dao_put_ctrl_blk, + .dao_set_spos = dao_set_spos, + .dao_commit_write = dao_commit_write, + .dao_get_spos = dao_get_spos, + + .daio_mgr_get_ctrl_blk = daio_mgr_get_ctrl_blk, + .daio_mgr_put_ctrl_blk = daio_mgr_put_ctrl_blk, + .daio_mgr_enb_dai = daio_mgr_enb_dai, + .daio_mgr_dsb_dai = daio_mgr_dsb_dai, + .daio_mgr_enb_dao = daio_mgr_enb_dao, + .daio_mgr_dsb_dao = daio_mgr_dsb_dao, + .daio_mgr_dao_init = daio_mgr_dao_init, + .daio_mgr_set_imaparc = daio_mgr_set_imaparc, + .daio_mgr_set_imapnxt = daio_mgr_set_imapnxt, + .daio_mgr_set_imapaddr = daio_mgr_set_imapaddr, + .daio_mgr_commit_write = daio_mgr_commit_write, + + .set_timer_irq = set_timer_irq, + .set_timer_tick = set_timer_tick, + .get_wc = get_wc, +}; + +int create_20k2_hw_obj(struct hw **rhw) +{ + struct hw20k2 *hw20k2; + + *rhw = NULL; + hw20k2 = kzalloc_obj(*hw20k2); + if (!hw20k2) + return -ENOMEM; + + hw20k2->hw = ct20k2_preset; + *rhw = &hw20k2->hw; + + return 0; +} + +int destroy_20k2_hw_obj(struct hw *hw) +{ + if (hw->io_base) + hw_card_shutdown(hw); + + kfree(hw); + return 0; +} diff --git a/sound/pci/ctxfi/cthw20k2.h b/sound/pci/ctxfi/cthw20k2.h new file mode 100644 index 000000000..6993a3d52 --- /dev/null +++ b/sound/pci/ctxfi/cthw20k2.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File cthw20k2.h + * + * @Brief + * This file contains the definition of hardware access methord. + * + * @Author Liu Chun + * @Date May 13 2008 + */ + +#ifndef CTHW20K2_H +#define CTHW20K2_H + +#include "cthardware.h" + +int create_20k2_hw_obj(struct hw **rhw); +int destroy_20k2_hw_obj(struct hw *hw); + +#endif /* CTHW20K2_H */ diff --git a/sound/pci/ctxfi/ctimap.c b/sound/pci/ctxfi/ctimap.c new file mode 100644 index 000000000..d5a53d2f5 --- /dev/null +++ b/sound/pci/ctxfi/ctimap.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctimap.c + * + * @Brief + * This file contains the implementation of generic input mapper operations + * for input mapper management. + * + * @Author Liu Chun + * @Date May 23 2008 + */ + +#include "ctimap.h" +#include + +int input_mapper_add(struct list_head *mappers, struct imapper *entry, + int (*map_op)(void *, struct imapper *), void *data) +{ + struct list_head *pos, *pre, *head; + struct imapper *pre_ent, *pos_ent; + + head = mappers; + + if (list_empty(head)) { + entry->next = entry->addr; + map_op(data, entry); + list_add(&entry->list, head); + return 0; + } + + list_for_each(pos, head) { + pos_ent = list_entry(pos, struct imapper, list); + if (pos_ent->slot > entry->slot) { + /* found a position in list */ + break; + } + } + + if (pos != head) { + pre = pos->prev; + if (pre == head) + pre = head->prev; + + __list_add(&entry->list, pos->prev, pos); + } else { + pre = head->prev; + pos = head->next; + list_add_tail(&entry->list, head); + } + + pre_ent = list_entry(pre, struct imapper, list); + pos_ent = list_entry(pos, struct imapper, list); + + entry->next = pos_ent->addr; + map_op(data, entry); + pre_ent->next = entry->addr; + map_op(data, pre_ent); + + return 0; +} + +int input_mapper_delete(struct list_head *mappers, struct imapper *entry, + int (*map_op)(void *, struct imapper *), void *data) +{ + struct list_head *next, *pre, *head; + struct imapper *pre_ent, *next_ent; + + head = mappers; + + if (list_empty(head)) + return 0; + + pre = (entry->list.prev == head) ? head->prev : entry->list.prev; + next = (entry->list.next == head) ? head->next : entry->list.next; + + if (pre == &entry->list) { + /* entry is the only one node in mappers list */ + entry->next = entry->addr = entry->user = entry->slot = 0; + map_op(data, entry); + list_del(&entry->list); + return 0; + } + + pre_ent = list_entry(pre, struct imapper, list); + next_ent = list_entry(next, struct imapper, list); + + pre_ent->next = next_ent->addr; + map_op(data, pre_ent); + list_del(&entry->list); + + return 0; +} + +void free_input_mapper_list(struct list_head *head) +{ + struct imapper *entry; + struct list_head *pos; + + while (!list_empty(head)) { + pos = head->next; + list_del(pos); + entry = list_entry(pos, struct imapper, list); + kfree(entry); + } +} + diff --git a/sound/pci/ctxfi/ctimap.h b/sound/pci/ctxfi/ctimap.h new file mode 100644 index 000000000..49b1bb831 --- /dev/null +++ b/sound/pci/ctxfi/ctimap.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctimap.h + * + * @Brief + * This file contains the definition of generic input mapper operations + * for input mapper management. + * + * @Author Liu Chun + * @Date May 23 2008 + */ + +#ifndef CTIMAP_H +#define CTIMAP_H + +#include + +struct imapper { + unsigned short slot; /* the id of the slot containing input data */ + unsigned short user; /* the id of the user resource consuming data */ + unsigned short addr; /* the input mapper ram id */ + unsigned short next; /* the next input mapper ram id */ + struct list_head list; +}; + +int input_mapper_add(struct list_head *mappers, struct imapper *entry, + int (*map_op)(void *, struct imapper *), void *data); + +int input_mapper_delete(struct list_head *mappers, struct imapper *entry, + int (*map_op)(void *, struct imapper *), void *data); + +void free_input_mapper_list(struct list_head *mappers); + +#endif /* CTIMAP_H */ diff --git a/sound/pci/ctxfi/ctmixer.c b/sound/pci/ctxfi/ctmixer.c new file mode 100644 index 000000000..9abe1d53c --- /dev/null +++ b/sound/pci/ctxfi/ctmixer.c @@ -0,0 +1,1272 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctmixer.c + * + * @Brief + * This file contains the implementation of alsa mixer device functions. + * + * @Author Liu Chun + * @Date May 28 2008 + */ + + +#include "ctmixer.h" +#include "ctamixer.h" +#include +#include +#include +#include +#include +#include + +enum CT_SUM_CTL { + SUM_IN_F, + SUM_IN_R, + SUM_IN_C, + SUM_IN_S, + SUM_IN_F_C, + + NUM_CT_SUMS +}; + +enum CT_AMIXER_CTL { + /* volume control mixers */ + AMIXER_MASTER_F, + AMIXER_MASTER_R, + AMIXER_MASTER_C, + AMIXER_MASTER_S, + AMIXER_PCM_F, + AMIXER_PCM_R, + AMIXER_PCM_C, + AMIXER_PCM_S, + AMIXER_SPDIFI, + AMIXER_LINEIN, + AMIXER_MIC, + AMIXER_SPDIFO, + AMIXER_WAVE_F, + AMIXER_WAVE_R, + AMIXER_WAVE_C, + AMIXER_WAVE_S, + AMIXER_MASTER_F_C, + AMIXER_PCM_F_C, + AMIXER_SPDIFI_C, + AMIXER_LINEIN_C, + AMIXER_MIC_C, + + /* this should always be the last one */ + NUM_CT_AMIXERS +}; + +enum CTALSA_MIXER_CTL { + /* volume control mixers */ + MIXER_MASTER_P, + MIXER_PCM_P, + MIXER_LINEIN_P, + MIXER_MIC_P, + MIXER_SPDIFI_P, + MIXER_SPDIFO_P, + MIXER_WAVEF_P, + MIXER_WAVER_P, + MIXER_WAVEC_P, + MIXER_WAVES_P, + MIXER_MASTER_C, + MIXER_PCM_C, + MIXER_LINEIN_C, + MIXER_MIC_C, + MIXER_SPDIFI_C, + + /* switch control mixers */ + MIXER_PCM_C_S, + MIXER_LINEIN_C_S, + MIXER_MIC_C_S, + MIXER_SPDIFI_C_S, + MIXER_SPDIFO_P_S, + MIXER_WAVEF_P_S, + MIXER_WAVER_P_S, + MIXER_WAVEC_P_S, + MIXER_WAVES_P_S, + MIXER_DIGITAL_IO_S, + MIXER_IEC958_MASK, + MIXER_IEC958_DEFAULT, + MIXER_IEC958_STREAM, + + /* this should always be the last one */ + NUM_CTALSA_MIXERS +}; + +#define VOL_MIXER_START MIXER_MASTER_P +#define VOL_MIXER_END MIXER_SPDIFI_C +#define VOL_MIXER_NUM (VOL_MIXER_END - VOL_MIXER_START + 1) +#define SWH_MIXER_START MIXER_PCM_C_S +#define SWH_MIXER_END MIXER_DIGITAL_IO_S +#define SWH_CAPTURE_START MIXER_PCM_C_S +#define SWH_CAPTURE_END MIXER_SPDIFI_C_S + +#define CHN_NUM 2 + +struct ct_kcontrol_init { + unsigned char ctl; + char *name; +}; + +static struct ct_kcontrol_init +ct_kcontrol_init_table[NUM_CTALSA_MIXERS] = { + [MIXER_MASTER_P] = { + .ctl = 1, + .name = "Master Playback Volume", + }, + [MIXER_MASTER_C] = { + .ctl = 1, + .name = "Master Capture Volume", + }, + [MIXER_PCM_P] = { + .ctl = 1, + .name = "PCM Playback Volume", + }, + [MIXER_PCM_C] = { + .ctl = 1, + .name = "PCM Capture Volume", + }, + [MIXER_LINEIN_P] = { + .ctl = 1, + .name = "Line Playback Volume", + }, + [MIXER_LINEIN_C] = { + .ctl = 1, + .name = "Line Capture Volume", + }, + [MIXER_MIC_P] = { + .ctl = 1, + .name = "Mic Playback Volume", + }, + [MIXER_MIC_C] = { + .ctl = 1, + .name = "Mic Capture Volume", + }, + [MIXER_SPDIFI_P] = { + .ctl = 1, + .name = "IEC958 Playback Volume", + }, + [MIXER_SPDIFI_C] = { + .ctl = 1, + .name = "IEC958 Capture Volume", + }, + [MIXER_SPDIFO_P] = { + .ctl = 1, + .name = "Digital Playback Volume", + }, + [MIXER_WAVEF_P] = { + .ctl = 1, + .name = "Front Playback Volume", + }, + [MIXER_WAVES_P] = { + .ctl = 1, + .name = "Side Playback Volume", + }, + [MIXER_WAVEC_P] = { + .ctl = 1, + .name = "Center/LFE Playback Volume", + }, + [MIXER_WAVER_P] = { + .ctl = 1, + .name = "Surround Playback Volume", + }, + [MIXER_PCM_C_S] = { + .ctl = 1, + .name = "PCM Capture Switch", + }, + [MIXER_LINEIN_C_S] = { + .ctl = 1, + .name = "Line Capture Switch", + }, + [MIXER_MIC_C_S] = { + .ctl = 1, + .name = "Mic Capture Switch", + }, + [MIXER_SPDIFI_C_S] = { + .ctl = 1, + .name = "IEC958 Capture Switch", + }, + [MIXER_SPDIFO_P_S] = { + .ctl = 1, + .name = "Digital Playback Switch", + }, + [MIXER_WAVEF_P_S] = { + .ctl = 1, + .name = "Front Playback Switch", + }, + [MIXER_WAVES_P_S] = { + .ctl = 1, + .name = "Side Playback Switch", + }, + [MIXER_WAVEC_P_S] = { + .ctl = 1, + .name = "Center/LFE Playback Switch", + }, + [MIXER_WAVER_P_S] = { + .ctl = 1, + .name = "Surround Playback Switch", + }, + [MIXER_DIGITAL_IO_S] = { + .ctl = 0, + .name = "Digit-IO Playback Switch", + }, +}; + +static void +ct_mixer_recording_select(struct ct_mixer *mixer, enum CT_AMIXER_CTL type); + +static void +ct_mixer_recording_unselect(struct ct_mixer *mixer, enum CT_AMIXER_CTL type); + +static enum CT_AMIXER_CTL get_amixer_index(enum CTALSA_MIXER_CTL alsa_index) +{ + switch (alsa_index) { + case MIXER_MASTER_P: return AMIXER_MASTER_F; + case MIXER_MASTER_C: return AMIXER_MASTER_F_C; + case MIXER_PCM_P: return AMIXER_PCM_F; + case MIXER_PCM_C: + case MIXER_PCM_C_S: return AMIXER_PCM_F_C; + case MIXER_LINEIN_P: return AMIXER_LINEIN; + case MIXER_LINEIN_C: + case MIXER_LINEIN_C_S: return AMIXER_LINEIN_C; + case MIXER_MIC_P: return AMIXER_MIC; + case MIXER_MIC_C: + case MIXER_MIC_C_S: return AMIXER_MIC_C; + case MIXER_SPDIFI_P: return AMIXER_SPDIFI; + case MIXER_SPDIFI_C: + case MIXER_SPDIFI_C_S: return AMIXER_SPDIFI_C; + case MIXER_SPDIFO_P: return AMIXER_SPDIFO; + case MIXER_WAVEF_P: return AMIXER_WAVE_F; + case MIXER_WAVES_P: return AMIXER_WAVE_S; + case MIXER_WAVEC_P: return AMIXER_WAVE_C; + case MIXER_WAVER_P: return AMIXER_WAVE_R; + default: return NUM_CT_AMIXERS; + } +} + +static enum CT_AMIXER_CTL get_recording_amixer(enum CT_AMIXER_CTL index) +{ + switch (index) { + case AMIXER_MASTER_F: return AMIXER_MASTER_F_C; + case AMIXER_PCM_F: return AMIXER_PCM_F_C; + case AMIXER_SPDIFI: return AMIXER_SPDIFI_C; + case AMIXER_LINEIN: return AMIXER_LINEIN_C; + case AMIXER_MIC: return AMIXER_MIC_C; + default: return NUM_CT_AMIXERS; + } +} + +static unsigned char +get_switch_state(struct ct_mixer *mixer, enum CTALSA_MIXER_CTL type) +{ + return (mixer->switch_state & (0x1 << (type - SWH_MIXER_START))) + ? 1 : 0; +} + +static void +set_switch_state(struct ct_mixer *mixer, + enum CTALSA_MIXER_CTL type, unsigned char state) +{ + if (state) + mixer->switch_state |= (0x1 << (type - SWH_MIXER_START)); + else + mixer->switch_state &= ~(0x1 << (type - SWH_MIXER_START)); +} + +#if 0 /* not used */ +/* Map integer value ranging from 0 to 65535 to 14-bit float value ranging + * from 2^-6 to (1+1023/1024) */ +static unsigned int uint16_to_float14(unsigned int x) +{ + unsigned int i; + + if (x < 17) + return 0; + + x *= 2031; + x /= 65535; + x += 16; + + /* i <= 6 */ + for (i = 0; !(x & 0x400); i++) + x <<= 1; + + x = (((7 - i) & 0x7) << 10) | (x & 0x3ff); + + return x; +} + +static unsigned int float14_to_uint16(unsigned int x) +{ + unsigned int e; + + if (!x) + return x; + + e = (x >> 10) & 0x7; + x &= 0x3ff; + x += 1024; + x >>= (7 - e); + x -= 16; + x *= 65535; + x /= 2031; + + return x; +} +#endif /* not used */ + +#define VOL_SCALE 0x1c +#define VOL_MAX 0x100 + +static const DECLARE_TLV_DB_SCALE(ct_vol_db_scale, -6400, 25, 1); + +static int ct_alsa_mix_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 = VOL_MAX; + + return 0; +} + +static int ct_alsa_mix_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + enum CT_AMIXER_CTL type = get_amixer_index(kcontrol->private_value); + struct amixer *amixer; + int i, val; + + for (i = 0; i < 2; i++) { + amixer = ((struct ct_mixer *)atc->mixer)-> + amixers[type*CHN_NUM+i]; + val = amixer->ops->get_scale(amixer) / VOL_SCALE; + if (val < 0) + val = 0; + else if (val > VOL_MAX) + val = VOL_MAX; + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int ct_alsa_mix_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + struct ct_mixer *mixer = atc->mixer; + enum CT_AMIXER_CTL type = get_amixer_index(kcontrol->private_value); + struct amixer *amixer; + int i, j, val, oval, change = 0; + + for (i = 0; i < 2; i++) { + val = ucontrol->value.integer.value[i]; + if (val < 0) + val = 0; + else if (val > VOL_MAX) + val = VOL_MAX; + val *= VOL_SCALE; + amixer = mixer->amixers[type*CHN_NUM+i]; + oval = amixer->ops->get_scale(amixer); + if (val != oval) { + amixer->ops->set_scale(amixer, val); + amixer->ops->commit_write(amixer); + change = 1; + /* Synchronize Master/PCM playback AMIXERs. */ + if (AMIXER_MASTER_F == type || AMIXER_PCM_F == type) { + for (j = 1; j < 4; j++) { + amixer = mixer-> + amixers[(type+j)*CHN_NUM+i]; + amixer->ops->set_scale(amixer, val); + amixer->ops->commit_write(amixer); + } + } + } + } + + return change; +} + +static struct snd_kcontrol_new vol_ctl = { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = ct_alsa_mix_volume_info, + .get = ct_alsa_mix_volume_get, + .put = ct_alsa_mix_volume_put, + .tlv = { .p = ct_vol_db_scale }, +}; + +static int output_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + static const char *const names[3] = { + "FP Headphones", "Headphones", "Speakers" + }; + + return snd_ctl_enum_info(info, 1, 3, names); +} + +static int output_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = atc->output_switch_get(atc); + return 0; +} + +static int output_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + return atc->output_switch_put(atc, ucontrol->value.enumerated.item[0]); +} + +static struct snd_kcontrol_new output_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Output Playback Enum", + .info = output_switch_info, + .get = output_switch_get, + .put = output_switch_put, +}; + +static int mic_source_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + static const char *const names[3] = { + "Mic", "FP Mic", "Aux" + }; + + return snd_ctl_enum_info(info, 1, 3, names); +} + +static int mic_source_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = atc->mic_source_switch_get(atc); + return 0; +} + +static int mic_source_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + return atc->mic_source_switch_put(atc, + ucontrol->value.enumerated.item[0]); +} + +static struct snd_kcontrol_new mic_source_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Source Capture Enum", + .info = mic_source_switch_info, + .get = mic_source_switch_get, + .put = mic_source_switch_put, +}; + +static void +do_line_mic_switch(struct ct_atc *atc, enum CTALSA_MIXER_CTL type) +{ + struct ct_mixer *mixer = atc->mixer; + + if (MIXER_LINEIN_C_S == type) { + atc->select_line_in(atc); + set_switch_state(mixer, MIXER_MIC_C_S, 0); + snd_ctl_notify(atc->card, SNDRV_CTL_EVENT_MASK_VALUE, + &mixer->line_mic_kctls[1]->id); + } else if (MIXER_MIC_C_S == type) { + atc->select_mic_in(atc); + set_switch_state(mixer, MIXER_LINEIN_C_S, 0); + snd_ctl_notify(atc->card, SNDRV_CTL_EVENT_MASK_VALUE, + &mixer->line_mic_kctls[0]->id); + } +} + +static void +do_digit_io_switch(struct ct_atc *atc, int state) +{ + struct ct_mixer *mixer = atc->mixer; + + if (state) { + atc->select_digit_io(atc); + atc->spdif_out_unmute(atc, + get_switch_state(mixer, MIXER_SPDIFO_P_S)); + atc->spdif_in_unmute(atc, 1); + atc->line_in_unmute(atc, 0); + return; + } + + if (get_switch_state(mixer, MIXER_LINEIN_C_S)) + atc->select_line_in(atc); + else if (get_switch_state(mixer, MIXER_MIC_C_S)) + atc->select_mic_in(atc); + + atc->spdif_out_unmute(atc, 0); + atc->spdif_in_unmute(atc, 0); + atc->line_in_unmute(atc, 1); + return; +} + +static void do_switch(struct ct_atc *atc, enum CTALSA_MIXER_CTL type, int state) +{ + struct ct_mixer *mixer = atc->mixer; + struct capabilities cap = atc->capabilities(atc); + + /* Do changes in mixer. */ + if ((SWH_CAPTURE_START <= type) && (SWH_CAPTURE_END >= type)) { + if (state) { + ct_mixer_recording_select(mixer, + get_amixer_index(type)); + } else { + ct_mixer_recording_unselect(mixer, + get_amixer_index(type)); + } + } + /* Do changes out of mixer. */ + if (!cap.dedicated_mic && + (MIXER_LINEIN_C_S == type || MIXER_MIC_C_S == type)) { + if (state) + do_line_mic_switch(atc, type); + atc->line_in_unmute(atc, state); + } else if (cap.dedicated_mic && (MIXER_LINEIN_C_S == type)) + atc->line_in_unmute(atc, state); + else if (cap.dedicated_mic && (MIXER_MIC_C_S == type)) + atc->mic_unmute(atc, state); + else if (MIXER_SPDIFI_C_S == type) + atc->spdif_in_unmute(atc, state); + else if (MIXER_WAVEF_P_S == type) { + if (cap.dedicated_rca) { + atc->rca_unmute(atc, atc->rca_state ? 0 : state); + atc->line_front_unmute(atc, atc->rca_state ? state : 0); + } else { + atc->line_front_unmute(atc, state); + } + } + else if (MIXER_WAVES_P_S == type) + atc->line_surround_unmute(atc, state); + else if (MIXER_WAVEC_P_S == type) + atc->line_clfe_unmute(atc, state); + else if (MIXER_WAVER_P_S == type) + atc->line_rear_unmute(atc, state); + else if (MIXER_SPDIFO_P_S == type) + atc->spdif_out_unmute(atc, state); + else if (MIXER_DIGITAL_IO_S == type) + do_digit_io_switch(atc, state); + + return; +} + +static int ct_alsa_mix_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + uinfo->value.integer.step = 1; + + return 0; +} + +static int ct_alsa_mix_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_mixer *mixer = + ((struct ct_atc *)snd_kcontrol_chip(kcontrol))->mixer; + enum CTALSA_MIXER_CTL type = kcontrol->private_value; + + ucontrol->value.integer.value[0] = get_switch_state(mixer, type); + return 0; +} + +static int ct_alsa_mix_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + struct ct_mixer *mixer = atc->mixer; + enum CTALSA_MIXER_CTL type = kcontrol->private_value; + int state; + + state = ucontrol->value.integer.value[0]; + if (get_switch_state(mixer, type) == state) + return 0; + + set_switch_state(mixer, type, state); + do_switch(atc, type, state); + + return 1; +} + +static struct snd_kcontrol_new swh_ctl = { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = ct_alsa_mix_switch_info, + .get = ct_alsa_mix_switch_get, + .put = ct_alsa_mix_switch_put +}; + +static int dedicated_rca_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { + "RCA", "Front" + }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int dedicated_rca_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = atc->rca_state; + return 0; +} + +static int dedicated_rca_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + unsigned int rca_state = ucontrol->value.enumerated.item[0]; + unsigned char state; + + if (rca_state > 1) + return -EINVAL; + + if (rca_state == atc->rca_state) + return 0; + + state = get_switch_state(atc->mixer, MIXER_WAVEF_P_S); + do_switch(atc, MIXER_WAVEF_P_S, 0); + + atc->rca_state = rca_state; + atc->dedicated_rca_select(atc); + + do_switch(atc, MIXER_WAVEF_P_S, state); + + return 1; +} + +static struct snd_kcontrol_new rca_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Playback Route", + .info = dedicated_rca_info, + .get = dedicated_rca_get, + .put = dedicated_rca_put, +}; + +static int ct_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int ct_spdif_get_mask(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static int ct_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + unsigned int status; + + atc->spdif_out_get_status(atc, &status); + + if (status == 0) + status = SNDRV_PCM_DEFAULT_CON_SPDIF; + + ucontrol->value.iec958.status[0] = (status >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (status >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (status >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (status >> 24) & 0xff; + + return 0; +} + +static int ct_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ct_atc *atc = snd_kcontrol_chip(kcontrol); + int change; + unsigned int status, old_status; + + status = (ucontrol->value.iec958.status[0] << 0) | + (ucontrol->value.iec958.status[1] << 8) | + (ucontrol->value.iec958.status[2] << 16) | + (ucontrol->value.iec958.status[3] << 24); + + atc->spdif_out_get_status(atc, &old_status); + change = (old_status != status); + if (change) + atc->spdif_out_set_status(atc, status); + + return change; +} + +static struct snd_kcontrol_new iec958_mask_ctl = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .count = 1, + .info = ct_spdif_info, + .get = ct_spdif_get_mask, + .private_value = MIXER_IEC958_MASK +}; + +static struct snd_kcontrol_new iec958_default_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .count = 1, + .info = ct_spdif_info, + .get = ct_spdif_get, + .put = ct_spdif_put, + .private_value = MIXER_IEC958_DEFAULT +}; + +static struct snd_kcontrol_new iec958_ctl = { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), + .count = 1, + .info = ct_spdif_info, + .get = ct_spdif_get, + .put = ct_spdif_put, + .private_value = MIXER_IEC958_STREAM +}; + +#define NUM_IEC958_CTL 3 + +static int +ct_mixer_kcontrol_new(struct ct_mixer *mixer, struct snd_kcontrol_new *new) +{ + struct snd_kcontrol *kctl; + int err; + + kctl = snd_ctl_new1(new, mixer->atc); + if (!kctl) + return -ENOMEM; + + if (SNDRV_CTL_ELEM_IFACE_PCM == kctl->id.iface) + kctl->id.device = IEC958; + + err = snd_ctl_add(mixer->atc->card, kctl); + if (err) + return err; + + switch (new->private_value) { + case MIXER_LINEIN_C_S: + mixer->line_mic_kctls[0] = kctl; break; + case MIXER_MIC_C_S: + mixer->line_mic_kctls[1] = kctl; break; + default: + break; + } + + return 0; +} + +static int ct_mixer_kcontrols_create(struct ct_mixer *mixer) +{ + enum CTALSA_MIXER_CTL type; + struct ct_atc *atc = mixer->atc; + struct capabilities cap = atc->capabilities(atc); + int err; + + /* Create snd kcontrol instances on demand */ + for (type = VOL_MIXER_START; type <= VOL_MIXER_END; type++) { + if (ct_kcontrol_init_table[type].ctl) { + vol_ctl.name = ct_kcontrol_init_table[type].name; + vol_ctl.private_value = (unsigned long)type; + err = ct_mixer_kcontrol_new(mixer, &vol_ctl); + if (err) + return err; + } + } + + ct_kcontrol_init_table[MIXER_DIGITAL_IO_S].ctl = cap.digit_io_switch; + + for (type = SWH_MIXER_START; type <= SWH_MIXER_END; type++) { + if (ct_kcontrol_init_table[type].ctl) { + swh_ctl.name = ct_kcontrol_init_table[type].name; + swh_ctl.private_value = (unsigned long)type; + err = ct_mixer_kcontrol_new(mixer, &swh_ctl); + if (err) + return err; + } + } + + err = ct_mixer_kcontrol_new(mixer, &iec958_mask_ctl); + if (err) + return err; + + err = ct_mixer_kcontrol_new(mixer, &iec958_default_ctl); + if (err) + return err; + + err = ct_mixer_kcontrol_new(mixer, &iec958_ctl); + if (err) + return err; + + if (cap.output_switch) { + err = ct_mixer_kcontrol_new(mixer, &output_ctl); + if (err) + return err; + } + + if (cap.mic_source_switch) { + err = ct_mixer_kcontrol_new(mixer, &mic_source_ctl); + if (err) + return err; + } + + if (cap.dedicated_rca) { + err = ct_mixer_kcontrol_new(mixer, &rca_ctl); + if (err) + return err; + + atc->line_front_unmute(atc, 0); + atc->rca_unmute(atc, 1); + } else { + atc->line_front_unmute(atc, 1); + } + set_switch_state(mixer, MIXER_WAVEF_P_S, 1); + atc->line_surround_unmute(atc, 0); + set_switch_state(mixer, MIXER_WAVES_P_S, 0); + atc->line_clfe_unmute(atc, 0); + set_switch_state(mixer, MIXER_WAVEC_P_S, 0); + atc->line_rear_unmute(atc, 0); + set_switch_state(mixer, MIXER_WAVER_P_S, 0); + atc->spdif_out_unmute(atc, 0); + set_switch_state(mixer, MIXER_SPDIFO_P_S, 0); + atc->line_in_unmute(atc, 0); + if (cap.dedicated_mic) + atc->mic_unmute(atc, 0); + atc->spdif_in_unmute(atc, 0); + set_switch_state(mixer, MIXER_PCM_C_S, 0); + set_switch_state(mixer, MIXER_LINEIN_C_S, 0); + set_switch_state(mixer, MIXER_SPDIFI_C_S, 0); + + return 0; +} + +static void +ct_mixer_recording_select(struct ct_mixer *mixer, enum CT_AMIXER_CTL type) +{ + struct amixer *amix_d; + struct sum *sum_c; + int i; + + for (i = 0; i < 2; i++) { + amix_d = mixer->amixers[type*CHN_NUM+i]; + sum_c = mixer->sums[SUM_IN_F_C*CHN_NUM+i]; + amix_d->ops->set_sum(amix_d, sum_c); + amix_d->ops->commit_write(amix_d); + } +} + +static void +ct_mixer_recording_unselect(struct ct_mixer *mixer, enum CT_AMIXER_CTL type) +{ + struct amixer *amix_d; + int i; + + for (i = 0; i < 2; i++) { + amix_d = mixer->amixers[type*CHN_NUM+i]; + amix_d->ops->set_sum(amix_d, NULL); + amix_d->ops->commit_write(amix_d); + } +} + +static int ct_mixer_get_resources(struct ct_mixer *mixer) +{ + struct sum_mgr *sum_mgr; + struct sum *sum; + struct sum_desc sum_desc = {0}; + struct amixer_mgr *amixer_mgr; + struct amixer *amixer; + struct amixer_desc am_desc = {0}; + int err; + int i; + + /* Allocate sum resources for mixer obj */ + sum_mgr = (struct sum_mgr *)mixer->atc->rsc_mgrs[SUM]; + sum_desc.msr = mixer->atc->msr; + for (i = 0; i < (NUM_CT_SUMS * CHN_NUM); i++) { + err = sum_mgr->get_sum(sum_mgr, &sum_desc, &sum); + if (err) { + dev_err(mixer->atc->card->dev, + "Failed to get sum resources for front output!\n"); + break; + } + mixer->sums[i] = sum; + } + if (err) + goto error1; + + /* Allocate amixer resources for mixer obj */ + amixer_mgr = (struct amixer_mgr *)mixer->atc->rsc_mgrs[AMIXER]; + am_desc.msr = mixer->atc->msr; + for (i = 0; i < (NUM_CT_AMIXERS * CHN_NUM); i++) { + err = amixer_mgr->get_amixer(amixer_mgr, &am_desc, &amixer); + if (err) { + dev_err(mixer->atc->card->dev, + "Failed to get amixer resources for mixer obj!\n"); + break; + } + mixer->amixers[i] = amixer; + } + if (err) + goto error2; + + return 0; + +error2: + for (i = 0; i < (NUM_CT_AMIXERS * CHN_NUM); i++) { + if (NULL != mixer->amixers[i]) { + amixer = mixer->amixers[i]; + amixer_mgr->put_amixer(amixer_mgr, amixer); + mixer->amixers[i] = NULL; + } + } +error1: + for (i = 0; i < (NUM_CT_SUMS * CHN_NUM); i++) { + if (NULL != mixer->sums[i]) { + sum_mgr->put_sum(sum_mgr, (struct sum *)mixer->sums[i]); + mixer->sums[i] = NULL; + } + } + + return err; +} + +static int ct_mixer_get_mem(struct ct_mixer **rmixer) +{ + struct ct_mixer *mixer; + size_t alloc_size; + + *rmixer = NULL; + /* Allocate mem for mixer obj */ + alloc_size = struct_size(mixer, amixers, NUM_CT_AMIXERS * CHN_NUM); + alloc_size += sizeof(*mixer->sums) * NUM_CT_SUMS * CHN_NUM; + mixer = kzalloc(alloc_size, GFP_KERNEL); + if (!mixer) + return -ENOMEM; + + mixer->sums = (struct sum **)(mixer->amixers + (NUM_CT_AMIXERS * CHN_NUM)); + + *rmixer = mixer; + return 0; +} + +static int ct_mixer_topology_build(struct ct_mixer *mixer) +{ + struct sum *sum; + struct amixer *amix_d, *amix_s; + enum CT_AMIXER_CTL i, j; + enum CT_SUM_CTL k; + + /* Build topology from destination to source */ + + /* Set up Master mixer */ + for (i = AMIXER_MASTER_F, k = SUM_IN_F; + i <= AMIXER_MASTER_S; i++, k++) { + amix_d = mixer->amixers[i*CHN_NUM]; + sum = mixer->sums[k*CHN_NUM]; + amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL); + amix_d = mixer->amixers[i*CHN_NUM+1]; + sum = mixer->sums[k*CHN_NUM+1]; + amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL); + } + + /* Set up Wave-out mixer */ + for (i = AMIXER_WAVE_F, j = AMIXER_MASTER_F; + i <= AMIXER_WAVE_S; i++, j++) { + amix_d = mixer->amixers[i*CHN_NUM]; + amix_s = mixer->amixers[j*CHN_NUM]; + amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL); + amix_d = mixer->amixers[i*CHN_NUM+1]; + amix_s = mixer->amixers[j*CHN_NUM+1]; + amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL); + } + + /* Set up S/PDIF-out mixer */ + amix_d = mixer->amixers[AMIXER_SPDIFO*CHN_NUM]; + amix_s = mixer->amixers[AMIXER_MASTER_F*CHN_NUM]; + amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL); + amix_d = mixer->amixers[AMIXER_SPDIFO*CHN_NUM+1]; + amix_s = mixer->amixers[AMIXER_MASTER_F*CHN_NUM+1]; + amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL); + + /* Set up PCM-in mixer */ + for (i = AMIXER_PCM_F, k = SUM_IN_F; i <= AMIXER_PCM_S; i++, k++) { + amix_d = mixer->amixers[i*CHN_NUM]; + sum = mixer->sums[k*CHN_NUM]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + amix_d = mixer->amixers[i*CHN_NUM+1]; + sum = mixer->sums[k*CHN_NUM+1]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + } + + /* Set up Line-in mixer */ + amix_d = mixer->amixers[AMIXER_LINEIN*CHN_NUM]; + sum = mixer->sums[SUM_IN_F*CHN_NUM]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + amix_d = mixer->amixers[AMIXER_LINEIN*CHN_NUM+1]; + sum = mixer->sums[SUM_IN_F*CHN_NUM+1]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + + /* Set up Mic-in mixer */ + amix_d = mixer->amixers[AMIXER_MIC*CHN_NUM]; + sum = mixer->sums[SUM_IN_F*CHN_NUM]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + amix_d = mixer->amixers[AMIXER_MIC*CHN_NUM+1]; + sum = mixer->sums[SUM_IN_F*CHN_NUM+1]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + + /* Set up S/PDIF-in mixer */ + amix_d = mixer->amixers[AMIXER_SPDIFI*CHN_NUM]; + sum = mixer->sums[SUM_IN_F*CHN_NUM]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + amix_d = mixer->amixers[AMIXER_SPDIFI*CHN_NUM+1]; + sum = mixer->sums[SUM_IN_F*CHN_NUM+1]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + + /* Set up Master recording mixer */ + amix_d = mixer->amixers[AMIXER_MASTER_F_C*CHN_NUM]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM]; + amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL); + amix_d = mixer->amixers[AMIXER_MASTER_F_C*CHN_NUM+1]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1]; + amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL); + + /* Set up PCM-in recording mixer */ + amix_d = mixer->amixers[AMIXER_PCM_F_C*CHN_NUM]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + amix_d = mixer->amixers[AMIXER_PCM_F_C*CHN_NUM+1]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + + /* Set up Line-in recording mixer */ + amix_d = mixer->amixers[AMIXER_LINEIN_C*CHN_NUM]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + amix_d = mixer->amixers[AMIXER_LINEIN_C*CHN_NUM+1]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + + /* Set up Mic-in recording mixer */ + amix_d = mixer->amixers[AMIXER_MIC_C*CHN_NUM]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + amix_d = mixer->amixers[AMIXER_MIC_C*CHN_NUM+1]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + + /* Set up S/PDIF-in recording mixer */ + amix_d = mixer->amixers[AMIXER_SPDIFI_C*CHN_NUM]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + amix_d = mixer->amixers[AMIXER_SPDIFI_C*CHN_NUM+1]; + sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1]; + amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum); + + return 0; +} + +static int mixer_set_input_port(struct amixer *amixer, struct rsc *rsc) +{ + amixer->ops->set_input(amixer, rsc); + amixer->ops->commit_write(amixer); + + return 0; +} + +static enum CT_AMIXER_CTL port_to_amixer(enum MIXER_PORT_T type) +{ + switch (type) { + case MIX_WAVE_FRONT: return AMIXER_WAVE_F; + case MIX_WAVE_SURROUND: return AMIXER_WAVE_S; + case MIX_WAVE_CENTLFE: return AMIXER_WAVE_C; + case MIX_WAVE_REAR: return AMIXER_WAVE_R; + case MIX_PCMO_FRONT: return AMIXER_MASTER_F_C; + case MIX_SPDIF_OUT: return AMIXER_SPDIFO; + case MIX_LINE_IN: return AMIXER_LINEIN; + case MIX_MIC_IN: return AMIXER_MIC; + case MIX_SPDIF_IN: return AMIXER_SPDIFI; + case MIX_PCMI_FRONT: return AMIXER_PCM_F; + case MIX_PCMI_SURROUND: return AMIXER_PCM_S; + case MIX_PCMI_CENTLFE: return AMIXER_PCM_C; + case MIX_PCMI_REAR: return AMIXER_PCM_R; + default: return 0; + } +} + +static int mixer_get_output_ports(struct ct_mixer *mixer, + enum MIXER_PORT_T type, + struct rsc **rleft, struct rsc **rright) +{ + enum CT_AMIXER_CTL amix = port_to_amixer(type); + + if (NULL != rleft) + *rleft = &((struct amixer *)mixer->amixers[amix*CHN_NUM])->rsc; + + if (NULL != rright) + *rright = + &((struct amixer *)mixer->amixers[amix*CHN_NUM+1])->rsc; + + return 0; +} + +static int mixer_set_input_left(struct ct_mixer *mixer, + enum MIXER_PORT_T type, struct rsc *rsc) +{ + enum CT_AMIXER_CTL amix = port_to_amixer(type); + + mixer_set_input_port(mixer->amixers[amix*CHN_NUM], rsc); + amix = get_recording_amixer(amix); + if (amix < NUM_CT_AMIXERS) + mixer_set_input_port(mixer->amixers[amix*CHN_NUM], rsc); + + return 0; +} + +static int +mixer_set_input_right(struct ct_mixer *mixer, + enum MIXER_PORT_T type, struct rsc *rsc) +{ + enum CT_AMIXER_CTL amix = port_to_amixer(type); + + mixer_set_input_port(mixer->amixers[amix*CHN_NUM+1], rsc); + amix = get_recording_amixer(amix); + if (amix < NUM_CT_AMIXERS) + mixer_set_input_port(mixer->amixers[amix*CHN_NUM+1], rsc); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mixer_resume(struct ct_mixer *mixer) +{ + int i, state; + struct amixer *amixer; + + /* resume topology and volume gain. */ + for (i = 0; i < NUM_CT_AMIXERS*CHN_NUM; i++) { + amixer = mixer->amixers[i]; + amixer->ops->commit_write(amixer); + } + + /* resume switch state. */ + for (i = SWH_MIXER_START; i <= SWH_MIXER_END; i++) { + state = get_switch_state(mixer, i); + do_switch(mixer->atc, i, state); + } + + return 0; +} +#endif + +int ct_mixer_destroy(struct ct_mixer *mixer) +{ + struct sum_mgr *sum_mgr = (struct sum_mgr *)mixer->atc->rsc_mgrs[SUM]; + struct amixer_mgr *amixer_mgr = + (struct amixer_mgr *)mixer->atc->rsc_mgrs[AMIXER]; + struct amixer *amixer; + int i = 0; + + /* Release amixer resources */ + for (i = 0; i < (NUM_CT_AMIXERS * CHN_NUM); i++) { + if (NULL != mixer->amixers[i]) { + amixer = mixer->amixers[i]; + amixer_mgr->put_amixer(amixer_mgr, amixer); + } + } + + /* Release sum resources */ + for (i = 0; i < (NUM_CT_SUMS * CHN_NUM); i++) { + if (NULL != mixer->sums[i]) + sum_mgr->put_sum(sum_mgr, (struct sum *)mixer->sums[i]); + } + + /* Release mem assigned to mixer object */ + kfree(mixer); + + return 0; +} + +int ct_mixer_create(struct ct_atc *atc, struct ct_mixer **rmixer) +{ + struct ct_mixer *mixer; + int err; + + *rmixer = NULL; + + /* Allocate mem for mixer obj */ + err = ct_mixer_get_mem(&mixer); + if (err) + return err; + + mixer->switch_state = 0; + mixer->atc = atc; + /* Set operations */ + mixer->get_output_ports = mixer_get_output_ports; + mixer->set_input_left = mixer_set_input_left; + mixer->set_input_right = mixer_set_input_right; +#ifdef CONFIG_PM_SLEEP + mixer->resume = mixer_resume; +#endif + + /* Allocate chip resources for mixer obj */ + err = ct_mixer_get_resources(mixer); + if (err) + goto error; + + /* Build internal mixer topology */ + ct_mixer_topology_build(mixer); + + *rmixer = mixer; + + return 0; + +error: + ct_mixer_destroy(mixer); + return err; +} + +int ct_alsa_mix_create(struct ct_atc *atc, + enum CTALSADEVS device, + const char *device_name) +{ + int err; + + /* Create snd kcontrol instances on demand */ + /* vol_ctl.device = swh_ctl.device = device; */ /* better w/ device 0 */ + err = ct_mixer_kcontrols_create((struct ct_mixer *)atc->mixer); + if (err) + return err; + + strscpy(atc->card->mixername, device_name); + + return 0; +} diff --git a/sound/pci/ctxfi/ctmixer.h b/sound/pci/ctxfi/ctmixer.h new file mode 100644 index 000000000..0c0963bba --- /dev/null +++ b/sound/pci/ctxfi/ctmixer.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctmixer.h + * + * @Brief + * This file contains the definition of the mixer device functions. + * + * @Author Liu Chun + * @Date Mar 28 2008 + */ + +#ifndef CTMIXER_H +#define CTMIXER_H + +#include "ctatc.h" +#include "ctresource.h" + +struct snd_kcontrol; + +#define INIT_VOL 0x1c00 + +enum MIXER_PORT_T { + MIX_WAVE_FRONT, + MIX_WAVE_REAR, + MIX_WAVE_CENTLFE, + MIX_WAVE_SURROUND, + MIX_SPDIF_OUT, + MIX_PCMO_FRONT, + MIX_MIC_IN, + MIX_LINE_IN, + MIX_SPDIF_IN, + MIX_PCMI_FRONT, + MIX_PCMI_REAR, + MIX_PCMI_CENTLFE, + MIX_PCMI_SURROUND, + + NUM_MIX_PORTS +}; + +/* alsa mixer descriptor */ +struct ct_mixer { + struct ct_atc *atc; + + struct sum **sums; /* sum resources for signal collection */ + struct snd_kcontrol *line_mic_kctls[2]; /* line/mic capture switch controls */ + unsigned int switch_state; /* A bit-map to indicate state of switches */ + + int (*get_output_ports)(struct ct_mixer *mixer, enum MIXER_PORT_T type, + struct rsc **rleft, struct rsc **rright); + + int (*set_input_left)(struct ct_mixer *mixer, + enum MIXER_PORT_T type, struct rsc *rsc); + int (*set_input_right)(struct ct_mixer *mixer, + enum MIXER_PORT_T type, struct rsc *rsc); +#ifdef CONFIG_PM_SLEEP + int (*resume)(struct ct_mixer *mixer); +#endif + struct amixer *amixers[]; /* amixer resources for volume control */ +}; + +int ct_alsa_mix_create(struct ct_atc *atc, + enum CTALSADEVS device, + const char *device_name); +int ct_mixer_create(struct ct_atc *atc, struct ct_mixer **rmixer); +int ct_mixer_destroy(struct ct_mixer *mixer); + +#endif /* CTMIXER_H */ diff --git a/sound/pci/ctxfi/ctpcm.c b/sound/pci/ctxfi/ctpcm.c new file mode 100644 index 000000000..fea6024f9 --- /dev/null +++ b/sound/pci/ctxfi/ctpcm.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctpcm.c + * + * @Brief + * This file contains the definition of the pcm device functions. + * + * @Author Liu Chun + * @Date Apr 2 2008 + */ + +#include "ctpcm.h" +#include "cttimer.h" +#include +#include + +/* Hardware descriptions for playback */ +static const struct snd_pcm_hardware ct_pcm_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_FLOAT_LE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_192000), + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = (64), + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware ct_spdif_passthru_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000), + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = (64), + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* Hardware descriptions for capture */ +static const struct snd_pcm_hardware ct_pcm_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_FLOAT_LE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_96000), + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = (384), + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +static void ct_atc_pcm_interrupt(struct ct_atc_pcm *atc_pcm) +{ + struct ct_atc_pcm *apcm = atc_pcm; + + if (!apcm->substream) + return; + + snd_pcm_period_elapsed(apcm->substream); +} + +static void ct_atc_pcm_free_substream(struct snd_pcm_runtime *runtime) +{ + struct ct_atc_pcm *apcm = runtime->private_data; + struct ct_atc *atc = snd_pcm_substream_chip(apcm->substream); + + atc->pcm_release_resources(atc, apcm); + ct_timer_instance_free(apcm->timer); + kfree(apcm); + runtime->private_data = NULL; +} + +/* pcm playback operations */ +static int ct_pcm_playback_open(struct snd_pcm_substream *substream) +{ + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm; + int err; + + apcm = kzalloc_obj(*apcm); + if (!apcm) + return -ENOMEM; + + apcm->substream = substream; + apcm->interrupt = ct_atc_pcm_interrupt; + if (IEC958 == substream->pcm->device) { + runtime->hw = ct_spdif_passthru_playback_hw; + atc->spdif_out_passthru(atc, 1); + } else { + runtime->hw = ct_pcm_playback_hw; + if (FRONT == substream->pcm->device) + runtime->hw.channels_max = 8; + } + + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto free_pcm; + + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 1024, UINT_MAX); + if (err < 0) + goto free_pcm; + + apcm->timer = ct_timer_instance_new(atc->timer, apcm); + if (!apcm->timer) { + err = -ENOMEM; + goto free_pcm; + } + runtime->private_data = apcm; + runtime->private_free = ct_atc_pcm_free_substream; + + return 0; + +free_pcm: + kfree(apcm); + return err; +} + +static int ct_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct ct_atc *atc = snd_pcm_substream_chip(substream); + + /* TODO: Notify mixer inactive. */ + if (IEC958 == substream->pcm->device) + atc->spdif_out_passthru(atc, 0); + + /* The ct_atc_pcm object will be freed by runtime->private_free */ + + return 0; +} + +static int ct_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct ct_atc_pcm *apcm = substream->runtime->private_data; + + /* clear previous resources */ + atc->pcm_release_resources(atc, apcm); + return 0; +} + +static int ct_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct ct_atc_pcm *apcm = substream->runtime->private_data; + + /* clear previous resources */ + atc->pcm_release_resources(atc, apcm); + return 0; +} + + +static int ct_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + int err; + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm = runtime->private_data; + + if (IEC958 == substream->pcm->device) + err = atc->spdif_passthru_playback_prepare(atc, apcm); + else + err = atc->pcm_playback_prepare(atc, apcm); + + if (err < 0) { + dev_err(atc->card->dev, + "Preparing pcm playback failed!!!\n"); + return err; + } + + return 0; +} + +static int +ct_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + atc->pcm_playback_start(atc, apcm); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + atc->pcm_playback_stop(atc, apcm); + break; + default: + break; + } + + return 0; +} + +static snd_pcm_uframes_t +ct_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + unsigned long position; + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm = runtime->private_data; + + /* Read out playback position */ + position = atc->pcm_playback_position(atc, apcm); + position = bytes_to_frames(runtime, position); + if (position >= runtime->buffer_size) + position = 0; + return position; +} + +/* pcm capture operations */ +static int ct_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm; + int err; + + apcm = kzalloc_obj(*apcm); + if (!apcm) + return -ENOMEM; + + apcm->started = 0; + apcm->substream = substream; + apcm->interrupt = ct_atc_pcm_interrupt; + runtime->hw = ct_pcm_capture_hw; + runtime->hw.rate_max = atc->rsr * atc->msr; + + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto free_pcm; + + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 1024, UINT_MAX); + if (err < 0) + goto free_pcm; + + apcm->timer = ct_timer_instance_new(atc->timer, apcm); + if (!apcm->timer) { + err = -ENOMEM; + goto free_pcm; + } + runtime->private_data = apcm; + runtime->private_free = ct_atc_pcm_free_substream; + + return 0; + +free_pcm: + kfree(apcm); + return err; +} + +static int ct_pcm_capture_close(struct snd_pcm_substream *substream) +{ + /* The ct_atc_pcm object will be freed by runtime->private_free */ + /* TODO: Notify mixer inactive. */ + return 0; +} + +static int ct_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + int err; + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm = runtime->private_data; + + err = atc->pcm_capture_prepare(atc, apcm); + if (err < 0) { + dev_err(atc->card->dev, + "Preparing pcm capture failed!!!\n"); + return err; + } + + return 0; +} + +static int +ct_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + atc->pcm_capture_start(atc, apcm); + break; + case SNDRV_PCM_TRIGGER_STOP: + atc->pcm_capture_stop(atc, apcm); + break; + default: + atc->pcm_capture_stop(atc, apcm); + break; + } + + return 0; +} + +static snd_pcm_uframes_t +ct_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + unsigned long position; + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm = runtime->private_data; + + /* Read out playback position */ + position = atc->pcm_capture_position(atc, apcm); + position = bytes_to_frames(runtime, position); + if (position >= runtime->buffer_size) + position = 0; + return position; +} + +/* PCM operators for playback */ +static const struct snd_pcm_ops ct_pcm_playback_ops = { + .open = ct_pcm_playback_open, + .close = ct_pcm_playback_close, + .hw_params = ct_pcm_hw_params, + .hw_free = ct_pcm_hw_free, + .prepare = ct_pcm_playback_prepare, + .trigger = ct_pcm_playback_trigger, + .pointer = ct_pcm_playback_pointer, +}; + +/* PCM operators for capture */ +static const struct snd_pcm_ops ct_pcm_capture_ops = { + .open = ct_pcm_capture_open, + .close = ct_pcm_capture_close, + .hw_params = ct_pcm_hw_params, + .hw_free = ct_pcm_hw_free, + .prepare = ct_pcm_capture_prepare, + .trigger = ct_pcm_capture_trigger, + .pointer = ct_pcm_capture_pointer, +}; + +static const struct snd_pcm_chmap_elem surround_map[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, + .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +static const struct snd_pcm_chmap_elem clfe_map[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, + .map = { SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, + { } +}; + +static const struct snd_pcm_chmap_elem side_map[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, + .map = { SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, + { } +}; + +/* Create ALSA pcm device */ +int ct_alsa_pcm_create(struct ct_atc *atc, + enum CTALSADEVS device, + const char *device_name) +{ + struct snd_pcm *pcm; + const struct snd_pcm_chmap_elem *map; + int chs; + int err; + int playback_count, capture_count; + + playback_count = (IEC958 == device) ? 1 : 256; + capture_count = (FRONT == device) ? 1 : 0; + err = snd_pcm_new(atc->card, "ctxfi", device, + playback_count, capture_count, &pcm); + if (err < 0) { + dev_err(atc->card->dev, "snd_pcm_new failed!! Err=%d\n", + err); + return err; + } + + pcm->private_data = atc; + pcm->info_flags = 0; + pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + strscpy(pcm->name, device_name, sizeof(pcm->name)); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ct_pcm_playback_ops); + + if (FRONT == device) + snd_pcm_set_ops(pcm, + SNDRV_PCM_STREAM_CAPTURE, &ct_pcm_capture_ops); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &atc->pci->dev, 128*1024, 128*1024); + + chs = 2; + switch (device) { + case FRONT: + chs = 8; + map = snd_pcm_std_chmaps; + break; + case SURROUND: + map = surround_map; + break; + case CLFE: + map = clfe_map; + break; + case SIDE: + map = side_map; + break; + default: + map = snd_pcm_std_chmaps; + break; + } + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, map, chs, + 0, NULL); + if (err < 0) + return err; + +#ifdef CONFIG_PM_SLEEP + atc->pcms[device] = pcm; +#endif + + return 0; +} diff --git a/sound/pci/ctxfi/ctpcm.h b/sound/pci/ctxfi/ctpcm.h new file mode 100644 index 000000000..8b39bdd26 --- /dev/null +++ b/sound/pci/ctxfi/ctpcm.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctpcm.h + * + * @Brief + * This file contains the definition of the pcm device functions. + * + * @Author Liu Chun + * @Date Mar 28 2008 + */ + +#ifndef CTPCM_H +#define CTPCM_H + +#include "ctatc.h" + +int ct_alsa_pcm_create(struct ct_atc *atc, + enum CTALSADEVS device, + const char *device_name); + +#endif /* CTPCM_H */ diff --git a/sound/pci/ctxfi/ctresource.c b/sound/pci/ctxfi/ctresource.c new file mode 100644 index 000000000..be1d3e613 --- /dev/null +++ b/sound/pci/ctxfi/ctresource.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctresource.c + * + * @Brief + * This file contains the implementation of some generic helper functions. + * + * @Author Liu Chun + * @Date May 15 2008 + */ + +#include "ctresource.h" +#include "cthardware.h" +#include +#include + +#define AUDIO_SLOT_BLOCK_NUM 256 + +/* Resource allocation based on bit-map management mechanism */ +static int +get_resource(u8 *rscs, unsigned int amount, + unsigned int multi, unsigned int *ridx) +{ + int i, j, k, n; + + /* Check whether there are sufficient resources to meet request. */ + for (i = 0, n = multi; i < amount; i++) { + j = i / 8; + k = i % 8; + if (rscs[j] & ((u8)1 << k)) { + n = multi; + continue; + } + if (!(--n)) + break; /* found sufficient contiguous resources */ + } + + if (i >= amount) { + /* Can not find sufficient contiguous resources */ + return -ENOENT; + } + + /* Mark the contiguous bits in resource bit-map as used */ + for (n = multi; n > 0; n--) { + j = i / 8; + k = i % 8; + rscs[j] |= ((u8)1 << k); + i--; + } + + *ridx = i + 1; + + return 0; +} + +static int put_resource(u8 *rscs, unsigned int multi, unsigned int idx) +{ + unsigned int i, j, k, n; + + /* Mark the contiguous bits in resource bit-map as used */ + for (n = multi, i = idx; n > 0; n--) { + j = i / 8; + k = i % 8; + rscs[j] &= ~((u8)1 << k); + i++; + } + + return 0; +} + +int mgr_get_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int *ridx) +{ + int err; + + if (n > mgr->avail) + return -ENOENT; + + err = get_resource(mgr->rscs, mgr->amount, n, ridx); + if (!err) + mgr->avail -= n; + + return err; +} + +int mgr_put_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int idx) +{ + put_resource(mgr->rscs, n, idx); + mgr->avail += n; + + return 0; +} + +static const unsigned char offset_in_audio_slot_block[NUM_RSCTYP] = { + /* SRC channel is at Audio Ring slot 1 every 16 slots. */ + [SRC] = 0x1, + [AMIXER] = 0x4, + [SUM] = 0xc, +}; + +static int rsc_index(const struct rsc *rsc) +{ + return rsc->conj; +} + +static int audio_ring_slot(const struct rsc *rsc) +{ + return (rsc->conj << 4) + offset_in_audio_slot_block[rsc->type]; +} + +static void rsc_next_conj(struct rsc *rsc) +{ + unsigned int i; + for (i = 0; (i < 8) && (!(rsc->msr & (0x1 << i))); ) + i++; + rsc->conj += (AUDIO_SLOT_BLOCK_NUM >> i); +} + +static void rsc_master(struct rsc *rsc) +{ + rsc->conj = rsc->idx; +} + +static const struct rsc_ops rsc_generic_ops = { + .index = rsc_index, + .output_slot = audio_ring_slot, + .master = rsc_master, + .next_conj = rsc_next_conj, +}; + +int +rsc_init(struct rsc *rsc, u32 idx, enum RSCTYP type, u32 msr, struct hw *hw) +{ + int err = 0; + + rsc->idx = idx; + rsc->conj = idx; + rsc->type = type; + rsc->msr = msr; + rsc->hw = hw; + rsc->ops = &rsc_generic_ops; + if (!hw) { + rsc->ctrl_blk = NULL; + return 0; + } + + switch (type) { + case SRC: + err = hw->src_rsc_get_ctrl_blk(&rsc->ctrl_blk); + break; + case AMIXER: + err = hw->amixer_rsc_get_ctrl_blk(&rsc->ctrl_blk); + break; + case SRCIMP: + case SUM: + case DAIO: + break; + default: + dev_err(((struct hw *)hw)->card->dev, + "Invalid resource type value %d!\n", type); + return -EINVAL; + } + + if (err) { + dev_err(((struct hw *)hw)->card->dev, + "Failed to get resource control block!\n"); + return err; + } + + return 0; +} + +int rsc_uninit(struct rsc *rsc) +{ + if ((NULL != rsc->hw) && (NULL != rsc->ctrl_blk)) { + switch (rsc->type) { + case SRC: + rsc->hw->src_rsc_put_ctrl_blk(rsc->ctrl_blk); + break; + case AMIXER: + rsc->hw->amixer_rsc_put_ctrl_blk(rsc->ctrl_blk); + break; + case SUM: + case DAIO: + break; + default: + dev_err(((struct hw *)rsc->hw)->card->dev, + "Invalid resource type value %d!\n", + rsc->type); + break; + } + + rsc->hw = rsc->ctrl_blk = NULL; + } + + rsc->idx = rsc->conj = 0; + rsc->type = NUM_RSCTYP; + rsc->msr = 0; + + return 0; +} + +int rsc_mgr_init(struct rsc_mgr *mgr, enum RSCTYP type, + unsigned int amount, struct hw *hw) +{ + int err = 0; + + mgr->type = NUM_RSCTYP; + + mgr->rscs = kzalloc(DIV_ROUND_UP(amount, 8), GFP_KERNEL); + if (!mgr->rscs) + return -ENOMEM; + + switch (type) { + case SRC: + err = hw->src_mgr_get_ctrl_blk(&mgr->ctrl_blk); + break; + case SRCIMP: + err = hw->srcimp_mgr_get_ctrl_blk(&mgr->ctrl_blk); + break; + case AMIXER: + err = hw->amixer_mgr_get_ctrl_blk(&mgr->ctrl_blk); + break; + case DAIO: + err = hw->daio_mgr_get_ctrl_blk(hw, &mgr->ctrl_blk); + break; + case SUM: + break; + default: + dev_err(hw->card->dev, + "Invalid resource type value %d!\n", type); + err = -EINVAL; + goto error; + } + + if (err) { + dev_err(hw->card->dev, + "Failed to get manager control block!\n"); + goto error; + } + + mgr->type = type; + mgr->avail = mgr->amount = amount; + mgr->hw = hw; + + return 0; + +error: + kfree(mgr->rscs); + return err; +} + +int rsc_mgr_uninit(struct rsc_mgr *mgr) +{ + kfree(mgr->rscs); + mgr->rscs = NULL; + + if ((NULL != mgr->hw) && (NULL != mgr->ctrl_blk)) { + switch (mgr->type) { + case SRC: + mgr->hw->src_mgr_put_ctrl_blk(mgr->ctrl_blk); + break; + case SRCIMP: + mgr->hw->srcimp_mgr_put_ctrl_blk(mgr->ctrl_blk); + break; + case AMIXER: + mgr->hw->amixer_mgr_put_ctrl_blk(mgr->ctrl_blk); + break; + case DAIO: + mgr->hw->daio_mgr_put_ctrl_blk(mgr->ctrl_blk); + break; + case SUM: + break; + default: + dev_err(((struct hw *)mgr->hw)->card->dev, + "Invalid resource type value %d!\n", + mgr->type); + break; + } + + mgr->hw = mgr->ctrl_blk = NULL; + } + + mgr->type = NUM_RSCTYP; + mgr->avail = mgr->amount = 0; + + return 0; +} diff --git a/sound/pci/ctxfi/ctresource.h b/sound/pci/ctxfi/ctresource.h new file mode 100644 index 000000000..58553bda4 --- /dev/null +++ b/sound/pci/ctxfi/ctresource.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctresource.h + * + * @Brief + * This file contains the definition of generic hardware resources for + * resource management. + * + * @Author Liu Chun + * @Date May 13 2008 + */ + +#ifndef CTRESOURCE_H +#define CTRESOURCE_H + +#include + +enum RSCTYP { + SRC, + SRCIMP, + AMIXER, + SUM, + DAIO, + NUM_RSCTYP /* This must be the last one and less than 16 */ +}; + +struct rsc_ops; + +struct rsc { + u32 idx:12; /* The index of a resource */ + u32 type:4; /* The type (RSCTYP) of a resource */ + u32 conj:12; /* Current conjugate index */ + u32 msr:4; /* The Master Sample Rate a resource working on */ + void *ctrl_blk; /* Chip specific control info block for a resource */ + struct hw *hw; /* Chip specific object for hardware access means */ + const struct rsc_ops *ops; /* Generic resource operations */ +}; + +struct rsc_ops { + void (*master)(struct rsc *rsc); /* Move to master resource */ + void (*next_conj)(struct rsc *rsc); /* Move to next conjugate resource */ + int (*index)(const struct rsc *rsc); /* Return the index of resource */ + /* Return the output slot number */ + int (*output_slot)(const struct rsc *rsc); +}; + +int +rsc_init(struct rsc *rsc, u32 idx, enum RSCTYP type, u32 msr, struct hw *hw); +int rsc_uninit(struct rsc *rsc); + +struct rsc_mgr { + enum RSCTYP type; /* The type (RSCTYP) of resource to manage */ + unsigned int amount; /* The total amount of a kind of resource */ + unsigned int avail; /* The amount of currently available resources */ + unsigned char *rscs; /* The bit-map for resource allocation */ + void *ctrl_blk; /* Chip specific control info block */ + struct hw *hw; /* Chip specific object for hardware access */ +}; + +/* Resource management is based on bit-map mechanism */ +int rsc_mgr_init(struct rsc_mgr *mgr, enum RSCTYP type, + unsigned int amount, struct hw *hw); +int rsc_mgr_uninit(struct rsc_mgr *mgr); +int mgr_get_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int *ridx); +int mgr_put_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int idx); + +#endif /* CTRESOURCE_H */ diff --git a/sound/pci/ctxfi/ctsrc.c b/sound/pci/ctxfi/ctsrc.c new file mode 100644 index 000000000..46dc1f509 --- /dev/null +++ b/sound/pci/ctxfi/ctsrc.c @@ -0,0 +1,851 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctsrc.c + * + * @Brief + * This file contains the implementation of the Sample Rate Convertor + * resource management object. + * + * @Author Liu Chun + * @Date May 13 2008 + */ + +#include "ctsrc.h" +#include "cthardware.h" +#include + +#define SRC_RESOURCE_NUM 256 +#define SRCIMP_RESOURCE_NUM 256 + +static unsigned int conj_mask; + +static int src_default_config_memrd(struct src *src); +static int src_default_config_memwr(struct src *src); +static int src_default_config_arcrw(struct src *src); + +static int (*src_default_config[3])(struct src *) = { + [MEMRD] = src_default_config_memrd, + [MEMWR] = src_default_config_memwr, + [ARCRW] = src_default_config_arcrw +}; + +static int src_set_state(struct src *src, unsigned int state) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_state(src->rsc.ctrl_blk, state); + + return 0; +} + +static int src_set_bm(struct src *src, unsigned int bm) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_bm(src->rsc.ctrl_blk, bm); + + return 0; +} + +static int src_set_sf(struct src *src, unsigned int sf) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_sf(src->rsc.ctrl_blk, sf); + + return 0; +} + +static int src_set_pm(struct src *src, unsigned int pm) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_pm(src->rsc.ctrl_blk, pm); + + return 0; +} + +static int src_set_rom(struct src *src, unsigned int rom) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_rom(src->rsc.ctrl_blk, rom); + + return 0; +} + +static int src_set_vo(struct src *src, unsigned int vo) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_vo(src->rsc.ctrl_blk, vo); + + return 0; +} + +static int src_set_st(struct src *src, unsigned int st) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_st(src->rsc.ctrl_blk, st); + + return 0; +} + +static int src_set_bp(struct src *src, unsigned int bp) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_bp(src->rsc.ctrl_blk, bp); + + return 0; +} + +static int src_set_cisz(struct src *src, unsigned int cisz) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_cisz(src->rsc.ctrl_blk, cisz); + + return 0; +} + +static int src_set_ca(struct src *src, unsigned int ca) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_ca(src->rsc.ctrl_blk, ca); + + return 0; +} + +static int src_set_sa(struct src *src, unsigned int sa) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_sa(src->rsc.ctrl_blk, sa); + + return 0; +} + +static int src_set_la(struct src *src, unsigned int la) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_la(src->rsc.ctrl_blk, la); + + return 0; +} + +static int src_set_pitch(struct src *src, unsigned int pitch) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_pitch(src->rsc.ctrl_blk, pitch); + + return 0; +} + +static int src_set_clear_zbufs(struct src *src) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1); + + return 0; +} + +static int src_commit_write(struct src *src) +{ + struct hw *hw; + int i; + unsigned int dirty = 0; + + hw = src->rsc.hw; + src->rsc.ops->master(&src->rsc); + if (src->rsc.msr > 1) { + /* Save dirty flags for conjugate resource programming */ + dirty = hw->src_get_dirty(src->rsc.ctrl_blk) & conj_mask; + } + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + + /* Program conjugate parameter mixer resources */ + if (MEMWR == src->mode) + return 0; + + for (i = 1; i < src->rsc.msr; i++) { + src->rsc.ops->next_conj(&src->rsc); + hw->src_set_dirty(src->rsc.ctrl_blk, dirty); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + } + src->rsc.ops->master(&src->rsc); + + return 0; +} + +static int src_get_ca(struct src *src) +{ + struct hw *hw; + + hw = src->rsc.hw; + return hw->src_get_ca(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); +} + +static int src_init(struct src *src) +{ + src_default_config[src->mode](src); + + return 0; +} + +static struct src *src_next_interleave(struct src *src) +{ + return src->intlv; +} + +static int src_default_config_memrd(struct src *src) +{ + struct hw *hw = src->rsc.hw; + unsigned int rsr, msr; + + hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF); + hw->src_set_bm(src->rsc.ctrl_blk, 1); + for (rsr = 0, msr = src->rsc.msr; msr > 1; msr >>= 1) + rsr++; + + hw->src_set_rsr(src->rsc.ctrl_blk, rsr); + hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_S16); + hw->src_set_wr(src->rsc.ctrl_blk, 0); + hw->src_set_pm(src->rsc.ctrl_blk, 0); + hw->src_set_rom(src->rsc.ctrl_blk, 0); + hw->src_set_vo(src->rsc.ctrl_blk, 0); + hw->src_set_st(src->rsc.ctrl_blk, 0); + hw->src_set_ilsz(src->rsc.ctrl_blk, src->multi - 1); + hw->src_set_cisz(src->rsc.ctrl_blk, 0x80); + hw->src_set_sa(src->rsc.ctrl_blk, 0x0); + hw->src_set_la(src->rsc.ctrl_blk, 0x1000); + hw->src_set_ca(src->rsc.ctrl_blk, 0x80); + hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000); + hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1); + + src->rsc.ops->master(&src->rsc); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + + for (msr = 1; msr < src->rsc.msr; msr++) { + src->rsc.ops->next_conj(&src->rsc); + hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + } + src->rsc.ops->master(&src->rsc); + + return 0; +} + +static int src_default_config_memwr(struct src *src) +{ + struct hw *hw = src->rsc.hw; + + hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF); + hw->src_set_bm(src->rsc.ctrl_blk, 1); + hw->src_set_rsr(src->rsc.ctrl_blk, 0); + hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_S16); + hw->src_set_wr(src->rsc.ctrl_blk, 1); + hw->src_set_pm(src->rsc.ctrl_blk, 0); + hw->src_set_rom(src->rsc.ctrl_blk, 0); + hw->src_set_vo(src->rsc.ctrl_blk, 0); + hw->src_set_st(src->rsc.ctrl_blk, 0); + hw->src_set_ilsz(src->rsc.ctrl_blk, 0); + hw->src_set_cisz(src->rsc.ctrl_blk, 0x80); + hw->src_set_sa(src->rsc.ctrl_blk, 0x0); + hw->src_set_la(src->rsc.ctrl_blk, 0x1000); + hw->src_set_ca(src->rsc.ctrl_blk, 0x80); + hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000); + hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1); + + src->rsc.ops->master(&src->rsc); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + + return 0; +} + +static int src_default_config_arcrw(struct src *src) +{ + struct hw *hw = src->rsc.hw; + unsigned int rsr, msr; + unsigned int dirty; + + hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF); + hw->src_set_bm(src->rsc.ctrl_blk, 0); + for (rsr = 0, msr = src->rsc.msr; msr > 1; msr >>= 1) + rsr++; + + hw->src_set_rsr(src->rsc.ctrl_blk, rsr); + hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_F32); + hw->src_set_wr(src->rsc.ctrl_blk, 0); + hw->src_set_pm(src->rsc.ctrl_blk, 0); + hw->src_set_rom(src->rsc.ctrl_blk, 0); + hw->src_set_vo(src->rsc.ctrl_blk, 0); + hw->src_set_st(src->rsc.ctrl_blk, 0); + hw->src_set_ilsz(src->rsc.ctrl_blk, 0); + hw->src_set_cisz(src->rsc.ctrl_blk, 0x80); + hw->src_set_sa(src->rsc.ctrl_blk, 0x0); + /*hw->src_set_sa(src->rsc.ctrl_blk, 0x100);*/ + hw->src_set_la(src->rsc.ctrl_blk, 0x1000); + /*hw->src_set_la(src->rsc.ctrl_blk, 0x03ffffe0);*/ + hw->src_set_ca(src->rsc.ctrl_blk, 0x80); + hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000); + hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1); + + dirty = hw->src_get_dirty(src->rsc.ctrl_blk); + src->rsc.ops->master(&src->rsc); + for (msr = 0; msr < src->rsc.msr; msr++) { + hw->src_set_dirty(src->rsc.ctrl_blk, dirty); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + src->rsc.ops->next_conj(&src->rsc); + } + src->rsc.ops->master(&src->rsc); + + return 0; +} + +static const struct src_rsc_ops src_rsc_ops = { + .set_state = src_set_state, + .set_bm = src_set_bm, + .set_sf = src_set_sf, + .set_pm = src_set_pm, + .set_rom = src_set_rom, + .set_vo = src_set_vo, + .set_st = src_set_st, + .set_bp = src_set_bp, + .set_cisz = src_set_cisz, + .set_ca = src_set_ca, + .set_sa = src_set_sa, + .set_la = src_set_la, + .set_pitch = src_set_pitch, + .set_clr_zbufs = src_set_clear_zbufs, + .commit_write = src_commit_write, + .get_ca = src_get_ca, + .init = src_init, + .next_interleave = src_next_interleave, +}; + +static int +src_rsc_init(struct src *src, u32 idx, + const struct src_desc *desc, struct src_mgr *mgr) +{ + int err; + int i, n; + struct src *p; + + n = (MEMRD == desc->mode) ? desc->multi : 1; + for (i = 0, p = src; i < n; i++, p++) { + err = rsc_init(&p->rsc, idx + i, SRC, desc->msr, mgr->mgr.hw); + if (err) + goto error1; + + /* Initialize src specific rsc operations */ + p->ops = &src_rsc_ops; + p->multi = (0 == i) ? desc->multi : 1; + p->mode = desc->mode; + src_default_config[desc->mode](p); + mgr->src_enable(mgr, p); + p->intlv = p + 1; + } + (--p)->intlv = NULL; /* Set @intlv of the last SRC to NULL */ + + mgr->commit_write(mgr); + + return 0; + +error1: + for (i--, p--; i >= 0; i--, p--) { + mgr->src_disable(mgr, p); + rsc_uninit(&p->rsc); + } + mgr->commit_write(mgr); + return err; +} + +static int src_rsc_uninit(struct src *src, struct src_mgr *mgr) +{ + int i, n; + struct src *p; + + n = (MEMRD == src->mode) ? src->multi : 1; + for (i = 0, p = src; i < n; i++, p++) { + mgr->src_disable(mgr, p); + rsc_uninit(&p->rsc); + p->multi = 0; + p->ops = NULL; + p->mode = NUM_SRCMODES; + p->intlv = NULL; + } + mgr->commit_write(mgr); + + return 0; +} + +static int +get_src_rsc(struct src_mgr *mgr, const struct src_desc *desc, struct src **rsrc) +{ + unsigned int idx = SRC_RESOURCE_NUM; + int err; + struct src *src; + + *rsrc = NULL; + + /* Check whether there are sufficient src resources to meet request. */ + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + if (MEMRD == desc->mode) + err = mgr_get_resource(&mgr->mgr, desc->multi, &idx); + else + err = mgr_get_resource(&mgr->mgr, 1, &idx); + } + if (err) { + dev_err(mgr->card->dev, + "Can't meet SRC resource request!\n"); + return err; + } + + /* Allocate mem for master src resource */ + if (MEMRD == desc->mode) + src = kcalloc(desc->multi, sizeof(*src), GFP_KERNEL); + else + src = kzalloc(sizeof(*src), GFP_KERNEL); + + if (!src) { + err = -ENOMEM; + goto error1; + } + + err = src_rsc_init(src, idx, desc, mgr); + if (err) + goto error2; + + *rsrc = src; + + return 0; + +error2: + kfree(src); +error1: + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + if (MEMRD == desc->mode) + mgr_put_resource(&mgr->mgr, desc->multi, idx); + else + mgr_put_resource(&mgr->mgr, 1, idx); + } + return err; +} + +static int put_src_rsc(struct src_mgr *mgr, struct src *src) +{ + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + src->rsc.ops->master(&src->rsc); + if (MEMRD == src->mode) + mgr_put_resource(&mgr->mgr, src->multi, + src->rsc.ops->index(&src->rsc)); + else + mgr_put_resource(&mgr->mgr, 1, src->rsc.ops->index(&src->rsc)); + } + src_rsc_uninit(src, mgr); + kfree(src); + + return 0; +} + +static int src_enable_s(struct src_mgr *mgr, struct src *src) +{ + struct hw *hw = mgr->mgr.hw; + int i; + + src->rsc.ops->master(&src->rsc); + for (i = 0; i < src->rsc.msr; i++) { + hw->src_mgr_enbs_src(mgr->mgr.ctrl_blk, + src->rsc.ops->index(&src->rsc)); + src->rsc.ops->next_conj(&src->rsc); + } + src->rsc.ops->master(&src->rsc); + + return 0; +} + +static int src_enable(struct src_mgr *mgr, struct src *src) +{ + struct hw *hw = mgr->mgr.hw; + int i; + + src->rsc.ops->master(&src->rsc); + for (i = 0; i < src->rsc.msr; i++) { + hw->src_mgr_enb_src(mgr->mgr.ctrl_blk, + src->rsc.ops->index(&src->rsc)); + src->rsc.ops->next_conj(&src->rsc); + } + src->rsc.ops->master(&src->rsc); + + return 0; +} + +static int src_disable(struct src_mgr *mgr, struct src *src) +{ + struct hw *hw = mgr->mgr.hw; + int i; + + src->rsc.ops->master(&src->rsc); + for (i = 0; i < src->rsc.msr; i++) { + hw->src_mgr_dsb_src(mgr->mgr.ctrl_blk, + src->rsc.ops->index(&src->rsc)); + src->rsc.ops->next_conj(&src->rsc); + } + src->rsc.ops->master(&src->rsc); + + return 0; +} + +static int src_mgr_commit_write(struct src_mgr *mgr) +{ + struct hw *hw = mgr->mgr.hw; + + hw->src_mgr_commit_write(hw, mgr->mgr.ctrl_blk); + + return 0; +} + +int src_mgr_create(struct hw *hw, void **rsrc_mgr) +{ + int err, i; + struct src_mgr *src_mgr; + + *rsrc_mgr = NULL; + src_mgr = kzalloc_obj(*src_mgr); + if (!src_mgr) + return -ENOMEM; + + err = rsc_mgr_init(&src_mgr->mgr, SRC, SRC_RESOURCE_NUM, hw); + if (err) + goto error1; + + spin_lock_init(&src_mgr->mgr_lock); + conj_mask = hw->src_dirty_conj_mask(); + + src_mgr->get_src = get_src_rsc; + src_mgr->put_src = put_src_rsc; + src_mgr->src_enable_s = src_enable_s; + src_mgr->src_enable = src_enable; + src_mgr->src_disable = src_disable; + src_mgr->commit_write = src_mgr_commit_write; + src_mgr->card = hw->card; + + /* Disable all SRC resources. */ + for (i = 0; i < 256; i++) + hw->src_mgr_dsb_src(src_mgr->mgr.ctrl_blk, i); + + hw->src_mgr_commit_write(hw, src_mgr->mgr.ctrl_blk); + + *rsrc_mgr = src_mgr; + + return 0; + +error1: + kfree(src_mgr); + return err; +} + +int src_mgr_destroy(void *ptr) +{ + struct src_mgr *src_mgr = ptr; + rsc_mgr_uninit(&src_mgr->mgr); + kfree(src_mgr); + + return 0; +} + +/* SRCIMP resource manager operations */ + +static void srcimp_master(struct rsc *rsc) +{ + rsc->conj = 0; + rsc->idx = container_of(rsc, struct srcimp, rsc)->idx[0]; +} + +static void srcimp_next_conj(struct rsc *rsc) +{ + rsc->conj++; +} + +static int srcimp_index(const struct rsc *rsc) +{ + return container_of(rsc, struct srcimp, rsc)->idx[rsc->conj]; +} + +static const struct rsc_ops srcimp_basic_rsc_ops = { + .master = srcimp_master, + .next_conj = srcimp_next_conj, + .index = srcimp_index, + .output_slot = NULL, +}; + +static int srcimp_map(struct srcimp *srcimp, struct src *src, struct rsc *input) +{ + struct imapper *entry; + int i; + + srcimp->rsc.ops->master(&srcimp->rsc); + src->rsc.ops->master(&src->rsc); + input->ops->master(input); + + /* Program master and conjugate resources */ + for (i = 0; i < srcimp->rsc.msr; i++) { + entry = &srcimp->imappers[i]; + entry->slot = input->ops->output_slot(input); + entry->user = src->rsc.ops->index(&src->rsc); + entry->addr = srcimp->rsc.ops->index(&srcimp->rsc); + srcimp->mgr->imap_add(srcimp->mgr, entry); + srcimp->mapped |= (0x1 << i); + + srcimp->rsc.ops->next_conj(&srcimp->rsc); + input->ops->next_conj(input); + } + + srcimp->rsc.ops->master(&srcimp->rsc); + input->ops->master(input); + + return 0; +} + +static int srcimp_unmap(struct srcimp *srcimp) +{ + int i; + + /* Program master and conjugate resources */ + for (i = 0; i < srcimp->rsc.msr; i++) { + if (srcimp->mapped & (0x1 << i)) { + srcimp->mgr->imap_delete(srcimp->mgr, + &srcimp->imappers[i]); + srcimp->mapped &= ~(0x1 << i); + } + } + + return 0; +} + +static const struct srcimp_rsc_ops srcimp_ops = { + .map = srcimp_map, + .unmap = srcimp_unmap +}; + +static int srcimp_rsc_init(struct srcimp *srcimp, + const struct srcimp_desc *desc, + struct srcimp_mgr *mgr) +{ + int err; + + err = rsc_init(&srcimp->rsc, srcimp->idx[0], + SRCIMP, desc->msr, mgr->mgr.hw); + if (err) + return err; + + /* Set srcimp specific operations */ + srcimp->rsc.ops = &srcimp_basic_rsc_ops; + srcimp->ops = &srcimp_ops; + srcimp->mgr = mgr; + + srcimp->rsc.ops->master(&srcimp->rsc); + + return 0; +} + +static int srcimp_rsc_uninit(struct srcimp *srcimp) +{ + srcimp->ops = NULL; + srcimp->mgr = NULL; + rsc_uninit(&srcimp->rsc); + + return 0; +} + +static int get_srcimp_rsc(struct srcimp_mgr *mgr, + const struct srcimp_desc *desc, + struct srcimp **rsrcimp) +{ + int err, i; + unsigned int idx; + struct srcimp *srcimp; + + *rsrcimp = NULL; + + /* Allocate mem for SRCIMP resource */ + srcimp = kzalloc_flex(*srcimp, imappers, desc->msr); + if (!srcimp) + return -ENOMEM; + + /* Check whether there are sufficient SRCIMP resources. */ + err = 0; + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + for (i = 0; i < desc->msr; i++) { + err = mgr_get_resource(&mgr->mgr, 1, &idx); + if (err) + break; + + srcimp->idx[i] = idx; + } + } + if (err) { + dev_err(mgr->card->dev, + "Can't meet SRCIMP resource request!\n"); + goto error1; + } + + err = srcimp_rsc_init(srcimp, desc, mgr); + if (err) + goto error1; + + *rsrcimp = srcimp; + + return 0; + +error1: + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + for (i--; i >= 0; i--) + mgr_put_resource(&mgr->mgr, 1, srcimp->idx[i]); + } + kfree(srcimp); + return err; +} + +static int put_srcimp_rsc(struct srcimp_mgr *mgr, struct srcimp *srcimp) +{ + int i; + + scoped_guard(spinlock_irqsave, &mgr->mgr_lock) { + for (i = 0; i < srcimp->rsc.msr; i++) + mgr_put_resource(&mgr->mgr, 1, srcimp->idx[i]); + } + srcimp_rsc_uninit(srcimp); + kfree(srcimp); + + return 0; +} + +static int srcimp_map_op(void *data, struct imapper *entry) +{ + struct rsc_mgr *mgr = &((struct srcimp_mgr *)data)->mgr; + struct hw *hw = mgr->hw; + + hw->srcimp_mgr_set_imaparc(mgr->ctrl_blk, entry->slot); + hw->srcimp_mgr_set_imapuser(mgr->ctrl_blk, entry->user); + hw->srcimp_mgr_set_imapnxt(mgr->ctrl_blk, entry->next); + hw->srcimp_mgr_set_imapaddr(mgr->ctrl_blk, entry->addr); + hw->srcimp_mgr_commit_write(mgr->hw, mgr->ctrl_blk); + + return 0; +} + +static int srcimp_imap_add(struct srcimp_mgr *mgr, struct imapper *entry) +{ + guard(spinlock_irqsave)(&mgr->imap_lock); + if ((0 == entry->addr) && (mgr->init_imap_added)) { + input_mapper_delete(&mgr->imappers, + mgr->init_imap, srcimp_map_op, mgr); + mgr->init_imap_added = 0; + } + return input_mapper_add(&mgr->imappers, entry, srcimp_map_op, mgr); +} + +static int srcimp_imap_delete(struct srcimp_mgr *mgr, struct imapper *entry) +{ + int err; + + guard(spinlock_irqsave)(&mgr->imap_lock); + err = input_mapper_delete(&mgr->imappers, entry, srcimp_map_op, mgr); + if (list_empty(&mgr->imappers)) { + input_mapper_add(&mgr->imappers, mgr->init_imap, + srcimp_map_op, mgr); + mgr->init_imap_added = 1; + } + + return err; +} + +int srcimp_mgr_create(struct hw *hw, void **rsrcimp_mgr) +{ + int err; + struct srcimp_mgr *srcimp_mgr; + struct imapper *entry; + + *rsrcimp_mgr = NULL; + srcimp_mgr = kzalloc_obj(*srcimp_mgr); + if (!srcimp_mgr) + return -ENOMEM; + + err = rsc_mgr_init(&srcimp_mgr->mgr, SRCIMP, SRCIMP_RESOURCE_NUM, hw); + if (err) + goto error1; + + spin_lock_init(&srcimp_mgr->mgr_lock); + spin_lock_init(&srcimp_mgr->imap_lock); + INIT_LIST_HEAD(&srcimp_mgr->imappers); + entry = kzalloc_obj(*entry); + if (!entry) { + err = -ENOMEM; + goto error2; + } + entry->slot = entry->addr = entry->next = entry->user = 0; + list_add(&entry->list, &srcimp_mgr->imappers); + srcimp_mgr->init_imap = entry; + srcimp_mgr->init_imap_added = 1; + + srcimp_mgr->get_srcimp = get_srcimp_rsc; + srcimp_mgr->put_srcimp = put_srcimp_rsc; + srcimp_mgr->imap_add = srcimp_imap_add; + srcimp_mgr->imap_delete = srcimp_imap_delete; + srcimp_mgr->card = hw->card; + + *rsrcimp_mgr = srcimp_mgr; + + return 0; + +error2: + rsc_mgr_uninit(&srcimp_mgr->mgr); +error1: + kfree(srcimp_mgr); + return err; +} + +int srcimp_mgr_destroy(void *ptr) +{ + struct srcimp_mgr *srcimp_mgr = ptr; + + /* free src input mapper list */ + scoped_guard(spinlock_irqsave, &srcimp_mgr->imap_lock) { + free_input_mapper_list(&srcimp_mgr->imappers); + } + + rsc_mgr_uninit(&srcimp_mgr->mgr); + kfree(srcimp_mgr); + + return 0; +} diff --git a/sound/pci/ctxfi/ctsrc.h b/sound/pci/ctxfi/ctsrc.h new file mode 100644 index 000000000..7ba94adde --- /dev/null +++ b/sound/pci/ctxfi/ctsrc.h @@ -0,0 +1,148 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctsrc.h + * + * @Brief + * This file contains the definition of the Sample Rate Convertor + * resource management object. + * + * @Author Liu Chun + * @Date May 13 2008 + */ + +#ifndef CTSRC_H +#define CTSRC_H + +#include "ctresource.h" +#include "ctimap.h" +#include +#include +#include + +#define SRC_STATE_OFF 0x0 +#define SRC_STATE_INIT 0x4 +#define SRC_STATE_RUN 0x5 + +#define SRC_SF_U8 0x0 +#define SRC_SF_S16 0x1 +#define SRC_SF_S24 0x2 +#define SRC_SF_S32 0x3 +#define SRC_SF_F32 0x4 + +/* Define the descriptor of a src resource */ +enum SRCMODE { + MEMRD, /* Read data from host memory */ + MEMWR, /* Write data to host memory */ + ARCRW, /* Read from and write to audio ring channel */ + NUM_SRCMODES +}; + +struct src_rsc_ops; + +struct src { + struct rsc rsc; /* Basic resource info */ + struct src *intlv; /* Pointer to next interleaved SRC in a series */ + const struct src_rsc_ops *ops; /* SRC specific operations */ + /* Number of contiguous srcs for interleaved usage */ + unsigned char multi; + unsigned char mode; /* Working mode of this SRC resource */ +}; + +struct src_rsc_ops { + int (*set_state)(struct src *src, unsigned int state); + int (*set_bm)(struct src *src, unsigned int bm); + int (*set_sf)(struct src *src, unsigned int sf); + int (*set_pm)(struct src *src, unsigned int pm); + int (*set_rom)(struct src *src, unsigned int rom); + int (*set_vo)(struct src *src, unsigned int vo); + int (*set_st)(struct src *src, unsigned int st); + int (*set_bp)(struct src *src, unsigned int bp); + int (*set_cisz)(struct src *src, unsigned int cisz); + int (*set_ca)(struct src *src, unsigned int ca); + int (*set_sa)(struct src *src, unsigned int sa); + int (*set_la)(struct src *src, unsigned int la); + int (*set_pitch)(struct src *src, unsigned int pitch); + int (*set_clr_zbufs)(struct src *src); + int (*commit_write)(struct src *src); + int (*get_ca)(struct src *src); + int (*init)(struct src *src); + struct src* (*next_interleave)(struct src *src); +}; + +/* Define src resource request description info */ +struct src_desc { + /* Number of contiguous master srcs for interleaved usage */ + unsigned char multi; + unsigned char msr; + unsigned char mode; /* Working mode of the requested srcs */ +}; + +/* Define src manager object */ +struct src_mgr { + struct rsc_mgr mgr; /* Basic resource manager info */ + struct snd_card *card; /* pointer to this card */ + spinlock_t mgr_lock; + + /* request src resource */ + int (*get_src)(struct src_mgr *mgr, + const struct src_desc *desc, struct src **rsrc); + /* return src resource */ + int (*put_src)(struct src_mgr *mgr, struct src *src); + int (*src_enable_s)(struct src_mgr *mgr, struct src *src); + int (*src_enable)(struct src_mgr *mgr, struct src *src); + int (*src_disable)(struct src_mgr *mgr, struct src *src); + int (*commit_write)(struct src_mgr *mgr); +}; + +/* Define the descriptor of a SRC Input Mapper resource */ +struct srcimp_mgr; +struct srcimp_rsc_ops; + +struct srcimp { + struct rsc rsc; + unsigned char idx[8]; + unsigned int mapped; /* A bit-map indicating which conj rsc is mapped */ + struct srcimp_mgr *mgr; + const struct srcimp_rsc_ops *ops; + struct imapper imappers[]; +}; + +struct srcimp_rsc_ops { + int (*map)(struct srcimp *srcimp, struct src *user, struct rsc *input); + int (*unmap)(struct srcimp *srcimp); +}; + +/* Define SRCIMP resource request description info */ +struct srcimp_desc { + unsigned int msr; +}; + +struct srcimp_mgr { + struct rsc_mgr mgr; /* Basic resource manager info */ + struct snd_card *card; /* pointer to this card */ + spinlock_t mgr_lock; + spinlock_t imap_lock; + struct list_head imappers; + struct imapper *init_imap; + unsigned int init_imap_added; + + /* request srcimp resource */ + int (*get_srcimp)(struct srcimp_mgr *mgr, + const struct srcimp_desc *desc, + struct srcimp **rsrcimp); + /* return srcimp resource */ + int (*put_srcimp)(struct srcimp_mgr *mgr, struct srcimp *srcimp); + int (*imap_add)(struct srcimp_mgr *mgr, struct imapper *entry); + int (*imap_delete)(struct srcimp_mgr *mgr, struct imapper *entry); +}; + +/* Constructor and destructor of SRC resource manager */ +int src_mgr_create(struct hw *hw, void **ptr); +int src_mgr_destroy(void *ptr); +/* Constructor and destructor of SRCIMP resource manager */ +int srcimp_mgr_create(struct hw *hw, void **ptr); +int srcimp_mgr_destroy(void *ptr); + +#endif /* CTSRC_H */ diff --git a/sound/pci/ctxfi/cttimer.c b/sound/pci/ctxfi/cttimer.c new file mode 100644 index 000000000..cc379d880 --- /dev/null +++ b/sound/pci/ctxfi/cttimer.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PCM timer handling on ctxfi + */ + +#include +#include +#include +#include +#include +#include "ctatc.h" +#include "cthardware.h" +#include "cttimer.h" + +static bool use_system_timer; +MODULE_PARM_DESC(use_system_timer, "Force to use system-timer"); +module_param(use_system_timer, bool, 0444); + +struct ct_timer_ops { + void (*init)(struct ct_timer_instance *); + void (*prepare)(struct ct_timer_instance *); + void (*start)(struct ct_timer_instance *); + void (*stop)(struct ct_timer_instance *); + void (*free_instance)(struct ct_timer_instance *); + void (*interrupt)(struct ct_timer *); + void (*free_global)(struct ct_timer *); +}; + +/* timer instance -- assigned to each PCM stream */ +struct ct_timer_instance { + spinlock_t lock; + struct ct_timer *timer_base; + struct ct_atc_pcm *apcm; + struct snd_pcm_substream *substream; + struct timer_list timer; + struct list_head instance_list; + struct list_head running_list; + unsigned int position; + unsigned int frag_count; + unsigned int running:1; + unsigned int need_update:1; +}; + +/* timer instance manager */ +struct ct_timer { + spinlock_t lock; /* global timer lock (for xfitimer) */ + spinlock_t list_lock; /* lock for instance list */ + struct ct_atc *atc; + const struct ct_timer_ops *ops; + struct list_head instance_head; + struct list_head running_head; + unsigned int wc; /* current wallclock */ + unsigned int irq_handling:1; /* in IRQ handling */ + unsigned int reprogram:1; /* need to reprogram the internval */ + unsigned int running:1; /* global timer running */ +}; + + +/* + * system-timer-based updates + */ + +static void ct_systimer_callback(struct timer_list *t) +{ + struct ct_timer_instance *ti = timer_container_of(ti, t, timer); + struct snd_pcm_substream *substream = ti->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm = ti->apcm; + unsigned int period_size = runtime->period_size; + unsigned int buffer_size = runtime->buffer_size; + unsigned int position, dist, interval; + + position = substream->ops->pointer(substream); + dist = (position + buffer_size - ti->position) % buffer_size; + if (dist >= period_size || + position / period_size != ti->position / period_size) { + apcm->interrupt(apcm); + ti->position = position; + } + /* Add extra HZ*5/1000 to avoid overrun issue when recording + * at 8kHz in 8-bit format or at 88kHz in 24-bit format. */ + interval = ((period_size - (position % period_size)) + * HZ + (runtime->rate - 1)) / runtime->rate + HZ * 5 / 1000; + guard(spinlock_irqsave)(&ti->lock); + if (ti->running) + mod_timer(&ti->timer, jiffies + interval); +} + +static void ct_systimer_init(struct ct_timer_instance *ti) +{ + timer_setup(&ti->timer, ct_systimer_callback, 0); +} + +static void ct_systimer_start(struct ct_timer_instance *ti) +{ + struct snd_pcm_runtime *runtime = ti->substream->runtime; + + guard(spinlock_irqsave)(&ti->lock); + ti->running = 1; + mod_timer(&ti->timer, + jiffies + (runtime->period_size * HZ + + (runtime->rate - 1)) / runtime->rate); +} + +static void ct_systimer_stop(struct ct_timer_instance *ti) +{ + guard(spinlock_irqsave)(&ti->lock); + ti->running = 0; + timer_delete(&ti->timer); +} + +static void ct_systimer_prepare(struct ct_timer_instance *ti) +{ + ct_systimer_stop(ti); + timer_delete_sync_try(&ti->timer); +} + +#define ct_systimer_free ct_systimer_prepare + +static const struct ct_timer_ops ct_systimer_ops = { + .init = ct_systimer_init, + .free_instance = ct_systimer_free, + .prepare = ct_systimer_prepare, + .start = ct_systimer_start, + .stop = ct_systimer_stop, +}; + + +/* + * Handling multiple streams using a global emu20k1 timer irq + */ + +#define CT_TIMER_FREQ 48000 +#define MIN_TICKS 1 +#define MAX_TICKS ((1 << 13) - 1) + +static void ct_xfitimer_irq_rearm(struct ct_timer *atimer, int ticks) +{ + struct hw *hw = atimer->atc->hw; + if (ticks > MAX_TICKS) + ticks = MAX_TICKS; + hw->set_timer_tick(hw, ticks); + if (!atimer->running) + hw->set_timer_irq(hw, 1); + atimer->running = 1; +} + +static void ct_xfitimer_irq_stop(struct ct_timer *atimer) +{ + if (atimer->running) { + struct hw *hw = atimer->atc->hw; + hw->set_timer_irq(hw, 0); + hw->set_timer_tick(hw, 0); + atimer->running = 0; + } +} + +static inline unsigned int ct_xfitimer_get_wc(struct ct_timer *atimer) +{ + struct hw *hw = atimer->atc->hw; + return hw->get_wc(hw); +} + +/* + * reprogram the timer interval; + * checks the running instance list and determines the next timer interval. + * also updates the each stream position, returns the number of streams + * to call snd_pcm_period_elapsed() appropriately + * + * call this inside the lock and irq disabled + */ +static int ct_xfitimer_reprogram(struct ct_timer *atimer, int can_update) +{ + struct ct_timer_instance *ti; + unsigned int min_intr = (unsigned int)-1; + int updates = 0; + unsigned int wc, diff; + + if (list_empty(&atimer->running_head)) { + ct_xfitimer_irq_stop(atimer); + atimer->reprogram = 0; /* clear flag */ + return 0; + } + + wc = ct_xfitimer_get_wc(atimer); + diff = wc - atimer->wc; + atimer->wc = wc; + list_for_each_entry(ti, &atimer->running_head, running_list) { + if (ti->frag_count > diff) + ti->frag_count -= diff; + else { + unsigned int pos; + unsigned int period_size, rate; + + period_size = ti->substream->runtime->period_size; + rate = ti->substream->runtime->rate; + pos = ti->substream->ops->pointer(ti->substream); + if (pos / period_size != ti->position / period_size) { + ti->need_update = 1; + ti->position = pos; + updates++; + } + pos %= period_size; + pos = period_size - pos; + ti->frag_count = div_u64((u64)pos * CT_TIMER_FREQ + + rate - 1, rate); + } + if (ti->need_update && !can_update) + min_intr = 0; /* pending to the next irq */ + if (ti->frag_count < min_intr) + min_intr = ti->frag_count; + } + + if (min_intr < MIN_TICKS) + min_intr = MIN_TICKS; + ct_xfitimer_irq_rearm(atimer, min_intr); + atimer->reprogram = 0; /* clear flag */ + return updates; +} + +/* look through the instance list and call period_elapsed if needed */ +static void ct_xfitimer_check_period(struct ct_timer *atimer) +{ + struct ct_timer_instance *ti; + + guard(spinlock_irqsave)(&atimer->list_lock); + list_for_each_entry(ti, &atimer->instance_head, instance_list) { + if (ti->running && ti->need_update) { + ti->need_update = 0; + ti->apcm->interrupt(ti->apcm); + } + } +} + +/* Handle timer-interrupt */ +static void ct_xfitimer_callback(struct ct_timer *atimer) +{ + int update; + + guard(spinlock_irqsave)(&atimer->lock); + atimer->irq_handling = 1; + do { + update = ct_xfitimer_reprogram(atimer, 1); + spin_unlock(&atimer->lock); + if (update) + ct_xfitimer_check_period(atimer); + spin_lock(&atimer->lock); + } while (atimer->reprogram); + atimer->irq_handling = 0; +} + +static void ct_xfitimer_prepare(struct ct_timer_instance *ti) +{ + ti->frag_count = ti->substream->runtime->period_size; + ti->running = 0; + ti->need_update = 0; +} + + +/* start/stop the timer */ +static void ct_xfitimer_update(struct ct_timer *atimer) +{ + guard(spinlock_irqsave)(&atimer->lock); + if (atimer->irq_handling) { + /* reached from IRQ handler; let it handle later */ + atimer->reprogram = 1; + return; + } + + ct_xfitimer_irq_stop(atimer); + ct_xfitimer_reprogram(atimer, 0); +} + +static void ct_xfitimer_start(struct ct_timer_instance *ti) +{ + struct ct_timer *atimer = ti->timer_base; + + scoped_guard(spinlock_irqsave, &atimer->lock) { + if (list_empty(&ti->running_list)) + atimer->wc = ct_xfitimer_get_wc(atimer); + ti->running = 1; + ti->need_update = 0; + list_add(&ti->running_list, &atimer->running_head); + } + ct_xfitimer_update(atimer); +} + +static void ct_xfitimer_stop(struct ct_timer_instance *ti) +{ + struct ct_timer *atimer = ti->timer_base; + + scoped_guard(spinlock_irqsave, &atimer->lock) { + list_del_init(&ti->running_list); + ti->running = 0; + } + ct_xfitimer_update(atimer); +} + +static void ct_xfitimer_free_global(struct ct_timer *atimer) +{ + ct_xfitimer_irq_stop(atimer); +} + +static const struct ct_timer_ops ct_xfitimer_ops = { + .prepare = ct_xfitimer_prepare, + .start = ct_xfitimer_start, + .stop = ct_xfitimer_stop, + .interrupt = ct_xfitimer_callback, + .free_global = ct_xfitimer_free_global, +}; + +/* + * timer instance + */ + +struct ct_timer_instance * +ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm) +{ + struct ct_timer_instance *ti; + + ti = kzalloc(sizeof(*ti), GFP_KERNEL); + if (!ti) + return NULL; + spin_lock_init(&ti->lock); + INIT_LIST_HEAD(&ti->instance_list); + INIT_LIST_HEAD(&ti->running_list); + ti->timer_base = atimer; + ti->apcm = apcm; + ti->substream = apcm->substream; + if (atimer->ops->init) + atimer->ops->init(ti); + + scoped_guard(spinlock_irq, &atimer->list_lock) { + list_add(&ti->instance_list, &atimer->instance_head); + } + + return ti; +} + +void ct_timer_prepare(struct ct_timer_instance *ti) +{ + if (ti->timer_base->ops->prepare) + ti->timer_base->ops->prepare(ti); + ti->position = 0; + ti->running = 0; +} + +void ct_timer_start(struct ct_timer_instance *ti) +{ + struct ct_timer *atimer = ti->timer_base; + atimer->ops->start(ti); +} + +void ct_timer_stop(struct ct_timer_instance *ti) +{ + struct ct_timer *atimer = ti->timer_base; + atimer->ops->stop(ti); +} + +void ct_timer_instance_free(struct ct_timer_instance *ti) +{ + struct ct_timer *atimer = ti->timer_base; + + atimer->ops->stop(ti); /* to be sure */ + if (atimer->ops->free_instance) + atimer->ops->free_instance(ti); + + scoped_guard(spinlock_irq, &atimer->list_lock) { + list_del(&ti->instance_list); + } + + kfree(ti); +} + +/* + * timer manager + */ + +static void ct_timer_interrupt(void *data, unsigned int status) +{ + struct ct_timer *timer = data; + + /* Interval timer interrupt */ + if ((status & IT_INT) && timer->ops->interrupt) + timer->ops->interrupt(timer); +} + +struct ct_timer *ct_timer_new(struct ct_atc *atc) +{ + struct ct_timer *atimer; + struct hw *hw; + + atimer = kzalloc_obj(*atimer); + if (!atimer) + return NULL; + spin_lock_init(&atimer->lock); + spin_lock_init(&atimer->list_lock); + INIT_LIST_HEAD(&atimer->instance_head); + INIT_LIST_HEAD(&atimer->running_head); + atimer->atc = atc; + hw = atc->hw; + if (!use_system_timer && hw->set_timer_irq) { + dev_info(atc->card->dev, "Use xfi-native timer\n"); + atimer->ops = &ct_xfitimer_ops; + hw->irq_callback_data = atimer; + hw->irq_callback = ct_timer_interrupt; + } else { + dev_info(atc->card->dev, "Use system timer\n"); + atimer->ops = &ct_systimer_ops; + } + return atimer; +} + +void ct_timer_free(struct ct_timer *atimer) +{ + struct hw *hw = atimer->atc->hw; + hw->irq_callback = NULL; + if (atimer->ops->free_global) + atimer->ops->free_global(atimer); + kfree(atimer); +} + diff --git a/sound/pci/ctxfi/cttimer.h b/sound/pci/ctxfi/cttimer.h new file mode 100644 index 000000000..9c5cb403b --- /dev/null +++ b/sound/pci/ctxfi/cttimer.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Timer handling + */ + +#ifndef __CTTIMER_H +#define __CTTIMER_H + +#include +#include +#include + +struct snd_pcm_substream; +struct ct_atc; +struct ct_atc_pcm; + +struct ct_timer; +struct ct_timer_instance; + +struct ct_timer *ct_timer_new(struct ct_atc *atc); +void ct_timer_free(struct ct_timer *atimer); + +struct ct_timer_instance * +ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm); +void ct_timer_instance_free(struct ct_timer_instance *ti); +void ct_timer_start(struct ct_timer_instance *ti); +void ct_timer_stop(struct ct_timer_instance *ti); +void ct_timer_prepare(struct ct_timer_instance *ti); + +#endif /* __CTTIMER_H */ diff --git a/sound/pci/ctxfi/ctvmem.c b/sound/pci/ctxfi/ctvmem.c new file mode 100644 index 000000000..25e071cae --- /dev/null +++ b/sound/pci/ctxfi/ctvmem.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctvmem.c + * + * @Brief + * This file contains the implementation of virtual memory management object + * for card device. + * + * @Author Liu Chun + * @Date Apr 1 2008 + */ + +#include "ctvmem.h" +#include "ctatc.h" +#include +#include +#include +#include + +#define CT_PTES_PER_PAGE (CT_PAGE_SIZE / sizeof(void *)) +#define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * CT_PAGE_SIZE) + +/* * + * Find or create vm block based on requested @size. + * @size must be page aligned. + * */ +static struct ct_vm_block * +get_vm_block(struct ct_vm *vm, unsigned int size, struct ct_atc *atc) +{ + struct ct_vm_block *block, *entry; + struct list_head *pos; + + size = CT_PAGE_ALIGN(size); + if (size > vm->size) { + dev_err(atc->card->dev, + "Fail! No sufficient device virtual memory space available!\n"); + return NULL; + } + + guard(mutex)(&vm->lock); + list_for_each(pos, &vm->unused) { + entry = list_entry(pos, struct ct_vm_block, list); + if (entry->size >= size) + break; /* found a block that is big enough */ + } + if (pos == &vm->unused) + return NULL; + + if (entry->size == size) { + /* Move the vm node from unused list to used list directly */ + list_move(&entry->list, &vm->used); + vm->size -= size; + return entry; + } + + block = kzalloc_obj(*block); + if (!block) + return NULL; + + block->addr = entry->addr; + block->size = size; + list_add(&block->list, &vm->used); + entry->addr += size; + entry->size -= size; + vm->size -= size; + + return block; +} + +static void put_vm_block(struct ct_vm *vm, struct ct_vm_block *block) +{ + struct ct_vm_block *entry, *pre_ent; + struct list_head *pos, *pre; + + block->size = CT_PAGE_ALIGN(block->size); + + guard(mutex)(&vm->lock); + list_del(&block->list); + vm->size += block->size; + + list_for_each(pos, &vm->unused) { + entry = list_entry(pos, struct ct_vm_block, list); + if (entry->addr >= (block->addr + block->size)) + break; /* found a position */ + } + if (pos == &vm->unused) { + list_add_tail(&block->list, &vm->unused); + entry = block; + } else { + if ((block->addr + block->size) == entry->addr) { + entry->addr = block->addr; + entry->size += block->size; + kfree(block); + } else { + __list_add(&block->list, pos->prev, pos); + entry = block; + } + } + + pos = &entry->list; + pre = pos->prev; + while (pre != &vm->unused) { + entry = list_entry(pos, struct ct_vm_block, list); + pre_ent = list_entry(pre, struct ct_vm_block, list); + if ((pre_ent->addr + pre_ent->size) > entry->addr) + break; + + pre_ent->size += entry->size; + list_del(pos); + kfree(entry); + pos = pre; + pre = pos->prev; + } +} + +/* Map host addr (kmalloced/vmalloced) to device logical addr. */ +static struct ct_vm_block * +ct_vm_map(struct ct_vm *vm, struct snd_pcm_substream *substream, int size) +{ + struct ct_vm_block *block; + unsigned int pte_start; + unsigned i, pages; + unsigned long *ptp; + struct ct_atc *atc = snd_pcm_substream_chip(substream); + + block = get_vm_block(vm, size, atc); + if (block == NULL) { + dev_err(atc->card->dev, + "No virtual memory block that is big enough to allocate!\n"); + return NULL; + } + + ptp = (unsigned long *)vm->ptp[0].area; + pte_start = (block->addr >> CT_PAGE_SHIFT); + pages = block->size >> CT_PAGE_SHIFT; + for (i = 0; i < pages; i++) { + unsigned long addr; + addr = snd_pcm_sgbuf_get_addr(substream, i << CT_PAGE_SHIFT); + ptp[pte_start + i] = addr; + } + + block->size = size; + return block; +} + +static void ct_vm_unmap(struct ct_vm *vm, struct ct_vm_block *block) +{ + /* do unmapping */ + put_vm_block(vm, block); +} + +/* * + * return the host physical addr of the @index-th device + * page table page on success, or ~0UL on failure. + * The first returned ~0UL indicates the termination. + * */ +static dma_addr_t +ct_get_ptp_phys(struct ct_vm *vm, int index) +{ + return (index >= CT_PTP_NUM) ? ~0UL : vm->ptp[index].addr; +} + +int ct_vm_create(struct ct_vm **rvm, struct pci_dev *pci) +{ + struct ct_vm *vm; + struct ct_vm_block *block; + int i, err = 0; + + *rvm = NULL; + + vm = kzalloc_obj(*vm); + if (!vm) + return -ENOMEM; + + mutex_init(&vm->lock); + + /* Allocate page table pages */ + for (i = 0; i < CT_PTP_NUM; i++) { + err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + &pci->dev, + PAGE_SIZE, &vm->ptp[i]); + if (err < 0) + break; + } + if (err < 0) { + /* no page table pages are allocated */ + ct_vm_destroy(vm); + return -ENOMEM; + } + vm->size = CT_ADDRS_PER_PAGE * i; + vm->map = ct_vm_map; + vm->unmap = ct_vm_unmap; + vm->get_ptp_phys = ct_get_ptp_phys; + INIT_LIST_HEAD(&vm->unused); + INIT_LIST_HEAD(&vm->used); + block = kzalloc_obj(*block); + if (NULL != block) { + block->addr = 0; + block->size = vm->size; + list_add(&block->list, &vm->unused); + } + + *rvm = vm; + return 0; +} + +/* The caller must ensure no mapping pages are being used + * by hardware before calling this function */ +void ct_vm_destroy(struct ct_vm *vm) +{ + int i; + struct list_head *pos; + struct ct_vm_block *entry; + + /* free used and unused list nodes */ + while (!list_empty(&vm->used)) { + pos = vm->used.next; + list_del(pos); + entry = list_entry(pos, struct ct_vm_block, list); + kfree(entry); + } + while (!list_empty(&vm->unused)) { + pos = vm->unused.next; + list_del(pos); + entry = list_entry(pos, struct ct_vm_block, list); + kfree(entry); + } + + /* free allocated page table pages */ + for (i = 0; i < CT_PTP_NUM; i++) + snd_dma_free_pages(&vm->ptp[i]); + + vm->size = 0; + + kfree(vm); +} diff --git a/sound/pci/ctxfi/ctvmem.h b/sound/pci/ctxfi/ctvmem.h new file mode 100644 index 000000000..43a0065b4 --- /dev/null +++ b/sound/pci/ctxfi/ctvmem.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * @File ctvmem.h + * + * @Brief + * This file contains the definition of virtual memory management object + * for card device. + * + * @Author Liu Chun + * @Date Mar 28 2008 + */ + +#ifndef CTVMEM_H +#define CTVMEM_H + +#define CT_PTP_NUM 1 /* num of device page table pages */ + +#include +#include +#include +#include + +/* The chip can handle the page table of 4k pages + * (emu20k1 can handle even 8k pages, but we don't use it right now) + */ +#define CT_PAGE_SIZE 4096 +#define CT_PAGE_SHIFT 12 +#define CT_PAGE_MASK (~(PAGE_SIZE - 1)) +#define CT_PAGE_ALIGN(addr) ALIGN(addr, CT_PAGE_SIZE) + +struct ct_vm_block { + unsigned int addr; /* starting logical addr of this block */ + unsigned int size; /* size of this device virtual mem block */ + struct list_head list; +}; + +struct snd_pcm_substream; + +/* Virtual memory management object for card device */ +struct ct_vm { + struct snd_dma_buffer ptp[CT_PTP_NUM]; /* Device page table pages */ + unsigned int size; /* Available addr space in bytes */ + struct list_head unused; /* List of unused blocks */ + struct list_head used; /* List of used blocks */ + struct mutex lock; + + /* Map host addr (kmalloced/vmalloced) to device logical addr. */ + struct ct_vm_block *(*map)(struct ct_vm *, struct snd_pcm_substream *, + int size); + /* Unmap device logical addr area. */ + void (*unmap)(struct ct_vm *, struct ct_vm_block *block); + dma_addr_t (*get_ptp_phys)(struct ct_vm *vm, int index); +}; + +int ct_vm_create(struct ct_vm **rvm, struct pci_dev *pci); +void ct_vm_destroy(struct ct_vm *vm); + +#endif /* CTVMEM_H */ diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c new file mode 100644 index 000000000..d8dd84d41 --- /dev/null +++ b/sound/pci/ctxfi/xfi.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * xfi linux driver. + * + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "ctatc.h" +#include "cthardware.h" + +MODULE_AUTHOR("Creative Technology Ltd"); +MODULE_DESCRIPTION("X-Fi driver version 1.03"); +MODULE_LICENSE("GPL v2"); + +static unsigned int reference_rate = 48000; +static unsigned int multiple = 2; +MODULE_PARM_DESC(reference_rate, "Reference rate (default=48000)"); +module_param(reference_rate, uint, 0444); +MODULE_PARM_DESC(multiple, "Rate multiplier (default=2)"); +module_param(multiple, uint, 0444); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static unsigned int subsystem[SNDRV_CARDS]; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Creative X-Fi driver"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Creative X-Fi driver"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Creative X-Fi driver"); +module_param_array(subsystem, int, NULL, 0444); +MODULE_PARM_DESC(subsystem, "Override subsystem ID for Creative X-Fi driver"); + +static const struct pci_device_id ct_pci_dev_ids[] = { + /* only X-Fi is supported, so... */ + { PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_DEVICE_ID_CREATIVE_20K1), + .driver_data = ATC20K1, + }, + { PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_DEVICE_ID_CREATIVE_20K2), + .driver_data = ATC20K2, + }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, ct_pci_dev_ids); + +static int +ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct ct_atc *atc; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + + if (!enable[dev]) { + dev++; + return -ENOENT; + } + err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err) + return err; + if ((reference_rate != 48000) && (reference_rate != 44100)) { + dev_err(card->dev, + "Invalid reference_rate value %u!!!\n", + reference_rate); + dev_err(card->dev, + "The valid values for reference_rate are 48000 and 44100, Value 48000 is assumed.\n"); + reference_rate = 48000; + } + if ((multiple != 1) && (multiple != 2) && (multiple != 4)) { + dev_err(card->dev, "Invalid multiple value %u!!!\n", + multiple); + dev_err(card->dev, + "The valid values for multiple are 1, 2 and 4, Value 2 is assumed.\n"); + multiple = 2; + } + err = ct_atc_create(card, pci, reference_rate, multiple, + pci_id->driver_data, subsystem[dev], &atc); + if (err < 0) + goto error; + + card->private_data = atc; + + /* Create alsa devices supported by this card */ + err = ct_atc_create_alsa_devs(atc); + if (err < 0) + goto error; + + strscpy(card->driver, "SB-XFi"); + strscpy(card->shortname, "Creative X-Fi"); + snprintf(card->longname, sizeof(card->longname), "%s %s %s", + card->shortname, atc->chip_name, atc->model_name); + + err = snd_card_register(card); + if (err < 0) + goto error; + + pci_set_drvdata(pci, card); + dev++; + + return 0; + +error: + snd_card_free(card); + return err; +} + +static void ct_card_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); +} + +#ifdef CONFIG_PM_SLEEP +static int ct_card_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct ct_atc *atc = card->private_data; + + return atc->suspend(atc); +} + +static int ct_card_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct ct_atc *atc = card->private_data; + + return atc->resume(atc); +} + +static SIMPLE_DEV_PM_OPS(ct_card_pm, ct_card_suspend, ct_card_resume); +#define CT_CARD_PM_OPS &ct_card_pm +#else +#define CT_CARD_PM_OPS NULL +#endif + +static struct pci_driver ct_driver = { + .name = KBUILD_MODNAME, + .id_table = ct_pci_dev_ids, + .probe = ct_card_probe, + .remove = ct_card_remove, + .driver = { + .pm = CT_CARD_PM_OPS, + }, +}; + +module_pci_driver(ct_driver); diff --git a/sound/pci/echoaudio/Makefile b/sound/pci/echoaudio/Makefile new file mode 100644 index 000000000..96667641c --- /dev/null +++ b/sound/pci/echoaudio/Makefile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA Echoaudio soundcard drivers +# Copyright (c) 2003 by Giuliano Pochini +# + +snd-darla20-y := darla20.o +snd-gina20-y := gina20.o +snd-layla20-y := layla20.o +snd-darla24-y := darla24.o +snd-gina24-y := gina24.o +snd-layla24-y := layla24.o +snd-mona-y := mona.o +snd-mia-y := mia.o +snd-echo3g-y := echo3g.o +snd-indigo-y := indigo.o +snd-indigoio-y := indigoio.o +snd-indigodj-y := indigodj.o +snd-indigoiox-y := indigoiox.o +snd-indigodjx-y := indigodjx.o + +obj-$(CONFIG_SND_DARLA20) += snd-darla20.o +obj-$(CONFIG_SND_GINA20) += snd-gina20.o +obj-$(CONFIG_SND_LAYLA20) += snd-layla20.o +obj-$(CONFIG_SND_DARLA24) += snd-darla24.o +obj-$(CONFIG_SND_GINA24) += snd-gina24.o +obj-$(CONFIG_SND_LAYLA24) += snd-layla24.o +obj-$(CONFIG_SND_MONA) += snd-mona.o +obj-$(CONFIG_SND_MIA) += snd-mia.o +obj-$(CONFIG_SND_ECHO3G) += snd-echo3g.o +obj-$(CONFIG_SND_INDIGO) += snd-indigo.o +obj-$(CONFIG_SND_INDIGOIO) += snd-indigoio.o +obj-$(CONFIG_SND_INDIGODJ) += snd-indigodj.o +obj-$(CONFIG_SND_INDIGOIOX) += snd-indigoiox.o +obj-$(CONFIG_SND_INDIGODJX) += snd-indigodjx.o diff --git a/sound/pci/echoaudio/darla20.c b/sound/pci/echoaudio/darla20.c new file mode 100644 index 000000000..48c25bd3f --- /dev/null +++ b/sound/pci/echoaudio/darla20.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define ECHOGALS_FAMILY +#define ECHOCARD_DARLA20 +#define ECHOCARD_NAME "Darla20" +#define ECHOCARD_HAS_MONITOR + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 0 */ +#define PX_NUM 10 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 0 */ +#define BX_ANALOG_IN 8 /* 2 */ +#define BX_DIGITAL_IN 10 /* 0 */ +#define BX_NUM 10 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/darla20_dsp.fw"); + +#define FW_DARLA20_DSP 0 + +static const struct firmware card_fw[] = { + {0, "darla20_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0010) }, /* DSP 56301 Darla20 rev.0 */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, + /* One page (4k) contains 512 instructions. I don't know if the hw + supports lists longer than this. In this case periods_max=220 is a + safe limit to make sure the list never exceeds 512 instructions. */ +}; + + +#include "darla20_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" diff --git a/sound/pci/echoaudio/darla20_dsp.c b/sound/pci/echoaudio/darla20_dsp.c new file mode 100644 index 000000000..5a41644b1 --- /dev/null +++ b/sound/pci/echoaudio/darla20_dsp.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != DARLA20)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw: could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->dsp_code_to_load = FW_DARLA20_DSP; + chip->spdif_status = GD_SPDIF_STATUS_UNDEF; + chip->clock_state = GD_CLOCK_UNDEF; + /* Since this card has no ASIC, mark it as loaded so everything + works OK */ + chip->asic_loaded = true; + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + return init_line_levels(chip); +} + + + +/* The Darla20 has no external clock sources */ +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + return ECHO_CLOCK_BIT_INTERNAL; +} + + + +/* The Darla20 has no ASIC. Just do nothing */ +static int load_asic(struct echoaudio *chip) +{ + return 0; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u8 clock_state, spdif_status; + + if (wait_handshake(chip)) + return -EIO; + + switch (rate) { + case 44100: + clock_state = GD_CLOCK_44; + spdif_status = GD_SPDIF_STATUS_44; + break; + case 48000: + clock_state = GD_CLOCK_48; + spdif_status = GD_SPDIF_STATUS_48; + break; + default: + clock_state = GD_CLOCK_NOCHANGE; + spdif_status = GD_SPDIF_STATUS_NOCHANGE; + break; + } + + if (chip->clock_state == clock_state) + clock_state = GD_CLOCK_NOCHANGE; + if (spdif_status == chip->spdif_status) + spdif_status = GD_SPDIF_STATUS_NOCHANGE; + + chip->comm_page->sample_rate = cpu_to_le32(rate); + chip->comm_page->gd_clock_state = clock_state; + chip->comm_page->gd_spdif_status = spdif_status; + chip->comm_page->gd_resampler_state = 3; /* magic number - should always be 3 */ + + /* Save the new audio state if it changed */ + if (clock_state != GD_CLOCK_NOCHANGE) + chip->clock_state = clock_state; + if (spdif_status != GD_SPDIF_STATUS_NOCHANGE) + chip->spdif_status = spdif_status; + chip->sample_rate = rate; + + clear_handshake(chip); + return send_vector(chip, DSP_VC_SET_GD_AUDIO_STATE); +} diff --git a/sound/pci/echoaudio/darla24.c b/sound/pci/echoaudio/darla24.c new file mode 100644 index 000000000..c8cdd1b05 --- /dev/null +++ b/sound/pci/echoaudio/darla24.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define ECHOGALS_FAMILY +#define ECHOCARD_DARLA24 +#define ECHOCARD_NAME "Darla24" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_SUPER_INTERLEAVE + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 0 */ +#define PX_NUM 10 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 0 */ +#define BX_ANALOG_IN 8 /* 2 */ +#define BX_DIGITAL_IN 10 /* 0 */ +#define BX_NUM 10 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/darla24_dsp.fw"); + +#define FW_DARLA24_DSP 0 + +static const struct firmware card_fw[] = { + {0, "darla24_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0040) }, /* DSP 56301 Darla24 rev.0 */ + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0041) }, /* DSP 56301 Darla24 rev.1 */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, + /* One page (4k) contains 512 instructions. I don't know if the hw + supports lists longer than this. In this case periods_max=220 is a + safe limit to make sure the list never exceeds 512 instructions. */ +}; + + +#include "darla24_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" diff --git a/sound/pci/echoaudio/darla24_dsp.c b/sound/pci/echoaudio/darla24_dsp.c new file mode 100644 index 000000000..5dc0efaca --- /dev/null +++ b/sound/pci/echoaudio/darla24_dsp.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != DARLA24)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw: could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->dsp_code_to_load = FW_DARLA24_DSP; + /* Since this card has no ASIC, mark it as loaded so everything + works OK */ + chip->asic_loaded = true; + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL | + ECHO_CLOCK_BIT_ESYNC; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + u32 clocks_from_dsp, clock_bits; + + /* Map the DSP clock detect bits to the generic driver clock + detect bits */ + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + clock_bits = ECHO_CLOCK_BIT_INTERNAL; + + if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_ESYNC) + clock_bits |= ECHO_CLOCK_BIT_ESYNC; + + return clock_bits; +} + + + +/* The Darla24 has no ASIC. Just do nothing */ +static int load_asic(struct echoaudio *chip) +{ + return 0; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u8 clock; + + switch (rate) { + case 96000: + clock = GD24_96000; + break; + case 88200: + clock = GD24_88200; + break; + case 48000: + clock = GD24_48000; + break; + case 44100: + clock = GD24_44100; + break; + case 32000: + clock = GD24_32000; + break; + case 22050: + clock = GD24_22050; + break; + case 16000: + clock = GD24_16000; + break; + case 11025: + clock = GD24_11025; + break; + case 8000: + clock = GD24_8000; + break; + default: + dev_err(chip->card->dev, + "set_sample_rate: Error, invalid sample rate %d\n", + rate); + return -EINVAL; + } + + if (wait_handshake(chip)) + return -EIO; + + dev_dbg(chip->card->dev, + "set_sample_rate: %d clock %d\n", rate, clock); + chip->sample_rate = rate; + + /* Override the sample rate if this card is set to Echo sync. */ + if (chip->input_clock == ECHO_CLOCK_ESYNC) + clock = GD24_EXT_SYNC; + + chip->comm_page->sample_rate = cpu_to_le32(rate); /* ignored by the DSP ? */ + chip->comm_page->gd_clock_state = clock; + clear_handshake(chip); + return send_vector(chip, DSP_VC_SET_GD_AUDIO_STATE); +} + + + +static int set_input_clock(struct echoaudio *chip, u16 clock) +{ + if (snd_BUG_ON(clock != ECHO_CLOCK_INTERNAL && + clock != ECHO_CLOCK_ESYNC)) + return -EINVAL; + chip->input_clock = clock; + return set_sample_rate(chip, chip->sample_rate); +} + diff --git a/sound/pci/echoaudio/echo3g.c b/sound/pci/echoaudio/echo3g.c new file mode 100644 index 000000000..e8b476e07 --- /dev/null +++ b/sound/pci/echoaudio/echo3g.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define ECHO3G_FAMILY +#define ECHOCARD_ECHO3G +#define ECHOCARD_NAME "Echo3G" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH +#define ECHOCARD_HAS_ADAT 6 +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 +#define ECHOCARD_HAS_MIDI +#define ECHOCARD_HAS_PHANTOM_POWER + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 +#define PX_DIGITAL_OUT chip->px_digital_out +#define PX_ANALOG_IN chip->px_analog_in +#define PX_DIGITAL_IN chip->px_digital_in +#define PX_NUM chip->px_num + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 +#define BX_DIGITAL_OUT chip->bx_digital_out +#define BX_ANALOG_IN chip->bx_analog_in +#define BX_DIGITAL_IN chip->bx_digital_in +#define BX_NUM chip->bx_num + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/echo3g_dsp.fw"); +MODULE_FIRMWARE("ea/3g_asic.fw"); + +#define FW_361_LOADER 0 +#define FW_ECHO3G_DSP 1 +#define FW_3G_ASIC 2 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "echo3g_dsp.fw"}, + {0, "3g_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0100) }, /* Echo 3G */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 32000, + .rate_max = 100000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, +}; + +#include "echo3g_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio_3g.c" +#include "echoaudio.c" +#include "midi.c" diff --git a/sound/pci/echoaudio/echo3g_dsp.c b/sound/pci/echoaudio/echo3g_dsp.c new file mode 100644 index 000000000..38084c2c3 --- /dev/null +++ b/sound/pci/echoaudio/echo3g_dsp.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + +static int load_asic(struct echoaudio *chip); +static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode); +static int set_digital_mode(struct echoaudio *chip, u8 mode); +static int check_asic_status(struct echoaudio *chip); +static int set_sample_rate(struct echoaudio *chip, u32 rate); +static int set_input_clock(struct echoaudio *chip, u16 clock); +static int set_professional_spdif(struct echoaudio *chip, char prof); +static int set_phantom_power(struct echoaudio *chip, char on); +static int write_control_reg(struct echoaudio *chip, u32 ctl, u32 frq, + char force); + +#include + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + local_irq_enable(); + if (snd_BUG_ON((subdevice_id & 0xfff0) != ECHO3G)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->comm_page->e3g_frq_register = + cpu_to_le32((E3G_MAGIC_NUMBER / 48000) - 2); + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->has_midi = true; + chip->dsp_code_to_load = FW_ECHO3G_DSP; + + /* Load the DSP code and the ASIC on the PCI card and get + what type of external box is attached */ + err = load_firmware(chip); + + if (err < 0) { + return err; + } else if (err == E3G_GINA3G_BOX_TYPE) { + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL | + ECHO_CLOCK_BIT_SPDIF | + ECHO_CLOCK_BIT_ADAT; + chip->card_name = "Gina3G"; + chip->px_digital_out = chip->bx_digital_out = 6; + chip->px_analog_in = chip->bx_analog_in = 14; + chip->px_digital_in = chip->bx_digital_in = 16; + chip->px_num = chip->bx_num = 24; + chip->has_phantom_power = true; + chip->hasnt_input_nominal_level = true; + } else if (err == E3G_LAYLA3G_BOX_TYPE) { + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL | + ECHO_CLOCK_BIT_SPDIF | + ECHO_CLOCK_BIT_ADAT | + ECHO_CLOCK_BIT_WORD; + chip->card_name = "Layla3G"; + chip->px_digital_out = chip->bx_digital_out = 8; + chip->px_analog_in = chip->bx_analog_in = 16; + chip->px_digital_in = chip->bx_digital_in = 24; + chip->px_num = chip->bx_num = 32; + } else { + return -ENODEV; + } + + chip->digital_modes = ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA | + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL | + ECHOCAPS_HAS_DIGITAL_MODE_ADAT; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + chip->digital_mode = DIGITAL_MODE_SPDIF_RCA; + chip->professional_spdif = false; + chip->non_audio_spdif = false; + chip->bad_board = false; + chip->phantom_power = false; + return init_line_levels(chip); +} + + + +static int set_phantom_power(struct echoaudio *chip, char on) +{ + u32 control_reg = le32_to_cpu(chip->comm_page->control_register); + + if (on) + control_reg |= E3G_PHANTOM_POWER; + else + control_reg &= ~E3G_PHANTOM_POWER; + + chip->phantom_power = on; + return write_control_reg(chip, control_reg, + le32_to_cpu(chip->comm_page->e3g_frq_register), + 0); +} diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c new file mode 100644 index 000000000..214d68657 --- /dev/null +++ b/sound/pci/echoaudio/echoaudio.c @@ -0,0 +1,2212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * Copyright (C) 2020 Mark Hills + */ + +#include +#include + +MODULE_AUTHOR("Giuliano Pochini "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Echoaudio " ECHOCARD_NAME " soundcards driver"); +MODULE_DEVICE_TABLE(pci, snd_echo_ids); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " ECHOCARD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " ECHOCARD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " ECHOCARD_NAME " soundcard."); + +static const unsigned int channels_list[10] = {1, 2, 4, 6, 8, 10, 12, 14, 16, 999999}; +static const DECLARE_TLV_DB_SCALE(db_scale_output_gain, -12800, 100, 1); + + + +static int get_firmware(const struct firmware **fw_entry, + struct echoaudio *chip, const short fw_index) +{ + int err; + char name[30]; + + if (chip->fw_cache[fw_index]) { + dev_dbg(chip->card->dev, + "firmware requested: %s is cached\n", + card_fw[fw_index].data); + *fw_entry = chip->fw_cache[fw_index]; + return 0; + } + + dev_dbg(chip->card->dev, + "firmware requested: %s\n", card_fw[fw_index].data); + snprintf(name, sizeof(name), "ea/%s", card_fw[fw_index].data); + err = request_firmware(fw_entry, name, &chip->pci->dev); + if (err < 0) + dev_err(chip->card->dev, + "get_firmware(): Firmware not available (%d)\n", err); + else + chip->fw_cache[fw_index] = *fw_entry; + return err; +} + + + +static void free_firmware(const struct firmware *fw_entry, + struct echoaudio *chip) +{ + dev_dbg(chip->card->dev, "firmware not released (kept in cache)\n"); +} + + + +static void free_firmware_cache(struct echoaudio *chip) +{ + int i; + + for (i = 0; i < 8 ; i++) + if (chip->fw_cache[i]) { + release_firmware(chip->fw_cache[i]); + dev_dbg(chip->card->dev, "release_firmware(%d)\n", i); + } +} + + + +/****************************************************************************** + PCM interface +******************************************************************************/ + +static void audiopipe_free(struct snd_pcm_runtime *runtime) +{ + struct audiopipe *pipe = runtime->private_data; + + if (pipe->sgpage.area) + snd_dma_free_pages(&pipe->sgpage); + kfree(pipe); +} + + + +static int hw_rule_capture_format_by_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); + struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_mask fmt; + + snd_mask_any(&fmt); + +#ifndef ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + /* >=2 channels cannot be S32_BE */ + if (c->min == 2) { + fmt.bits[0] &= ~SNDRV_PCM_FMTBIT_S32_BE; + return snd_mask_refine(f, &fmt); + } +#endif + /* > 2 channels cannot be U8 and S32_BE */ + if (c->min > 2) { + fmt.bits[0] &= ~(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_BE); + return snd_mask_refine(f, &fmt); + } + /* Mono is ok with any format */ + return 0; +} + + + +static int hw_rule_capture_channels_by_format(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); + struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_interval ch; + + snd_interval_any(&ch); + + /* S32_BE is mono (and stereo) only */ + if (f->bits[0] == SNDRV_PCM_FMTBIT_S32_BE) { + ch.min = 1; +#ifdef ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + ch.max = 2; +#else + ch.max = 1; +#endif + ch.integer = 1; + return snd_interval_refine(c, &ch); + } + /* U8 can be only mono or stereo */ + if (f->bits[0] == SNDRV_PCM_FMTBIT_U8) { + ch.min = 1; + ch.max = 2; + ch.integer = 1; + return snd_interval_refine(c, &ch); + } + /* S16_LE, S24_3LE and S32_LE support any number of channels. */ + return 0; +} + + + +static int hw_rule_playback_format_by_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); + struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_mask fmt; + u64 fmask; + snd_mask_any(&fmt); + + fmask = fmt.bits[0] + ((u64)fmt.bits[1] << 32); + + /* >2 channels must be S16_LE, S24_3LE or S32_LE */ + if (c->min > 2) { + fmask &= SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE; + /* 1 channel must be S32_BE or S32_LE */ + } else if (c->max == 1) + fmask &= SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE; +#ifndef ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + /* 2 channels cannot be S32_BE */ + else if (c->min == 2 && c->max == 2) + fmask &= ~SNDRV_PCM_FMTBIT_S32_BE; +#endif + else + return 0; + + fmt.bits[0] &= (u32)fmask; + fmt.bits[1] &= (u32)(fmask >> 32); + return snd_mask_refine(f, &fmt); +} + + + +static int hw_rule_playback_channels_by_format(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); + struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_interval ch; + u64 fmask; + + snd_interval_any(&ch); + ch.integer = 1; + fmask = f->bits[0] + ((u64)f->bits[1] << 32); + + /* S32_BE is mono (and stereo) only */ + if (fmask == SNDRV_PCM_FMTBIT_S32_BE) { + ch.min = 1; +#ifdef ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + ch.max = 2; +#else + ch.max = 1; +#endif + /* U8 is stereo only */ + } else if (fmask == SNDRV_PCM_FMTBIT_U8) + ch.min = ch.max = 2; + /* S16_LE and S24_3LE must be at least stereo */ + else if (!(fmask & ~(SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE))) + ch.min = 2; + else + return 0; + + return snd_interval_refine(c, &ch); +} + + + +/* Since the sample rate is a global setting, do allow the user to change the +sample rate only if there is only one pcm device open. */ +static int hw_rule_sample_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct echoaudio *chip = rule->private; + struct snd_interval fixed; + int err; + + guard(mutex)(&chip->mode_mutex); + + if (chip->can_set_rate) { + err = 0; + } else { + snd_interval_any(&fixed); + fixed.min = fixed.max = chip->sample_rate; + err = snd_interval_refine(rate, &fixed); + } + + return err; +} + + +static int pcm_open(struct snd_pcm_substream *substream, + signed char max_channels) +{ + struct echoaudio *chip; + struct snd_pcm_runtime *runtime; + struct audiopipe *pipe; + int err, i; + + if (max_channels <= 0) + return -EAGAIN; + + chip = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + pipe = kzalloc_obj(struct audiopipe); + if (!pipe) + return -ENOMEM; + pipe->index = -1; /* Not configured yet */ + + /* Set up hw capabilities and contraints */ + memcpy(&pipe->hw, &pcm_hardware_skel, sizeof(struct snd_pcm_hardware)); + dev_dbg(chip->card->dev, "max_channels=%d\n", max_channels); + pipe->constr.list = channels_list; + pipe->constr.mask = 0; + for (i = 0; channels_list[i] <= max_channels; i++); + pipe->constr.count = i; + if (pipe->hw.channels_max > max_channels) + pipe->hw.channels_max = max_channels; + if (chip->digital_mode == DIGITAL_MODE_ADAT) { + pipe->hw.rate_max = 48000; + pipe->hw.rates &= SNDRV_PCM_RATE_8000_48000; + } + + runtime->hw = pipe->hw; + runtime->private_data = pipe; + runtime->private_free = audiopipe_free; + snd_pcm_set_sync(substream); + + /* Only mono and any even number of channels are allowed */ + err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &pipe->constr); + if (err < 0) + return err; + + /* All periods should have the same size */ + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + /* The hw accesses memory in chunks 32 frames long and they should be + 32-bytes-aligned. It's not a requirement, but it seems that IRQs are + generated with a resolution of 32 frames. Thus we need the following */ + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + hw_rule_sample_rate, chip, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + /* Allocate a page for the scatter-gather list */ + err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + PAGE_SIZE, &pipe->sgpage); + if (err < 0) { + dev_err(chip->card->dev, "s-g list allocation failed\n"); + return err; + } + + /* + * Sole ownership required to set the rate + */ + + dev_dbg(chip->card->dev, "pcm_open opencount=%d can_set_rate=%d, rate_set=%d", + chip->opencount, chip->can_set_rate, chip->rate_set); + + chip->opencount++; + if (chip->opencount > 1 && chip->rate_set) + chip->can_set_rate = 0; + + return 0; +} + + + +static int pcm_analog_in_open(struct snd_pcm_substream *substream) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + int err; + + err = pcm_open(substream, + num_analog_busses_in(chip) - substream->number); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_capture_channels_by_format, NULL, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + hw_rule_capture_format_by_channels, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return 0; +} + + + +static int pcm_analog_out_open(struct snd_pcm_substream *substream) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + int max_channels, err; + +#ifdef ECHOCARD_HAS_VMIXER + max_channels = num_pipes_out(chip); +#else + max_channels = num_analog_busses_out(chip); +#endif + err = pcm_open(substream, max_channels - substream->number); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_playback_channels_by_format, + NULL, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + hw_rule_playback_format_by_channels, + NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return 0; +} + + + +#ifdef ECHOCARD_HAS_DIGITAL_IO + +static int pcm_digital_in_open(struct snd_pcm_substream *substream) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + int err, max_channels; + + max_channels = num_digital_busses_in(chip) - substream->number; + guard(mutex)(&chip->mode_mutex); + if (chip->digital_mode == DIGITAL_MODE_ADAT) + err = pcm_open(substream, max_channels); + else /* If the card has ADAT, subtract the 6 channels + * that S/PDIF doesn't have + */ + err = pcm_open(substream, max_channels - ECHOCARD_HAS_ADAT); + + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_capture_channels_by_format, NULL, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + hw_rule_capture_format_by_channels, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return 0; +} + + + +#ifndef ECHOCARD_HAS_VMIXER /* See the note in snd_echo_new_pcm() */ + +static int pcm_digital_out_open(struct snd_pcm_substream *substream) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + int err, max_channels; + + max_channels = num_digital_busses_out(chip) - substream->number; + guard(mutex)(&chip->mode_mutex); + if (chip->digital_mode == DIGITAL_MODE_ADAT) + err = pcm_open(substream, max_channels); + else /* If the card has ADAT, subtract the 6 channels + * that S/PDIF doesn't have + */ + err = pcm_open(substream, max_channels - ECHOCARD_HAS_ADAT); + + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_playback_channels_by_format, + NULL, SNDRV_PCM_HW_PARAM_FORMAT, + -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + hw_rule_playback_format_by_channels, + NULL, SNDRV_PCM_HW_PARAM_CHANNELS, + -1); + if (err < 0) + return err; + + return 0; +} + +#endif /* !ECHOCARD_HAS_VMIXER */ + +#endif /* ECHOCARD_HAS_DIGITAL_IO */ + + + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + + /* Nothing to do here. Audio is already off and pipe will be + * freed by its callback + */ + + guard(mutex)(&chip->mode_mutex); + + dev_dbg(chip->card->dev, "pcm_open opencount=%d can_set_rate=%d, rate_set=%d", + chip->opencount, chip->can_set_rate, chip->rate_set); + + chip->opencount--; + + switch (chip->opencount) { + case 1: + chip->can_set_rate = 1; + break; + + case 0: + chip->rate_set = 0; + break; + } + + return 0; +} + + + +/* Channel allocation and scatter-gather list setup */ +static int init_engine(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + int pipe_index, int interleave) +{ + struct echoaudio *chip; + int err, per, rest, page, edge, offs; + struct audiopipe *pipe; + + chip = snd_pcm_substream_chip(substream); + pipe = (struct audiopipe *) substream->runtime->private_data; + + /* Sets up che hardware. If it's already initialized, reset and + * redo with the new parameters + */ + scoped_guard(spinlock_irq, &chip->lock) { + if (pipe->index >= 0) { + dev_dbg(chip->card->dev, "hwp_ie free(%d)\n", pipe->index); + err = free_pipes(chip, pipe); + snd_BUG_ON(err); + chip->substream[pipe->index] = NULL; + } + + err = allocate_pipes(chip, pipe, pipe_index, interleave); + if (err < 0) { + dev_err(chip->card->dev, "allocate_pipes(%d) err=%d\n", + pipe_index, err); + return err; + } + } + dev_dbg(chip->card->dev, "allocate_pipes()=%d\n", pipe_index); + + dev_dbg(chip->card->dev, + "pcm_hw_params (bufsize=%dB periods=%d persize=%dB)\n", + params_buffer_bytes(hw_params), params_periods(hw_params), + params_period_bytes(hw_params)); + + sglist_init(chip, pipe); + edge = PAGE_SIZE; + for (offs = page = per = 0; offs < params_buffer_bytes(hw_params); + per++) { + rest = params_period_bytes(hw_params); + if (offs + rest > params_buffer_bytes(hw_params)) + rest = params_buffer_bytes(hw_params) - offs; + while (rest) { + dma_addr_t addr; + addr = snd_pcm_sgbuf_get_addr(substream, offs); + if (rest <= edge - offs) { + sglist_add_mapping(chip, pipe, addr, rest); + sglist_add_irq(chip, pipe); + offs += rest; + rest = 0; + } else { + sglist_add_mapping(chip, pipe, addr, + edge - offs); + rest -= edge - offs; + offs = edge; + } + if (offs == edge) { + edge += PAGE_SIZE; + page++; + } + } + } + + /* Close the ring buffer */ + sglist_wrap(chip, pipe); + + /* This stuff is used by the irq handler, so it must be + * initialized before chip->substream + */ + pipe->last_period = 0; + pipe->last_counter = 0; + pipe->position = 0; + smp_wmb(); + chip->substream[pipe_index] = substream; + chip->rate_set = 1; + guard(spinlock_irq)(&chip->lock); + set_sample_rate(chip, hw_params->rate_num / hw_params->rate_den); + return 0; +} + + + +static int pcm_analog_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + + return init_engine(substream, hw_params, px_analog_in(chip) + + substream->number, params_channels(hw_params)); +} + + + +static int pcm_analog_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return init_engine(substream, hw_params, substream->number, + params_channels(hw_params)); +} + + + +#ifdef ECHOCARD_HAS_DIGITAL_IO + +static int pcm_digital_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + + return init_engine(substream, hw_params, px_digital_in(chip) + + substream->number, params_channels(hw_params)); +} + + + +#ifndef ECHOCARD_HAS_VMIXER /* See the note in snd_echo_new_pcm() */ +static int pcm_digital_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + + return init_engine(substream, hw_params, px_digital_out(chip) + + substream->number, params_channels(hw_params)); +} +#endif /* !ECHOCARD_HAS_VMIXER */ + +#endif /* ECHOCARD_HAS_DIGITAL_IO */ + + + +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct echoaudio *chip; + struct audiopipe *pipe; + + chip = snd_pcm_substream_chip(substream); + pipe = (struct audiopipe *) substream->runtime->private_data; + + guard(spinlock_irq)(&chip->lock); + if (pipe->index >= 0) { + dev_dbg(chip->card->dev, "pcm_hw_free(%d)\n", pipe->index); + free_pipes(chip, pipe); + chip->substream[pipe->index] = NULL; + pipe->index = -1; + } + + return 0; +} + + + +static int pcm_prepare(struct snd_pcm_substream *substream) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct audioformat format; + int pipe_index = ((struct audiopipe *)runtime->private_data)->index; + + dev_dbg(chip->card->dev, "Prepare rate=%d format=%d channels=%d\n", + runtime->rate, runtime->format, runtime->channels); + format.interleave = runtime->channels; + format.data_are_bigendian = 0; + format.mono_to_stereo = 0; + switch (runtime->format) { + case SNDRV_PCM_FORMAT_U8: + format.bits_per_sample = 8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + format.bits_per_sample = 16; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + format.bits_per_sample = 24; + break; + case SNDRV_PCM_FORMAT_S32_BE: + format.data_are_bigendian = 1; + fallthrough; + case SNDRV_PCM_FORMAT_S32_LE: + format.bits_per_sample = 32; + break; + default: + dev_err(chip->card->dev, + "Prepare error: unsupported format %d\n", + runtime->format); + return -EINVAL; + } + + if (snd_BUG_ON(pipe_index >= px_num(chip))) + return -EINVAL; + + /* + * We passed checks we can do independently; now take + * exclusive control + */ + + guard(spinlock_irq)(&chip->lock); + + if (snd_BUG_ON(!is_pipe_allocated(chip, pipe_index))) + return -EINVAL; + + set_audio_format(chip, pipe_index, &format); + + return 0; +} + + + +static int pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + struct audiopipe *pipe; + int i, err; + u32 channelmask = 0; + struct snd_pcm_substream *s; + + snd_pcm_group_for_each_entry(s, substream) { + for (i = 0; i < DSP_MAXPIPES; i++) { + if (s == chip->substream[i]) { + channelmask |= 1 << i; + snd_pcm_trigger_done(s, substream); + } + } + } + + guard(spinlock)(&chip->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + for (i = 0; i < DSP_MAXPIPES; i++) { + if (channelmask & (1 << i)) { + pipe = chip->substream[i]->runtime->private_data; + switch (pipe->state) { + case PIPE_STATE_STOPPED: + pipe->last_period = 0; + pipe->last_counter = 0; + pipe->position = 0; + *pipe->dma_counter = 0; + fallthrough; + case PIPE_STATE_PAUSED: + pipe->state = PIPE_STATE_STARTED; + break; + case PIPE_STATE_STARTED: + break; + } + } + } + err = start_transport(chip, channelmask, + chip->pipe_cyclic_mask); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + for (i = 0; i < DSP_MAXPIPES; i++) { + if (channelmask & (1 << i)) { + pipe = chip->substream[i]->runtime->private_data; + pipe->state = PIPE_STATE_STOPPED; + } + } + err = stop_transport(chip, channelmask); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + for (i = 0; i < DSP_MAXPIPES; i++) { + if (channelmask & (1 << i)) { + pipe = chip->substream[i]->runtime->private_data; + pipe->state = PIPE_STATE_PAUSED; + } + } + err = pause_transport(chip, channelmask); + break; + default: + err = -EINVAL; + } + return err; +} + + + +static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audiopipe *pipe = runtime->private_data; + u32 counter, step; + + /* + * IRQ handling runs concurrently. Do not share tracking of + * counter with it, which would race or require locking + */ + + counter = le32_to_cpu(*pipe->dma_counter); /* presumed atomic */ + + step = counter - pipe->last_counter; /* handles wrapping */ + pipe->last_counter = counter; + + /* counter doesn't neccessarily wrap on a multiple of + * buffer_size, so can't derive the position; must + * accumulate */ + + pipe->position += step; + pipe->position %= frames_to_bytes(runtime, runtime->buffer_size); /* wrap */ + + return bytes_to_frames(runtime, pipe->position); +} + + + +/* pcm *_ops structures */ +static const struct snd_pcm_ops analog_playback_ops = { + .open = pcm_analog_out_open, + .close = pcm_close, + .hw_params = pcm_analog_out_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, +}; +static const struct snd_pcm_ops analog_capture_ops = { + .open = pcm_analog_in_open, + .close = pcm_close, + .hw_params = pcm_analog_in_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, +}; +#ifdef ECHOCARD_HAS_DIGITAL_IO +#ifndef ECHOCARD_HAS_VMIXER +static const struct snd_pcm_ops digital_playback_ops = { + .open = pcm_digital_out_open, + .close = pcm_close, + .hw_params = pcm_digital_out_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, +}; +#endif /* !ECHOCARD_HAS_VMIXER */ +static const struct snd_pcm_ops digital_capture_ops = { + .open = pcm_digital_in_open, + .close = pcm_close, + .hw_params = pcm_digital_in_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, +}; +#endif /* ECHOCARD_HAS_DIGITAL_IO */ + + + +/* Preallocate memory only for the first substream because it's the most + * used one + */ +static void snd_echo_preallocate_pages(struct snd_pcm *pcm, struct device *dev) +{ + struct snd_pcm_substream *ss; + int stream; + + for (stream = 0; stream < 2; stream++) + for (ss = pcm->streams[stream].substream; ss; ss = ss->next) + snd_pcm_set_managed_buffer(ss, SNDRV_DMA_TYPE_DEV_SG, + dev, + ss->number ? 0 : 128<<10, + 256<<10); +} + + + +/*<--snd_echo_probe() */ +static int snd_echo_new_pcm(struct echoaudio *chip) +{ + struct snd_pcm *pcm; + int err; + +#ifdef ECHOCARD_HAS_VMIXER + /* This card has a Vmixer, that is there is no direct mapping from PCM + streams to physical outputs. The user can mix the streams as he wishes + via control interface and it's possible to send any stream to any + output, thus it makes no sense to keep analog and digital outputs + separated */ + + /* PCM#0 Virtual outputs and analog inputs */ + err = snd_pcm_new(chip->card, "PCM", 0, num_pipes_out(chip), + num_analog_busses_in(chip), &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + chip->analog_pcm = pcm; + strscpy(pcm->name, chip->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &analog_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &analog_capture_ops); + snd_echo_preallocate_pages(pcm, &chip->pci->dev); + +#ifdef ECHOCARD_HAS_DIGITAL_IO + /* PCM#1 Digital inputs, no outputs */ + err = snd_pcm_new(chip->card, "Digital PCM", 1, 0, + num_digital_busses_in(chip), &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + chip->digital_pcm = pcm; + strscpy(pcm->name, chip->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &digital_capture_ops); + snd_echo_preallocate_pages(pcm, &chip->pci->dev); +#endif /* ECHOCARD_HAS_DIGITAL_IO */ + +#else /* ECHOCARD_HAS_VMIXER */ + + /* The card can manage substreams formed by analog and digital channels + at the same time, but I prefer to keep analog and digital channels + separated, because that mixed thing is confusing and useless. So we + register two PCM devices: */ + + /* PCM#0 Analog i/o */ + err = snd_pcm_new(chip->card, "Analog PCM", 0, + num_analog_busses_out(chip), + num_analog_busses_in(chip), &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + chip->analog_pcm = pcm; + strscpy(pcm->name, chip->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &analog_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &analog_capture_ops); + snd_echo_preallocate_pages(pcm, &chip->pci->dev); + +#ifdef ECHOCARD_HAS_DIGITAL_IO + /* PCM#1 Digital i/o */ + err = snd_pcm_new(chip->card, "Digital PCM", 1, + num_digital_busses_out(chip), + num_digital_busses_in(chip), &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + chip->digital_pcm = pcm; + strscpy(pcm->name, chip->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &digital_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &digital_capture_ops); + snd_echo_preallocate_pages(pcm, &chip->pci->dev); +#endif /* ECHOCARD_HAS_DIGITAL_IO */ + +#endif /* ECHOCARD_HAS_VMIXER */ + + return 0; +} + + + + +/****************************************************************************** + Control interface +******************************************************************************/ + +#if !defined(ECHOCARD_HAS_VMIXER) || defined(ECHOCARD_HAS_LINE_OUT_GAIN) + +/******************* PCM output volume *******************/ +static int snd_echo_output_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = num_busses_out(chip); + uinfo->value.integer.min = ECHOGAIN_MINOUT; + uinfo->value.integer.max = ECHOGAIN_MAXOUT; + return 0; +} + +static int snd_echo_output_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int c; + + chip = snd_kcontrol_chip(kcontrol); + for (c = 0; c < num_busses_out(chip); c++) + ucontrol->value.integer.value[c] = chip->output_gain[c]; + return 0; +} + +static int snd_echo_output_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int c, changed, gain; + + changed = 0; + chip = snd_kcontrol_chip(kcontrol); + guard(spinlock_irq)(&chip->lock); + for (c = 0; c < num_busses_out(chip); c++) { + gain = ucontrol->value.integer.value[c]; + /* Ignore out of range values */ + if (gain < ECHOGAIN_MINOUT || gain > ECHOGAIN_MAXOUT) + continue; + if (chip->output_gain[c] != gain) { + set_output_gain(chip, c, gain); + changed = 1; + } + } + if (changed) + update_output_line_level(chip); + return changed; +} + +#ifdef ECHOCARD_HAS_LINE_OUT_GAIN +/* On the Mia this one controls the line-out volume */ +static const struct snd_kcontrol_new snd_echo_line_output_gain = { + .name = "Line Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_echo_output_gain_info, + .get = snd_echo_output_gain_get, + .put = snd_echo_output_gain_put, + .tlv = {.p = db_scale_output_gain}, +}; +#else +static const struct snd_kcontrol_new snd_echo_pcm_output_gain = { + .name = "PCM Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_echo_output_gain_info, + .get = snd_echo_output_gain_get, + .put = snd_echo_output_gain_put, + .tlv = {.p = db_scale_output_gain}, +}; +#endif + +#endif /* !ECHOCARD_HAS_VMIXER || ECHOCARD_HAS_LINE_OUT_GAIN */ + + + +#ifdef ECHOCARD_HAS_INPUT_GAIN + +/******************* Analog input volume *******************/ +static int snd_echo_input_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = num_analog_busses_in(chip); + uinfo->value.integer.min = ECHOGAIN_MININP; + uinfo->value.integer.max = ECHOGAIN_MAXINP; + return 0; +} + +static int snd_echo_input_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int c; + + chip = snd_kcontrol_chip(kcontrol); + for (c = 0; c < num_analog_busses_in(chip); c++) + ucontrol->value.integer.value[c] = chip->input_gain[c]; + return 0; +} + +static int snd_echo_input_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int c, gain, changed; + + changed = 0; + chip = snd_kcontrol_chip(kcontrol); + guard(spinlock_irq)(&chip->lock); + for (c = 0; c < num_analog_busses_in(chip); c++) { + gain = ucontrol->value.integer.value[c]; + /* Ignore out of range values */ + if (gain < ECHOGAIN_MININP || gain > ECHOGAIN_MAXINP) + continue; + if (chip->input_gain[c] != gain) { + set_input_gain(chip, c, gain); + changed = 1; + } + } + if (changed) + update_input_line_level(chip); + return changed; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_input_gain, -2500, 50, 0); + +static const struct snd_kcontrol_new snd_echo_line_input_gain = { + .name = "Line Capture Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_echo_input_gain_info, + .get = snd_echo_input_gain_get, + .put = snd_echo_input_gain_put, + .tlv = {.p = db_scale_input_gain}, +}; + +#endif /* ECHOCARD_HAS_INPUT_GAIN */ + + + +#ifdef ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL + +/************ Analog output nominal level (+4dBu / -10dBV) ***************/ +static int snd_echo_output_nominal_info (struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = num_analog_busses_out(chip); + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_echo_output_nominal_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int c; + + chip = snd_kcontrol_chip(kcontrol); + for (c = 0; c < num_analog_busses_out(chip); c++) + ucontrol->value.integer.value[c] = chip->nominal_level[c]; + return 0; +} + +static int snd_echo_output_nominal_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int c, changed; + + changed = 0; + chip = snd_kcontrol_chip(kcontrol); + guard(spinlock_irq)(&chip->lock); + for (c = 0; c < num_analog_busses_out(chip); c++) { + if (chip->nominal_level[c] != ucontrol->value.integer.value[c]) { + set_nominal_level(chip, c, + ucontrol->value.integer.value[c]); + changed = 1; + } + } + if (changed) + update_output_line_level(chip); + return changed; +} + +static const struct snd_kcontrol_new snd_echo_output_nominal_level = { + .name = "Line Playback Switch (-10dBV)", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_echo_output_nominal_info, + .get = snd_echo_output_nominal_get, + .put = snd_echo_output_nominal_put, +}; + +#endif /* ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL */ + + + +#ifdef ECHOCARD_HAS_INPUT_NOMINAL_LEVEL + +/*************** Analog input nominal level (+4dBu / -10dBV) ***************/ +static int snd_echo_input_nominal_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = num_analog_busses_in(chip); + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_echo_input_nominal_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int c; + + chip = snd_kcontrol_chip(kcontrol); + for (c = 0; c < num_analog_busses_in(chip); c++) + ucontrol->value.integer.value[c] = + chip->nominal_level[bx_analog_in(chip) + c]; + return 0; +} + +static int snd_echo_input_nominal_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int c, changed; + + changed = 0; + chip = snd_kcontrol_chip(kcontrol); + guard(spinlock_irq)(&chip->lock); + for (c = 0; c < num_analog_busses_in(chip); c++) { + if (chip->nominal_level[bx_analog_in(chip) + c] != + ucontrol->value.integer.value[c]) { + set_nominal_level(chip, bx_analog_in(chip) + c, + ucontrol->value.integer.value[c]); + changed = 1; + } + } + if (changed) + update_output_line_level(chip); /* "Output" is not a mistake + * here. + */ + return changed; +} + +static const struct snd_kcontrol_new snd_echo_intput_nominal_level = { + .name = "Line Capture Switch (-10dBV)", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_echo_input_nominal_info, + .get = snd_echo_input_nominal_get, + .put = snd_echo_input_nominal_put, +}; + +#endif /* ECHOCARD_HAS_INPUT_NOMINAL_LEVEL */ + + + +#ifdef ECHOCARD_HAS_MONITOR + +/******************* Monitor mixer *******************/ +static int snd_echo_mixer_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 = ECHOGAIN_MINOUT; + uinfo->value.integer.max = ECHOGAIN_MAXOUT; + return 0; +} + +static int snd_echo_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip = snd_kcontrol_chip(kcontrol); + unsigned int out = ucontrol->id.index / num_busses_in(chip); + unsigned int in = ucontrol->id.index % num_busses_in(chip); + + if (out >= ECHO_MAXAUDIOOUTPUTS || in >= ECHO_MAXAUDIOINPUTS) + return -EINVAL; + + ucontrol->value.integer.value[0] = chip->monitor_gain[out][in]; + return 0; +} + +static int snd_echo_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int changed, gain; + unsigned int out, in; + + changed = 0; + chip = snd_kcontrol_chip(kcontrol); + out = ucontrol->id.index / num_busses_in(chip); + in = ucontrol->id.index % num_busses_in(chip); + if (out >= ECHO_MAXAUDIOOUTPUTS || in >= ECHO_MAXAUDIOINPUTS) + return -EINVAL; + gain = ucontrol->value.integer.value[0]; + if (gain < ECHOGAIN_MINOUT || gain > ECHOGAIN_MAXOUT) + return -EINVAL; + if (chip->monitor_gain[out][in] != gain) { + guard(spinlock_irq)(&chip->lock); + set_monitor_gain(chip, out, in, gain); + update_output_line_level(chip); + changed = 1; + } + return changed; +} + +static struct snd_kcontrol_new snd_echo_monitor_mixer = { + .name = "Monitor Mixer Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_echo_mixer_info, + .get = snd_echo_mixer_get, + .put = snd_echo_mixer_put, + .tlv = {.p = db_scale_output_gain}, +}; + +#endif /* ECHOCARD_HAS_MONITOR */ + + + +#ifdef ECHOCARD_HAS_VMIXER + +/******************* Vmixer *******************/ +static int snd_echo_vmixer_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 = ECHOGAIN_MINOUT; + uinfo->value.integer.max = ECHOGAIN_MAXOUT; + return 0; +} + +static int snd_echo_vmixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = + chip->vmixer_gain[ucontrol->id.index / num_pipes_out(chip)] + [ucontrol->id.index % num_pipes_out(chip)]; + return 0; +} + +static int snd_echo_vmixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int gain, changed; + short vch, out; + + changed = 0; + chip = snd_kcontrol_chip(kcontrol); + out = ucontrol->id.index / num_pipes_out(chip); + vch = ucontrol->id.index % num_pipes_out(chip); + gain = ucontrol->value.integer.value[0]; + if (gain < ECHOGAIN_MINOUT || gain > ECHOGAIN_MAXOUT) + return -EINVAL; + if (chip->vmixer_gain[out][vch] != ucontrol->value.integer.value[0]) { + guard(spinlock_irq)(&chip->lock); + set_vmixer_gain(chip, out, vch, ucontrol->value.integer.value[0]); + update_vmixer_level(chip); + changed = 1; + } + return changed; +} + +static struct snd_kcontrol_new snd_echo_vmixer = { + .name = "VMixer Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_echo_vmixer_info, + .get = snd_echo_vmixer_get, + .put = snd_echo_vmixer_put, + .tlv = {.p = db_scale_output_gain}, +}; + +#endif /* ECHOCARD_HAS_VMIXER */ + + + +#ifdef ECHOCARD_HAS_DIGITAL_MODE_SWITCH + +/******************* Digital mode switch *******************/ +static int snd_echo_digital_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const names[4] = { + "S/PDIF Coaxial", "S/PDIF Optical", "ADAT Optical", + "S/PDIF Cdrom" + }; + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + return snd_ctl_enum_info(uinfo, 1, chip->num_digital_modes, names); +} + +static int snd_echo_digital_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int i, mode; + + chip = snd_kcontrol_chip(kcontrol); + mode = chip->digital_mode; + for (i = chip->num_digital_modes - 1; i >= 0; i--) + if (mode == chip->digital_mode_list[i]) { + ucontrol->value.enumerated.item[0] = i; + break; + } + return 0; +} + +static int snd_echo_digital_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int changed; + unsigned short emode, dmode; + + changed = 0; + chip = snd_kcontrol_chip(kcontrol); + + emode = ucontrol->value.enumerated.item[0]; + if (emode >= chip->num_digital_modes) + return -EINVAL; + dmode = chip->digital_mode_list[emode]; + + if (dmode != chip->digital_mode) { + /* mode_mutex is required to make this operation atomic wrt + pcm_digital_*_open() and set_input_clock() functions. */ + guard(mutex)(&chip->mode_mutex); + + /* Do not allow the user to change the digital mode when a pcm + device is open because it also changes the number of channels + and the allowed sample rates */ + if (chip->opencount) { + changed = -EAGAIN; + } else { + changed = set_digital_mode(chip, dmode); + /* If we had to change the clock source, report it */ + if (changed > 0 && chip->clock_src_ctl) { + snd_ctl_notify(chip->card, + SNDRV_CTL_EVENT_MASK_VALUE, + &chip->clock_src_ctl->id); + dev_dbg(chip->card->dev, + "SDM() =%d\n", changed); + } + if (changed >= 0) + changed = 1; /* No errors */ + } + } + return changed; +} + +static const struct snd_kcontrol_new snd_echo_digital_mode_switch = { + .name = "Digital mode Switch", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = snd_echo_digital_mode_info, + .get = snd_echo_digital_mode_get, + .put = snd_echo_digital_mode_put, +}; + +#endif /* ECHOCARD_HAS_DIGITAL_MODE_SWITCH */ + + + +#ifdef ECHOCARD_HAS_DIGITAL_IO + +/******************* S/PDIF mode switch *******************/ +static int snd_echo_spdif_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const names[2] = {"Consumer", "Professional"}; + + return snd_ctl_enum_info(uinfo, 1, 2, names); +} + +static int snd_echo_spdif_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = !!chip->professional_spdif; + return 0; +} + +static int snd_echo_spdif_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int mode; + + chip = snd_kcontrol_chip(kcontrol); + mode = !!ucontrol->value.enumerated.item[0]; + if (mode != chip->professional_spdif) { + guard(spinlock_irq)(&chip->lock); + set_professional_spdif(chip, mode); + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new snd_echo_spdif_mode_switch = { + .name = "S/PDIF mode Switch", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = snd_echo_spdif_mode_info, + .get = snd_echo_spdif_mode_get, + .put = snd_echo_spdif_mode_put, +}; + +#endif /* ECHOCARD_HAS_DIGITAL_IO */ + + + +#ifdef ECHOCARD_HAS_EXTERNAL_CLOCK + +/******************* Select input clock source *******************/ +static int snd_echo_clock_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const names[8] = { + "Internal", "Word", "Super", "S/PDIF", "ADAT", "ESync", + "ESync96", "MTC" + }; + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + return snd_ctl_enum_info(uinfo, 1, chip->num_clock_sources, names); +} + +static int snd_echo_clock_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int i, clock; + + chip = snd_kcontrol_chip(kcontrol); + clock = chip->input_clock; + + for (i = 0; i < chip->num_clock_sources; i++) + if (clock == chip->clock_source_list[i]) + ucontrol->value.enumerated.item[0] = i; + + return 0; +} + +static int snd_echo_clock_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int changed; + unsigned int eclock, dclock; + + changed = 0; + chip = snd_kcontrol_chip(kcontrol); + eclock = ucontrol->value.enumerated.item[0]; + if (eclock >= chip->input_clock_types) + return -EINVAL; + dclock = chip->clock_source_list[eclock]; + if (chip->input_clock != dclock) { + guard(mutex)(&chip->mode_mutex); + guard(spinlock_irq)(&chip->lock); + changed = set_input_clock(chip, dclock); + if (!changed) + changed = 1; /* no errors */ + } + + if (changed < 0) + dev_dbg(chip->card->dev, + "seticlk val%d err 0x%x\n", dclock, changed); + + return changed; +} + +static const struct snd_kcontrol_new snd_echo_clock_source_switch = { + .name = "Sample Clock Source", + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_echo_clock_source_info, + .get = snd_echo_clock_source_get, + .put = snd_echo_clock_source_put, +}; + +#endif /* ECHOCARD_HAS_EXTERNAL_CLOCK */ + + + +#ifdef ECHOCARD_HAS_PHANTOM_POWER + +/******************* Phantom power switch *******************/ +#define snd_echo_phantom_power_info snd_ctl_boolean_mono_info + +static int snd_echo_phantom_power_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = chip->phantom_power; + return 0; +} + +static int snd_echo_phantom_power_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip = snd_kcontrol_chip(kcontrol); + int power, changed = 0; + + power = !!ucontrol->value.integer.value[0]; + if (chip->phantom_power != power) { + guard(spinlock_irq)(&chip->lock); + changed = set_phantom_power(chip, power); + if (changed == 0) + changed = 1; /* no errors */ + } + return changed; +} + +static const struct snd_kcontrol_new snd_echo_phantom_power_switch = { + .name = "Phantom power Switch", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = snd_echo_phantom_power_info, + .get = snd_echo_phantom_power_get, + .put = snd_echo_phantom_power_put, +}; + +#endif /* ECHOCARD_HAS_PHANTOM_POWER */ + + + +#ifdef ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE + +/******************* Digital input automute switch *******************/ +#define snd_echo_automute_info snd_ctl_boolean_mono_info + +static int snd_echo_automute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = chip->digital_in_automute; + return 0; +} + +static int snd_echo_automute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip = snd_kcontrol_chip(kcontrol); + int automute, changed = 0; + + automute = !!ucontrol->value.integer.value[0]; + if (chip->digital_in_automute != automute) { + guard(spinlock_irq)(&chip->lock); + changed = set_input_auto_mute(chip, automute); + if (changed == 0) + changed = 1; /* no errors */ + } + return changed; +} + +static const struct snd_kcontrol_new snd_echo_automute_switch = { + .name = "Digital Capture Switch (automute)", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = snd_echo_automute_info, + .get = snd_echo_automute_get, + .put = snd_echo_automute_put, +}; + +#endif /* ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE */ + + + +/******************* VU-meters switch *******************/ +#define snd_echo_vumeters_switch_info snd_ctl_boolean_mono_info + +static int snd_echo_vumeters_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + guard(spinlock_irq)(&chip->lock); + set_meters_on(chip, ucontrol->value.integer.value[0]); + return 1; +} + +static const struct snd_kcontrol_new snd_echo_vumeters_switch = { + .name = "VU-meters Switch", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = snd_echo_vumeters_switch_info, + .put = snd_echo_vumeters_switch_put, +}; + + + +/***** Read VU-meters (input, output, analog and digital together) *****/ +static int snd_echo_vumeters_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 96; + uinfo->value.integer.min = ECHOGAIN_MINOUT; + uinfo->value.integer.max = 0; + return 0; +} + +static int snd_echo_vumeters_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + get_audio_meters(chip, ucontrol->value.integer.value); + return 0; +} + +static const struct snd_kcontrol_new snd_echo_vumeters = { + .name = "VU-meters", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_echo_vumeters_info, + .get = snd_echo_vumeters_get, + .tlv = {.p = db_scale_output_gain}, +}; + + + +/*** Channels info - it exports informations about the number of channels ***/ +static int snd_echo_channels_info_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 6; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1 << ECHO_CLOCK_NUMBER; + return 0; +} + +static int snd_echo_channels_info_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct echoaudio *chip; + int detected, clocks, bit, src; + + chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = num_busses_in(chip); + ucontrol->value.integer.value[1] = num_analog_busses_in(chip); + ucontrol->value.integer.value[2] = num_busses_out(chip); + ucontrol->value.integer.value[3] = num_analog_busses_out(chip); + ucontrol->value.integer.value[4] = num_pipes_out(chip); + + /* Compute the bitmask of the currently valid input clocks */ + detected = detect_input_clocks(chip); + clocks = 0; + src = chip->num_clock_sources - 1; + for (bit = ECHO_CLOCK_NUMBER - 1; bit >= 0; bit--) + if (detected & (1 << bit)) + for (; src >= 0; src--) + if (bit == chip->clock_source_list[src]) { + clocks |= 1 << src; + break; + } + ucontrol->value.integer.value[5] = clocks; + + return 0; +} + +static const struct snd_kcontrol_new snd_echo_channels_info = { + .name = "Channels info", + .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_echo_channels_info_info, + .get = snd_echo_channels_info_get, +}; + + + + +/****************************************************************************** + IRQ Handling +******************************************************************************/ +/* Check if a period has elapsed since last interrupt + * + * Don't make any updates to state; PCM core handles this with the + * correct locks. + * + * \return true if a period has elapsed, otherwise false + */ +static bool period_has_elapsed(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audiopipe *pipe = runtime->private_data; + u32 counter, step; + size_t period_bytes; + + if (pipe->state != PIPE_STATE_STARTED) + return false; + + period_bytes = frames_to_bytes(runtime, runtime->period_size); + + counter = le32_to_cpu(*pipe->dma_counter); /* presumed atomic */ + + step = counter - pipe->last_period; /* handles wrapping */ + step -= step % period_bytes; /* acknowledge whole periods only */ + + if (step == 0) + return false; /* haven't advanced a whole period yet */ + + pipe->last_period += step; /* used exclusively by us */ + return true; +} + +static irqreturn_t snd_echo_interrupt(int irq, void *dev_id) +{ + struct echoaudio *chip = dev_id; + int ss, st; + + spin_lock(&chip->lock); + st = service_irq(chip); + if (st < 0) { + spin_unlock(&chip->lock); + return IRQ_NONE; + } + /* The hardware doesn't tell us which substream caused the irq, + thus we have to check all running substreams. */ + for (ss = 0; ss < DSP_MAXPIPES; ss++) { + struct snd_pcm_substream *substream; + + substream = chip->substream[ss]; + if (substream && period_has_elapsed(substream)) { + spin_unlock(&chip->lock); + snd_pcm_period_elapsed(substream); + spin_lock(&chip->lock); + } + } + spin_unlock(&chip->lock); + +#ifdef ECHOCARD_HAS_MIDI + if (st > 0 && chip->midi_in) { + snd_rawmidi_receive(chip->midi_in, chip->midi_buffer, st); + dev_dbg(chip->card->dev, "rawmidi_iread=%d\n", st); + } +#endif + return IRQ_HANDLED; +} + + + + +/****************************************************************************** + Module construction / destruction +******************************************************************************/ + +static void snd_echo_free(struct snd_card *card) +{ + struct echoaudio *chip = card->private_data; + + if (chip->comm_page) + rest_in_peace(chip); + + if (chip->irq >= 0) + free_irq(chip->irq, chip); + + /* release chip data */ + free_firmware_cache(chip); +} + +/* <--snd_echo_probe() */ +static int snd_echo_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct echoaudio *chip = card->private_data; + int err; + size_t sz; + + pci_write_config_byte(pci, PCI_LATENCY_TIMER, 0xC0); + + err = pcim_enable_device(pci); + if (err < 0) + return err; + pci_set_master(pci); + + /* Allocate chip if needed */ + spin_lock_init(&chip->lock); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->opencount = 0; + mutex_init(&chip->mode_mutex); + chip->can_set_rate = 1; + + /* PCI resource allocation */ + err = pcim_request_all_regions(pci, ECHOCARD_NAME); + if (err < 0) + return err; + + chip->dsp_registers_phys = pci_resource_start(pci, 0); + sz = pci_resource_len(pci, 0); + if (sz > PAGE_SIZE) + sz = PAGE_SIZE; /* We map only the required part */ + + chip->dsp_registers = devm_ioremap(&pci->dev, chip->dsp_registers_phys, sz); + if (!chip->dsp_registers) { + dev_err(chip->card->dev, "ioremap failed\n"); + return -ENOMEM; + } + + if (request_irq(pci->irq, snd_echo_interrupt, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(chip->card->dev, "cannot grab irq\n"); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + dev_dbg(card->dev, "pci=%p irq=%d subdev=%04x Init hardware...\n", + chip->pci, chip->irq, chip->pci->subsystem_device); + + card->private_free = snd_echo_free; + + /* Create the DSP comm page - this is the area of memory used for most + of the communication with the DSP, which accesses it via bus mastering */ + chip->commpage_dma_buf = + snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + sizeof(struct comm_page)); + if (!chip->commpage_dma_buf) + return -ENOMEM; + chip->comm_page_phys = chip->commpage_dma_buf->addr; + chip->comm_page = (struct comm_page *)chip->commpage_dma_buf->area; + + err = init_hw(chip, chip->pci->device, chip->pci->subsystem_device); + if (err >= 0) + err = set_mixer_defaults(chip); + if (err < 0) { + dev_err(card->dev, "init_hw err=%d\n", err); + return err; + } + + return 0; +} + +/* constructor */ +static int __snd_echo_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct echoaudio *chip; + char *dsp; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + err = snd_echo_create(card, pci); + if (err < 0) + return err; + + strscpy(card->driver, "Echo_" ECHOCARD_NAME); + strscpy(card->shortname, chip->card_name); + + dsp = "56301"; + if (pci_id->device == 0x3410) + dsp = "56361"; + + sprintf(card->longname, "%s rev.%d (DSP%s) at 0x%lx irq %i", + card->shortname, pci_id->subdevice & 0x000f, dsp, + chip->dsp_registers_phys, chip->irq); + + err = snd_echo_new_pcm(chip); + if (err < 0) { + dev_err(chip->card->dev, "new pcm error %d\n", err); + return err; + } + +#ifdef ECHOCARD_HAS_MIDI + if (chip->has_midi) { /* Some Mia's do not have midi */ + err = snd_echo_midi_create(card, chip); + if (err < 0) { + dev_err(chip->card->dev, "new midi error %d\n", err); + return err; + } + } +#endif + +#ifdef ECHOCARD_HAS_VMIXER + snd_echo_vmixer.count = num_pipes_out(chip) * num_busses_out(chip); + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_vmixer, chip)); + if (err < 0) + return err; +#ifdef ECHOCARD_HAS_LINE_OUT_GAIN + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_echo_line_output_gain, chip)); + if (err < 0) + return err; +#endif +#else /* ECHOCARD_HAS_VMIXER */ + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_echo_pcm_output_gain, chip)); + if (err < 0) + return err; +#endif /* ECHOCARD_HAS_VMIXER */ + +#ifdef ECHOCARD_HAS_INPUT_GAIN + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_line_input_gain, chip)); + if (err < 0) + return err; +#endif + +#ifdef ECHOCARD_HAS_INPUT_NOMINAL_LEVEL + if (!chip->hasnt_input_nominal_level) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_intput_nominal_level, chip)); + if (err < 0) + return err; + } +#endif + +#ifdef ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_output_nominal_level, chip)); + if (err < 0) + return err; +#endif + + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_vumeters_switch, chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_vumeters, chip)); + if (err < 0) + return err; + +#ifdef ECHOCARD_HAS_MONITOR + snd_echo_monitor_mixer.count = num_busses_in(chip) * num_busses_out(chip); + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_monitor_mixer, chip)); + if (err < 0) + return err; +#endif + +#ifdef ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_automute_switch, chip)); + if (err < 0) + return err; +#endif + + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_channels_info, chip)); + if (err < 0) + return err; + +#ifdef ECHOCARD_HAS_DIGITAL_MODE_SWITCH + /* Creates a list of available digital modes */ + chip->num_digital_modes = 0; + for (int i = 0; i < 6; i++) + if (chip->digital_modes & (1 << i)) + chip->digital_mode_list[chip->num_digital_modes++] = i; + + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_digital_mode_switch, chip)); + if (err < 0) + return err; +#endif /* ECHOCARD_HAS_DIGITAL_MODE_SWITCH */ + +#ifdef ECHOCARD_HAS_EXTERNAL_CLOCK + /* Creates a list of available clock sources */ + chip->num_clock_sources = 0; + for (int i = 0; i < 10; i++) + if (chip->input_clock_types & (1 << i)) + chip->clock_source_list[chip->num_clock_sources++] = i; + + if (chip->num_clock_sources > 1) { + chip->clock_src_ctl = snd_ctl_new1(&snd_echo_clock_source_switch, chip); + err = snd_ctl_add(chip->card, chip->clock_src_ctl); + if (err < 0) + return err; + } +#endif /* ECHOCARD_HAS_EXTERNAL_CLOCK */ + +#ifdef ECHOCARD_HAS_DIGITAL_IO + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_spdif_mode_switch, chip)); + if (err < 0) + return err; +#endif + +#ifdef ECHOCARD_HAS_PHANTOM_POWER + if (chip->has_phantom_power) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_phantom_power_switch, chip)); + if (err < 0) + return err; + } +#endif + + err = snd_card_register(card); + if (err < 0) + return err; + dev_info(card->dev, "Card registered: %s\n", card->longname); + + pci_set_drvdata(pci, chip); + dev++; + return 0; +} + +static int snd_echo_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_echo_probe(pci, pci_id)); +} + + +static int snd_echo_suspend(struct device *dev) +{ + struct echoaudio *chip = dev_get_drvdata(dev); + +#ifdef ECHOCARD_HAS_MIDI + /* This call can sleep */ + if (chip->midi_out) + snd_echo_midi_output_trigger(chip->midi_out, 0); +#endif + scoped_guard(spinlock_irq, &chip->lock) { + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + if (send_vector(chip, DSP_VC_GO_COMATOSE) < 0) + return -EIO; + } + + chip->dsp_code = NULL; + free_irq(chip->irq, chip); + chip->irq = -1; + chip->card->sync_irq = -1; + return 0; +} + + + +static int snd_echo_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct echoaudio *chip = dev_get_drvdata(dev); + struct comm_page *commpage, *commpage_bak; + u32 pipe_alloc_mask; + int err; + + commpage = chip->comm_page; + commpage_bak = kmemdup(commpage, sizeof(*commpage), GFP_KERNEL); + if (commpage_bak == NULL) + return -ENOMEM; + + err = init_hw(chip, chip->pci->device, chip->pci->subsystem_device); + if (err < 0) { + kfree(commpage_bak); + dev_err(dev, "resume init_hw err=%d\n", err); + return err; + } + + /* Temporarily set chip->pipe_alloc_mask=0 otherwise + * restore_dsp_settings() fails. + */ + pipe_alloc_mask = chip->pipe_alloc_mask; + chip->pipe_alloc_mask = 0; + err = restore_dsp_settings(chip); + chip->pipe_alloc_mask = pipe_alloc_mask; + if (err < 0) { + kfree(commpage_bak); + return err; + } + + memcpy(&commpage->audio_format, &commpage_bak->audio_format, + sizeof(commpage->audio_format)); + memcpy(&commpage->sglist_addr, &commpage_bak->sglist_addr, + sizeof(commpage->sglist_addr)); + memcpy(&commpage->midi_output, &commpage_bak->midi_output, + sizeof(commpage->midi_output)); + kfree(commpage_bak); + + if (request_irq(pci->irq, snd_echo_interrupt, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(chip->card->dev, "cannot grab irq\n"); + return -EBUSY; + } + chip->irq = pci->irq; + chip->card->sync_irq = chip->irq; + dev_dbg(dev, "resume irq=%d\n", chip->irq); + +#ifdef ECHOCARD_HAS_MIDI + if (chip->midi_input_enabled) + enable_midi_input(chip, true); + if (chip->midi_out) + snd_echo_midi_output_trigger(chip->midi_out, 1); +#endif + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_echo_pm, snd_echo_suspend, snd_echo_resume); + +/****************************************************************************** + Everything starts and ends here +******************************************************************************/ + +/* pci_driver definition */ +static struct pci_driver echo_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_echo_ids, + .probe = snd_echo_probe, + .driver = { + .pm = &snd_echo_pm, + }, +}; + +module_pci_driver(echo_driver); diff --git a/sound/pci/echoaudio/echoaudio.h b/sound/pci/echoaudio/echoaudio.h new file mode 100644 index 000000000..7a5744bf3 --- /dev/null +++ b/sound/pci/echoaudio/echoaudio.h @@ -0,0 +1,549 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + **************************************************************************** + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + + **************************************************************************** + + + Here's a block diagram of how most of the cards work: + + +-----------+ + record | |<-------------------- Inputs + <-------| | | + PCI | Transport | | + bus | engine | \|/ + ------->| | +-------+ + play | |--->|monitor|-------> Outputs + +-----------+ | mixer | + +-------+ + + The lines going to and from the PCI bus represent "pipes". A pipe performs + audio transport - moving audio data to and from buffers on the host via + bus mastering. + + The inputs and outputs on the right represent input and output "busses." + A bus is a physical, real connection to the outside world. An example + of a bus would be the 1/4" analog connectors on the back of Layla or + an RCA S/PDIF connector. + + For most cards, there is a one-to-one correspondence between outputs + and busses; that is, each individual pipe is hard-wired to a single bus. + + Cards that work this way are Darla20, Gina20, Layla20, Darla24, Gina24, + Layla24, Mona, and Indigo. + + + Mia has a feature called "virtual outputs." + + + +-----------+ + record | |<----------------------------- Inputs + <-------| | | + PCI | Transport | | + bus | engine | \|/ + ------->| | +------+ +-------+ + play | |-->|vmixer|-->|monitor|-------> Outputs + +-----------+ +------+ | mixer | + +-------+ + + + Obviously, the difference here is the box labeled "vmixer." Vmixer is + short for "virtual output mixer." For Mia, pipes are *not* hard-wired + to a single bus; the vmixer lets you mix any pipe to any bus in any + combination. + + Note, however, that the left-hand side of the diagram is unchanged. + Transport works exactly the same way - the difference is in the mixer stage. + + + Pipes and busses are numbered starting at zero. + + + + Pipe index + ========== + + A number of calls in CEchoGals refer to a "pipe index". A pipe index is + a unique number for a pipe that unambiguously refers to a playback or record + pipe. Pipe indices are numbered starting with analog outputs, followed by + digital outputs, then analog inputs, then digital inputs. + + Take Gina24 as an example: + + Pipe index + + 0-7 Analog outputs (0 .. FirstDigitalBusOut-1) + 8-15 Digital outputs (FirstDigitalBusOut .. NumBussesOut-1) + 16-17 Analog inputs + 18-25 Digital inputs + + + You get the pipe index by calling CEchoGals::OpenAudio; the other transport + functions take the pipe index as a parameter. If you need a pipe index for + some other reason, use the handy Makepipe_index method. + + + Some calls take a CChannelMask parameter; CChannelMask is a handy way to + group pipe indices. + + + + Digital mode switch + =================== + + Some cards (right now, Gina24, Layla24, and Mona) have a Digital Mode Switch + or DMS. Cards with a DMS can be set to one of three mutually exclusive + digital modes: S/PDIF RCA, S/PDIF optical, or ADAT optical. + + This may create some confusion since ADAT optical is 8 channels wide and + S/PDIF is only two channels wide. Gina24, Layla24, and Mona handle this + by acting as if they always have 8 digital outs and ins. If you are in + either S/PDIF mode, the last 6 channels don't do anything - data sent + out these channels is thrown away and you will always record zeros. + + Note that with Gina24, Layla24, and Mona, sample rates above 50 kHz are + only available if you have the card configured for S/PDIF optical or S/PDIF + RCA. + + + + Double speed mode + ================= + + Some of the cards support 88.2 kHz and 96 kHz sampling (Darla24, Gina24, + Layla24, Mona, Mia, and Indigo). For these cards, the driver sometimes has + to worry about "double speed mode"; double speed mode applies whenever the + sampling rate is above 50 kHz. + + For instance, Mona and Layla24 support word clock sync. However, they + actually support two different word clock modes - single speed (below + 50 kHz) and double speed (above 50 kHz). The hardware detects if a single + or double speed word clock signal is present; the generic code uses that + information to determine which mode to use. + + The generic code takes care of all this for you. +*/ + + +#ifndef _ECHOAUDIO_H_ +#define _ECHOAUDIO_H_ + + +#include "echoaudio_dsp.h" + + + +/*********************************************************************** + + PCI configuration space + +***********************************************************************/ + +/* + * PCI vendor ID and device IDs for the hardware + */ +#define VENDOR_ID 0x1057 +#define DEVICE_ID_56301 0x1801 +#define DEVICE_ID_56361 0x3410 +#define SUBVENDOR_ID 0xECC0 + + +/* + * Valid Echo PCI subsystem card IDs + */ +#define DARLA20 0x0010 +#define GINA20 0x0020 +#define LAYLA20 0x0030 +#define DARLA24 0x0040 +#define GINA24 0x0050 +#define LAYLA24 0x0060 +#define MONA 0x0070 +#define MIA 0x0080 +#define INDIGO 0x0090 +#define INDIGO_IO 0x00a0 +#define INDIGO_DJ 0x00b0 +#define DC8 0x00c0 +#define INDIGO_IOX 0x00d0 +#define INDIGO_DJX 0x00e0 +#define ECHO3G 0x0100 + + +/************************************************************************ + + Array sizes and so forth + +***********************************************************************/ + +/* + * Sizes + */ +#define ECHO_MAXAUDIOINPUTS 32 /* Max audio input channels */ +#define ECHO_MAXAUDIOOUTPUTS 32 /* Max audio output channels */ +#define ECHO_MAXAUDIOPIPES 32 /* Max number of input and output + * pipes */ +#define E3G_MAX_OUTPUTS 16 +#define ECHO_MAXMIDIJACKS 1 /* Max MIDI ports */ +#define ECHO_MIDI_QUEUE_SZ 512 /* Max MIDI input queue entries */ +#define ECHO_MTC_QUEUE_SZ 32 /* Max MIDI time code input queue + * entries */ + +/* + * MIDI activity indicator timeout + */ +#define MIDI_ACTIVITY_TIMEOUT_USEC 200000 + + +/**************************************************************************** + + Clocks + +*****************************************************************************/ + +/* + * Clock numbers + */ +#define ECHO_CLOCK_INTERNAL 0 +#define ECHO_CLOCK_WORD 1 +#define ECHO_CLOCK_SUPER 2 +#define ECHO_CLOCK_SPDIF 3 +#define ECHO_CLOCK_ADAT 4 +#define ECHO_CLOCK_ESYNC 5 +#define ECHO_CLOCK_ESYNC96 6 +#define ECHO_CLOCK_MTC 7 +#define ECHO_CLOCK_NUMBER 8 +#define ECHO_CLOCKS 0xffff + +/* + * Clock bit numbers - used to report capabilities and whatever clocks + * are being detected dynamically. + */ +#define ECHO_CLOCK_BIT_INTERNAL (1 << ECHO_CLOCK_INTERNAL) +#define ECHO_CLOCK_BIT_WORD (1 << ECHO_CLOCK_WORD) +#define ECHO_CLOCK_BIT_SUPER (1 << ECHO_CLOCK_SUPER) +#define ECHO_CLOCK_BIT_SPDIF (1 << ECHO_CLOCK_SPDIF) +#define ECHO_CLOCK_BIT_ADAT (1 << ECHO_CLOCK_ADAT) +#define ECHO_CLOCK_BIT_ESYNC (1 << ECHO_CLOCK_ESYNC) +#define ECHO_CLOCK_BIT_ESYNC96 (1 << ECHO_CLOCK_ESYNC96) +#define ECHO_CLOCK_BIT_MTC (1<comm_page->handshake = 0; +} + +static inline u32 get_dsp_register(struct echoaudio *chip, u32 index) +{ + return readl(&chip->dsp_registers[index]); +} + +static inline void set_dsp_register(struct echoaudio *chip, u32 index, + u32 value) +{ + writel(value, &chip->dsp_registers[index]); +} + + +/* Pipe and bus indexes. PX_* and BX_* are defined as chip->px_* and chip->bx_* +for 3G cards because they depend on the external box. They are integer +constants for all other cards. +Never use those defines directly, use the following functions instead. */ + +static inline int px_digital_out(const struct echoaudio *chip) +{ + return PX_DIGITAL_OUT; +} + +static inline int px_analog_in(const struct echoaudio *chip) +{ + return PX_ANALOG_IN; +} + +static inline int px_digital_in(const struct echoaudio *chip) +{ + return PX_DIGITAL_IN; +} + +static inline int px_num(const struct echoaudio *chip) +{ + return PX_NUM; +} + +static inline int bx_digital_out(const struct echoaudio *chip) +{ + return BX_DIGITAL_OUT; +} + +static inline int bx_analog_in(const struct echoaudio *chip) +{ + return BX_ANALOG_IN; +} + +static inline int bx_digital_in(const struct echoaudio *chip) +{ + return BX_DIGITAL_IN; +} + +static inline int bx_num(const struct echoaudio *chip) +{ + return BX_NUM; +} + +static inline int num_pipes_out(const struct echoaudio *chip) +{ + return px_analog_in(chip); +} + +static inline int num_pipes_in(const struct echoaudio *chip) +{ + return px_num(chip) - px_analog_in(chip); +} + +static inline int num_busses_out(const struct echoaudio *chip) +{ + return bx_analog_in(chip); +} + +static inline int num_busses_in(const struct echoaudio *chip) +{ + return bx_num(chip) - bx_analog_in(chip); +} + +static inline int num_analog_busses_out(const struct echoaudio *chip) +{ + return bx_digital_out(chip); +} + +static inline int num_analog_busses_in(const struct echoaudio *chip) +{ + return bx_digital_in(chip) - bx_analog_in(chip); +} + +static inline int num_digital_busses_out(const struct echoaudio *chip) +{ + return num_busses_out(chip) - num_analog_busses_out(chip); +} + +static inline int num_digital_busses_in(const struct echoaudio *chip) +{ + return num_busses_in(chip) - num_analog_busses_in(chip); +} + +/* The monitor array is a one-dimensional array; compute the offset + * into the array */ +static inline int monitor_index(const struct echoaudio *chip, int out, int in) +{ + return out * num_busses_in(chip) + in; +} + +#endif /* _ECHOAUDIO_H_ */ diff --git a/sound/pci/echoaudio/echoaudio_3g.c b/sound/pci/echoaudio/echoaudio_3g.c new file mode 100644 index 000000000..4b3c6a8bc --- /dev/null +++ b/sound/pci/echoaudio/echoaudio_3g.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + + +/* These functions are common for all "3G" cards */ + + +static int check_asic_status(struct echoaudio *chip) +{ + u32 box_status; + + if (wait_handshake(chip)) + return -EIO; + + chip->comm_page->ext_box_status = cpu_to_le32(E3G_ASIC_NOT_LOADED); + chip->asic_loaded = false; + clear_handshake(chip); + send_vector(chip, DSP_VC_TEST_ASIC); + + if (wait_handshake(chip)) { + chip->dsp_code = NULL; + return -EIO; + } + + box_status = le32_to_cpu(chip->comm_page->ext_box_status); + dev_dbg(chip->card->dev, "box_status=%x\n", box_status); + if (box_status == E3G_ASIC_NOT_LOADED) + return -ENODEV; + + chip->asic_loaded = true; + return box_status & E3G_BOX_TYPE_MASK; +} + + + +static inline u32 get_frq_reg(struct echoaudio *chip) +{ + return le32_to_cpu(chip->comm_page->e3g_frq_register); +} + + + +/* Most configuration of 3G cards is accomplished by writing the control +register. write_control_reg sends the new control register value to the DSP. */ +static int write_control_reg(struct echoaudio *chip, u32 ctl, u32 frq, + char force) +{ + __le32 ctl_reg, frq_reg; + + if (wait_handshake(chip)) + return -EIO; + + dev_dbg(chip->card->dev, + "WriteControlReg: Setting 0x%x, 0x%x\n", ctl, frq); + + ctl_reg = cpu_to_le32(ctl); + frq_reg = cpu_to_le32(frq); + + if (ctl_reg != chip->comm_page->control_register || + frq_reg != chip->comm_page->e3g_frq_register || force) { + chip->comm_page->e3g_frq_register = frq_reg; + chip->comm_page->control_register = ctl_reg; + clear_handshake(chip); + return send_vector(chip, DSP_VC_WRITE_CONTROL_REG); + } + + dev_dbg(chip->card->dev, "WriteControlReg: not written, no change\n"); + return 0; +} + + + +/* Set the digital mode - currently for Gina24, Layla24, Mona, 3G */ +static int set_digital_mode(struct echoaudio *chip, u8 mode) +{ + u8 previous_mode; + int err, i, o; + + /* All audio channels must be closed before changing the digital mode */ + if (snd_BUG_ON(chip->pipe_alloc_mask)) + return -EAGAIN; + + if (snd_BUG_ON(!(chip->digital_modes & (1 << mode)))) + return -EINVAL; + + previous_mode = chip->digital_mode; + err = dsp_set_digital_mode(chip, mode); + + /* If we successfully changed the digital mode from or to ADAT, + * then make sure all output, input and monitor levels are + * updated by the DSP comm object. */ + if (err >= 0 && previous_mode != mode && + (previous_mode == DIGITAL_MODE_ADAT || mode == DIGITAL_MODE_ADAT)) { + guard(spinlock_irq)(&chip->lock); + for (o = 0; o < num_busses_out(chip); o++) + for (i = 0; i < num_busses_in(chip); i++) + set_monitor_gain(chip, o, i, + chip->monitor_gain[o][i]); + +#ifdef ECHOCARD_HAS_INPUT_GAIN + for (i = 0; i < num_busses_in(chip); i++) + set_input_gain(chip, i, chip->input_gain[i]); + update_input_line_level(chip); +#endif + + for (o = 0; o < num_busses_out(chip); o++) + set_output_gain(chip, o, chip->output_gain[o]); + update_output_line_level(chip); + } + + return err; +} + + + +static u32 set_spdif_bits(struct echoaudio *chip, u32 control_reg, u32 rate) +{ + control_reg &= E3G_SPDIF_FORMAT_CLEAR_MASK; + + switch (rate) { + case 32000 : + control_reg |= E3G_SPDIF_SAMPLE_RATE0 | E3G_SPDIF_SAMPLE_RATE1; + break; + case 44100 : + if (chip->professional_spdif) + control_reg |= E3G_SPDIF_SAMPLE_RATE0; + break; + case 48000 : + control_reg |= E3G_SPDIF_SAMPLE_RATE1; + break; + } + + if (chip->professional_spdif) + control_reg |= E3G_SPDIF_PRO_MODE; + + if (chip->non_audio_spdif) + control_reg |= E3G_SPDIF_NOT_AUDIO; + + control_reg |= E3G_SPDIF_24_BIT | E3G_SPDIF_TWO_CHANNEL | + E3G_SPDIF_COPY_PERMIT; + + return control_reg; +} + + + +/* Set the S/PDIF output format */ +static int set_professional_spdif(struct echoaudio *chip, char prof) +{ + u32 control_reg; + + control_reg = le32_to_cpu(chip->comm_page->control_register); + chip->professional_spdif = prof; + control_reg = set_spdif_bits(chip, control_reg, chip->sample_rate); + return write_control_reg(chip, control_reg, get_frq_reg(chip), 0); +} + + + +/* detect_input_clocks() returns a bitmask consisting of all the input clocks +currently connected to the hardware; this changes as the user connects and +disconnects clock inputs. You should use this information to determine which +clocks the user is allowed to select. */ +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + u32 clocks_from_dsp, clock_bits; + + /* Map the DSP clock detect bits to the generic driver clock + * detect bits */ + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + clock_bits = ECHO_CLOCK_BIT_INTERNAL; + + if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_WORD) + clock_bits |= ECHO_CLOCK_BIT_WORD; + + switch(chip->digital_mode) { + case DIGITAL_MODE_SPDIF_RCA: + case DIGITAL_MODE_SPDIF_OPTICAL: + if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_SPDIF) + clock_bits |= ECHO_CLOCK_BIT_SPDIF; + break; + case DIGITAL_MODE_ADAT: + if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_ADAT) + clock_bits |= ECHO_CLOCK_BIT_ADAT; + break; + } + + return clock_bits; +} + + + +static int load_asic(struct echoaudio *chip) +{ + int box_type, err; + + if (chip->asic_loaded) + return 0; + + /* Give the DSP a few milliseconds to settle down */ + mdelay(2); + + err = load_asic_generic(chip, DSP_FNC_LOAD_3G_ASIC, FW_3G_ASIC); + if (err < 0) + return err; + + chip->asic_code = FW_3G_ASIC; + + /* Now give the new ASIC some time to set up */ + msleep(1000); + /* See if it worked */ + box_type = check_asic_status(chip); + + /* Set up the control register if the load succeeded - + * 48 kHz, internal clock, S/PDIF RCA mode */ + if (box_type >= 0) { + err = write_control_reg(chip, E3G_48KHZ, + E3G_FREQ_REG_DEFAULT, true); + if (err < 0) + return err; + } + + return box_type; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u32 control_reg, clock, base_rate, frq_reg; + + /* Only set the clock for internal mode. */ + if (chip->input_clock != ECHO_CLOCK_INTERNAL) { + dev_warn(chip->card->dev, + "Cannot set sample rate - clock not set to CLK_CLOCKININTERNAL\n"); + /* Save the rate anyhow */ + chip->comm_page->sample_rate = cpu_to_le32(rate); + chip->sample_rate = rate; + set_input_clock(chip, chip->input_clock); + return 0; + } + + if (snd_BUG_ON(rate >= 50000 && + chip->digital_mode == DIGITAL_MODE_ADAT)) + return -EINVAL; + + control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg &= E3G_CLOCK_CLEAR_MASK; + + switch (rate) { + case 96000: + clock = E3G_96KHZ; + break; + case 88200: + clock = E3G_88KHZ; + break; + case 48000: + clock = E3G_48KHZ; + break; + case 44100: + clock = E3G_44KHZ; + break; + case 32000: + clock = E3G_32KHZ; + break; + default: + clock = E3G_CONTINUOUS_CLOCK; + if (rate > 50000) + clock |= E3G_DOUBLE_SPEED_MODE; + break; + } + + control_reg |= clock; + control_reg = set_spdif_bits(chip, control_reg, rate); + + base_rate = rate; + if (base_rate > 50000) + base_rate /= 2; + if (base_rate < 32000) + base_rate = 32000; + + frq_reg = E3G_MAGIC_NUMBER / base_rate - 2; + if (frq_reg > E3G_FREQ_REG_MAX) + frq_reg = E3G_FREQ_REG_MAX; + + chip->comm_page->sample_rate = cpu_to_le32(rate); /* ignored by the DSP */ + chip->sample_rate = rate; + dev_dbg(chip->card->dev, + "SetSampleRate: %d clock %x\n", rate, control_reg); + + /* Tell the DSP about it - DSP reads both control reg & freq reg */ + return write_control_reg(chip, control_reg, frq_reg, 0); +} + + + +/* Set the sample clock source to internal, S/PDIF, ADAT */ +static int set_input_clock(struct echoaudio *chip, u16 clock) +{ + u32 control_reg, clocks_from_dsp; + + + /* Mask off the clock select bits */ + control_reg = le32_to_cpu(chip->comm_page->control_register) & + E3G_CLOCK_CLEAR_MASK; + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + switch (clock) { + case ECHO_CLOCK_INTERNAL: + chip->input_clock = ECHO_CLOCK_INTERNAL; + return set_sample_rate(chip, chip->sample_rate); + case ECHO_CLOCK_SPDIF: + if (chip->digital_mode == DIGITAL_MODE_ADAT) + return -EAGAIN; + control_reg |= E3G_SPDIF_CLOCK; + if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_SPDIF96) + control_reg |= E3G_DOUBLE_SPEED_MODE; + else + control_reg &= ~E3G_DOUBLE_SPEED_MODE; + break; + case ECHO_CLOCK_ADAT: + if (chip->digital_mode != DIGITAL_MODE_ADAT) + return -EAGAIN; + control_reg |= E3G_ADAT_CLOCK; + control_reg &= ~E3G_DOUBLE_SPEED_MODE; + break; + case ECHO_CLOCK_WORD: + control_reg |= E3G_WORD_CLOCK; + if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_WORD96) + control_reg |= E3G_DOUBLE_SPEED_MODE; + else + control_reg &= ~E3G_DOUBLE_SPEED_MODE; + break; + default: + dev_err(chip->card->dev, + "Input clock 0x%x not supported for Echo3G\n", clock); + return -EINVAL; + } + + chip->input_clock = clock; + return write_control_reg(chip, control_reg, get_frq_reg(chip), 1); +} + + + +static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode) +{ + u32 control_reg; + int err, incompatible_clock; + + /* Set clock to "internal" if it's not compatible with the new mode */ + incompatible_clock = false; + switch (mode) { + case DIGITAL_MODE_SPDIF_OPTICAL: + case DIGITAL_MODE_SPDIF_RCA: + if (chip->input_clock == ECHO_CLOCK_ADAT) + incompatible_clock = true; + break; + case DIGITAL_MODE_ADAT: + if (chip->input_clock == ECHO_CLOCK_SPDIF) + incompatible_clock = true; + break; + default: + dev_err(chip->card->dev, + "Digital mode not supported: %d\n", mode); + return -EINVAL; + } + + guard(spinlock_irq)(&chip->lock); + + if (incompatible_clock) { + chip->sample_rate = 48000; + set_input_clock(chip, ECHO_CLOCK_INTERNAL); + } + + /* Clear the current digital mode */ + control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg &= E3G_DIGITAL_MODE_CLEAR_MASK; + + /* Tweak the control reg */ + switch (mode) { + case DIGITAL_MODE_SPDIF_OPTICAL: + control_reg |= E3G_SPDIF_OPTICAL_MODE; + break; + case DIGITAL_MODE_SPDIF_RCA: + /* E3G_SPDIF_OPTICAL_MODE bit cleared */ + break; + case DIGITAL_MODE_ADAT: + control_reg |= E3G_ADAT_MODE; + control_reg &= ~E3G_DOUBLE_SPEED_MODE; /* @@ useless */ + break; + } + + err = write_control_reg(chip, control_reg, get_frq_reg(chip), 1); + if (err < 0) + return err; + chip->digital_mode = mode; + + dev_dbg(chip->card->dev, "set_digital_mode(%d)\n", chip->digital_mode); + return incompatible_clock; +} diff --git a/sound/pci/echoaudio/echoaudio_dsp.c b/sound/pci/echoaudio/echoaudio_dsp.c new file mode 100644 index 000000000..4dd494508 --- /dev/null +++ b/sound/pci/echoaudio/echoaudio_dsp.c @@ -0,0 +1,1141 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + +#if PAGE_SIZE < 4096 +#error PAGE_SIZE is < 4k +#endif + +static int restore_dsp_settings(struct echoaudio *chip); + + +/* Some vector commands involve the DSP reading or writing data to and from the +comm page; if you send one of these commands to the DSP, it will complete the +command and then write a non-zero value to the Handshake field in the +comm page. This function waits for the handshake to show up. */ +static int wait_handshake(struct echoaudio *chip) +{ + int i; + + /* Wait up to 20ms for the handshake from the DSP */ + for (i = 0; i < HANDSHAKE_TIMEOUT; i++) { + /* Look for the handshake value */ + barrier(); + if (chip->comm_page->handshake) { + return 0; + } + udelay(1); + } + + dev_err(chip->card->dev, "wait_handshake(): Timeout waiting for DSP\n"); + return -EBUSY; +} + + + +/* Much of the interaction between the DSP and the driver is done via vector +commands; send_vector writes a vector command to the DSP. Typically, this +causes the DSP to read or write fields in the comm page. +PCI posting is not required thanks to the handshake logic. */ +static int send_vector(struct echoaudio *chip, u32 command) +{ + int i; + + wmb(); /* Flush all pending writes before sending the command */ + + /* Wait up to 100ms for the "vector busy" bit to be off */ + for (i = 0; i < VECTOR_BUSY_TIMEOUT; i++) { + if (!(get_dsp_register(chip, CHI32_VECTOR_REG) & + CHI32_VECTOR_BUSY)) { + set_dsp_register(chip, CHI32_VECTOR_REG, command); + /*if (i) DE_ACT(("send_vector time: %d\n", i));*/ + return 0; + } + udelay(1); + } + + dev_err(chip->card->dev, "timeout on send_vector\n"); + return -EBUSY; +} + + + +/* write_dsp writes a 32-bit value to the DSP; this is used almost +exclusively for loading the DSP. */ +static int write_dsp(struct echoaudio *chip, u32 data) +{ + u32 status, i; + + for (i = 0; i < 10000000; i++) { /* timeout = 10s */ + status = get_dsp_register(chip, CHI32_STATUS_REG); + if ((status & CHI32_STATUS_HOST_WRITE_EMPTY) != 0) { + set_dsp_register(chip, CHI32_DATA_REG, data); + wmb(); /* write it immediately */ + return 0; + } + udelay(1); + cond_resched(); + } + + chip->bad_board = true; /* Set true until DSP re-loaded */ + dev_dbg(chip->card->dev, "write_dsp: Set bad_board to true\n"); + return -EIO; +} + + + +/* read_dsp reads a 32-bit value from the DSP; this is used almost +exclusively for loading the DSP and checking the status of the ASIC. */ +static int read_dsp(struct echoaudio *chip, u32 *data) +{ + u32 status, i; + + for (i = 0; i < READ_DSP_TIMEOUT; i++) { + status = get_dsp_register(chip, CHI32_STATUS_REG); + if ((status & CHI32_STATUS_HOST_READ_FULL) != 0) { + *data = get_dsp_register(chip, CHI32_DATA_REG); + return 0; + } + udelay(1); + cond_resched(); + } + + chip->bad_board = true; /* Set true until DSP re-loaded */ + dev_err(chip->card->dev, "read_dsp: Set bad_board to true\n"); + return -EIO; +} + + + +/**************************************************************************** + Firmware loading functions + ****************************************************************************/ + +/* This function is used to read back the serial number from the DSP; +this is triggered by the SET_COMMPAGE_ADDR command. +Only some early Echogals products have serial numbers in the ROM; +the serial number is not used, but you still need to do this as +part of the DSP load process. */ +static int read_sn(struct echoaudio *chip) +{ + int i; + u32 sn[6]; + + for (i = 0; i < 5; i++) { + if (read_dsp(chip, &sn[i])) { + dev_err(chip->card->dev, + "Failed to read serial number\n"); + return -EIO; + } + } + dev_dbg(chip->card->dev, + "Read serial number %08x %08x %08x %08x %08x\n", + sn[0], sn[1], sn[2], sn[3], sn[4]); + return 0; +} + + + +#ifndef ECHOCARD_HAS_ASIC +/* This card has no ASIC, just return ok */ +static inline int check_asic_status(struct echoaudio *chip) +{ + chip->asic_loaded = true; + return 0; +} + +#endif /* !ECHOCARD_HAS_ASIC */ + + + +#ifdef ECHOCARD_HAS_ASIC + +/* Load ASIC code - done after the DSP is loaded */ +static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic) +{ + const struct firmware *fw; + int err; + u32 i, size; + u8 *code; + + err = get_firmware(&fw, chip, asic); + if (err < 0) { + dev_warn(chip->card->dev, "Firmware not found !\n"); + return err; + } + + code = (u8 *)fw->data; + size = fw->size; + + /* Send the "Here comes the ASIC" command */ + if (write_dsp(chip, cmd) < 0) + goto la_error; + + /* Write length of ASIC file in bytes */ + if (write_dsp(chip, size) < 0) + goto la_error; + + for (i = 0; i < size; i++) { + if (write_dsp(chip, code[i]) < 0) + goto la_error; + } + + free_firmware(fw, chip); + return 0; + +la_error: + dev_err(chip->card->dev, "failed on write_dsp\n"); + free_firmware(fw, chip); + return -EIO; +} + +#endif /* ECHOCARD_HAS_ASIC */ + + + +#ifdef DSP_56361 + +/* Install the resident loader for 56361 DSPs; The resident loader is on +the EPROM on the board for 56301 DSP. The resident loader is a tiny little +program that is used to load the real DSP code. */ +static int install_resident_loader(struct echoaudio *chip) +{ + u32 address; + int index, words, i; + u16 *code; + u32 status; + const struct firmware *fw; + + /* 56361 cards only! This check is required by the old 56301-based + Mona and Gina24 */ + if (chip->device_id != DEVICE_ID_56361) + return 0; + + /* Look to see if the resident loader is present. If the resident + loader is already installed, host flag 5 will be on. */ + status = get_dsp_register(chip, CHI32_STATUS_REG); + if (status & CHI32_STATUS_REG_HF5) { + dev_dbg(chip->card->dev, + "Resident loader already installed; status is 0x%x\n", + status); + return 0; + } + + i = get_firmware(&fw, chip, FW_361_LOADER); + if (i < 0) { + dev_warn(chip->card->dev, "Firmware not found !\n"); + return i; + } + + /* The DSP code is an array of 16 bit words. The array is divided up + into sections. The first word of each section is the size in words, + followed by the section type. + Since DSP addresses and data are 24 bits wide, they each take up two + 16 bit words in the array. + This is a lot like the other loader loop, but it's not a loop, you + don't write the memory type, and you don't write a zero at the end. */ + + /* Set DSP format bits for 24 bit mode */ + set_dsp_register(chip, CHI32_CONTROL_REG, + get_dsp_register(chip, CHI32_CONTROL_REG) | 0x900); + + code = (u16 *)fw->data; + + /* Skip the header section; the first word in the array is the size + of the first section, so the first real section of code is pointed + to by Code[0]. */ + index = code[0]; + + /* Skip the section size, LRS block type, and DSP memory type */ + index += 3; + + /* Get the number of DSP words to write */ + words = code[index++]; + + /* Get the DSP address for this block; 24 bits, so build from two words */ + address = ((u32)code[index] << 16) + code[index + 1]; + index += 2; + + /* Write the count to the DSP */ + if (write_dsp(chip, words)) { + dev_err(chip->card->dev, + "install_resident_loader: Failed to write word count!\n"); + goto irl_error; + } + /* Write the DSP address */ + if (write_dsp(chip, address)) { + dev_err(chip->card->dev, + "install_resident_loader: Failed to write DSP address!\n"); + goto irl_error; + } + /* Write out this block of code to the DSP */ + for (i = 0; i < words; i++) { + u32 data; + + data = ((u32)code[index] << 16) + code[index + 1]; + if (write_dsp(chip, data)) { + dev_err(chip->card->dev, + "install_resident_loader: Failed to write DSP code\n"); + goto irl_error; + } + index += 2; + } + + /* Wait for flag 5 to come up */ + for (i = 0; i < 200; i++) { /* Timeout is 50us * 200 = 10ms */ + udelay(50); + status = get_dsp_register(chip, CHI32_STATUS_REG); + if (status & CHI32_STATUS_REG_HF5) + break; + } + + if (i == 200) { + dev_err(chip->card->dev, "Resident loader failed to set HF5\n"); + goto irl_error; + } + + dev_dbg(chip->card->dev, "Resident loader successfully installed\n"); + free_firmware(fw, chip); + return 0; + +irl_error: + free_firmware(fw, chip); + return -EIO; +} + +#endif /* DSP_56361 */ + + +static int load_dsp(struct echoaudio *chip, u16 *code) +{ + u32 address, data; + int index, words, i; + + if (chip->dsp_code == code) { + dev_warn(chip->card->dev, "DSP is already loaded!\n"); + return 0; + } + chip->bad_board = true; /* Set true until DSP loaded */ + chip->dsp_code = NULL; /* Current DSP code not loaded */ + chip->asic_loaded = false; /* Loading the DSP code will reset the ASIC */ + + dev_dbg(chip->card->dev, "load_dsp: Set bad_board to true\n"); + + /* If this board requires a resident loader, install it. */ +#ifdef DSP_56361 + i = install_resident_loader(chip); + if (i < 0) + return i; +#endif + + /* Send software reset command */ + if (send_vector(chip, DSP_VC_RESET) < 0) { + dev_err(chip->card->dev, + "LoadDsp: send_vector DSP_VC_RESET failed, Critical Failure\n"); + return -EIO; + } + /* Delay 10us */ + udelay(10); + + /* Wait 10ms for HF3 to indicate that software reset is complete */ + for (i = 0; i < 1000; i++) { /* Timeout is 10us * 1000 = 10ms */ + if (get_dsp_register(chip, CHI32_STATUS_REG) & + CHI32_STATUS_REG_HF3) + break; + udelay(10); + } + + if (i == 1000) { + dev_err(chip->card->dev, + "load_dsp: Timeout waiting for CHI32_STATUS_REG_HF3\n"); + return -EIO; + } + + /* Set DSP format bits for 24 bit mode now that soft reset is done */ + set_dsp_register(chip, CHI32_CONTROL_REG, + get_dsp_register(chip, CHI32_CONTROL_REG) | 0x900); + + /* Main loader loop */ + + index = code[0]; + for (;;) { + int block_type, mem_type; + + /* Total Block Size */ + index++; + + /* Block Type */ + block_type = code[index]; + if (block_type == 4) /* We're finished */ + break; + + index++; + + /* Memory Type P=0,X=1,Y=2 */ + mem_type = code[index++]; + + /* Block Code Size */ + words = code[index++]; + if (words == 0) /* We're finished */ + break; + + /* Start Address */ + address = ((u32)code[index] << 16) + code[index + 1]; + index += 2; + + if (write_dsp(chip, words) < 0) { + dev_err(chip->card->dev, + "load_dsp: failed to write number of DSP words\n"); + return -EIO; + } + if (write_dsp(chip, address) < 0) { + dev_err(chip->card->dev, + "load_dsp: failed to write DSP address\n"); + return -EIO; + } + if (write_dsp(chip, mem_type) < 0) { + dev_err(chip->card->dev, + "load_dsp: failed to write DSP memory type\n"); + return -EIO; + } + /* Code */ + for (i = 0; i < words; i++, index+=2) { + data = ((u32)code[index] << 16) + code[index + 1]; + if (write_dsp(chip, data) < 0) { + dev_err(chip->card->dev, + "load_dsp: failed to write DSP data\n"); + return -EIO; + } + } + } + + if (write_dsp(chip, 0) < 0) { /* We're done!!! */ + dev_err(chip->card->dev, + "load_dsp: Failed to write final zero\n"); + return -EIO; + } + udelay(10); + + for (i = 0; i < 5000; i++) { /* Timeout is 100us * 5000 = 500ms */ + /* Wait for flag 4 - indicates that the DSP loaded OK */ + if (get_dsp_register(chip, CHI32_STATUS_REG) & + CHI32_STATUS_REG_HF4) { + set_dsp_register(chip, CHI32_CONTROL_REG, + get_dsp_register(chip, CHI32_CONTROL_REG) & ~0x1b00); + + if (write_dsp(chip, DSP_FNC_SET_COMMPAGE_ADDR) < 0) { + dev_err(chip->card->dev, + "load_dsp: Failed to write DSP_FNC_SET_COMMPAGE_ADDR\n"); + return -EIO; + } + + if (write_dsp(chip, chip->comm_page_phys) < 0) { + dev_err(chip->card->dev, + "load_dsp: Failed to write comm page address\n"); + return -EIO; + } + + /* Get the serial number via slave mode. + This is triggered by the SET_COMMPAGE_ADDR command. + We don't actually use the serial number but we have to + get it as part of the DSP init voodoo. */ + if (read_sn(chip) < 0) { + dev_err(chip->card->dev, + "load_dsp: Failed to read serial number\n"); + return -EIO; + } + + chip->dsp_code = code; /* Show which DSP code loaded */ + chip->bad_board = false; /* DSP OK */ + return 0; + } + udelay(100); + } + + dev_err(chip->card->dev, + "load_dsp: DSP load timed out waiting for HF4\n"); + return -EIO; +} + + + +/* load_firmware takes care of loading the DSP and any ASIC code. */ +static int load_firmware(struct echoaudio *chip) +{ + const struct firmware *fw; + int box_type, err; + + if (snd_BUG_ON(!chip->comm_page)) + return -EPERM; + + /* See if the ASIC is present and working - only if the DSP is already loaded */ + if (chip->dsp_code) { + box_type = check_asic_status(chip); + if (box_type >= 0) + return box_type; + /* ASIC check failed; force the DSP to reload */ + chip->dsp_code = NULL; + } + + err = get_firmware(&fw, chip, chip->dsp_code_to_load); + if (err < 0) + return err; + err = load_dsp(chip, (u16 *)fw->data); + free_firmware(fw, chip); + if (err < 0) + return err; + + box_type = load_asic(chip); + if (box_type < 0) + return box_type; /* error */ + + return box_type; +} + + + +/**************************************************************************** + Mixer functions + ****************************************************************************/ + +#if defined(ECHOCARD_HAS_INPUT_NOMINAL_LEVEL) || \ + defined(ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL) + +/* Set the nominal level for an input or output bus (true = -10dBV, false = +4dBu) */ +static int set_nominal_level(struct echoaudio *chip, u16 index, char consumer) +{ + if (snd_BUG_ON(index >= num_busses_out(chip) + num_busses_in(chip))) + return -EINVAL; + + /* Wait for the handshake (OK even if ASIC is not loaded) */ + if (wait_handshake(chip)) + return -EIO; + + chip->nominal_level[index] = consumer; + + if (consumer) + chip->comm_page->nominal_level_mask |= cpu_to_le32(1 << index); + else + chip->comm_page->nominal_level_mask &= ~cpu_to_le32(1 << index); + + return 0; +} + +#endif /* ECHOCARD_HAS_*_NOMINAL_LEVEL */ + + + +/* Set the gain for a single physical output channel (dB). */ +static int set_output_gain(struct echoaudio *chip, u16 channel, s8 gain) +{ + if (snd_BUG_ON(channel >= num_busses_out(chip))) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + /* Save the new value */ + chip->output_gain[channel] = gain; + chip->comm_page->line_out_level[channel] = gain; + return 0; +} + + + +#ifdef ECHOCARD_HAS_MONITOR +/* Set the monitor level from an input bus to an output bus. */ +static int set_monitor_gain(struct echoaudio *chip, u16 output, u16 input, + s8 gain) +{ + if (snd_BUG_ON(output >= num_busses_out(chip) || + input >= num_busses_in(chip))) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + chip->monitor_gain[output][input] = gain; + chip->comm_page->monitors[monitor_index(chip, output, input)] = gain; + return 0; +} +#endif /* ECHOCARD_HAS_MONITOR */ + + +/* Tell the DSP to read and update output, nominal & monitor levels in comm page. */ +static int update_output_line_level(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_OUTVOL); +} + + + +/* Tell the DSP to read and update input levels in comm page */ +static int update_input_line_level(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_INGAIN); +} + + + +/* set_meters_on turns the meters on or off. If meters are turned on, the DSP +will write the meter and clock detect values to the comm page at about 30Hz */ +static void set_meters_on(struct echoaudio *chip, char on) +{ + if (on && !chip->meters_enabled) { + send_vector(chip, DSP_VC_METERS_ON); + chip->meters_enabled = 1; + } else if (!on && chip->meters_enabled) { + send_vector(chip, DSP_VC_METERS_OFF); + chip->meters_enabled = 0; + memset((s8 *)chip->comm_page->vu_meter, ECHOGAIN_MUTED, + DSP_MAXPIPES); + memset((s8 *)chip->comm_page->peak_meter, ECHOGAIN_MUTED, + DSP_MAXPIPES); + } +} + + + +/* Fill out an the given array using the current values in the comm page. +Meters are written in the comm page by the DSP in this order: + Output busses + Input busses + Output pipes (vmixer cards only) + +This function assumes there are no more than 16 in/out busses or pipes +Meters is an array [3][16][2] of long. */ +static void get_audio_meters(struct echoaudio *chip, long *meters) +{ + unsigned int i, m, n; + + for (i = 0 ; i < 96; i++) + meters[i] = 0; + + for (m = 0, n = 0, i = 0; i < num_busses_out(chip); i++, m++) { + meters[n++] = chip->comm_page->vu_meter[m]; + meters[n++] = chip->comm_page->peak_meter[m]; + } + +#ifdef ECHOCARD_ECHO3G + m = E3G_MAX_OUTPUTS; /* Skip unused meters */ +#endif + + for (n = 32, i = 0; i < num_busses_in(chip); i++, m++) { + meters[n++] = chip->comm_page->vu_meter[m]; + meters[n++] = chip->comm_page->peak_meter[m]; + } +#ifdef ECHOCARD_HAS_VMIXER + for (n = 64, i = 0; i < num_pipes_out(chip); i++, m++) { + meters[n++] = chip->comm_page->vu_meter[m]; + meters[n++] = chip->comm_page->peak_meter[m]; + } +#endif +} + + + +static int restore_dsp_settings(struct echoaudio *chip) +{ + int i, o, err; + + err = check_asic_status(chip); + if (err < 0) + return err; + + /* Gina20/Darla20 only. Should be harmless for other cards. */ + chip->comm_page->gd_clock_state = GD_CLOCK_UNDEF; + chip->comm_page->gd_spdif_status = GD_SPDIF_STATUS_UNDEF; + chip->comm_page->handshake = cpu_to_le32(0xffffffff); + + /* Restore output busses */ + for (i = 0; i < num_busses_out(chip); i++) { + err = set_output_gain(chip, i, chip->output_gain[i]); + if (err < 0) + return err; + } + +#ifdef ECHOCARD_HAS_VMIXER + for (i = 0; i < num_pipes_out(chip); i++) + for (o = 0; o < num_busses_out(chip); o++) { + err = set_vmixer_gain(chip, o, i, + chip->vmixer_gain[o][i]); + if (err < 0) + return err; + } + if (update_vmixer_level(chip) < 0) + return -EIO; +#endif /* ECHOCARD_HAS_VMIXER */ + +#ifdef ECHOCARD_HAS_MONITOR + for (o = 0; o < num_busses_out(chip); o++) + for (i = 0; i < num_busses_in(chip); i++) { + err = set_monitor_gain(chip, o, i, + chip->monitor_gain[o][i]); + if (err < 0) + return err; + } +#endif /* ECHOCARD_HAS_MONITOR */ + +#ifdef ECHOCARD_HAS_INPUT_GAIN + for (i = 0; i < num_busses_in(chip); i++) { + err = set_input_gain(chip, i, chip->input_gain[i]); + if (err < 0) + return err; + } +#endif /* ECHOCARD_HAS_INPUT_GAIN */ + + err = update_output_line_level(chip); + if (err < 0) + return err; + + err = update_input_line_level(chip); + if (err < 0) + return err; + + err = set_sample_rate(chip, chip->sample_rate); + if (err < 0) + return err; + + if (chip->meters_enabled) { + err = send_vector(chip, DSP_VC_METERS_ON); + if (err < 0) + return err; + } + +#ifdef ECHOCARD_HAS_DIGITAL_MODE_SWITCH + if (set_digital_mode(chip, chip->digital_mode) < 0) + return -EIO; +#endif + +#ifdef ECHOCARD_HAS_DIGITAL_IO + if (set_professional_spdif(chip, chip->professional_spdif) < 0) + return -EIO; +#endif + +#ifdef ECHOCARD_HAS_PHANTOM_POWER + if (set_phantom_power(chip, chip->phantom_power) < 0) + return -EIO; +#endif + +#ifdef ECHOCARD_HAS_EXTERNAL_CLOCK + /* set_input_clock() also restores automute setting */ + if (set_input_clock(chip, chip->input_clock) < 0) + return -EIO; +#endif + +#ifdef ECHOCARD_HAS_OUTPUT_CLOCK_SWITCH + if (set_output_clock(chip, chip->output_clock) < 0) + return -EIO; +#endif + + if (wait_handshake(chip) < 0) + return -EIO; + clear_handshake(chip); + if (send_vector(chip, DSP_VC_UPDATE_FLAGS) < 0) + return -EIO; + + return 0; +} + + + +/**************************************************************************** + Transport functions + ****************************************************************************/ + +/* set_audio_format() sets the format of the audio data in host memory for +this pipe. Note that _MS_ (mono-to-stereo) playback modes are not used by ALSA +but they are here because they are just mono while capturing */ +static void set_audio_format(struct echoaudio *chip, u16 pipe_index, + const struct audioformat *format) +{ + u16 dsp_format; + + dsp_format = DSP_AUDIOFORM_SS_16LE; + + /* Look for super-interleave (no big-endian and 8 bits) */ + if (format->interleave > 2) { + switch (format->bits_per_sample) { + case 16: + dsp_format = DSP_AUDIOFORM_SUPER_INTERLEAVE_16LE; + break; + case 24: + dsp_format = DSP_AUDIOFORM_SUPER_INTERLEAVE_24LE; + break; + case 32: + dsp_format = DSP_AUDIOFORM_SUPER_INTERLEAVE_32LE; + break; + } + dsp_format |= format->interleave; + } else if (format->data_are_bigendian) { + /* For big-endian data, only 32 bit samples are supported */ + switch (format->interleave) { + case 1: + dsp_format = DSP_AUDIOFORM_MM_32BE; + break; +#ifdef ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + case 2: + dsp_format = DSP_AUDIOFORM_SS_32BE; + break; +#endif + } + } else if (format->interleave == 1 && + format->bits_per_sample == 32 && !format->mono_to_stereo) { + /* 32 bit little-endian mono->mono case */ + dsp_format = DSP_AUDIOFORM_MM_32LE; + } else { + /* Handle the other little-endian formats */ + switch (format->bits_per_sample) { + case 8: + if (format->interleave == 2) + dsp_format = DSP_AUDIOFORM_SS_8; + else + dsp_format = DSP_AUDIOFORM_MS_8; + break; + default: + case 16: + if (format->interleave == 2) + dsp_format = DSP_AUDIOFORM_SS_16LE; + else + dsp_format = DSP_AUDIOFORM_MS_16LE; + break; + case 24: + if (format->interleave == 2) + dsp_format = DSP_AUDIOFORM_SS_24LE; + else + dsp_format = DSP_AUDIOFORM_MS_24LE; + break; + case 32: + if (format->interleave == 2) + dsp_format = DSP_AUDIOFORM_SS_32LE; + else + dsp_format = DSP_AUDIOFORM_MS_32LE; + break; + } + } + dev_dbg(chip->card->dev, + "set_audio_format[%d] = %x\n", pipe_index, dsp_format); + chip->comm_page->audio_format[pipe_index] = cpu_to_le16(dsp_format); +} + + + +/* start_transport starts transport for a set of pipes. +The bits 1 in channel_mask specify what pipes to start. Only the bit of the +first channel must be set, regardless its interleave. +Same thing for pause_ and stop_ -trasport below. */ +static int start_transport(struct echoaudio *chip, u32 channel_mask, + u32 cyclic_mask) +{ + + if (wait_handshake(chip)) + return -EIO; + + chip->comm_page->cmd_start |= cpu_to_le32(channel_mask); + + if (chip->comm_page->cmd_start) { + clear_handshake(chip); + send_vector(chip, DSP_VC_START_TRANSFER); + if (wait_handshake(chip)) + return -EIO; + /* Keep track of which pipes are transporting */ + chip->active_mask |= channel_mask; + chip->comm_page->cmd_start = 0; + return 0; + } + + dev_err(chip->card->dev, "start_transport: No pipes to start!\n"); + return -EINVAL; +} + + + +static int pause_transport(struct echoaudio *chip, u32 channel_mask) +{ + + if (wait_handshake(chip)) + return -EIO; + + chip->comm_page->cmd_stop |= cpu_to_le32(channel_mask); + chip->comm_page->cmd_reset = 0; + if (chip->comm_page->cmd_stop) { + clear_handshake(chip); + send_vector(chip, DSP_VC_STOP_TRANSFER); + if (wait_handshake(chip)) + return -EIO; + /* Keep track of which pipes are transporting */ + chip->active_mask &= ~channel_mask; + chip->comm_page->cmd_stop = 0; + chip->comm_page->cmd_reset = 0; + return 0; + } + + dev_dbg(chip->card->dev, "pause_transport: No pipes to stop!\n"); + return 0; +} + + + +static int stop_transport(struct echoaudio *chip, u32 channel_mask) +{ + + if (wait_handshake(chip)) + return -EIO; + + chip->comm_page->cmd_stop |= cpu_to_le32(channel_mask); + chip->comm_page->cmd_reset |= cpu_to_le32(channel_mask); + if (chip->comm_page->cmd_reset) { + clear_handshake(chip); + send_vector(chip, DSP_VC_STOP_TRANSFER); + if (wait_handshake(chip)) + return -EIO; + /* Keep track of which pipes are transporting */ + chip->active_mask &= ~channel_mask; + chip->comm_page->cmd_stop = 0; + chip->comm_page->cmd_reset = 0; + return 0; + } + + dev_dbg(chip->card->dev, "stop_transport: No pipes to stop!\n"); + return 0; +} + + + +static inline int is_pipe_allocated(struct echoaudio *chip, u16 pipe_index) +{ + return (chip->pipe_alloc_mask & (1 << pipe_index)); +} + + + +/* Stops everything and turns off the DSP. All pipes should be already +stopped and unallocated. */ +static int rest_in_peace(struct echoaudio *chip) +{ + + /* Stops all active pipes (just to be sure) */ + stop_transport(chip, chip->active_mask); + + set_meters_on(chip, false); + +#ifdef ECHOCARD_HAS_MIDI + enable_midi_input(chip, false); +#endif + + /* Go to sleep */ + if (chip->dsp_code) { + /* Make load_firmware do a complete reload */ + chip->dsp_code = NULL; + /* Put the DSP to sleep */ + return send_vector(chip, DSP_VC_GO_COMATOSE); + } + return 0; +} + + + +/* Fills the comm page with default values */ +static int init_dsp_comm_page(struct echoaudio *chip) +{ + /* Check if the compiler added extra padding inside the structure */ + if (offsetof(struct comm_page, midi_output) != 0xbe0) { + dev_err(chip->card->dev, + "init_dsp_comm_page() - Invalid struct comm_page structure\n"); + return -EPERM; + } + + /* Init all the basic stuff */ + chip->card_name = ECHOCARD_NAME; + chip->bad_board = true; /* Set true until DSP loaded */ + chip->dsp_code = NULL; /* Current DSP code not loaded */ + chip->asic_loaded = false; + memset(chip->comm_page, 0, sizeof(struct comm_page)); + + /* Init the comm page */ + chip->comm_page->comm_size = + cpu_to_le32(sizeof(struct comm_page)); + chip->comm_page->handshake = cpu_to_le32(0xffffffff); + chip->comm_page->midi_out_free_count = + cpu_to_le32(DSP_MIDI_OUT_FIFO_SIZE); + chip->comm_page->sample_rate = cpu_to_le32(44100); + + /* Set line levels so we don't blast any inputs on startup */ + memset(chip->comm_page->monitors, ECHOGAIN_MUTED, MONITOR_ARRAY_SIZE); + memset(chip->comm_page->vmixer, ECHOGAIN_MUTED, VMIXER_ARRAY_SIZE); + + return 0; +} + + + +/* This function initializes the chip structure with default values, ie. all + * muted and internal clock source. Then it copies the settings to the DSP. + * This MUST be called after the DSP is up and running ! + */ +static int init_line_levels(struct echoaudio *chip) +{ + memset(chip->output_gain, ECHOGAIN_MUTED, sizeof(chip->output_gain)); + memset(chip->input_gain, ECHOGAIN_MUTED, sizeof(chip->input_gain)); + memset(chip->monitor_gain, ECHOGAIN_MUTED, sizeof(chip->monitor_gain)); + memset(chip->vmixer_gain, ECHOGAIN_MUTED, sizeof(chip->vmixer_gain)); + chip->input_clock = ECHO_CLOCK_INTERNAL; + chip->output_clock = ECHO_CLOCK_WORD; + chip->sample_rate = 44100; + return restore_dsp_settings(chip); +} + + + +/* This is low level part of the interrupt handler. +It returns -1 if the IRQ is not ours, or N>=0 if it is, where N is the number +of midi data in the input queue. */ +static int service_irq(struct echoaudio *chip) +{ + int st; + + /* Read the DSP status register and see if this DSP generated this interrupt */ + if (get_dsp_register(chip, CHI32_STATUS_REG) & CHI32_STATUS_IRQ) { + st = 0; +#ifdef ECHOCARD_HAS_MIDI + /* Get and parse midi data if present */ + if (chip->comm_page->midi_input[0]) /* The count is at index 0 */ + st = midi_service_irq(chip); /* Returns how many midi bytes we received */ +#endif + /* Clear the hardware interrupt */ + chip->comm_page->midi_input[0] = 0; + send_vector(chip, DSP_VC_ACK_INT); + return st; + } + return -1; +} + + + + +/****************************************************************************** + Functions for opening and closing pipes + ******************************************************************************/ + +/* allocate_pipes is used to reserve audio pipes for your exclusive use. +The call will fail if some pipes are already allocated. */ +static int allocate_pipes(struct echoaudio *chip, struct audiopipe *pipe, + int pipe_index, int interleave) +{ + int i; + u32 channel_mask; + + dev_dbg(chip->card->dev, + "allocate_pipes: ch=%d int=%d\n", pipe_index, interleave); + + if (chip->bad_board) + return -EIO; + + for (channel_mask = i = 0; i < interleave; i++) + channel_mask |= 1 << (pipe_index + i); + if (chip->pipe_alloc_mask & channel_mask) { + dev_err(chip->card->dev, + "allocate_pipes: channel already open\n"); + return -EAGAIN; + } + + chip->comm_page->position[pipe_index] = 0; + chip->pipe_alloc_mask |= channel_mask; + /* This driver uses cyclic buffers only */ + chip->pipe_cyclic_mask |= channel_mask; + pipe->index = pipe_index; + pipe->interleave = interleave; + pipe->state = PIPE_STATE_STOPPED; + + /* The counter register is where the DSP writes the 32 bit DMA + position for a pipe. The DSP is constantly updating this value as + it moves data. The DMA counter is in units of bytes, not samples. */ + pipe->dma_counter = (__le32 *)&chip->comm_page->position[pipe_index]; + *pipe->dma_counter = 0; + return pipe_index; +} + + + +static int free_pipes(struct echoaudio *chip, struct audiopipe *pipe) +{ + u32 channel_mask; + int i; + + if (snd_BUG_ON(!is_pipe_allocated(chip, pipe->index))) + return -EINVAL; + if (snd_BUG_ON(pipe->state != PIPE_STATE_STOPPED)) + return -EINVAL; + + for (channel_mask = i = 0; i < pipe->interleave; i++) + channel_mask |= 1 << (pipe->index + i); + + chip->pipe_alloc_mask &= ~channel_mask; + chip->pipe_cyclic_mask &= ~channel_mask; + return 0; +} + + + +/****************************************************************************** + Functions for managing the scatter-gather list +******************************************************************************/ + +static int sglist_init(struct echoaudio *chip, struct audiopipe *pipe) +{ + pipe->sglist_head = 0; + memset(pipe->sgpage.area, 0, PAGE_SIZE); + chip->comm_page->sglist_addr[pipe->index].addr = + cpu_to_le32(pipe->sgpage.addr); + return 0; +} + + + +static int sglist_add_mapping(struct echoaudio *chip, struct audiopipe *pipe, + dma_addr_t address, size_t length) +{ + int head = pipe->sglist_head; + struct sg_entry *list = (struct sg_entry *)pipe->sgpage.area; + + if (head < MAX_SGLIST_ENTRIES - 1) { + list[head].addr = cpu_to_le32(address); + list[head].size = cpu_to_le32(length); + pipe->sglist_head++; + } else { + dev_err(chip->card->dev, "SGlist: too many fragments\n"); + return -ENOMEM; + } + return 0; +} + + + +static inline int sglist_add_irq(struct echoaudio *chip, struct audiopipe *pipe) +{ + return sglist_add_mapping(chip, pipe, 0, 0); +} + + + +static inline int sglist_wrap(struct echoaudio *chip, struct audiopipe *pipe) +{ + return sglist_add_mapping(chip, pipe, pipe->sgpage.addr, 0); +} diff --git a/sound/pci/echoaudio/echoaudio_dsp.h b/sound/pci/echoaudio/echoaudio_dsp.h new file mode 100644 index 000000000..4f73bc037 --- /dev/null +++ b/sound/pci/echoaudio/echoaudio_dsp.h @@ -0,0 +1,683 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + +#ifndef _ECHO_DSP_ +#define _ECHO_DSP_ + + +/**** Echogals: Darla20, Gina20, Layla20, and Darla24 ****/ +#if defined(ECHOGALS_FAMILY) + +#define NUM_ASIC_TESTS 5 +#define READ_DSP_TIMEOUT 1000000L /* one second */ + +/**** Echo24: Gina24, Layla24, Mona, Mia, Mia-midi ****/ +#elif defined(ECHO24_FAMILY) + +#define DSP_56361 /* Some Echo24 cards use the 56361 DSP */ +#define READ_DSP_TIMEOUT 100000L /* .1 second */ + +/**** 3G: Gina3G, Layla3G ****/ +#elif defined(ECHO3G_FAMILY) + +#define DSP_56361 +#define READ_DSP_TIMEOUT 100000L /* .1 second */ +#define MIN_MTC_1X_RATE 32000 + +/**** Indigo: Indigo, Indigo IO, Indigo DJ ****/ +#elif defined(INDIGO_FAMILY) + +#define DSP_56361 +#define READ_DSP_TIMEOUT 100000L /* .1 second */ + +#else + +#error No family is defined + +#endif + + + +/* + * + * Max inputs and outputs + * + */ + +#define DSP_MAXAUDIOINPUTS 16 /* Max audio input channels */ +#define DSP_MAXAUDIOOUTPUTS 16 /* Max audio output channels */ +#define DSP_MAXPIPES 32 /* Max total pipes (input + output) */ + + +/* + * + * These are the offsets for the memory-mapped DSP registers; the DSP base + * address is treated as the start of a u32 array. + */ + +#define CHI32_CONTROL_REG 4 +#define CHI32_STATUS_REG 5 +#define CHI32_VECTOR_REG 6 +#define CHI32_DATA_REG 7 + + +/* + * + * Interesting bits within the DSP registers + * + */ + +#define CHI32_VECTOR_BUSY 0x00000001 +#define CHI32_STATUS_REG_HF3 0x00000008 +#define CHI32_STATUS_REG_HF4 0x00000010 +#define CHI32_STATUS_REG_HF5 0x00000020 +#define CHI32_STATUS_HOST_READ_FULL 0x00000004 +#define CHI32_STATUS_HOST_WRITE_EMPTY 0x00000002 +#define CHI32_STATUS_IRQ 0x00000040 + + +/* + * + * DSP commands sent via slave mode; these are sent to the DSP by write_dsp() + * + */ + +#define DSP_FNC_SET_COMMPAGE_ADDR 0x02 +#define DSP_FNC_LOAD_LAYLA_ASIC 0xa0 +#define DSP_FNC_LOAD_GINA24_ASIC 0xa0 +#define DSP_FNC_LOAD_MONA_PCI_CARD_ASIC 0xa0 +#define DSP_FNC_LOAD_LAYLA24_PCI_CARD_ASIC 0xa0 +#define DSP_FNC_LOAD_MONA_EXTERNAL_ASIC 0xa1 +#define DSP_FNC_LOAD_LAYLA24_EXTERNAL_ASIC 0xa1 +#define DSP_FNC_LOAD_3G_ASIC 0xa0 + + +/* + * + * Defines to handle the MIDI input state engine; these are used to properly + * extract MIDI time code bytes and their timestamps from the MIDI input stream. + * + */ + +#define MIDI_IN_STATE_NORMAL 0 +#define MIDI_IN_STATE_TS_HIGH 1 +#define MIDI_IN_STATE_TS_LOW 2 +#define MIDI_IN_STATE_F1_DATA 3 +#define MIDI_IN_SKIP_DATA (-1) + + +/*---------------------------------------------------------------------------- + +Setting the sample rates on Layla24 is somewhat schizophrenic. + +For standard rates, it works exactly like Mona and Gina24. That is, for +8, 11.025, 16, 22.05, 32, 44.1, 48, 88.2, and 96 kHz, you just set the +appropriate bits in the control register and write the control register. + +In order to support MIDI time code sync (and possibly SMPTE LTC sync in +the future), Layla24 also has "continuous sample rate mode". In this mode, +Layla24 can generate any sample rate between 25 and 50 kHz inclusive, or +50 to 100 kHz inclusive for double speed mode. + +To use continuous mode: + +-Set the clock select bits in the control register to 0xe (see the #define + below) + +-Set double-speed mode if you want to use sample rates above 50 kHz + +-Write the control register as you would normally + +-Now, you need to set the frequency register. First, you need to determine the + value for the frequency register. This is given by the following formula: + +frequency_reg = (LAYLA24_MAGIC_NUMBER / sample_rate) - 2 + +Note the #define below for the magic number + +-Wait for the DSP handshake +-Write the frequency_reg value to the .SampleRate field of the comm page +-Send the vector command SET_LAYLA24_FREQUENCY_REG (see vmonkey.h) + +Once you have set the control register up for continuous mode, you can just +write the frequency register to change the sample rate. This could be +used for MIDI time code sync. For MTC sync, the control register is set for +continuous mode. The driver then just keeps writing the +SET_LAYLA24_FREQUENCY_REG command. + +-----------------------------------------------------------------------------*/ + +#define LAYLA24_MAGIC_NUMBER 677376000 +#define LAYLA24_CONTINUOUS_CLOCK 0x000e + + +/* + * + * DSP vector commands + * + */ + +#define DSP_VC_RESET 0x80ff + +#ifndef DSP_56361 + +#define DSP_VC_ACK_INT 0x8073 +#define DSP_VC_SET_VMIXER_GAIN 0x0000 /* Not used, only for compile */ +#define DSP_VC_START_TRANSFER 0x0075 /* Handshke rqd. */ +#define DSP_VC_METERS_ON 0x0079 +#define DSP_VC_METERS_OFF 0x007b +#define DSP_VC_UPDATE_OUTVOL 0x007d /* Handshke rqd. */ +#define DSP_VC_UPDATE_INGAIN 0x007f /* Handshke rqd. */ +#define DSP_VC_ADD_AUDIO_BUFFER 0x0081 /* Handshke rqd. */ +#define DSP_VC_TEST_ASIC 0x00eb +#define DSP_VC_UPDATE_CLOCKS 0x00ef /* Handshke rqd. */ +#define DSP_VC_SET_LAYLA_SAMPLE_RATE 0x00f1 /* Handshke rqd. */ +#define DSP_VC_SET_GD_AUDIO_STATE 0x00f1 /* Handshke rqd. */ +#define DSP_VC_WRITE_CONTROL_REG 0x00f1 /* Handshke rqd. */ +#define DSP_VC_MIDI_WRITE 0x00f5 /* Handshke rqd. */ +#define DSP_VC_STOP_TRANSFER 0x00f7 /* Handshke rqd. */ +#define DSP_VC_UPDATE_FLAGS 0x00fd /* Handshke rqd. */ +#define DSP_VC_GO_COMATOSE 0x00f9 + +#else /* !DSP_56361 */ + +/* Vector commands for families that use either the 56301 or 56361 */ +#define DSP_VC_ACK_INT 0x80F5 +#define DSP_VC_SET_VMIXER_GAIN 0x00DB /* Handshke rqd. */ +#define DSP_VC_START_TRANSFER 0x00DD /* Handshke rqd. */ +#define DSP_VC_METERS_ON 0x00EF +#define DSP_VC_METERS_OFF 0x00F1 +#define DSP_VC_UPDATE_OUTVOL 0x00E3 /* Handshke rqd. */ +#define DSP_VC_UPDATE_INGAIN 0x00E5 /* Handshke rqd. */ +#define DSP_VC_ADD_AUDIO_BUFFER 0x00E1 /* Handshke rqd. */ +#define DSP_VC_TEST_ASIC 0x00ED +#define DSP_VC_UPDATE_CLOCKS 0x00E9 /* Handshke rqd. */ +#define DSP_VC_SET_LAYLA24_FREQUENCY_REG 0x00E9 /* Handshke rqd. */ +#define DSP_VC_SET_LAYLA_SAMPLE_RATE 0x00EB /* Handshke rqd. */ +#define DSP_VC_SET_GD_AUDIO_STATE 0x00EB /* Handshke rqd. */ +#define DSP_VC_WRITE_CONTROL_REG 0x00EB /* Handshke rqd. */ +#define DSP_VC_MIDI_WRITE 0x00E7 /* Handshke rqd. */ +#define DSP_VC_STOP_TRANSFER 0x00DF /* Handshke rqd. */ +#define DSP_VC_UPDATE_FLAGS 0x00FB /* Handshke rqd. */ +#define DSP_VC_GO_COMATOSE 0x00d9 + +#endif /* !DSP_56361 */ + + +/* + * + * Timeouts + * + */ + +#define HANDSHAKE_TIMEOUT 20000 /* send_vector command timeout (20ms) */ +#define VECTOR_BUSY_TIMEOUT 100000 /* 100ms */ +#define MIDI_OUT_DELAY_USEC 2000 /* How long to wait after MIDI fills up */ + + +/* + * + * Flags for .Flags field in the comm page + * + */ + +#define DSP_FLAG_MIDI_INPUT 0x0001 /* Enable MIDI input */ +#define DSP_FLAG_SPDIF_NONAUDIO 0x0002 /* Sets the "non-audio" bit + * in the S/PDIF out status + * bits. Clear this flag for + * audio data; + * set it for AC3 or WMA or + * some such */ +#define DSP_FLAG_PROFESSIONAL_SPDIF 0x0008 /* 1 Professional, 0 Consumer */ + + +/* + * + * Clock detect bits reported by the DSP for Gina20, Layla20, Darla24, and Mia + * + */ + +#define GLDM_CLOCK_DETECT_BIT_WORD 0x0002 +#define GLDM_CLOCK_DETECT_BIT_SUPER 0x0004 +#define GLDM_CLOCK_DETECT_BIT_SPDIF 0x0008 +#define GLDM_CLOCK_DETECT_BIT_ESYNC 0x0010 + + +/* + * + * Clock detect bits reported by the DSP for Gina24, Mona, and Layla24 + * + */ + +#define GML_CLOCK_DETECT_BIT_WORD96 0x0002 +#define GML_CLOCK_DETECT_BIT_WORD48 0x0004 +#define GML_CLOCK_DETECT_BIT_SPDIF48 0x0008 +#define GML_CLOCK_DETECT_BIT_SPDIF96 0x0010 +#define GML_CLOCK_DETECT_BIT_WORD (GML_CLOCK_DETECT_BIT_WORD96 | GML_CLOCK_DETECT_BIT_WORD48) +#define GML_CLOCK_DETECT_BIT_SPDIF (GML_CLOCK_DETECT_BIT_SPDIF48 | GML_CLOCK_DETECT_BIT_SPDIF96) +#define GML_CLOCK_DETECT_BIT_ESYNC 0x0020 +#define GML_CLOCK_DETECT_BIT_ADAT 0x0040 + + +/* + * + * Layla clock numbers to send to DSP + * + */ + +#define LAYLA20_CLOCK_INTERNAL 0 +#define LAYLA20_CLOCK_SPDIF 1 +#define LAYLA20_CLOCK_WORD 2 +#define LAYLA20_CLOCK_SUPER 3 + + +/* + * + * Gina/Darla clock states + * + */ + +#define GD_CLOCK_NOCHANGE 0 +#define GD_CLOCK_44 1 +#define GD_CLOCK_48 2 +#define GD_CLOCK_SPDIFIN 3 +#define GD_CLOCK_UNDEF 0xff + + +/* + * + * Gina/Darla S/PDIF status bits + * + */ + +#define GD_SPDIF_STATUS_NOCHANGE 0 +#define GD_SPDIF_STATUS_44 1 +#define GD_SPDIF_STATUS_48 2 +#define GD_SPDIF_STATUS_UNDEF 0xff + + +/* + * + * Layla20 output clocks + * + */ + +#define LAYLA20_OUTPUT_CLOCK_SUPER 0 +#define LAYLA20_OUTPUT_CLOCK_WORD 1 + + +/**************************************************************************** + + Magic constants for the Darla24 hardware + + ****************************************************************************/ + +#define GD24_96000 0x0 +#define GD24_48000 0x1 +#define GD24_44100 0x2 +#define GD24_32000 0x3 +#define GD24_22050 0x4 +#define GD24_16000 0x5 +#define GD24_11025 0x6 +#define GD24_8000 0x7 +#define GD24_88200 0x8 +#define GD24_EXT_SYNC 0x9 + + +/* + * + * Return values from the DSP when ASIC is loaded + * + */ + +#define ASIC_ALREADY_LOADED 0x1 +#define ASIC_NOT_LOADED 0x0 + + +/* + * + * DSP Audio formats + * + * These are the audio formats that the DSP can transfer + * via input and output pipes. LE means little-endian, + * BE means big-endian. + * + * DSP_AUDIOFORM_MS_8 + * + * 8-bit mono unsigned samples. For playback, + * mono data is duplicated out the left and right channels + * of the output bus. The "MS" part of the name + * means mono->stereo. + * + * DSP_AUDIOFORM_MS_16LE + * + * 16-bit signed little-endian mono samples. Playback works + * like the previous code. + * + * DSP_AUDIOFORM_MS_24LE + * + * 24-bit signed little-endian mono samples. Data is packed + * three bytes per sample; if you had two samples 0x112233 and 0x445566 + * they would be stored in memory like this: 33 22 11 66 55 44. + * + * DSP_AUDIOFORM_MS_32LE + * + * 24-bit signed little-endian mono samples in a 32-bit + * container. In other words, each sample is a 32-bit signed + * integer, where the actual audio data is left-justified + * in the 32 bits and only the 24 most significant bits are valid. + * + * DSP_AUDIOFORM_SS_8 + * DSP_AUDIOFORM_SS_16LE + * DSP_AUDIOFORM_SS_24LE + * DSP_AUDIOFORM_SS_32LE + * + * Like the previous ones, except now with stereo interleaved + * data. "SS" means stereo->stereo. + * + * DSP_AUDIOFORM_MM_32LE + * + * Similar to DSP_AUDIOFORM_MS_32LE, except that the mono + * data is not duplicated out both the left and right outputs. + * This mode is used by the ASIO driver. Here, "MM" means + * mono->mono. + * + * DSP_AUDIOFORM_MM_32BE + * + * Just like DSP_AUDIOFORM_MM_32LE, but now the data is + * in big-endian format. + * + */ + +#define DSP_AUDIOFORM_MS_8 0 /* 8 bit mono */ +#define DSP_AUDIOFORM_MS_16LE 1 /* 16 bit mono */ +#define DSP_AUDIOFORM_MS_24LE 2 /* 24 bit mono */ +#define DSP_AUDIOFORM_MS_32LE 3 /* 32 bit mono */ +#define DSP_AUDIOFORM_SS_8 4 /* 8 bit stereo */ +#define DSP_AUDIOFORM_SS_16LE 5 /* 16 bit stereo */ +#define DSP_AUDIOFORM_SS_24LE 6 /* 24 bit stereo */ +#define DSP_AUDIOFORM_SS_32LE 7 /* 32 bit stereo */ +#define DSP_AUDIOFORM_MM_32LE 8 /* 32 bit mono->mono little-endian */ +#define DSP_AUDIOFORM_MM_32BE 9 /* 32 bit mono->mono big-endian */ +#define DSP_AUDIOFORM_SS_32BE 10 /* 32 bit stereo big endian */ +#define DSP_AUDIOFORM_INVALID 0xFF /* Invalid audio format */ + + +/* + * + * Super-interleave is defined as interleaving by 4 or more. Darla20 and Gina20 + * do not support super interleave. + * + * 16 bit, 24 bit, and 32 bit little endian samples are supported for super + * interleave. The interleave factor must be even. 16 - way interleave is the + * current maximum, so you can interleave by 4, 6, 8, 10, 12, 14, and 16. + * + * The actual format code is derived by taking the define below and or-ing with + * the interleave factor. So, 32 bit interleave by 6 is 0x86 and + * 16 bit interleave by 16 is (0x40 | 0x10) = 0x50. + * + */ + +#define DSP_AUDIOFORM_SUPER_INTERLEAVE_16LE 0x40 +#define DSP_AUDIOFORM_SUPER_INTERLEAVE_24LE 0xc0 +#define DSP_AUDIOFORM_SUPER_INTERLEAVE_32LE 0x80 + + +/* + * + * Gina24, Mona, and Layla24 control register defines + * + */ + +#define GML_CONVERTER_ENABLE 0x0010 +#define GML_SPDIF_PRO_MODE 0x0020 /* Professional S/PDIF == 1, + consumer == 0 */ +#define GML_SPDIF_SAMPLE_RATE0 0x0040 +#define GML_SPDIF_SAMPLE_RATE1 0x0080 +#define GML_SPDIF_TWO_CHANNEL 0x0100 /* 1 == two channels, + 0 == one channel */ +#define GML_SPDIF_NOT_AUDIO 0x0200 +#define GML_SPDIF_COPY_PERMIT 0x0400 +#define GML_SPDIF_24_BIT 0x0800 /* 1 == 24 bit, 0 == 20 bit */ +#define GML_ADAT_MODE 0x1000 /* 1 == ADAT mode, 0 == S/PDIF mode */ +#define GML_SPDIF_OPTICAL_MODE 0x2000 /* 1 == optical mode, 0 == RCA mode */ +#define GML_SPDIF_CDROM_MODE 0x3000 /* 1 == CDROM mode, + * 0 == RCA or optical mode */ +#define GML_DOUBLE_SPEED_MODE 0x4000 /* 1 == double speed, + 0 == single speed */ + +#define GML_DIGITAL_IN_AUTO_MUTE 0x800000 + +#define GML_96KHZ (0x0 | GML_DOUBLE_SPEED_MODE) +#define GML_88KHZ (0x1 | GML_DOUBLE_SPEED_MODE) +#define GML_48KHZ 0x2 +#define GML_44KHZ 0x3 +#define GML_32KHZ 0x4 +#define GML_22KHZ 0x5 +#define GML_16KHZ 0x6 +#define GML_11KHZ 0x7 +#define GML_8KHZ 0x8 +#define GML_SPDIF_CLOCK 0x9 +#define GML_ADAT_CLOCK 0xA +#define GML_WORD_CLOCK 0xB +#define GML_ESYNC_CLOCK 0xC +#define GML_ESYNCx2_CLOCK 0xD + +#define GML_CLOCK_CLEAR_MASK 0xffffbff0 +#define GML_SPDIF_RATE_CLEAR_MASK (~(GML_SPDIF_SAMPLE_RATE0|GML_SPDIF_SAMPLE_RATE1)) +#define GML_DIGITAL_MODE_CLEAR_MASK 0xffffcfff +#define GML_SPDIF_FORMAT_CLEAR_MASK 0xfffff01f + + +/* + * + * Mia sample rate and clock setting constants + * + */ + +#define MIA_32000 0x0040 +#define MIA_44100 0x0042 +#define MIA_48000 0x0041 +#define MIA_88200 0x0142 +#define MIA_96000 0x0141 + +#define MIA_SPDIF 0x00000044 +#define MIA_SPDIF96 0x00000144 + +#define MIA_MIDI_REV 1 /* Must be Mia rev 1 for MIDI support */ + + +/* + * + * 3G register bits + * + */ + +#define E3G_CONVERTER_ENABLE 0x0010 +#define E3G_SPDIF_PRO_MODE 0x0020 /* Professional S/PDIF == 1, + consumer == 0 */ +#define E3G_SPDIF_SAMPLE_RATE0 0x0040 +#define E3G_SPDIF_SAMPLE_RATE1 0x0080 +#define E3G_SPDIF_TWO_CHANNEL 0x0100 /* 1 == two channels, + 0 == one channel */ +#define E3G_SPDIF_NOT_AUDIO 0x0200 +#define E3G_SPDIF_COPY_PERMIT 0x0400 +#define E3G_SPDIF_24_BIT 0x0800 /* 1 == 24 bit, 0 == 20 bit */ +#define E3G_DOUBLE_SPEED_MODE 0x4000 /* 1 == double speed, + 0 == single speed */ +#define E3G_PHANTOM_POWER 0x8000 /* 1 == phantom power on, + 0 == phantom power off */ + +#define E3G_96KHZ (0x0 | E3G_DOUBLE_SPEED_MODE) +#define E3G_88KHZ (0x1 | E3G_DOUBLE_SPEED_MODE) +#define E3G_48KHZ 0x2 +#define E3G_44KHZ 0x3 +#define E3G_32KHZ 0x4 +#define E3G_22KHZ 0x5 +#define E3G_16KHZ 0x6 +#define E3G_11KHZ 0x7 +#define E3G_8KHZ 0x8 +#define E3G_SPDIF_CLOCK 0x9 +#define E3G_ADAT_CLOCK 0xA +#define E3G_WORD_CLOCK 0xB +#define E3G_CONTINUOUS_CLOCK 0xE + +#define E3G_ADAT_MODE 0x1000 +#define E3G_SPDIF_OPTICAL_MODE 0x2000 + +#define E3G_CLOCK_CLEAR_MASK 0xbfffbff0 +#define E3G_DIGITAL_MODE_CLEAR_MASK 0xffffcfff +#define E3G_SPDIF_FORMAT_CLEAR_MASK 0xfffff01f + +/* Clock detect bits reported by the DSP */ +#define E3G_CLOCK_DETECT_BIT_WORD96 0x0001 +#define E3G_CLOCK_DETECT_BIT_WORD48 0x0002 +#define E3G_CLOCK_DETECT_BIT_SPDIF48 0x0004 +#define E3G_CLOCK_DETECT_BIT_ADAT 0x0004 +#define E3G_CLOCK_DETECT_BIT_SPDIF96 0x0008 +#define E3G_CLOCK_DETECT_BIT_WORD (E3G_CLOCK_DETECT_BIT_WORD96|E3G_CLOCK_DETECT_BIT_WORD48) +#define E3G_CLOCK_DETECT_BIT_SPDIF (E3G_CLOCK_DETECT_BIT_SPDIF48|E3G_CLOCK_DETECT_BIT_SPDIF96) + +/* Frequency control register */ +#define E3G_MAGIC_NUMBER 677376000 +#define E3G_FREQ_REG_DEFAULT (E3G_MAGIC_NUMBER / 48000 - 2) +#define E3G_FREQ_REG_MAX 0xffff + +/* 3G external box types */ +#define E3G_GINA3G_BOX_TYPE 0x00 +#define E3G_LAYLA3G_BOX_TYPE 0x10 +#define E3G_ASIC_NOT_LOADED 0xffff +#define E3G_BOX_TYPE_MASK 0xf0 + +/* Indigo express control register values */ +#define INDIGO_EXPRESS_32000 0x02 +#define INDIGO_EXPRESS_44100 0x01 +#define INDIGO_EXPRESS_48000 0x00 +#define INDIGO_EXPRESS_DOUBLE_SPEED 0x10 +#define INDIGO_EXPRESS_QUAD_SPEED 0x04 +#define INDIGO_EXPRESS_CLOCK_MASK 0x17 + + +/* + * + * Gina20 & Layla20 have input gain controls for the analog inputs; + * this is the magic number for the hardware that gives you 0 dB at -10. + * + */ + +#define GL20_INPUT_GAIN_MAGIC_NUMBER 0xC8 + + +/* + * + * Defines how much time must pass between DSP load attempts + * + */ + +#define DSP_LOAD_ATTEMPT_PERIOD 1000000L /* One second */ + + +/* + * + * Size of arrays for the comm page. MAX_PLAY_TAPS and MAX_REC_TAPS are + * no longer used, but the sizes must still be right for the DSP to see + * the comm page correctly. + * + */ + +#define MONITOR_ARRAY_SIZE 0x180 +#define VMIXER_ARRAY_SIZE 0x40 +#define MIDI_OUT_BUFFER_SIZE 32 +#define MIDI_IN_BUFFER_SIZE 256 +#define MAX_PLAY_TAPS 168 +#define MAX_REC_TAPS 192 +#define DSP_MIDI_OUT_FIFO_SIZE 64 + + +/* sg_entry is a single entry for the scatter-gather list. The array of struct +sg_entry struct is read by the DSP, so all values must be little-endian. */ + +#define MAX_SGLIST_ENTRIES 512 + +struct sg_entry { + __le32 addr; + __le32 size; +}; + + +/**************************************************************************** + + The comm page. This structure is read and written by the DSP; the + DSP code is a firm believer in the byte offsets written in the comments + at the end of each line. This structure should not be changed. + + Any reads from or writes to this structure should be in little-endian format. + + ****************************************************************************/ + +struct comm_page { /* Base Length*/ + __le32 comm_size; /* size of this object 0x000 4 */ + __le32 flags; /* See Appendix A below 0x004 4 */ + __le32 unused; /* Unused entry 0x008 4 */ + __le32 sample_rate; /* Card sample rate in Hz 0x00c 4 */ + __le32 handshake; /* DSP command handshake 0x010 4 */ + __le32 cmd_start; /* Chs. to start mask 0x014 4 */ + __le32 cmd_stop; /* Chs. to stop mask 0x018 4 */ + __le32 cmd_reset; /* Chs. to reset mask 0x01c 4 */ + __le16 audio_format[DSP_MAXPIPES]; /* Chs. audio format 0x020 32*2 */ + struct sg_entry sglist_addr[DSP_MAXPIPES]; + /* Chs. Physical sglist addrs 0x060 32*8 */ + __le32 position[DSP_MAXPIPES]; + /* Positions for ea. ch. 0x160 32*4 */ + s8 vu_meter[DSP_MAXPIPES]; + /* VU meters 0x1e0 32*1 */ + s8 peak_meter[DSP_MAXPIPES]; + /* Peak meters 0x200 32*1 */ + s8 line_out_level[DSP_MAXAUDIOOUTPUTS]; + /* Output gain 0x220 16*1 */ + s8 line_in_level[DSP_MAXAUDIOINPUTS]; + /* Input gain 0x230 16*1 */ + s8 monitors[MONITOR_ARRAY_SIZE]; + /* Monitor map 0x240 0x180 */ + __le32 play_coeff[MAX_PLAY_TAPS]; + /* Gina/Darla play filters - obsolete 0x3c0 168*4 */ + __le32 rec_coeff[MAX_REC_TAPS]; + /* Gina/Darla record filters - obsolete 0x660 192*4 */ + __le16 midi_input[MIDI_IN_BUFFER_SIZE]; + /* MIDI input data transfer buffer 0x960 256*2 */ + u8 gd_clock_state; /* Chg Gina/Darla clock state 0xb60 1 */ + u8 gd_spdif_status; /* Chg. Gina/Darla S/PDIF state 0xb61 1 */ + u8 gd_resampler_state; /* Should always be 3 0xb62 1 */ + u8 filler2; /* 0xb63 1 */ + __le32 nominal_level_mask; /* -10 level enable mask 0xb64 4 */ + __le16 input_clock; /* Chg. Input clock state 0xb68 2 */ + __le16 output_clock; /* Chg. Output clock state 0xb6a 2 */ + __le32 status_clocks; /* Current Input clock state 0xb6c 4 */ + __le32 ext_box_status; /* External box status 0xb70 4 */ + __le32 cmd_add_buffer; /* Pipes to add (obsolete) 0xb74 4 */ + __le32 midi_out_free_count; + /* # of bytes free in MIDI output FIFO 0xb78 4 */ + __le32 unused2; /* Cyclic pipes 0xb7c 4 */ + __le32 control_register; + /* Mona, Gina24, Layla24, 3G ctrl reg 0xb80 4 */ + __le32 e3g_frq_register; /* 3G frequency register 0xb84 4 */ + u8 filler[24]; /* filler 0xb88 24*1 */ + s8 vmixer[VMIXER_ARRAY_SIZE]; + /* Vmixer levels 0xba0 64*1 */ + u8 midi_output[MIDI_OUT_BUFFER_SIZE]; + /* MIDI output data 0xbe0 32*1 */ +}; + +#endif /* _ECHO_DSP_ */ diff --git a/sound/pci/echoaudio/echoaudio_gml.c b/sound/pci/echoaudio/echoaudio_gml.c new file mode 100644 index 000000000..682a8cc63 --- /dev/null +++ b/sound/pci/echoaudio/echoaudio_gml.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +/* These functions are common for Gina24, Layla24 and Mona cards */ + + +/* ASIC status check - some cards have one or two ASICs that need to be +loaded. Once that load is complete, this function is called to see if +the load was successful. +If this load fails, it does not necessarily mean that the hardware is +defective - the external box may be disconnected or turned off. */ +static int check_asic_status(struct echoaudio *chip) +{ + u32 asic_status; + + send_vector(chip, DSP_VC_TEST_ASIC); + + /* The DSP will return a value to indicate whether or not the + ASIC is currently loaded */ + if (read_dsp(chip, &asic_status) < 0) { + dev_err(chip->card->dev, + "check_asic_status: failed on read_dsp\n"); + chip->asic_loaded = false; + return -EIO; + } + + chip->asic_loaded = (asic_status == ASIC_ALREADY_LOADED); + return chip->asic_loaded ? 0 : -EIO; +} + + + +/* Most configuration of Gina24, Layla24, or Mona is accomplished by writing +the control register. write_control_reg sends the new control register +value to the DSP. */ +static int write_control_reg(struct echoaudio *chip, u32 value, char force) +{ + __le32 reg_value; + + /* Handle the digital input auto-mute */ + if (chip->digital_in_automute) + value |= GML_DIGITAL_IN_AUTO_MUTE; + else + value &= ~GML_DIGITAL_IN_AUTO_MUTE; + + dev_dbg(chip->card->dev, "write_control_reg: 0x%x\n", value); + + /* Write the control register */ + reg_value = cpu_to_le32(value); + if (reg_value != chip->comm_page->control_register || force) { + if (wait_handshake(chip)) + return -EIO; + chip->comm_page->control_register = reg_value; + clear_handshake(chip); + return send_vector(chip, DSP_VC_WRITE_CONTROL_REG); + } + return 0; +} + + + +/* Gina24, Layla24, and Mona support digital input auto-mute. If the digital +input auto-mute is enabled, the DSP will only enable the digital inputs if +the card is syncing to a valid clock on the ADAT or S/PDIF inputs. +If the auto-mute is disabled, the digital inputs are enabled regardless of +what the input clock is set or what is connected. */ +static int set_input_auto_mute(struct echoaudio *chip, int automute) +{ + dev_dbg(chip->card->dev, "set_input_auto_mute %d\n", automute); + + chip->digital_in_automute = automute; + + /* Re-set the input clock to the current value - indirectly causes + the auto-mute flag to be sent to the DSP */ + return set_input_clock(chip, chip->input_clock); +} + + + +/* S/PDIF coax / S/PDIF optical / ADAT - switch */ +static int set_digital_mode(struct echoaudio *chip, u8 mode) +{ + u8 previous_mode; + int err, i, o; + + if (chip->bad_board) + return -EIO; + + /* All audio channels must be closed before changing the digital mode */ + if (snd_BUG_ON(chip->pipe_alloc_mask)) + return -EAGAIN; + + if (snd_BUG_ON(!(chip->digital_modes & (1 << mode)))) + return -EINVAL; + + previous_mode = chip->digital_mode; + err = dsp_set_digital_mode(chip, mode); + + /* If we successfully changed the digital mode from or to ADAT, + then make sure all output, input and monitor levels are + updated by the DSP comm object. */ + if (err >= 0 && previous_mode != mode && + (previous_mode == DIGITAL_MODE_ADAT || mode == DIGITAL_MODE_ADAT)) { + spin_lock_irq(&chip->lock); + for (o = 0; o < num_busses_out(chip); o++) + for (i = 0; i < num_busses_in(chip); i++) + set_monitor_gain(chip, o, i, + chip->monitor_gain[o][i]); + +#ifdef ECHOCARD_HAS_INPUT_GAIN + for (i = 0; i < num_busses_in(chip); i++) + set_input_gain(chip, i, chip->input_gain[i]); + update_input_line_level(chip); +#endif + + for (o = 0; o < num_busses_out(chip); o++) + set_output_gain(chip, o, chip->output_gain[o]); + update_output_line_level(chip); + spin_unlock_irq(&chip->lock); + } + + return err; +} + + + +/* Set the S/PDIF output format */ +static int set_professional_spdif(struct echoaudio *chip, char prof) +{ + u32 control_reg; + int err; + + /* Clear the current S/PDIF flags */ + control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg &= GML_SPDIF_FORMAT_CLEAR_MASK; + + /* Set the new S/PDIF flags depending on the mode */ + control_reg |= GML_SPDIF_TWO_CHANNEL | GML_SPDIF_24_BIT | + GML_SPDIF_COPY_PERMIT; + if (prof) { + /* Professional mode */ + control_reg |= GML_SPDIF_PRO_MODE; + + switch (chip->sample_rate) { + case 32000: + control_reg |= GML_SPDIF_SAMPLE_RATE0 | + GML_SPDIF_SAMPLE_RATE1; + break; + case 44100: + control_reg |= GML_SPDIF_SAMPLE_RATE0; + break; + case 48000: + control_reg |= GML_SPDIF_SAMPLE_RATE1; + break; + } + } else { + /* Consumer mode */ + switch (chip->sample_rate) { + case 32000: + control_reg |= GML_SPDIF_SAMPLE_RATE0 | + GML_SPDIF_SAMPLE_RATE1; + break; + case 48000: + control_reg |= GML_SPDIF_SAMPLE_RATE1; + break; + } + } + + err = write_control_reg(chip, control_reg, false); + if (err) + return err; + chip->professional_spdif = prof; + dev_dbg(chip->card->dev, "set_professional_spdif to %s\n", + prof ? "Professional" : "Consumer"); + return 0; +} diff --git a/sound/pci/echoaudio/gina20.c b/sound/pci/echoaudio/gina20.c new file mode 100644 index 000000000..b5f88922e --- /dev/null +++ b/sound/pci/echoaudio/gina20.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define ECHOGALS_FAMILY +#define ECHOCARD_GINA20 +#define ECHOCARD_NAME "Gina20" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_INPUT_GAIN +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT false + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 2 */ +#define PX_ANALOG_IN 10 /* 2 */ +#define PX_DIGITAL_IN 12 /* 2 */ +#define PX_NUM 14 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 2 */ +#define BX_ANALOG_IN 10 /* 2 */ +#define BX_DIGITAL_IN 12 /* 2 */ +#define BX_NUM 14 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/gina20_dsp.fw"); + +#define FW_GINA20_DSP 0 + +static const struct firmware card_fw[] = { + {0, "gina20_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0020) }, /* DSP 56301 Gina20 rev.0 */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, + /* One page (4k) contains 512 instructions. I don't know if the hw + supports lists longer than this. In this case periods_max=220 is a + safe limit to make sure the list never exceeds 512 instructions. */ +}; + + +#include "gina20_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" diff --git a/sound/pci/echoaudio/gina20_dsp.c b/sound/pci/echoaudio/gina20_dsp.c new file mode 100644 index 000000000..ba4c398f8 --- /dev/null +++ b/sound/pci/echoaudio/gina20_dsp.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int set_professional_spdif(struct echoaudio *chip, char prof); +static int update_flags(struct echoaudio *chip); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != GINA20)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->dsp_code_to_load = FW_GINA20_DSP; + chip->spdif_status = GD_SPDIF_STATUS_UNDEF; + chip->clock_state = GD_CLOCK_UNDEF; + /* Since this card has no ASIC, mark it as loaded so everything + works OK */ + chip->asic_loaded = true; + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL | + ECHO_CLOCK_BIT_SPDIF; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + chip->professional_spdif = false; + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + u32 clocks_from_dsp, clock_bits; + + /* Map the DSP clock detect bits to the generic driver clock + detect bits */ + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + clock_bits = ECHO_CLOCK_BIT_INTERNAL; + + if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_SPDIF) + clock_bits |= ECHO_CLOCK_BIT_SPDIF; + + return clock_bits; +} + + + +/* The Gina20 has no ASIC. Just do nothing */ +static int load_asic(struct echoaudio *chip) +{ + return 0; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u8 clock_state, spdif_status; + + if (wait_handshake(chip)) + return -EIO; + + switch (rate) { + case 44100: + clock_state = GD_CLOCK_44; + spdif_status = GD_SPDIF_STATUS_44; + break; + case 48000: + clock_state = GD_CLOCK_48; + spdif_status = GD_SPDIF_STATUS_48; + break; + default: + clock_state = GD_CLOCK_NOCHANGE; + spdif_status = GD_SPDIF_STATUS_NOCHANGE; + break; + } + + if (chip->clock_state == clock_state) + clock_state = GD_CLOCK_NOCHANGE; + if (spdif_status == chip->spdif_status) + spdif_status = GD_SPDIF_STATUS_NOCHANGE; + + chip->comm_page->sample_rate = cpu_to_le32(rate); + chip->comm_page->gd_clock_state = clock_state; + chip->comm_page->gd_spdif_status = spdif_status; + chip->comm_page->gd_resampler_state = 3; /* magic number - should always be 3 */ + + /* Save the new audio state if it changed */ + if (clock_state != GD_CLOCK_NOCHANGE) + chip->clock_state = clock_state; + if (spdif_status != GD_SPDIF_STATUS_NOCHANGE) + chip->spdif_status = spdif_status; + chip->sample_rate = rate; + + clear_handshake(chip); + return send_vector(chip, DSP_VC_SET_GD_AUDIO_STATE); +} + + + +static int set_input_clock(struct echoaudio *chip, u16 clock) +{ + + switch (clock) { + case ECHO_CLOCK_INTERNAL: + /* Reset the audio state to unknown (just in case) */ + chip->clock_state = GD_CLOCK_UNDEF; + chip->spdif_status = GD_SPDIF_STATUS_UNDEF; + set_sample_rate(chip, chip->sample_rate); + chip->input_clock = clock; + break; + case ECHO_CLOCK_SPDIF: + chip->comm_page->gd_clock_state = GD_CLOCK_SPDIFIN; + chip->comm_page->gd_spdif_status = GD_SPDIF_STATUS_NOCHANGE; + clear_handshake(chip); + send_vector(chip, DSP_VC_SET_GD_AUDIO_STATE); + chip->clock_state = GD_CLOCK_SPDIFIN; + chip->input_clock = clock; + break; + default: + return -EINVAL; + } + + return 0; +} + + + +/* Set input bus gain (one unit is 0.5dB !) */ +static int set_input_gain(struct echoaudio *chip, u16 input, int gain) +{ + if (snd_BUG_ON(input >= num_busses_in(chip))) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + chip->input_gain[input] = gain; + gain += GL20_INPUT_GAIN_MAGIC_NUMBER; + chip->comm_page->line_in_level[input] = gain; + return 0; +} + + + +/* Tell the DSP to reread the flags from the comm page */ +static int update_flags(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_FLAGS); +} + + + +static int set_professional_spdif(struct echoaudio *chip, char prof) +{ + if (prof) + chip->comm_page->flags |= + cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF); + else + chip->comm_page->flags &= + ~cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF); + chip->professional_spdif = prof; + return update_flags(chip); +} diff --git a/sound/pci/echoaudio/gina24.c b/sound/pci/echoaudio/gina24.c new file mode 100644 index 000000000..fe0e51020 --- /dev/null +++ b/sound/pci/echoaudio/gina24.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define ECHO24_FAMILY +#define ECHOCARD_GINA24 +#define ECHOCARD_NAME "Gina24" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE +#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT 6 +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 8 */ +#define PX_ANALOG_IN 16 /* 2 */ +#define PX_DIGITAL_IN 18 /* 8 */ +#define PX_NUM 26 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 8 */ +#define BX_ANALOG_IN 16 /* 2 */ +#define BX_DIGITAL_IN 18 /* 8 */ +#define BX_NUM 26 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/gina24_301_dsp.fw"); +MODULE_FIRMWARE("ea/gina24_361_dsp.fw"); +MODULE_FIRMWARE("ea/gina24_301_asic.fw"); +MODULE_FIRMWARE("ea/gina24_361_asic.fw"); + +#define FW_361_LOADER 0 +#define FW_GINA24_301_DSP 1 +#define FW_GINA24_361_DSP 2 +#define FW_GINA24_301_ASIC 3 +#define FW_GINA24_361_ASIC 4 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "gina24_301_dsp.fw"}, + {0, "gina24_361_dsp.fw"}, + {0, "gina24_301_asic.fw"}, + {0, "gina24_361_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0050) }, /* DSP 56301 Gina24 rev.0 */ + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0051) }, /* DSP 56301 Gina24 rev.1 */ + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0050) }, /* DSP 56361 Gina24 rev.0 */ + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0051) }, /* DSP 56361 Gina24 rev.1 */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, + /* One page (4k) contains 512 instructions. I don't know if the hw + supports lists longer than this. In this case periods_max=220 is a + safe limit to make sure the list never exceeds 512 instructions. + 220 ~= (512 - 1 - (BUFFER_BYTES_MAX / PAGE_SIZE)) / 2 */ +}; + +#include "gina24_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio_gml.c" +#include "echoaudio.c" diff --git a/sound/pci/echoaudio/gina24_dsp.c b/sound/pci/echoaudio/gina24_dsp.c new file mode 100644 index 000000000..fa1d8c06f --- /dev/null +++ b/sound/pci/echoaudio/gina24_dsp.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int write_control_reg(struct echoaudio *chip, u32 value, char force); +static int set_input_clock(struct echoaudio *chip, u16 clock); +static int set_professional_spdif(struct echoaudio *chip, char prof); +static int set_digital_mode(struct echoaudio *chip, u8 mode); +static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic); +static int check_asic_status(struct echoaudio *chip); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != GINA24)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->input_clock_types = + ECHO_CLOCK_BIT_INTERNAL | ECHO_CLOCK_BIT_SPDIF | + ECHO_CLOCK_BIT_ESYNC | ECHO_CLOCK_BIT_ESYNC96 | + ECHO_CLOCK_BIT_ADAT; + + /* Gina24 comes in both '301 and '361 flavors */ + if (chip->device_id == DEVICE_ID_56361) { + chip->dsp_code_to_load = FW_GINA24_361_DSP; + chip->digital_modes = + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA | + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL | + ECHOCAPS_HAS_DIGITAL_MODE_ADAT; + } else { + chip->dsp_code_to_load = FW_GINA24_301_DSP; + chip->digital_modes = + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA | + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL | + ECHOCAPS_HAS_DIGITAL_MODE_ADAT | + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_CDROM; + } + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + chip->digital_mode = DIGITAL_MODE_SPDIF_RCA; + chip->professional_spdif = false; + chip->digital_in_automute = true; + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + u32 clocks_from_dsp, clock_bits; + + /* Map the DSP clock detect bits to the generic driver clock + detect bits */ + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + clock_bits = ECHO_CLOCK_BIT_INTERNAL; + + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF) + clock_bits |= ECHO_CLOCK_BIT_SPDIF; + + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_ADAT) + clock_bits |= ECHO_CLOCK_BIT_ADAT; + + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_ESYNC) + clock_bits |= ECHO_CLOCK_BIT_ESYNC | ECHO_CLOCK_BIT_ESYNC96; + + return clock_bits; +} + + + +/* Gina24 has an ASIC on the PCI card which must be loaded for anything +interesting to happen. */ +static int load_asic(struct echoaudio *chip) +{ + u32 control_reg; + int err; + short asic; + + if (chip->asic_loaded) + return 1; + + /* Give the DSP a few milliseconds to settle down */ + mdelay(10); + + /* Pick the correct ASIC for '301 or '361 Gina24 */ + if (chip->device_id == DEVICE_ID_56361) + asic = FW_GINA24_361_ASIC; + else + asic = FW_GINA24_301_ASIC; + + err = load_asic_generic(chip, DSP_FNC_LOAD_GINA24_ASIC, asic); + if (err < 0) + return err; + + chip->asic_code = asic; + + /* Now give the new ASIC a little time to set up */ + mdelay(10); + /* See if it worked */ + err = check_asic_status(chip); + + /* Set up the control register if the load succeeded - + 48 kHz, internal clock, S/PDIF RCA mode */ + if (!err) { + control_reg = GML_CONVERTER_ENABLE | GML_48KHZ; + err = write_control_reg(chip, control_reg, true); + } + return err; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u32 control_reg, clock; + + if (snd_BUG_ON(rate >= 50000 && + chip->digital_mode == DIGITAL_MODE_ADAT)) + return -EINVAL; + + /* Only set the clock for internal mode. */ + if (chip->input_clock != ECHO_CLOCK_INTERNAL) { + dev_warn(chip->card->dev, + "Cannot set sample rate - clock not set to CLK_CLOCKININTERNAL\n"); + /* Save the rate anyhow */ + chip->comm_page->sample_rate = cpu_to_le32(rate); + chip->sample_rate = rate; + return 0; + } + + clock = 0; + + control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg &= GML_CLOCK_CLEAR_MASK & GML_SPDIF_RATE_CLEAR_MASK; + + switch (rate) { + case 96000: + clock = GML_96KHZ; + break; + case 88200: + clock = GML_88KHZ; + break; + case 48000: + clock = GML_48KHZ | GML_SPDIF_SAMPLE_RATE1; + break; + case 44100: + clock = GML_44KHZ; + /* Professional mode ? */ + if (control_reg & GML_SPDIF_PRO_MODE) + clock |= GML_SPDIF_SAMPLE_RATE0; + break; + case 32000: + clock = GML_32KHZ | GML_SPDIF_SAMPLE_RATE0 | + GML_SPDIF_SAMPLE_RATE1; + break; + case 22050: + clock = GML_22KHZ; + break; + case 16000: + clock = GML_16KHZ; + break; + case 11025: + clock = GML_11KHZ; + break; + case 8000: + clock = GML_8KHZ; + break; + default: + dev_err(chip->card->dev, + "set_sample_rate: %d invalid!\n", rate); + return -EINVAL; + } + + control_reg |= clock; + + chip->comm_page->sample_rate = cpu_to_le32(rate); /* ignored by the DSP */ + chip->sample_rate = rate; + dev_dbg(chip->card->dev, "set_sample_rate: %d clock %d\n", rate, clock); + + return write_control_reg(chip, control_reg, false); +} + + + +static int set_input_clock(struct echoaudio *chip, u16 clock) +{ + u32 control_reg, clocks_from_dsp; + + + /* Mask off the clock select bits */ + control_reg = le32_to_cpu(chip->comm_page->control_register) & + GML_CLOCK_CLEAR_MASK; + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + switch (clock) { + case ECHO_CLOCK_INTERNAL: + chip->input_clock = ECHO_CLOCK_INTERNAL; + return set_sample_rate(chip, chip->sample_rate); + case ECHO_CLOCK_SPDIF: + if (chip->digital_mode == DIGITAL_MODE_ADAT) + return -EAGAIN; + control_reg |= GML_SPDIF_CLOCK; + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF96) + control_reg |= GML_DOUBLE_SPEED_MODE; + else + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + case ECHO_CLOCK_ADAT: + if (chip->digital_mode != DIGITAL_MODE_ADAT) + return -EAGAIN; + control_reg |= GML_ADAT_CLOCK; + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + case ECHO_CLOCK_ESYNC: + control_reg |= GML_ESYNC_CLOCK; + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + case ECHO_CLOCK_ESYNC96: + control_reg |= GML_ESYNC_CLOCK | GML_DOUBLE_SPEED_MODE; + break; + default: + dev_err(chip->card->dev, + "Input clock 0x%x not supported for Gina24\n", clock); + return -EINVAL; + } + + chip->input_clock = clock; + return write_control_reg(chip, control_reg, true); +} + + + +static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode) +{ + u32 control_reg; + int err, incompatible_clock; + + /* Set clock to "internal" if it's not compatible with the new mode */ + incompatible_clock = false; + switch (mode) { + case DIGITAL_MODE_SPDIF_OPTICAL: + case DIGITAL_MODE_SPDIF_CDROM: + case DIGITAL_MODE_SPDIF_RCA: + if (chip->input_clock == ECHO_CLOCK_ADAT) + incompatible_clock = true; + break; + case DIGITAL_MODE_ADAT: + if (chip->input_clock == ECHO_CLOCK_SPDIF) + incompatible_clock = true; + break; + default: + dev_err(chip->card->dev, + "Digital mode not supported: %d\n", mode); + return -EINVAL; + } + + guard(spinlock_irq)(&chip->lock); + + if (incompatible_clock) { /* Switch to 48KHz, internal */ + chip->sample_rate = 48000; + set_input_clock(chip, ECHO_CLOCK_INTERNAL); + } + + /* Clear the current digital mode */ + control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg &= GML_DIGITAL_MODE_CLEAR_MASK; + + /* Tweak the control reg */ + switch (mode) { + case DIGITAL_MODE_SPDIF_OPTICAL: + control_reg |= GML_SPDIF_OPTICAL_MODE; + break; + case DIGITAL_MODE_SPDIF_CDROM: + /* '361 Gina24 cards do not have the S/PDIF CD-ROM mode */ + if (chip->device_id == DEVICE_ID_56301) + control_reg |= GML_SPDIF_CDROM_MODE; + break; + case DIGITAL_MODE_SPDIF_RCA: + /* GML_SPDIF_OPTICAL_MODE bit cleared */ + break; + case DIGITAL_MODE_ADAT: + control_reg |= GML_ADAT_MODE; + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + } + + err = write_control_reg(chip, control_reg, true); + if (err < 0) + return err; + chip->digital_mode = mode; + + dev_dbg(chip->card->dev, + "set_digital_mode to %d\n", chip->digital_mode); + return incompatible_clock; +} diff --git a/sound/pci/echoaudio/indigo.c b/sound/pci/echoaudio/indigo.c new file mode 100644 index 000000000..496cc5aa9 --- /dev/null +++ b/sound/pci/echoaudio/indigo.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO +#define ECHOCARD_NAME "Indigo" +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 0 */ +#define PX_DIGITAL_IN 8 /* 0 */ +#define PX_NUM 8 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 2 */ +#define BX_DIGITAL_OUT 2 /* 0 */ +#define BX_ANALOG_IN 2 /* 0 */ +#define BX_DIGITAL_IN 2 /* 0 */ +#define BX_NUM 2 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0090) }, /* Indigo */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 32000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, +}; + +#include "indigo_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" + diff --git a/sound/pci/echoaudio/indigo_dsp.c b/sound/pci/echoaudio/indigo_dsp.c new file mode 100644 index 000000000..c53643279 --- /dev/null +++ b/sound/pci/echoaudio/indigo_dsp.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe, + int gain); +static int update_vmixer_level(struct echoaudio *chip); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->dsp_code_to_load = FW_INDIGO_DSP; + /* Since this card has no ASIC, mark it as loaded so everything + works OK */ + chip->asic_loaded = true; + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + return ECHO_CLOCK_BIT_INTERNAL; +} + + + +/* The Indigo has no ASIC. Just do nothing */ +static int load_asic(struct echoaudio *chip) +{ + return 0; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u32 control_reg; + + switch (rate) { + case 96000: + control_reg = MIA_96000; + break; + case 88200: + control_reg = MIA_88200; + break; + case 48000: + control_reg = MIA_48000; + break; + case 44100: + control_reg = MIA_44100; + break; + case 32000: + control_reg = MIA_32000; + break; + default: + dev_err(chip->card->dev, + "set_sample_rate: %d invalid!\n", rate); + return -EINVAL; + } + + /* Set the control register if it has changed */ + if (control_reg != le32_to_cpu(chip->comm_page->control_register)) { + if (wait_handshake(chip)) + return -EIO; + + chip->comm_page->sample_rate = cpu_to_le32(rate); /* ignored by the DSP */ + chip->comm_page->control_register = cpu_to_le32(control_reg); + chip->sample_rate = rate; + + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_CLOCKS); + } + return 0; +} + + + +/* This function routes the sound from a virtual channel to a real output */ +static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe, + int gain) +{ + int index; + + if (snd_BUG_ON(pipe >= num_pipes_out(chip) || + output >= num_busses_out(chip))) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + chip->vmixer_gain[output][pipe] = gain; + index = output * num_pipes_out(chip) + pipe; + chip->comm_page->vmixer[index] = gain; + + dev_dbg(chip->card->dev, + "set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain); + return 0; +} + + + +/* Tell the DSP to read and update virtual mixer levels in comm page. */ +static int update_vmixer_level(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_SET_VMIXER_GAIN); +} + diff --git a/sound/pci/echoaudio/indigo_express_dsp.c b/sound/pci/echoaudio/indigo_express_dsp.c new file mode 100644 index 000000000..7a032b702 --- /dev/null +++ b/sound/pci/echoaudio/indigo_express_dsp.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/************************************************************************ + +This file is part of Echo Digital Audio's generic driver library. +Copyright Echo Digital Audio Corporation (c) 1998 - 2005 +All rights reserved +www.echoaudio.com + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +*************************************************************************/ + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u32 clock, control_reg, old_control_reg; + + if (wait_handshake(chip)) + return -EIO; + + old_control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg = old_control_reg & ~INDIGO_EXPRESS_CLOCK_MASK; + + switch (rate) { + case 32000: + clock = INDIGO_EXPRESS_32000; + break; + case 44100: + clock = INDIGO_EXPRESS_44100; + break; + case 48000: + clock = INDIGO_EXPRESS_48000; + break; + case 64000: + clock = INDIGO_EXPRESS_32000|INDIGO_EXPRESS_DOUBLE_SPEED; + break; + case 88200: + clock = INDIGO_EXPRESS_44100|INDIGO_EXPRESS_DOUBLE_SPEED; + break; + case 96000: + clock = INDIGO_EXPRESS_48000|INDIGO_EXPRESS_DOUBLE_SPEED; + break; + default: + return -EINVAL; + } + + control_reg |= clock; + if (control_reg != old_control_reg) { + dev_dbg(chip->card->dev, + "set_sample_rate: %d clock %d\n", rate, clock); + chip->comm_page->control_register = cpu_to_le32(control_reg); + chip->sample_rate = rate; + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_CLOCKS); + } + return 0; +} + + + +/* This function routes the sound from a virtual channel to a real output */ +static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe, + int gain) +{ + int index; + + if (snd_BUG_ON(pipe >= num_pipes_out(chip) || + output >= num_busses_out(chip))) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + chip->vmixer_gain[output][pipe] = gain; + index = output * num_pipes_out(chip) + pipe; + chip->comm_page->vmixer[index] = gain; + + dev_dbg(chip->card->dev, + "set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain); + return 0; +} + + + +/* Tell the DSP to read and update virtual mixer levels in comm page. */ +static int update_vmixer_level(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_SET_VMIXER_GAIN); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + return ECHO_CLOCK_BIT_INTERNAL; +} + + + +/* The IndigoIO has no ASIC. Just do nothing */ +static int load_asic(struct echoaudio *chip) +{ + return 0; +} diff --git a/sound/pci/echoaudio/indigodj.c b/sound/pci/echoaudio/indigodj.c new file mode 100644 index 000000000..45ad92e8f --- /dev/null +++ b/sound/pci/echoaudio/indigodj.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO_DJ +#define ECHOCARD_NAME "Indigo DJ" +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 0 */ +#define PX_DIGITAL_IN 8 /* 0 */ +#define PX_NUM 8 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 4 */ +#define BX_DIGITAL_OUT 4 /* 0 */ +#define BX_ANALOG_IN 4 /* 0 */ +#define BX_DIGITAL_IN 4 /* 0 */ +#define BX_NUM 4 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_dj_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_DJ_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_dj_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x00B0) }, /* Indigo DJ*/ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 32000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 4, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, +}; + +#include "indigodj_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" + diff --git a/sound/pci/echoaudio/indigodj_dsp.c b/sound/pci/echoaudio/indigodj_dsp.c new file mode 100644 index 000000000..0f8299ab1 --- /dev/null +++ b/sound/pci/echoaudio/indigodj_dsp.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe, + int gain); +static int update_vmixer_level(struct echoaudio *chip); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO_DJ)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->dsp_code_to_load = FW_INDIGO_DJ_DSP; + /* Since this card has no ASIC, mark it as loaded so everything + works OK */ + chip->asic_loaded = true; + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + return ECHO_CLOCK_BIT_INTERNAL; +} + + + +/* The IndigoDJ has no ASIC. Just do nothing */ +static int load_asic(struct echoaudio *chip) +{ + return 0; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u32 control_reg; + + switch (rate) { + case 96000: + control_reg = MIA_96000; + break; + case 88200: + control_reg = MIA_88200; + break; + case 48000: + control_reg = MIA_48000; + break; + case 44100: + control_reg = MIA_44100; + break; + case 32000: + control_reg = MIA_32000; + break; + default: + dev_err(chip->card->dev, + "set_sample_rate: %d invalid!\n", rate); + return -EINVAL; + } + + /* Set the control register if it has changed */ + if (control_reg != le32_to_cpu(chip->comm_page->control_register)) { + if (wait_handshake(chip)) + return -EIO; + + chip->comm_page->sample_rate = cpu_to_le32(rate); /* ignored by the DSP */ + chip->comm_page->control_register = cpu_to_le32(control_reg); + chip->sample_rate = rate; + + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_CLOCKS); + } + return 0; +} + + + +/* This function routes the sound from a virtual channel to a real output */ +static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe, + int gain) +{ + int index; + + if (snd_BUG_ON(pipe >= num_pipes_out(chip) || + output >= num_busses_out(chip))) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + chip->vmixer_gain[output][pipe] = gain; + index = output * num_pipes_out(chip) + pipe; + chip->comm_page->vmixer[index] = gain; + + dev_dbg(chip->card->dev, + "set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain); + return 0; +} + + + +/* Tell the DSP to read and update virtual mixer levels in comm page. */ +static int update_vmixer_level(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_SET_VMIXER_GAIN); +} + diff --git a/sound/pci/echoaudio/indigodjx.c b/sound/pci/echoaudio/indigodjx.c new file mode 100644 index 000000000..b1878ecb8 --- /dev/null +++ b/sound/pci/echoaudio/indigodjx.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2009 Giuliano Pochini + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO_DJX +#define ECHOCARD_NAME "Indigo DJx" +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 0 */ +#define PX_DIGITAL_IN 8 /* 0 */ +#define PX_NUM 8 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 4 */ +#define BX_DIGITAL_OUT 4 /* 0 */ +#define BX_ANALOG_IN 4 /* 0 */ +#define BX_DIGITAL_IN 4 /* 0 */ +#define BX_NUM 4 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_djx_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_DJX_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_djx_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x00E0) }, /* Indigo DJx*/ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 32000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 4, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, +}; + +#include "indigodjx_dsp.c" +#include "indigo_express_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" diff --git a/sound/pci/echoaudio/indigodjx_dsp.c b/sound/pci/echoaudio/indigodjx_dsp.c new file mode 100644 index 000000000..677cd8ed7 --- /dev/null +++ b/sound/pci/echoaudio/indigodjx_dsp.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/************************************************************************ + +This file is part of Echo Digital Audio's generic driver library. +Copyright Echo Digital Audio Corporation (c) 1998 - 2005 +All rights reserved +www.echoaudio.com + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +*************************************************************************/ + +static int update_vmixer_level(struct echoaudio *chip); +static int set_vmixer_gain(struct echoaudio *chip, u16 output, + u16 pipe, int gain); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO_DJX)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err < 0) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->dsp_code_to_load = FW_INDIGO_DJX_DSP; + /* Since this card has no ASIC, mark it as loaded so everything + works OK */ + chip->asic_loaded = true; + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + return init_line_levels(chip); +} diff --git a/sound/pci/echoaudio/indigoio.c b/sound/pci/echoaudio/indigoio.c new file mode 100644 index 000000000..2c6a6f74b --- /dev/null +++ b/sound/pci/echoaudio/indigoio.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO_IO +#define ECHOCARD_NAME "Indigo IO" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 0 */ +#define PX_NUM 10 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 2 */ +#define BX_DIGITAL_OUT 2 /* 0 */ +#define BX_ANALOG_IN 2 /* 2 */ +#define BX_DIGITAL_IN 4 /* 0 */ +#define BX_NUM 4 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_io_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_IO_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_io_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x00A0) }, /* Indigo IO*/ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 32000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, +}; + +#include "indigoio_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" + diff --git a/sound/pci/echoaudio/indigoio_dsp.c b/sound/pci/echoaudio/indigoio_dsp.c new file mode 100644 index 000000000..55d6431c8 --- /dev/null +++ b/sound/pci/echoaudio/indigoio_dsp.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe, + int gain); +static int update_vmixer_level(struct echoaudio *chip); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO_IO)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->dsp_code_to_load = FW_INDIGO_IO_DSP; + /* Since this card has no ASIC, mark it as loaded so everything + works OK */ + chip->asic_loaded = true; + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + return ECHO_CLOCK_BIT_INTERNAL; +} + + + +/* The IndigoIO has no ASIC. Just do nothing */ +static int load_asic(struct echoaudio *chip) +{ + return 0; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + if (wait_handshake(chip)) + return -EIO; + + chip->sample_rate = rate; + chip->comm_page->sample_rate = cpu_to_le32(rate); + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_CLOCKS); +} + + + +/* This function routes the sound from a virtual channel to a real output */ +static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe, + int gain) +{ + int index; + + if (snd_BUG_ON(pipe >= num_pipes_out(chip) || + output >= num_busses_out(chip))) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + chip->vmixer_gain[output][pipe] = gain; + index = output * num_pipes_out(chip) + pipe; + chip->comm_page->vmixer[index] = gain; + + dev_dbg(chip->card->dev, + "set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain); + return 0; +} + + + +/* Tell the DSP to read and update virtual mixer levels in comm page. */ +static int update_vmixer_level(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_SET_VMIXER_GAIN); +} + diff --git a/sound/pci/echoaudio/indigoiox.c b/sound/pci/echoaudio/indigoiox.c new file mode 100644 index 000000000..dfaa56331 --- /dev/null +++ b/sound/pci/echoaudio/indigoiox.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2009 Giuliano Pochini + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO_IOX +#define ECHOCARD_NAME "Indigo IOx" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 0 */ +#define PX_NUM 10 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 2 */ +#define BX_DIGITAL_OUT 2 /* 0 */ +#define BX_ANALOG_IN 2 /* 2 */ +#define BX_DIGITAL_IN 4 /* 0 */ +#define BX_NUM 4 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_iox_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_IOX_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_iox_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x00D0) }, /* Indigo IOx */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 32000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, +}; + +#include "indigoiox_dsp.c" +#include "indigo_express_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" + diff --git a/sound/pci/echoaudio/indigoiox_dsp.c b/sound/pci/echoaudio/indigoiox_dsp.c new file mode 100644 index 000000000..48958e51d --- /dev/null +++ b/sound/pci/echoaudio/indigoiox_dsp.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/************************************************************************ + +This file is part of Echo Digital Audio's generic driver library. +Copyright Echo Digital Audio Corporation (c) 1998 - 2005 +All rights reserved +www.echoaudio.com + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +*************************************************************************/ + +static int update_vmixer_level(struct echoaudio *chip); +static int set_vmixer_gain(struct echoaudio *chip, u16 output, + u16 pipe, int gain); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO_IOX)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err < 0) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->dsp_code_to_load = FW_INDIGO_IOX_DSP; + /* Since this card has no ASIC, mark it as loaded so everything + works OK */ + chip->asic_loaded = true; + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + return init_line_levels(chip); +} diff --git a/sound/pci/echoaudio/layla20.c b/sound/pci/echoaudio/layla20.c new file mode 100644 index 000000000..82c089f7b --- /dev/null +++ b/sound/pci/echoaudio/layla20.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define ECHOGALS_FAMILY +#define ECHOCARD_LAYLA20 +#define ECHOCARD_NAME "Layla20" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_INPUT_GAIN +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT false +#define ECHOCARD_HAS_OUTPUT_CLOCK_SWITCH +#define ECHOCARD_HAS_MIDI + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 10 */ +#define PX_DIGITAL_OUT 10 /* 2 */ +#define PX_ANALOG_IN 12 /* 8 */ +#define PX_DIGITAL_IN 20 /* 2 */ +#define PX_NUM 22 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 10 */ +#define BX_DIGITAL_OUT 10 /* 2 */ +#define BX_ANALOG_IN 12 /* 8 */ +#define BX_DIGITAL_IN 20 /* 2 */ +#define BX_NUM 22 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/layla20_dsp.fw"); +MODULE_FIRMWARE("ea/layla20_asic.fw"); + +#define FW_LAYLA20_DSP 0 +#define FW_LAYLA20_ASIC 1 + +static const struct firmware card_fw[] = { + {0, "layla20_dsp.fw"}, + {0, "layla20_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0030) }, /* DSP 56301 Layla20 rev.0 */ + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0031) }, /* DSP 56301 Layla20 rev.1 */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 50000, + .channels_min = 1, + .channels_max = 10, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, + /* One page (4k) contains 512 instructions. I don't know if the hw + supports lists longer than this. In this case periods_max=220 is a + safe limit to make sure the list never exceeds 512 instructions. */ +}; + +#include "layla20_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" +#include "midi.c" diff --git a/sound/pci/echoaudio/layla20_dsp.c b/sound/pci/echoaudio/layla20_dsp.c new file mode 100644 index 000000000..e0349a3c2 --- /dev/null +++ b/sound/pci/echoaudio/layla20_dsp.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int read_dsp(struct echoaudio *chip, u32 *data); +static int set_professional_spdif(struct echoaudio *chip, char prof); +static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic); +static int check_asic_status(struct echoaudio *chip); +static int update_flags(struct echoaudio *chip); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != LAYLA20)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->has_midi = true; + chip->dsp_code_to_load = FW_LAYLA20_DSP; + chip->input_clock_types = + ECHO_CLOCK_BIT_INTERNAL | ECHO_CLOCK_BIT_SPDIF | + ECHO_CLOCK_BIT_WORD | ECHO_CLOCK_BIT_SUPER; + chip->output_clock_types = + ECHO_CLOCK_BIT_WORD | ECHO_CLOCK_BIT_SUPER; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + chip->professional_spdif = false; + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + u32 clocks_from_dsp, clock_bits; + + /* Map the DSP clock detect bits to the generic driver clock detect bits */ + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + clock_bits = ECHO_CLOCK_BIT_INTERNAL; + + if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_SPDIF) + clock_bits |= ECHO_CLOCK_BIT_SPDIF; + + if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_WORD) { + if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_SUPER) + clock_bits |= ECHO_CLOCK_BIT_SUPER; + else + clock_bits |= ECHO_CLOCK_BIT_WORD; + } + + return clock_bits; +} + + + +/* ASIC status check - some cards have one or two ASICs that need to be +loaded. Once that load is complete, this function is called to see if +the load was successful. +If this load fails, it does not necessarily mean that the hardware is +defective - the external box may be disconnected or turned off. +This routine sometimes fails for Layla20; for Layla20, the loop runs +5 times and succeeds if it wins on three of the loops. */ +static int check_asic_status(struct echoaudio *chip) +{ + u32 asic_status; + int goodcnt, i; + + chip->asic_loaded = false; + for (i = goodcnt = 0; i < 5; i++) { + send_vector(chip, DSP_VC_TEST_ASIC); + + /* The DSP will return a value to indicate whether or not + the ASIC is currently loaded */ + if (read_dsp(chip, &asic_status) < 0) { + dev_err(chip->card->dev, + "check_asic_status: failed on read_dsp\n"); + return -EIO; + } + + if (asic_status == ASIC_ALREADY_LOADED) { + if (++goodcnt == 3) { + chip->asic_loaded = true; + return 0; + } + } + } + return -EIO; +} + + + +/* Layla20 has an ASIC in the external box */ +static int load_asic(struct echoaudio *chip) +{ + int err; + + if (chip->asic_loaded) + return 0; + + err = load_asic_generic(chip, DSP_FNC_LOAD_LAYLA_ASIC, + FW_LAYLA20_ASIC); + if (err < 0) + return err; + + /* Check if ASIC is alive and well. */ + return check_asic_status(chip); +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + if (snd_BUG_ON(rate < 8000 || rate > 50000)) + return -EINVAL; + + /* Only set the clock for internal mode. Do not return failure, + simply treat it as a non-event. */ + if (chip->input_clock != ECHO_CLOCK_INTERNAL) { + dev_warn(chip->card->dev, + "Cannot set sample rate - clock not set to CLK_CLOCKININTERNAL\n"); + chip->comm_page->sample_rate = cpu_to_le32(rate); + chip->sample_rate = rate; + return 0; + } + + if (wait_handshake(chip)) + return -EIO; + + dev_dbg(chip->card->dev, "set_sample_rate(%d)\n", rate); + chip->sample_rate = rate; + chip->comm_page->sample_rate = cpu_to_le32(rate); + clear_handshake(chip); + return send_vector(chip, DSP_VC_SET_LAYLA_SAMPLE_RATE); +} + + + +static int set_input_clock(struct echoaudio *chip, u16 clock_source) +{ + u16 clock; + u32 rate; + + rate = 0; + switch (clock_source) { + case ECHO_CLOCK_INTERNAL: + rate = chip->sample_rate; + clock = LAYLA20_CLOCK_INTERNAL; + break; + case ECHO_CLOCK_SPDIF: + clock = LAYLA20_CLOCK_SPDIF; + break; + case ECHO_CLOCK_WORD: + clock = LAYLA20_CLOCK_WORD; + break; + case ECHO_CLOCK_SUPER: + clock = LAYLA20_CLOCK_SUPER; + break; + default: + dev_err(chip->card->dev, + "Input clock 0x%x not supported for Layla24\n", + clock_source); + return -EINVAL; + } + chip->input_clock = clock_source; + + chip->comm_page->input_clock = cpu_to_le16(clock); + clear_handshake(chip); + send_vector(chip, DSP_VC_UPDATE_CLOCKS); + + if (rate) + set_sample_rate(chip, rate); + + return 0; +} + + + +static int set_output_clock(struct echoaudio *chip, u16 clock) +{ + switch (clock) { + case ECHO_CLOCK_SUPER: + clock = LAYLA20_OUTPUT_CLOCK_SUPER; + break; + case ECHO_CLOCK_WORD: + clock = LAYLA20_OUTPUT_CLOCK_WORD; + break; + default: + dev_err(chip->card->dev, "set_output_clock wrong clock\n"); + return -EINVAL; + } + + if (wait_handshake(chip)) + return -EIO; + + chip->comm_page->output_clock = cpu_to_le16(clock); + chip->output_clock = clock; + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_CLOCKS); +} + + + +/* Set input bus gain (one unit is 0.5dB !) */ +static int set_input_gain(struct echoaudio *chip, u16 input, int gain) +{ + if (snd_BUG_ON(input >= num_busses_in(chip))) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + chip->input_gain[input] = gain; + gain += GL20_INPUT_GAIN_MAGIC_NUMBER; + chip->comm_page->line_in_level[input] = gain; + return 0; +} + + + +/* Tell the DSP to reread the flags from the comm page */ +static int update_flags(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_FLAGS); +} + + + +static int set_professional_spdif(struct echoaudio *chip, char prof) +{ + if (prof) + chip->comm_page->flags |= + cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF); + else + chip->comm_page->flags &= + ~cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF); + chip->professional_spdif = prof; + return update_flags(chip); +} diff --git a/sound/pci/echoaudio/layla24.c b/sound/pci/echoaudio/layla24.c new file mode 100644 index 000000000..efd676467 --- /dev/null +++ b/sound/pci/echoaudio/layla24.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define ECHO24_FAMILY +#define ECHOCARD_LAYLA24 +#define ECHOCARD_NAME "Layla24" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE +#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT 6 +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 +#define ECHOCARD_HAS_MIDI + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 8 */ +#define PX_ANALOG_IN 16 /* 8 */ +#define PX_DIGITAL_IN 24 /* 8 */ +#define PX_NUM 32 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 8 */ +#define BX_ANALOG_IN 16 /* 8 */ +#define BX_DIGITAL_IN 24 /* 8 */ +#define BX_NUM 32 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/layla24_dsp.fw"); +MODULE_FIRMWARE("ea/layla24_1_asic.fw"); +MODULE_FIRMWARE("ea/layla24_2A_asic.fw"); +MODULE_FIRMWARE("ea/layla24_2S_asic.fw"); + +#define FW_361_LOADER 0 +#define FW_LAYLA24_DSP 1 +#define FW_LAYLA24_1_ASIC 2 +#define FW_LAYLA24_2A_ASIC 3 +#define FW_LAYLA24_2S_ASIC 4 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "layla24_dsp.fw"}, + {0, "layla24_1_asic.fw"}, + {0, "layla24_2A_asic.fw"}, + {0, "layla24_2S_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0060) }, /* DSP 56361 Layla24 rev.0 */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_8000_96000, + .rate_min = 8000, + .rate_max = 100000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, + /* One page (4k) contains 512 instructions. I don't know if the hw + supports lists longer than this. In this case periods_max=220 is a + safe limit to make sure the list never exceeds 512 instructions. */ +}; + + +#include "layla24_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio_gml.c" +#include "echoaudio.c" +#include "midi.c" diff --git a/sound/pci/echoaudio/layla24_dsp.c b/sound/pci/echoaudio/layla24_dsp.c new file mode 100644 index 000000000..e7b8f5fe7 --- /dev/null +++ b/sound/pci/echoaudio/layla24_dsp.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int write_control_reg(struct echoaudio *chip, u32 value, char force); +static int set_input_clock(struct echoaudio *chip, u16 clock); +static int set_professional_spdif(struct echoaudio *chip, char prof); +static int set_digital_mode(struct echoaudio *chip, u8 mode); +static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic); +static int check_asic_status(struct echoaudio *chip); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != LAYLA24)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->has_midi = true; + chip->dsp_code_to_load = FW_LAYLA24_DSP; + chip->input_clock_types = + ECHO_CLOCK_BIT_INTERNAL | ECHO_CLOCK_BIT_SPDIF | + ECHO_CLOCK_BIT_WORD | ECHO_CLOCK_BIT_ADAT; + chip->digital_modes = + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA | + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL | + ECHOCAPS_HAS_DIGITAL_MODE_ADAT; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + err = init_line_levels(chip); + if (err < 0) + return err; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + chip->digital_mode = DIGITAL_MODE_SPDIF_RCA; + chip->professional_spdif = false; + chip->digital_in_automute = true; + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + u32 clocks_from_dsp, clock_bits; + + /* Map the DSP clock detect bits to the generic driver clock detect bits */ + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + clock_bits = ECHO_CLOCK_BIT_INTERNAL; + + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF) + clock_bits |= ECHO_CLOCK_BIT_SPDIF; + + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_ADAT) + clock_bits |= ECHO_CLOCK_BIT_ADAT; + + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_WORD) + clock_bits |= ECHO_CLOCK_BIT_WORD; + + return clock_bits; +} + + + +/* Layla24 has an ASIC on the PCI card and another ASIC in the external box; +both need to be loaded. */ +static int load_asic(struct echoaudio *chip) +{ + int err; + + if (chip->asic_loaded) + return 1; + + + /* Give the DSP a few milliseconds to settle down */ + mdelay(10); + + /* Load the ASIC for the PCI card */ + err = load_asic_generic(chip, DSP_FNC_LOAD_LAYLA24_PCI_CARD_ASIC, + FW_LAYLA24_1_ASIC); + if (err < 0) + return err; + + chip->asic_code = FW_LAYLA24_2S_ASIC; + + /* Now give the new ASIC a little time to set up */ + mdelay(10); + + /* Do the external one */ + err = load_asic_generic(chip, DSP_FNC_LOAD_LAYLA24_EXTERNAL_ASIC, + FW_LAYLA24_2S_ASIC); + if (err < 0) + return err; + + /* Now give the external ASIC a little time to set up */ + mdelay(10); + + /* See if it worked */ + err = check_asic_status(chip); + + /* Set up the control register if the load succeeded - + 48 kHz, internal clock, S/PDIF RCA mode */ + if (!err) + err = write_control_reg(chip, GML_CONVERTER_ENABLE | GML_48KHZ, + true); + + return err; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u32 control_reg, clock, base_rate; + + if (snd_BUG_ON(rate >= 50000 && + chip->digital_mode == DIGITAL_MODE_ADAT)) + return -EINVAL; + + /* Only set the clock for internal mode. */ + if (chip->input_clock != ECHO_CLOCK_INTERNAL) { + dev_warn(chip->card->dev, + "Cannot set sample rate - clock not set to CLK_CLOCKININTERNAL\n"); + /* Save the rate anyhow */ + chip->comm_page->sample_rate = cpu_to_le32(rate); + chip->sample_rate = rate; + return 0; + } + + /* Get the control register & clear the appropriate bits */ + control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg &= GML_CLOCK_CLEAR_MASK & GML_SPDIF_RATE_CLEAR_MASK; + + clock = 0; + + switch (rate) { + case 96000: + clock = GML_96KHZ; + break; + case 88200: + clock = GML_88KHZ; + break; + case 48000: + clock = GML_48KHZ | GML_SPDIF_SAMPLE_RATE1; + break; + case 44100: + clock = GML_44KHZ; + /* Professional mode */ + if (control_reg & GML_SPDIF_PRO_MODE) + clock |= GML_SPDIF_SAMPLE_RATE0; + break; + case 32000: + clock = GML_32KHZ | GML_SPDIF_SAMPLE_RATE0 | + GML_SPDIF_SAMPLE_RATE1; + break; + case 22050: + clock = GML_22KHZ; + break; + case 16000: + clock = GML_16KHZ; + break; + case 11025: + clock = GML_11KHZ; + break; + case 8000: + clock = GML_8KHZ; + break; + default: + /* If this is a non-standard rate, then the driver needs to + use Layla24's special "continuous frequency" mode */ + clock = LAYLA24_CONTINUOUS_CLOCK; + if (rate > 50000) { + base_rate = rate >> 1; + control_reg |= GML_DOUBLE_SPEED_MODE; + } else { + base_rate = rate; + } + + if (base_rate < 25000) + base_rate = 25000; + + if (wait_handshake(chip)) + return -EIO; + + chip->comm_page->sample_rate = + cpu_to_le32(LAYLA24_MAGIC_NUMBER / base_rate - 2); + + clear_handshake(chip); + send_vector(chip, DSP_VC_SET_LAYLA24_FREQUENCY_REG); + } + + control_reg |= clock; + + chip->comm_page->sample_rate = cpu_to_le32(rate); /* ignored by the DSP ? */ + chip->sample_rate = rate; + dev_dbg(chip->card->dev, + "set_sample_rate: %d clock %d\n", rate, control_reg); + + return write_control_reg(chip, control_reg, false); +} + + + +static int set_input_clock(struct echoaudio *chip, u16 clock) +{ + u32 control_reg, clocks_from_dsp; + + /* Mask off the clock select bits */ + control_reg = le32_to_cpu(chip->comm_page->control_register) & + GML_CLOCK_CLEAR_MASK; + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + /* Pick the new clock */ + switch (clock) { + case ECHO_CLOCK_INTERNAL: + chip->input_clock = ECHO_CLOCK_INTERNAL; + return set_sample_rate(chip, chip->sample_rate); + case ECHO_CLOCK_SPDIF: + if (chip->digital_mode == DIGITAL_MODE_ADAT) + return -EAGAIN; + control_reg |= GML_SPDIF_CLOCK; + /* Layla24 doesn't support 96KHz S/PDIF */ + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + case ECHO_CLOCK_WORD: + control_reg |= GML_WORD_CLOCK; + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_WORD96) + control_reg |= GML_DOUBLE_SPEED_MODE; + else + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + case ECHO_CLOCK_ADAT: + if (chip->digital_mode != DIGITAL_MODE_ADAT) + return -EAGAIN; + control_reg |= GML_ADAT_CLOCK; + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + default: + dev_err(chip->card->dev, + "Input clock 0x%x not supported for Layla24\n", clock); + return -EINVAL; + } + + chip->input_clock = clock; + return write_control_reg(chip, control_reg, true); +} + + + +/* Depending on what digital mode you want, Layla24 needs different ASICs +loaded. This function checks the ASIC needed for the new mode and sees +if it matches the one already loaded. */ +static int switch_asic(struct echoaudio *chip, short asic) +{ + s8 *monitors; + + /* Check to see if this is already loaded */ + if (asic != chip->asic_code) { + monitors = kmemdup(chip->comm_page->monitors, + MONITOR_ARRAY_SIZE, GFP_KERNEL); + if (! monitors) + return -ENOMEM; + + memset(chip->comm_page->monitors, ECHOGAIN_MUTED, + MONITOR_ARRAY_SIZE); + + /* Load the desired ASIC */ + if (load_asic_generic(chip, DSP_FNC_LOAD_LAYLA24_EXTERNAL_ASIC, + asic) < 0) { + memcpy(chip->comm_page->monitors, monitors, + MONITOR_ARRAY_SIZE); + kfree(monitors); + return -EIO; + } + chip->asic_code = asic; + memcpy(chip->comm_page->monitors, monitors, MONITOR_ARRAY_SIZE); + kfree(monitors); + } + + return 0; +} + + + +static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode) +{ + u32 control_reg; + int err, incompatible_clock; + short asic; + + /* Set clock to "internal" if it's not compatible with the new mode */ + incompatible_clock = false; + switch (mode) { + case DIGITAL_MODE_SPDIF_OPTICAL: + case DIGITAL_MODE_SPDIF_RCA: + if (chip->input_clock == ECHO_CLOCK_ADAT) + incompatible_clock = true; + asic = FW_LAYLA24_2S_ASIC; + break; + case DIGITAL_MODE_ADAT: + if (chip->input_clock == ECHO_CLOCK_SPDIF) + incompatible_clock = true; + asic = FW_LAYLA24_2A_ASIC; + break; + default: + dev_err(chip->card->dev, + "Digital mode not supported: %d\n", mode); + return -EINVAL; + } + + if (incompatible_clock) { /* Switch to 48KHz, internal */ + chip->sample_rate = 48000; + guard(spinlock_irq)(&chip->lock); + set_input_clock(chip, ECHO_CLOCK_INTERNAL); + } + + /* switch_asic() can sleep */ + if (switch_asic(chip, asic) < 0) + return -EIO; + + guard(spinlock_irq)(&chip->lock); + + /* Tweak the control register */ + control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg &= GML_DIGITAL_MODE_CLEAR_MASK; + + switch (mode) { + case DIGITAL_MODE_SPDIF_OPTICAL: + control_reg |= GML_SPDIF_OPTICAL_MODE; + break; + case DIGITAL_MODE_SPDIF_RCA: + /* GML_SPDIF_OPTICAL_MODE bit cleared */ + break; + case DIGITAL_MODE_ADAT: + control_reg |= GML_ADAT_MODE; + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + } + + err = write_control_reg(chip, control_reg, true); + if (err < 0) + return err; + chip->digital_mode = mode; + + dev_dbg(chip->card->dev, "set_digital_mode to %d\n", mode); + return incompatible_clock; +} diff --git a/sound/pci/echoaudio/mia.c b/sound/pci/echoaudio/mia.c new file mode 100644 index 000000000..950aaec74 --- /dev/null +++ b/sound/pci/echoaudio/mia.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define ECHO24_FAMILY +#define ECHOCARD_MIA +#define ECHOCARD_NAME "Mia" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT false +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 +#define ECHOCARD_HAS_MIDI +#define ECHOCARD_HAS_LINE_OUT_GAIN + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 2 */ +#define PX_NUM 12 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 2 */ +#define BX_DIGITAL_OUT 2 /* 2 */ +#define BX_ANALOG_IN 4 /* 2 */ +#define BX_DIGITAL_IN 6 /* 2 */ +#define BX_NUM 8 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/mia_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_MIA_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "mia_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0080) }, /* DSP 56361 Mia rev.0 */ + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0081) }, /* DSP 56361 Mia rev.1 */ + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, + /* One page (4k) contains 512 instructions. I don't know if the hw + supports lists longer than this. In this case periods_max=220 is a + safe limit to make sure the list never exceeds 512 instructions. */ +}; + + +#include "mia_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio.c" +#include "midi.c" diff --git a/sound/pci/echoaudio/mia_dsp.c b/sound/pci/echoaudio/mia_dsp.c new file mode 100644 index 000000000..f040a09f3 --- /dev/null +++ b/sound/pci/echoaudio/mia_dsp.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int set_input_clock(struct echoaudio *chip, u16 clock); +static int set_professional_spdif(struct echoaudio *chip, char prof); +static int update_flags(struct echoaudio *chip); +static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe, + int gain); +static int update_vmixer_level(struct echoaudio *chip); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != MIA)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->dsp_code_to_load = FW_MIA_DSP; + /* Since this card has no ASIC, mark it as loaded so everything + works OK */ + chip->asic_loaded = true; + if ((subdevice_id & 0x0000f) == MIA_MIDI_REV) + chip->has_midi = true; + chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL | + ECHO_CLOCK_BIT_SPDIF; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + u32 clocks_from_dsp, clock_bits; + + /* Map the DSP clock detect bits to the generic driver clock + detect bits */ + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + clock_bits = ECHO_CLOCK_BIT_INTERNAL; + + if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_SPDIF) + clock_bits |= ECHO_CLOCK_BIT_SPDIF; + + return clock_bits; +} + + + +/* The Mia has no ASIC. Just do nothing */ +static int load_asic(struct echoaudio *chip) +{ + return 0; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u32 control_reg; + + switch (rate) { + case 96000: + control_reg = MIA_96000; + break; + case 88200: + control_reg = MIA_88200; + break; + case 48000: + control_reg = MIA_48000; + break; + case 44100: + control_reg = MIA_44100; + break; + case 32000: + control_reg = MIA_32000; + break; + default: + dev_err(chip->card->dev, + "set_sample_rate: %d invalid!\n", rate); + return -EINVAL; + } + + /* Override the clock setting if this Mia is set to S/PDIF clock */ + if (chip->input_clock == ECHO_CLOCK_SPDIF) + control_reg |= MIA_SPDIF; + + /* Set the control register if it has changed */ + if (control_reg != le32_to_cpu(chip->comm_page->control_register)) { + if (wait_handshake(chip)) + return -EIO; + + chip->comm_page->sample_rate = cpu_to_le32(rate); /* ignored by the DSP */ + chip->comm_page->control_register = cpu_to_le32(control_reg); + chip->sample_rate = rate; + + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_CLOCKS); + } + return 0; +} + + + +static int set_input_clock(struct echoaudio *chip, u16 clock) +{ + dev_dbg(chip->card->dev, "set_input_clock(%d)\n", clock); + if (snd_BUG_ON(clock != ECHO_CLOCK_INTERNAL && + clock != ECHO_CLOCK_SPDIF)) + return -EINVAL; + + chip->input_clock = clock; + return set_sample_rate(chip, chip->sample_rate); +} + + + +/* This function routes the sound from a virtual channel to a real output */ +static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe, + int gain) +{ + int index; + + if (snd_BUG_ON(pipe >= num_pipes_out(chip) || + output >= num_busses_out(chip))) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + chip->vmixer_gain[output][pipe] = gain; + index = output * num_pipes_out(chip) + pipe; + chip->comm_page->vmixer[index] = gain; + + dev_dbg(chip->card->dev, + "set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain); + return 0; +} + + + +/* Tell the DSP to read and update virtual mixer levels in comm page. */ +static int update_vmixer_level(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_SET_VMIXER_GAIN); +} + + + +/* Tell the DSP to reread the flags from the comm page */ +static int update_flags(struct echoaudio *chip) +{ + if (wait_handshake(chip)) + return -EIO; + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_FLAGS); +} + + + +static int set_professional_spdif(struct echoaudio *chip, char prof) +{ + dev_dbg(chip->card->dev, "set_professional_spdif %d\n", prof); + if (prof) + chip->comm_page->flags |= + cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF); + else + chip->comm_page->flags &= + ~cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF); + chip->professional_spdif = prof; + return update_flags(chip); +} + diff --git a/sound/pci/echoaudio/midi.c b/sound/pci/echoaudio/midi.c new file mode 100644 index 000000000..4d66163a6 --- /dev/null +++ b/sound/pci/echoaudio/midi.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +/****************************************************************************** + MIDI lowlevel code +******************************************************************************/ + +/* Start and stop Midi input */ +static int enable_midi_input(struct echoaudio *chip, char enable) +{ + dev_dbg(chip->card->dev, "enable_midi_input(%d)\n", enable); + + if (wait_handshake(chip)) + return -EIO; + + if (enable) { + chip->mtc_state = MIDI_IN_STATE_NORMAL; + chip->comm_page->flags |= + cpu_to_le32(DSP_FLAG_MIDI_INPUT); + } else + chip->comm_page->flags &= + ~cpu_to_le32(DSP_FLAG_MIDI_INPUT); + + clear_handshake(chip); + return send_vector(chip, DSP_VC_UPDATE_FLAGS); +} + + + +/* Send a buffer full of MIDI data to the DSP +Returns how many actually written or < 0 on error */ +static int write_midi(struct echoaudio *chip, u8 *data, int bytes) +{ + if (snd_BUG_ON(bytes <= 0 || bytes >= MIDI_OUT_BUFFER_SIZE)) + return -EINVAL; + + if (wait_handshake(chip)) + return -EIO; + + /* HF4 indicates that it is safe to write MIDI output data */ + if (! (get_dsp_register(chip, CHI32_STATUS_REG) & CHI32_STATUS_REG_HF4)) + return 0; + + chip->comm_page->midi_output[0] = bytes; + memcpy(&chip->comm_page->midi_output[1], data, bytes); + chip->comm_page->midi_out_free_count = 0; + clear_handshake(chip); + send_vector(chip, DSP_VC_MIDI_WRITE); + dev_dbg(chip->card->dev, "write_midi: %d\n", bytes); + return bytes; +} + + + +/* Run the state machine for MIDI input data +MIDI time code sync isn't supported by this code right now, but you still need +this state machine to parse the incoming MIDI data stream. Every time the DSP +sees a 0xF1 byte come in, it adds the DSP sample position to the MIDI data +stream. The DSP sample position is represented as a 32 bit unsigned value, +with the high 16 bits first, followed by the low 16 bits. Since these aren't +real MIDI bytes, the following logic is needed to skip them. */ +static inline int mtc_process_data(struct echoaudio *chip, short midi_byte) +{ + switch (chip->mtc_state) { + case MIDI_IN_STATE_NORMAL: + if (midi_byte == 0xF1) + chip->mtc_state = MIDI_IN_STATE_TS_HIGH; + break; + case MIDI_IN_STATE_TS_HIGH: + chip->mtc_state = MIDI_IN_STATE_TS_LOW; + return MIDI_IN_SKIP_DATA; + break; + case MIDI_IN_STATE_TS_LOW: + chip->mtc_state = MIDI_IN_STATE_F1_DATA; + return MIDI_IN_SKIP_DATA; + break; + case MIDI_IN_STATE_F1_DATA: + chip->mtc_state = MIDI_IN_STATE_NORMAL; + break; + } + return 0; +} + + + +/* This function is called from the IRQ handler and it reads the midi data +from the DSP's buffer. It returns the number of bytes received. */ +static int midi_service_irq(struct echoaudio *chip) +{ + short int count, midi_byte, i, received; + + /* The count is at index 0, followed by actual data */ + count = le16_to_cpu(chip->comm_page->midi_input[0]); + + if (snd_BUG_ON(count >= MIDI_IN_BUFFER_SIZE)) + return 0; + + /* Get the MIDI data from the comm page */ + received = 0; + for (i = 1; i <= count; i++) { + /* Get the MIDI byte */ + midi_byte = le16_to_cpu(chip->comm_page->midi_input[i]); + + /* Parse the incoming MIDI stream. The incoming MIDI data + consists of MIDI bytes and timestamps for the MIDI time code + 0xF1 bytes. mtc_process_data() is a little state machine that + parses the stream. If you get MIDI_IN_SKIP_DATA back, then + this is a timestamp byte, not a MIDI byte, so don't store it + in the MIDI input buffer. */ + if (mtc_process_data(chip, midi_byte) == MIDI_IN_SKIP_DATA) + continue; + + chip->midi_buffer[received++] = (u8)midi_byte; + } + + return received; +} + + + + +/****************************************************************************** + MIDI interface +******************************************************************************/ + +static int snd_echo_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct echoaudio *chip = substream->rmidi->private_data; + + chip->midi_in = substream; + return 0; +} + + + +static void snd_echo_midi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct echoaudio *chip = substream->rmidi->private_data; + + if (up != chip->midi_input_enabled) { + guard(spinlock_irq)(&chip->lock); + enable_midi_input(chip, up); + chip->midi_input_enabled = up; + } +} + + + +static int snd_echo_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct echoaudio *chip = substream->rmidi->private_data; + + chip->midi_in = NULL; + return 0; +} + + + +static int snd_echo_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct echoaudio *chip = substream->rmidi->private_data; + + chip->tinuse = 0; + chip->midi_full = 0; + chip->midi_out = substream; + return 0; +} + + + +static void snd_echo_midi_output_write(struct timer_list *t) +{ + struct echoaudio *chip = timer_container_of(chip, t, timer); + int bytes, sent, time; + unsigned char buf[MIDI_OUT_BUFFER_SIZE - 1]; + + /* No interrupts are involved: we have to check at regular intervals + if the card's output buffer has room for new data. */ + sent = 0; + guard(spinlock_irqsave)(&chip->lock); + chip->midi_full = 0; + if (!snd_rawmidi_transmit_empty(chip->midi_out)) { + bytes = snd_rawmidi_transmit_peek(chip->midi_out, buf, + MIDI_OUT_BUFFER_SIZE - 1); + dev_dbg(chip->card->dev, "Try to send %d bytes...\n", bytes); + sent = write_midi(chip, buf, bytes); + if (sent < 0) { + dev_err(chip->card->dev, + "write_midi() error %d\n", sent); + /* retry later */ + sent = 9000; + chip->midi_full = 1; + } else if (sent > 0) { + dev_dbg(chip->card->dev, "%d bytes sent\n", sent); + snd_rawmidi_transmit_ack(chip->midi_out, sent); + } else { + /* Buffer is full. DSP's internal buffer is 64 (128 ?) + bytes long. Let's wait until half of them are sent */ + dev_dbg(chip->card->dev, "Full\n"); + sent = 32; + chip->midi_full = 1; + } + } + + /* We restart the timer only if there is some data left to send */ + if (!snd_rawmidi_transmit_empty(chip->midi_out) && chip->tinuse) { + /* The timer will expire slightly after the data has been + sent */ + time = (sent << 3) / 25 + 1; /* 8/25=0.32ms to send a byte */ + mod_timer(&chip->timer, jiffies + (time * HZ + 999) / 1000); + dev_dbg(chip->card->dev, + "Timer armed(%d)\n", ((time * HZ + 999) / 1000)); + } +} + + + +static void snd_echo_midi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct echoaudio *chip = substream->rmidi->private_data; + bool remove_timer = false; + + dev_dbg(chip->card->dev, "snd_echo_midi_output_trigger(%d)\n", up); + scoped_guard(spinlock_irq, &chip->lock) { + if (up) { + if (!chip->tinuse) { + timer_setup(&chip->timer, snd_echo_midi_output_write, + 0); + chip->tinuse = 1; + } + } else { + if (chip->tinuse) { + chip->tinuse = 0; + remove_timer = true; + } + } + } + + if (remove_timer) { + timer_delete_sync(&chip->timer); + dev_dbg(chip->card->dev, "Timer removed\n"); + return; + } + + if (up && !chip->midi_full) + snd_echo_midi_output_write(&chip->timer); +} + + + +static int snd_echo_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct echoaudio *chip = substream->rmidi->private_data; + + chip->midi_out = NULL; + return 0; +} + + + +static const struct snd_rawmidi_ops snd_echo_midi_input = { + .open = snd_echo_midi_input_open, + .close = snd_echo_midi_input_close, + .trigger = snd_echo_midi_input_trigger, +}; + +static const struct snd_rawmidi_ops snd_echo_midi_output = { + .open = snd_echo_midi_output_open, + .close = snd_echo_midi_output_close, + .trigger = snd_echo_midi_output_trigger, +}; + + + +/* <--snd_echo_probe() */ +static int snd_echo_midi_create(struct snd_card *card, + struct echoaudio *chip) +{ + int err; + + err = snd_rawmidi_new(card, card->shortname, 0, 1, 1, &chip->rmidi); + if (err < 0) + return err; + + strscpy(chip->rmidi->name, card->shortname); + chip->rmidi->private_data = chip; + + snd_rawmidi_set_ops(chip->rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_echo_midi_input); + snd_rawmidi_set_ops(chip->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_echo_midi_output); + + chip->rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + return 0; +} diff --git a/sound/pci/echoaudio/mona.c b/sound/pci/echoaudio/mona.c new file mode 100644 index 000000000..4f999324f --- /dev/null +++ b/sound/pci/echoaudio/mona.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + */ + +#define ECHO24_FAMILY +#define ECHOCARD_MONA +#define ECHOCARD_NAME "Mona" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE +#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT 6 +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 6 */ +#define PX_DIGITAL_OUT 6 /* 8 */ +#define PX_ANALOG_IN 14 /* 4 */ +#define PX_DIGITAL_IN 18 /* 8 */ +#define PX_NUM 26 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 6 */ +#define BX_DIGITAL_OUT 6 /* 8 */ +#define BX_ANALOG_IN 14 /* 4 */ +#define BX_DIGITAL_IN 18 /* 8 */ +#define BX_NUM 26 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/mona_301_dsp.fw"); +MODULE_FIRMWARE("ea/mona_361_dsp.fw"); +MODULE_FIRMWARE("ea/mona_301_1_asic_48.fw"); +MODULE_FIRMWARE("ea/mona_301_1_asic_96.fw"); +MODULE_FIRMWARE("ea/mona_361_1_asic_48.fw"); +MODULE_FIRMWARE("ea/mona_361_1_asic_96.fw"); +MODULE_FIRMWARE("ea/mona_2_asic.fw"); + +#define FW_361_LOADER 0 +#define FW_MONA_301_DSP 1 +#define FW_MONA_361_DSP 2 +#define FW_MONA_301_1_ASIC48 3 +#define FW_MONA_301_1_ASIC96 4 +#define FW_MONA_361_1_ASIC48 5 +#define FW_MONA_361_1_ASIC96 6 +#define FW_MONA_2_ASIC 7 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "mona_301_dsp.fw"}, + {0, "mona_361_dsp.fw"}, + {0, "mona_301_1_asic_48.fw"}, + {0, "mona_301_1_asic_96.fw"}, + {0, "mona_361_1_asic_48.fw"}, + {0, "mona_361_1_asic_96.fw"}, + {0, "mona_2_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + /* DSP 56301 Mona rev.0 */ + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0070) }, + /* DSP 56301 Mona rev.1 */ + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0071) }, + /* DSP 56301 Mona rev.2 */ + { PCI_DEVICE_SUB(0x1057, 0x1801, 0xECC0, 0x0072) }, + /* DSP 56361 Mona rev.0 */ + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0070) }, + /* DSP 56361 Mona rev.1 */ + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0071) }, + /* DSP 56361 Mona rev.2 */ + { PCI_DEVICE_SUB(0x1057, 0x3410, 0xECC0, 0x0072) }, + { } +}; + +static const struct snd_pcm_hardware pcm_hardware_skel = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S32_BE, + .rates = SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 262144, + .period_bytes_min = 32, + .period_bytes_max = 131072, + .periods_min = 2, + .periods_max = 220, + /* One page (4k) contains 512 instructions. I don't know if the hw + supports lists longer than this. In this case periods_max=220 is a + safe limit to make sure the list never exceeds 512 instructions. */ +}; + + +#include "mona_dsp.c" +#include "echoaudio_dsp.c" +#include "echoaudio_gml.c" +#include "echoaudio.c" diff --git a/sound/pci/echoaudio/mona_dsp.c b/sound/pci/echoaudio/mona_dsp.c new file mode 100644 index 000000000..6089152eb --- /dev/null +++ b/sound/pci/echoaudio/mona_dsp.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + + Copyright Echo Digital Audio Corporation (c) 1998 - 2004 + All rights reserved + www.echoaudio.com + + This file is part of Echo Digital Audio's generic driver library. + ************************************************************************* + + Translation from C++ and adaptation for use in ALSA-Driver + were made by Giuliano Pochini + +****************************************************************************/ + + +static int write_control_reg(struct echoaudio *chip, u32 value, char force); +static int set_input_clock(struct echoaudio *chip, u16 clock); +static int set_professional_spdif(struct echoaudio *chip, char prof); +static int set_digital_mode(struct echoaudio *chip, u8 mode); +static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic); +static int check_asic_status(struct echoaudio *chip); + + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + if (snd_BUG_ON((subdevice_id & 0xfff0) != MONA)) + return -ENODEV; + + err = init_dsp_comm_page(chip); + if (err) { + dev_err(chip->card->dev, + "init_hw - could not initialize DSP comm page\n"); + return err; + } + + chip->device_id = device_id; + chip->subdevice_id = subdevice_id; + chip->bad_board = true; + chip->input_clock_types = + ECHO_CLOCK_BIT_INTERNAL | ECHO_CLOCK_BIT_SPDIF | + ECHO_CLOCK_BIT_WORD | ECHO_CLOCK_BIT_ADAT; + chip->digital_modes = + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA | + ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL | + ECHOCAPS_HAS_DIGITAL_MODE_ADAT; + + /* Mona comes in both '301 and '361 flavors */ + if (chip->device_id == DEVICE_ID_56361) + chip->dsp_code_to_load = FW_MONA_361_DSP; + else + chip->dsp_code_to_load = FW_MONA_301_DSP; + + err = load_firmware(chip); + if (err < 0) + return err; + chip->bad_board = false; + + return err; +} + + + +static int set_mixer_defaults(struct echoaudio *chip) +{ + chip->digital_mode = DIGITAL_MODE_SPDIF_RCA; + chip->professional_spdif = false; + chip->digital_in_automute = true; + return init_line_levels(chip); +} + + + +static u32 detect_input_clocks(const struct echoaudio *chip) +{ + u32 clocks_from_dsp, clock_bits; + + /* Map the DSP clock detect bits to the generic driver clock + detect bits */ + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + clock_bits = ECHO_CLOCK_BIT_INTERNAL; + + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF) + clock_bits |= ECHO_CLOCK_BIT_SPDIF; + + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_ADAT) + clock_bits |= ECHO_CLOCK_BIT_ADAT; + + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_WORD) + clock_bits |= ECHO_CLOCK_BIT_WORD; + + return clock_bits; +} + + + +/* Mona has an ASIC on the PCI card and another ASIC in the external box; +both need to be loaded. */ +static int load_asic(struct echoaudio *chip) +{ + u32 control_reg; + int err; + short asic; + + if (chip->asic_loaded) + return 0; + + mdelay(10); + + if (chip->device_id == DEVICE_ID_56361) + asic = FW_MONA_361_1_ASIC48; + else + asic = FW_MONA_301_1_ASIC48; + + err = load_asic_generic(chip, DSP_FNC_LOAD_MONA_PCI_CARD_ASIC, asic); + if (err < 0) + return err; + + chip->asic_code = asic; + mdelay(10); + + /* Do the external one */ + err = load_asic_generic(chip, DSP_FNC_LOAD_MONA_EXTERNAL_ASIC, + FW_MONA_2_ASIC); + if (err < 0) + return err; + + mdelay(10); + err = check_asic_status(chip); + + /* Set up the control register if the load succeeded - + 48 kHz, internal clock, S/PDIF RCA mode */ + if (!err) { + control_reg = GML_CONVERTER_ENABLE | GML_48KHZ; + err = write_control_reg(chip, control_reg, true); + } + + return err; +} + + + +/* Depending on what digital mode you want, Mona needs different ASICs +loaded. This function checks the ASIC needed for the new mode and sees +if it matches the one already loaded. */ +static int switch_asic(struct echoaudio *chip, char double_speed) +{ + int err; + short asic; + + /* Check the clock detect bits to see if this is + a single-speed clock or a double-speed clock; load + a new ASIC if necessary. */ + if (chip->device_id == DEVICE_ID_56361) { + if (double_speed) + asic = FW_MONA_361_1_ASIC96; + else + asic = FW_MONA_361_1_ASIC48; + } else { + if (double_speed) + asic = FW_MONA_301_1_ASIC96; + else + asic = FW_MONA_301_1_ASIC48; + } + + if (asic != chip->asic_code) { + /* Load the desired ASIC */ + err = load_asic_generic(chip, DSP_FNC_LOAD_MONA_PCI_CARD_ASIC, + asic); + if (err < 0) + return err; + chip->asic_code = asic; + } + + return 0; +} + + + +static int set_sample_rate(struct echoaudio *chip, u32 rate) +{ + u32 control_reg, clock; + short asic; + char force_write; + + /* Only set the clock for internal mode. */ + if (chip->input_clock != ECHO_CLOCK_INTERNAL) { + dev_dbg(chip->card->dev, + "Cannot set sample rate - clock not set to CLK_CLOCKININTERNAL\n"); + /* Save the rate anyhow */ + chip->comm_page->sample_rate = cpu_to_le32(rate); + chip->sample_rate = rate; + return 0; + } + + /* Now, check to see if the required ASIC is loaded */ + if (rate >= 88200) { + if (chip->digital_mode == DIGITAL_MODE_ADAT) + return -EINVAL; + if (chip->device_id == DEVICE_ID_56361) + asic = FW_MONA_361_1_ASIC96; + else + asic = FW_MONA_301_1_ASIC96; + } else { + if (chip->device_id == DEVICE_ID_56361) + asic = FW_MONA_361_1_ASIC48; + else + asic = FW_MONA_301_1_ASIC48; + } + + force_write = 0; + if (asic != chip->asic_code) { + int err; + /* Load the desired ASIC (load_asic_generic() can sleep) */ + spin_unlock_irq(&chip->lock); + err = load_asic_generic(chip, DSP_FNC_LOAD_MONA_PCI_CARD_ASIC, + asic); + spin_lock_irq(&chip->lock); + + if (err < 0) + return err; + chip->asic_code = asic; + force_write = 1; + } + + /* Compute the new control register value */ + clock = 0; + control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg &= GML_CLOCK_CLEAR_MASK; + control_reg &= GML_SPDIF_RATE_CLEAR_MASK; + + switch (rate) { + case 96000: + clock = GML_96KHZ; + break; + case 88200: + clock = GML_88KHZ; + break; + case 48000: + clock = GML_48KHZ | GML_SPDIF_SAMPLE_RATE1; + break; + case 44100: + clock = GML_44KHZ; + /* Professional mode */ + if (control_reg & GML_SPDIF_PRO_MODE) + clock |= GML_SPDIF_SAMPLE_RATE0; + break; + case 32000: + clock = GML_32KHZ | GML_SPDIF_SAMPLE_RATE0 | + GML_SPDIF_SAMPLE_RATE1; + break; + case 22050: + clock = GML_22KHZ; + break; + case 16000: + clock = GML_16KHZ; + break; + case 11025: + clock = GML_11KHZ; + break; + case 8000: + clock = GML_8KHZ; + break; + default: + dev_err(chip->card->dev, + "set_sample_rate: %d invalid!\n", rate); + return -EINVAL; + } + + control_reg |= clock; + + chip->comm_page->sample_rate = cpu_to_le32(rate); /* ignored by the DSP */ + chip->sample_rate = rate; + dev_dbg(chip->card->dev, + "set_sample_rate: %d clock %d\n", rate, clock); + + return write_control_reg(chip, control_reg, force_write); +} + + + +static int set_input_clock(struct echoaudio *chip, u16 clock) +{ + u32 control_reg, clocks_from_dsp; + int err; + + /* Mask off the clock select bits */ + control_reg = le32_to_cpu(chip->comm_page->control_register) & + GML_CLOCK_CLEAR_MASK; + clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks); + + switch (clock) { + case ECHO_CLOCK_INTERNAL: + chip->input_clock = ECHO_CLOCK_INTERNAL; + return set_sample_rate(chip, chip->sample_rate); + case ECHO_CLOCK_SPDIF: + if (chip->digital_mode == DIGITAL_MODE_ADAT) + return -EAGAIN; + spin_unlock_irq(&chip->lock); + err = switch_asic(chip, clocks_from_dsp & + GML_CLOCK_DETECT_BIT_SPDIF96); + spin_lock_irq(&chip->lock); + if (err < 0) + return err; + control_reg |= GML_SPDIF_CLOCK; + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF96) + control_reg |= GML_DOUBLE_SPEED_MODE; + else + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + case ECHO_CLOCK_WORD: + spin_unlock_irq(&chip->lock); + err = switch_asic(chip, clocks_from_dsp & + GML_CLOCK_DETECT_BIT_WORD96); + spin_lock_irq(&chip->lock); + if (err < 0) + return err; + control_reg |= GML_WORD_CLOCK; + if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_WORD96) + control_reg |= GML_DOUBLE_SPEED_MODE; + else + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + case ECHO_CLOCK_ADAT: + dev_dbg(chip->card->dev, "Set Mona clock to ADAT\n"); + if (chip->digital_mode != DIGITAL_MODE_ADAT) + return -EAGAIN; + control_reg |= GML_ADAT_CLOCK; + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + default: + dev_err(chip->card->dev, + "Input clock 0x%x not supported for Mona\n", clock); + return -EINVAL; + } + + chip->input_clock = clock; + return write_control_reg(chip, control_reg, true); +} + + + +static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode) +{ + u32 control_reg; + int err, incompatible_clock; + + /* Set clock to "internal" if it's not compatible with the new mode */ + incompatible_clock = false; + switch (mode) { + case DIGITAL_MODE_SPDIF_OPTICAL: + case DIGITAL_MODE_SPDIF_RCA: + if (chip->input_clock == ECHO_CLOCK_ADAT) + incompatible_clock = true; + break; + case DIGITAL_MODE_ADAT: + if (chip->input_clock == ECHO_CLOCK_SPDIF) + incompatible_clock = true; + break; + default: + dev_err(chip->card->dev, + "Digital mode not supported: %d\n", mode); + return -EINVAL; + } + + guard(spinlock_irq)(&chip->lock); + + if (incompatible_clock) { /* Switch to 48KHz, internal */ + chip->sample_rate = 48000; + set_input_clock(chip, ECHO_CLOCK_INTERNAL); + } + + /* Clear the current digital mode */ + control_reg = le32_to_cpu(chip->comm_page->control_register); + control_reg &= GML_DIGITAL_MODE_CLEAR_MASK; + + /* Tweak the control reg */ + switch (mode) { + case DIGITAL_MODE_SPDIF_OPTICAL: + control_reg |= GML_SPDIF_OPTICAL_MODE; + break; + case DIGITAL_MODE_SPDIF_RCA: + /* GML_SPDIF_OPTICAL_MODE bit cleared */ + break; + case DIGITAL_MODE_ADAT: + /* If the current ASIC is the 96KHz ASIC, switch the ASIC + and set to 48 KHz */ + if (chip->asic_code == FW_MONA_361_1_ASIC96 || + chip->asic_code == FW_MONA_301_1_ASIC96) { + set_sample_rate(chip, 48000); + } + control_reg |= GML_ADAT_MODE; + control_reg &= ~GML_DOUBLE_SPEED_MODE; + break; + } + + err = write_control_reg(chip, control_reg, false); + if (err < 0) + return err; + chip->digital_mode = mode; + + dev_dbg(chip->card->dev, "set_digital_mode to %d\n", mode); + return incompatible_clock; +} diff --git a/sound/pci/emu10k1/Makefile b/sound/pci/emu10k1/Makefile new file mode 100644 index 000000000..1f325abcb --- /dev/null +++ b/sound/pci/emu10k1/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-emu10k1-y := emu10k1.o emu10k1_main.o \ + irq.o memory.o voice.o emumpu401.o emupcm.o io.o \ + emumixer.o emufx.o timer.o p16v.o +snd-emu10k1-$(CONFIG_SND_PROC_FS) += emuproc.o +snd-emu10k1-synth-y := emu10k1_synth.o emu10k1_callback.o emu10k1_patch.o +snd-emu10k1x-y := emu10k1x.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_EMU10K1) += snd-emu10k1.o +obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-emu10k1-synth.o +obj-$(CONFIG_SND_EMU10K1X) += snd-emu10k1x.o diff --git a/sound/pci/emu10k1/emu10k1.c b/sound/pci/emu10k1/emu10k1.c new file mode 100644 index 000000000..3b21bd288 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The driver for the EMU10K1 (SB Live!) based soundcards + * Copyright (c) by Jaroslav Kysela + * James Courtier-Dutton + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("EMU10K1"); +MODULE_LICENSE("GPL"); + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) +#define ENABLE_SYNTH +#include +#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_PNP; /* Enable this card */ +static int extin[SNDRV_CARDS]; +static int extout[SNDRV_CARDS]; +static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4}; +static int max_synth_voices[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 64}; +static int max_buffer_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 128}; +static bool enable_ir[SNDRV_CARDS]; +static uint subsystem[SNDRV_CARDS]; /* Force card subsystem model */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the EMU10K1 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the EMU10K1 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable the EMU10K1 soundcard."); +module_param_array(extin, int, NULL, 0444); +MODULE_PARM_DESC(extin, "Available external inputs for FX8010. Zero=default."); +module_param_array(extout, int, NULL, 0444); +MODULE_PARM_DESC(extout, "Available external outputs for FX8010. Zero=default."); +module_param_array(seq_ports, int, NULL, 0444); +MODULE_PARM_DESC(seq_ports, "Allocated sequencer ports for internal synthesizer."); +module_param_array(max_synth_voices, int, NULL, 0444); +MODULE_PARM_DESC(max_synth_voices, "Maximum number of voices for WaveTable."); +module_param_array(max_buffer_size, int, NULL, 0444); +MODULE_PARM_DESC(max_buffer_size, "Maximum sample buffer size in MB."); +module_param_array(enable_ir, bool, NULL, 0444); +MODULE_PARM_DESC(enable_ir, "Enable IR."); +module_param_array(subsystem, uint, NULL, 0444); +MODULE_PARM_DESC(subsystem, "Force card subsystem model."); +/* + * Class 0401: 1102:0008 (rev 00) Subsystem: 1102:1001 -> Audigy2 Value Model:SB0400 + */ +static const struct pci_device_id snd_emu10k1_ids[] = { + { PCI_VDEVICE(CREATIVE, 0x0002), .driver_data = 0 }, /* EMU10K1 */ + { PCI_VDEVICE(CREATIVE, 0x0004), .driver_data = 1 }, /* Audigy */ + { PCI_VDEVICE(CREATIVE, 0x0008), .driver_data = 1 }, /* Audigy 2 Value SB0400 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_emu10k1_ids); + +static int snd_card_emu10k1_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_emu10k1 *emu; +#ifdef ENABLE_SYNTH + struct snd_seq_device *wave = NULL; +#endif + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*emu), &card); + if (err < 0) + return err; + emu = card->private_data; + + if (max_buffer_size[dev] < 32) + max_buffer_size[dev] = 32; + else if (max_buffer_size[dev] > 1024) + max_buffer_size[dev] = 1024; + err = snd_emu10k1_create(card, pci, extin[dev], extout[dev], + (long)max_buffer_size[dev] * 1024 * 1024, + enable_ir[dev], subsystem[dev]); + if (err < 0) + return err; + err = snd_emu10k1_pcm(emu, 0); + if (err < 0) + return err; + if (emu->card_capabilities->ac97_chip) { + err = snd_emu10k1_pcm_mic(emu, 1); + if (err < 0) + return err; + } + err = snd_emu10k1_pcm_efx(emu, 2); + if (err < 0) + return err; + /* This stores the periods table. */ + if (emu->card_capabilities->ca0151_chip) { /* P16V */ + emu->p16v_buffer = + snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, 1024); + if (!emu->p16v_buffer) + return -ENOMEM; + } + + err = snd_emu10k1_mixer(emu, 0, 3); + if (err < 0) + return err; + + err = snd_emu10k1_timer(emu, 0); + if (err < 0) + return err; + + err = snd_emu10k1_pcm_multi(emu, 3); + if (err < 0) + return err; + if (emu->card_capabilities->ca0151_chip) { /* P16V */ + err = snd_p16v_pcm(emu, 4); + if (err < 0) + return err; + } + if (emu->audigy) { + err = snd_emu10k1_audigy_midi(emu); + if (err < 0) + return err; + } else { + err = snd_emu10k1_midi(emu); + if (err < 0) + return err; + } + err = snd_emu10k1_fx8010_new(emu, 0); + if (err < 0) + return err; +#ifdef ENABLE_SYNTH + if (snd_seq_device_new(card, 1, SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH, + sizeof(struct snd_emu10k1_synth_arg), &wave) < 0 || + wave == NULL) { + dev_warn(emu->card->dev, + "can't initialize Emu10k1 wavetable synth\n"); + } else { + struct snd_emu10k1_synth_arg *arg; + arg = SNDRV_SEQ_DEVICE_ARGPTR(wave); + strscpy(wave->name, "Emu-10k1 Synth"); + arg->hwptr = emu; + arg->index = 1; + arg->seq_ports = seq_ports[dev]; + arg->max_voices = max_synth_voices[dev]; + } +#endif + + strscpy(card->driver, emu->card_capabilities->driver, + sizeof(card->driver)); + strscpy(card->shortname, emu->card_capabilities->name, + sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s (rev.%d, serial:0x%x) at 0x%lx, irq %i", + card->shortname, emu->revision, emu->serial, emu->port, emu->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int snd_emu10k1_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_emu10k1 *emu = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + emu->suspend = 1; + + cancel_work_sync(&emu->emu1010.work); + + snd_ac97_suspend(emu->ac97); + + snd_emu10k1_efx_suspend(emu); + snd_emu10k1_suspend_regs(emu); + if (emu->card_capabilities->ca0151_chip) + snd_p16v_suspend(emu); + + snd_emu10k1_done(emu); + return 0; +} + +static int snd_emu10k1_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_emu10k1 *emu = card->private_data; + + snd_emu10k1_resume_init(emu); + snd_emu10k1_efx_resume(emu); + snd_ac97_resume(emu->ac97); + snd_emu10k1_resume_regs(emu); + + if (emu->card_capabilities->ca0151_chip) + snd_p16v_resume(emu); + + emu->suspend = 0; + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_emu10k1_pm, snd_emu10k1_suspend, snd_emu10k1_resume); +#define SND_EMU10K1_PM_OPS &snd_emu10k1_pm +#else +#define SND_EMU10K1_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct pci_driver emu10k1_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_emu10k1_ids, + .probe = snd_card_emu10k1_probe, + .driver = { + .pm = SND_EMU10K1_PM_OPS, + }, +}; + +module_pci_driver(emu10k1_driver); diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c new file mode 100644 index 000000000..ef26e4d3e --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * synth callback routines for Emu10k1 + * + * Copyright (C) 2000 Takashi Iwai + */ + +#include +#include "emu10k1_synth_local.h" +#include + +/* voice status */ +enum { + V_FREE=0, V_OFF, V_RELEASED, V_PLAYING, V_END +}; + +/* Keeps track of what we are finding */ +struct best_voice { + unsigned int time; + int voice; +}; + +/* + * prototypes + */ +static void lookup_voices(struct snd_emux *emux, struct snd_emu10k1 *hw, + struct best_voice *best, int active_only); +static struct snd_emux_voice *get_voice(struct snd_emux *emux, + 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 terminate_voice(struct snd_emux_voice *vp); +static void free_voice(struct snd_emux_voice *vp); +static u32 make_fmmod(struct snd_emux_voice *vp); +static u32 make_fm2frq2(struct snd_emux_voice *vp); +static int get_pitch_shift(struct snd_emux *emu); + +/* + * 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 emu10k1_ops = { + .owner = THIS_MODULE, + .get_voice = get_voice, + .prepare = start_voice, + .trigger = trigger_voice, + .release = release_voice, + .update = update_voice, + .terminate = terminate_voice, + .free_voice = free_voice, + .sample_new = snd_emu10k1_sample_new, + .sample_free = snd_emu10k1_sample_free, + .get_pitch_shift = get_pitch_shift, +}; + +void +snd_emu10k1_ops_setup(struct snd_emux *emux) +{ + emux->ops = emu10k1_ops; +} + + +/* + * get more voice for pcm + * + * terminate most inactive voice and give it as a pcm voice. + * + * voice_lock is already held. + */ +int +snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw) +{ + struct snd_emux *emu; + struct snd_emux_voice *vp; + struct best_voice best[V_END]; + int i; + + emu = hw->synth; + + lookup_voices(emu, hw, best, 1); /* no OFF voices */ + for (i = 0; i < V_END; i++) { + if (best[i].voice >= 0) { + int ch; + vp = &emu->voices[best[i].voice]; + ch = vp->ch; + if (ch < 0) { + /* + dev_warn(emu->card->dev, + "synth_get_voice: ch < 0 (%d) ??", i); + */ + continue; + } + vp->emu->num_voices--; + vp->ch = -1; + vp->state = SNDRV_EMUX_ST_OFF; + return ch; + } + } + + /* not found */ + return -ENOMEM; +} + + +/* + * turn off the voice (not terminated) + */ +static void +release_voice(struct snd_emux_voice *vp) +{ + struct snd_emu10k1 *hw; + + hw = vp->hw; + snd_emu10k1_ptr_write_multiple(hw, vp->ch, + DCYSUSM, (unsigned char)vp->reg.parm.modrelease | DCYSUSM_PHASE1_MASK, + DCYSUSV, (unsigned char)vp->reg.parm.volrelease | DCYSUSV_PHASE1_MASK | DCYSUSV_CHANNELENABLE_MASK, + REGLIST_END); +} + + +/* + * terminate the voice + */ +static void +terminate_voice(struct snd_emux_voice *vp) +{ + struct snd_emu10k1 *hw; + + if (snd_BUG_ON(!vp)) + return; + hw = vp->hw; + snd_emu10k1_ptr_write_multiple(hw, vp->ch, + DCYSUSV, 0, + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + PTRX, 0, + CPF, 0, + REGLIST_END); + if (vp->block) { + struct snd_emu10k1_memblk *emem; + emem = (struct snd_emu10k1_memblk *)vp->block; + if (emem->map_locked > 0) + emem->map_locked--; + } +} + +/* + * release the voice to system + */ +static void +free_voice(struct snd_emux_voice *vp) +{ + struct snd_emu10k1 *hw; + + hw = vp->hw; + /* FIXME: emu10k1_synth is broken. */ + /* This can get called with hw == 0 */ + /* Problem apparent on plug, unplug then plug */ + /* on the Audigy 2 ZS Notebook. */ + if (hw && (vp->ch >= 0)) { + snd_emu10k1_voice_free(hw, &hw->voices[vp->ch]); + vp->emu->num_voices--; + vp->ch = -1; + } +} + + +/* + * update registers + */ +static void +update_voice(struct snd_emux_voice *vp, int update) +{ + struct snd_emu10k1 *hw; + + hw = vp->hw; + if (update & SNDRV_EMUX_UPDATE_VOLUME) + snd_emu10k1_ptr_write(hw, IFATN_ATTENUATION, vp->ch, vp->avol); + if (update & SNDRV_EMUX_UPDATE_PITCH) + snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch); + if (update & SNDRV_EMUX_UPDATE_PAN) { + snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_A, vp->ch, vp->apan); + snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_B, vp->ch, vp->aaux); + } + if (update & SNDRV_EMUX_UPDATE_FMMOD) + snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, make_fmmod(vp)); + if (update & SNDRV_EMUX_UPDATE_TREMFREQ) + snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq); + if (update & SNDRV_EMUX_UPDATE_FM2FRQ2) + snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, make_fm2frq2(vp)); + if (update & SNDRV_EMUX_UPDATE_Q) + snd_emu10k1_ptr_write(hw, CCCA_RESONANCE, vp->ch, vp->reg.parm.filterQ); +} + + +/* + * look up voice table - get the best voice in order of preference + */ +/* spinlock held! */ +static void +lookup_voices(struct snd_emux *emu, struct snd_emu10k1 *hw, + struct best_voice *best, int active_only) +{ + struct snd_emux_voice *vp; + struct best_voice *bp; + int i; + + for (i = 0; i < V_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. + * NOTE: could also look at volume and pick the quietest one. + */ + for (i = 0; i < emu->max_voices; i++) { + int state, val; + + vp = &emu->voices[i]; + state = vp->state; + if (state == SNDRV_EMUX_ST_OFF) { + if (vp->ch < 0) { + if (active_only) + continue; + bp = best + V_FREE; + } else + bp = best + V_OFF; + } + else if (state == SNDRV_EMUX_ST_RELEASED || + state == SNDRV_EMUX_ST_PENDING) { + bp = best + V_RELEASED; +#if 1 + val = snd_emu10k1_ptr_read(hw, CVCF_CURRENTVOL, vp->ch); + if (! val) + bp = best + V_OFF; +#endif + } + else if (state == SNDRV_EMUX_ST_STANDBY) + continue; + else if (state & SNDRV_EMUX_ST_ON) + bp = best + V_PLAYING; + else + continue; + + /* check if sample is finished playing (non-looping only) */ + if (bp != best + V_OFF && bp != best + V_FREE && + (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) { + val = snd_emu10k1_ptr_read(hw, CCCA_CURRADDR, vp->ch) - 64 + 3; + if (val >= vp->reg.loopstart) + bp = best + V_OFF; + } + + if (vp->time < bp->time) { + bp->time = vp->time; + bp->voice = i; + } + } +} + +/* + * get an empty voice + * + * emu->voice_lock is already held. + */ +static struct snd_emux_voice * +get_voice(struct snd_emux *emu, struct snd_emux_port *port) +{ + struct snd_emu10k1 *hw; + struct snd_emux_voice *vp; + struct best_voice best[V_END]; + int i; + + hw = emu->hw; + + lookup_voices(emu, hw, best, 0); + for (i = 0; i < V_END; i++) { + if (best[i].voice >= 0) { + vp = &emu->voices[best[i].voice]; + if (vp->ch < 0) { + /* allocate a voice */ + struct snd_emu10k1_voice *hwvoice; + if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, 1, NULL, &hwvoice) < 0) + continue; + vp->ch = hwvoice->number; + emu->num_voices++; + } + return vp; + } + } + + /* not found */ + return NULL; +} + +/* + * prepare envelopes and LFOs + */ +static int +start_voice(struct snd_emux_voice *vp) +{ + unsigned int temp; + int ch; + bool w_16; + u32 psst, dsl, map, ccca, vtarget; + unsigned int addr, mapped_offset; + struct snd_midi_channel *chan; + struct snd_emu10k1 *hw; + struct snd_emu10k1_memblk *emem; + + hw = vp->hw; + ch = vp->ch; + if (snd_BUG_ON(ch < 0)) + return -EINVAL; + chan = vp->chan; + w_16 = !(vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS); + + emem = (struct snd_emu10k1_memblk *)vp->block; + if (emem == NULL) + return -EINVAL; + emem->map_locked++; + if (snd_emu10k1_memblk_map(hw, emem) < 0) { + /* dev_err(hw->card->devK, "emu: cannot map!\n"); */ + return -ENOMEM; + } + mapped_offset = snd_emu10k1_memblk_offset(emem) >> w_16; + vp->reg.start += mapped_offset; + vp->reg.end += mapped_offset; + vp->reg.loopstart += mapped_offset; + vp->reg.loopend += mapped_offset; + + /* set channel routing */ + /* A = left(0), B = right(1), C = reverb(c), D = chorus(d) */ + if (hw->audigy) { + temp = FXBUS_MIDI_LEFT | (FXBUS_MIDI_RIGHT << 8) | + (FXBUS_MIDI_REVERB << 16) | (FXBUS_MIDI_CHORUS << 24); + snd_emu10k1_ptr_write(hw, A_FXRT1, ch, temp); + } else { + temp = (FXBUS_MIDI_LEFT << 16) | (FXBUS_MIDI_RIGHT << 20) | + (FXBUS_MIDI_REVERB << 24) | (FXBUS_MIDI_CHORUS << 28); + snd_emu10k1_ptr_write(hw, FXRT, ch, temp); + } + + temp = vp->reg.parm.reverb; + temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10; + LIMITMAX(temp, 255); + addr = vp->reg.loopstart; + psst = (temp << 24) | addr; + + addr = vp->reg.loopend; + temp = vp->reg.parm.chorus; + temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10; + LIMITMAX(temp, 255); + dsl = (temp << 24) | addr; + + map = (hw->silent_page.addr << hw->address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); + + addr = vp->reg.start + 64 - 3; + temp = vp->reg.parm.filterQ; + ccca = (temp << 28) | addr; + if (vp->apitch < 0xe400) + ccca |= CCCA_INTERPROM_0; + else { + unsigned int shift = (vp->apitch - 0xe000) >> 10; + ccca |= shift << 25; + } + if (!w_16) + ccca |= CCCA_8BITSELECT; + + vtarget = (unsigned int)vp->vtarget << 16; + + snd_emu10k1_ptr_write_multiple(hw, ch, + /* channel to be silent and idle */ + DCYSUSV, 0, + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + PTRX, 0, + CPF, 0, + + /* set pitch offset */ + IP, vp->apitch, + + /* set envelope parameters */ + ENVVAL, vp->reg.parm.moddelay, + ATKHLDM, vp->reg.parm.modatkhld, + DCYSUSM, vp->reg.parm.moddcysus, + ENVVOL, vp->reg.parm.voldelay, + ATKHLDV, vp->reg.parm.volatkhld, + /* decay/sustain parameter for volume envelope is used + for triggerg the voice */ + + /* cutoff and volume */ + IFATN, (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol, + + /* modulation envelope heights */ + PEFE, vp->reg.parm.pefe, + + /* lfo1/2 delay */ + LFOVAL1, vp->reg.parm.lfo1delay, + LFOVAL2, vp->reg.parm.lfo2delay, + + /* lfo1 pitch & cutoff shift */ + FMMOD, make_fmmod(vp), + /* lfo1 volume & freq */ + TREMFRQ, vp->reg.parm.tremfrq, + /* lfo2 pitch & freq */ + FM2FRQ2, make_fm2frq2(vp), + + /* reverb and loop start (reverb 8bit, MSB) */ + PSST, psst, + + /* chorus & loop end (chorus 8bit, MSB) */ + DSL, dsl, + + /* clear filter delay memory */ + Z1, 0, + Z2, 0, + + /* invalidate maps */ + MAPA, map, + MAPB, map, + + /* Q & current address (Q 4bit value, MSB) */ + CCCA, ccca, + + /* cache */ + CCR, REG_VAL_PUT(CCR_CACHEINVALIDSIZE, 64), + + /* reset volume */ + VTFT, vtarget | vp->ftarget, + CVCF, vtarget | CVCF_CURRENTFILTER_MASK, + + REGLIST_END); + + hw->voices[ch].dirty = 1; + return 0; +} + +/* + * Start envelope + */ +static void +trigger_voice(struct snd_emux_voice *vp) +{ + unsigned int ptarget; + struct snd_emu10k1 *hw; + struct snd_emu10k1_memblk *emem; + + hw = vp->hw; + + emem = (struct snd_emu10k1_memblk *)vp->block; + if (! emem || emem->mapped_page < 0) + return; /* not mapped */ + +#if 0 + ptarget = (unsigned int)vp->ptarget << 16; +#else + ptarget = IP_TO_CP(vp->apitch); +#endif + snd_emu10k1_ptr_write_multiple(hw, vp->ch, + /* set pitch target and pan (volume) */ + PTRX, ptarget | (vp->apan << 8) | vp->aaux, + + /* current pitch and fractional address */ + CPF, ptarget, + + /* enable envelope engine */ + DCYSUSV, vp->reg.parm.voldcysus | DCYSUSV_CHANNELENABLE_MASK, + + REGLIST_END); +} + +#define MOD_SENSE 18 + +/* calculate lfo1 modulation height and cutoff register */ +static u32 +make_fmmod(struct snd_emux_voice *vp) +{ + 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); + return ((unsigned char)pitch << 8) | cutoff; +} + +/* calculate set lfo2 pitch & frequency register */ +static u32 +make_fm2frq2(struct snd_emux_voice *vp) +{ + 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); + return ((unsigned char)pitch << 8) | freq; +} + +static int get_pitch_shift(struct snd_emux *emu) +{ + struct snd_emu10k1 *hw = emu->hw; + + return (hw->card_capabilities->emu_model && + hw->emu1010.word_clock == 44100) ? 0 : -501; +} diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c new file mode 100644 index 000000000..b2fe2d164 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -0,0 +1,1792 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * James Courtier-Dutton + * Oswald Buddenhagen + * Creative Labs, Inc. + * + * Routines for control of EMU10K1 chips + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include "p16v.h" +#include "tina2.h" +#include "p17v.h" + + +#define HANA_FILENAME "emu/hana.fw" +#define DOCK_FILENAME "emu/audio_dock.fw" +#define EMU1010B_FILENAME "emu/emu1010b.fw" +#define MICRO_DOCK_FILENAME "emu/micro_dock.fw" +#define EMU0404_FILENAME "emu/emu0404.fw" +#define EMU1010_NOTEBOOK_FILENAME "emu/emu1010_notebook.fw" + +MODULE_FIRMWARE(HANA_FILENAME); +MODULE_FIRMWARE(DOCK_FILENAME); +MODULE_FIRMWARE(EMU1010B_FILENAME); +MODULE_FIRMWARE(MICRO_DOCK_FILENAME); +MODULE_FIRMWARE(EMU0404_FILENAME); +MODULE_FIRMWARE(EMU1010_NOTEBOOK_FILENAME); + + +/************************************************************************* + * EMU10K1 init / done + *************************************************************************/ + +void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) +{ + snd_emu10k1_ptr_write_multiple(emu, ch, + DCYSUSV, 0, + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + PTRX, 0, + CPF, 0, + CCR, 0, + + PSST, 0, + DSL, 0x10, + CCCA, 0, + Z1, 0, + Z2, 0, + FXRT, 0x32100000, + + // The rest is meaningless as long as DCYSUSV_CHANNELENABLE_MASK is zero + DCYSUSM, 0, + ATKHLDV, 0, + ATKHLDM, 0, + IP, 0, + IFATN, IFATN_FILTERCUTOFF_MASK | IFATN_ATTENUATION_MASK, + PEFE, 0, + FMMOD, 0, + TREMFRQ, 24, /* 1 Hz */ + FM2FRQ2, 24, /* 1 Hz */ + LFOVAL2, 0, + LFOVAL1, 0, + ENVVOL, 0, + ENVVAL, 0, + + REGLIST_END); + + /* Audigy extra stuffs */ + if (emu->audigy) { + snd_emu10k1_ptr_write_multiple(emu, ch, + A_CSBA, 0, + A_CSDC, 0, + A_CSFE, 0, + A_CSHG, 0, + A_FXRT1, 0x03020100, + A_FXRT2, 0x07060504, + A_SENDAMOUNTS, 0, + REGLIST_END); + } +} + +static const unsigned int spi_dac_init[] = { + 0x00ff, + 0x02ff, + 0x0400, + 0x0520, + 0x0600, + 0x08ff, + 0x0aff, + 0x0cff, + 0x0eff, + 0x10ff, + 0x1200, + 0x1400, + 0x1480, + 0x1800, + 0x1aff, + 0x1cff, + 0x1e00, + 0x0530, + 0x0602, + 0x0622, + 0x1400, +}; + +static const unsigned int i2c_adc_init[][2] = { + { 0x17, 0x00 }, /* Reset */ + { 0x07, 0x00 }, /* Timeout */ + { 0x0b, 0x22 }, /* Interface control */ + { 0x0c, 0x22 }, /* Master mode control */ + { 0x0d, 0x08 }, /* Powerdown control */ + { 0x0e, 0xcf }, /* Attenuation Left 0x01 = -103dB, 0xff = 24dB */ + { 0x0f, 0xcf }, /* Attenuation Right 0.5dB steps */ + { 0x10, 0x7b }, /* ALC Control 1 */ + { 0x11, 0x00 }, /* ALC Control 2 */ + { 0x12, 0x32 }, /* ALC Control 3 */ + { 0x13, 0x00 }, /* Noise gate control */ + { 0x14, 0xa6 }, /* Limiter control */ + { 0x15, ADC_MUX_2 }, /* ADC Mixer control. Mic for A2ZS Notebook */ +}; + +static int snd_emu10k1_init(struct snd_emu10k1 *emu, int enable_ir) +{ + unsigned int silent_page; + int ch; + u32 tmp; + + /* disable audio and lock cache */ + outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | + HCFG_MUTEBUTTONENABLE, emu->port + HCFG); + + outl(0, emu->port + INTE); + + snd_emu10k1_ptr_write_multiple(emu, 0, + /* reset recording buffers */ + MICBS, ADCBS_BUFSIZE_NONE, + MICBA, 0, + FXBS, ADCBS_BUFSIZE_NONE, + FXBA, 0, + ADCBS, ADCBS_BUFSIZE_NONE, + ADCBA, 0, + + /* disable channel interrupt */ + CLIEL, 0, + CLIEH, 0, + + /* disable stop on loop end */ + SOLEL, 0, + SOLEH, 0, + + REGLIST_END); + + if (emu->audigy) { + /* set SPDIF bypass mode */ + snd_emu10k1_ptr_write(emu, SPBYPASS, 0, SPBYPASS_FORMAT); + /* enable rear left + rear right AC97 slots */ + snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_REAR_RIGHT | + AC97SLOT_REAR_LEFT); + } + + /* init envelope engine */ + for (ch = 0; ch < NUM_G; ch++) + snd_emu10k1_voice_init(emu, ch); + + snd_emu10k1_ptr_write_multiple(emu, 0, + SPCS0, emu->spdif_bits[0], + SPCS1, emu->spdif_bits[1], + SPCS2, emu->spdif_bits[2], + REGLIST_END); + + if (emu->card_capabilities->emu_model) { + } else if (emu->card_capabilities->ca0151_chip) { /* audigy2 */ + /* Hacks for Alice3 to work independent of haP16V driver */ + /* Setup SRCMulti_I2S SamplingRate */ + snd_emu10k1_ptr_write(emu, A_I2S_CAPTURE_RATE, 0, A_I2S_CAPTURE_96000); + + /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */ + snd_emu10k1_ptr20_write(emu, SRCSel, 0, 0x14); + /* Setup SRCMulti Input Audio Enable */ + /* Use 0xFFFFFFFF to enable P16V sounds. */ + snd_emu10k1_ptr20_write(emu, SRCMULTI_ENABLE, 0, 0xFFFFFFFF); + + /* Enabled Phased (8-channel) P16V playback */ + outl(0x0201, emu->port + HCFG2); + /* Set playback routing. */ + snd_emu10k1_ptr20_write(emu, CAPTURE_P16V_SOURCE, 0, 0x78e4); + } else if (emu->card_capabilities->ca0108_chip) { /* audigy2 Value */ + /* Hacks for Alice3 to work independent of haP16V driver */ + dev_info(emu->card->dev, "Audigy2 value: Special config.\n"); + /* Setup SRCMulti_I2S SamplingRate */ + snd_emu10k1_ptr_write(emu, A_I2S_CAPTURE_RATE, 0, A_I2S_CAPTURE_96000); + + /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */ + snd_emu10k1_ptr20_write(emu, P17V_SRCSel, 0, 0x14); + + /* Setup SRCMulti Input Audio Enable */ + snd_emu10k1_ptr20_write(emu, P17V_MIXER_I2S_ENABLE, 0, 0xFF000000); + + /* Setup SPDIF Out Audio Enable */ + /* The Audigy 2 Value has a separate SPDIF out, + * so no need for a mixer switch + */ + snd_emu10k1_ptr20_write(emu, P17V_MIXER_SPDIF_ENABLE, 0, 0xFF000000); + + tmp = inw(emu->port + A_IOCFG) & ~0x8; /* Clear bit 3 */ + outw(tmp, emu->port + A_IOCFG); + } + if (emu->card_capabilities->spi_dac) { /* Audigy 2 ZS Notebook with DAC Wolfson WM8768/WM8568 */ + int size, n; + + size = ARRAY_SIZE(spi_dac_init); + for (n = 0; n < size; n++) + snd_emu10k1_spi_write(emu, spi_dac_init[n]); + + snd_emu10k1_ptr20_write(emu, 0x60, 0, 0x10); + /* Enable GPIOs + * GPIO0: Unknown + * GPIO1: Speakers-enabled. + * GPIO2: Unknown + * GPIO3: Unknown + * GPIO4: IEC958 Output on. + * GPIO5: Unknown + * GPIO6: Unknown + * GPIO7: Unknown + */ + outw(0x76, emu->port + A_IOCFG); /* Windows uses 0x3f76 */ + } + if (emu->card_capabilities->i2c_adc) { /* Audigy 2 ZS Notebook with ADC Wolfson WM8775 */ + int size, n; + + snd_emu10k1_ptr20_write(emu, P17V_I2S_SRC_SEL, 0, 0x2020205f); + tmp = inw(emu->port + A_IOCFG); + outw(tmp | 0x4, emu->port + A_IOCFG); /* Set bit 2 for mic input */ + tmp = inw(emu->port + A_IOCFG); + size = ARRAY_SIZE(i2c_adc_init); + for (n = 0; n < size; n++) + snd_emu10k1_i2c_write(emu, i2c_adc_init[n][0], i2c_adc_init[n][1]); + for (n = 0; n < 4; n++) { + emu->i2c_capture_volume[n][0] = 0xcf; + emu->i2c_capture_volume[n][1] = 0xcf; + } + } + + + snd_emu10k1_ptr_write(emu, PTB, 0, emu->ptb_pages.addr); + snd_emu10k1_ptr_write(emu, TCB, 0, 0); /* taken from original driver */ + snd_emu10k1_ptr_write(emu, TCBS, 0, TCBS_BUFFSIZE_256K); /* taken from original driver */ + + silent_page = (emu->silent_page.addr << emu->address_mode) | (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); + for (ch = 0; ch < NUM_G; ch++) { + snd_emu10k1_ptr_write(emu, MAPA, ch, silent_page); + snd_emu10k1_ptr_write(emu, MAPB, ch, silent_page); + } + + if (emu->card_capabilities->emu_model) { + outl(HCFG_AUTOMUTE_ASYNC | + HCFG_EMU32_SLAVE | + HCFG_AUDIOENABLE, emu->port + HCFG); + /* + * Hokay, setup HCFG + * Mute Disable Audio = 0 + * Lock Tank Memory = 1 + * Lock Sound Memory = 0 + * Auto Mute = 1 + */ + } else if (emu->audigy) { + if (emu->revision == 4) /* audigy2 */ + outl(HCFG_AUDIOENABLE | + HCFG_AC3ENABLE_CDSPDIF | + HCFG_AC3ENABLE_GPSPDIF | + HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG); + else + outl(HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG); + /* FIXME: Remove all these emu->model and replace it with a card recognition parameter, + * e.g. card_capabilities->joystick */ + } else if (emu->model == 0x20 || + emu->model == 0xc400 || + (emu->model == 0x21 && emu->revision < 6)) + outl(HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE, emu->port + HCFG); + else + /* With on-chip joystick */ + outl(HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG); + + if (enable_ir) { /* enable IR for SB Live */ + if (emu->card_capabilities->emu_model) { + ; /* Disable all access to A_IOCFG for the emu1010 */ + } else if (emu->card_capabilities->i2c_adc) { + ; /* Disable A_IOCFG for Audigy 2 ZS Notebook */ + } else if (emu->audigy) { + u16 reg = inw(emu->port + A_IOCFG); + outw(reg | A_IOCFG_GPOUT2, emu->port + A_IOCFG); + udelay(500); + outw(reg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, emu->port + A_IOCFG); + udelay(100); + outw(reg, emu->port + A_IOCFG); + } else { + unsigned int reg = inl(emu->port + HCFG); + outl(reg | HCFG_GPOUT2, emu->port + HCFG); + udelay(500); + outl(reg | HCFG_GPOUT1 | HCFG_GPOUT2, emu->port + HCFG); + udelay(100); + outl(reg, emu->port + HCFG); + } + } + + if (emu->card_capabilities->emu_model) { + ; /* Disable all access to A_IOCFG for the emu1010 */ + } else if (emu->card_capabilities->i2c_adc) { + ; /* Disable A_IOCFG for Audigy 2 ZS Notebook */ + } else if (emu->audigy) { /* enable analog output */ + u16 reg = inw(emu->port + A_IOCFG); + outw(reg | A_IOCFG_GPOUT0, emu->port + A_IOCFG); + } + + if (emu->address_mode == 0) { + /* use 16M in 4G */ + outl(inl(emu->port + HCFG) | HCFG_EXPANDED_MEM, emu->port + HCFG); + } + + return 0; +} + +static void snd_emu10k1_audio_enable(struct snd_emu10k1 *emu) +{ + /* + * Enable the audio bit + */ + outl(inl(emu->port + HCFG) | HCFG_AUDIOENABLE, emu->port + HCFG); + + /* Enable analog/digital outs on audigy */ + if (emu->card_capabilities->emu_model) { + ; /* Disable all access to A_IOCFG for the emu1010 */ + } else if (emu->card_capabilities->i2c_adc) { + ; /* Disable A_IOCFG for Audigy 2 ZS Notebook */ + } else if (emu->audigy) { + outw(inw(emu->port + A_IOCFG) & ~0x44, emu->port + A_IOCFG); + + if (emu->card_capabilities->ca0151_chip) { /* audigy2 */ + /* Unmute Analog now. Set GPO6 to 1 for Apollo. + * This has to be done after init ALice3 I2SOut beyond 48KHz. + * So, sequence is important. */ + outw(inw(emu->port + A_IOCFG) | 0x0040, emu->port + A_IOCFG); + } else if (emu->card_capabilities->ca0108_chip) { /* audigy2 value */ + /* Unmute Analog now. */ + outw(inw(emu->port + A_IOCFG) | 0x0060, emu->port + A_IOCFG); + } else { + /* Disable routing from AC97 line out to Front speakers */ + outw(inw(emu->port + A_IOCFG) | 0x0080, emu->port + A_IOCFG); + } + } + +#if 0 + { + unsigned int tmp; + /* FIXME: the following routine disables LiveDrive-II !! */ + /* TOSLink detection */ + emu->tos_link = 0; + tmp = inl(emu->port + HCFG); + if (tmp & (HCFG_GPINPUT0 | HCFG_GPINPUT1)) { + outl(tmp|0x800, emu->port + HCFG); + udelay(50); + if (tmp != (inl(emu->port + HCFG) & ~0x800)) { + emu->tos_link = 1; + outl(tmp, emu->port + HCFG); + } + } + } +#endif + + if (emu->card_capabilities->emu_model) + snd_emu10k1_intr_enable(emu, INTE_PCIERRORENABLE | INTE_A_GPIOENABLE); + else + snd_emu10k1_intr_enable(emu, INTE_PCIERRORENABLE); +} + +int snd_emu10k1_done(struct snd_emu10k1 *emu) +{ + int ch; + + outl(0, emu->port + INTE); + + /* + * Shutdown the voices + */ + for (ch = 0; ch < NUM_G; ch++) { + snd_emu10k1_ptr_write_multiple(emu, ch, + DCYSUSV, 0, + VTFT, 0, + CVCF, 0, + PTRX, 0, + CPF, 0, + REGLIST_END); + } + + // stop the DSP + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, A_DBG_SINGLE_STEP); + else + snd_emu10k1_ptr_write(emu, DBG, 0, EMU10K1_DBG_SINGLE_STEP); + + snd_emu10k1_ptr_write_multiple(emu, 0, + /* reset recording buffers */ + MICBS, 0, + MICBA, 0, + FXBS, 0, + FXBA, 0, + FXWC, 0, + ADCBS, ADCBS_BUFSIZE_NONE, + ADCBA, 0, + TCBS, TCBS_BUFFSIZE_16K, + TCB, 0, + + /* disable channel interrupt */ + CLIEL, 0, + CLIEH, 0, + SOLEL, 0, + SOLEH, 0, + + PTB, 0, + + REGLIST_END); + + /* disable audio and lock cache */ + outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, emu->port + HCFG); + + return 0; +} + +/************************************************************************* + * ECARD functional implementation + *************************************************************************/ + +/* In A1 Silicon, these bits are in the HC register */ +#define HOOKN_BIT (1L << 12) +#define HANDN_BIT (1L << 11) +#define PULSEN_BIT (1L << 10) + +#define EC_GDI1 (1 << 13) +#define EC_GDI0 (1 << 14) + +#define EC_NUM_CONTROL_BITS 20 + +#define EC_AC3_DATA_SELN 0x0001L +#define EC_EE_DATA_SEL 0x0002L +#define EC_EE_CNTRL_SELN 0x0004L +#define EC_EECLK 0x0008L +#define EC_EECS 0x0010L +#define EC_EESDO 0x0020L +#define EC_TRIM_CSN 0x0040L +#define EC_TRIM_SCLK 0x0080L +#define EC_TRIM_SDATA 0x0100L +#define EC_TRIM_MUTEN 0x0200L +#define EC_ADCCAL 0x0400L +#define EC_ADCRSTN 0x0800L +#define EC_DACCAL 0x1000L +#define EC_DACMUTEN 0x2000L +#define EC_LEDN 0x4000L + +#define EC_SPDIF0_SEL_SHIFT 15 +#define EC_SPDIF1_SEL_SHIFT 17 +#define EC_SPDIF0_SEL_MASK (0x3L << EC_SPDIF0_SEL_SHIFT) +#define EC_SPDIF1_SEL_MASK (0x7L << EC_SPDIF1_SEL_SHIFT) +#define EC_SPDIF0_SELECT(_x) (((_x) << EC_SPDIF0_SEL_SHIFT) & EC_SPDIF0_SEL_MASK) +#define EC_SPDIF1_SELECT(_x) (((_x) << EC_SPDIF1_SEL_SHIFT) & EC_SPDIF1_SEL_MASK) +#define EC_CURRENT_PROM_VERSION 0x01 /* Self-explanatory. This should + * be incremented any time the EEPROM's + * format is changed. */ + +#define EC_EEPROM_SIZE 0x40 /* ECARD EEPROM has 64 16-bit words */ + +/* Addresses for special values stored in to EEPROM */ +#define EC_PROM_VERSION_ADDR 0x20 /* Address of the current prom version */ +#define EC_BOARDREV0_ADDR 0x21 /* LSW of board rev */ +#define EC_BOARDREV1_ADDR 0x22 /* MSW of board rev */ + +#define EC_LAST_PROMFILE_ADDR 0x2f + +#define EC_SERIALNUM_ADDR 0x30 /* First word of serial number. The + * can be up to 30 characters in length + * and is stored as a NULL-terminated + * ASCII string. Any unused bytes must be + * filled with zeros */ +#define EC_CHECKSUM_ADDR 0x3f /* Location at which checksum is stored */ + + +/* Most of this stuff is pretty self-evident. According to the hardware + * dudes, we need to leave the ADCCAL bit low in order to avoid a DC + * offset problem. Weird. + */ +#define EC_RAW_RUN_MODE (EC_DACMUTEN | EC_ADCRSTN | EC_TRIM_MUTEN | \ + EC_TRIM_CSN) + + +#define EC_DEFAULT_ADC_GAIN 0xC4C4 +#define EC_DEFAULT_SPDIF0_SEL 0x0 +#define EC_DEFAULT_SPDIF1_SEL 0x4 + +/************************************************************************** + * @func Clock bits into the Ecard's control latch. The Ecard uses a + * control latch will is loaded bit-serially by toggling the Modem control + * lines from function 2 on the E8010. This function hides these details + * and presents the illusion that we are actually writing to a distinct + * register. + */ + +static void snd_emu10k1_ecard_write(struct snd_emu10k1 *emu, unsigned int value) +{ + unsigned short count; + unsigned int data; + unsigned long hc_port; + unsigned int hc_value; + + hc_port = emu->port + HCFG; + hc_value = inl(hc_port) & ~(HOOKN_BIT | HANDN_BIT | PULSEN_BIT); + outl(hc_value, hc_port); + + for (count = 0; count < EC_NUM_CONTROL_BITS; count++) { + + /* Set up the value */ + data = ((value & 0x1) ? PULSEN_BIT : 0); + value >>= 1; + + outl(hc_value | data, hc_port); + + /* Clock the shift register */ + outl(hc_value | data | HANDN_BIT, hc_port); + outl(hc_value | data, hc_port); + } + + /* Latch the bits */ + outl(hc_value | HOOKN_BIT, hc_port); + outl(hc_value, hc_port); +} + +/************************************************************************** + * @func Set the gain of the ECARD's CS3310 Trim/gain controller. The + * trim value consists of a 16bit value which is composed of two + * 8 bit gain/trim values, one for the left channel and one for the + * right channel. The following table maps from the Gain/Attenuation + * value in decibels into the corresponding bit pattern for a single + * channel. + */ + +static void snd_emu10k1_ecard_setadcgain(struct snd_emu10k1 *emu, + unsigned short gain) +{ + unsigned int bit; + + /* Enable writing to the TRIM registers */ + snd_emu10k1_ecard_write(emu, emu->ecard_ctrl & ~EC_TRIM_CSN); + + /* Do it again to insure that we meet hold time requirements */ + snd_emu10k1_ecard_write(emu, emu->ecard_ctrl & ~EC_TRIM_CSN); + + for (bit = (1 << 15); bit; bit >>= 1) { + unsigned int value; + + value = emu->ecard_ctrl & ~(EC_TRIM_CSN | EC_TRIM_SDATA); + + if (gain & bit) + value |= EC_TRIM_SDATA; + + /* Clock the bit */ + snd_emu10k1_ecard_write(emu, value); + snd_emu10k1_ecard_write(emu, value | EC_TRIM_SCLK); + snd_emu10k1_ecard_write(emu, value); + } + + snd_emu10k1_ecard_write(emu, emu->ecard_ctrl); +} + +static int snd_emu10k1_ecard_init(struct snd_emu10k1 *emu) +{ + unsigned int hc_value; + + /* Set up the initial settings */ + emu->ecard_ctrl = EC_RAW_RUN_MODE | + EC_SPDIF0_SELECT(EC_DEFAULT_SPDIF0_SEL) | + EC_SPDIF1_SELECT(EC_DEFAULT_SPDIF1_SEL); + + /* Step 0: Set the codec type in the hardware control register + * and enable audio output */ + hc_value = inl(emu->port + HCFG); + outl(hc_value | HCFG_AUDIOENABLE | HCFG_CODECFORMAT_I2S, emu->port + HCFG); + inl(emu->port + HCFG); + + /* Step 1: Turn off the led and deassert TRIM_CS */ + snd_emu10k1_ecard_write(emu, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN); + + /* Step 2: Calibrate the ADC and DAC */ + snd_emu10k1_ecard_write(emu, EC_DACCAL | EC_LEDN | EC_TRIM_CSN); + + /* Step 3: Wait for awhile; XXX We can't get away with this + * under a real operating system; we'll need to block and wait that + * way. */ + snd_emu10k1_wait(emu, 48000); + + /* Step 4: Switch off the DAC and ADC calibration. Note + * That ADC_CAL is actually an inverted signal, so we assert + * it here to stop calibration. */ + snd_emu10k1_ecard_write(emu, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN); + + /* Step 4: Switch into run mode */ + snd_emu10k1_ecard_write(emu, emu->ecard_ctrl); + + /* Step 5: Set the analog input gain */ + snd_emu10k1_ecard_setadcgain(emu, EC_DEFAULT_ADC_GAIN); + + return 0; +} + +static int snd_emu10k1_cardbus_init(struct snd_emu10k1 *emu) +{ + unsigned long special_port; + __always_unused unsigned int value; + + /* Special initialisation routine + * before the rest of the IO-Ports become active. + */ + special_port = emu->port + 0x38; + value = inl(special_port); + outl(0x00d00000, special_port); + value = inl(special_port); + outl(0x00d00001, special_port); + value = inl(special_port); + outl(0x00d0005f, special_port); + value = inl(special_port); + outl(0x00d0007f, special_port); + value = inl(special_port); + outl(0x0090007f, special_port); + value = inl(special_port); + + snd_emu10k1_ptr20_write(emu, TINA2_VOLUME, 0, 0xfefefefe); /* Defaults to 0x30303030 */ + /* Delay to give time for ADC chip to switch on. It needs 113ms */ + msleep(200); + return 0; +} + +/* firmware file names, per model, init-fw and dock-fw (optional) */ +static const char * const firmware_names[5][2] = { + [EMU_MODEL_EMU1010] = { + HANA_FILENAME, DOCK_FILENAME + }, + [EMU_MODEL_EMU1010B] = { + EMU1010B_FILENAME, MICRO_DOCK_FILENAME + }, + [EMU_MODEL_EMU1616] = { + EMU1010_NOTEBOOK_FILENAME, MICRO_DOCK_FILENAME + }, + [EMU_MODEL_EMU0404] = { + EMU0404_FILENAME, NULL + }, +}; + +static int snd_emu1010_load_firmware(struct snd_emu10k1 *emu, int dock, + const struct firmware **fw) +{ + const char *filename; + int err; + + if (!*fw) { + filename = firmware_names[emu->card_capabilities->emu_model][dock]; + if (!filename) + return 0; + err = request_firmware(fw, filename, &emu->pci->dev); + if (err) + return err; + } + + snd_emu1010_load_firmware_entry(emu, dock, *fw); + return 0; +} + +static void snd_emu1010_load_dock_firmware(struct snd_emu10k1 *emu) +{ + u32 tmp, tmp2; + int err; + + // The docking events clearly arrive prematurely - while the + // Dock's FPGA seems to be successfully programmed, the Dock + // fails to initialize subsequently if we don't give it some + // time to "warm up" here. + msleep(200); + + dev_info(emu->card->dev, "emu1010: Loading Audio Dock Firmware\n"); + err = snd_emu1010_load_firmware(emu, 1, &emu->dock_fw); + if (err < 0) + return; + snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG, 0); + + snd_emu1010_fpga_read(emu, EMU_HANA_ID, &tmp); + dev_dbg(emu->card->dev, "emu1010: EMU_HANA+DOCK_ID = 0x%x\n", tmp); + if ((tmp & 0x1f) != 0x15) { + /* FPGA failed to be programmed */ + dev_err(emu->card->dev, + "emu1010: Loading Audio Dock Firmware failed, reg = 0x%x\n", + tmp); + return; + } + dev_info(emu->card->dev, "emu1010: Audio Dock Firmware loaded\n"); + + snd_emu1010_fpga_read(emu, EMU_DOCK_MAJOR_REV, &tmp); + snd_emu1010_fpga_read(emu, EMU_DOCK_MINOR_REV, &tmp2); + dev_info(emu->card->dev, "Audio Dock ver: %u.%u\n", tmp, tmp2); + + /* Allow DLL to settle, to sync clocking between 1010 and Dock */ + msleep(10); +} + +static void emu1010_dock_event(struct snd_emu10k1 *emu) +{ + u32 reg; + + snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, ®); /* OPTIONS: Which cards are attached to the EMU */ + if (reg & EMU_HANA_OPTION_DOCK_OFFLINE) { + /* Audio Dock attached */ + snd_emu1010_load_dock_firmware(emu); + /* Unmute all. Default is muted after a firmware load */ + snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE); + } else if (!(reg & EMU_HANA_OPTION_DOCK_ONLINE)) { + /* Audio Dock removed */ + dev_info(emu->card->dev, "emu1010: Audio Dock detached\n"); + /* The hardware auto-mutes all, so we unmute again */ + snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE); + } +} + +static void emu1010_clock_event(struct snd_emu10k1 *emu) +{ + struct snd_ctl_elem_id id; + + scoped_guard(spinlock_irq, &emu->reg_lock) { + // This is the only thing that can actually happen. + emu->emu1010.clock_source = emu->emu1010.clock_fallback; + emu->emu1010.wclock = 1 - emu->emu1010.clock_source; + snd_emu1010_update_clock(emu); + } + snd_ctl_build_ioff(&id, emu->ctl_clock_source, 0); + snd_ctl_notify(emu->card, SNDRV_CTL_EVENT_MASK_VALUE, &id); +} + +static void emu1010_work(struct work_struct *work) +{ + struct snd_emu10k1 *emu; + u32 sts; + + emu = container_of(work, struct snd_emu10k1, emu1010.work); + if (emu->card->shutdown) + return; +#ifdef CONFIG_PM_SLEEP + if (emu->suspend) + return; +#endif + + guard(snd_emu1010_fpga_lock)(emu); + + snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, &sts); + + // The distinction of the IRQ status bits is unreliable, + // so we dispatch later based on option card status. + if (sts & (EMU_HANA_IRQ_DOCK | EMU_HANA_IRQ_DOCK_LOST)) + emu1010_dock_event(emu); + + if (sts & EMU_HANA_IRQ_WCLK_CHANGED) + emu1010_clock_event(emu); +} + +static void emu1010_interrupt(struct snd_emu10k1 *emu) +{ + // We get an interrupt on each GPIO input pin change, but we + // care only about the ones triggered by the dedicated pin. + u16 sts = inw(emu->port + A_GPIO); + u16 bit = emu->card_capabilities->ca0108_chip ? 0x2000 : 0x8000; + if (!(sts & bit)) + return; + + schedule_work(&emu->emu1010.work); +} + +/* + * Current status of the driver: + * ---------------------------- + * * only 44.1/48kHz supported (the MS Win driver supports up to 192 kHz) + * * PCM device nb. 2: + * 16 x 16-bit playback - snd_emu10k1_fx8010_playback_ops + * 16 x 32-bit capture - snd_emu10k1_capture_efx_ops + */ +static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) +{ + u32 tmp, tmp2, reg; + int err; + + dev_info(emu->card->dev, "emu1010: Special config.\n"); + + /* Mute, and disable audio and lock cache, just in case. + * Proper init follows in snd_emu10k1_init(). */ + outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK, emu->port + HCFG); + + guard(snd_emu1010_fpga_lock)(emu); + + dev_info(emu->card->dev, "emu1010: Loading Hana Firmware\n"); + err = snd_emu1010_load_firmware(emu, 0, &emu->firmware); + if (err < 0) { + dev_info(emu->card->dev, "emu1010: Loading Firmware failed\n"); + return err; + } + + /* ID, should read & 0x7f = 0x55 when FPGA programmed. */ + snd_emu1010_fpga_read(emu, EMU_HANA_ID, ®); + if ((reg & 0x3f) != 0x15) { + /* FPGA failed to be programmed */ + dev_info(emu->card->dev, + "emu1010: Loading Hana Firmware file failed, reg = 0x%x\n", + reg); + return -ENODEV; + } + + dev_info(emu->card->dev, "emu1010: Hana Firmware loaded\n"); + snd_emu1010_fpga_read(emu, EMU_HANA_MAJOR_REV, &tmp); + snd_emu1010_fpga_read(emu, EMU_HANA_MINOR_REV, &tmp2); + dev_info(emu->card->dev, "emu1010: Hana version: %u.%u\n", tmp, tmp2); + /* Enable 48Volt power to Audio Dock */ + snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_PWR, EMU_HANA_DOCK_PWR_ON); + + snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, ®); + dev_info(emu->card->dev, "emu1010: Card options = 0x%x\n", reg); + if (reg & EMU_HANA_OPTION_DOCK_OFFLINE) + snd_emu1010_load_dock_firmware(emu); + if (emu->card_capabilities->no_adat) { + emu->emu1010.optical_in = 0; /* IN_SPDIF */ + emu->emu1010.optical_out = 0; /* OUT_SPDIF */ + } else { + /* Optical -> ADAT I/O */ + emu->emu1010.optical_in = 1; /* IN_ADAT */ + emu->emu1010.optical_out = 1; /* OUT_ADAT */ + } + tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : EMU_HANA_OPTICAL_IN_SPDIF) | + (emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : EMU_HANA_OPTICAL_OUT_SPDIF); + snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, tmp); + /* Set no attenuation on Audio Dock pads. */ + emu->emu1010.adc_pads = 0x00; + snd_emu1010_fpga_write(emu, EMU_HANA_ADC_PADS, emu->emu1010.adc_pads); + /* Unmute Audio dock DACs, Headphone source DAC-4. */ + snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_MISC, EMU_HANA_DOCK_PHONES_192_DAC4); + /* DAC PADs. */ + emu->emu1010.dac_pads = EMU_HANA_DOCK_DAC_PAD1 | EMU_HANA_DOCK_DAC_PAD2 | + EMU_HANA_DOCK_DAC_PAD3 | EMU_HANA_DOCK_DAC_PAD4; + snd_emu1010_fpga_write(emu, EMU_HANA_DAC_PADS, emu->emu1010.dac_pads); + /* SPDIF Format. Set Consumer mode, 24bit, copy enable */ + snd_emu1010_fpga_write(emu, EMU_HANA_SPDIF_MODE, EMU_HANA_SPDIF_MODE_RX_INVALID); + /* MIDI routing */ + snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_IN, EMU_HANA_MIDI_INA_FROM_HAMOA | EMU_HANA_MIDI_INB_FROM_DOCK2); + snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_OUT, EMU_HANA_MIDI_OUT_DOCK2 | EMU_HANA_MIDI_OUT_SYNC2); + + emu->gpio_interrupt = emu1010_interrupt; + // Note: The Audigy INTE is set later + snd_emu1010_fpga_write(emu, EMU_HANA_IRQ_ENABLE, + EMU_HANA_IRQ_DOCK | EMU_HANA_IRQ_DOCK_LOST | EMU_HANA_IRQ_WCLK_CHANGED); + snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, ®); // Clear pending IRQs + + emu->emu1010.clock_source = 1; /* 48000 */ + emu->emu1010.clock_fallback = 1; /* 48000 */ + /* Default WCLK set to 48kHz. */ + snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K); + /* Word Clock source, Internal 48kHz x1 */ + emu->emu1010.wclock = EMU_HANA_WCLOCK_INT_48K; + snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K); + /* snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_4X); */ + snd_emu1010_update_clock(emu); + + // The routes are all set to EMU_SRC_SILENCE due to the reset, + // so it is safe to simply enable the outputs. + snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE); + + return 0; +} +/* + * Create the EMU10K1 instance + */ + +#ifdef CONFIG_PM_SLEEP +static int alloc_pm_buffer(struct snd_emu10k1 *emu); +static void free_pm_buffer(struct snd_emu10k1 *emu); +#endif + +static void snd_emu10k1_free(struct snd_card *card) +{ + struct snd_emu10k1 *emu = card->private_data; + + if (emu->port) { /* avoid access to already used hardware */ + snd_emu10k1_fx8010_tram_setup(emu, 0); + snd_emu10k1_done(emu); + snd_emu10k1_free_efx(emu); + } + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1010) { + /* Disable 48Volt power to Audio Dock */ + snd_emu1010_fpga_write_lock(emu, EMU_HANA_DOCK_PWR, 0); + } + cancel_work_sync(&emu->emu1010.work); + mutex_destroy(&emu->emu1010.lock); + release_firmware(emu->firmware); + release_firmware(emu->dock_fw); + snd_util_memhdr_free(emu->memhdr); + if (emu->silent_page.area) + snd_dma_free_pages(&emu->silent_page); + if (emu->ptb_pages.area) + snd_dma_free_pages(&emu->ptb_pages); + vfree(emu->page_ptr_table); + vfree(emu->page_addr_table); +#ifdef CONFIG_PM_SLEEP + free_pm_buffer(emu); +#endif +} + +static const struct snd_emu_chip_details emu_chip_details[] = { + /* Audigy 5/Rx SB1550 */ + /* Tested by michael@gernoth.net 28 Mar 2015 */ + /* DSP: CA10300-IAT LF + * DAC: Cirrus Logic CS4382-KQZ + * ADC: Philips 1361T + * AC97: Sigmatel STAC9750 + * CA0151: None + */ + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x10241102, + .driver = "Audigy2", .name = "SB Audigy 5/Rx [SB1550]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .spk71 = 1, + .adc_1361t = 1, /* 24 bit capture instead of 16bit */ + .ac97_chip = 1}, + /* Audigy4 (Not PRO) SB0610 */ + /* Tested by James@superbug.co.uk 4th April 2006 */ + /* A_IOCFG bits + * Output + * 0: ? + * 1: ? + * 2: ? + * 3: 0 - Digital Out, 1 - Line in + * 4: ? + * 5: ? + * 6: ? + * 7: ? + * Input + * 8: ? + * 9: ? + * A: Green jack sense (Front) + * B: ? + * C: Black jack sense (Rear/Side Right) + * D: Yellow jack sense (Center/LFE/Side Left) + * E: ? + * F: ? + * + * Digital Out/Line in switch using A_IOCFG bit 3 (0x08) + * 0 - Digital Out + * 1 - Line in + */ + /* Mic input not tested. + * Analog CD input not tested + * Digital Out not tested. + * Line in working. + * Audio output 5.1 working. Side outputs not working. + */ + /* DSP: CA10300-IAT LF + * DAC: Cirrus Logic CS4382-KQZ + * ADC: Philips 1361T + * AC97: Sigmatel STAC9750 + * CA0151: None + */ + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x10211102, + .driver = "Audigy2", .name = "SB Audigy 4 [SB0610]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .spk71 = 1, + .adc_1361t = 1, /* 24 bit capture instead of 16bit */ + .ac97_chip = 1} , + /* Audigy 2 Value AC3 out does not work yet. + * Need to find out how to turn off interpolators. + */ + /* Tested by James@superbug.co.uk 3rd July 2005 */ + /* DSP: CA0108-IAT + * DAC: CS4382-KQ + * ADC: Philips 1361T + * AC97: STAC9750 + * CA0151: None + */ + /* + * A_IOCFG Input (GPIO) + * 0x400 = Front analog jack plugged in. (Green socket) + * 0x1000 = Rear analog jack plugged in. (Black socket) + * 0x2000 = Center/LFE analog jack plugged in. (Orange socket) + * A_IOCFG Output (GPIO) + * 0x60 = Sound out of front Left. + * Win sets it to 0xXX61 + */ + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x10011102, + .driver = "Audigy2", .name = "SB Audigy 2 Value [SB0400]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .spk71 = 1, + .ac97_chip = 1} , + /* Audigy 2 ZS Notebook Cardbus card.*/ + /* Tested by James@superbug.co.uk 6th November 2006 */ + /* Audio output 7.1/Headphones working. + * Digital output working. (AC3 not checked, only PCM) + * Audio Mic/Line inputs working. + * Digital input not tested. + */ + /* DSP: Tina2 + * DAC: Wolfson WM8768/WM8568 + * ADC: Wolfson WM8775 + * AC97: None + * CA0151: None + */ + /* Tested by James@superbug.co.uk 4th April 2006 */ + /* A_IOCFG bits + * Output + * 0: Not Used + * 1: 0 = Mute all the 7.1 channel out. 1 = unmute. + * 2: Analog input 0 = line in, 1 = mic in + * 3: Not Used + * 4: Digital output 0 = off, 1 = on. + * 5: Not Used + * 6: Not Used + * 7: Not Used + * Input + * All bits 1 (0x3fxx) means nothing plugged in. + * 8-9: 0 = Line in/Mic, 2 = Optical in, 3 = Nothing. + * A-B: 0 = Headphones, 2 = Optical out, 3 = Nothing. + * C-D: 2 = Front/Rear/etc, 3 = nothing. + * E-F: Always 0 + * + */ + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x20011102, + .driver = "Audigy2", .name = "Audigy 2 ZS Notebook [SB0530]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .ca_cardbus_chip = 1, + .spi_dac = 1, + .i2c_adc = 1, + .spk71 = 1} , + /* This is MAEM8950 "Mana" */ + /* Attach MicroDock[M] to make it an E-MU 1616[m]. */ + /* Does NOT support sync daughter card (obviously). */ + /* Tested by James@superbug.co.uk 4th Nov 2007. */ + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x42011102, + .driver = "Audigy2", .name = "E-MU 02 CardBus [MAEM8950]", + .id = "EMU1010", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .ca_cardbus_chip = 1, + .spk71 = 1 , + .emu_model = EMU_MODEL_EMU1616}, + /* Tested by James@superbug.co.uk 4th Nov 2007. */ + /* This is MAEM8960 "Hana3", 0202 is MAEM8980 */ + /* Attach 0202 daughter card to make it an E-MU 1212m, OR a + * MicroDock[M] to make it an E-MU 1616[m]. */ + /* Does NOT support sync daughter card. */ + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40041102, + .driver = "Audigy2", .name = "E-MU 1010b PCI [MAEM8960]", + .id = "EMU1010", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .spk71 = 1, + .emu_model = EMU_MODEL_EMU1010B}, /* EMU 1010 new revision */ + /* Tested by Maxim Kachur 17th Oct 2012. */ + /* This is MAEM8986, 0202 is MAEM8980 */ + /* Attach 0202 daughter card to make it an E-MU 1212m, OR a + * MicroDockM to make it an E-MU 1616m. The non-m + * version was never sold with this card, but should + * still work. */ + /* Does NOT support sync daughter card. */ + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40071102, + .driver = "Audigy2", .name = "E-MU 1010 PCIe [MAEM8986]", + .id = "EMU1010", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .spk71 = 1, + .emu_model = EMU_MODEL_EMU1010B}, /* EMU 1010 PCIe */ + /* Tested by James@superbug.co.uk 8th July 2005. */ + /* This is MAEM8810 "Hana", 0202 is MAEM8820 "Hamoa" */ + /* Attach 0202 daughter card to make it an E-MU 1212m, OR an + * AudioDock[M] to make it an E-MU 1820[m]. */ + /* Supports sync daughter card. */ + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40011102, + .driver = "Audigy2", .name = "E-MU 1010 [MAEM8810]", + .id = "EMU1010", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .spk71 = 1, + .emu_model = EMU_MODEL_EMU1010}, /* EMU 1010 old revision */ + /* This is MAEM8852 "HanaLiteLite" */ + /* Supports sync daughter card. */ + /* Tested by oswald.buddenhagen@gmx.de Mar 2023. */ + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40021102, + .driver = "Audigy2", .name = "E-MU 0404b PCI [MAEM8852]", + .id = "EMU0404", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .spk20 = 1, + .no_adat = 1, + .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 new revision */ + /* This is MAEM8850 "HanaLite" */ + /* Supports sync daughter card. */ + /* Tested by James@superbug.co.uk 20-3-2007. */ + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40021102, + .driver = "Audigy2", .name = "E-MU 0404 [MAEM8850]", + .id = "EMU0404", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .spk20 = 1, + .no_adat = 1, + .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 */ + /* EMU0404 PCIe */ + /* Does NOT support sync daughter card. */ + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40051102, + .driver = "Audigy2", .name = "E-MU 0404 PCIe [MAEM8984]", + .id = "EMU0404", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .spk20 = 1, + .no_adat = 1, + .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 PCIe ver_03 */ + {.vendor = 0x1102, .device = 0x0008, + .driver = "Audigy2", .name = "SB Audigy 2 Value [Unknown]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .ac97_chip = 1} , + /* Tested by James@superbug.co.uk 3rd July 2005 */ + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20071102, + .driver = "Audigy2", .name = "SB Audigy 4 PRO [SB0380]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spk71 = 1, + .spdif_bug = 1, + .ac97_chip = 1} , + /* Tested by shane-alsa@cm.nu 5th Nov 2005 */ + /* The 0x20061102 does have SB0350 written on it + * Just like 0x20021102 + */ + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20061102, + .driver = "Audigy2", .name = "SB Audigy 2 [SB0350b]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spk71 = 1, + .spdif_bug = 1, + .invert_shared_spdif = 1, /* digital/analog switch swapped */ + .ac97_chip = 1} , + /* 0x20051102 also has SB0350 written on it, treated as Audigy 2 ZS by + Creative's Windows driver */ + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20051102, + .driver = "Audigy2", .name = "SB Audigy 2 ZS [SB0350a]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spk71 = 1, + .spdif_bug = 1, + .invert_shared_spdif = 1, /* digital/analog switch swapped */ + .ac97_chip = 1} , + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20021102, + .driver = "Audigy2", .name = "SB Audigy 2 ZS [SB0350]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spk71 = 1, + .spdif_bug = 1, + .invert_shared_spdif = 1, /* digital/analog switch swapped */ + .ac97_chip = 1} , + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20011102, + .driver = "Audigy2", .name = "SB Audigy 2 ZS [SB0360]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spk71 = 1, + .spdif_bug = 1, + .invert_shared_spdif = 1, /* digital/analog switch swapped */ + .ac97_chip = 1} , + /* Audigy 2 */ + /* Tested by James@superbug.co.uk 3rd July 2005 */ + /* DSP: CA0102-IAT + * DAC: CS4382-KQ + * ADC: Philips 1361T + * AC97: STAC9721 + * CA0151: Yes + */ + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10071102, + .driver = "Audigy2", .name = "SB Audigy 2 [SB0240]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spk71 = 1, + .spdif_bug = 1, + .adc_1361t = 1, /* 24 bit capture instead of 16bit */ + .ac97_chip = 1} , + /* Audigy 2 Platinum EX */ + /* Win driver sets A_IOCFG output to 0x1c00 */ + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10051102, + .driver = "Audigy2", .name = "Audigy 2 Platinum EX [SB0280]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spk71 = 1, + .spdif_bug = 1} , + /* Dell OEM/Creative Labs Audigy 2 ZS */ + /* See ALSA bug#1365 */ + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10031102, + .driver = "Audigy2", .name = "SB Audigy 2 ZS [SB0353]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spk71 = 1, + .spdif_bug = 1, + .invert_shared_spdif = 1, /* digital/analog switch swapped */ + .ac97_chip = 1} , + /* Audigy 2 Platinum */ + /* Win driver sets A_IOCFG output to 0xa00 */ + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10021102, + .driver = "Audigy2", .name = "SB Audigy 2 Platinum [SB0240P]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spk71 = 1, + .spdif_bug = 1, + .invert_shared_spdif = 1, /* digital/analog switch swapped */ + .adc_1361t = 1, /* 24 bit capture instead of 16bit. Fixes ALSA bug#324 */ + .ac97_chip = 1} , + {.vendor = 0x1102, .device = 0x0004, .revision = 0x04, + .driver = "Audigy2", .name = "SB Audigy 2 [Unknown]", + .id = "Audigy2", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ca0151_chip = 1, + .spdif_bug = 1, + .ac97_chip = 1} , + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x00531102, + .driver = "Audigy", .name = "SB Audigy 1 [SB0092]", + .id = "Audigy", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ac97_chip = 1} , + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x00521102, + .driver = "Audigy", .name = "SB Audigy 1 ES [SB0160]", + .id = "Audigy", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .spdif_bug = 1, + .ac97_chip = 1} , + {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x00511102, + .driver = "Audigy", .name = "SB Audigy 1 [SB0090]", + .id = "Audigy", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ac97_chip = 1} , + {.vendor = 0x1102, .device = 0x0004, + .driver = "Audigy", .name = "Audigy 1 [Unknown]", + .id = "Audigy", + .emu10k2_chip = 1, + .ca0102_chip = 1, + .ac97_chip = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x100a1102, + .driver = "EMU10K1", .name = "SB Live! 5.1 [SB0220]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x806b1102, + .driver = "EMU10K1", .name = "SB Live! [SB0105]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x806a1102, + .driver = "EMU10K1", .name = "SB Live! Value [SB0103]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80691102, + .driver = "EMU10K1", .name = "SB Live! Value [SB0101]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + /* Tested by ALSA bug#1680 26th December 2005 */ + /* note: It really has SB0220 written on the card, */ + /* but it's SB0228 according to kx.inf */ + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80661102, + .driver = "EMU10K1", .name = "SB Live! 5.1 Dell OEM [SB0228]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + /* Tested by Thomas Zehetbauer 27th Aug 2005 */ + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80651102, + .driver = "EMU10K1", .name = "SB Live! 5.1 [SB0220]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80641102, + .driver = "EMU10K1", .name = "SB Live! 5.1", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + /* Tested by alsa bugtrack user "hus" bug #1297 12th Aug 2005 */ + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80611102, + .driver = "EMU10K1", .name = "SB Live! 5.1 [SB0060]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 2, /* ac97 is optional; both SBLive 5.1 and platinum + * share the same IDs! + */ + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80511102, + .driver = "EMU10K1", .name = "SB Live! Value [CT4850]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + /* SB Live! Platinum */ + /* Win driver sets A_IOCFG output to 0 */ + /* Tested by Jonathan Dowland Apr 2023. */ + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80401102, + .driver = "EMU10K1", .name = "SB Live! Platinum [CT4760P]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80321102, + .driver = "EMU10K1", .name = "SB Live! Value [CT4871]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80311102, + .driver = "EMU10K1", .name = "SB Live! Value [CT4831]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80281102, + .driver = "EMU10K1", .name = "SB Live! Value [CT4870]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + /* Tested by James@superbug.co.uk 3rd July 2005 */ + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80271102, + .driver = "EMU10K1", .name = "SB Live! Value [CT4832]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80261102, + .driver = "EMU10K1", .name = "SB Live! Value [CT4830]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80231102, + .driver = "EMU10K1", .name = "SB PCI512 [CT4790]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80221102, + .driver = "EMU10K1", .name = "SB Live! Value [CT4780]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x40011102, + .driver = "EMU10K1", .name = "E-MU APS [PC545]", + .id = "APS", + .emu10k1_chip = 1, + .ecard = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x00211102, + .driver = "EMU10K1", .name = "SB Live! [CT4620]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x00201102, + .driver = "EMU10K1", .name = "SB Live! Value [CT4670]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + {.vendor = 0x1102, .device = 0x0002, + .driver = "EMU10K1", .name = "SB Live! [Unknown]", + .id = "Live", + .emu10k1_chip = 1, + .ac97_chip = 1, + .sblive51 = 1} , + { } /* terminator */ +}; + +/* + * The chip (at least the Audigy 2 CA0102 chip, but most likely others, too) + * has a problem that from time to time it likes to do few DMA reads a bit + * beyond its normal allocation and gets very confused if these reads get + * blocked by a IOMMU. + * + * This behaviour has been observed for the first (reserved) page + * (for which it happens multiple times at every playback), often for various + * synth pages and sometimes for PCM playback buffers and the page table + * memory itself. + * + * As a workaround let's widen these DMA allocations by an extra page if we + * detect that the device is behind a non-passthrough IOMMU. + */ +static void snd_emu10k1_detect_iommu(struct snd_emu10k1 *emu) +{ + struct iommu_domain *domain; + + emu->iommu_workaround = false; + + domain = iommu_get_domain_for_dev(emu->card->dev); + if (!domain || domain->type == IOMMU_DOMAIN_IDENTITY) + return; + + dev_notice(emu->card->dev, + "non-passthrough IOMMU detected, widening DMA allocations"); + emu->iommu_workaround = true; +} + +int snd_emu10k1_create(struct snd_card *card, + struct pci_dev *pci, + unsigned short extin_mask, + unsigned short extout_mask, + long max_cache_bytes, + int enable_ir, + uint subsystem) +{ + struct snd_emu10k1 *emu = card->private_data; + int idx, err; + int is_audigy; + size_t page_table_size; + __le32 *pgtbl; + unsigned int silent_page; + const struct snd_emu_chip_details *c; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + + card->private_free = snd_emu10k1_free; + emu->card = card; + spin_lock_init(&emu->reg_lock); + spin_lock_init(&emu->emu_lock); + spin_lock_init(&emu->spi_lock); + spin_lock_init(&emu->i2c_lock); + spin_lock_init(&emu->voice_lock); + spin_lock_init(&emu->synth_lock); + spin_lock_init(&emu->memblk_lock); + mutex_init(&emu->fx8010.lock); + INIT_LIST_HEAD(&emu->mapped_link_head); + INIT_LIST_HEAD(&emu->mapped_order_link_head); + emu->pci = pci; + emu->irq = -1; + emu->synth = NULL; + emu->get_synth_voice = NULL; + INIT_WORK(&emu->emu1010.work, emu1010_work); + mutex_init(&emu->emu1010.lock); + /* read revision & serial */ + emu->revision = pci->revision; + pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &emu->serial); + pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &emu->model); + dev_dbg(card->dev, + "vendor = 0x%x, device = 0x%x, subsystem_vendor_id = 0x%x, subsystem_id = 0x%x\n", + pci->vendor, pci->device, emu->serial, emu->model); + + for (c = emu_chip_details; c->vendor; c++) { + if (c->vendor == pci->vendor && c->device == pci->device) { + if (subsystem) { + if (c->subsystem && (c->subsystem == subsystem)) + break; + else + continue; + } else { + if (c->subsystem && (c->subsystem != emu->serial)) + continue; + if (c->revision && c->revision != emu->revision) + continue; + } + break; + } + } + if (c->vendor == 0) { + dev_err(card->dev, "emu10k1: Card not recognised\n"); + return -ENOENT; + } + emu->card_capabilities = c; + if (c->subsystem && !subsystem) + dev_dbg(card->dev, "Sound card name = %s\n", c->name); + else if (subsystem) + dev_dbg(card->dev, "Sound card name = %s, " + "vendor = 0x%x, device = 0x%x, subsystem = 0x%x. " + "Forced to subsystem = 0x%x\n", c->name, + pci->vendor, pci->device, emu->serial, c->subsystem); + else + dev_dbg(card->dev, "Sound card name = %s, " + "vendor = 0x%x, device = 0x%x, subsystem = 0x%x.\n", + c->name, pci->vendor, pci->device, + emu->serial); + + if (!*card->id && c->id) + strscpy(card->id, c->id, sizeof(card->id)); + + is_audigy = emu->audigy = c->emu10k2_chip; + + snd_emu10k1_detect_iommu(emu); + + /* set addressing mode */ + emu->address_mode = is_audigy ? 0 : 1; + /* set the DMA transfer mask */ + emu->dma_mask = emu->address_mode ? EMU10K1_DMA_MASK : AUDIGY_DMA_MASK; + if (dma_set_mask_and_coherent(&pci->dev, emu->dma_mask) < 0) { + dev_err(card->dev, + "architecture does not support PCI busmaster DMA with mask 0x%lx\n", + emu->dma_mask); + return -ENXIO; + } + if (is_audigy) + emu->gpr_base = A_FXGPREGBASE; + else + emu->gpr_base = FXGPREGBASE; + + err = pcim_request_all_regions(pci, "EMU10K1"); + if (err < 0) + return err; + emu->port = pci_resource_start(pci, 0); + + emu->max_cache_pages = max_cache_bytes >> PAGE_SHIFT; + + page_table_size = sizeof(u32) * (emu->address_mode ? MAXPAGES1 : + MAXPAGES0); + if (snd_emu10k1_alloc_pages_maybe_wider(emu, page_table_size, + &emu->ptb_pages) < 0) + return -ENOMEM; + dev_dbg(card->dev, "page table address range is %.8lx:%.8lx\n", + (unsigned long)emu->ptb_pages.addr, + (unsigned long)(emu->ptb_pages.addr + emu->ptb_pages.bytes)); + + emu->page_ptr_table = vmalloc(array_size(sizeof(void *), + emu->max_cache_pages)); + emu->page_addr_table = vmalloc(array_size(sizeof(unsigned long), + emu->max_cache_pages)); + if (!emu->page_ptr_table || !emu->page_addr_table) + return -ENOMEM; + + if (snd_emu10k1_alloc_pages_maybe_wider(emu, EMUPAGESIZE, + &emu->silent_page) < 0) + return -ENOMEM; + dev_dbg(card->dev, "silent page range is %.8lx:%.8lx\n", + (unsigned long)emu->silent_page.addr, + (unsigned long)(emu->silent_page.addr + + emu->silent_page.bytes)); + + emu->memhdr = snd_util_memhdr_new(emu->max_cache_pages * PAGE_SIZE); + if (!emu->memhdr) + return -ENOMEM; + emu->memhdr->block_extra_size = sizeof(struct snd_emu10k1_memblk) - + sizeof(struct snd_util_memblk); + + pci_set_master(pci); + + // The masks are not used for Audigy. + // FIXME: these should come from the card_capabilites table. + if (extin_mask == 0) + extin_mask = 0x3fcf; // EXTIN_* + if (extout_mask == 0) + extout_mask = 0x7fff; // EXTOUT_* + emu->fx8010.extin_mask = extin_mask; + emu->fx8010.extout_mask = extout_mask; + emu->enable_ir = enable_ir; + + if (emu->card_capabilities->ca_cardbus_chip) { + err = snd_emu10k1_cardbus_init(emu); + if (err < 0) + return err; + } + if (emu->card_capabilities->ecard) { + err = snd_emu10k1_ecard_init(emu); + if (err < 0) + return err; + } else if (emu->card_capabilities->emu_model) { + err = snd_emu10k1_emu1010_init(emu); + if (err < 0) + return err; + } else { + /* 5.1: Enable the additional AC97 Slots. If the emu10k1 version + does not support this, it shouldn't do any harm */ + snd_emu10k1_ptr_write(emu, AC97SLOT, 0, + AC97SLOT_CNTR|AC97SLOT_LFE); + } + + /* initialize TRAM setup */ + emu->fx8010.itram_size = (16 * 1024)/2; + emu->fx8010.etram_pages.area = NULL; + emu->fx8010.etram_pages.bytes = 0; + + /* irq handler must be registered after I/O ports are activated */ + if (devm_request_irq(&pci->dev, pci->irq, snd_emu10k1_interrupt, + IRQF_SHARED, KBUILD_MODNAME, emu)) + return -EBUSY; + emu->irq = pci->irq; + card->sync_irq = emu->irq; + + /* + * Init to 0x02109204 : + * Clock accuracy = 0 (1000ppm) + * Sample Rate = 2 (48kHz) + * Audio Channel = 1 (Left of 2) + * Source Number = 0 (Unspecified) + * Generation Status = 1 (Original for Cat Code 12) + * Cat Code = 12 (Digital Signal Mixer) + * Mode = 0 (Mode 0) + * Emphasis = 0 (None) + * CP = 1 (Copyright unasserted) + * AN = 0 (Audio data) + * P = 0 (Consumer) + */ + emu->spdif_bits[0] = emu->spdif_bits[1] = + emu->spdif_bits[2] = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | + SPCS_GENERATIONSTATUS | 0x00001200 | + 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT; + + /* Clear silent pages and set up pointers */ + memset(emu->silent_page.area, 0, emu->silent_page.bytes); + silent_page = emu->silent_page.addr << emu->address_mode; + pgtbl = (__le32 *)emu->ptb_pages.area; + for (idx = 0; idx < (emu->address_mode ? MAXPAGES1 : MAXPAGES0); idx++) + pgtbl[idx] = cpu_to_le32(silent_page | idx); + + /* set up voice indices */ + for (idx = 0; idx < NUM_G; idx++) + emu->voices[idx].number = idx; + + err = snd_emu10k1_init(emu, enable_ir); + if (err < 0) + return err; +#ifdef CONFIG_PM_SLEEP + err = alloc_pm_buffer(emu); + if (err < 0) + return err; +#endif + + /* Initialize the effect engine */ + err = snd_emu10k1_init_efx(emu); + if (err < 0) + return err; + snd_emu10k1_audio_enable(emu); + +#ifdef CONFIG_SND_PROC_FS + snd_emu10k1_proc_init(emu); +#endif + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static const unsigned char saved_regs[] = { + CPF, PTRX, CVCF, VTFT, Z1, Z2, PSST, DSL, CCCA, CCR, CLP, + FXRT, MAPA, MAPB, ENVVOL, ATKHLDV, DCYSUSV, LFOVAL1, ENVVAL, + ATKHLDM, DCYSUSM, LFOVAL2, IP, IFATN, PEFE, FMMOD, TREMFRQ, FM2FRQ2, + TEMPENV, ADCCR, FXWC, MICBA, ADCBA, FXBA, + MICBS, ADCBS, FXBS, CDCS, GPSCS, SPCS0, SPCS1, SPCS2, + SPBYPASS, AC97SLOT, CDSRCS, GPSRCS, ZVSRCS, MICIDX, ADCIDX, FXIDX, + 0xff /* end */ +}; +static const unsigned char saved_regs_audigy[] = { + A_ADCIDX, A_MICIDX, A_FXWC1, A_FXWC2, A_EHC, + A_FXRT2, A_SENDAMOUNTS, A_FXRT1, + 0xff /* end */ +}; + +static int alloc_pm_buffer(struct snd_emu10k1 *emu) +{ + int size; + + size = ARRAY_SIZE(saved_regs); + if (emu->audigy) + size += ARRAY_SIZE(saved_regs_audigy); + emu->saved_ptr = vmalloc(array3_size(4, NUM_G, size)); + if (!emu->saved_ptr) + return -ENOMEM; + if (snd_emu10k1_efx_alloc_pm_buffer(emu) < 0) + return -ENOMEM; + if (emu->card_capabilities->ca0151_chip && + snd_p16v_alloc_pm_buffer(emu) < 0) + return -ENOMEM; + return 0; +} + +static void free_pm_buffer(struct snd_emu10k1 *emu) +{ + vfree(emu->saved_ptr); + snd_emu10k1_efx_free_pm_buffer(emu); + if (emu->card_capabilities->ca0151_chip) + snd_p16v_free_pm_buffer(emu); +} + +void snd_emu10k1_suspend_regs(struct snd_emu10k1 *emu) +{ + int i; + const unsigned char *reg; + unsigned int *val; + + val = emu->saved_ptr; + for (reg = saved_regs; *reg != 0xff; reg++) + for (i = 0; i < NUM_G; i++, val++) + *val = snd_emu10k1_ptr_read(emu, *reg, i); + if (emu->audigy) { + for (reg = saved_regs_audigy; *reg != 0xff; reg++) + for (i = 0; i < NUM_G; i++, val++) + *val = snd_emu10k1_ptr_read(emu, *reg, i); + } + if (emu->audigy) + emu->saved_a_iocfg = inw(emu->port + A_IOCFG); + emu->saved_hcfg = inl(emu->port + HCFG); +} + +void snd_emu10k1_resume_init(struct snd_emu10k1 *emu) +{ + if (emu->card_capabilities->ca_cardbus_chip) + snd_emu10k1_cardbus_init(emu); + if (emu->card_capabilities->ecard) + snd_emu10k1_ecard_init(emu); + else if (emu->card_capabilities->emu_model) + snd_emu10k1_emu1010_init(emu); + else + snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_CNTR|AC97SLOT_LFE); + snd_emu10k1_init(emu, emu->enable_ir); +} + +void snd_emu10k1_resume_regs(struct snd_emu10k1 *emu) +{ + int i; + const unsigned char *reg; + unsigned int *val; + + snd_emu10k1_audio_enable(emu); + + /* resore for spdif */ + if (emu->audigy) + outw(emu->saved_a_iocfg, emu->port + A_IOCFG); + outl(emu->saved_hcfg, emu->port + HCFG); + + val = emu->saved_ptr; + for (reg = saved_regs; *reg != 0xff; reg++) + for (i = 0; i < NUM_G; i++, val++) + snd_emu10k1_ptr_write(emu, *reg, i, *val); + if (emu->audigy) { + for (reg = saved_regs_audigy; *reg != 0xff; reg++) + for (i = 0; i < NUM_G; i++, val++) + snd_emu10k1_ptr_write(emu, *reg, i, *val); + } +} +#endif diff --git a/sound/pci/emu10k1/emu10k1_patch.c b/sound/pci/emu10k1/emu10k1_patch.c new file mode 100644 index 000000000..806b4f95c --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_patch.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Patch transfer callback for Emu10k1 + * + * Copyright (C) 2000 Takashi iwai + */ +/* + * All the code for loading in a patch. There is very little that is + * chip specific here. Just the actual writing to the board. + */ + +#include "emu10k1_synth_local.h" + +/* + */ +#define BLANK_LOOP_START 4 +#define BLANK_LOOP_END 8 +#define BLANK_LOOP_SIZE 12 +#define BLANK_HEAD_SIZE 3 + +/* + * allocate a sample block and copy data from userspace + */ +int +snd_emu10k1_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr, + const void __user *data, long count) +{ + u8 fill; + u32 xor; + int shift; + int offset; + int truesize, size, blocksize; + int loop_start, loop_end, loop_size, data_end, unroll; + struct snd_emu10k1 *emu; + + emu = rec->hw; + if (snd_BUG_ON(!sp || !hdr)) + return -EINVAL; + + if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP | SNDRV_SFNT_SAMPLE_REVERSE_LOOP)) { + /* should instead return -ENOTSUPP; but compatibility */ + dev_warn(emu->card->dev, + "Emu10k1 wavetable patch %d with unsupported loop feature\n", + sp->v.sample); + } + + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) { + shift = 0; + fill = 0x80; + xor = (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_UNSIGNED) ? 0 : 0x80808080; + } else { + shift = 1; + fill = 0; + xor = (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_UNSIGNED) ? 0x80008000 : 0; + } + + /* compute true data size to be loaded */ + truesize = sp->v.size + BLANK_HEAD_SIZE; + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) { + truesize += BLANK_LOOP_SIZE; + /* if no blank loop is attached in the sample, add it */ + 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; + } + } + + loop_start = sp->v.loopstart; + loop_end = sp->v.loopend; + loop_size = loop_end - loop_start; + if (!loop_size) + return -EINVAL; + data_end = sp->v.end; + + /* recalculate offset */ + sp->v.start += BLANK_HEAD_SIZE; + sp->v.end += BLANK_HEAD_SIZE; + sp->v.loopstart += BLANK_HEAD_SIZE; + sp->v.loopend += BLANK_HEAD_SIZE; + + // Automatic pre-filling of the cache does not work in the presence + // of loops (*), and we don't want to fill it manually, as that is + // fiddly and slow. So we unroll the loop until the loop end is + // beyond the cache size. + // (*) Strictly speaking, a single iteration is supported (that's + // how it works when the playback engine runs), but handling this + // special case is not worth it. + unroll = 0; + while (sp->v.loopend < 64) { + truesize += loop_size; + sp->v.loopstart += loop_size; + sp->v.loopend += loop_size; + sp->v.end += loop_size; + unroll++; + } + + /* try to allocate a memory block */ + blocksize = truesize << shift; + sp->block = snd_emu10k1_synth_alloc(emu, blocksize); + if (sp->block == NULL) { + dev_dbg(emu->card->dev, + "synth malloc failed (size=%d)\n", blocksize); + /* not ENOMEM (for compatibility with OSS) */ + return -ENOSPC; + } + /* set the total size */ + sp->v.truesize = blocksize; + + /* write blank samples at head */ + offset = 0; + size = BLANK_HEAD_SIZE << shift; + snd_emu10k1_synth_memset(emu, sp->block, offset, size, fill); + offset += size; + + /* copy provided samples */ + if (unroll && loop_end <= data_end) { + size = loop_end << shift; + if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size, xor)) + goto faulty; + offset += size; + + data += loop_start << shift; + while (--unroll > 0) { + size = loop_size << shift; + if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size, xor)) + goto faulty; + offset += size; + } + + size = (data_end - loop_start) << shift; + } else { + size = data_end << shift; + } + if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size, xor)) + goto faulty; + offset += size; + + /* clear rest of samples (if any) */ + if (offset < blocksize) + snd_emu10k1_synth_memset(emu, sp->block, offset, blocksize - offset, fill); + + return 0; + +faulty: + snd_emu10k1_synth_free(emu, sp->block); + sp->block = NULL; + return -EFAULT; +} + +/* + * free a sample block + */ +int +snd_emu10k1_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr) +{ + struct snd_emu10k1 *emu; + + emu = rec->hw; + if (snd_BUG_ON(!sp || !hdr)) + return -EINVAL; + + if (sp->block) { + snd_emu10k1_synth_free(emu, sp->block); + sp->block = NULL; + } + return 0; +} + diff --git a/sound/pci/emu10k1/emu10k1_synth.c b/sound/pci/emu10k1/emu10k1_synth.c new file mode 100644 index 000000000..26499c31e --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_synth.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2000 Takashi Iwai + * + * Routines for control of EMU10K1 WaveTable synth + */ + +#include "emu10k1_synth_local.h" +#include +#include + +MODULE_AUTHOR("Takashi Iwai"); +MODULE_DESCRIPTION("Routines for control of EMU10K1 WaveTable synth"); +MODULE_LICENSE("GPL"); + +/* + * create a new hardware dependent device for Emu10k1 + */ +static int snd_emu10k1_synth_probe(struct snd_seq_device *dev) +{ + struct snd_emux *emux; + struct snd_emu10k1 *hw; + struct snd_emu10k1_synth_arg *arg; + + arg = SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (arg == NULL) + return -EINVAL; + + if (arg->seq_ports <= 0) + return 0; /* nothing */ + if (arg->max_voices < 1) + arg->max_voices = 1; + else if (arg->max_voices > 64) + arg->max_voices = 64; + + if (snd_emux_new(&emux) < 0) + return -ENOMEM; + + snd_emu10k1_ops_setup(emux); + hw = arg->hwptr; + emux->hw = hw; + emux->max_voices = arg->max_voices; + emux->num_ports = arg->seq_ports; + emux->memhdr = hw->memhdr; + /* maximum two ports */ + emux->midi_ports = arg->seq_ports < 2 ? arg->seq_ports : 2; + /* audigy has two external midis */ + emux->midi_devidx = hw->audigy ? 2 : 1; + emux->linear_panning = 0; + emux->hwdep_idx = 2; /* FIXED */ + + if (snd_emux_register(emux, dev->card, arg->index, "Emu10k1") < 0) { + snd_emux_free(emux); + return -ENOMEM; + } + + guard(spinlock_irq)(&hw->voice_lock); + hw->synth = emux; + hw->get_synth_voice = snd_emu10k1_synth_get_voice; + + dev->driver_data = emux; + + return 0; +} + +static void snd_emu10k1_synth_remove(struct snd_seq_device *dev) +{ + struct snd_emux *emux; + struct snd_emu10k1 *hw; + + if (dev->driver_data == NULL) + return; /* not registered actually */ + + emux = dev->driver_data; + + hw = emux->hw; + scoped_guard(spinlock_irq, &hw->voice_lock) { + hw->synth = NULL; + hw->get_synth_voice = NULL; + } + + snd_emux_free(emux); +} + +/* + * INIT part + */ + +static struct snd_seq_driver emu10k1_synth_driver = { + .probe = snd_emu10k1_synth_probe, + .remove = snd_emu10k1_synth_remove, + .driver = { + .name = KBUILD_MODNAME, + }, + .id = SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH, + .argsize = sizeof(struct snd_emu10k1_synth_arg), +}; + +module_snd_seq_driver(emu10k1_synth_driver); diff --git a/sound/pci/emu10k1/emu10k1_synth_local.h b/sound/pci/emu10k1/emu10k1_synth_local.h new file mode 100644 index 000000000..113736953 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_synth_local.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __EMU10K1_SYNTH_LOCAL_H +#define __EMU10K1_SYNTH_LOCAL_H +/* + * Local defininitons for Emu10k1 wavetable + * + * Copyright (C) 2000 Takashi Iwai + */ + +#include +#include +#include + +/* emu10k1_patch.c */ +int snd_emu10k1_sample_new(struct snd_emux *private_data, + struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr, + const void __user *_data, long count); +int snd_emu10k1_sample_free(struct snd_emux *private_data, + struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr); +int snd_emu10k1_memhdr_init(struct snd_emux *emu); + +/* emu10k1_callback.c */ +void snd_emu10k1_ops_setup(struct snd_emux *emu); +int snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw); + + +#endif /* __EMU10K1_SYNTH_LOCAL_H */ diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c new file mode 100644 index 000000000..ed4630c83 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1x.c @@ -0,0 +1,1533 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Francisco Moraes + * Driver EMU10K1X chips + * + * Parts of this code were adapted from audigyls.c driver which is + * Copyright (c) by James Courtier-Dutton + * + * BUGS: + * -- + * + * TODO: + * + * Chips (SB0200 model): + * - EMU10K1X-DBQ + * - STAC 9708T + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Francisco Moraes "); +MODULE_DESCRIPTION("EMU10K1X"); +MODULE_LICENSE("GPL"); + +// module parameters (see "Module Parameters") +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the EMU10K1X soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the EMU10K1X soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable the EMU10K1X soundcard."); + + +// some definitions were borrowed from emu10k1 driver as they seem to be the same +/************************************************************************************************/ +/* PCI function 0 registers, address = + PCIBASE0 */ +/************************************************************************************************/ + +#define PTR 0x00 /* Indexed register set pointer register */ + /* NOTE: The CHANNELNUM and ADDRESS words can */ + /* be modified independently of each other. */ + +#define DATA 0x04 /* Indexed register set data register */ + +#define IPR 0x08 /* Global interrupt pending register */ + /* Clear pending interrupts by writing a 1 to */ + /* the relevant bits and zero to the other bits */ +#define IPR_MIDITRANSBUFEMPTY 0x00000001 /* MIDI UART transmit buffer empty */ +#define IPR_MIDIRECVBUFEMPTY 0x00000002 /* MIDI UART receive buffer empty */ +#define IPR_CH_0_LOOP 0x00000800 /* Channel 0 loop */ +#define IPR_CH_0_HALF_LOOP 0x00000100 /* Channel 0 half loop */ +#define IPR_CAP_0_LOOP 0x00080000 /* Channel capture loop */ +#define IPR_CAP_0_HALF_LOOP 0x00010000 /* Channel capture half loop */ + +#define INTE 0x0c /* Interrupt enable register */ +#define INTE_MIDITXENABLE 0x00000001 /* Enable MIDI transmit-buffer-empty interrupts */ +#define INTE_MIDIRXENABLE 0x00000002 /* Enable MIDI receive-buffer-empty interrupts */ +#define INTE_CH_0_LOOP 0x00000800 /* Channel 0 loop */ +#define INTE_CH_0_HALF_LOOP 0x00000100 /* Channel 0 half loop */ +#define INTE_CAP_0_LOOP 0x00080000 /* Channel capture loop */ +#define INTE_CAP_0_HALF_LOOP 0x00010000 /* Channel capture half loop */ + +#define HCFG 0x14 /* Hardware config register */ + +#define HCFG_LOCKSOUNDCACHE 0x00000008 /* 1 = Cancel bustmaster accesses to soundcache */ + /* NOTE: This should generally never be used. */ +#define HCFG_AUDIOENABLE 0x00000001 /* 0 = CODECs transmit zero-valued samples */ + /* Should be set to 1 when the EMU10K1 is */ + /* completely initialized. */ +#define GPIO 0x18 /* Defaults: 00001080-Analog, 00001000-SPDIF. */ + + +#define AC97DATA 0x1c /* AC97 register set data register (16 bit) */ + +#define AC97ADDRESS 0x1e /* AC97 register set address register (8 bit) */ + +/********************************************************************************************************/ +/* Emu10k1x pointer-offset register set, accessed through the PTR and DATA registers */ +/********************************************************************************************************/ +#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */ + /* One list entry: 4 bytes for DMA address, + * 4 bytes for period_size << 16. + * One list entry is 8 bytes long. + * One list entry for each period in the buffer. + */ +#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */ +#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */ +#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA address */ +#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size */ +#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Sample currently in DAC */ +#define PLAYBACK_UNKNOWN1 0x07 +#define PLAYBACK_UNKNOWN2 0x08 + +/* Only one capture channel supported */ +#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */ +#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */ +#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */ +#define CAPTURE_UNKNOWN 0x13 + +/* From 0x20 - 0x3f, last samples played on each channel */ + +#define TRIGGER_CHANNEL 0x40 /* Trigger channel playback */ +#define TRIGGER_CHANNEL_0 0x00000001 /* Trigger channel 0 */ +#define TRIGGER_CHANNEL_1 0x00000002 /* Trigger channel 1 */ +#define TRIGGER_CHANNEL_2 0x00000004 /* Trigger channel 2 */ +#define TRIGGER_CAPTURE 0x00000100 /* Trigger capture channel */ + +#define ROUTING 0x41 /* Setup sound routing ? */ +#define ROUTING_FRONT_LEFT 0x00000001 +#define ROUTING_FRONT_RIGHT 0x00000002 +#define ROUTING_REAR_LEFT 0x00000004 +#define ROUTING_REAR_RIGHT 0x00000008 +#define ROUTING_CENTER_LFE 0x00010000 + +#define SPCS0 0x42 /* SPDIF output Channel Status 0 register */ + +#define SPCS1 0x43 /* SPDIF output Channel Status 1 register */ + +#define SPCS2 0x44 /* SPDIF output Channel Status 2 register */ + +#define SPCS_CLKACCYMASK 0x30000000 /* Clock accuracy */ +#define SPCS_CLKACCY_1000PPM 0x00000000 /* 1000 parts per million */ +#define SPCS_CLKACCY_50PPM 0x10000000 /* 50 parts per million */ +#define SPCS_CLKACCY_VARIABLE 0x20000000 /* Variable accuracy */ +#define SPCS_SAMPLERATEMASK 0x0f000000 /* Sample rate */ +#define SPCS_SAMPLERATE_44 0x00000000 /* 44.1kHz sample rate */ +#define SPCS_SAMPLERATE_48 0x02000000 /* 48kHz sample rate */ +#define SPCS_SAMPLERATE_32 0x03000000 /* 32kHz sample rate */ +#define SPCS_CHANNELNUMMASK 0x00f00000 /* Channel number */ +#define SPCS_CHANNELNUM_UNSPEC 0x00000000 /* Unspecified channel number */ +#define SPCS_CHANNELNUM_LEFT 0x00100000 /* Left channel */ +#define SPCS_CHANNELNUM_RIGHT 0x00200000 /* Right channel */ +#define SPCS_SOURCENUMMASK 0x000f0000 /* Source number */ +#define SPCS_SOURCENUM_UNSPEC 0x00000000 /* Unspecified source number */ +#define SPCS_GENERATIONSTATUS 0x00008000 /* Originality flag (see IEC-958 spec) */ +#define SPCS_CATEGORYCODEMASK 0x00007f00 /* Category code (see IEC-958 spec) */ +#define SPCS_MODEMASK 0x000000c0 /* Mode (see IEC-958 spec) */ +#define SPCS_EMPHASISMASK 0x00000038 /* Emphasis */ +#define SPCS_EMPHASIS_NONE 0x00000000 /* No emphasis */ +#define SPCS_EMPHASIS_50_15 0x00000008 /* 50/15 usec 2 channel */ +#define SPCS_COPYRIGHT 0x00000004 /* Copyright asserted flag -- do not modify */ +#define SPCS_NOTAUDIODATA 0x00000002 /* 0 = Digital audio, 1 = not audio */ +#define SPCS_PROFESSIONAL 0x00000001 /* 0 = Consumer (IEC-958), 1 = pro (AES3-1992) */ + +#define SPDIF_SELECT 0x45 /* Enables SPDIF or Analogue outputs 0-Analogue, 0x700-SPDIF */ + +/* This is the MPU port on the card */ +#define MUDATA 0x47 +#define MUCMD 0x48 +#define MUSTAT MUCMD + +/* From 0x50 - 0x5f, last samples captured */ + +/* + * The hardware has 3 channels for playback and 1 for capture. + * - channel 0 is the front channel + * - channel 1 is the rear channel + * - channel 2 is the center/lfe channel + * Volume is controlled by the AC97 for the front and rear channels by + * the PCM Playback Volume, Sigmatel Surround Playback Volume and + * Surround Playback Volume. The Sigmatel 4-Speaker Stereo switch affects + * the front/rear channel mixing in the REAR OUT jack. When using the + * 4-Speaker Stereo, both front and rear channels will be mixed in the + * REAR OUT. + * The center/lfe channel has no volume control and cannot be muted during + * playback. + */ + +struct emu10k1x_voice { + struct emu10k1x *emu; + int number; + int use; + + struct emu10k1x_pcm *epcm; +}; + +struct emu10k1x_pcm { + struct emu10k1x *emu; + struct snd_pcm_substream *substream; + struct emu10k1x_voice *voice; + unsigned short running; +}; + +struct emu10k1x_midi { + struct emu10k1x *emu; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream_input; + struct snd_rawmidi_substream *substream_output; + unsigned int midi_mode; + spinlock_t input_lock; + spinlock_t output_lock; + spinlock_t open_lock; + int tx_enable, rx_enable; + int port; + int ipr_tx, ipr_rx; + void (*interrupt)(struct emu10k1x *emu, unsigned int status); +}; + +// definition of the chip-specific record +struct emu10k1x { + struct snd_card *card; + struct pci_dev *pci; + + unsigned long port; + int irq; + + unsigned char revision; /* chip revision */ + unsigned int serial; /* serial number */ + unsigned short model; /* subsystem id */ + + spinlock_t emu_lock; + spinlock_t voice_lock; + + struct snd_ac97 *ac97; + struct snd_pcm *pcm; + + struct emu10k1x_voice voices[3]; + struct emu10k1x_voice capture_voice; + u32 spdif_bits[3]; // SPDIF out setup + + struct snd_dma_buffer *dma_buffer; + + struct emu10k1x_midi midi; +}; + +/* hardware definition */ +static const struct snd_pcm_hardware snd_emu10k1x_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (32*1024), + .period_bytes_min = 64, + .period_bytes_max = (16*1024), + .periods_min = 2, + .periods_max = 8, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_emu10k1x_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (32*1024), + .period_bytes_min = 64, + .period_bytes_max = (16*1024), + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +static unsigned int snd_emu10k1x_ptr_read(struct emu10k1x * emu, + unsigned int reg, + unsigned int chn) +{ + unsigned int regptr; + + regptr = (reg << 16) | chn; + + guard(spinlock_irqsave)(&emu->emu_lock); + outl(regptr, emu->port + PTR); + return inl(emu->port + DATA); +} + +static void snd_emu10k1x_ptr_write(struct emu10k1x *emu, + unsigned int reg, + unsigned int chn, + unsigned int data) +{ + unsigned int regptr; + + regptr = (reg << 16) | chn; + + guard(spinlock_irqsave)(&emu->emu_lock); + outl(regptr, emu->port + PTR); + outl(data, emu->port + DATA); +} + +static void snd_emu10k1x_intr_enable(struct emu10k1x *emu, unsigned int intrenb) +{ + unsigned int intr_enable; + + guard(spinlock_irqsave)(&emu->emu_lock); + intr_enable = inl(emu->port + INTE) | intrenb; + outl(intr_enable, emu->port + INTE); +} + +static void snd_emu10k1x_intr_disable(struct emu10k1x *emu, unsigned int intrenb) +{ + unsigned int intr_enable; + + guard(spinlock_irqsave)(&emu->emu_lock); + intr_enable = inl(emu->port + INTE) & ~intrenb; + outl(intr_enable, emu->port + INTE); +} + +static void snd_emu10k1x_gpio_write(struct emu10k1x *emu, unsigned int value) +{ + guard(spinlock_irqsave)(&emu->emu_lock); + outl(value, emu->port + GPIO); +} + +static void snd_emu10k1x_pcm_free_substream(struct snd_pcm_runtime *runtime) +{ + kfree(runtime->private_data); +} + +static void snd_emu10k1x_pcm_interrupt(struct emu10k1x *emu, struct emu10k1x_voice *voice) +{ + struct emu10k1x_pcm *epcm; + + epcm = voice->epcm; + if (!epcm) + return; + if (epcm->substream == NULL) + return; +#if 0 + dev_info(emu->card->dev, + "IRQ: position = 0x%x, period = 0x%x, size = 0x%x\n", + epcm->substream->ops->pointer(epcm->substream), + snd_pcm_lib_period_bytes(epcm->substream), + snd_pcm_lib_buffer_bytes(epcm->substream)); +#endif + snd_pcm_period_elapsed(epcm->substream); +} + +/* open callback */ +static int snd_emu10k1x_playback_open(struct snd_pcm_substream *substream) +{ + struct emu10k1x *chip = snd_pcm_substream_chip(substream); + struct emu10k1x_pcm *epcm; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); + if (err < 0) + return err; + + epcm = kzalloc_obj(*epcm); + if (epcm == NULL) + return -ENOMEM; + epcm->emu = chip; + epcm->substream = substream; + + runtime->private_data = epcm; + runtime->private_free = snd_emu10k1x_pcm_free_substream; + + runtime->hw = snd_emu10k1x_playback_hw; + + return 0; +} + +/* close callback */ +static int snd_emu10k1x_playback_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* hw_params callback */ +static int snd_emu10k1x_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + + if (! epcm->voice) { + epcm->voice = &epcm->emu->voices[substream->pcm->device]; + epcm->voice->use = 1; + epcm->voice->epcm = epcm; + } + + return 0; +} + +/* hw_free callback */ +static int snd_emu10k1x_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct emu10k1x_pcm *epcm; + + if (runtime->private_data == NULL) + return 0; + + epcm = runtime->private_data; + + if (epcm->voice) { + epcm->voice->use = 0; + epcm->voice->epcm = NULL; + epcm->voice = NULL; + } + + return 0; +} + +/* prepare callback */ +static int snd_emu10k1x_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct emu10k1x *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + int voice = epcm->voice->number; + u32 *table_base = (u32 *)(emu->dma_buffer->area+1024*voice); + u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size); + int i; + + for(i = 0; i < runtime->periods; i++) { + *table_base++=runtime->dma_addr+(i*period_size_bytes); + *table_base++=period_size_bytes<<16; + } + + snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_ADDR, voice, emu->dma_buffer->addr+1024*voice); + snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_SIZE, voice, (runtime->periods - 1) << 19); + snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_PTR, voice, 0); + snd_emu10k1x_ptr_write(emu, PLAYBACK_POINTER, voice, 0); + snd_emu10k1x_ptr_write(emu, PLAYBACK_UNKNOWN1, voice, 0); + snd_emu10k1x_ptr_write(emu, PLAYBACK_UNKNOWN2, voice, 0); + snd_emu10k1x_ptr_write(emu, PLAYBACK_DMA_ADDR, voice, runtime->dma_addr); + + snd_emu10k1x_ptr_write(emu, PLAYBACK_PERIOD_SIZE, voice, frames_to_bytes(runtime, runtime->period_size)<<16); + + return 0; +} + +/* trigger callback */ +static int snd_emu10k1x_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct emu10k1x *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + int channel = epcm->voice->number; + int result = 0; + + /* + dev_dbg(emu->card->dev, + "trigger - emu10k1x = 0x%x, cmd = %i, pointer = %d\n", + (int)emu, cmd, (int)substream->ops->pointer(substream)); + */ + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if(runtime->periods == 2) + snd_emu10k1x_intr_enable(emu, (INTE_CH_0_LOOP | INTE_CH_0_HALF_LOOP) << channel); + else + snd_emu10k1x_intr_enable(emu, INTE_CH_0_LOOP << channel); + epcm->running = 1; + snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0)|(TRIGGER_CHANNEL_0<running = 0; + snd_emu10k1x_intr_disable(emu, (INTE_CH_0_LOOP | INTE_CH_0_HALF_LOOP) << channel); + snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0) & ~(TRIGGER_CHANNEL_0<runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + int channel = epcm->voice->number; + snd_pcm_uframes_t ptr = 0, ptr1 = 0, ptr2= 0,ptr3 = 0,ptr4 = 0; + + if (!epcm->running) + return 0; + + ptr3 = snd_emu10k1x_ptr_read(emu, PLAYBACK_LIST_PTR, channel); + ptr1 = snd_emu10k1x_ptr_read(emu, PLAYBACK_POINTER, channel); + ptr4 = snd_emu10k1x_ptr_read(emu, PLAYBACK_LIST_PTR, channel); + + if(ptr4 == 0 && ptr1 == frames_to_bytes(runtime, runtime->buffer_size)) + return 0; + + if (ptr3 != ptr4) + ptr1 = snd_emu10k1x_ptr_read(emu, PLAYBACK_POINTER, channel); + ptr2 = bytes_to_frames(runtime, ptr1); + ptr2 += (ptr4 >> 3) * runtime->period_size; + ptr = ptr2; + + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + + return ptr; +} + +/* operators */ +static const struct snd_pcm_ops snd_emu10k1x_playback_ops = { + .open = snd_emu10k1x_playback_open, + .close = snd_emu10k1x_playback_close, + .hw_params = snd_emu10k1x_pcm_hw_params, + .hw_free = snd_emu10k1x_pcm_hw_free, + .prepare = snd_emu10k1x_pcm_prepare, + .trigger = snd_emu10k1x_pcm_trigger, + .pointer = snd_emu10k1x_pcm_pointer, +}; + +/* open_capture callback */ +static int snd_emu10k1x_pcm_open_capture(struct snd_pcm_substream *substream) +{ + struct emu10k1x *chip = snd_pcm_substream_chip(substream); + struct emu10k1x_pcm *epcm; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); + if (err < 0) + return err; + + epcm = kzalloc_obj(*epcm); + if (epcm == NULL) + return -ENOMEM; + + epcm->emu = chip; + epcm->substream = substream; + + runtime->private_data = epcm; + runtime->private_free = snd_emu10k1x_pcm_free_substream; + + runtime->hw = snd_emu10k1x_capture_hw; + + return 0; +} + +/* close callback */ +static int snd_emu10k1x_pcm_close_capture(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* hw_params callback */ +static int snd_emu10k1x_pcm_hw_params_capture(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + + if (! epcm->voice) { + if (epcm->emu->capture_voice.use) + return -EBUSY; + epcm->voice = &epcm->emu->capture_voice; + epcm->voice->epcm = epcm; + epcm->voice->use = 1; + } + + return 0; +} + +/* hw_free callback */ +static int snd_emu10k1x_pcm_hw_free_capture(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + struct emu10k1x_pcm *epcm; + + if (runtime->private_data == NULL) + return 0; + epcm = runtime->private_data; + + if (epcm->voice) { + epcm->voice->use = 0; + epcm->voice->epcm = NULL; + epcm->voice = NULL; + } + + return 0; +} + +/* prepare capture callback */ +static int snd_emu10k1x_pcm_prepare_capture(struct snd_pcm_substream *substream) +{ + struct emu10k1x *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_emu10k1x_ptr_write(emu, CAPTURE_DMA_ADDR, 0, runtime->dma_addr); + snd_emu10k1x_ptr_write(emu, CAPTURE_BUFFER_SIZE, 0, frames_to_bytes(runtime, runtime->buffer_size)<<16); // buffer size in bytes + snd_emu10k1x_ptr_write(emu, CAPTURE_POINTER, 0, 0); + snd_emu10k1x_ptr_write(emu, CAPTURE_UNKNOWN, 0, 0); + + return 0; +} + +/* trigger_capture callback */ +static int snd_emu10k1x_pcm_trigger_capture(struct snd_pcm_substream *substream, + int cmd) +{ + struct emu10k1x *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + int result = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_emu10k1x_intr_enable(emu, INTE_CAP_0_LOOP | + INTE_CAP_0_HALF_LOOP); + snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0)|TRIGGER_CAPTURE); + epcm->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + epcm->running = 0; + snd_emu10k1x_intr_disable(emu, INTE_CAP_0_LOOP | + INTE_CAP_0_HALF_LOOP); + snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0) & ~(TRIGGER_CAPTURE)); + break; + default: + result = -EINVAL; + break; + } + return result; +} + +/* pointer_capture callback */ +static snd_pcm_uframes_t +snd_emu10k1x_pcm_pointer_capture(struct snd_pcm_substream *substream) +{ + struct emu10k1x *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + snd_pcm_uframes_t ptr; + + if (!epcm->running) + return 0; + + ptr = bytes_to_frames(runtime, snd_emu10k1x_ptr_read(emu, CAPTURE_POINTER, 0)); + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + + return ptr; +} + +static const struct snd_pcm_ops snd_emu10k1x_capture_ops = { + .open = snd_emu10k1x_pcm_open_capture, + .close = snd_emu10k1x_pcm_close_capture, + .hw_params = snd_emu10k1x_pcm_hw_params_capture, + .hw_free = snd_emu10k1x_pcm_hw_free_capture, + .prepare = snd_emu10k1x_pcm_prepare_capture, + .trigger = snd_emu10k1x_pcm_trigger_capture, + .pointer = snd_emu10k1x_pcm_pointer_capture, +}; + +static unsigned short snd_emu10k1x_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct emu10k1x *emu = ac97->private_data; + + guard(spinlock_irqsave)(&emu->emu_lock); + outb(reg, emu->port + AC97ADDRESS); + return inw(emu->port + AC97DATA); +} + +static void snd_emu10k1x_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + struct emu10k1x *emu = ac97->private_data; + + guard(spinlock_irqsave)(&emu->emu_lock); + outb(reg, emu->port + AC97ADDRESS); + outw(val, emu->port + AC97DATA); +} + +static int snd_emu10k1x_ac97(struct emu10k1x *chip) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_emu10k1x_ac97_write, + .read = snd_emu10k1x_ac97_read, + }; + + err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + pbus->no_vra = 1; /* we don't need VRA */ + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.scaps = AC97_SCAP_NO_SPDIF; + return snd_ac97_mixer(pbus, &ac97, &chip->ac97); +} + +static void snd_emu10k1x_free(struct snd_card *card) +{ + struct emu10k1x *chip = card->private_data; + + snd_emu10k1x_ptr_write(chip, TRIGGER_CHANNEL, 0, 0); + // disable interrupts + outl(0, chip->port + INTE); + // disable audio + outl(HCFG_LOCKSOUNDCACHE, chip->port + HCFG); +} + +static irqreturn_t snd_emu10k1x_interrupt(int irq, void *dev_id) +{ + unsigned int status; + + struct emu10k1x *chip = dev_id; + struct emu10k1x_voice *pvoice = chip->voices; + int i; + int mask; + + status = inl(chip->port + IPR); + + if (! status) + return IRQ_NONE; + + // capture interrupt + if (status & (IPR_CAP_0_LOOP | IPR_CAP_0_HALF_LOOP)) { + struct emu10k1x_voice *cap_voice = &chip->capture_voice; + if (cap_voice->use) + snd_emu10k1x_pcm_interrupt(chip, cap_voice); + else + snd_emu10k1x_intr_disable(chip, + INTE_CAP_0_LOOP | + INTE_CAP_0_HALF_LOOP); + } + + mask = IPR_CH_0_LOOP|IPR_CH_0_HALF_LOOP; + for (i = 0; i < 3; i++) { + if (status & mask) { + if (pvoice->use) + snd_emu10k1x_pcm_interrupt(chip, pvoice); + else + snd_emu10k1x_intr_disable(chip, mask); + } + pvoice++; + mask <<= 1; + } + + if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) { + if (chip->midi.interrupt) + chip->midi.interrupt(chip, status); + else + snd_emu10k1x_intr_disable(chip, INTE_MIDITXENABLE|INTE_MIDIRXENABLE); + } + + // acknowledge the interrupt if necessary + outl(status, chip->port + IPR); + + /* dev_dbg(chip->card->dev, "interrupt %08x\n", status); */ + return IRQ_HANDLED; +} + +static const struct snd_pcm_chmap_elem surround_map[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +static const struct snd_pcm_chmap_elem clfe_map[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, + { } +}; + +static int snd_emu10k1x_pcm(struct emu10k1x *emu, int device) +{ + struct snd_pcm *pcm; + const struct snd_pcm_chmap_elem *map = NULL; + int err; + int capture = 0; + + if (device == 0) + capture = 1; + + err = snd_pcm_new(emu->card, "emu10k1x", device, 1, capture, &pcm); + if (err < 0) + return err; + + pcm->private_data = emu; + + switch(device) { + case 0: + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1x_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1x_capture_ops); + break; + case 1: + case 2: + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1x_playback_ops); + break; + } + + pcm->info_flags = 0; + switch(device) { + case 0: + strscpy(pcm->name, "EMU10K1X Front"); + map = snd_pcm_std_chmaps; + break; + case 1: + strscpy(pcm->name, "EMU10K1X Rear"); + map = surround_map; + break; + case 2: + strscpy(pcm->name, "EMU10K1X Center/LFE"); + map = clfe_map; + break; + } + emu->pcm = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &emu->pci->dev, 32*1024, 32*1024); + + return snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, map, 2, + 1 << 2, NULL); +} + +static int snd_emu10k1x_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct emu10k1x *chip = card->private_data; + int err; + int ch; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(28)) < 0) { + dev_err(card->dev, "error to set 28bit mask DMA\n"); + return -ENXIO; + } + + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + spin_lock_init(&chip->emu_lock); + spin_lock_init(&chip->voice_lock); + + err = pcim_request_all_regions(pci, "EMU10K1X"); + if (err < 0) + return err; + chip->port = pci_resource_start(pci, 0); + + if (devm_request_irq(&pci->dev, pci->irq, snd_emu10k1x_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "cannot grab irq %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_emu10k1x_free; + + chip->dma_buffer = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + 4 * 1024); + if (!chip->dma_buffer) + return -ENOMEM; + + pci_set_master(pci); + /* read revision & serial */ + chip->revision = pci->revision; + pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &chip->serial); + pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &chip->model); + dev_info(card->dev, "Model %04x Rev %08x Serial %08x\n", chip->model, + chip->revision, chip->serial); + + outl(0, chip->port + INTE); + + for(ch = 0; ch < 3; ch++) { + chip->voices[ch].emu = chip; + chip->voices[ch].number = ch; + } + + /* + * Init to 0x02109204 : + * Clock accuracy = 0 (1000ppm) + * Sample Rate = 2 (48kHz) + * Audio Channel = 1 (Left of 2) + * Source Number = 0 (Unspecified) + * Generation Status = 1 (Original for Cat Code 12) + * Cat Code = 12 (Digital Signal Mixer) + * Mode = 0 (Mode 0) + * Emphasis = 0 (None) + * CP = 1 (Copyright unasserted) + * AN = 0 (Audio data) + * P = 0 (Consumer) + */ + snd_emu10k1x_ptr_write(chip, SPCS0, 0, + chip->spdif_bits[0] = + SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | + SPCS_GENERATIONSTATUS | 0x00001200 | + 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT); + snd_emu10k1x_ptr_write(chip, SPCS1, 0, + chip->spdif_bits[1] = + SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | + SPCS_GENERATIONSTATUS | 0x00001200 | + 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT); + snd_emu10k1x_ptr_write(chip, SPCS2, 0, + chip->spdif_bits[2] = + SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | + SPCS_GENERATIONSTATUS | 0x00001200 | + 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT); + + snd_emu10k1x_ptr_write(chip, SPDIF_SELECT, 0, 0x700); // disable SPDIF + snd_emu10k1x_ptr_write(chip, ROUTING, 0, 0x1003F); // routing + snd_emu10k1x_gpio_write(chip, 0x1080); // analog mode + + outl(HCFG_LOCKSOUNDCACHE|HCFG_AUDIOENABLE, chip->port+HCFG); + + return 0; +} + +static void snd_emu10k1x_proc_reg_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct emu10k1x *emu = entry->private_data; + unsigned long value,value1,value2; + int i; + + snd_iprintf(buffer, "Registers:\n\n"); + for(i = 0; i < 0x20; i+=4) { + guard(spinlock_irqsave)(&emu->emu_lock); + value = inl(emu->port + i); + snd_iprintf(buffer, "Register %02X: %08lX\n", i, value); + } + snd_iprintf(buffer, "\nRegisters\n\n"); + for(i = 0; i <= 0x48; i++) { + value = snd_emu10k1x_ptr_read(emu, i, 0); + if(i < 0x10 || (i >= 0x20 && i < 0x40)) { + value1 = snd_emu10k1x_ptr_read(emu, i, 1); + value2 = snd_emu10k1x_ptr_read(emu, i, 2); + snd_iprintf(buffer, "%02X: %08lX %08lX %08lX\n", i, value, value1, value2); + } else { + snd_iprintf(buffer, "%02X: %08lX\n", i, value); + } + } +} + +static void snd_emu10k1x_proc_reg_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct emu10k1x *emu = entry->private_data; + char line[64]; + unsigned int reg, channel_id , val; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x %x", ®, &channel_id, &val) != 3) + continue; + + if (reg < 0x49 && channel_id <= 2) + snd_emu10k1x_ptr_write(emu, reg, channel_id, val); + } +} + +static int snd_emu10k1x_proc_init(struct emu10k1x *emu) +{ + snd_card_rw_proc_new(emu->card, "emu10k1x_regs", emu, + snd_emu10k1x_proc_reg_read, + snd_emu10k1x_proc_reg_write); + return 0; +} + +#define snd_emu10k1x_shared_spdif_info snd_ctl_boolean_mono_info + +static int snd_emu10k1x_shared_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct emu10k1x *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = (snd_emu10k1x_ptr_read(emu, SPDIF_SELECT, 0) == 0x700) ? 0 : 1; + + return 0; +} + +static int snd_emu10k1x_shared_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct emu10k1x *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + + val = ucontrol->value.integer.value[0] ; + + if (val) { + // enable spdif output + snd_emu10k1x_ptr_write(emu, SPDIF_SELECT, 0, 0x000); + snd_emu10k1x_ptr_write(emu, ROUTING, 0, 0x700); + snd_emu10k1x_gpio_write(emu, 0x1000); + } else { + // disable spdif output + snd_emu10k1x_ptr_write(emu, SPDIF_SELECT, 0, 0x700); + snd_emu10k1x_ptr_write(emu, ROUTING, 0, 0x1003F); + snd_emu10k1x_gpio_write(emu, 0x1080); + } + return 0; +} + +static const struct snd_kcontrol_new snd_emu10k1x_shared_spdif = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog/Digital Output Jack", + .info = snd_emu10k1x_shared_spdif_info, + .get = snd_emu10k1x_shared_spdif_get, + .put = snd_emu10k1x_shared_spdif_put +}; + +static int snd_emu10k1x_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_emu10k1x_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct emu10k1x *emu = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + ucontrol->value.iec958.status[0] = (emu->spdif_bits[idx] >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (emu->spdif_bits[idx] >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (emu->spdif_bits[idx] >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (emu->spdif_bits[idx] >> 24) & 0xff; + return 0; +} + +static int snd_emu10k1x_spdif_get_mask(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static int snd_emu10k1x_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct emu10k1x *emu = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + int change; + unsigned int val; + + val = (ucontrol->value.iec958.status[0] << 0) | + (ucontrol->value.iec958.status[1] << 8) | + (ucontrol->value.iec958.status[2] << 16) | + (ucontrol->value.iec958.status[3] << 24); + change = val != emu->spdif_bits[idx]; + if (change) { + snd_emu10k1x_ptr_write(emu, SPCS0 + idx, 0, val); + emu->spdif_bits[idx] = val; + } + return change; +} + +static const struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .count = 3, + .info = snd_emu10k1x_spdif_info, + .get = snd_emu10k1x_spdif_get_mask +}; + +static const struct snd_kcontrol_new snd_emu10k1x_spdif_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .count = 3, + .info = snd_emu10k1x_spdif_info, + .get = snd_emu10k1x_spdif_get, + .put = snd_emu10k1x_spdif_put +}; + +static int snd_emu10k1x_mixer(struct emu10k1x *emu) +{ + int err; + struct snd_kcontrol *kctl; + struct snd_card *card = emu->card; + + kctl = snd_ctl_new1(&snd_emu10k1x_spdif_mask_control, emu); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(card, kctl); + if (err) + return err; + kctl = snd_ctl_new1(&snd_emu10k1x_shared_spdif, emu); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(card, kctl); + if (err) + return err; + kctl = snd_ctl_new1(&snd_emu10k1x_spdif_control, emu); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(card, kctl); + if (err) + return err; + + return 0; +} + +#define EMU10K1X_MIDI_MODE_INPUT (1<<0) +#define EMU10K1X_MIDI_MODE_OUTPUT (1<<1) + +static inline unsigned char mpu401_read(struct emu10k1x *emu, struct emu10k1x_midi *mpu, int idx) +{ + return (unsigned char)snd_emu10k1x_ptr_read(emu, mpu->port + idx, 0); +} + +static inline void mpu401_write(struct emu10k1x *emu, struct emu10k1x_midi *mpu, int data, int idx) +{ + snd_emu10k1x_ptr_write(emu, mpu->port + idx, 0, data); +} + +#define mpu401_write_data(emu, mpu, data) mpu401_write(emu, mpu, data, 0) +#define mpu401_write_cmd(emu, mpu, data) mpu401_write(emu, mpu, data, 1) +#define mpu401_read_data(emu, mpu) mpu401_read(emu, mpu, 0) +#define mpu401_read_stat(emu, mpu) mpu401_read(emu, mpu, 1) + +#define mpu401_input_avail(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x80)) +#define mpu401_output_ready(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x40)) + +#define MPU401_RESET 0xff +#define MPU401_ENTER_UART 0x3f +#define MPU401_ACK 0xfe + +static void mpu401_clear_rx(struct emu10k1x *emu, struct emu10k1x_midi *mpu) +{ + int timeout = 100000; + for (; timeout > 0 && mpu401_input_avail(emu, mpu); timeout--) + mpu401_read_data(emu, mpu); +#ifdef CONFIG_SND_DEBUG + if (timeout <= 0) + dev_err(emu->card->dev, + "cmd: clear rx timeout (status = 0x%x)\n", + mpu401_read_stat(emu, mpu)); +#endif +} + +/* + + */ + +static void do_emu10k1x_midi_interrupt(struct emu10k1x *emu, + struct emu10k1x_midi *midi, unsigned int status) +{ + unsigned char byte; + + if (midi->rmidi == NULL) { + snd_emu10k1x_intr_disable(emu, midi->tx_enable | midi->rx_enable); + return; + } + + scoped_guard(spinlock, &midi->input_lock) { + if ((status & midi->ipr_rx) && mpu401_input_avail(emu, midi)) { + if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT)) { + mpu401_clear_rx(emu, midi); + } else { + byte = mpu401_read_data(emu, midi); + if (midi->substream_input) + snd_rawmidi_receive(midi->substream_input, &byte, 1); + } + } + } + + scoped_guard(spinlock, &midi->output_lock) { + if ((status & midi->ipr_tx) && mpu401_output_ready(emu, midi)) { + if (midi->substream_output && + snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) { + mpu401_write_data(emu, midi, byte); + } else { + snd_emu10k1x_intr_disable(emu, midi->tx_enable); + } + } + } +} + +static void snd_emu10k1x_midi_interrupt(struct emu10k1x *emu, unsigned int status) +{ + do_emu10k1x_midi_interrupt(emu, &emu->midi, status); +} + +static int snd_emu10k1x_midi_cmd(struct emu10k1x * emu, + struct emu10k1x_midi *midi, unsigned char cmd, int ack) +{ + int timeout, ok; + + scoped_guard(spinlock_irqsave, &midi->input_lock) { + mpu401_write_data(emu, midi, 0x00); + /* mpu401_clear_rx(emu, midi); */ + + mpu401_write_cmd(emu, midi, cmd); + if (ack) { + ok = 0; + timeout = 10000; + while (!ok && timeout-- > 0) { + if (mpu401_input_avail(emu, midi)) { + if (mpu401_read_data(emu, midi) == MPU401_ACK) + ok = 1; + } + } + if (!ok && mpu401_read_data(emu, midi) == MPU401_ACK) + ok = 1; + } else { + ok = 1; + } + } + if (!ok) { + dev_err(emu->card->dev, + "midi_cmd: 0x%x failed at 0x%lx (status = 0x%x, data = 0x%x)!!!\n", + cmd, emu->port, + mpu401_read_stat(emu, midi), + mpu401_read_data(emu, midi)); + return 1; + } + return 0; +} + +static int snd_emu10k1x_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct emu10k1x *emu; + struct emu10k1x_midi *midi = substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + scoped_guard(spinlock_irqsave, &midi->open_lock) { + midi->midi_mode |= EMU10K1X_MIDI_MODE_INPUT; + midi->substream_input = substream; + if (midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT) + return 0; + } + if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 1)) + return -EIO; + if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_ENTER_UART, 1)) + return -EIO; + return 0; +} + +static int snd_emu10k1x_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct emu10k1x *emu; + struct emu10k1x_midi *midi = substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + scoped_guard(spinlock_irqsave, &midi->open_lock) { + midi->midi_mode |= EMU10K1X_MIDI_MODE_OUTPUT; + midi->substream_output = substream; + if (midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT) + return 0; + } + if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 1)) + return -EIO; + if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_ENTER_UART, 1)) + return -EIO; + return 0; +} + +static int snd_emu10k1x_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct emu10k1x *emu; + struct emu10k1x_midi *midi = substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + scoped_guard(spinlock_irqsave, &midi->open_lock) { + snd_emu10k1x_intr_disable(emu, midi->rx_enable); + midi->midi_mode &= ~EMU10K1X_MIDI_MODE_INPUT; + midi->substream_input = NULL; + if (midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT) + return 0; + } + return snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 0); +} + +static int snd_emu10k1x_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct emu10k1x *emu; + struct emu10k1x_midi *midi = substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + scoped_guard(spinlock_irqsave, &midi->open_lock) { + snd_emu10k1x_intr_disable(emu, midi->tx_enable); + midi->midi_mode &= ~EMU10K1X_MIDI_MODE_OUTPUT; + midi->substream_output = NULL; + if (midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT) + return 0; + } + return snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 0); +} + +static void snd_emu10k1x_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct emu10k1x *emu; + struct emu10k1x_midi *midi = substream->rmidi->private_data; + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return; + + if (up) + snd_emu10k1x_intr_enable(emu, midi->rx_enable); + else + snd_emu10k1x_intr_disable(emu, midi->rx_enable); +} + +static void snd_emu10k1x_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct emu10k1x *emu; + struct emu10k1x_midi *midi = substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return; + + if (up) { + int max = 4; + unsigned char byte; + + /* try to send some amount of bytes here before interrupts */ + scoped_guard(spinlock_irqsave, &midi->output_lock) { + while (max > 0) { + if (mpu401_output_ready(emu, midi)) { + if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT) || + snd_rawmidi_transmit(substream, &byte, 1) != 1) { + /* no more data */ + return; + } + mpu401_write_data(emu, midi, byte); + max--; + } else { + break; + } + } + } + snd_emu10k1x_intr_enable(emu, midi->tx_enable); + } else { + snd_emu10k1x_intr_disable(emu, midi->tx_enable); + } +} + +/* + + */ + +static const struct snd_rawmidi_ops snd_emu10k1x_midi_output = +{ + .open = snd_emu10k1x_midi_output_open, + .close = snd_emu10k1x_midi_output_close, + .trigger = snd_emu10k1x_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_emu10k1x_midi_input = +{ + .open = snd_emu10k1x_midi_input_open, + .close = snd_emu10k1x_midi_input_close, + .trigger = snd_emu10k1x_midi_input_trigger, +}; + +static void snd_emu10k1x_midi_free(struct snd_rawmidi *rmidi) +{ + struct emu10k1x_midi *midi = rmidi->private_data; + midi->interrupt = NULL; + midi->rmidi = NULL; +} + +static int emu10k1x_midi_init(struct emu10k1x *emu, + struct emu10k1x_midi *midi, int device, + char *name) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(emu->card, name, device, 1, 1, &rmidi); + if (err < 0) + return err; + midi->emu = emu; + spin_lock_init(&midi->open_lock); + spin_lock_init(&midi->input_lock); + spin_lock_init(&midi->output_lock); + strscpy(rmidi->name, name); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_emu10k1x_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_emu10k1x_midi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = midi; + rmidi->private_free = snd_emu10k1x_midi_free; + midi->rmidi = rmidi; + return 0; +} + +static int snd_emu10k1x_midi(struct emu10k1x *emu) +{ + struct emu10k1x_midi *midi = &emu->midi; + int err; + + err = emu10k1x_midi_init(emu, midi, 0, "EMU10K1X MPU-401 (UART)"); + if (err < 0) + return err; + + midi->tx_enable = INTE_MIDITXENABLE; + midi->rx_enable = INTE_MIDIRXENABLE; + midi->port = MUDATA; + midi->ipr_tx = IPR_MIDITRANSBUFEMPTY; + midi->ipr_rx = IPR_MIDIRECVBUFEMPTY; + midi->interrupt = snd_emu10k1x_midi_interrupt; + return 0; +} + +static int __snd_emu10k1x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct emu10k1x *chip; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + err = snd_emu10k1x_create(card, pci); + if (err < 0) + return err; + + err = snd_emu10k1x_pcm(chip, 0); + if (err < 0) + return err; + err = snd_emu10k1x_pcm(chip, 1); + if (err < 0) + return err; + err = snd_emu10k1x_pcm(chip, 2); + if (err < 0) + return err; + + err = snd_emu10k1x_ac97(chip); + if (err < 0) + return err; + + err = snd_emu10k1x_mixer(chip); + if (err < 0) + return err; + + err = snd_emu10k1x_midi(chip); + if (err < 0) + return err; + + snd_emu10k1x_proc_init(chip); + + strscpy(card->driver, "EMU10K1X"); + strscpy(card->shortname, "Dell Sound Blaster Live!"); + sprintf(card->longname, "%s at 0x%lx irq %i", + card->shortname, chip->port, chip->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_emu10k1x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_emu10k1x_probe(pci, pci_id)); +} + +// PCI IDs +static const struct pci_device_id snd_emu10k1x_ids[] = { + { PCI_VDEVICE(CREATIVE, 0x0006) }, /* Dell OEM version (EMU10K1) */ + { } +}; +MODULE_DEVICE_TABLE(pci, snd_emu10k1x_ids); + +// pci_driver definition +static struct pci_driver emu10k1x_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_emu10k1x_ids, + .probe = snd_emu10k1x_probe, +}; + +module_pci_driver(emu10k1x_driver); diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c new file mode 100644 index 000000000..49cabb2eb --- /dev/null +++ b/sound/pci/emu10k1/emufx.c @@ -0,0 +1,2722 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * James Courtier-Dutton + * Oswald Buddenhagen + * Creative Labs, Inc. + * + * Routines for effect processor FX8010 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if 0 /* for testing purposes - digital out -> capture */ +#define EMU10K1_CAPTURE_DIGITAL_OUT +#endif +#if 0 /* for testing purposes - set S/PDIF to AC3 output */ +#define EMU10K1_SET_AC3_IEC958 +#endif +#if 0 /* for testing purposes - feed the front signal to Center/LFE outputs */ +#define EMU10K1_CENTER_LFE_FROM_FRONT +#endif + +static bool high_res_gpr_volume; +module_param(high_res_gpr_volume, bool, 0444); +MODULE_PARM_DESC(high_res_gpr_volume, "GPR mixer controls use 31-bit range."); + +/* + * Tables + */ + +// Playback channel labels; corresponds with the public FXBUS_* defines. +// Unlike the tables below, this is not determined by the hardware. +const char * const snd_emu10k1_fxbus[32] = { + /* 0x00 */ "PCM Left", + /* 0x01 */ "PCM Right", + /* 0x02 */ "PCM Rear Left", + /* 0x03 */ "PCM Rear Right", + /* 0x04 */ "MIDI Left", + /* 0x05 */ "MIDI Right", + /* 0x06 */ "PCM Center", + /* 0x07 */ "PCM LFE", + /* 0x08 */ "PCM Front Left", + /* 0x09 */ "PCM Front Right", + /* 0x0a */ NULL, + /* 0x0b */ NULL, + /* 0x0c */ "MIDI Reverb", + /* 0x0d */ "MIDI Chorus", + /* 0x0e */ "PCM Side Left", + /* 0x0f */ "PCM Side Right", + /* 0x10 */ NULL, + /* 0x11 */ NULL, + /* 0x12 */ NULL, + /* 0x13 */ NULL, + /* 0x14 */ "Passthrough Left", + /* 0x15 */ "Passthrough Right", + /* 0x16 */ NULL, + /* 0x17 */ NULL, + /* 0x18 */ NULL, + /* 0x19 */ NULL, + /* 0x1a */ NULL, + /* 0x1b */ NULL, + /* 0x1c */ NULL, + /* 0x1d */ NULL, + /* 0x1e */ NULL, + /* 0x1f */ NULL +}; + +// Physical inputs; corresponds with the public EXTIN_* defines. +const char * const snd_emu10k1_sblive_ins[16] = { + /* 0x00 */ "AC97 Left", + /* 0x01 */ "AC97 Right", + /* 0x02 */ "TTL IEC958 Left", + /* 0x03 */ "TTL IEC958 Right", + /* 0x04 */ "Zoom Video Left", + /* 0x05 */ "Zoom Video Right", + /* 0x06 */ "Optical IEC958 Left", + /* 0x07 */ "Optical IEC958 Right", + /* 0x08 */ "Line/Mic 1 Left", + /* 0x09 */ "Line/Mic 1 Right", + /* 0x0a */ "Coaxial IEC958 Left", + /* 0x0b */ "Coaxial IEC958 Right", + /* 0x0c */ "Line/Mic 2 Left", + /* 0x0d */ "Line/Mic 2 Right", + /* 0x0e */ NULL, + /* 0x0f */ NULL +}; + +// Physical inputs; corresponds with the public A_EXTIN_* defines. +const char * const snd_emu10k1_audigy_ins[16] = { + /* 0x00 */ "AC97 Left", + /* 0x01 */ "AC97 Right", + /* 0x02 */ "Audigy CD Left", + /* 0x03 */ "Audigy CD Right", + /* 0x04 */ "Optical IEC958 Left", + /* 0x05 */ "Optical IEC958 Right", + /* 0x06 */ NULL, + /* 0x07 */ NULL, + /* 0x08 */ "Line/Mic 2 Left", + /* 0x09 */ "Line/Mic 2 Right", + /* 0x0a */ "SPDIF Left", + /* 0x0b */ "SPDIF Right", + /* 0x0c */ "Aux2 Left", + /* 0x0d */ "Aux2 Right", + /* 0x0e */ NULL, + /* 0x0f */ NULL +}; + +// Physical outputs; corresponds with the public EXTOUT_* defines. +const char * const snd_emu10k1_sblive_outs[32] = { + /* 0x00 */ "AC97 Left", + /* 0x01 */ "AC97 Right", + /* 0x02 */ "Optical IEC958 Left", + /* 0x03 */ "Optical IEC958 Right", + /* 0x04 */ "Center", + /* 0x05 */ "LFE", + /* 0x06 */ "Headphone Left", + /* 0x07 */ "Headphone Right", + /* 0x08 */ "Surround Left", + /* 0x09 */ "Surround Right", + /* 0x0a */ "PCM Capture Left", + /* 0x0b */ "PCM Capture Right", + /* 0x0c */ "MIC Capture", + /* 0x0d */ "AC97 Surround Left", + /* 0x0e */ "AC97 Surround Right", + /* 0x0f */ NULL, + // This is actually the FXBUS2 range; SB Live! 5.1 only. + /* 0x10 */ NULL, + /* 0x11 */ "Analog Center", + /* 0x12 */ "Analog LFE", + /* 0x13 */ NULL, + /* 0x14 */ NULL, + /* 0x15 */ NULL, + /* 0x16 */ NULL, + /* 0x17 */ NULL, + /* 0x18 */ NULL, + /* 0x19 */ NULL, + /* 0x1a */ NULL, + /* 0x1b */ NULL, + /* 0x1c */ NULL, + /* 0x1d */ NULL, + /* 0x1e */ NULL, + /* 0x1f */ NULL, +}; + +// Physical outputs; corresponds with the public A_EXTOUT_* defines. +const char * const snd_emu10k1_audigy_outs[32] = { + /* 0x00 */ "Digital Front Left", + /* 0x01 */ "Digital Front Right", + /* 0x02 */ "Digital Center", + /* 0x03 */ "Digital LEF", + /* 0x04 */ "Headphone Left", + /* 0x05 */ "Headphone Right", + /* 0x06 */ "Digital Rear Left", + /* 0x07 */ "Digital Rear Right", + /* 0x08 */ "Front Left", + /* 0x09 */ "Front Right", + /* 0x0a */ "Center", + /* 0x0b */ "LFE", + /* 0x0c */ NULL, + /* 0x0d */ NULL, + /* 0x0e */ "Rear Left", + /* 0x0f */ "Rear Right", + /* 0x10 */ "AC97 Front Left", + /* 0x11 */ "AC97 Front Right", + /* 0x12 */ "ADC Capture Left", + /* 0x13 */ "ADC Capture Right", + /* 0x14 */ NULL, + /* 0x15 */ NULL, + /* 0x16 */ NULL, + /* 0x17 */ NULL, + /* 0x18 */ NULL, + /* 0x19 */ NULL, + /* 0x1a */ NULL, + /* 0x1b */ NULL, + /* 0x1c */ NULL, + /* 0x1d */ NULL, + /* 0x1e */ NULL, + /* 0x1f */ NULL, +}; + +// On the SB Live! 5.1, FXBUS2[1] and FXBUS2[2] are occupied by EXTOUT_ACENTER +// and EXTOUT_ALFE, so we can't connect inputs to them for multitrack recording. +// +// Since only 14 of the 16 EXTINs are used, this is not a big problem. +// We route AC97 to FX capture 14 and 15, SPDIF_CD to FX capture 0 and 3, +// and the rest of the EXTINs to the corresponding FX capture channel. +// Multitrack recorders will still see the center/LFE output signal +// on the second and third "input" channel. +const s8 snd_emu10k1_sblive51_fxbus2_map[16] = { + 2, -1, -1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 1 +}; + +static const u32 bass_table[41][5] = { + { 0x3e4f844f, 0x84ed4cc3, 0x3cc69927, 0x7b03553a, 0xc4da8486 }, + { 0x3e69a17a, 0x84c280fb, 0x3cd77cd4, 0x7b2f2a6f, 0xc4b08d1d }, + { 0x3e82ff42, 0x849991d5, 0x3ce7466b, 0x7b5917c6, 0xc48863ee }, + { 0x3e9bab3c, 0x847267f0, 0x3cf5ffe8, 0x7b813560, 0xc461f22c }, + { 0x3eb3b275, 0x844ced29, 0x3d03b295, 0x7ba79a1c, 0xc43d223b }, + { 0x3ecb2174, 0x84290c8b, 0x3d106714, 0x7bcc5ba3, 0xc419dfa5 }, + { 0x3ee2044b, 0x8406b244, 0x3d1c2561, 0x7bef8e77, 0xc3f8170f }, + { 0x3ef86698, 0x83e5cb96, 0x3d26f4d8, 0x7c114600, 0xc3d7b625 }, + { 0x3f0e5390, 0x83c646c9, 0x3d30dc39, 0x7c319498, 0xc3b8ab97 }, + { 0x3f23d60b, 0x83a81321, 0x3d39e1af, 0x7c508b9c, 0xc39ae704 }, + { 0x3f38f884, 0x838b20d2, 0x3d420ad2, 0x7c6e3b75, 0xc37e58f1 }, + { 0x3f4dc52c, 0x836f60ef, 0x3d495cab, 0x7c8ab3a6, 0xc362f2be }, + { 0x3f6245e8, 0x8354c565, 0x3d4fdbb8, 0x7ca602d6, 0xc348a69b }, + { 0x3f76845f, 0x833b40ec, 0x3d558bf0, 0x7cc036df, 0xc32f677c }, + { 0x3f8a8a03, 0x8322c6fb, 0x3d5a70c4, 0x7cd95cd7, 0xc317290b }, + { 0x3f9e6014, 0x830b4bc3, 0x3d5e8d25, 0x7cf1811a, 0xc2ffdfa5 }, + { 0x3fb20fae, 0x82f4c420, 0x3d61e37f, 0x7d08af56, 0xc2e9804a }, + { 0x3fc5a1cc, 0x82df2592, 0x3d6475c3, 0x7d1ef294, 0xc2d40096 }, + { 0x3fd91f55, 0x82ca6632, 0x3d664564, 0x7d345541, 0xc2bf56b9 }, + { 0x3fec9120, 0x82b67cac, 0x3d675356, 0x7d48e138, 0xc2ab796e }, + { 0x40000000, 0x82a36037, 0x3d67a012, 0x7d5c9fc9, 0xc2985fee }, + { 0x401374c7, 0x8291088a, 0x3d672b93, 0x7d6f99c3, 0xc28601f2 }, + { 0x4026f857, 0x827f6dd7, 0x3d65f559, 0x7d81d77c, 0xc27457a3 }, + { 0x403a939f, 0x826e88c5, 0x3d63fc63, 0x7d9360d4, 0xc2635996 }, + { 0x404e4faf, 0x825e5266, 0x3d613f32, 0x7da43d42, 0xc25300c6 }, + { 0x406235ba, 0x824ec434, 0x3d5dbbc3, 0x7db473d7, 0xc243468e }, + { 0x40764f1f, 0x823fd80c, 0x3d596f8f, 0x7dc40b44, 0xc23424a2 }, + { 0x408aa576, 0x82318824, 0x3d545787, 0x7dd309e2, 0xc2259509 }, + { 0x409f4296, 0x8223cf0b, 0x3d4e7012, 0x7de175b5, 0xc2179218 }, + { 0x40b430a0, 0x8216a7a1, 0x3d47b505, 0x7def5475, 0xc20a1670 }, + { 0x40c97a0a, 0x820a0d12, 0x3d4021a1, 0x7dfcab8d, 0xc1fd1cf5 }, + { 0x40df29a6, 0x81fdfad6, 0x3d37b08d, 0x7e098028, 0xc1f0a0ca }, + { 0x40f54ab1, 0x81f26ca9, 0x3d2e5bd1, 0x7e15d72b, 0xc1e49d52 }, + { 0x410be8da, 0x81e75e89, 0x3d241cce, 0x7e21b544, 0xc1d90e24 }, + { 0x41231051, 0x81dcccb3, 0x3d18ec37, 0x7e2d1ee6, 0xc1cdef10 }, + { 0x413acdd0, 0x81d2b39e, 0x3d0cc20a, 0x7e38184e, 0xc1c33c13 }, + { 0x41532ea7, 0x81c90ffb, 0x3cff9585, 0x7e42a58b, 0xc1b8f15a }, + { 0x416c40cd, 0x81bfdeb2, 0x3cf15d21, 0x7e4cca7c, 0xc1af0b3f }, + { 0x418612ea, 0x81b71cdc, 0x3ce20e85, 0x7e568ad3, 0xc1a58640 }, + { 0x41a0b465, 0x81aec7c5, 0x3cd19e7c, 0x7e5fea1e, 0xc19c5f03 }, + { 0x41bc3573, 0x81a6dcea, 0x3cc000e9, 0x7e68ebc2, 0xc1939250 } +}; + +static const u32 treble_table[41][5] = { + { 0x0125cba9, 0xfed5debd, 0x00599b6c, 0x0d2506da, 0xfa85b354 }, + { 0x0142f67e, 0xfeb03163, 0x0066cd0f, 0x0d14c69d, 0xfa914473 }, + { 0x016328bd, 0xfe860158, 0x0075b7f2, 0x0d03eb27, 0xfa9d32d2 }, + { 0x0186b438, 0xfe56c982, 0x00869234, 0x0cf27048, 0xfaa97fca }, + { 0x01adf358, 0xfe21f5fe, 0x00999842, 0x0ce051c2, 0xfab62ca5 }, + { 0x01d949fa, 0xfde6e287, 0x00af0d8d, 0x0ccd8b4a, 0xfac33aa7 }, + { 0x02092669, 0xfda4d8bf, 0x00c73d4c, 0x0cba1884, 0xfad0ab07 }, + { 0x023e0268, 0xfd5b0e4a, 0x00e27b54, 0x0ca5f509, 0xfade7ef2 }, + { 0x0278645c, 0xfd08a2b0, 0x01012509, 0x0c911c63, 0xfaecb788 }, + { 0x02b8e091, 0xfcac9d1a, 0x0123a262, 0x0c7b8a14, 0xfafb55df }, + { 0x03001a9a, 0xfc45e9ce, 0x014a6709, 0x0c65398f, 0xfb0a5aff }, + { 0x034ec6d7, 0xfbd3576b, 0x0175f397, 0x0c4e2643, 0xfb19c7e4 }, + { 0x03a5ac15, 0xfb5393ee, 0x01a6d6ed, 0x0c364b94, 0xfb299d7c }, + { 0x0405a562, 0xfac52968, 0x01ddafae, 0x0c1da4e2, 0xfb39dca5 }, + { 0x046fa3fe, 0xfa267a66, 0x021b2ddd, 0x0c042d8d, 0xfb4a8631 }, + { 0x04e4b17f, 0xf975be0f, 0x0260149f, 0x0be9e0f2, 0xfb5b9ae0 }, + { 0x0565f220, 0xf8b0fbe5, 0x02ad3c29, 0x0bceba73, 0xfb6d1b60 }, + { 0x05f4a745, 0xf7d60722, 0x030393d4, 0x0bb2b578, 0xfb7f084d }, + { 0x06923236, 0xf6e279bd, 0x03642465, 0x0b95cd75, 0xfb916233 }, + { 0x07401713, 0xf5d3aef9, 0x03d01283, 0x0b77fded, 0xfba42984 }, + { 0x08000000, 0xf4a6bd88, 0x0448a161, 0x0b594278, 0xfbb75e9f }, + { 0x08d3c097, 0xf3587131, 0x04cf35a4, 0x0b3996c9, 0xfbcb01cb }, + { 0x09bd59a2, 0xf1e543f9, 0x05655880, 0x0b18f6b2, 0xfbdf1333 }, + { 0x0abefd0f, 0xf04956ca, 0x060cbb12, 0x0af75e2c, 0xfbf392e8 }, + { 0x0bdb123e, 0xee806984, 0x06c739fe, 0x0ad4c962, 0xfc0880dd }, + { 0x0d143a94, 0xec85d287, 0x0796e150, 0x0ab134b0, 0xfc1ddce5 }, + { 0x0e6d5664, 0xea547598, 0x087df0a0, 0x0a8c9cb6, 0xfc33a6ad }, + { 0x0fe98a2a, 0xe7e6ba35, 0x097edf83, 0x0a66fe5b, 0xfc49ddc2 }, + { 0x118c4421, 0xe536813a, 0x0a9c6248, 0x0a4056d7, 0xfc608185 }, + { 0x1359422e, 0xe23d19eb, 0x0bd96efb, 0x0a18a3bf, 0xfc77912c }, + { 0x1554982b, 0xdef33645, 0x0d3942bd, 0x09efe312, 0xfc8f0bc1 }, + { 0x1782b68a, 0xdb50deb1, 0x0ebf676d, 0x09c6133f, 0xfca6f019 }, + { 0x19e8715d, 0xd74d64fd, 0x106fb999, 0x099b3337, 0xfcbf3cd6 }, + { 0x1c8b07b8, 0xd2df56ab, 0x124e6ec8, 0x096f4274, 0xfcd7f060 }, + { 0x1f702b6d, 0xcdfc6e92, 0x14601c10, 0x0942410b, 0xfcf108e5 }, + { 0x229e0933, 0xc89985cd, 0x16a9bcfa, 0x09142fb5, 0xfd0a8451 }, + { 0x261b5118, 0xc2aa8409, 0x1930bab6, 0x08e50fdc, 0xfd24604d }, + { 0x29ef3f5d, 0xbc224f28, 0x1bfaf396, 0x08b4e3aa, 0xfd3e9a3b }, + { 0x2e21a59b, 0xb4f2ba46, 0x1f0ec2d6, 0x0883ae15, 0xfd592f33 }, + { 0x32baf44b, 0xad0c7429, 0x227308a3, 0x085172eb, 0xfd741bfd }, + { 0x37c4448b, 0xa45ef51d, 0x262f3267, 0x081e36dc, 0xfd8f5d14 } +}; + +/* dB gain = (float) 20 * log10( float(db_table_value) / 0x8000000 ) */ +static const u32 db_table[101] = { + 0x00000000, 0x01571f82, 0x01674b41, 0x01783a1b, 0x0189f540, + 0x019c8651, 0x01aff763, 0x01c45306, 0x01d9a446, 0x01eff6b8, + 0x0207567a, 0x021fd03d, 0x0239714c, 0x02544792, 0x027061a1, + 0x028dcebb, 0x02ac9edc, 0x02cce2bf, 0x02eeabe8, 0x03120cb0, + 0x0337184e, 0x035de2df, 0x03868173, 0x03b10a18, 0x03dd93e9, + 0x040c3713, 0x043d0cea, 0x04702ff3, 0x04a5bbf2, 0x04ddcdfb, + 0x0518847f, 0x0555ff62, 0x05966005, 0x05d9c95d, 0x06206005, + 0x066a4a52, 0x06b7b067, 0x0708bc4c, 0x075d9a01, 0x07b6779d, + 0x08138561, 0x0874f5d5, 0x08dafde1, 0x0945d4ed, 0x09b5b4fd, + 0x0a2adad1, 0x0aa58605, 0x0b25f936, 0x0bac7a24, 0x0c3951d8, + 0x0ccccccc, 0x0d673b17, 0x0e08f093, 0x0eb24510, 0x0f639481, + 0x101d3f2d, 0x10dfa9e6, 0x11ab3e3f, 0x12806ac3, 0x135fa333, + 0x144960c5, 0x153e2266, 0x163e6cfe, 0x174acbb7, 0x1863d04d, + 0x198a1357, 0x1abe349f, 0x1c00db77, 0x1d52b712, 0x1eb47ee6, + 0x2026f30f, 0x21aadcb6, 0x23410e7e, 0x24ea64f9, 0x26a7c71d, + 0x287a26c4, 0x2a62812c, 0x2c61df84, 0x2e795779, 0x30aa0bcf, + 0x32f52cfe, 0x355bf9d8, 0x37dfc033, 0x3a81dda4, 0x3d43c038, + 0x4026e73c, 0x432ce40f, 0x46575af8, 0x49a8040f, 0x4d20ac2a, + 0x50c335d3, 0x54919a57, 0x588dead1, 0x5cba514a, 0x611911ea, + 0x65ac8c2f, 0x6a773c39, 0x6f7bbc23, 0x74bcc56c, 0x7a3d3272, + 0x7fffffff, +}; + +/* EMU10k1/EMU10k2 DSP control db gain */ +static const DECLARE_TLV_DB_SCALE(snd_emu10k1_db_scale1, -4000, 40, 1); +static const DECLARE_TLV_DB_LINEAR(snd_emu10k1_db_linear, TLV_DB_GAIN_MUTE, 0); + +/* EMU10K1 bass/treble db gain */ +static const DECLARE_TLV_DB_SCALE(snd_emu10k1_bass_treble_db_scale, -1200, 60, 0); + +static const u32 onoff_table[2] = { + 0x00000000, 0x00000001 +}; + +/* + * controls + */ + +static int snd_emu10k1_gpr_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_emu10k1_fx8010_ctl *ctl = + (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value; + + if (ctl->min == 0 && ctl->max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = ctl->vcount; + uinfo->value.integer.min = ctl->min; + uinfo->value.integer.max = ctl->max; + return 0; +} + +static int snd_emu10k1_gpr_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1_fx8010_ctl *ctl = + (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value; + unsigned int i; + + for (i = 0; i < ctl->vcount; i++) + ucontrol->value.integer.value[i] = ctl->value[i]; + return 0; +} + +static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_fx8010_ctl *ctl = + (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value; + int nval, val; + unsigned int i, j; + int change = 0; + + for (i = 0; i < ctl->vcount; i++) { + nval = ucontrol->value.integer.value[i]; + if (nval < ctl->min) + nval = ctl->min; + if (nval > ctl->max) + nval = ctl->max; + if (nval != ctl->value[i]) + change = 1; + val = ctl->value[i] = nval; + switch (ctl->translation) { + case EMU10K1_GPR_TRANSLATION_NONE: + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, val); + break; + case EMU10K1_GPR_TRANSLATION_NEGATE: + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, ~val); + break; + case EMU10K1_GPR_TRANSLATION_TABLE100: + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, db_table[val]); + break; + case EMU10K1_GPR_TRANSLATION_NEG_TABLE100: + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, + val == 100 ? 0x80000000 : -(int)db_table[val]); + break; + case EMU10K1_GPR_TRANSLATION_BASS: + if ((ctl->count % 5) != 0 || (ctl->count / 5) != ctl->vcount) { + change = -EIO; + goto __error; + } + for (j = 0; j < 5; j++) + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[j * ctl->vcount + i], 0, bass_table[val][j]); + break; + case EMU10K1_GPR_TRANSLATION_TREBLE: + if ((ctl->count % 5) != 0 || (ctl->count / 5) != ctl->vcount) { + change = -EIO; + goto __error; + } + for (j = 0; j < 5; j++) + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[j * ctl->vcount + i], 0, treble_table[val][j]); + break; + case EMU10K1_GPR_TRANSLATION_ONOFF: + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, onoff_table[val]); + break; + } + } + __error: + return change; +} + +/* + * Interrupt handler + */ + +static void snd_emu10k1_fx8010_interrupt(struct snd_emu10k1 *emu) +{ + struct snd_emu10k1_fx8010_irq *irq, *nirq; + + irq = emu->fx8010.irq_handlers; + while (irq) { + nirq = irq->next; /* irq ptr can be removed from list */ + if (snd_emu10k1_ptr_read(emu, emu->gpr_base + irq->gpr_running, 0) & 0xffff0000) { + if (irq->handler) + irq->handler(emu, irq->private_data); + snd_emu10k1_ptr_write(emu, emu->gpr_base + irq->gpr_running, 0, 1); + } + irq = nirq; + } +} + +int snd_emu10k1_fx8010_register_irq_handler(struct snd_emu10k1 *emu, + snd_fx8010_irq_handler_t *handler, + unsigned char gpr_running, + void *private_data, + struct snd_emu10k1_fx8010_irq *irq) +{ + irq->handler = handler; + irq->gpr_running = gpr_running; + irq->private_data = private_data; + irq->next = NULL; + guard(spinlock_irqsave)(&emu->fx8010.irq_lock); + if (emu->fx8010.irq_handlers == NULL) { + emu->fx8010.irq_handlers = irq; + emu->dsp_interrupt = snd_emu10k1_fx8010_interrupt; + snd_emu10k1_intr_enable(emu, INTE_FXDSPENABLE); + } else { + irq->next = emu->fx8010.irq_handlers; + emu->fx8010.irq_handlers = irq; + } + return 0; +} + +int snd_emu10k1_fx8010_unregister_irq_handler(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_irq *irq) +{ + struct snd_emu10k1_fx8010_irq *tmp; + + guard(spinlock_irqsave)(&emu->fx8010.irq_lock); + tmp = emu->fx8010.irq_handlers; + if (tmp == irq) { + emu->fx8010.irq_handlers = tmp->next; + if (emu->fx8010.irq_handlers == NULL) { + snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE); + emu->dsp_interrupt = NULL; + } + } else { + while (tmp && tmp->next != irq) + tmp = tmp->next; + if (tmp) + tmp->next = tmp->next->next; + } + return 0; +} + +/************************************************************************* + * EMU10K1 effect manager + *************************************************************************/ + +static void snd_emu10k1_write_op(struct snd_emu10k1_fx8010_code *icode, + unsigned int *ptr, + u32 op, u32 r, u32 a, u32 x, u32 y) +{ + u_int32_t *code; + if (snd_BUG_ON(*ptr >= 512)) + return; + code = icode->code + (*ptr) * 2; + set_bit(*ptr, icode->code_valid); + code[0] = ((x & 0x3ff) << 10) | (y & 0x3ff); + code[1] = ((op & 0x0f) << 20) | ((r & 0x3ff) << 10) | (a & 0x3ff); + (*ptr)++; +} + +#define OP(icode, ptr, op, r, a, x, y) \ + snd_emu10k1_write_op(icode, ptr, op, r, a, x, y) + +static void snd_emu10k1_audigy_write_op(struct snd_emu10k1_fx8010_code *icode, + unsigned int *ptr, + u32 op, u32 r, u32 a, u32 x, u32 y) +{ + u_int32_t *code; + if (snd_BUG_ON(*ptr >= 1024)) + return; + code = icode->code + (*ptr) * 2; + set_bit(*ptr, icode->code_valid); + code[0] = ((x & 0x7ff) << 12) | (y & 0x7ff); + code[1] = ((op & 0x0f) << 24) | ((r & 0x7ff) << 12) | (a & 0x7ff); + (*ptr)++; +} + +#define A_OP(icode, ptr, op, r, a, x, y) \ + snd_emu10k1_audigy_write_op(icode, ptr, op, r, a, x, y) + +static void snd_emu10k1_efx_write(struct snd_emu10k1 *emu, unsigned int pc, unsigned int data) +{ + pc += emu->audigy ? A_MICROCODEBASE : MICROCODEBASE; + snd_emu10k1_ptr_write(emu, pc, 0, data); +} + +unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc) +{ + pc += emu->audigy ? A_MICROCODEBASE : MICROCODEBASE; + return snd_emu10k1_ptr_read(emu, pc, 0); +} + +static int snd_emu10k1_gpr_poke(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode, + bool in_kernel) +{ + int gpr; + u32 val; + + for (gpr = 0; gpr < (emu->audigy ? 0x200 : 0x100); gpr++) { + if (!test_bit(gpr, icode->gpr_valid)) + continue; + if (in_kernel) + val = icode->gpr_map[gpr]; + else if (get_user(val, (__user u32 *)&icode->gpr_map[gpr])) + return -EFAULT; + snd_emu10k1_ptr_write(emu, emu->gpr_base + gpr, 0, val); + } + return 0; +} + +static int snd_emu10k1_gpr_peek(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode) +{ + int gpr; + u32 val; + + for (gpr = 0; gpr < (emu->audigy ? 0x200 : 0x100); gpr++) { + set_bit(gpr, icode->gpr_valid); + val = snd_emu10k1_ptr_read(emu, emu->gpr_base + gpr, 0); + if (put_user(val, (__user u32 *)&icode->gpr_map[gpr])) + return -EFAULT; + } + return 0; +} + +static int snd_emu10k1_tram_poke(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode, + bool in_kernel) +{ + int tram; + u32 addr, val; + + for (tram = 0; tram < (emu->audigy ? 0x100 : 0xa0); tram++) { + if (!test_bit(tram, icode->tram_valid)) + continue; + if (in_kernel) { + val = icode->tram_data_map[tram]; + addr = icode->tram_addr_map[tram]; + } else { + if (get_user(val, (__user __u32 *)&icode->tram_data_map[tram]) || + get_user(addr, (__user __u32 *)&icode->tram_addr_map[tram])) + return -EFAULT; + } + snd_emu10k1_ptr_write(emu, TANKMEMDATAREGBASE + tram, 0, val); + if (!emu->audigy) { + snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + tram, 0, addr); + } else { + snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + tram, 0, addr << 12); + snd_emu10k1_ptr_write(emu, A_TANKMEMCTLREGBASE + tram, 0, addr >> 20); + } + } + return 0; +} + +static int snd_emu10k1_tram_peek(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode) +{ + int tram; + u32 val, addr; + + memset(icode->tram_valid, 0, sizeof(icode->tram_valid)); + for (tram = 0; tram < (emu->audigy ? 0x100 : 0xa0); tram++) { + set_bit(tram, icode->tram_valid); + val = snd_emu10k1_ptr_read(emu, TANKMEMDATAREGBASE + tram, 0); + if (!emu->audigy) { + addr = snd_emu10k1_ptr_read(emu, TANKMEMADDRREGBASE + tram, 0); + } else { + addr = snd_emu10k1_ptr_read(emu, TANKMEMADDRREGBASE + tram, 0) >> 12; + addr |= snd_emu10k1_ptr_read(emu, A_TANKMEMCTLREGBASE + tram, 0) << 20; + } + if (put_user(val, (__user u32 *)&icode->tram_data_map[tram]) || + put_user(addr, (__user u32 *)&icode->tram_addr_map[tram])) + return -EFAULT; + } + return 0; +} + +static int snd_emu10k1_code_poke(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode, + bool in_kernel) +{ + u32 pc, lo, hi; + + for (pc = 0; pc < (emu->audigy ? 2*1024 : 2*512); pc += 2) { + if (!test_bit(pc / 2, icode->code_valid)) + continue; + if (in_kernel) { + lo = icode->code[pc + 0]; + hi = icode->code[pc + 1]; + } else { + if (get_user(lo, (__user u32 *)&icode->code[pc + 0]) || + get_user(hi, (__user u32 *)&icode->code[pc + 1])) + return -EFAULT; + } + snd_emu10k1_efx_write(emu, pc + 0, lo); + snd_emu10k1_efx_write(emu, pc + 1, hi); + } + return 0; +} + +static int snd_emu10k1_code_peek(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode) +{ + u32 pc; + + memset(icode->code_valid, 0, sizeof(icode->code_valid)); + for (pc = 0; pc < (emu->audigy ? 2*1024 : 2*512); pc += 2) { + set_bit(pc / 2, icode->code_valid); + if (put_user(snd_emu10k1_efx_read(emu, pc + 0), + (__user u32 *)&icode->code[pc + 0])) + return -EFAULT; + if (put_user(snd_emu10k1_efx_read(emu, pc + 1), + (__user u32 *)&icode->code[pc + 1])) + return -EFAULT; + } + return 0; +} + +static struct snd_emu10k1_fx8010_ctl * +snd_emu10k1_look_for_ctl(struct snd_emu10k1 *emu, + struct emu10k1_ctl_elem_id *_id) +{ + struct snd_ctl_elem_id *id = (struct snd_ctl_elem_id *)_id; + struct snd_emu10k1_fx8010_ctl *ctl; + struct snd_kcontrol *kcontrol; + + list_for_each_entry(ctl, &emu->fx8010.gpr_ctl, list) { + kcontrol = ctl->kcontrol; + if (kcontrol->id.iface == id->iface && + kcontrol->id.index == id->index && + !strcmp(kcontrol->id.name, id->name)) + return ctl; + } + return NULL; +} + +#define MAX_TLV_SIZE 256 + +static unsigned int *copy_tlv(const unsigned int __user *_tlv, bool in_kernel) +{ + unsigned int data[2]; + unsigned int *tlv; + + if (!_tlv) + return NULL; + if (in_kernel) + memcpy(data, (__force void *)_tlv, sizeof(data)); + else if (copy_from_user(data, _tlv, sizeof(data))) + return NULL; + if (data[1] >= MAX_TLV_SIZE) + return NULL; + tlv = kmalloc(data[1] + sizeof(data), GFP_KERNEL); + if (!tlv) + return NULL; + memcpy(tlv, data, sizeof(data)); + if (in_kernel) { + memcpy(tlv + 2, (__force void *)(_tlv + 2), data[1]); + } else if (copy_from_user(tlv + 2, _tlv + 2, data[1])) { + kfree(tlv); + return NULL; + } + return tlv; +} + +static int copy_gctl(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_control_gpr *dst, + struct snd_emu10k1_fx8010_control_gpr *src, + int idx, bool in_kernel) +{ + struct snd_emu10k1_fx8010_control_gpr __user *_src; + struct snd_emu10k1_fx8010_control_old_gpr *octl; + struct snd_emu10k1_fx8010_control_old_gpr __user *_octl; + + _src = (struct snd_emu10k1_fx8010_control_gpr __user *)src; + if (emu->support_tlv) { + if (in_kernel) + *dst = src[idx]; + else if (copy_from_user(dst, &_src[idx], sizeof(*src))) + return -EFAULT; + return 0; + } + + octl = (struct snd_emu10k1_fx8010_control_old_gpr *)src; + _octl = (struct snd_emu10k1_fx8010_control_old_gpr __user *)octl; + if (in_kernel) + memcpy(dst, &octl[idx], sizeof(*octl)); + else if (copy_from_user(dst, &_octl[idx], sizeof(*octl))) + return -EFAULT; + dst->tlv = NULL; + return 0; +} + +static int copy_gctl_to_user(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_control_gpr *dst, + struct snd_emu10k1_fx8010_control_gpr *src, + int idx) +{ + struct snd_emu10k1_fx8010_control_gpr __user *_dst; + struct snd_emu10k1_fx8010_control_old_gpr __user *octl; + + _dst = (struct snd_emu10k1_fx8010_control_gpr __user *)dst; + if (emu->support_tlv) + return copy_to_user(&_dst[idx], src, sizeof(*src)); + + octl = (struct snd_emu10k1_fx8010_control_old_gpr __user *)dst; + return copy_to_user(&octl[idx], src, sizeof(*octl)); +} + +static int copy_ctl_elem_id(const struct emu10k1_ctl_elem_id *list, int i, + struct emu10k1_ctl_elem_id *ret, bool in_kernel) +{ + struct emu10k1_ctl_elem_id __user *_id = + (struct emu10k1_ctl_elem_id __user *)&list[i]; + + if (in_kernel) + *ret = list[i]; + else if (copy_from_user(ret, _id, sizeof(*ret))) + return -EFAULT; + return 0; +} + +static int snd_emu10k1_verify_controls(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode, + bool in_kernel) +{ + unsigned int i; + struct emu10k1_ctl_elem_id id; + struct snd_emu10k1_fx8010_control_gpr *gctl; + struct snd_ctl_elem_id *gctl_id; + int err; + + for (i = 0; i < icode->gpr_del_control_count; i++) { + err = copy_ctl_elem_id(icode->gpr_del_controls, i, &id, + in_kernel); + if (err < 0) + return err; + if (snd_emu10k1_look_for_ctl(emu, &id) == NULL) + return -ENOENT; + } + gctl = kmalloc_obj(*gctl); + if (! gctl) + return -ENOMEM; + err = 0; + for (i = 0; i < icode->gpr_add_control_count; i++) { + if (copy_gctl(emu, gctl, icode->gpr_add_controls, i, + in_kernel)) { + err = -EFAULT; + goto __error; + } + if (snd_emu10k1_look_for_ctl(emu, &gctl->id)) + continue; + gctl_id = (struct snd_ctl_elem_id *)&gctl->id; + if (snd_ctl_find_id(emu->card, gctl_id)) { + err = -EEXIST; + goto __error; + } + if (gctl_id->iface != SNDRV_CTL_ELEM_IFACE_MIXER && + gctl_id->iface != SNDRV_CTL_ELEM_IFACE_PCM) { + err = -EINVAL; + goto __error; + } + switch (gctl->translation) { + case EMU10K1_GPR_TRANSLATION_NONE: + case EMU10K1_GPR_TRANSLATION_NEGATE: + break; + case EMU10K1_GPR_TRANSLATION_TABLE100: + case EMU10K1_GPR_TRANSLATION_NEG_TABLE100: + if (gctl->min != 0 || gctl->max != 100) { + err = -EINVAL; + goto __error; + } + break; + case EMU10K1_GPR_TRANSLATION_BASS: + case EMU10K1_GPR_TRANSLATION_TREBLE: + if (gctl->min != 0 || gctl->max != 40) { + err = -EINVAL; + goto __error; + } + break; + case EMU10K1_GPR_TRANSLATION_ONOFF: + if (gctl->min != 0 || gctl->max != 1) { + err = -EINVAL; + goto __error; + } + break; + default: + err = -EINVAL; + goto __error; + } + } + for (i = 0; i < icode->gpr_list_control_count; i++) { + /* FIXME: we need to check the WRITE access */ + if (copy_gctl(emu, gctl, icode->gpr_list_controls, i, + in_kernel)) { + err = -EFAULT; + goto __error; + } + } + __error: + kfree(gctl); + return err; +} + +static void snd_emu10k1_ctl_private_free(struct snd_kcontrol *kctl) +{ + struct snd_emu10k1_fx8010_ctl *ctl; + + ctl = (struct snd_emu10k1_fx8010_ctl *) kctl->private_value; + kctl->private_value = 0; + list_del(&ctl->list); + kfree(ctl); + kfree(kctl->tlv.p); +} + +static int snd_emu10k1_add_controls(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode, + bool in_kernel) +{ + unsigned int i, j; + struct snd_emu10k1_fx8010_control_gpr *gctl; + struct snd_ctl_elem_id *gctl_id; + struct snd_emu10k1_fx8010_ctl *ctl, *nctl; + struct snd_kcontrol_new knew; + struct snd_kcontrol *kctl; + struct snd_ctl_elem_value *val; + int err = 0; + + val = kmalloc_obj(*val); + gctl = kmalloc_obj(*gctl); + nctl = kmalloc_obj(*nctl); + if (!val || !gctl || !nctl) { + err = -ENOMEM; + goto __error; + } + + for (i = 0; i < icode->gpr_add_control_count; i++) { + if (copy_gctl(emu, gctl, icode->gpr_add_controls, i, + in_kernel)) { + err = -EFAULT; + goto __error; + } + gctl_id = (struct snd_ctl_elem_id *)&gctl->id; + if (gctl_id->iface != SNDRV_CTL_ELEM_IFACE_MIXER && + gctl_id->iface != SNDRV_CTL_ELEM_IFACE_PCM) { + err = -EINVAL; + goto __error; + } + if (!*gctl_id->name) { + err = -EINVAL; + goto __error; + } + ctl = snd_emu10k1_look_for_ctl(emu, &gctl->id); + memset(&knew, 0, sizeof(knew)); + knew.iface = gctl_id->iface; + knew.name = gctl_id->name; + knew.index = gctl_id->index; + knew.device = gctl_id->device; + knew.subdevice = gctl_id->subdevice; + knew.info = snd_emu10k1_gpr_ctl_info; + knew.tlv.p = copy_tlv((const unsigned int __user *)gctl->tlv, in_kernel); + if (knew.tlv.p) + knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + knew.get = snd_emu10k1_gpr_ctl_get; + knew.put = snd_emu10k1_gpr_ctl_put; + memset(nctl, 0, sizeof(*nctl)); + nctl->vcount = gctl->vcount; + nctl->count = gctl->count; + for (j = 0; j < 32; j++) { + nctl->gpr[j] = gctl->gpr[j]; + nctl->value[j] = ~gctl->value[j]; /* inverted, we want to write new value in gpr_ctl_put() */ + val->value.integer.value[j] = gctl->value[j]; + } + nctl->min = gctl->min; + nctl->max = gctl->max; + nctl->translation = gctl->translation; + if (ctl == NULL) { + ctl = kmalloc_obj(*ctl); + if (ctl == NULL) { + err = -ENOMEM; + kfree(knew.tlv.p); + goto __error; + } + knew.private_value = (unsigned long)ctl; + *ctl = *nctl; + kctl = snd_ctl_new1(&knew, emu); + err = snd_ctl_add(emu->card, kctl); + if (err < 0) { + kfree(ctl); + kfree(knew.tlv.p); + goto __error; + } + kctl->private_free = snd_emu10k1_ctl_private_free; + ctl->kcontrol = kctl; + list_add_tail(&ctl->list, &emu->fx8010.gpr_ctl); + } else { + /* overwrite */ + nctl->list = ctl->list; + nctl->kcontrol = ctl->kcontrol; + *ctl = *nctl; + snd_ctl_notify(emu->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &ctl->kcontrol->id); + } + snd_emu10k1_gpr_ctl_put(ctl->kcontrol, val); + } + __error: + kfree(nctl); + kfree(gctl); + kfree(val); + return err; +} + +static int snd_emu10k1_del_controls(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode, + bool in_kernel) +{ + unsigned int i; + struct emu10k1_ctl_elem_id id; + struct snd_emu10k1_fx8010_ctl *ctl; + struct snd_card *card = emu->card; + int err; + + for (i = 0; i < icode->gpr_del_control_count; i++) { + err = copy_ctl_elem_id(icode->gpr_del_controls, i, &id, + in_kernel); + if (err < 0) + return err; + ctl = snd_emu10k1_look_for_ctl(emu, &id); + if (ctl) + snd_ctl_remove(card, ctl->kcontrol); + } + return 0; +} + +static int snd_emu10k1_list_controls(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode) +{ + unsigned int i = 0, j; + unsigned int total = 0; + struct snd_emu10k1_fx8010_control_gpr *gctl; + struct snd_emu10k1_fx8010_ctl *ctl; + struct snd_ctl_elem_id *id; + + gctl = kmalloc_obj(*gctl); + if (! gctl) + return -ENOMEM; + + list_for_each_entry(ctl, &emu->fx8010.gpr_ctl, list) { + total++; + if (icode->gpr_list_controls && + i < icode->gpr_list_control_count) { + memset(gctl, 0, sizeof(*gctl)); + id = &ctl->kcontrol->id; + gctl->id.iface = id->iface; + strscpy(gctl->id.name, id->name, sizeof(gctl->id.name)); + gctl->id.index = id->index; + gctl->id.device = id->device; + gctl->id.subdevice = id->subdevice; + gctl->vcount = ctl->vcount; + gctl->count = ctl->count; + for (j = 0; j < 32; j++) { + gctl->gpr[j] = ctl->gpr[j]; + gctl->value[j] = ctl->value[j]; + } + gctl->min = ctl->min; + gctl->max = ctl->max; + gctl->translation = ctl->translation; + if (copy_gctl_to_user(emu, icode->gpr_list_controls, + gctl, i)) { + kfree(gctl); + return -EFAULT; + } + i++; + } + } + icode->gpr_list_control_total = total; + kfree(gctl); + return 0; +} + +static int snd_emu10k1_icode_poke(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode, + bool in_kernel) +{ + int err; + + guard(mutex)(&emu->fx8010.lock); + err = snd_emu10k1_verify_controls(emu, icode, in_kernel); + if (err < 0) + return err; + strscpy(emu->fx8010.name, icode->name, sizeof(emu->fx8010.name)); + /* stop FX processor - this may be dangerous, but it's better to miss + some samples than generate wrong ones - [jk] */ + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_SINGLE_STEP); + else + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_SINGLE_STEP); + /* ok, do the main job */ + err = snd_emu10k1_del_controls(emu, icode, in_kernel); + if (err < 0) + return err; + err = snd_emu10k1_gpr_poke(emu, icode, in_kernel); + if (err < 0) + return err; + err = snd_emu10k1_tram_poke(emu, icode, in_kernel); + if (err < 0) + return err; + err = snd_emu10k1_code_poke(emu, icode, in_kernel); + if (err < 0) + return err; + err = snd_emu10k1_add_controls(emu, icode, in_kernel); + if (err < 0) + return err; + /* start FX processor when the DSP code is updated */ + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg); + else + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg); + return 0; +} + +static int snd_emu10k1_icode_peek(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode) +{ + int err; + + guard(mutex)(&emu->fx8010.lock); + strscpy(icode->name, emu->fx8010.name, sizeof(icode->name)); + /* ok, do the main job */ + err = snd_emu10k1_gpr_peek(emu, icode); + if (err >= 0) + err = snd_emu10k1_tram_peek(emu, icode); + if (err >= 0) + err = snd_emu10k1_code_peek(emu, icode); + if (err >= 0) + err = snd_emu10k1_list_controls(emu, icode); + return err; +} + +static int snd_emu10k1_ipcm_poke(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_pcm_rec *ipcm) +{ + unsigned int i; + struct snd_emu10k1_fx8010_pcm *pcm; + + if (ipcm->substream >= EMU10K1_FX8010_PCM_COUNT) + return -EINVAL; + ipcm->substream = array_index_nospec(ipcm->substream, + EMU10K1_FX8010_PCM_COUNT); + if (ipcm->channels > 32) + return -EINVAL; + pcm = &emu->fx8010.pcm[ipcm->substream]; + guard(mutex)(&emu->fx8010.lock); + guard(spinlock_irq)(&emu->reg_lock); + if (pcm->opened) + return -EBUSY; + if (ipcm->channels == 0) { /* remove */ + pcm->valid = 0; + } else { + /* FIXME: we need to add universal code to the PCM transfer routine */ + if (ipcm->channels != 2) + return -EINVAL; + pcm->valid = 1; + pcm->opened = 0; + pcm->channels = ipcm->channels; + pcm->tram_start = ipcm->tram_start; + pcm->buffer_size = ipcm->buffer_size; + pcm->gpr_size = ipcm->gpr_size; + pcm->gpr_count = ipcm->gpr_count; + pcm->gpr_tmpcount = ipcm->gpr_tmpcount; + pcm->gpr_ptr = ipcm->gpr_ptr; + pcm->gpr_trigger = ipcm->gpr_trigger; + pcm->gpr_running = ipcm->gpr_running; + for (i = 0; i < pcm->channels; i++) + pcm->etram[i] = ipcm->etram[i]; + } + return 0; +} + +static int snd_emu10k1_ipcm_peek(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_pcm_rec *ipcm) +{ + unsigned int i; + struct snd_emu10k1_fx8010_pcm *pcm; + + if (ipcm->substream >= EMU10K1_FX8010_PCM_COUNT) + return -EINVAL; + ipcm->substream = array_index_nospec(ipcm->substream, + EMU10K1_FX8010_PCM_COUNT); + pcm = &emu->fx8010.pcm[ipcm->substream]; + guard(mutex)(&emu->fx8010.lock); + guard(spinlock_irq)(&emu->reg_lock); + ipcm->channels = pcm->channels; + ipcm->tram_start = pcm->tram_start; + ipcm->buffer_size = pcm->buffer_size; + ipcm->gpr_size = pcm->gpr_size; + ipcm->gpr_ptr = pcm->gpr_ptr; + ipcm->gpr_count = pcm->gpr_count; + ipcm->gpr_tmpcount = pcm->gpr_tmpcount; + ipcm->gpr_trigger = pcm->gpr_trigger; + ipcm->gpr_running = pcm->gpr_running; + for (i = 0; i < pcm->channels; i++) + ipcm->etram[i] = pcm->etram[i]; + ipcm->res1 = ipcm->res2 = 0; + ipcm->pad = 0; + return 0; +} + +#define SND_EMU10K1_GPR_CONTROLS 44 +#define SND_EMU10K1_INPUTS 12 +#define SND_EMU10K1_PLAYBACK_CHANNELS 8 +#define SND_EMU10K1_CAPTURE_CHANNELS 4 + +#define HR_VAL(v) ((v) * 0x80000000LL / 100 - 1) + +static void +snd_emu10k1_init_mono_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) +{ + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + if (high_res_gpr_volume) { + ctl->min = -1; + ctl->max = 0x7fffffff; + ctl->tlv = snd_emu10k1_db_linear; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; + defval = defval_hr; + } else { + ctl->min = 0; + ctl->max = 100; + ctl->tlv = snd_emu10k1_db_scale1; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEG_TABLE100; + } + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +} +#define snd_emu10k1_init_mono_control(ctl, name, gpr, defval) \ + snd_emu10k1_init_mono_control2(ctl, name, gpr, defval, HR_VAL(defval)) + +static void +snd_emu10k1_init_stereo_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) +{ + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + if (high_res_gpr_volume) { + ctl->min = -1; + ctl->max = 0x7fffffff; + ctl->tlv = snd_emu10k1_db_linear; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; + defval = defval_hr; + } else { + ctl->min = 0; + ctl->max = 100; + ctl->tlv = snd_emu10k1_db_scale1; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEG_TABLE100; + } + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; + ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; +} +#define snd_emu10k1_init_stereo_control(ctl, name, gpr, defval) \ + snd_emu10k1_init_stereo_control2(ctl, name, gpr, defval, HR_VAL(defval)) + +static void +snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) +{ + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; + ctl->min = 0; + ctl->max = 1; + ctl->translation = EMU10K1_GPR_TRANSLATION_ONOFF; +} + +static void +snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) +{ + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; + ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; + ctl->min = 0; + ctl->max = 1; + ctl->translation = EMU10K1_GPR_TRANSLATION_ONOFF; +} + +/* + * Used for emu1010 - conversion from 32-bit capture inputs from the FPGA + * to 2 x 16-bit registers in Audigy - their values are read via DMA. + * Conversion is performed by Audigy DSP instructions of FX8010. + */ +static void snd_emu10k1_audigy_dsp_convert_32_to_2x16( + struct snd_emu10k1_fx8010_code *icode, + u32 *ptr, int tmp, int bit_shifter16, + int reg_in, int reg_out) +{ + // This leaves the low word in place, which is fine, + // as the low bits are completely ignored subsequently. + // reg_out[1] = reg_in + A_OP(icode, ptr, iACC3, reg_out + 1, reg_in, A_C_00000000, A_C_00000000); + // It is fine to read reg_in multiple times. + // tmp = reg_in << 15 + A_OP(icode, ptr, iMACINT1, A_GPR(tmp), A_C_00000000, reg_in, A_GPR(bit_shifter16)); + // Left-shift once more. This is a separate step, as the + // signed multiplication would clobber the MSB. + // reg_out[0] = tmp + ((tmp << 31) >> 31) + A_OP(icode, ptr, iMAC3, reg_out, A_GPR(tmp), A_GPR(tmp), A_C_80000000); +} + +#define ENUM_GPR(name, size) name, name ## _dummy = name + (size) - 1 + +/* + * initial DSP configuration for Audigy + */ + +static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) +{ + int err, z, nctl; + enum { + ENUM_GPR(playback, SND_EMU10K1_PLAYBACK_CHANNELS), + ENUM_GPR(stereo_mix, 2), + ENUM_GPR(capture, 2), + ENUM_GPR(bit_shifter16, 1), + // The fixed allocation of these breaks the pattern, but why not. + // Splitting these into left/right is questionable, as it will break + // down for center/lfe. But it works for stereo/quadro, so whatever. + ENUM_GPR(bass_gpr, 2 * 5), // two sides, five coefficients + ENUM_GPR(treble_gpr, 2 * 5), + ENUM_GPR(bass_tmp, SND_EMU10K1_PLAYBACK_CHANNELS * 4), // four delay stages + ENUM_GPR(treble_tmp, SND_EMU10K1_PLAYBACK_CHANNELS * 4), + ENUM_GPR(tmp, 3), + num_static_gprs + }; + int gpr = num_static_gprs; + u32 ptr, ptr_skip; + struct snd_emu10k1_fx8010_code *icode = NULL; + struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl; + u32 *gpr_map; + + err = -ENOMEM; + icode = kzalloc_obj(*icode); + if (!icode) + return err; + + icode->gpr_map = kzalloc_objs(u_int32_t, 512 + 256 + 256 + 2 * 1024); + if (!icode->gpr_map) + goto __err_gpr; + controls = kzalloc_objs(*controls, SND_EMU10K1_GPR_CONTROLS); + if (!controls) + goto __err_ctrls; + + gpr_map = icode->gpr_map; + + icode->tram_data_map = icode->gpr_map + 512; + icode->tram_addr_map = icode->tram_data_map + 256; + icode->code = icode->tram_addr_map + 256; + + /* clear free GPRs */ + memset(icode->gpr_valid, 0xff, 512 / 8); + + /* clear TRAM data & address lines */ + memset(icode->tram_valid, 0xff, 256 / 8); + + strscpy(icode->name, "Audigy DSP code for ALSA"); + ptr = 0; + nctl = 0; + gpr_map[bit_shifter16] = 0x00008000; + +#if 1 + /* PCM front Playback Volume (independent from stereo mix) + * playback = -gpr * FXBUS_PCM_LEFT_FRONT >> 31 + * where gpr contains negated attenuation from corresponding mixer control + * (snd_emu10k1_init_stereo_control) + */ + A_OP(icode, &ptr, iMAC1, A_GPR(playback), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_FRONT)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_FRONT)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Front Playback Volume", gpr, 100); + gpr += 2; + + /* PCM Surround Playback (independent from stereo mix) */ + A_OP(icode, &ptr, iMAC1, A_GPR(playback+2), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_REAR)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+3), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_REAR)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Surround Playback Volume", gpr, 100); + gpr += 2; + + /* PCM Side Playback (independent from stereo mix) */ + if (emu->card_capabilities->spk71) { + A_OP(icode, &ptr, iMAC1, A_GPR(playback+6), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_SIDE)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+7), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_SIDE)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Side Playback Volume", gpr, 100); + gpr += 2; + } + + /* PCM Center Playback (independent from stereo mix) */ + A_OP(icode, &ptr, iMAC1, A_GPR(playback+4), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_CENTER)); + snd_emu10k1_init_mono_control(&controls[nctl++], "PCM Center Playback Volume", gpr, 100); + gpr++; + + /* PCM LFE Playback (independent from stereo mix) */ + A_OP(icode, &ptr, iMAC1, A_GPR(playback+5), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LFE)); + snd_emu10k1_init_mono_control(&controls[nctl++], "PCM LFE Playback Volume", gpr, 100); + gpr++; + + /* + * Stereo Mix + */ + /* Wave (PCM) Playback Volume (will be renamed later) */ + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Wave Playback Volume", gpr, 100); + gpr += 2; + + /* Synth Playback */ + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix+0), A_GPR(stereo_mix+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix+1), A_GPR(stereo_mix+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Playback Volume", gpr, 100); + gpr += 2; + + /* Wave (PCM) Capture */ + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Capture Volume", gpr, 0); + gpr += 2; + + /* Synth Capture */ + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Capture Volume", gpr, 0); + gpr += 2; + + // We need to double the volume, as we configure the voices for half volume, + // which is necessary for bit-identical reproduction. + { static_assert(stereo_mix == playback + SND_EMU10K1_PLAYBACK_CHANNELS); } + for (z = 0; z < SND_EMU10K1_PLAYBACK_CHANNELS + 2; z++) + A_OP(icode, &ptr, iACC3, A_GPR(playback + z), A_GPR(playback + z), A_GPR(playback + z), A_C_00000000); + + /* + * inputs + */ +#define A_ADD_VOLUME_IN(var,vol,input) \ + A_OP(icode, &ptr, iMAC1, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) + + if (emu->card_capabilities->emu_model) { + /* EMU1010 DSP 0 and DSP 1 Capture */ + // The 24 MSB hold the actual value. We implicitly discard the 16 LSB. + if (emu->card_capabilities->ca0108_chip) { + // For unclear reasons, the EMU32IN cannot be the Y operand! + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_GPR(capture+0), A3_EMU32IN(0x0), A_GPR(gpr)); + // A3_EMU32IN(0) is delayed by one sample, so all other A3_EMU32IN channels + // need to be delayed as well; we use an auxiliary register for that. + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A3_EMU32IN(0x1), A_C_00000000, A_C_00000000); + } else { + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_GPR(capture+0), A_P16VIN(0x0), A_GPR(gpr)); + // A_P16VIN(0) is delayed by one sample, so all other A_P16VIN channels + // need to be delayed as well; we use an auxiliary register for that. + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A_P16VIN(0x1), A_C_00000000, A_C_00000000); + } + snd_emu10k1_init_stereo_control(&controls[nctl++], "EMU Capture Volume", gpr, 0); + gpr_map[gpr + 2] = 0x00000000; + gpr += 3; + } else { + if (emu->card_capabilities->ac97_chip) { + /* AC'97 Playback Volume - used only for mic (renamed later) */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0); + gpr += 2; + /* AC'97 Capture Volume - used only for mic */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0); + gpr += 2; + + /* mic capture buffer */ + A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), A_C_40000000, A_EXTIN(A_EXTIN_AC97_R)); + } + + /* Audigy CD Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_SPDIF_CD_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_SPDIF_CD_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Audigy CD Playback Volume" : "CD Playback Volume", + gpr, 0); + gpr += 2; + /* Audigy CD Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_SPDIF_CD_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_SPDIF_CD_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Audigy CD Capture Volume" : "CD Capture Volume", + gpr, 0); + gpr += 2; + + /* Optical SPDIF Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_OPT_SPDIF_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_OPT_SPDIF_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",PLAYBACK,VOLUME), gpr, 0); + gpr += 2; + /* Optical SPDIF Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_OPT_SPDIF_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_OPT_SPDIF_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",CAPTURE,VOLUME), gpr, 0); + gpr += 2; + + /* Line2 Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_LINE2_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_LINE2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Line2 Playback Volume" : "Line Playback Volume", + gpr, 0); + gpr += 2; + /* Line2 Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_LINE2_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_LINE2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Line2 Capture Volume" : "Line Capture Volume", + gpr, 0); + gpr += 2; + + /* Philips ADC Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_ADC_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_ADC_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Playback Volume", gpr, 0); + gpr += 2; + /* Philips ADC Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_ADC_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_ADC_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Capture Volume", gpr, 0); + gpr += 2; + + /* Aux2 Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AUX2_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AUX2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Aux2 Playback Volume" : "Aux Playback Volume", + gpr, 0); + gpr += 2; + /* Aux2 Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AUX2_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AUX2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Aux2 Capture Volume" : "Aux Capture Volume", + gpr, 0); + gpr += 2; + } + + /* Stereo Mix Front Playback Volume */ + A_OP(icode, &ptr, iMAC1, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+1), A_GPR(playback+1), A_GPR(gpr+1), A_GPR(stereo_mix+1)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Front Playback Volume", gpr, 100); + gpr += 2; + + /* Stereo Mix Surround Playback */ + A_OP(icode, &ptr, iMAC1, A_GPR(playback+2), A_GPR(playback+2), A_GPR(gpr), A_GPR(stereo_mix)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+3), A_GPR(playback+3), A_GPR(gpr+1), A_GPR(stereo_mix+1)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Surround Playback Volume", gpr, 0); + gpr += 2; + + /* Stereo Mix Center Playback */ + /* Center = sub = Left/2 + Right/2 */ + A_OP(icode, &ptr, iINTERP, A_GPR(tmp), A_GPR(stereo_mix), A_C_40000000, A_GPR(stereo_mix+1)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+4), A_GPR(playback+4), A_GPR(gpr), A_GPR(tmp)); + snd_emu10k1_init_mono_control(&controls[nctl++], "Center Playback Volume", gpr, 0); + gpr++; + + /* Stereo Mix LFE Playback */ + A_OP(icode, &ptr, iMAC1, A_GPR(playback+5), A_GPR(playback+5), A_GPR(gpr), A_GPR(tmp)); + snd_emu10k1_init_mono_control(&controls[nctl++], "LFE Playback Volume", gpr, 0); + gpr++; + + if (emu->card_capabilities->spk71) { + /* Stereo Mix Side Playback */ + A_OP(icode, &ptr, iMAC1, A_GPR(playback+6), A_GPR(playback+6), A_GPR(gpr), A_GPR(stereo_mix)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+7), A_GPR(playback+7), A_GPR(gpr+1), A_GPR(stereo_mix+1)); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Side Playback Volume", gpr, 0); + gpr += 2; + } + + /* + * outputs + */ +#define A_PUT_OUTPUT(out,src) A_OP(icode, &ptr, iACC3, A_EXTOUT(out), A_C_00000000, A_C_00000000, A_GPR(src)) +#define A_PUT_STEREO_OUTPUT(out1,out2,src) \ + {A_PUT_OUTPUT(out1,src); A_PUT_OUTPUT(out2,src+1);} + +#define _A_SWITCH(icode, ptr, dst, src, sw) \ + A_OP((icode), ptr, iMACINT0, dst, A_C_00000000, src, sw); +#define A_SWITCH(icode, ptr, dst, src, sw) \ + _A_SWITCH(icode, ptr, A_GPR(dst), A_GPR(src), A_GPR(sw)) +#define _A_SWITCH_NEG(icode, ptr, dst, src) \ + A_OP((icode), ptr, iANDXOR, dst, src, A_C_00000001, A_C_00000001); +#define A_SWITCH_NEG(icode, ptr, dst, src) \ + _A_SWITCH_NEG(icode, ptr, A_GPR(dst), A_GPR(src)) + + + /* + * Process tone control + */ + ctl = &controls[nctl + 0]; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; + ctl->min = 0; + ctl->max = 40; + ctl->value[0] = ctl->value[1] = 20; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[nctl + 1]; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; + ctl->min = 0; + ctl->max = 40; + ctl->value[0] = ctl->value[1] = 20; + ctl->translation = EMU10K1_GPR_TRANSLATION_TREBLE; + for (z = 0; z < 5; z++) { + int j; + for (j = 0; j < 2; j++) { + controls[nctl + 0].gpr[z * 2 + j] = bass_gpr + z * 2 + j; + controls[nctl + 1].gpr[z * 2 + j] = treble_gpr + z * 2 + j; + } + } + nctl += 2; + + A_OP(icode, &ptr, iACC3, A_C_00000000, A_GPR(gpr), A_C_00000000, A_C_00000000); + snd_emu10k1_init_mono_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0); + gpr++; + A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_CC_REG_ZERO, A_GPR(gpr)); + ptr_skip = ptr; + for (z = 0; z < 4; z++) { /* front/rear/center-lfe/side */ + int j, k, l, d; + for (j = 0; j < 2; j++) { /* left/right */ + k = bass_tmp + (z * 8) + (j * 4); + l = treble_tmp + (z * 8) + (j * 4); + d = playback + z * 2 + j; + + A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(d), A_GPR(bass_gpr + 0 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(k+1), A_GPR(k), A_GPR(k+1), A_GPR(bass_gpr + 4 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(k), A_GPR(d), A_GPR(k), A_GPR(bass_gpr + 2 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(k+3), A_GPR(k+2), A_GPR(k+3), A_GPR(bass_gpr + 8 + j)); + A_OP(icode, &ptr, iMAC0, A_GPR(k+2), A_GPR_ACCU, A_GPR(k+2), A_GPR(bass_gpr + 6 + j)); + A_OP(icode, &ptr, iACC3, A_GPR(k+2), A_GPR(k+2), A_GPR(k+2), A_C_00000000); + + A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(k+2), A_GPR(treble_gpr + 0 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(l+1), A_GPR(l), A_GPR(l+1), A_GPR(treble_gpr + 4 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(l), A_GPR(k+2), A_GPR(l), A_GPR(treble_gpr + 2 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(l+3), A_GPR(l+2), A_GPR(l+3), A_GPR(treble_gpr + 8 + j)); + A_OP(icode, &ptr, iMAC0, A_GPR(l+2), A_GPR_ACCU, A_GPR(l+2), A_GPR(treble_gpr + 6 + j)); + A_OP(icode, &ptr, iMACINT0, A_GPR(l+2), A_C_00000000, A_GPR(l+2), A_C_00000010); + + A_OP(icode, &ptr, iACC3, A_GPR(d), A_GPR(l+2), A_C_00000000, A_C_00000000); + + if (z == 2) /* center */ + break; + } + } + gpr_map[gpr++] = ptr - ptr_skip; + + /* Master volume (will be renamed later) */ + for (z = 0; z < 8; z++) + A_OP(icode, &ptr, iMAC1, A_GPR(playback+z), A_C_00000000, A_GPR(gpr), A_GPR(playback+z)); + snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); + gpr++; + + if (emu->card_capabilities->emu_model) { + /* EMU1010 Outputs from PCM Front, Rear, Center, LFE, Side */ + dev_info(emu->card->dev, "EMU outputs on\n"); + for (z = 0; z < 8; z++) { + if (emu->card_capabilities->ca0108_chip) { + A_OP(icode, &ptr, iACC3, A3_EMU32OUT(z), A_GPR(playback + z), A_C_00000000, A_C_00000000); + } else { + A_OP(icode, &ptr, iACC3, A_EMU32OUTL(z), A_GPR(playback + z), A_C_00000000, A_C_00000000); + } + } + } else { + /* analog speakers */ + A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback); + A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5); + if (emu->card_capabilities->spk71) + A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6); + + /* headphone */ + A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback); + + /* IEC958 Optical Raw Playback Switch */ + gpr_map[gpr++] = 0; + gpr_map[gpr++] = 0x1008; + gpr_map[gpr++] = 0xffff0000; + for (z = 0; z < 2; z++) { + A_OP(icode, &ptr, iMAC0, A_GPR(tmp + 2), A_FXBUS(FXBUS_PT_LEFT + z), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_GPR(gpr - 2), A_C_00000001); + A_OP(icode, &ptr, iACC3, A_GPR(tmp + 2), A_C_00000000, A_C_00010000, A_GPR(tmp + 2)); + A_OP(icode, &ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_GPR(gpr - 1), A_C_00000000); + A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z); + A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z); + A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); + if ((z==1) && (emu->card_capabilities->spdif_bug)) { + /* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */ + dev_info(emu->card->dev, + "Installing spdif_bug patch: %s\n", + emu->card_capabilities->name); + A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(gpr - 3), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 3), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); + } else { + A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); + } + } + snd_emu10k1_init_stereo_onoff_control(controls + nctl++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0); + gpr += 2; + + A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5); + } + + /* ADC buffer */ +#ifdef EMU10K1_CAPTURE_DIGITAL_OUT + A_PUT_STEREO_OUTPUT(A_EXTOUT_ADC_CAP_L, A_EXTOUT_ADC_CAP_R, playback); +#else + A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_L, capture); + A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_R, capture+1); +#endif + + if (emu->card_capabilities->emu_model) { + /* Capture 16 channels of S32_LE sound. */ + if (emu->card_capabilities->ca0108_chip) { + dev_info(emu->card->dev, "EMU2 inputs on\n"); + /* Note that the Tina[2] DSPs have 16 more EMU32 inputs which we don't use. */ + + snd_emu10k1_audigy_dsp_convert_32_to_2x16( + icode, &ptr, tmp, bit_shifter16, A3_EMU32IN(0), A_FXBUS2(0)); + // A3_EMU32IN(0) is delayed by one sample, so all other A3_EMU32IN channels + // need to be delayed as well; we use an auxiliary register for that. + for (z = 1; z < 0x10; z++) { + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr), + A_FXBUS2(z*2) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr), A3_EMU32IN(z), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + } + } else { + dev_info(emu->card->dev, "EMU inputs on\n"); + /* Note that the Alice2 DSPs have 6 I2S inputs which we don't use. */ + + /* + dev_dbg(emu->card->dev, "emufx.c: gpr=0x%x, tmp=0x%x\n", + gpr, tmp); + */ + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_P16VIN(0x0), A_FXBUS2(0) ); + /* A_P16VIN(0) is delayed by one sample, so all other A_P16VIN channels + * will need to also be delayed; we use an auxiliary register for that. */ + for (z = 1; z < 0x10; z++) { + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr), A_FXBUS2(z * 2) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr), A_P16VIN(z), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + } + } + +#if 0 + for (z = 4; z < 8; z++) { + A_OP(icode, &ptr, iACC3, A_FXBUS2(z), A_C_00000000, A_C_00000000, A_C_00000000); + } + for (z = 0xc; z < 0x10; z++) { + A_OP(icode, &ptr, iACC3, A_FXBUS2(z), A_C_00000000, A_C_00000000, A_C_00000000); + } +#endif + } else { + /* EFX capture - capture the 16 EXTINs */ + /* Capture 16 channels of S16_LE sound */ + for (z = 0; z < 16; z++) { + A_OP(icode, &ptr, iACC3, A_FXBUS2(z), A_C_00000000, A_C_00000000, A_EXTIN(z)); + } + } + +#endif /* JCD test */ + /* + * ok, set up done.. + */ + + if (gpr > 512) { + snd_BUG(); + err = -EIO; + goto __err; + } + + /* clear remaining instruction memory */ + while (ptr < 0x400) + A_OP(icode, &ptr, 0x0f, 0xc0, 0xc0, 0xcf, 0xc0); + + icode->gpr_add_control_count = nctl; + icode->gpr_add_controls = controls; + emu->support_tlv = 1; /* support TLV */ + err = snd_emu10k1_icode_poke(emu, icode, true); + emu->support_tlv = 0; /* clear again */ + +__err: + kfree(controls); +__err_ctrls: + kfree(icode->gpr_map); +__err_gpr: + kfree(icode); + return err; +} + + +/* + * initial DSP configuration for Emu10k1 + */ + +/* Volumes are in the [-2^31, 0] range, zero being mute. */ +static void _volume(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol) +{ + OP(icode, ptr, iMAC1, dst, C_00000000, src, vol); +} +static void _volume_add(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol) +{ + OP(icode, ptr, iMAC1, dst, dst, src, vol); +} + +#define VOLUME(icode, ptr, dst, src, vol) \ + _volume(icode, ptr, GPR(dst), GPR(src), GPR(vol)) +#define VOLUME_IN(icode, ptr, dst, src, vol) \ + _volume(icode, ptr, GPR(dst), EXTIN(src), GPR(vol)) +#define VOLUME_ADD(icode, ptr, dst, src, vol) \ + _volume_add(icode, ptr, GPR(dst), GPR(src), GPR(vol)) +#define VOLUME_ADDIN(icode, ptr, dst, src, vol) \ + _volume_add(icode, ptr, GPR(dst), EXTIN(src), GPR(vol)) +#define VOLUME_OUT(icode, ptr, dst, src, vol) \ + _volume(icode, ptr, EXTOUT(dst), GPR(src), GPR(vol)) +#define _SWITCH(icode, ptr, dst, src, sw) \ + OP((icode), ptr, iMACINT0, dst, C_00000000, src, sw); +#define SWITCH(icode, ptr, dst, src, sw) \ + _SWITCH(icode, ptr, GPR(dst), GPR(src), GPR(sw)) +#define SWITCH_IN(icode, ptr, dst, src, sw) \ + _SWITCH(icode, ptr, GPR(dst), EXTIN(src), GPR(sw)) +#define _SWITCH_NEG(icode, ptr, dst, src) \ + OP((icode), ptr, iANDXOR, dst, src, C_00000001, C_00000001); +#define SWITCH_NEG(icode, ptr, dst, src) \ + _SWITCH_NEG(icode, ptr, GPR(dst), GPR(src)) + + +static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) +{ + int err, i, z, gpr, tmp, playback, capture; + u32 ptr, ptr_skip; + struct snd_emu10k1_fx8010_code *icode; + struct snd_emu10k1_fx8010_pcm_rec *ipcm = NULL; + struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl; + u32 *gpr_map; + + err = -ENOMEM; + icode = kzalloc_obj(*icode); + if (!icode) + return err; + + icode->gpr_map = kzalloc_objs(u_int32_t, 256 + 160 + 160 + 2 * 512); + if (!icode->gpr_map) + goto __err_gpr; + + controls = kzalloc_objs(struct snd_emu10k1_fx8010_control_gpr, + SND_EMU10K1_GPR_CONTROLS); + if (!controls) + goto __err_ctrls; + + ipcm = kzalloc_obj(*ipcm); + if (!ipcm) + goto __err_ipcm; + + gpr_map = icode->gpr_map; + + icode->tram_data_map = icode->gpr_map + 256; + icode->tram_addr_map = icode->tram_data_map + 160; + icode->code = icode->tram_addr_map + 160; + + /* clear free GPRs */ + memset(icode->gpr_valid, 0xff, 256 / 8); + + /* clear TRAM data & address lines */ + memset(icode->tram_valid, 0xff, 160 / 8); + + strscpy(icode->name, "SB Live! FX8010 code for ALSA v1.2 by Jaroslav Kysela"); + ptr = 0; i = 0; + /* we have 12 inputs */ + playback = SND_EMU10K1_INPUTS; + /* we have 6 playback channels and tone control doubles */ + capture = playback + SND_EMU10K1_PLAYBACK_CHANNELS; + gpr = capture + SND_EMU10K1_CAPTURE_CHANNELS; + tmp = 0x88; /* we need 4 temporary GPR */ + /* from 0x8c to 0xff is the area for tone control */ + + /* + * Process FX Buses + */ + OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(8), C_00000000, C_00000000, C_00000000); /* S/PDIF left */ + OP(icode, &ptr, iMACINT0, GPR(9), C_00000000, C_00000000, C_00000000); /* S/PDIF right */ + OP(icode, &ptr, iMACINT0, GPR(10), C_00000000, FXBUS(FXBUS_PCM_LEFT_FRONT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(11), C_00000000, FXBUS(FXBUS_PCM_RIGHT_FRONT), C_00000008); + + /* Raw S/PDIF PCM */ + ipcm->substream = 0; + ipcm->channels = 2; + ipcm->tram_start = 0; + ipcm->buffer_size = (64 * 1024) / 2; + ipcm->gpr_size = gpr++; + ipcm->gpr_ptr = gpr++; + ipcm->gpr_count = gpr++; + ipcm->gpr_tmpcount = gpr++; + ipcm->gpr_trigger = gpr++; + ipcm->gpr_running = gpr++; + ipcm->etram[0] = 0; + ipcm->etram[1] = 1; + + gpr_map[gpr + 0] = 0xfffff000; + gpr_map[gpr + 1] = 0xffff0000; + gpr_map[gpr + 2] = 0x70000000; + gpr_map[gpr + 3] = 0x00000007; + gpr_map[gpr + 4] = 0x001f << 11; + gpr_map[gpr + 5] = 0x001c << 11; + gpr_map[gpr + 6] = (0x22 - 0x01) - 1; /* skip at 01 to 22 */ + gpr_map[gpr + 7] = (0x22 - 0x06) - 1; /* skip at 06 to 22 */ + gpr_map[gpr + 8] = 0x2000000 + (2<<11); + gpr_map[gpr + 9] = 0x4000000 + (2<<11); + gpr_map[gpr + 10] = 1<<11; + gpr_map[gpr + 11] = (0x24 - 0x0a) - 1; /* skip at 0a to 24 */ + gpr_map[gpr + 12] = 0; + + /* if the trigger flag is not set, skip */ + /* 00: */ OP(icode, &ptr, iMAC0, C_00000000, GPR(ipcm->gpr_trigger), C_00000000, C_00000000); + /* 01: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_ZERO, GPR(gpr + 6)); + /* if the running flag is set, we're running */ + /* 02: */ OP(icode, &ptr, iMAC0, C_00000000, GPR(ipcm->gpr_running), C_00000000, C_00000000); + /* 03: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000004); + /* wait until ((GPR_DBAC>>11) & 0x1f) == 0x1c) */ + /* 04: */ OP(icode, &ptr, iANDXOR, GPR(tmp + 0), GPR_DBAC, GPR(gpr + 4), C_00000000); + /* 05: */ OP(icode, &ptr, iMACINT0, C_00000000, GPR(tmp + 0), C_ffffffff, GPR(gpr + 5)); + /* 06: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, GPR(gpr + 7)); + /* 07: */ OP(icode, &ptr, iACC3, GPR(gpr + 12), C_00000010, C_00000001, C_00000000); + + /* 08: */ OP(icode, &ptr, iANDXOR, GPR(ipcm->gpr_running), GPR(ipcm->gpr_running), C_00000000, C_00000001); + /* 09: */ OP(icode, &ptr, iACC3, GPR(gpr + 12), GPR(gpr + 12), C_ffffffff, C_00000000); + /* 0a: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, GPR(gpr + 11)); + /* 0b: */ OP(icode, &ptr, iACC3, GPR(gpr + 12), C_00000001, C_00000000, C_00000000); + + /* 0c: */ OP(icode, &ptr, iANDXOR, GPR(tmp + 0), ETRAM_DATA(ipcm->etram[0]), GPR(gpr + 0), C_00000000); + /* 0d: */ OP(icode, &ptr, iLOG, GPR(tmp + 0), GPR(tmp + 0), GPR(gpr + 3), C_00000000); + /* 0e: */ OP(icode, &ptr, iANDXOR, GPR(8), GPR(tmp + 0), GPR(gpr + 1), GPR(gpr + 2)); + /* 0f: */ OP(icode, &ptr, iSKIP, C_00000000, GPR_COND, CC_REG_MINUS, C_00000001); + /* 10: */ OP(icode, &ptr, iANDXOR, GPR(8), GPR(8), GPR(gpr + 1), GPR(gpr + 2)); + + /* 11: */ OP(icode, &ptr, iANDXOR, GPR(tmp + 0), ETRAM_DATA(ipcm->etram[1]), GPR(gpr + 0), C_00000000); + /* 12: */ OP(icode, &ptr, iLOG, GPR(tmp + 0), GPR(tmp + 0), GPR(gpr + 3), C_00000000); + /* 13: */ OP(icode, &ptr, iANDXOR, GPR(9), GPR(tmp + 0), GPR(gpr + 1), GPR(gpr + 2)); + /* 14: */ OP(icode, &ptr, iSKIP, C_00000000, GPR_COND, CC_REG_MINUS, C_00000001); + /* 15: */ OP(icode, &ptr, iANDXOR, GPR(9), GPR(9), GPR(gpr + 1), GPR(gpr + 2)); + + /* 16: */ OP(icode, &ptr, iACC3, GPR(tmp + 0), GPR(ipcm->gpr_ptr), C_00000001, C_00000000); + /* 17: */ OP(icode, &ptr, iMACINT0, C_00000000, GPR(tmp + 0), C_ffffffff, GPR(ipcm->gpr_size)); + /* 18: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_MINUS, C_00000001); + /* 19: */ OP(icode, &ptr, iACC3, GPR(tmp + 0), C_00000000, C_00000000, C_00000000); + /* 1a: */ OP(icode, &ptr, iACC3, GPR(ipcm->gpr_ptr), GPR(tmp + 0), C_00000000, C_00000000); + + /* 1b: */ OP(icode, &ptr, iACC3, GPR(ipcm->gpr_tmpcount), GPR(ipcm->gpr_tmpcount), C_ffffffff, C_00000000); + /* 1c: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002); + /* 1d: */ OP(icode, &ptr, iACC3, GPR(ipcm->gpr_tmpcount), GPR(ipcm->gpr_count), C_00000000, C_00000000); + /* 1e: */ OP(icode, &ptr, iACC3, GPR_IRQ, C_80000000, C_00000000, C_00000000); + /* 1f: */ OP(icode, &ptr, iANDXOR, GPR(ipcm->gpr_running), GPR(ipcm->gpr_running), C_00000001, C_00010000); + + /* 20: */ OP(icode, &ptr, iANDXOR, GPR(ipcm->gpr_running), GPR(ipcm->gpr_running), C_00010000, C_00000001); + /* 21: */ OP(icode, &ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000002); + + /* 22: */ OP(icode, &ptr, iMACINT1, ETRAM_ADDR(ipcm->etram[0]), GPR(gpr + 8), GPR_DBAC, C_ffffffff); + /* 23: */ OP(icode, &ptr, iMACINT1, ETRAM_ADDR(ipcm->etram[1]), GPR(gpr + 9), GPR_DBAC, C_ffffffff); + + /* 24: */ + gpr += 13; + + /* Wave Playback Volume */ + for (z = 0; z < 2; z++) + VOLUME(icode, &ptr, playback + z, z, gpr + z); + snd_emu10k1_init_stereo_control(controls + i++, "Wave Playback Volume", gpr, 100); + gpr += 2; + + /* Wave Surround Playback Volume */ + for (z = 0; z < 2; z++) + VOLUME(icode, &ptr, playback + 2 + z, z, gpr + z); + snd_emu10k1_init_stereo_control(controls + i++, "Wave Surround Playback Volume", gpr, 0); + gpr += 2; + + /* Wave Center/LFE Playback Volume */ + OP(icode, &ptr, iACC3, GPR(tmp + 0), FXBUS(FXBUS_PCM_LEFT), FXBUS(FXBUS_PCM_RIGHT), C_00000000); + OP(icode, &ptr, iMACINT0, GPR(tmp + 0), C_00000000, GPR(tmp + 0), C_00000004); + VOLUME(icode, &ptr, playback + 4, tmp + 0, gpr); + snd_emu10k1_init_mono_control(controls + i++, "Wave Center Playback Volume", gpr++, 0); + VOLUME(icode, &ptr, playback + 5, tmp + 0, gpr); + snd_emu10k1_init_mono_control(controls + i++, "Wave LFE Playback Volume", gpr++, 0); + + /* Wave Capture Volume + Switch */ + for (z = 0; z < 2; z++) { + SWITCH(icode, &ptr, tmp + 0, z, gpr + 2 + z); + VOLUME(icode, &ptr, capture + z, tmp + 0, gpr + z); + } + snd_emu10k1_init_stereo_control(controls + i++, "Wave Capture Volume", gpr, 0); + snd_emu10k1_init_stereo_onoff_control(controls + i++, "Wave Capture Switch", gpr + 2, 0); + gpr += 4; + + /* Synth Playback Volume */ + for (z = 0; z < 2; z++) + VOLUME_ADD(icode, &ptr, playback + z, 2 + z, gpr + z); + snd_emu10k1_init_stereo_control(controls + i++, "Synth Playback Volume", gpr, 100); + gpr += 2; + + /* Synth Capture Volume + Switch */ + for (z = 0; z < 2; z++) { + SWITCH(icode, &ptr, tmp + 0, 2 + z, gpr + 2 + z); + VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z); + } + snd_emu10k1_init_stereo_control(controls + i++, "Synth Capture Volume", gpr, 0); + snd_emu10k1_init_stereo_onoff_control(controls + i++, "Synth Capture Switch", gpr + 2, 0); + gpr += 4; + + /* Surround Digital Playback Volume (renamed later without Digital) */ + for (z = 0; z < 2; z++) + VOLUME_ADD(icode, &ptr, playback + 2 + z, 4 + z, gpr + z); + snd_emu10k1_init_stereo_control(controls + i++, "Surround Digital Playback Volume", gpr, 100); + gpr += 2; + + /* Surround Capture Volume + Switch */ + for (z = 0; z < 2; z++) { + SWITCH(icode, &ptr, tmp + 0, 4 + z, gpr + 2 + z); + VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z); + } + snd_emu10k1_init_stereo_control(controls + i++, "Surround Capture Volume", gpr, 0); + snd_emu10k1_init_stereo_onoff_control(controls + i++, "Surround Capture Switch", gpr + 2, 0); + gpr += 4; + + /* Center Playback Volume (renamed later without Digital) */ + VOLUME_ADD(icode, &ptr, playback + 4, 6, gpr); + snd_emu10k1_init_mono_control(controls + i++, "Center Digital Playback Volume", gpr++, 100); + + /* LFE Playback Volume + Switch (renamed later without Digital) */ + VOLUME_ADD(icode, &ptr, playback + 5, 7, gpr); + snd_emu10k1_init_mono_control(controls + i++, "LFE Digital Playback Volume", gpr++, 100); + + /* Front Playback Volume */ + for (z = 0; z < 2; z++) + VOLUME_ADD(icode, &ptr, playback + z, 10 + z, gpr + z); + snd_emu10k1_init_stereo_control(controls + i++, "Front Playback Volume", gpr, 100); + gpr += 2; + + /* Front Capture Volume + Switch */ + for (z = 0; z < 2; z++) { + SWITCH(icode, &ptr, tmp + 0, 10 + z, gpr + 2); + VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z); + } + snd_emu10k1_init_stereo_control(controls + i++, "Front Capture Volume", gpr, 0); + snd_emu10k1_init_mono_onoff_control(controls + i++, "Front Capture Switch", gpr + 2, 0); + gpr += 3; + + /* + * Process inputs + */ + + if (emu->fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; + ctl->min = 0; + ctl->max = 40; + ctl->value[0] = ctl->value[1] = 20; + ctl->tlv = snd_emu10k1_bass_treble_db_scale; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[i + 1]; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; + ctl->min = 0; + ctl->max = 40; + ctl->value[0] = ctl->value[1] = 20; + ctl->tlv = snd_emu10k1_bass_treble_db_scale; + ctl->translation = EMU10K1_GPR_TRANSLATION_TREBLE; + +#define BASS_GPR 0x8c +#define TREBLE_GPR 0x96 + + for (z = 0; z < 5; z++) { + int j; + for (j = 0; j < 2; j++) { + controls[i + 0].gpr[z * 2 + j] = BASS_GPR + z * 2 + j; + controls[i + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j; + } + } + i += 2; + + OP(icode, &ptr, iACC3, C_00000000, GPR(gpr), C_00000000, C_00000000); + snd_emu10k1_init_mono_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0); + gpr++; + OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_ZERO, GPR(gpr)); + ptr_skip = ptr; + for (z = 0; z < 3; z++) { /* front/rear/center-lfe */ + int j, k, l, d; + for (j = 0; j < 2; j++) { /* left/right */ + k = 0xa0 + (z * 8) + (j * 4); + l = 0xd0 + (z * 8) + (j * 4); + d = playback + z * 2 + j; + + OP(icode, &ptr, iMAC0, C_00000000, C_00000000, GPR(d), GPR(BASS_GPR + 0 + j)); + OP(icode, &ptr, iMACMV, GPR(k+1), GPR(k), GPR(k+1), GPR(BASS_GPR + 4 + j)); + OP(icode, &ptr, iMACMV, GPR(k), GPR(d), GPR(k), GPR(BASS_GPR + 2 + j)); + OP(icode, &ptr, iMACMV, GPR(k+3), GPR(k+2), GPR(k+3), GPR(BASS_GPR + 8 + j)); + OP(icode, &ptr, iMAC0, GPR(k+2), GPR_ACCU, GPR(k+2), GPR(BASS_GPR + 6 + j)); + OP(icode, &ptr, iACC3, GPR(k+2), GPR(k+2), GPR(k+2), C_00000000); + + OP(icode, &ptr, iMAC0, C_00000000, C_00000000, GPR(k+2), GPR(TREBLE_GPR + 0 + j)); + OP(icode, &ptr, iMACMV, GPR(l+1), GPR(l), GPR(l+1), GPR(TREBLE_GPR + 4 + j)); + OP(icode, &ptr, iMACMV, GPR(l), GPR(k+2), GPR(l), GPR(TREBLE_GPR + 2 + j)); + OP(icode, &ptr, iMACMV, GPR(l+3), GPR(l+2), GPR(l+3), GPR(TREBLE_GPR + 8 + j)); + OP(icode, &ptr, iMAC0, GPR(l+2), GPR_ACCU, GPR(l+2), GPR(TREBLE_GPR + 6 + j)); + OP(icode, &ptr, iMACINT0, GPR(l+2), C_00000000, GPR(l+2), C_00000010); + + OP(icode, &ptr, iACC3, GPR(d), GPR(l+2), C_00000000, C_00000000); + + if (z == 2) /* center */ + break; + } + } + gpr_map[gpr++] = ptr - ptr_skip; + +#undef BASS_GPR +#undef TREBLE_GPR + + /* + * Process outputs + */ + if (emu->fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & (1<fx8010.extout_mask & (1<fx8010.extout_mask & (1<card_capabilities->sblive51) { + for (z = 0; z < 16; z++) { + s8 c = snd_emu10k1_sblive51_fxbus2_map[z]; + if (c != -1) + OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(c)); + } + } else { + for (z = 0; z < 16; z++) + OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(z)); + } + + + if (gpr > tmp) { + snd_BUG(); + err = -EIO; + goto __err; + } + if (i > SND_EMU10K1_GPR_CONTROLS) { + snd_BUG(); + err = -EIO; + goto __err; + } + + /* clear remaining instruction memory */ + while (ptr < 0x200) + OP(icode, &ptr, iACC3, C_00000000, C_00000000, C_00000000, C_00000000); + + err = snd_emu10k1_fx8010_tram_setup(emu, ipcm->buffer_size); + if (err < 0) + goto __err; + icode->gpr_add_control_count = i; + icode->gpr_add_controls = controls; + emu->support_tlv = 1; /* support TLV */ + err = snd_emu10k1_icode_poke(emu, icode, true); + emu->support_tlv = 0; /* clear again */ + if (err >= 0) + err = snd_emu10k1_ipcm_poke(emu, ipcm); +__err: + kfree(ipcm); +__err_ipcm: + kfree(controls); +__err_ctrls: + kfree(icode->gpr_map); +__err_gpr: + kfree(icode); + return err; +} + +int snd_emu10k1_init_efx(struct snd_emu10k1 *emu) +{ + spin_lock_init(&emu->fx8010.irq_lock); + INIT_LIST_HEAD(&emu->fx8010.gpr_ctl); + if (emu->audigy) + return _snd_emu10k1_audigy_init_efx(emu); + else + return _snd_emu10k1_init_efx(emu); +} + +void snd_emu10k1_free_efx(struct snd_emu10k1 *emu) +{ + /* stop processor */ + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg = A_DBG_SINGLE_STEP); + else + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg = EMU10K1_DBG_SINGLE_STEP); +} + +#if 0 /* FIXME: who use them? */ +int snd_emu10k1_fx8010_tone_control_activate(struct snd_emu10k1 *emu, int output) +{ + if (output < 0 || output >= 6) + return -EINVAL; + snd_emu10k1_ptr_write(emu, emu->gpr_base + 0x94 + output, 0, 1); + return 0; +} + +int snd_emu10k1_fx8010_tone_control_deactivate(struct snd_emu10k1 *emu, int output) +{ + if (output < 0 || output >= 6) + return -EINVAL; + snd_emu10k1_ptr_write(emu, emu->gpr_base + 0x94 + output, 0, 0); + return 0; +} +#endif + +int snd_emu10k1_fx8010_tram_setup(struct snd_emu10k1 *emu, u32 size) +{ + u8 size_reg = 0; + + /* size is in samples */ + if (size != 0) { + size = (size - 1) >> 13; + + while (size) { + size >>= 1; + size_reg++; + } + size = 0x2000 << size_reg; + } + if ((emu->fx8010.etram_pages.bytes / 2) == size) + return 0; + scoped_guard(spinlock_irq, &emu->emu_lock) { + outl(HCFG_LOCKTANKCACHE_MASK | inl(emu->port + HCFG), emu->port + HCFG); + } + snd_emu10k1_ptr_write(emu, TCB, 0, 0); + snd_emu10k1_ptr_write(emu, TCBS, 0, TCBS_BUFFSIZE_16K); + if (emu->fx8010.etram_pages.area != NULL) { + snd_dma_free_pages(&emu->fx8010.etram_pages); + emu->fx8010.etram_pages.area = NULL; + emu->fx8010.etram_pages.bytes = 0; + } + + if (size > 0) { + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &emu->pci->dev, + size * 2, &emu->fx8010.etram_pages) < 0) + return -ENOMEM; + memset(emu->fx8010.etram_pages.area, 0, size * 2); + snd_emu10k1_ptr_write(emu, TCB, 0, emu->fx8010.etram_pages.addr); + snd_emu10k1_ptr_write(emu, TCBS, 0, size_reg); + scoped_guard(spinlock_irq, &emu->emu_lock) { + outl(inl(emu->port + HCFG) & ~HCFG_LOCKTANKCACHE_MASK, emu->port + HCFG); + } + } + + return 0; +} + +static int snd_emu10k1_fx8010_open(struct snd_hwdep * hw, struct file *file) +{ + return 0; +} + +static void copy_string(char *dst, const char *src, const char *null, int idx) +{ + if (src == NULL) + sprintf(dst, "%s %02X", null, idx); + else + strcpy(dst, src); +} + +static void snd_emu10k1_fx8010_info(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_info *info) +{ + const char * const *fxbus, * const *extin, * const *extout; + unsigned short extin_mask, extout_mask; + int res; + + info->internal_tram_size = emu->fx8010.itram_size; + info->external_tram_size = emu->fx8010.etram_pages.bytes / 2; + fxbus = snd_emu10k1_fxbus; + extin = emu->audigy ? snd_emu10k1_audigy_ins : snd_emu10k1_sblive_ins; + extout = emu->audigy ? snd_emu10k1_audigy_outs : snd_emu10k1_sblive_outs; + extin_mask = emu->audigy ? ~0 : emu->fx8010.extin_mask; + extout_mask = emu->audigy ? ~0 : emu->fx8010.extout_mask; + for (res = 0; res < 16; res++, fxbus++, extin++, extout++) { + copy_string(info->fxbus_names[res], *fxbus, "FXBUS", res); + copy_string(info->extin_names[res], extin_mask & (1 << res) ? *extin : NULL, "Unused", res); + copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res); + } + for (res = 16; res < 32; res++, extout++) + copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res); + info->gpr_controls = emu->fx8010.gpr_count; +} + +static int snd_emu10k1_fx8010_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_emu10k1 *emu = hw->private_data; + struct snd_emu10k1_fx8010_info *info; + struct snd_emu10k1_fx8010_code *icode; + struct snd_emu10k1_fx8010_pcm_rec *ipcm; + unsigned int addr; + void __user *argp = (void __user *)arg; + int res; + + switch (cmd) { + case SNDRV_EMU10K1_IOCTL_PVERSION: + emu->support_tlv = 1; + return put_user(SNDRV_EMU10K1_VERSION, (int __user *)argp); + case SNDRV_EMU10K1_IOCTL_INFO: + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + snd_emu10k1_fx8010_info(emu, info); + if (copy_to_user(argp, info, sizeof(*info))) { + kfree(info); + return -EFAULT; + } + kfree(info); + return 0; + case SNDRV_EMU10K1_IOCTL_CODE_POKE: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + icode = memdup_user(argp, sizeof(*icode)); + if (IS_ERR(icode)) + return PTR_ERR(icode); + res = snd_emu10k1_icode_poke(emu, icode, false); + kfree(icode); + return res; + case SNDRV_EMU10K1_IOCTL_CODE_PEEK: + icode = memdup_user(argp, sizeof(*icode)); + if (IS_ERR(icode)) + return PTR_ERR(icode); + res = snd_emu10k1_icode_peek(emu, icode); + if (res == 0 && copy_to_user(argp, icode, sizeof(*icode))) { + kfree(icode); + return -EFAULT; + } + kfree(icode); + return res; + case SNDRV_EMU10K1_IOCTL_PCM_POKE: + ipcm = memdup_user(argp, sizeof(*ipcm)); + if (IS_ERR(ipcm)) + return PTR_ERR(ipcm); + res = snd_emu10k1_ipcm_poke(emu, ipcm); + kfree(ipcm); + return res; + case SNDRV_EMU10K1_IOCTL_PCM_PEEK: + ipcm = memdup_user(argp, sizeof(*ipcm)); + if (IS_ERR(ipcm)) + return PTR_ERR(ipcm); + res = snd_emu10k1_ipcm_peek(emu, ipcm); + if (res == 0 && copy_to_user(argp, ipcm, sizeof(*ipcm))) { + kfree(ipcm); + return -EFAULT; + } + kfree(ipcm); + return res; + case SNDRV_EMU10K1_IOCTL_TRAM_SETUP: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (get_user(addr, (unsigned int __user *)argp)) + return -EFAULT; + scoped_guard(mutex, &emu->fx8010.lock) { + res = snd_emu10k1_fx8010_tram_setup(emu, addr); + } + return res; + case SNDRV_EMU10K1_IOCTL_STOP: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP); + else + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP); + return 0; + case SNDRV_EMU10K1_IOCTL_CONTINUE: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg = 0); + else + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg = 0); + return 0; + case SNDRV_EMU10K1_IOCTL_ZERO_TRAM_COUNTER: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_ZC); + else + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_ZC); + udelay(10); + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg); + else + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg); + return 0; + case SNDRV_EMU10K1_IOCTL_SINGLE_STEP: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (get_user(addr, (unsigned int __user *)argp)) + return -EFAULT; + if (emu->audigy) { + if (addr > A_DBG_STEP_ADDR) + return -EINVAL; + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP); + udelay(10); + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_STEP | addr); + } else { + if (addr > EMU10K1_DBG_SINGLE_STEP_ADDR) + return -EINVAL; + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP); + udelay(10); + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_STEP | addr); + } + return 0; + case SNDRV_EMU10K1_IOCTL_DBG_READ: + if (emu->audigy) + addr = snd_emu10k1_ptr_read(emu, A_DBG, 0); + else + addr = snd_emu10k1_ptr_read(emu, DBG, 0); + if (put_user(addr, (unsigned int __user *)argp)) + return -EFAULT; + return 0; + } + return -ENOTTY; +} + +static int snd_emu10k1_fx8010_release(struct snd_hwdep * hw, struct file *file) +{ + return 0; +} + +int snd_emu10k1_fx8010_new(struct snd_emu10k1 *emu, int device) +{ + struct snd_hwdep *hw; + int err; + + err = snd_hwdep_new(emu->card, "FX8010", device, &hw); + if (err < 0) + return err; + strscpy(hw->name, "EMU10K1 (FX8010)"); + hw->iface = SNDRV_HWDEP_IFACE_EMU10K1; + hw->ops.open = snd_emu10k1_fx8010_open; + hw->ops.ioctl = snd_emu10k1_fx8010_ioctl; + hw->ops.release = snd_emu10k1_fx8010_release; + hw->private_data = emu; + return 0; +} + +#ifdef CONFIG_PM_SLEEP +int snd_emu10k1_efx_alloc_pm_buffer(struct snd_emu10k1 *emu) +{ + int len; + + len = emu->audigy ? 0x200 : 0x100; + emu->saved_gpr = kmalloc_array(len, 4, GFP_KERNEL); + if (! emu->saved_gpr) + return -ENOMEM; + len = emu->audigy ? 0x100 : 0xa0; + emu->tram_val_saved = kmalloc_array(len, 4, GFP_KERNEL); + emu->tram_addr_saved = kmalloc_array(len, 4, GFP_KERNEL); + if (! emu->tram_val_saved || ! emu->tram_addr_saved) + return -ENOMEM; + len = emu->audigy ? 2 * 1024 : 2 * 512; + emu->saved_icode = vmalloc(array_size(len, 4)); + if (! emu->saved_icode) + return -ENOMEM; + return 0; +} + +void snd_emu10k1_efx_free_pm_buffer(struct snd_emu10k1 *emu) +{ + kfree(emu->saved_gpr); + kfree(emu->tram_val_saved); + kfree(emu->tram_addr_saved); + vfree(emu->saved_icode); +} + +/* + * save/restore GPR, TRAM and codes + */ +void snd_emu10k1_efx_suspend(struct snd_emu10k1 *emu) +{ + int i, len; + + len = emu->audigy ? 0x200 : 0x100; + for (i = 0; i < len; i++) + emu->saved_gpr[i] = snd_emu10k1_ptr_read(emu, emu->gpr_base + i, 0); + + len = emu->audigy ? 0x100 : 0xa0; + for (i = 0; i < len; i++) { + emu->tram_val_saved[i] = snd_emu10k1_ptr_read(emu, TANKMEMDATAREGBASE + i, 0); + emu->tram_addr_saved[i] = snd_emu10k1_ptr_read(emu, TANKMEMADDRREGBASE + i, 0); + if (emu->audigy) { + emu->tram_addr_saved[i] >>= 12; + emu->tram_addr_saved[i] |= + snd_emu10k1_ptr_read(emu, A_TANKMEMCTLREGBASE + i, 0) << 20; + } + } + + len = emu->audigy ? 2 * 1024 : 2 * 512; + for (i = 0; i < len; i++) + emu->saved_icode[i] = snd_emu10k1_efx_read(emu, i); +} + +void snd_emu10k1_efx_resume(struct snd_emu10k1 *emu) +{ + int i, len; + + /* set up TRAM */ + if (emu->fx8010.etram_pages.bytes > 0) { + unsigned size, size_reg = 0; + size = emu->fx8010.etram_pages.bytes / 2; + size = (size - 1) >> 13; + while (size) { + size >>= 1; + size_reg++; + } + outl(HCFG_LOCKTANKCACHE_MASK | inl(emu->port + HCFG), emu->port + HCFG); + snd_emu10k1_ptr_write(emu, TCB, 0, emu->fx8010.etram_pages.addr); + snd_emu10k1_ptr_write(emu, TCBS, 0, size_reg); + outl(inl(emu->port + HCFG) & ~HCFG_LOCKTANKCACHE_MASK, emu->port + HCFG); + } + + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_SINGLE_STEP); + else + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_SINGLE_STEP); + + len = emu->audigy ? 0x200 : 0x100; + for (i = 0; i < len; i++) + snd_emu10k1_ptr_write(emu, emu->gpr_base + i, 0, emu->saved_gpr[i]); + + len = emu->audigy ? 0x100 : 0xa0; + for (i = 0; i < len; i++) { + snd_emu10k1_ptr_write(emu, TANKMEMDATAREGBASE + i, 0, + emu->tram_val_saved[i]); + if (! emu->audigy) + snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + i, 0, + emu->tram_addr_saved[i]); + else { + snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + i, 0, + emu->tram_addr_saved[i] << 12); + snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + i, 0, + emu->tram_addr_saved[i] >> 20); + } + } + + len = emu->audigy ? 2 * 1024 : 2 * 512; + for (i = 0; i < len; i++) + snd_emu10k1_efx_write(emu, i, emu->saved_icode[i]); + + /* start FX processor when the DSP code is updated */ + if (emu->audigy) + snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg); + else + snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg); +} +#endif diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c new file mode 100644 index 000000000..f4906ab30 --- /dev/null +++ b/sound/pci/emu10k1/emumixer.c @@ -0,0 +1,2376 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela , + * Takashi Iwai + * Lee Revell + * James Courtier-Dutton + * Oswald Buddenhagen + * Creative Labs, Inc. + * + * Routines for control of EMU10K1 chips / mixer routines + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "p17v.h" + +#define AC97_ID_STAC9758 0x83847658 + +static const DECLARE_TLV_DB_SCALE(snd_audigy_db_scale2, -10350, 50, 1); /* WM8775 gain scale */ + + +static int add_ctls(struct snd_emu10k1 *emu, const struct snd_kcontrol_new *tpl, + const char * const *ctls, unsigned nctls) +{ + struct snd_kcontrol_new kctl = *tpl; + int err; + + for (unsigned i = 0; i < nctls; i++) { + kctl.name = ctls[i]; + kctl.private_value = i; + err = snd_ctl_add(emu->card, snd_ctl_new1(&kctl, emu)); + if (err < 0) + return err; + } + return 0; +} + + +static int snd_emu10k1_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_emu10k1_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + /* Limit: emu->spdif_bits */ + if (idx >= 3) + return -EINVAL; + ucontrol->value.iec958.status[0] = (emu->spdif_bits[idx] >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (emu->spdif_bits[idx] >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (emu->spdif_bits[idx] >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (emu->spdif_bits[idx] >> 24) & 0xff; + return 0; +} + +static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +#define PAIR_PS(base, one, two, sfx) base " " one sfx, base " " two sfx +#define LR_PS(base, sfx) PAIR_PS(base, "Left", "Right", sfx) + +#define ADAT_PS(pfx, sfx) \ + pfx "ADAT 0" sfx, pfx "ADAT 1" sfx, pfx "ADAT 2" sfx, pfx "ADAT 3" sfx, \ + pfx "ADAT 4" sfx, pfx "ADAT 5" sfx, pfx "ADAT 6" sfx, pfx "ADAT 7" sfx + +#define PAIR_REGS(base, one, two) \ + base ## one ## 1, \ + base ## two ## 1 + +#define LR_REGS(base) PAIR_REGS(base, _LEFT, _RIGHT) + +#define ADAT_REGS(base) \ + base+0, base+1, base+2, base+3, base+4, base+5, base+6, base+7 + +/* + * List of data sources available for each destination + */ + +#define DSP_TEXTS \ + "DSP 0", "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", \ + "DSP 8", "DSP 9", "DSP 10", "DSP 11", "DSP 12", "DSP 13", "DSP 14", "DSP 15", \ + "DSP 16", "DSP 17", "DSP 18", "DSP 19", "DSP 20", "DSP 21", "DSP 22", "DSP 23", \ + "DSP 24", "DSP 25", "DSP 26", "DSP 27", "DSP 28", "DSP 29", "DSP 30", "DSP 31" + +#define PAIR_TEXTS(base, one, two) PAIR_PS(base, one, two, "") +#define LR_TEXTS(base) LR_PS(base, "") +#define ADAT_TEXTS(pfx) ADAT_PS(pfx, "") + +#define EMU32_SRC_REGS \ + EMU_SRC_ALICE_EMU32A, \ + EMU_SRC_ALICE_EMU32A+1, \ + EMU_SRC_ALICE_EMU32A+2, \ + EMU_SRC_ALICE_EMU32A+3, \ + EMU_SRC_ALICE_EMU32A+4, \ + EMU_SRC_ALICE_EMU32A+5, \ + EMU_SRC_ALICE_EMU32A+6, \ + EMU_SRC_ALICE_EMU32A+7, \ + EMU_SRC_ALICE_EMU32A+8, \ + EMU_SRC_ALICE_EMU32A+9, \ + EMU_SRC_ALICE_EMU32A+0xa, \ + EMU_SRC_ALICE_EMU32A+0xb, \ + EMU_SRC_ALICE_EMU32A+0xc, \ + EMU_SRC_ALICE_EMU32A+0xd, \ + EMU_SRC_ALICE_EMU32A+0xe, \ + EMU_SRC_ALICE_EMU32A+0xf, \ + EMU_SRC_ALICE_EMU32B, \ + EMU_SRC_ALICE_EMU32B+1, \ + EMU_SRC_ALICE_EMU32B+2, \ + EMU_SRC_ALICE_EMU32B+3, \ + EMU_SRC_ALICE_EMU32B+4, \ + EMU_SRC_ALICE_EMU32B+5, \ + EMU_SRC_ALICE_EMU32B+6, \ + EMU_SRC_ALICE_EMU32B+7, \ + EMU_SRC_ALICE_EMU32B+8, \ + EMU_SRC_ALICE_EMU32B+9, \ + EMU_SRC_ALICE_EMU32B+0xa, \ + EMU_SRC_ALICE_EMU32B+0xb, \ + EMU_SRC_ALICE_EMU32B+0xc, \ + EMU_SRC_ALICE_EMU32B+0xd, \ + EMU_SRC_ALICE_EMU32B+0xe, \ + EMU_SRC_ALICE_EMU32B+0xf + +/* 1010 rev1 */ + +#define EMU1010_COMMON_TEXTS \ + "Silence", \ + PAIR_TEXTS("Dock Mic", "A", "B"), \ + LR_TEXTS("Dock ADC1"), \ + LR_TEXTS("Dock ADC2"), \ + LR_TEXTS("Dock ADC3"), \ + LR_TEXTS("0202 ADC"), \ + LR_TEXTS("1010 SPDIF"), \ + ADAT_TEXTS("1010 ") + +static const char * const emu1010_src_texts[] = { + EMU1010_COMMON_TEXTS, + DSP_TEXTS, +}; + +static const unsigned short emu1010_src_regs[] = { + EMU_SRC_SILENCE, + PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B), + LR_REGS(EMU_SRC_DOCK_ADC1), + LR_REGS(EMU_SRC_DOCK_ADC2), + LR_REGS(EMU_SRC_DOCK_ADC3), + LR_REGS(EMU_SRC_HAMOA_ADC), + LR_REGS(EMU_SRC_HANA_SPDIF), + ADAT_REGS(EMU_SRC_HANA_ADAT), + EMU32_SRC_REGS, +}; +static_assert(ARRAY_SIZE(emu1010_src_regs) == ARRAY_SIZE(emu1010_src_texts)); + +/* 1010 rev2 */ + +#define EMU1010b_COMMON_TEXTS \ + "Silence", \ + PAIR_TEXTS("Dock Mic", "A", "B"), \ + LR_TEXTS("Dock ADC1"), \ + LR_TEXTS("Dock ADC2"), \ + LR_TEXTS("0202 ADC"), \ + LR_TEXTS("Dock SPDIF"), \ + LR_TEXTS("1010 SPDIF"), \ + ADAT_TEXTS("Dock "), \ + ADAT_TEXTS("1010 ") + +static const char * const emu1010b_src_texts[] = { + EMU1010b_COMMON_TEXTS, + DSP_TEXTS, +}; + +static const unsigned short emu1010b_src_regs[] = { + EMU_SRC_SILENCE, + PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B), + LR_REGS(EMU_SRC_DOCK_ADC1), + LR_REGS(EMU_SRC_DOCK_ADC2), + LR_REGS(EMU_SRC_HAMOA_ADC), + LR_REGS(EMU_SRC_MDOCK_SPDIF), + LR_REGS(EMU_SRC_HANA_SPDIF), + ADAT_REGS(EMU_SRC_MDOCK_ADAT), + ADAT_REGS(EMU_SRC_HANA_ADAT), + EMU32_SRC_REGS, +}; +static_assert(ARRAY_SIZE(emu1010b_src_regs) == ARRAY_SIZE(emu1010b_src_texts)); + +/* 1616(m) cardbus */ + +#define EMU1616_COMMON_TEXTS \ + "Silence", \ + PAIR_TEXTS("Mic", "A", "B"), \ + LR_TEXTS("ADC1"), \ + LR_TEXTS("ADC2"), \ + LR_TEXTS("SPDIF"), \ + ADAT_TEXTS("") + +static const char * const emu1616_src_texts[] = { + EMU1616_COMMON_TEXTS, + DSP_TEXTS, +}; + +static const unsigned short emu1616_src_regs[] = { + EMU_SRC_SILENCE, + PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B), + LR_REGS(EMU_SRC_DOCK_ADC1), + LR_REGS(EMU_SRC_DOCK_ADC2), + LR_REGS(EMU_SRC_MDOCK_SPDIF), + ADAT_REGS(EMU_SRC_MDOCK_ADAT), + EMU32_SRC_REGS, +}; +static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts)); + +/* 0404 rev1 & rev2 */ + +#define EMU0404_COMMON_TEXTS \ + "Silence", \ + LR_TEXTS("ADC"), \ + LR_TEXTS("SPDIF") + +static const char * const emu0404_src_texts[] = { + EMU0404_COMMON_TEXTS, + DSP_TEXTS, +}; + +static const unsigned short emu0404_src_regs[] = { + EMU_SRC_SILENCE, + LR_REGS(EMU_SRC_HAMOA_ADC), + LR_REGS(EMU_SRC_HANA_SPDIF), + EMU32_SRC_REGS, +}; +static_assert(ARRAY_SIZE(emu0404_src_regs) == ARRAY_SIZE(emu0404_src_texts)); + +/* + * Data destinations - physical EMU outputs. + * Each destination has an enum mixer control to choose a data source + */ + +#define LR_CTLS(base) LR_PS(base, " Playback Enum") +#define ADAT_CTLS(pfx) ADAT_PS(pfx, " Playback Enum") + +/* 1010 rev1 */ + +static const char * const emu1010_output_texts[] = { + LR_CTLS("Dock DAC1"), + LR_CTLS("Dock DAC2"), + LR_CTLS("Dock DAC3"), + LR_CTLS("Dock DAC4"), + LR_CTLS("Dock Phones"), + LR_CTLS("Dock SPDIF"), + LR_CTLS("0202 DAC"), + LR_CTLS("1010 SPDIF"), + ADAT_CTLS("1010 "), +}; +static_assert(ARRAY_SIZE(emu1010_output_texts) <= NUM_OUTPUT_DESTS); + +static const unsigned short emu1010_output_dst[] = { + LR_REGS(EMU_DST_DOCK_DAC1), + LR_REGS(EMU_DST_DOCK_DAC2), + LR_REGS(EMU_DST_DOCK_DAC3), + LR_REGS(EMU_DST_DOCK_DAC4), + LR_REGS(EMU_DST_DOCK_PHONES), + LR_REGS(EMU_DST_DOCK_SPDIF), + LR_REGS(EMU_DST_HAMOA_DAC), + LR_REGS(EMU_DST_HANA_SPDIF), + ADAT_REGS(EMU_DST_HANA_ADAT), +}; +static_assert(ARRAY_SIZE(emu1010_output_dst) == ARRAY_SIZE(emu1010_output_texts)); + +static const unsigned short emu1010_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, + EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, +}; +static_assert(ARRAY_SIZE(emu1010_output_dflt) == ARRAY_SIZE(emu1010_output_dst)); + +/* 1010 rev2 */ + +static const char * const snd_emu1010b_output_texts[] = { + LR_CTLS("Dock DAC1"), + LR_CTLS("Dock DAC2"), + LR_CTLS("Dock DAC3"), + LR_CTLS("Dock SPDIF"), + ADAT_CTLS("Dock "), + LR_CTLS("0202 DAC"), + LR_CTLS("1010 SPDIF"), + ADAT_CTLS("1010 "), +}; +static_assert(ARRAY_SIZE(snd_emu1010b_output_texts) <= NUM_OUTPUT_DESTS); + +static const unsigned short emu1010b_output_dst[] = { + LR_REGS(EMU_DST_DOCK_DAC1), + LR_REGS(EMU_DST_DOCK_DAC2), + LR_REGS(EMU_DST_DOCK_DAC3), + LR_REGS(EMU_DST_MDOCK_SPDIF), + ADAT_REGS(EMU_DST_MDOCK_ADAT), + LR_REGS(EMU_DST_HAMOA_DAC), + LR_REGS(EMU_DST_HANA_SPDIF), + ADAT_REGS(EMU_DST_HANA_ADAT), +}; +static_assert(ARRAY_SIZE(emu1010b_output_dst) == ARRAY_SIZE(snd_emu1010b_output_texts)); + +static const unsigned short emu1010b_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, +}; + +/* 1616(m) cardbus */ + +static const char * const snd_emu1616_output_texts[] = { + LR_CTLS("Dock DAC1"), + LR_CTLS("Dock DAC2"), + LR_CTLS("Dock DAC3"), + LR_CTLS("Dock SPDIF"), + ADAT_CTLS("Dock "), + LR_CTLS("Mana DAC"), +}; +static_assert(ARRAY_SIZE(snd_emu1616_output_texts) <= NUM_OUTPUT_DESTS); + +static const unsigned short emu1616_output_dst[] = { + LR_REGS(EMU_DST_DOCK_DAC1), + LR_REGS(EMU_DST_DOCK_DAC2), + LR_REGS(EMU_DST_DOCK_DAC3), + LR_REGS(EMU_DST_MDOCK_SPDIF), + ADAT_REGS(EMU_DST_MDOCK_ADAT), + EMU_DST_MANA_DAC_LEFT, EMU_DST_MANA_DAC_RIGHT, +}; +static_assert(ARRAY_SIZE(emu1616_output_dst) == ARRAY_SIZE(snd_emu1616_output_texts)); + +static const unsigned short emu1616_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, +}; +static_assert(ARRAY_SIZE(emu1616_output_dflt) == ARRAY_SIZE(emu1616_output_dst)); + +/* 0404 rev1 & rev2 */ + +static const char * const snd_emu0404_output_texts[] = { + LR_CTLS("DAC"), + LR_CTLS("SPDIF"), +}; +static_assert(ARRAY_SIZE(snd_emu0404_output_texts) <= NUM_OUTPUT_DESTS); + +static const unsigned short emu0404_output_dst[] = { + LR_REGS(EMU_DST_HAMOA_DAC), + LR_REGS(EMU_DST_HANA_SPDIF), +}; +static_assert(ARRAY_SIZE(emu0404_output_dst) == ARRAY_SIZE(snd_emu0404_output_texts)); + +static const unsigned short emu0404_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, +}; +static_assert(ARRAY_SIZE(emu0404_output_dflt) == ARRAY_SIZE(emu0404_output_dst)); + +/* + * Data destinations - FPGA outputs going to Alice2 (Audigy) for + * capture (EMU32 + I2S links) + * Each destination has an enum mixer control to choose a data source + */ + +static const char * const emu1010_input_texts[] = { + "DSP 0 Capture Enum", + "DSP 1 Capture Enum", + "DSP 2 Capture Enum", + "DSP 3 Capture Enum", + "DSP 4 Capture Enum", + "DSP 5 Capture Enum", + "DSP 6 Capture Enum", + "DSP 7 Capture Enum", + "DSP 8 Capture Enum", + "DSP 9 Capture Enum", + "DSP A Capture Enum", + "DSP B Capture Enum", + "DSP C Capture Enum", + "DSP D Capture Enum", + "DSP E Capture Enum", + "DSP F Capture Enum", + /* These exist only on rev1 EMU1010 cards. */ + "DSP 10 Capture Enum", + "DSP 11 Capture Enum", + "DSP 12 Capture Enum", + "DSP 13 Capture Enum", + "DSP 14 Capture Enum", + "DSP 15 Capture Enum", +}; +static_assert(ARRAY_SIZE(emu1010_input_texts) <= NUM_INPUT_DESTS); + +static const unsigned short emu1010_input_dst[] = { + EMU_DST_ALICE2_EMU32_0, + EMU_DST_ALICE2_EMU32_1, + EMU_DST_ALICE2_EMU32_2, + EMU_DST_ALICE2_EMU32_3, + EMU_DST_ALICE2_EMU32_4, + EMU_DST_ALICE2_EMU32_5, + EMU_DST_ALICE2_EMU32_6, + EMU_DST_ALICE2_EMU32_7, + EMU_DST_ALICE2_EMU32_8, + EMU_DST_ALICE2_EMU32_9, + EMU_DST_ALICE2_EMU32_A, + EMU_DST_ALICE2_EMU32_B, + EMU_DST_ALICE2_EMU32_C, + EMU_DST_ALICE2_EMU32_D, + EMU_DST_ALICE2_EMU32_E, + EMU_DST_ALICE2_EMU32_F, + /* These exist only on rev1 EMU1010 cards. */ + EMU_DST_ALICE_I2S0_LEFT, + EMU_DST_ALICE_I2S0_RIGHT, + EMU_DST_ALICE_I2S1_LEFT, + EMU_DST_ALICE_I2S1_RIGHT, + EMU_DST_ALICE_I2S2_LEFT, + EMU_DST_ALICE_I2S2_RIGHT, +}; +static_assert(ARRAY_SIZE(emu1010_input_dst) == ARRAY_SIZE(emu1010_input_texts)); + +static const unsigned short emu1010_input_dflt[] = { + EMU_SRC_DOCK_MIC_A1, + EMU_SRC_DOCK_MIC_B1, + EMU_SRC_HAMOA_ADC_LEFT1, + EMU_SRC_HAMOA_ADC_RIGHT1, + EMU_SRC_DOCK_ADC1_LEFT1, + EMU_SRC_DOCK_ADC1_RIGHT1, + EMU_SRC_DOCK_ADC2_LEFT1, + EMU_SRC_DOCK_ADC2_RIGHT1, + /* Pavel Hofman - setting defaults for all capture channels. + * Defaults only, users will set their own values anyways, let's + * just copy/paste. */ + EMU_SRC_DOCK_MIC_A1, + EMU_SRC_DOCK_MIC_B1, + EMU_SRC_HAMOA_ADC_LEFT1, + EMU_SRC_HAMOA_ADC_RIGHT1, + EMU_SRC_DOCK_ADC1_LEFT1, + EMU_SRC_DOCK_ADC1_RIGHT1, + EMU_SRC_DOCK_ADC2_LEFT1, + EMU_SRC_DOCK_ADC2_RIGHT1, + + EMU_SRC_DOCK_ADC1_LEFT1, + EMU_SRC_DOCK_ADC1_RIGHT1, + EMU_SRC_DOCK_ADC2_LEFT1, + EMU_SRC_DOCK_ADC2_RIGHT1, + EMU_SRC_DOCK_ADC3_LEFT1, + EMU_SRC_DOCK_ADC3_RIGHT1, +}; +static_assert(ARRAY_SIZE(emu1010_input_dflt) == ARRAY_SIZE(emu1010_input_dst)); + +static const unsigned short emu0404_input_dflt[] = { + EMU_SRC_HAMOA_ADC_LEFT1, + EMU_SRC_HAMOA_ADC_RIGHT1, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_HANA_SPDIF_LEFT1, + EMU_SRC_HANA_SPDIF_RIGHT1, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, +}; + +struct snd_emu1010_routing_info { + const char * const *src_texts; + const char * const *out_texts; + const unsigned short *src_regs; + const unsigned short *out_regs; + const unsigned short *in_regs; + const unsigned short *out_dflts; + const unsigned short *in_dflts; + unsigned n_srcs; + unsigned n_outs; + unsigned n_ins; +}; + +static const struct snd_emu1010_routing_info emu1010_routing_info[] = { + { + /* rev1 1010 */ + .src_regs = emu1010_src_regs, + .src_texts = emu1010_src_texts, + .n_srcs = ARRAY_SIZE(emu1010_src_texts), + + .out_dflts = emu1010_output_dflt, + .out_regs = emu1010_output_dst, + .out_texts = emu1010_output_texts, + .n_outs = ARRAY_SIZE(emu1010_output_dst), + + .in_dflts = emu1010_input_dflt, + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst), + }, + { + /* rev2 1010 */ + .src_regs = emu1010b_src_regs, + .src_texts = emu1010b_src_texts, + .n_srcs = ARRAY_SIZE(emu1010b_src_texts), + + .out_dflts = emu1010b_output_dflt, + .out_regs = emu1010b_output_dst, + .out_texts = snd_emu1010b_output_texts, + .n_outs = ARRAY_SIZE(emu1010b_output_dst), + + .in_dflts = emu1010_input_dflt, + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, + }, + { + /* 1616(m) cardbus */ + .src_regs = emu1616_src_regs, + .src_texts = emu1616_src_texts, + .n_srcs = ARRAY_SIZE(emu1616_src_texts), + + .out_dflts = emu1616_output_dflt, + .out_regs = emu1616_output_dst, + .out_texts = snd_emu1616_output_texts, + .n_outs = ARRAY_SIZE(emu1616_output_dst), + + .in_dflts = emu1010_input_dflt, + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, + }, + { + /* 0404 */ + .src_regs = emu0404_src_regs, + .src_texts = emu0404_src_texts, + .n_srcs = ARRAY_SIZE(emu0404_src_texts), + + .out_dflts = emu0404_output_dflt, + .out_regs = emu0404_output_dst, + .out_texts = snd_emu0404_output_texts, + .n_outs = ARRAY_SIZE(emu0404_output_dflt), + + .in_dflts = emu0404_input_dflt, + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, + }, +}; + +static unsigned emu1010_idx(struct snd_emu10k1 *emu) +{ + return emu->card_capabilities->emu_model - 1; +} + +static void snd_emu1010_output_source_apply(struct snd_emu10k1 *emu, + int channel, int src) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + + snd_emu1010_fpga_link_dst_src_write(emu, + emu_ri->out_regs[channel], emu_ri->src_regs[src]); +} + +static void snd_emu1010_input_source_apply(struct snd_emu10k1 *emu, + int channel, int src) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + + snd_emu1010_fpga_link_dst_src_write(emu, + emu_ri->in_regs[channel], emu_ri->src_regs[src]); +} + +static void snd_emu1010_apply_sources(struct snd_emu10k1 *emu) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + + for (unsigned i = 0; i < emu_ri->n_outs; i++) + snd_emu1010_output_source_apply( + emu, i, emu->emu1010.output_source[i]); + for (unsigned i = 0; i < emu_ri->n_ins; i++) + snd_emu1010_input_source_apply( + emu, i, emu->emu1010.input_source[i]); +} + +static u8 emu1010_map_source(const struct snd_emu1010_routing_info *emu_ri, + unsigned val) +{ + for (unsigned i = 0; i < emu_ri->n_srcs; i++) + if (val == emu_ri->src_regs[i]) + return i; + return 0; +} + +static int snd_emu1010_input_output_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + + return snd_ctl_enum_info(uinfo, 1, emu_ri->n_srcs, emu_ri->src_texts); +} + +static int snd_emu1010_output_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned channel = kcontrol->private_value; + + if (channel >= emu_ri->n_outs) + return -EINVAL; + ucontrol->value.enumerated.item[0] = emu->emu1010.output_source[channel]; + return 0; +} + +static int snd_emu1010_output_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned val = ucontrol->value.enumerated.item[0]; + unsigned channel = kcontrol->private_value; + int change; + + if (val >= emu_ri->n_srcs) + return -EINVAL; + if (channel >= emu_ri->n_outs) + return -EINVAL; + change = (emu->emu1010.output_source[channel] != val); + if (change) { + emu->emu1010.output_source[channel] = val; + guard(snd_emu1010_fpga_lock)(emu); + snd_emu1010_output_source_apply(emu, channel, val); + } + return change; +} + +static const struct snd_kcontrol_new emu1010_output_source_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_input_output_source_info, + .get = snd_emu1010_output_source_get, + .put = snd_emu1010_output_source_put +}; + +static int snd_emu1010_input_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned channel = kcontrol->private_value; + + if (channel >= emu_ri->n_ins) + return -EINVAL; + ucontrol->value.enumerated.item[0] = emu->emu1010.input_source[channel]; + return 0; +} + +static int snd_emu1010_input_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned val = ucontrol->value.enumerated.item[0]; + unsigned channel = kcontrol->private_value; + int change; + + if (val >= emu_ri->n_srcs) + return -EINVAL; + if (channel >= emu_ri->n_ins) + return -EINVAL; + change = (emu->emu1010.input_source[channel] != val); + if (change) { + emu->emu1010.input_source[channel] = val; + guard(snd_emu1010_fpga_lock)(emu); + snd_emu1010_input_source_apply(emu, channel, val); + } + return change; +} + +static const struct snd_kcontrol_new emu1010_input_source_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_input_output_source_info, + .get = snd_emu1010_input_source_get, + .put = snd_emu1010_input_source_put +}; + +static int add_emu1010_source_mixers(struct snd_emu10k1 *emu) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + int err; + + err = add_ctls(emu, &emu1010_output_source_ctl, + emu_ri->out_texts, emu_ri->n_outs); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_input_source_ctl, + emu1010_input_texts, emu_ri->n_ins); + return err; +} + + +static const char * const snd_emu1010_adc_pads[] = { + "ADC1 14dB PAD 0202 Capture Switch", + "ADC1 14dB PAD Audio Dock Capture Switch", + "ADC2 14dB PAD Audio Dock Capture Switch", + "ADC3 14dB PAD Audio Dock Capture Switch", +}; + +static const unsigned short snd_emu1010_adc_pad_regs[] = { + EMU_HANA_0202_ADC_PAD1, + EMU_HANA_DOCK_ADC_PAD1, + EMU_HANA_DOCK_ADC_PAD2, + EMU_HANA_DOCK_ADC_PAD3, +}; + +#define snd_emu1010_adc_pads_info snd_ctl_boolean_mono_info + +static int snd_emu1010_adc_pads_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int mask = snd_emu1010_adc_pad_regs[kcontrol->private_value]; + + ucontrol->value.integer.value[0] = (emu->emu1010.adc_pads & mask) ? 1 : 0; + return 0; +} + +static int snd_emu1010_adc_pads_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int mask = snd_emu1010_adc_pad_regs[kcontrol->private_value]; + unsigned int val, cache; + int change; + + val = ucontrol->value.integer.value[0]; + cache = emu->emu1010.adc_pads; + if (val == 1) + cache = cache | mask; + else + cache = cache & ~mask; + change = (cache != emu->emu1010.adc_pads); + if (change) { + snd_emu1010_fpga_write_lock(emu, EMU_HANA_ADC_PADS, cache ); + emu->emu1010.adc_pads = cache; + } + + return change; +} + +static const struct snd_kcontrol_new emu1010_adc_pads_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_adc_pads_info, + .get = snd_emu1010_adc_pads_get, + .put = snd_emu1010_adc_pads_put +}; + + +static const char * const snd_emu1010_dac_pads[] = { + "DAC1 0202 14dB PAD Playback Switch", + "DAC1 Audio Dock 14dB PAD Playback Switch", + "DAC2 Audio Dock 14dB PAD Playback Switch", + "DAC3 Audio Dock 14dB PAD Playback Switch", + "DAC4 Audio Dock 14dB PAD Playback Switch", +}; + +static const unsigned short snd_emu1010_dac_regs[] = { + EMU_HANA_0202_DAC_PAD1, + EMU_HANA_DOCK_DAC_PAD1, + EMU_HANA_DOCK_DAC_PAD2, + EMU_HANA_DOCK_DAC_PAD3, + EMU_HANA_DOCK_DAC_PAD4, +}; + +#define snd_emu1010_dac_pads_info snd_ctl_boolean_mono_info + +static int snd_emu1010_dac_pads_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int mask = snd_emu1010_dac_regs[kcontrol->private_value]; + + ucontrol->value.integer.value[0] = (emu->emu1010.dac_pads & mask) ? 1 : 0; + return 0; +} + +static int snd_emu1010_dac_pads_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int mask = snd_emu1010_dac_regs[kcontrol->private_value]; + unsigned int val, cache; + int change; + + val = ucontrol->value.integer.value[0]; + cache = emu->emu1010.dac_pads; + if (val == 1) + cache = cache | mask; + else + cache = cache & ~mask; + change = (cache != emu->emu1010.dac_pads); + if (change) { + snd_emu1010_fpga_write_lock(emu, EMU_HANA_DAC_PADS, cache ); + emu->emu1010.dac_pads = cache; + } + + return change; +} + +static const struct snd_kcontrol_new emu1010_dac_pads_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_dac_pads_info, + .get = snd_emu1010_dac_pads_get, + .put = snd_emu1010_dac_pads_put +}; + + +struct snd_emu1010_pads_info { + const char * const *adc_ctls, * const *dac_ctls; + unsigned n_adc_ctls, n_dac_ctls; +}; + +static const struct snd_emu1010_pads_info emu1010_pads_info[] = { + { + /* rev1 1010 */ + .adc_ctls = snd_emu1010_adc_pads, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads), + .dac_ctls = snd_emu1010_dac_pads, + .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads), + }, + { + /* rev2 1010 */ + .adc_ctls = snd_emu1010_adc_pads, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 1, + .dac_ctls = snd_emu1010_dac_pads, + .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 1, + }, + { + /* 1616(m) cardbus */ + .adc_ctls = snd_emu1010_adc_pads + 1, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 2, + .dac_ctls = snd_emu1010_dac_pads + 1, + .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 2, + }, + { + /* 0404 */ + .adc_ctls = NULL, + .n_adc_ctls = 0, + .dac_ctls = NULL, + .n_dac_ctls = 0, + }, +}; + +static const char * const emu1010_clock_texts[] = { + "44100", "48000", "SPDIF", "ADAT", "Dock", "BNC" +}; + +static const u8 emu1010_clock_vals[] = { + EMU_HANA_WCLOCK_INT_44_1K, + EMU_HANA_WCLOCK_INT_48K, + EMU_HANA_WCLOCK_HANA_SPDIF_IN, + EMU_HANA_WCLOCK_HANA_ADAT_IN, + EMU_HANA_WCLOCK_2ND_HANA, + EMU_HANA_WCLOCK_SYNC_BNC, +}; + +static const char * const emu0404_clock_texts[] = { + "44100", "48000", "SPDIF", "BNC" +}; + +static const u8 emu0404_clock_vals[] = { + EMU_HANA_WCLOCK_INT_44_1K, + EMU_HANA_WCLOCK_INT_48K, + EMU_HANA_WCLOCK_HANA_SPDIF_IN, + EMU_HANA_WCLOCK_SYNC_BNC, +}; + +struct snd_emu1010_clock_info { + const char * const *texts; + const u8 *vals; + unsigned num; +}; + +static const struct snd_emu1010_clock_info emu1010_clock_info[] = { + { + // rev1 1010 + .texts = emu1010_clock_texts, + .vals = emu1010_clock_vals, + .num = ARRAY_SIZE(emu1010_clock_vals), + }, + { + // rev2 1010 + .texts = emu1010_clock_texts, + .vals = emu1010_clock_vals, + .num = ARRAY_SIZE(emu1010_clock_vals) - 1, + }, + { + // 1616(m) CardBus + .texts = emu1010_clock_texts, + // TODO: determine what is actually available. + // Pedantically, *every* source comes from the 2nd FPGA, as the + // card itself has no own (digital) audio ports. The user manual + // claims that ADAT and S/PDIF clock sources are separate, which + // can mean two things: either E-MU mapped the dock's sources to + // the primary ones, or they determine the meaning of the "Dock" + // source depending on how the ports are actually configured + // (which the 2nd FPGA must be doing anyway). + .vals = emu1010_clock_vals, + .num = ARRAY_SIZE(emu1010_clock_vals), + }, + { + // 0404 + .texts = emu0404_clock_texts, + .vals = emu0404_clock_vals, + .num = ARRAY_SIZE(emu0404_clock_vals), + }, +}; + +static int snd_emu1010_clock_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_clock_info *emu_ci = + &emu1010_clock_info[emu1010_idx(emu)]; + + return snd_ctl_enum_info(uinfo, 1, emu_ci->num, emu_ci->texts); +} + +static int snd_emu1010_clock_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->emu1010.clock_source; + return 0; +} + +static int snd_emu1010_clock_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_clock_info *emu_ci = + &emu1010_clock_info[emu1010_idx(emu)]; + unsigned int val; + + val = ucontrol->value.enumerated.item[0] ; + if (val >= emu_ci->num) + return -EINVAL; + guard(snd_emu1010_fpga_lock)(emu); + scoped_guard(spinlock_irq, &emu->reg_lock) { + if (emu->emu1010.clock_source == val) + return 0; + emu->emu1010.clock_source = val; + emu->emu1010.wclock = emu_ci->vals[val]; + snd_emu1010_update_clock(emu); + + snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE); + snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, emu->emu1010.wclock); + } + + msleep(10); // Allow DLL to settle + snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE); + return 1; +} + +static const struct snd_kcontrol_new snd_emu1010_clock_source = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Source", + .count = 1, + .info = snd_emu1010_clock_source_info, + .get = snd_emu1010_clock_source_get, + .put = snd_emu1010_clock_source_put +}; + +static int snd_emu1010_clock_fallback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { + "44100", "48000" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_emu1010_clock_fallback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->emu1010.clock_fallback; + return 0; +} + +static int snd_emu1010_clock_fallback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val = ucontrol->value.enumerated.item[0]; + int change; + + if (val >= 2) + return -EINVAL; + change = (emu->emu1010.clock_fallback != val); + if (change) { + emu->emu1010.clock_fallback = val; + snd_emu1010_fpga_write_lock(emu, EMU_HANA_DEFCLOCK, 1 - val); + } + return change; +} + +static const struct snd_kcontrol_new snd_emu1010_clock_fallback = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Fallback", + .count = 1, + .info = snd_emu1010_clock_fallback_info, + .get = snd_emu1010_clock_fallback_get, + .put = snd_emu1010_clock_fallback_put +}; + +static int snd_emu1010_optical_out_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { + "SPDIF", "ADAT" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_emu1010_optical_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->emu1010.optical_out; + return 0; +} + +static int snd_emu1010_optical_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + u32 tmp; + int change = 0; + + val = ucontrol->value.enumerated.item[0]; + /* Limit: uinfo->value.enumerated.items = 2; */ + if (val >= 2) + return -EINVAL; + change = (emu->emu1010.optical_out != val); + if (change) { + emu->emu1010.optical_out = val; + tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : EMU_HANA_OPTICAL_IN_SPDIF) | + (emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : EMU_HANA_OPTICAL_OUT_SPDIF); + snd_emu1010_fpga_write_lock(emu, EMU_HANA_OPTICAL_TYPE, tmp); + } + return change; +} + +static const struct snd_kcontrol_new snd_emu1010_optical_out = { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Optical Output Mode", + .count = 1, + .info = snd_emu1010_optical_out_info, + .get = snd_emu1010_optical_out_get, + .put = snd_emu1010_optical_out_put +}; + +static int snd_emu1010_optical_in_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { + "SPDIF", "ADAT" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_emu1010_optical_in_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->emu1010.optical_in; + return 0; +} + +static int snd_emu1010_optical_in_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + u32 tmp; + int change = 0; + + val = ucontrol->value.enumerated.item[0]; + /* Limit: uinfo->value.enumerated.items = 2; */ + if (val >= 2) + return -EINVAL; + change = (emu->emu1010.optical_in != val); + if (change) { + emu->emu1010.optical_in = val; + tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : EMU_HANA_OPTICAL_IN_SPDIF) | + (emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : EMU_HANA_OPTICAL_OUT_SPDIF); + snd_emu1010_fpga_write_lock(emu, EMU_HANA_OPTICAL_TYPE, tmp); + } + return change; +} + +static const struct snd_kcontrol_new snd_emu1010_optical_in = { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Optical Input Mode", + .count = 1, + .info = snd_emu1010_optical_in_info, + .get = snd_emu1010_optical_in_get, + .put = snd_emu1010_optical_in_put +}; + +static int snd_audigy_i2c_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ +#if 0 + static const char * const texts[4] = { + "Unknown1", "Unknown2", "Mic", "Line" + }; +#endif + static const char * const texts[2] = { + "Mic", "Line" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_audigy_i2c_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->i2c_capture_source; + return 0; +} + +static int snd_audigy_i2c_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int source_id; + unsigned int ngain, ogain; + u16 gpio; + int change = 0; + u32 source; + /* If the capture source has changed, + * update the capture volume from the cached value + * for the particular source. + */ + source_id = ucontrol->value.enumerated.item[0]; + /* Limit: uinfo->value.enumerated.items = 2; */ + /* emu->i2c_capture_volume */ + if (source_id >= 2) + return -EINVAL; + change = (emu->i2c_capture_source != source_id); + if (change) { + snd_emu10k1_i2c_write(emu, ADC_MUX, 0); /* Mute input */ + scoped_guard(spinlock_irq, &emu->emu_lock) { + gpio = inw(emu->port + A_IOCFG); + if (source_id == 0) + outw(gpio | 0x4, emu->port + A_IOCFG); + else + outw(gpio & ~0x4, emu->port + A_IOCFG); + } + + ngain = emu->i2c_capture_volume[source_id][0]; /* Left */ + ogain = emu->i2c_capture_volume[emu->i2c_capture_source][0]; /* Left */ + if (ngain != ogain) + snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCL, ((ngain) & 0xff)); + ngain = emu->i2c_capture_volume[source_id][1]; /* Right */ + ogain = emu->i2c_capture_volume[emu->i2c_capture_source][1]; /* Right */ + if (ngain != ogain) + snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCR, ((ngain) & 0xff)); + + source = 1 << (source_id + 2); + snd_emu10k1_i2c_write(emu, ADC_MUX, source); /* Set source */ + emu->i2c_capture_source = source_id; + } + return change; +} + +static const struct snd_kcontrol_new snd_audigy_i2c_capture_source = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_audigy_i2c_capture_source_info, + .get = snd_audigy_i2c_capture_source_get, + .put = snd_audigy_i2c_capture_source_put +}; + +static int snd_audigy_i2c_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 = 255; + return 0; +} + +static int snd_audigy_i2c_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int source_id; + + source_id = kcontrol->private_value; + /* Limit: emu->i2c_capture_volume */ + /* capture_source: uinfo->value.enumerated.items = 2 */ + if (source_id >= 2) + return -EINVAL; + + ucontrol->value.integer.value[0] = emu->i2c_capture_volume[source_id][0]; + ucontrol->value.integer.value[1] = emu->i2c_capture_volume[source_id][1]; + return 0; +} + +static int snd_audigy_i2c_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int ogain; + unsigned int ngain0, ngain1; + unsigned int source_id; + int change = 0; + + source_id = kcontrol->private_value; + /* Limit: emu->i2c_capture_volume */ + /* capture_source: uinfo->value.enumerated.items = 2 */ + if (source_id >= 2) + return -EINVAL; + ngain0 = ucontrol->value.integer.value[0]; + ngain1 = ucontrol->value.integer.value[1]; + if (ngain0 > 0xff) + return -EINVAL; + if (ngain1 > 0xff) + return -EINVAL; + ogain = emu->i2c_capture_volume[source_id][0]; /* Left */ + if (ogain != ngain0) { + if (emu->i2c_capture_source == source_id) + snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCL, ngain0); + emu->i2c_capture_volume[source_id][0] = ngain0; + change = 1; + } + ogain = emu->i2c_capture_volume[source_id][1]; /* Right */ + if (ogain != ngain1) { + if (emu->i2c_capture_source == source_id) + snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCR, ngain1); + emu->i2c_capture_volume[source_id][1] = ngain1; + change = 1; + } + + return change; +} + +static const struct snd_kcontrol_new i2c_volume_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_audigy_i2c_volume_info, + .get = snd_audigy_i2c_volume_get, + .put = snd_audigy_i2c_volume_put, + .tlv = { .p = snd_audigy_db_scale2 } +}; + +static const char * const snd_audigy_i2c_volume_ctls[] = { + "Mic Capture Volume", + "Line Capture Volume", +}; + +#if 0 +static int snd_audigy_spdif_output_rate_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"44100", "48000", "96000"}; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_audigy_spdif_output_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int tmp; + + tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0); + switch (tmp & A_SPDIF_RATE_MASK) { + case A_SPDIF_44100: + ucontrol->value.enumerated.item[0] = 0; + break; + case A_SPDIF_48000: + ucontrol->value.enumerated.item[0] = 1; + break; + case A_SPDIF_96000: + ucontrol->value.enumerated.item[0] = 2; + break; + default: + ucontrol->value.enumerated.item[0] = 1; + } + return 0; +} + +static int snd_audigy_spdif_output_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + int change; + unsigned int reg, val, tmp; + + switch(ucontrol->value.enumerated.item[0]) { + case 0: + val = A_SPDIF_44100; + break; + case 1: + val = A_SPDIF_48000; + break; + case 2: + val = A_SPDIF_96000; + break; + default: + val = A_SPDIF_48000; + break; + } + + + guard(spinlock_irq)(&emu->reg_lock); + reg = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0); + tmp = reg & ~A_SPDIF_RATE_MASK; + tmp |= val; + change = (tmp != reg); + if (change) + snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp); + return change; +} + +static const struct snd_kcontrol_new snd_audigy_spdif_output_rate = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Audigy SPDIF Output Sample Rate", + .count = 1, + .info = snd_audigy_spdif_output_rate_info, + .get = snd_audigy_spdif_output_rate_get, + .put = snd_audigy_spdif_output_rate_put +}; +#endif + +static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + int change; + unsigned int val; + + /* Limit: emu->spdif_bits */ + if (idx >= 3) + return -EINVAL; + val = (ucontrol->value.iec958.status[0] << 0) | + (ucontrol->value.iec958.status[1] << 8) | + (ucontrol->value.iec958.status[2] << 16) | + (ucontrol->value.iec958.status[3] << 24); + change = val != emu->spdif_bits[idx]; + if (change) { + snd_emu10k1_ptr_write(emu, SPCS0 + idx, 0, val); + emu->spdif_bits[idx] = val; + } + return change; +} + +static const struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .count = 3, + .info = snd_emu10k1_spdif_info, + .get = snd_emu10k1_spdif_get_mask +}; + +static const struct snd_kcontrol_new snd_emu10k1_spdif_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .count = 3, + .info = snd_emu10k1_spdif_info, + .get = snd_emu10k1_spdif_get, + .put = snd_emu10k1_spdif_put +}; + + +static void update_emu10k1_fxrt(struct snd_emu10k1 *emu, int voice, unsigned char *route) +{ + if (emu->audigy) { + snd_emu10k1_ptr_write_multiple(emu, voice, + A_FXRT1, snd_emu10k1_compose_audigy_fxrt1(route), + A_FXRT2, snd_emu10k1_compose_audigy_fxrt2(route), + REGLIST_END); + } else { + snd_emu10k1_ptr_write(emu, FXRT, voice, + snd_emu10k1_compose_send_routing(route)); + } +} + +static void update_emu10k1_send_volume(struct snd_emu10k1 *emu, int voice, unsigned char *volume) +{ + snd_emu10k1_ptr_write(emu, PTRX_FXSENDAMOUNT_A, voice, volume[0]); + snd_emu10k1_ptr_write(emu, PTRX_FXSENDAMOUNT_B, voice, volume[1]); + snd_emu10k1_ptr_write(emu, PSST_FXSENDAMOUNT_C, voice, volume[2]); + snd_emu10k1_ptr_write(emu, DSL_FXSENDAMOUNT_D, voice, volume[3]); + if (emu->audigy) { + snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, + snd_emu10k1_compose_audigy_sendamounts(volume)); + } +} + +/* PCM stream controls */ + +static int snd_emu10k1_send_routing_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = emu->audigy ? 3*8 : 3*4; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = emu->audigy ? 0x3f : 0x0f; + return 0; +} + +static int snd_emu10k1_send_routing_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_pcm_mixer *mix = + &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; + int voice, idx; + int num_efx = emu->audigy ? 8 : 4; + int mask = emu->audigy ? 0x3f : 0x0f; + + for (voice = 0; voice < 3; voice++) + for (idx = 0; idx < num_efx; idx++) + ucontrol->value.integer.value[(voice * num_efx) + idx] = + mix->send_routing[voice][idx] & mask; + return 0; +} + +static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_pcm_mixer *mix = + &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; + int change = 0, voice, idx, val; + int num_efx = emu->audigy ? 8 : 4; + int mask = emu->audigy ? 0x3f : 0x0f; + + guard(spinlock_irq)(&emu->reg_lock); + for (voice = 0; voice < 3; voice++) + for (idx = 0; idx < num_efx; idx++) { + val = ucontrol->value.integer.value[(voice * num_efx) + idx] & mask; + if (mix->send_routing[voice][idx] != val) { + mix->send_routing[voice][idx] = val; + change = 1; + } + } + if (change && mix->epcm && mix->epcm->voices[0]) { + if (!mix->epcm->voices[0]->last) { + update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number, + &mix->send_routing[1][0]); + update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number + 1, + &mix->send_routing[2][0]); + } else { + update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number, + &mix->send_routing[0][0]); + } + } + return change; +} + +static const struct snd_kcontrol_new snd_emu10k1_send_routing_control = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "EMU10K1 PCM Send Routing", + .count = 32, + .info = snd_emu10k1_send_routing_info, + .get = snd_emu10k1_send_routing_get, + .put = snd_emu10k1_send_routing_put +}; + +static int snd_emu10k1_send_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = emu->audigy ? 3*8 : 3*4; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + return 0; +} + +static int snd_emu10k1_send_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_pcm_mixer *mix = + &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; + int idx; + int num_efx = emu->audigy ? 8 : 4; + + for (idx = 0; idx < 3*num_efx; idx++) + ucontrol->value.integer.value[idx] = mix->send_volume[idx/num_efx][idx%num_efx]; + return 0; +} + +static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_pcm_mixer *mix = + &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; + int change = 0, idx, val; + int num_efx = emu->audigy ? 8 : 4; + + guard(spinlock_irq)(&emu->reg_lock); + for (idx = 0; idx < 3*num_efx; idx++) { + val = ucontrol->value.integer.value[idx] & 255; + if (mix->send_volume[idx/num_efx][idx%num_efx] != val) { + mix->send_volume[idx/num_efx][idx%num_efx] = val; + change = 1; + } + } + if (change && mix->epcm && mix->epcm->voices[0]) { + if (!mix->epcm->voices[0]->last) { + update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number, + &mix->send_volume[1][0]); + update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number + 1, + &mix->send_volume[2][0]); + } else { + update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number, + &mix->send_volume[0][0]); + } + } + return change; +} + +static const struct snd_kcontrol_new snd_emu10k1_send_volume_control = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "EMU10K1 PCM Send Volume", + .count = 32, + .info = snd_emu10k1_send_volume_info, + .get = snd_emu10k1_send_volume_get, + .put = snd_emu10k1_send_volume_put +}; + +static int snd_emu10k1_attn_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0x1fffd; + return 0; +} + +static int snd_emu10k1_attn_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_pcm_mixer *mix = + &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; + int idx; + + for (idx = 0; idx < 3; idx++) + ucontrol->value.integer.value[idx] = mix->attn[idx] * 0xffffU / 0x8000U; + return 0; +} + +static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_pcm_mixer *mix = + &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; + int change = 0, idx, val; + + guard(spinlock_irq)(&emu->reg_lock); + for (idx = 0; idx < 3; idx++) { + unsigned uval = ucontrol->value.integer.value[idx] & 0x1ffff; + val = uval * 0x8000U / 0xffffU; + if (mix->attn[idx] != val) { + mix->attn[idx] = val; + change = 1; + } + } + if (change && mix->epcm && mix->epcm->voices[0]) { + if (!mix->epcm->voices[0]->last) { + snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[1]); + snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number + 1, mix->attn[2]); + } else { + snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[0]); + } + } + return change; +} + +static const struct snd_kcontrol_new snd_emu10k1_attn_control = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "EMU10K1 PCM Volume", + .count = 32, + .info = snd_emu10k1_attn_info, + .get = snd_emu10k1_attn_get, + .put = snd_emu10k1_attn_put +}; + +/* Mutichannel PCM stream controls */ + +static int snd_emu10k1_efx_send_routing_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = emu->audigy ? 8 : 4; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = emu->audigy ? 0x3f : 0x0f; + return 0; +} + +static int snd_emu10k1_efx_send_routing_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_pcm_mixer *mix = + &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; + int idx; + int num_efx = emu->audigy ? 8 : 4; + int mask = emu->audigy ? 0x3f : 0x0f; + + for (idx = 0; idx < num_efx; idx++) + ucontrol->value.integer.value[idx] = + mix->send_routing[0][idx] & mask; + return 0; +} + +static int snd_emu10k1_efx_send_routing_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[ch]; + int change = 0, idx, val; + int num_efx = emu->audigy ? 8 : 4; + int mask = emu->audigy ? 0x3f : 0x0f; + + guard(spinlock_irq)(&emu->reg_lock); + for (idx = 0; idx < num_efx; idx++) { + val = ucontrol->value.integer.value[idx] & mask; + if (mix->send_routing[0][idx] != val) { + mix->send_routing[0][idx] = val; + change = 1; + } + } + + if (change && mix->epcm) { + if (mix->epcm->voices[ch]) { + update_emu10k1_fxrt(emu, mix->epcm->voices[ch]->number, + &mix->send_routing[0][0]); + } + } + return change; +} + +static const struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Multichannel PCM Send Routing", + .count = 16, + .info = snd_emu10k1_efx_send_routing_info, + .get = snd_emu10k1_efx_send_routing_get, + .put = snd_emu10k1_efx_send_routing_put +}; + +static int snd_emu10k1_efx_send_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = emu->audigy ? 8 : 4; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + return 0; +} + +static int snd_emu10k1_efx_send_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_pcm_mixer *mix = + &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; + int idx; + int num_efx = emu->audigy ? 8 : 4; + + for (idx = 0; idx < num_efx; idx++) + ucontrol->value.integer.value[idx] = mix->send_volume[0][idx]; + return 0; +} + +static int snd_emu10k1_efx_send_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[ch]; + int change = 0, idx, val; + int num_efx = emu->audigy ? 8 : 4; + + guard(spinlock_irq)(&emu->reg_lock); + for (idx = 0; idx < num_efx; idx++) { + val = ucontrol->value.integer.value[idx] & 255; + if (mix->send_volume[0][idx] != val) { + mix->send_volume[0][idx] = val; + change = 1; + } + } + if (change && mix->epcm) { + if (mix->epcm->voices[ch]) { + update_emu10k1_send_volume(emu, mix->epcm->voices[ch]->number, + &mix->send_volume[0][0]); + } + } + return change; +} + + +static const struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Multichannel PCM Send Volume", + .count = 16, + .info = snd_emu10k1_efx_send_volume_info, + .get = snd_emu10k1_efx_send_volume_get, + .put = snd_emu10k1_efx_send_volume_put +}; + +static int snd_emu10k1_efx_attn_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 = 0x1fffd; + return 0; +} + +static int snd_emu10k1_efx_attn_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + struct snd_emu10k1_pcm_mixer *mix = + &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; + + ucontrol->value.integer.value[0] = mix->attn[0] * 0xffffU / 0x8000U; + return 0; +} + +static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[ch]; + int change = 0, val; + unsigned uval; + + guard(spinlock_irq)(&emu->reg_lock); + uval = ucontrol->value.integer.value[0] & 0x1ffff; + val = uval * 0x8000U / 0xffffU; + if (mix->attn[0] != val) { + mix->attn[0] = val; + change = 1; + } + if (change && mix->epcm) { + if (mix->epcm->voices[ch]) { + snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[ch]->number, mix->attn[0]); + } + } + return change; +} + +static const struct snd_kcontrol_new snd_emu10k1_efx_attn_control = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Multichannel PCM Volume", + .count = 16, + .info = snd_emu10k1_efx_attn_info, + .get = snd_emu10k1_efx_attn_get, + .put = snd_emu10k1_efx_attn_put +}; + +#define snd_emu10k1_shared_spdif_info snd_ctl_boolean_mono_info + +static int snd_emu10k1_shared_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + + if (emu->audigy) + ucontrol->value.integer.value[0] = inw(emu->port + A_IOCFG) & A_IOCFG_GPOUT0 ? 1 : 0; + else + ucontrol->value.integer.value[0] = inl(emu->port + HCFG) & HCFG_GPOUT0 ? 1 : 0; + if (emu->card_capabilities->invert_shared_spdif) + ucontrol->value.integer.value[0] = + !ucontrol->value.integer.value[0]; + + return 0; +} + +static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int reg, val, sw; + int change = 0; + + sw = ucontrol->value.integer.value[0]; + if (emu->card_capabilities->invert_shared_spdif) + sw = !sw; + guard(spinlock_irq)(&emu->emu_lock); + if ( emu->card_capabilities->i2c_adc) { + /* Do nothing for Audigy 2 ZS Notebook */ + } else if (emu->audigy) { + reg = inw(emu->port + A_IOCFG); + val = sw ? A_IOCFG_GPOUT0 : 0; + change = (reg & A_IOCFG_GPOUT0) != val; + if (change) { + reg &= ~A_IOCFG_GPOUT0; + reg |= val; + outw(reg | val, emu->port + A_IOCFG); + } + } + reg = inl(emu->port + HCFG); + val = sw ? HCFG_GPOUT0 : 0; + change |= (reg & HCFG_GPOUT0) != val; + if (change) { + reg &= ~HCFG_GPOUT0; + reg |= val; + outl(reg | val, emu->port + HCFG); + } + return change; +} + +static const struct snd_kcontrol_new snd_emu10k1_shared_spdif = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "SB Live Analog/Digital Output Jack", + .info = snd_emu10k1_shared_spdif_info, + .get = snd_emu10k1_shared_spdif_get, + .put = snd_emu10k1_shared_spdif_put +}; + +static const struct snd_kcontrol_new snd_audigy_shared_spdif = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Audigy Analog/Digital Output Jack", + .info = snd_emu10k1_shared_spdif_info, + .get = snd_emu10k1_shared_spdif_get, + .put = snd_emu10k1_shared_spdif_put +}; + +/* workaround for too low volume on Audigy due to 16bit/24bit conversion */ + +#define snd_audigy_capture_boost_info snd_ctl_boolean_mono_info + +static int snd_audigy_capture_boost_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + + /* FIXME: better to use a cached version */ + val = snd_ac97_read(emu->ac97, AC97_REC_GAIN); + ucontrol->value.integer.value[0] = !!val; + return 0; +} + +static int snd_audigy_capture_boost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + + if (ucontrol->value.integer.value[0]) + val = 0x0f0f; + else + val = 0; + return snd_ac97_update(emu->ac97, AC97_REC_GAIN, val); +} + +static const struct snd_kcontrol_new snd_audigy_capture_boost = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Extra Boost", + .info = snd_audigy_capture_boost_info, + .get = snd_audigy_capture_boost_get, + .put = snd_audigy_capture_boost_put +}; + + +/* + */ +static void snd_emu10k1_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct snd_emu10k1 *emu = ac97->private_data; + emu->ac97 = NULL; +} + +/* + */ +static int remove_ctl(struct snd_card *card, const char *name) +{ + struct snd_ctl_elem_id id; + memset(&id, 0, sizeof(id)); + strscpy(id.name, name); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + return snd_ctl_remove_id(card, &id); +} + +static int rename_ctl(struct snd_card *card, const char *src, const char *dst) +{ + struct snd_kcontrol *kctl = snd_ctl_find_id_mixer(card, src); + if (kctl) { + snd_ctl_rename(card, kctl, dst); + return 0; + } + return -ENOENT; +} + +int snd_emu10k1_mixer(struct snd_emu10k1 *emu, + int pcm_device, int multi_device) +{ + int err; + struct snd_kcontrol *kctl; + struct snd_card *card = emu->card; + const char * const *c; + static const char * const emu10k1_remove_ctls[] = { + /* no AC97 mono, surround, center/lfe */ + "Master Mono Playback Switch", + "Master Mono Playback Volume", + "PCM Out Path & Mute", + "Mono Output Select", + "Surround Playback Switch", + "Surround Playback Volume", + "Center Playback Switch", + "Center Playback Volume", + "LFE Playback Switch", + "LFE Playback Volume", + NULL + }; + static const char * const emu10k1_rename_ctls[] = { + "Surround Digital Playback Volume", "Surround Playback Volume", + "Center Digital Playback Volume", "Center Playback Volume", + "LFE Digital Playback Volume", "LFE Playback Volume", + NULL + }; + static const char * const audigy_remove_ctls[] = { + /* Master/PCM controls on ac97 of Audigy has no effect */ + /* On the Audigy2 the AC97 playback is piped into + * the Philips ADC for 24bit capture */ + "PCM Playback Switch", + "PCM Playback Volume", + "Master Playback Switch", + "Master Playback Volume", + "PCM Out Path & Mute", + "Mono Output Select", + /* remove unused AC97 capture controls */ + "Capture Source", + "Capture Switch", + "Capture Volume", + "Mic Select", + "Headphone Playback Switch", + "Headphone Playback Volume", + "3D Control - Center", + "3D Control - Depth", + "3D Control - Switch", + "Video Playback Switch", + "Video Playback Volume", + "Mic Playback Switch", + "Mic Playback Volume", + "External Amplifier", + NULL + }; + static const char * const audigy_rename_ctls[] = { + /* use conventional names */ + "Wave Playback Volume", "PCM Playback Volume", + /* "Wave Capture Volume", "PCM Capture Volume", */ + "Wave Master Playback Volume", "Master Playback Volume", + "AMic Playback Volume", "Mic Playback Volume", + "Master Mono Playback Switch", "Phone Output Playback Switch", + "Master Mono Playback Volume", "Phone Output Playback Volume", + NULL + }; + static const char * const audigy_rename_ctls_i2c_adc[] = { + //"Analog Mix Capture Volume","OLD Analog Mix Capture Volume", + "Line Capture Volume", "Analog Mix Capture Volume", + "Wave Playback Volume", "OLD PCM Playback Volume", + "Wave Master Playback Volume", "Master Playback Volume", + "AMic Playback Volume", "Old Mic Playback Volume", + "CD Capture Volume", "IEC958 Optical Capture Volume", + NULL + }; + static const char * const audigy_remove_ctls_i2c_adc[] = { + /* On the Audigy2 ZS Notebook + * Capture via WM8775 */ + "Mic Capture Volume", + "Analog Mix Capture Volume", + "Aux Capture Volume", + "IEC958 Optical Capture Volume", + NULL + }; + static const char * const audigy_remove_ctls_1361t_adc[] = { + /* On the Audigy2 the AC97 playback is piped into + * the Philips ADC for 24bit capture */ + "PCM Playback Switch", + "PCM Playback Volume", + "Capture Source", + "Capture Switch", + "Capture Volume", + "Mic Capture Volume", + "Headphone Playback Switch", + "Headphone Playback Volume", + "3D Control - Center", + "3D Control - Depth", + "3D Control - Switch", + "Line2 Playback Volume", + "Line2 Capture Volume", + NULL + }; + static const char * const audigy_rename_ctls_1361t_adc[] = { + "Master Playback Switch", "Master Capture Switch", + "Master Playback Volume", "Master Capture Volume", + "Wave Master Playback Volume", "Master Playback Volume", + "Beep Playback Switch", "Beep Capture Switch", + "Beep Playback Volume", "Beep Capture Volume", + "Phone Playback Switch", "Phone Capture Switch", + "Phone Playback Volume", "Phone Capture Volume", + "Mic Playback Switch", "Mic Capture Switch", + "Mic Playback Volume", "Mic Capture Volume", + "Line Playback Switch", "Line Capture Switch", + "Line Playback Volume", "Line Capture Volume", + "CD Playback Switch", "CD Capture Switch", + "CD Playback Volume", "CD Capture Volume", + "Aux Playback Switch", "Aux Capture Switch", + "Aux Playback Volume", "Aux Capture Volume", + "Video Playback Switch", "Video Capture Switch", + "Video Playback Volume", "Video Capture Volume", + "Master Mono Playback Switch", "Phone Output Playback Switch", + "Master Mono Playback Volume", "Phone Output Playback Volume", + NULL + }; + + if (emu->card_capabilities->ac97_chip) { + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + static const struct snd_ac97_bus_ops ops = { + .write = snd_emu10k1_ac97_write, + .read = snd_emu10k1_ac97_read, + }; + + err = snd_ac97_bus(emu->card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + pbus->no_vra = 1; /* we don't need VRA */ + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = emu; + ac97.private_free = snd_emu10k1_mixer_free_ac97; + ac97.scaps = AC97_SCAP_NO_SPDIF; + err = snd_ac97_mixer(pbus, &ac97, &emu->ac97); + if (err < 0) { + if (emu->card_capabilities->ac97_chip == 1) + return err; + dev_info(emu->card->dev, + "AC97 is optional on this board\n"); + dev_info(emu->card->dev, + "Proceeding without ac97 mixers...\n"); + snd_device_free(emu->card, pbus); + goto no_ac97; /* FIXME: get rid of ugly gotos.. */ + } + if (emu->audigy) { + /* set master volume to 0 dB */ + snd_ac97_write_cache(emu->ac97, AC97_MASTER, 0x0000); + /* set capture source to mic */ + snd_ac97_write_cache(emu->ac97, AC97_REC_SEL, 0x0000); + /* set mono output (TAD) to mic */ + snd_ac97_update_bits(emu->ac97, AC97_GENERAL_PURPOSE, + 0x0200, 0x0200); + if (emu->card_capabilities->adc_1361t) + c = audigy_remove_ctls_1361t_adc; + else + c = audigy_remove_ctls; + } else { + /* + * Credits for cards based on STAC9758: + * James Courtier-Dutton + * Voluspa + */ + if (emu->ac97->id == AC97_ID_STAC9758) { + emu->rear_ac97 = 1; + snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_CNTR|AC97SLOT_LFE|AC97SLOT_REAR_LEFT|AC97SLOT_REAR_RIGHT); + snd_ac97_write_cache(emu->ac97, AC97_HEADPHONE, 0x0202); + remove_ctl(card,"Front Playback Volume"); + remove_ctl(card,"Front Playback Switch"); + } + /* remove unused AC97 controls */ + snd_ac97_write_cache(emu->ac97, AC97_SURROUND_MASTER, 0x0202); + snd_ac97_write_cache(emu->ac97, AC97_CENTER_LFE_MASTER, 0x0202); + c = emu10k1_remove_ctls; + } + for (; *c; c++) + remove_ctl(card, *c); + } else if (emu->card_capabilities->i2c_adc) { + c = audigy_remove_ctls_i2c_adc; + for (; *c; c++) + remove_ctl(card, *c); + } else { + no_ac97: + if (emu->card_capabilities->ecard) + strscpy(emu->card->mixername, "EMU APS"); + else if (emu->audigy) + strscpy(emu->card->mixername, "SB Audigy"); + else + strscpy(emu->card->mixername, "Emu10k1"); + } + + if (emu->audigy) + if (emu->card_capabilities->adc_1361t) + c = audigy_rename_ctls_1361t_adc; + else if (emu->card_capabilities->i2c_adc) + c = audigy_rename_ctls_i2c_adc; + else + c = audigy_rename_ctls; + else + c = emu10k1_rename_ctls; + for (; *c; c += 2) + rename_ctl(card, c[0], c[1]); + + if (emu->card_capabilities->subsystem == 0x80401102) { /* SB Live! Platinum CT4760P */ + remove_ctl(card, "Center Playback Volume"); + remove_ctl(card, "LFE Playback Volume"); + remove_ctl(card, "Wave Center Playback Volume"); + remove_ctl(card, "Wave LFE Playback Volume"); + } + if (emu->card_capabilities->subsystem == 0x20071102) { /* Audigy 4 Pro */ + rename_ctl(card, "Line2 Capture Volume", "Line1/Mic Capture Volume"); + rename_ctl(card, "Analog Mix Capture Volume", "Line2 Capture Volume"); + rename_ctl(card, "Aux2 Capture Volume", "Line3 Capture Volume"); + rename_ctl(card, "Mic Capture Volume", "Unknown1 Capture Volume"); + } + kctl = emu->ctl_send_routing = snd_ctl_new1(&snd_emu10k1_send_routing_control, emu); + if (!kctl) + return -ENOMEM; + kctl->id.device = pcm_device; + err = snd_ctl_add(card, kctl); + if (err) + return err; + kctl = emu->ctl_send_volume = snd_ctl_new1(&snd_emu10k1_send_volume_control, emu); + if (!kctl) + return -ENOMEM; + kctl->id.device = pcm_device; + err = snd_ctl_add(card, kctl); + if (err) + return err; + kctl = emu->ctl_attn = snd_ctl_new1(&snd_emu10k1_attn_control, emu); + if (!kctl) + return -ENOMEM; + kctl->id.device = pcm_device; + err = snd_ctl_add(card, kctl); + if (err) + return err; + + kctl = emu->ctl_efx_send_routing = snd_ctl_new1(&snd_emu10k1_efx_send_routing_control, emu); + if (!kctl) + return -ENOMEM; + kctl->id.device = multi_device; + err = snd_ctl_add(card, kctl); + if (err) + return err; + + kctl = emu->ctl_efx_send_volume = snd_ctl_new1(&snd_emu10k1_efx_send_volume_control, emu); + if (!kctl) + return -ENOMEM; + kctl->id.device = multi_device; + err = snd_ctl_add(card, kctl); + if (err) + return err; + + kctl = emu->ctl_efx_attn = snd_ctl_new1(&snd_emu10k1_efx_attn_control, emu); + if (!kctl) + return -ENOMEM; + kctl->id.device = multi_device; + err = snd_ctl_add(card, kctl); + if (err) + return err; + + if (!emu->card_capabilities->ecard && !emu->card_capabilities->emu_model) { + /* sb live! and audigy */ + kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu); + if (!kctl) + return -ENOMEM; + if (!emu->audigy) + kctl->id.device = emu->pcm_efx->device; + err = snd_ctl_add(card, kctl); + if (err) + return err; + kctl = snd_ctl_new1(&snd_emu10k1_spdif_control, emu); + if (!kctl) + return -ENOMEM; + if (!emu->audigy) + kctl->id.device = emu->pcm_efx->device; + err = snd_ctl_add(card, kctl); + if (err) + return err; + } + + if (emu->card_capabilities->emu_model) { + ; /* Disable the snd_audigy_spdif_shared_spdif */ + } else if (emu->audigy) { + kctl = snd_ctl_new1(&snd_audigy_shared_spdif, emu); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(card, kctl); + if (err) + return err; +#if 0 + kctl = snd_ctl_new1(&snd_audigy_spdif_output_rate, emu); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(card, kctl); + if (err) + return err; +#endif + } else if (! emu->card_capabilities->ecard) { + /* sb live! */ + kctl = snd_ctl_new1(&snd_emu10k1_shared_spdif, emu); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(card, kctl); + if (err) + return err; + } + if (emu->card_capabilities->ca0151_chip) { /* P16V */ + err = snd_p16v_mixer(emu); + if (err) + return err; + } + + if (emu->card_capabilities->emu_model) { + unsigned i, emu_idx = emu1010_idx(emu); + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu_idx]; + const struct snd_emu1010_pads_info *emu_pi = &emu1010_pads_info[emu_idx]; + + for (i = 0; i < emu_ri->n_ins; i++) + emu->emu1010.input_source[i] = + emu1010_map_source(emu_ri, emu_ri->in_dflts[i]); + for (i = 0; i < emu_ri->n_outs; i++) + emu->emu1010.output_source[i] = + emu1010_map_source(emu_ri, emu_ri->out_dflts[i]); + scoped_guard(snd_emu1010_fpga_lock, emu) { + snd_emu1010_apply_sources(emu); + } + + kctl = emu->ctl_clock_source = snd_ctl_new1(&snd_emu1010_clock_source, emu); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + err = snd_ctl_add(card, + snd_ctl_new1(&snd_emu1010_clock_fallback, emu)); + if (err < 0) + return err; + + err = add_ctls(emu, &emu1010_adc_pads_ctl, + emu_pi->adc_ctls, emu_pi->n_adc_ctls); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_dac_pads_ctl, + emu_pi->dac_ctls, emu_pi->n_dac_ctls); + if (err < 0) + return err; + + if (!emu->card_capabilities->no_adat) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_emu1010_optical_out, emu)); + if (err < 0) + return err; + err = snd_ctl_add(card, + snd_ctl_new1(&snd_emu1010_optical_in, emu)); + if (err < 0) + return err; + } + + err = add_emu1010_source_mixers(emu); + if (err < 0) + return err; + } + + if ( emu->card_capabilities->i2c_adc) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_i2c_capture_source, emu)); + if (err < 0) + return err; + + err = add_ctls(emu, &i2c_volume_ctl, + snd_audigy_i2c_volume_ctls, + ARRAY_SIZE(snd_audigy_i2c_volume_ctls)); + if (err < 0) + return err; + } + + if (emu->card_capabilities->ac97_chip && emu->audigy) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_capture_boost, + emu)); + if (err < 0) + return err; + } + + return 0; +} diff --git a/sound/pci/emu10k1/emumpu401.c b/sound/pci/emu10k1/emumpu401.c new file mode 100644 index 000000000..c102a3599 --- /dev/null +++ b/sound/pci/emu10k1/emumpu401.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of EMU10K1 MPU-401 in UART mode + */ + +#include +#include +#include +#include + +#define EMU10K1_MIDI_MODE_INPUT (1<<0) +#define EMU10K1_MIDI_MODE_OUTPUT (1<<1) + +static inline unsigned char mpu401_read(struct snd_emu10k1 *emu, + struct snd_emu10k1_midi *mpu, int idx) +{ + if (emu->audigy) + return (unsigned char)snd_emu10k1_ptr_read(emu, mpu->port + idx, 0); + else + return inb(emu->port + mpu->port + idx); +} + +static inline void mpu401_write(struct snd_emu10k1 *emu, + struct snd_emu10k1_midi *mpu, int data, int idx) +{ + if (emu->audigy) + snd_emu10k1_ptr_write(emu, mpu->port + idx, 0, data); + else + outb(data, emu->port + mpu->port + idx); +} + +#define mpu401_write_data(emu, mpu, data) mpu401_write(emu, mpu, data, 0) +#define mpu401_write_cmd(emu, mpu, data) mpu401_write(emu, mpu, data, 1) +#define mpu401_read_data(emu, mpu) mpu401_read(emu, mpu, 0) +#define mpu401_read_stat(emu, mpu) mpu401_read(emu, mpu, 1) + +#define mpu401_input_avail(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x80)) +#define mpu401_output_ready(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x40)) + +#define MPU401_RESET 0xff +#define MPU401_ENTER_UART 0x3f +#define MPU401_ACK 0xfe + +static void mpu401_clear_rx(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *mpu) +{ + int timeout = 100000; + for (; timeout > 0 && mpu401_input_avail(emu, mpu); timeout--) + mpu401_read_data(emu, mpu); +#ifdef CONFIG_SND_DEBUG + if (timeout <= 0) + dev_err(emu->card->dev, + "cmd: clear rx timeout (status = 0x%x)\n", + mpu401_read_stat(emu, mpu)); +#endif +} + +/* + + */ + +static void do_emu10k1_midi_interrupt(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *midi, unsigned int status) +{ + unsigned char byte; + + if (midi->rmidi == NULL) { + snd_emu10k1_intr_disable(emu, midi->tx_enable | midi->rx_enable); + return; + } + + scoped_guard(spinlock, &midi->input_lock) { + if ((status & midi->ipr_rx) && mpu401_input_avail(emu, midi)) { + if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) { + mpu401_clear_rx(emu, midi); + } else { + byte = mpu401_read_data(emu, midi); + if (midi->substream_input) + snd_rawmidi_receive(midi->substream_input, &byte, 1); + } + } + } + + scoped_guard(spinlock, &midi->output_lock) { + if ((status & midi->ipr_tx) && mpu401_output_ready(emu, midi)) { + if (midi->substream_output && + snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) { + mpu401_write_data(emu, midi, byte); + } else { + snd_emu10k1_intr_disable(emu, midi->tx_enable); + } + } + } +} + +static void snd_emu10k1_midi_interrupt(struct snd_emu10k1 *emu, unsigned int status) +{ + do_emu10k1_midi_interrupt(emu, &emu->midi, status); +} + +static void snd_emu10k1_midi_interrupt2(struct snd_emu10k1 *emu, unsigned int status) +{ + do_emu10k1_midi_interrupt(emu, &emu->midi2, status); +} + +static int snd_emu10k1_midi_cmd(struct snd_emu10k1 * emu, struct snd_emu10k1_midi *midi, unsigned char cmd, int ack) +{ + int timeout, ok; + + scoped_guard(spinlock_irq, &midi->input_lock) { + mpu401_write_data(emu, midi, 0x00); + /* mpu401_clear_rx(emu, midi); */ + + mpu401_write_cmd(emu, midi, cmd); + if (ack) { + ok = 0; + timeout = 10000; + while (!ok && timeout-- > 0) { + if (mpu401_input_avail(emu, midi)) { + if (mpu401_read_data(emu, midi) == MPU401_ACK) + ok = 1; + } + } + if (!ok && mpu401_read_data(emu, midi) == MPU401_ACK) + ok = 1; + } else { + ok = 1; + } + } + if (!ok) { + dev_err(emu->card->dev, + "midi_cmd: 0x%x failed at 0x%lx (status = 0x%x, data = 0x%x)!!!\n", + cmd, emu->port, + mpu401_read_stat(emu, midi), + mpu401_read_data(emu, midi)); + return 1; + } + return 0; +} + +static int snd_emu10k1_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_emu10k1 *emu; + struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + scoped_guard(spinlock_irq, &midi->open_lock) { + midi->midi_mode |= EMU10K1_MIDI_MODE_INPUT; + midi->substream_input = substream; + if (midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT) + return 0; + } + if (snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1)) + return -EIO; + if (snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1)) + return -EIO; + return 0; +} + +static int snd_emu10k1_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_emu10k1 *emu; + struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + scoped_guard(spinlock_irq, &midi->open_lock) { + midi->midi_mode |= EMU10K1_MIDI_MODE_OUTPUT; + midi->substream_output = substream; + if (midi->midi_mode & EMU10K1_MIDI_MODE_INPUT) + return 0; + } + if (snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1)) + return -EIO; + if (snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1)) + return -EIO; + return 0; +} + +static int snd_emu10k1_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_emu10k1 *emu; + struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + scoped_guard(spinlock_irq, &midi->open_lock) { + snd_emu10k1_intr_disable(emu, midi->rx_enable); + midi->midi_mode &= ~EMU10K1_MIDI_MODE_INPUT; + midi->substream_input = NULL; + if (midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT) + return 0; + } + return snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0); +} + +static int snd_emu10k1_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_emu10k1 *emu; + struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + scoped_guard(spinlock_irq, &midi->open_lock) { + snd_emu10k1_intr_disable(emu, midi->tx_enable); + midi->midi_mode &= ~EMU10K1_MIDI_MODE_OUTPUT; + midi->substream_output = NULL; + if (midi->midi_mode & EMU10K1_MIDI_MODE_INPUT) + return 0; + } + return snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0); +} + +static void snd_emu10k1_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_emu10k1 *emu; + struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data; + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return; + + if (up) + snd_emu10k1_intr_enable(emu, midi->rx_enable); + else + snd_emu10k1_intr_disable(emu, midi->rx_enable); +} + +static void snd_emu10k1_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_emu10k1 *emu; + struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data; + + emu = midi->emu; + if (snd_BUG_ON(!emu)) + return; + + if (up) { + int max = 4; + unsigned char byte; + + /* try to send some amount of bytes here before interrupts */ + scoped_guard(spinlock_irq, &midi->output_lock) { + while (max > 0) { + if (mpu401_output_ready(emu, midi)) { + if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT) || + snd_rawmidi_transmit(substream, &byte, 1) != 1) { + /* no more data */ + return; + } + mpu401_write_data(emu, midi, byte); + max--; + } else { + break; + } + } + } + snd_emu10k1_intr_enable(emu, midi->tx_enable); + } else { + snd_emu10k1_intr_disable(emu, midi->tx_enable); + } +} + +/* + + */ + +static const struct snd_rawmidi_ops snd_emu10k1_midi_output = +{ + .open = snd_emu10k1_midi_output_open, + .close = snd_emu10k1_midi_output_close, + .trigger = snd_emu10k1_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_emu10k1_midi_input = +{ + .open = snd_emu10k1_midi_input_open, + .close = snd_emu10k1_midi_input_close, + .trigger = snd_emu10k1_midi_input_trigger, +}; + +static void snd_emu10k1_midi_free(struct snd_rawmidi *rmidi) +{ + struct snd_emu10k1_midi *midi = rmidi->private_data; + midi->interrupt = NULL; + midi->rmidi = NULL; +} + +static int emu10k1_midi_init(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *midi, int device, char *name) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(emu->card, name, device, 1, 1, &rmidi); + if (err < 0) + return err; + midi->emu = emu; + spin_lock_init(&midi->open_lock); + spin_lock_init(&midi->input_lock); + spin_lock_init(&midi->output_lock); + strscpy(rmidi->name, name); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_emu10k1_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_emu10k1_midi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = midi; + rmidi->private_free = snd_emu10k1_midi_free; + midi->rmidi = rmidi; + return 0; +} + +int snd_emu10k1_midi(struct snd_emu10k1 *emu) +{ + struct snd_emu10k1_midi *midi = &emu->midi; + int err; + + err = emu10k1_midi_init(emu, midi, 0, "EMU10K1 MPU-401 (UART)"); + if (err < 0) + return err; + + midi->tx_enable = INTE_MIDITXENABLE; + midi->rx_enable = INTE_MIDIRXENABLE; + midi->port = MUDATA; + midi->ipr_tx = IPR_MIDITRANSBUFEMPTY; + midi->ipr_rx = IPR_MIDIRECVBUFEMPTY; + midi->interrupt = snd_emu10k1_midi_interrupt; + return 0; +} + +int snd_emu10k1_audigy_midi(struct snd_emu10k1 *emu) +{ + struct snd_emu10k1_midi *midi; + int err; + + midi = &emu->midi; + err = emu10k1_midi_init(emu, midi, 0, "Audigy MPU-401 (UART)"); + if (err < 0) + return err; + + midi->tx_enable = INTE_MIDITXENABLE; + midi->rx_enable = INTE_MIDIRXENABLE; + midi->port = A_MUDATA1; + midi->ipr_tx = IPR_MIDITRANSBUFEMPTY; + midi->ipr_rx = IPR_MIDIRECVBUFEMPTY; + midi->interrupt = snd_emu10k1_midi_interrupt; + + midi = &emu->midi2; + err = emu10k1_midi_init(emu, midi, 1, "Audigy MPU-401 #2"); + if (err < 0) + return err; + + midi->tx_enable = INTE_A_MIDITXENABLE2; + midi->rx_enable = INTE_A_MIDIRXENABLE2; + midi->port = A_MUDATA2; + midi->ipr_tx = IPR_A_MIDITRANSBUFEMPTY2; + midi->ipr_rx = IPR_A_MIDIRECVBUFEMPTY2; + midi->interrupt = snd_emu10k1_midi_interrupt2; + return 0; +} diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c new file mode 100644 index 000000000..b8749e013 --- /dev/null +++ b/sound/pci/emu10k1/emupcm.c @@ -0,0 +1,1838 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Lee Revell + * James Courtier-Dutton + * Oswald Buddenhagen + * Creative Labs, Inc. + * + * Routines for control of EMU10K1 chips / PCM routines + */ + +#include +#include +#include +#include +#include +#include +#include + +static void snd_emu10k1_pcm_interrupt(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *voice) +{ + struct snd_emu10k1_pcm *epcm; + + epcm = voice->epcm; + if (!epcm) + return; + if (epcm->substream == NULL) + return; +#if 0 + dev_dbg(emu->card->dev, + "IRQ: position = 0x%x, period = 0x%x, size = 0x%x\n", + epcm->substream->runtime->hw->pointer(emu, epcm->substream), + snd_pcm_lib_period_bytes(epcm->substream), + snd_pcm_lib_buffer_bytes(epcm->substream)); +#endif + snd_pcm_period_elapsed(epcm->substream); +} + +static void snd_emu10k1_pcm_ac97adc_interrupt(struct snd_emu10k1 *emu, + unsigned int status) +{ +#if 0 + if (status & IPR_ADCBUFHALFFULL) { + if (emu->pcm_capture_substream->runtime->mode == SNDRV_PCM_MODE_FRAME) + return; + } +#endif + snd_pcm_period_elapsed(emu->pcm_capture_substream); +} + +static void snd_emu10k1_pcm_ac97mic_interrupt(struct snd_emu10k1 *emu, + unsigned int status) +{ +#if 0 + if (status & IPR_MICBUFHALFFULL) { + if (emu->pcm_capture_mic_substream->runtime->mode == SNDRV_PCM_MODE_FRAME) + return; + } +#endif + snd_pcm_period_elapsed(emu->pcm_capture_mic_substream); +} + +static void snd_emu10k1_pcm_efx_interrupt(struct snd_emu10k1 *emu, + unsigned int status) +{ +#if 0 + if (status & IPR_EFXBUFHALFFULL) { + if (emu->pcm_capture_efx_substream->runtime->mode == SNDRV_PCM_MODE_FRAME) + return; + } +#endif + snd_pcm_period_elapsed(emu->pcm_capture_efx_substream); +} + +static void snd_emu10k1_pcm_free_voices(struct snd_emu10k1_pcm *epcm) +{ + for (unsigned i = 0; i < ARRAY_SIZE(epcm->voices); i++) { + if (epcm->voices[i]) { + snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]); + epcm->voices[i] = NULL; + } + } +} + +static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm *epcm, + int type, int count, int channels) +{ + int err; + + snd_emu10k1_pcm_free_voices(epcm); + + err = snd_emu10k1_voice_alloc(epcm->emu, + type, count, channels, + epcm, &epcm->voices[0]); + if (err < 0) + return err; + + if (epcm->extra == NULL) { + // The hardware supports only (half-)loop interrupts, so to support an + // arbitrary number of periods per buffer, we use an extra voice with a + // period-sized loop as the interrupt source. Additionally, the interrupt + // timing of the hardware is "suboptimal" and needs some compensation. + err = snd_emu10k1_voice_alloc(epcm->emu, + type + 1, 1, 1, + epcm, &epcm->extra); + if (err < 0) { + /* + dev_dbg(emu->card->dev, "pcm_channel_alloc: " + "failed extra: voices=%d, frame=%d\n", + voices, frame); + */ + snd_emu10k1_pcm_free_voices(epcm); + return err; + } + epcm->extra->interrupt = snd_emu10k1_pcm_interrupt; + } + + return 0; +} + +// Primes 2-7 and 2^n multiples thereof, up to 16. +static const unsigned int efx_capture_channels[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_efx_capture_channels = { + .count = ARRAY_SIZE(efx_capture_channels), + .list = efx_capture_channels, + .mask = 0 +}; + +static const unsigned int capture_buffer_sizes[31] = { + 384, 448, 512, 640, + 384*2, 448*2, 512*2, 640*2, + 384*4, 448*4, 512*4, 640*4, + 384*8, 448*8, 512*8, 640*8, + 384*16, 448*16, 512*16, 640*16, + 384*32, 448*32, 512*32, 640*32, + 384*64, 448*64, 512*64, 640*64, + 384*128,448*128,512*128 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_capture_buffer_sizes = { + .count = 31, + .list = capture_buffer_sizes, + .mask = 0 +}; + +static unsigned int snd_emu10k1_capture_rate_reg(unsigned int rate) +{ + switch (rate) { + case 8000: return ADCCR_SAMPLERATE_8; + case 11025: return ADCCR_SAMPLERATE_11; + case 16000: return ADCCR_SAMPLERATE_16; + case 22050: return ADCCR_SAMPLERATE_22; + case 24000: return ADCCR_SAMPLERATE_24; + case 32000: return ADCCR_SAMPLERATE_32; + case 44100: return ADCCR_SAMPLERATE_44; + case 48000: return ADCCR_SAMPLERATE_48; + default: + snd_BUG(); + return ADCCR_SAMPLERATE_8; + } +} + +static unsigned int snd_emu10k1_audigy_capture_rate_reg(unsigned int rate) +{ + switch (rate) { + case 8000: return A_ADCCR_SAMPLERATE_8; + case 11025: return A_ADCCR_SAMPLERATE_11; + case 12000: return A_ADCCR_SAMPLERATE_12; + case 16000: return ADCCR_SAMPLERATE_16; + case 22050: return ADCCR_SAMPLERATE_22; + case 24000: return ADCCR_SAMPLERATE_24; + case 32000: return ADCCR_SAMPLERATE_32; + case 44100: return ADCCR_SAMPLERATE_44; + case 48000: return ADCCR_SAMPLERATE_48; + default: + snd_BUG(); + return A_ADCCR_SAMPLERATE_8; + } +} + +static void snd_emu10k1_constrain_capture_rates(struct snd_emu10k1 *emu, + struct snd_pcm_runtime *runtime) +{ + if (emu->card_capabilities->emu_model && + emu->emu1010.word_clock == 44100) { + runtime->hw.rates = SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100; + runtime->hw.rate_min = 11025; + runtime->hw.rate_max = 44100; + } else if (emu->audigy) { + runtime->hw.rates = SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_12000 | + SNDRV_PCM_RATE_24000; + } +} + +static void snd_emu1010_constrain_efx_rate(struct snd_emu10k1 *emu, + struct snd_pcm_runtime *runtime) +{ + int rate; + + rate = emu->emu1010.word_clock; + runtime->hw.rate_min = runtime->hw.rate_max = rate; + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); +} + +static unsigned int emu10k1_calc_pitch_target(unsigned int rate) +{ + unsigned int pitch_target; + + pitch_target = (rate << 8) / 375; + pitch_target = (pitch_target >> 1) + (pitch_target & 1); + return pitch_target; +} + +#define PITCH_48000 0x00004000 +#define PITCH_96000 0x00008000 +#define PITCH_85000 0x00007155 +#define PITCH_80726 0x00006ba2 +#define PITCH_67882 0x00005a82 +#define PITCH_57081 0x00004c1c + +static unsigned int emu10k1_select_interprom(unsigned int pitch_target) +{ + if (pitch_target == PITCH_48000) + return CCCA_INTERPROM_0; + else if (pitch_target < PITCH_48000) + return CCCA_INTERPROM_1; + else if (pitch_target >= PITCH_96000) + return CCCA_INTERPROM_0; + else if (pitch_target >= PITCH_85000) + return CCCA_INTERPROM_6; + else if (pitch_target >= PITCH_80726) + return CCCA_INTERPROM_5; + else if (pitch_target >= PITCH_67882) + return CCCA_INTERPROM_4; + else if (pitch_target >= PITCH_57081) + return CCCA_INTERPROM_3; + else + return CCCA_INTERPROM_2; +} + +static u16 emu10k1_send_target_from_amount(u8 amount) +{ + static const u8 shifts[8] = { 4, 4, 5, 6, 7, 8, 9, 10 }; + static const u16 offsets[8] = { 0, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000 }; + u8 exp; + + if (amount == 0xff) + return 0xffff; + exp = amount >> 5; + return ((amount & 0x1f) << shifts[exp]) + offsets[exp]; +} + +static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool w_16, bool stereo, + unsigned int start_addr, + unsigned int end_addr, + const unsigned char *send_routing, + const unsigned char *send_amount) +{ + unsigned int silent_page; + int voice; + + voice = evoice->number; + + silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | + (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); + snd_emu10k1_ptr_write_multiple(emu, voice, + // Not really necessary for the slave, but it doesn't hurt + CPF, stereo ? CPF_STEREO_MASK : 0, + // Assumption that PT is already 0 so no harm overwriting + PTRX, (send_amount[0] << 8) | send_amount[1], + // Stereo slaves don't need to have the addresses set, but it doesn't hurt + DSL, end_addr | (send_amount[3] << 24), + PSST, start_addr | (send_amount[2] << 24), + CCCA, emu10k1_select_interprom(evoice->epcm->pitch_target) | + (w_16 ? 0 : CCCA_8BITSELECT), + // Clear filter delay memory + Z1, 0, + Z2, 0, + // Invalidate maps + MAPA, silent_page, + MAPB, silent_page, + // Disable filter (in conjunction with CCCA_RESONANCE == 0) + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + REGLIST_END); + // Setup routing + if (emu->audigy) { + snd_emu10k1_ptr_write_multiple(emu, voice, + A_FXRT1, snd_emu10k1_compose_audigy_fxrt1(send_routing), + A_FXRT2, snd_emu10k1_compose_audigy_fxrt2(send_routing), + A_SENDAMOUNTS, snd_emu10k1_compose_audigy_sendamounts(send_amount), + REGLIST_END); + for (int i = 0; i < 4; i++) { + u32 aml = emu10k1_send_target_from_amount(send_amount[2 * i]); + u32 amh = emu10k1_send_target_from_amount(send_amount[2 * i + 1]); + snd_emu10k1_ptr_write(emu, A_CSBA + i, voice, (amh << 16) | aml); + } + } else { + snd_emu10k1_ptr_write(emu, FXRT, voice, + snd_emu10k1_compose_send_routing(send_routing)); + } + + emu->voices[voice].dirty = 1; +} + +static void snd_emu10k1_pcm_init_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool w_16, bool stereo, + unsigned int start_addr, + unsigned int end_addr, + struct snd_emu10k1_pcm_mixer *mix) +{ + guard(spinlock_irq)(&emu->reg_lock); + snd_emu10k1_pcm_init_voice(emu, evoice, w_16, stereo, + start_addr, end_addr, + &mix->send_routing[stereo][0], + &mix->send_volume[stereo][0]); + if (stereo) + snd_emu10k1_pcm_init_voice(emu, evoice + 1, w_16, true, + start_addr, end_addr, + &mix->send_routing[2][0], + &mix->send_volume[2][0]); +} + +static void snd_emu10k1_pcm_init_extra_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool w_16, + unsigned int start_addr, + unsigned int end_addr) +{ + static const unsigned char send_routing[8] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + static const unsigned char send_amount[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + snd_emu10k1_pcm_init_voice(emu, evoice, w_16, false, + start_addr, end_addr, + send_routing, send_amount); +} + +static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm = runtime->private_data; + size_t alloc_size; + int type, channels, count; + int err; + + if (epcm->type == PLAYBACK_EMUVOICE) { + type = EMU10K1_PCM; + channels = 1; + count = params_channels(hw_params); + } else { + type = EMU10K1_EFX; + channels = params_channels(hw_params); + count = 1; + } + err = snd_emu10k1_pcm_channel_alloc(epcm, type, count, channels); + if (err < 0) + return err; + + alloc_size = params_buffer_bytes(hw_params); + if (emu->iommu_workaround) + alloc_size += EMUPAGESIZE; + err = snd_pcm_lib_malloc_pages(substream, alloc_size); + if (err < 0) + return err; + if (emu->iommu_workaround && runtime->dma_bytes >= EMUPAGESIZE) + runtime->dma_bytes -= EMUPAGESIZE; + if (err > 0) { /* change */ + int mapped; + if (epcm->memblk != NULL) + snd_emu10k1_free_pages(emu, epcm->memblk); + epcm->memblk = snd_emu10k1_alloc_pages(emu, substream); + epcm->start_addr = 0; + if (! epcm->memblk) + return -ENOMEM; + mapped = ((struct snd_emu10k1_memblk *)epcm->memblk)->mapped_page; + if (mapped < 0) + return -ENOMEM; + epcm->start_addr = mapped << PAGE_SHIFT; + } + return 0; +} + +static int snd_emu10k1_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm; + + if (runtime->private_data == NULL) + return 0; + epcm = runtime->private_data; + if (epcm->extra) { + snd_emu10k1_voice_free(epcm->emu, epcm->extra); + epcm->extra = NULL; + } + snd_emu10k1_pcm_free_voices(epcm); + if (epcm->memblk) { + snd_emu10k1_free_pages(emu, epcm->memblk); + epcm->memblk = NULL; + epcm->start_addr = 0; + } + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm = runtime->private_data; + bool w_16 = snd_pcm_format_width(runtime->format) == 16; + bool stereo = runtime->channels == 2; + unsigned int start_addr, end_addr; + unsigned int rate; + + rate = runtime->rate; + if (emu->card_capabilities->emu_model && + emu->emu1010.word_clock == 44100) + rate = rate * 480 / 441; + epcm->pitch_target = emu10k1_calc_pitch_target(rate); + + start_addr = epcm->start_addr >> w_16; + end_addr = start_addr + runtime->period_size; + snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, w_16, + start_addr, end_addr); + start_addr >>= stereo; + epcm->ccca_start_addr = start_addr; + end_addr = start_addr + runtime->buffer_size; + snd_emu10k1_pcm_init_voices(emu, epcm->voices[0], w_16, stereo, + start_addr, end_addr, + &emu->pcm_mixer[substream->number]); + + return 0; +} + +static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm = runtime->private_data; + unsigned int start_addr; + unsigned int extra_size, channel_size; + unsigned int i; + + epcm->pitch_target = PITCH_48000; + + start_addr = epcm->start_addr >> 1; // 16-bit voices + + extra_size = runtime->period_size; + channel_size = runtime->buffer_size; + + snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, true, + start_addr, start_addr + extra_size); + + epcm->ccca_start_addr = start_addr; + for (i = 0; i < runtime->channels; i++) { + snd_emu10k1_pcm_init_voices(emu, epcm->voices[i], true, false, + start_addr, start_addr + channel_size, + &emu->efx_pcm_mixer[i]); + start_addr += channel_size; + } + + return 0; +} + +static const struct snd_pcm_hardware snd_emu10k1_efx_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = NUM_EFX_PLAYBACK, + .buffer_bytes_max = (128*1024), + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm = runtime->private_data; + int idx; + + /* zeroing the buffer size will stop capture */ + snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0); + switch (epcm->type) { + case CAPTURE_AC97ADC: + snd_emu10k1_ptr_write(emu, ADCCR, 0, 0); + break; + case CAPTURE_EFX: + if (emu->card_capabilities->emu_model) { + // The upper 32 16-bit capture voices, two for each of the 16 32-bit channels. + // The lower voices are occupied by A_EXTOUT_*_CAP*. + epcm->capture_cr_val = 0; + epcm->capture_cr_val2 = 0xffffffff >> (32 - runtime->channels * 2); + } + if (emu->audigy) { + snd_emu10k1_ptr_write_multiple(emu, 0, + A_FXWC1, 0, + A_FXWC2, 0, + REGLIST_END); + } else + snd_emu10k1_ptr_write(emu, FXWC, 0, 0); + break; + default: + break; + } + snd_emu10k1_ptr_write(emu, epcm->capture_ba_reg, 0, runtime->dma_addr); + epcm->capture_bufsize = snd_pcm_lib_buffer_bytes(substream); + epcm->capture_bs_val = 0; + for (idx = 0; idx < 31; idx++) { + if (capture_buffer_sizes[idx] == epcm->capture_bufsize) { + epcm->capture_bs_val = idx + 1; + break; + } + } + if (epcm->capture_bs_val == 0) { + snd_BUG(); + epcm->capture_bs_val++; + } + if (epcm->type == CAPTURE_AC97ADC) { + unsigned rate = runtime->rate; + if (!(runtime->hw.rates & SNDRV_PCM_RATE_48000)) + rate = rate * 480 / 441; + + epcm->capture_cr_val = emu->audigy ? A_ADCCR_LCHANENABLE : ADCCR_LCHANENABLE; + if (runtime->channels > 1) + epcm->capture_cr_val |= emu->audigy ? A_ADCCR_RCHANENABLE : ADCCR_RCHANENABLE; + epcm->capture_cr_val |= emu->audigy ? + snd_emu10k1_audigy_capture_rate_reg(rate) : + snd_emu10k1_capture_rate_reg(rate); + } + return 0; +} + +static void snd_emu10k1_playback_fill_cache(struct snd_emu10k1 *emu, + unsigned voice, + u32 sample, bool stereo) +{ + u32 ccr; + + // We assume that the cache is resting at this point (i.e., + // CCR_CACHEINVALIDSIZE is very small). + + // Clear leading frames. For simplicitly, this does too much, + // except for 16-bit stereo. And the interpolator will actually + // access them at all only when we're pitch-shifting. + for (int i = 0; i < 3; i++) + snd_emu10k1_ptr_write(emu, CD0 + i, voice, sample); + + // Fill cache + ccr = (64 - 3) << REG_SHIFT(CCR_CACHEINVALIDSIZE); + if (stereo) { + // The engine goes haywire if CCR_READADDRESS is out of sync + snd_emu10k1_ptr_write(emu, CCR, voice + 1, ccr); + } + snd_emu10k1_ptr_write(emu, CCR, voice, ccr); +} + +static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + bool w_16, bool stereo, + int channels) +{ + struct snd_pcm_substream *substream = epcm->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned eloop_start = epcm->start_addr >> w_16; + unsigned loop_start = eloop_start >> stereo; + unsigned eloop_size = runtime->period_size; + unsigned loop_size = runtime->buffer_size; + u32 sample = w_16 ? 0 : 0x80808080; + + // To make the playback actually start at the 1st frame, + // we need to compensate for two circumstances: + // - The actual position is delayed by the cache size (64 frames) + // - The interpolator is centered around the 4th frame + loop_start += (epcm->resume_pos + 64 - 3) % loop_size; + for (int i = 0; i < channels; i++) { + unsigned voice = epcm->voices[i]->number; + snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, voice, loop_start); + loop_start += loop_size; + snd_emu10k1_playback_fill_cache(emu, voice, sample, stereo); + } + + // The interrupt is triggered when CCCA_CURRADDR (CA) wraps around, + // which is ahead of the actual playback position, so the interrupt + // source needs to be delayed. + // + // In principle, this wouldn't need to be the cache's entire size - in + // practice, CCR_CACHEINVALIDSIZE (CIS) > `fetch threshold` has never + // been observed, and assuming 40 _bytes_ should be safe. + // + // The cache fills are somewhat random, which makes it impossible to + // align them with the interrupts. This makes a non-delayed interrupt + // source not practical, as the interrupt handler would have to wait + // for (CA - CIS) >= period_boundary for every channel in the stream. + // + // This is why all other (open) drivers for these chips use timer-based + // interrupts. + // + eloop_start += (epcm->resume_pos + eloop_size - 3) % eloop_size; + snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, epcm->extra->number, eloop_start); + + // It takes a moment until the cache fills complete, + // but the unmuting takes long enough for that. +} + +static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + unsigned int vattn) +{ + snd_emu10k1_ptr_write_multiple(emu, evoice->number, + VTFT, vattn | VTFT_FILTERTARGET_MASK, + CVCF, vattn | CVCF_CURRENTFILTER_MASK, + REGLIST_END); +} + +static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool stereo, bool master, + struct snd_emu10k1_pcm_mixer *mix) +{ + unsigned int vattn; + unsigned int tmp; + + tmp = stereo ? (master ? 1 : 2) : 0; + vattn = mix->attn[tmp] << 16; + snd_emu10k1_playback_commit_volume(emu, evoice, vattn); +} + +static void snd_emu10k1_playback_unmute_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool stereo, + struct snd_emu10k1_pcm_mixer *mix) +{ + snd_emu10k1_playback_unmute_voice(emu, evoice, stereo, true, mix); + if (stereo) + snd_emu10k1_playback_unmute_voice(emu, evoice + 1, true, false, mix); +} + +static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice) +{ + snd_emu10k1_playback_commit_volume(emu, evoice, 0); +} + +static void snd_emu10k1_playback_mute_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool stereo) +{ + snd_emu10k1_playback_mute_voice(emu, evoice); + if (stereo) + snd_emu10k1_playback_mute_voice(emu, evoice + 1); +} + +static void snd_emu10k1_playback_commit_pitch(struct snd_emu10k1 *emu, + u32 voice, u32 pitch_target) +{ + u32 ptrx = snd_emu10k1_ptr_read(emu, PTRX, voice); + u32 cpf = snd_emu10k1_ptr_read(emu, CPF, voice); + snd_emu10k1_ptr_write_multiple(emu, voice, + PTRX, (ptrx & ~PTRX_PITCHTARGET_MASK) | pitch_target, + CPF, (cpf & ~(CPF_CURRENTPITCH_MASK | CPF_FRACADDRESS_MASK)) | pitch_target, + REGLIST_END); +} + +static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice) +{ + unsigned int voice; + + voice = evoice->number; + snd_emu10k1_playback_commit_pitch(emu, voice, evoice->epcm->pitch_target << 16); +} + +static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice) +{ + unsigned int voice; + + voice = evoice->number; + snd_emu10k1_playback_commit_pitch(emu, voice, 0); +} + +static void snd_emu10k1_playback_set_running(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm) +{ + epcm->running = 1; + snd_emu10k1_voice_intr_enable(emu, epcm->extra->number); +} + +static void snd_emu10k1_playback_set_stopped(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm) +{ + snd_emu10k1_voice_intr_disable(emu, epcm->extra->number); + epcm->running = 0; +} + +static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm = runtime->private_data; + struct snd_emu10k1_pcm_mixer *mix; + bool w_16 = snd_pcm_format_width(runtime->format) == 16; + bool stereo = runtime->channels == 2; + + /* + dev_dbg(emu->card->dev, + "trigger - emu10k1 = 0x%x, cmd = %i, pointer = %i\n", + (int)emu, cmd, substream->ops->pointer(substream)) + */ + guard(spinlock)(&emu->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_emu10k1_playback_prepare_voices(emu, epcm, w_16, stereo, 1); + fallthrough; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + mix = &emu->pcm_mixer[substream->number]; + snd_emu10k1_playback_unmute_voices(emu, epcm->voices[0], stereo, mix); + snd_emu10k1_playback_set_running(emu, epcm); + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); + snd_emu10k1_playback_trigger_voice(emu, epcm->extra); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]); + snd_emu10k1_playback_stop_voice(emu, epcm->extra); + snd_emu10k1_playback_set_stopped(emu, epcm); + snd_emu10k1_playback_mute_voices(emu, epcm->voices[0], stereo); + break; + default: + return -EINVAL; + } + return 0; +} + +static int snd_emu10k1_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm = runtime->private_data; + + guard(spinlock)(&emu->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + /* hmm this should cause full and half full interrupt to be raised? */ + outl(epcm->capture_ipr, emu->port + IPR); + snd_emu10k1_intr_enable(emu, epcm->capture_inte); + /* + dev_dbg(emu->card->dev, "adccr = 0x%x, adcbs = 0x%x\n", + epcm->adccr, epcm->adcbs); + */ + switch (epcm->type) { + case CAPTURE_AC97ADC: + snd_emu10k1_ptr_write(emu, ADCCR, 0, epcm->capture_cr_val); + break; + case CAPTURE_EFX: + if (emu->audigy) { + snd_emu10k1_ptr_write_multiple(emu, 0, + A_FXWC1, epcm->capture_cr_val, + A_FXWC2, epcm->capture_cr_val2, + REGLIST_END); + dev_dbg(emu->card->dev, + "cr_val=0x%x, cr_val2=0x%x\n", + epcm->capture_cr_val, + epcm->capture_cr_val2); + } else + snd_emu10k1_ptr_write(emu, FXWC, 0, epcm->capture_cr_val); + break; + default: + break; + } + snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, epcm->capture_bs_val); + epcm->running = 1; + epcm->first_ptr = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + epcm->running = 0; + snd_emu10k1_intr_disable(emu, epcm->capture_inte); + outl(epcm->capture_ipr, emu->port + IPR); + snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0); + switch (epcm->type) { + case CAPTURE_AC97ADC: + snd_emu10k1_ptr_write(emu, ADCCR, 0, 0); + break; + case CAPTURE_EFX: + if (emu->audigy) { + snd_emu10k1_ptr_write_multiple(emu, 0, + A_FXWC1, 0, + A_FXWC2, 0, + REGLIST_END); + } else + snd_emu10k1_ptr_write(emu, FXWC, 0, 0); + break; + default: + break; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t snd_emu10k1_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm = runtime->private_data; + int ptr; + + if (!epcm->running) + return 0; + + ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff; + ptr -= epcm->ccca_start_addr; + + // This is the size of the whole cache minus the interpolator read-ahead, + // which leads us to the actual playback position. + // + // The cache is constantly kept mostly filled, so in principle we could + // return a more advanced position representing how far the hardware has + // already read the buffer, and set runtime->delay accordingly. However, + // this would be slightly different for every channel (and remarkably slow + // to obtain), so only a fixed worst-case value would be practical. + // + ptr -= 64 - 3; + if (ptr < 0) + ptr += runtime->buffer_size; + + /* + dev_dbg(emu->card->dev, + "ptr = 0x%lx, buffer_size = 0x%lx, period_size = 0x%lx\n", + (long)ptr, (long)runtime->buffer_size, + (long)runtime->period_size); + */ + return ptr; +} + +static u64 snd_emu10k1_efx_playback_voice_mask(struct snd_emu10k1_pcm *epcm, + int channels) +{ + u64 mask = 0; + + for (int i = 0; i < channels; i++) { + int voice = epcm->voices[i]->number; + mask |= 1ULL << voice; + } + return mask; +} + +static void snd_emu10k1_efx_playback_freeze_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + int channels) +{ + for (int i = 0; i < channels; i++) { + int voice = epcm->voices[i]->number; + snd_emu10k1_ptr_write(emu, CPF_STOP, voice, 1); + snd_emu10k1_playback_commit_pitch(emu, voice, PITCH_48000 << 16); + } +} + +static void snd_emu10k1_efx_playback_unmute_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + int channels) +{ + for (int i = 0; i < channels; i++) + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, true, + &emu->efx_pcm_mixer[i]); +} + +static void snd_emu10k1_efx_playback_stop_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + int channels) +{ + for (int i = 0; i < channels; i++) + snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]); + snd_emu10k1_playback_set_stopped(emu, epcm); + + for (int i = 0; i < channels; i++) + snd_emu10k1_playback_mute_voice(emu, epcm->voices[i]); +} + +static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm = runtime->private_data; + u64 mask; + int result = 0; + + guard(spinlock)(&emu->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + mask = snd_emu10k1_efx_playback_voice_mask( + epcm, runtime->channels); + for (int i = 0; i < 10; i++) { + // Note that the freeze is not interruptible, so we make no + // effort to reset the bits outside the error handling here. + snd_emu10k1_voice_set_loop_stop_multiple(emu, mask); + snd_emu10k1_efx_playback_freeze_voices( + emu, epcm, runtime->channels); + snd_emu10k1_playback_prepare_voices( + emu, epcm, true, false, runtime->channels); + + // It might seem to make more sense to unmute the voices only after + // they have been started, to potentially avoid torturing the speakers + // if something goes wrong. However, we cannot unmute atomically, + // which means that we'd get some mild artifacts in the regular case. + snd_emu10k1_efx_playback_unmute_voices(emu, epcm, runtime->channels); + + snd_emu10k1_playback_set_running(emu, epcm); + result = snd_emu10k1_voice_clear_loop_stop_multiple_atomic(emu, mask); + if (result == 0) { + // The extra voice is allowed to lag a bit + snd_emu10k1_playback_trigger_voice(emu, epcm->extra); + return 0; + } + + snd_emu10k1_efx_playback_stop_voices( + emu, epcm, runtime->channels); + + if (result != -EAGAIN) + break; + // The sync start can legitimately fail due to NMIs, etc. + } + snd_emu10k1_voice_clear_loop_stop_multiple(emu, mask); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + snd_emu10k1_playback_stop_voice(emu, epcm->extra); + snd_emu10k1_efx_playback_stop_voices( + emu, epcm, runtime->channels); + + epcm->resume_pos = snd_emu10k1_playback_pointer(substream); + break; + default: + return -EINVAL; + } + return result; +} + + +static snd_pcm_uframes_t snd_emu10k1_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm = runtime->private_data; + unsigned int ptr; + + if (!epcm->running) + return 0; + if (epcm->first_ptr) { + udelay(50); /* hack, it takes awhile until capture is started */ + epcm->first_ptr = 0; + } + ptr = snd_emu10k1_ptr_read(emu, epcm->capture_idx_reg, 0) & 0x0000ffff; + return bytes_to_frames(runtime, ptr); +} + +/* + * Playback support device description + */ + +static const struct snd_pcm_hardware snd_emu10k1_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_96000, + .rate_min = 4000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + * Capture support device description + */ + +static const struct snd_pcm_hardware snd_emu10k1_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_24000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (64*1024), + .period_bytes_min = 384, + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_emu10k1_capture_efx = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 16, + .buffer_bytes_max = (64*1024), + .period_bytes_min = 384, + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +/* + * + */ + +static void snd_emu10k1_pcm_mixer_notify1(struct snd_emu10k1 *emu, struct snd_kcontrol *kctl, int idx, int activate) +{ + struct snd_ctl_elem_id id; + + if (! kctl) + return; + if (activate) + kctl->vd[idx].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + else + kctl->vd[idx].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(emu->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, + snd_ctl_build_ioff(&id, kctl, idx)); +} + +static void snd_emu10k1_pcm_mixer_notify(struct snd_emu10k1 *emu, int idx, int activate) +{ + snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_send_routing, idx, activate); + snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_send_volume, idx, activate); + snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_attn, idx, activate); +} + +static void snd_emu10k1_pcm_efx_mixer_notify(struct snd_emu10k1 *emu, int idx, int activate) +{ + snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_routing, idx, activate); + snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_volume, idx, activate); + snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_attn, idx, activate); +} + +static void snd_emu10k1_pcm_free_substream(struct snd_pcm_runtime *runtime) +{ + kfree(runtime->private_data); +} + +static int snd_emu10k1_efx_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_pcm_mixer *mix; + int i; + + for (i = 0; i < NUM_EFX_PLAYBACK; i++) { + mix = &emu->efx_pcm_mixer[i]; + mix->epcm = NULL; + snd_emu10k1_pcm_efx_mixer_notify(emu, i, 0); + } + return 0; +} + +static int snd_emu10k1_playback_set_constraints(struct snd_pcm_runtime *runtime) +{ + int err; + + // The buffer size must be a multiple of the period size, to avoid a + // mismatch between the extra voice and the regular voices. + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + // The hardware is typically the cache's size of 64 frames ahead. + // Leave enough time for actually filling up the buffer. + err = snd_pcm_hw_constraint_minmax( + runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 128, UINT_MAX); + return err; +} + +static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_pcm *epcm; + struct snd_emu10k1_pcm_mixer *mix; + struct snd_pcm_runtime *runtime = substream->runtime; + int i, j, err; + + epcm = kzalloc_obj(*epcm); + if (epcm == NULL) + return -ENOMEM; + epcm->emu = emu; + epcm->type = PLAYBACK_EFX; + epcm->substream = substream; + + runtime->private_data = epcm; + runtime->private_free = snd_emu10k1_pcm_free_substream; + runtime->hw = snd_emu10k1_efx_playback; + if (emu->card_capabilities->emu_model) + snd_emu1010_constrain_efx_rate(emu, runtime); + err = snd_emu10k1_playback_set_constraints(runtime); + if (err < 0) { + kfree(epcm); + return err; + } + + for (i = 0; i < NUM_EFX_PLAYBACK; i++) { + mix = &emu->efx_pcm_mixer[i]; + for (j = 0; j < 8; j++) + mix->send_routing[0][j] = i + j; + memset(&mix->send_volume, 0, sizeof(mix->send_volume)); + mix->send_volume[0][0] = 255; + mix->attn[0] = 0x8000; + mix->epcm = epcm; + snd_emu10k1_pcm_efx_mixer_notify(emu, i, 1); + } + return 0; +} + +static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_pcm *epcm; + struct snd_emu10k1_pcm_mixer *mix; + struct snd_pcm_runtime *runtime = substream->runtime; + int i, err, sample_rate; + + epcm = kzalloc_obj(*epcm); + if (epcm == NULL) + return -ENOMEM; + epcm->emu = emu; + epcm->type = PLAYBACK_EMUVOICE; + epcm->substream = substream; + runtime->private_data = epcm; + runtime->private_free = snd_emu10k1_pcm_free_substream; + runtime->hw = snd_emu10k1_playback; + err = snd_emu10k1_playback_set_constraints(runtime); + if (err < 0) + goto free_epcm; + + if (emu->card_capabilities->emu_model) + sample_rate = emu->emu1010.word_clock; + else + sample_rate = 48000; + err = snd_pcm_hw_rule_noresample(runtime, sample_rate); + if (err < 0) + goto free_epcm; + + mix = &emu->pcm_mixer[substream->number]; + for (i = 0; i < 8; i++) + mix->send_routing[0][i] = mix->send_routing[1][i] = mix->send_routing[2][i] = i; + memset(&mix->send_volume, 0, sizeof(mix->send_volume)); + mix->send_volume[0][0] = mix->send_volume[0][1] = + mix->send_volume[1][0] = mix->send_volume[2][1] = 255; + mix->attn[0] = mix->attn[1] = mix->attn[2] = 0x8000; + mix->epcm = epcm; + snd_emu10k1_pcm_mixer_notify(emu, substream->number, 1); + return 0; + +free_epcm: + kfree(epcm); + return err; +} + +static int snd_emu10k1_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_pcm_mixer *mix = &emu->pcm_mixer[substream->number]; + + mix->epcm = NULL; + snd_emu10k1_pcm_mixer_notify(emu, substream->number, 0); + return 0; +} + +static int snd_emu10k1_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_pcm *epcm; + + epcm = kzalloc_obj(*epcm); + if (epcm == NULL) + return -ENOMEM; + epcm->emu = emu; + epcm->type = CAPTURE_AC97ADC; + epcm->substream = substream; + epcm->capture_ipr = IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL; + epcm->capture_inte = INTE_ADCBUFENABLE; + epcm->capture_ba_reg = ADCBA; + epcm->capture_bs_reg = ADCBS; + epcm->capture_idx_reg = emu->audigy ? A_ADCIDX : ADCIDX; + runtime->private_data = epcm; + runtime->private_free = snd_emu10k1_pcm_free_substream; + runtime->hw = snd_emu10k1_capture; + snd_emu10k1_constrain_capture_rates(emu, runtime); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &hw_constraints_capture_buffer_sizes); + emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt; + emu->pcm_capture_substream = substream; + return 0; +} + +static int snd_emu10k1_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + + emu->capture_interrupt = NULL; + emu->pcm_capture_substream = NULL; + return 0; +} + +static int snd_emu10k1_capture_mic_open(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_pcm *epcm; + struct snd_pcm_runtime *runtime = substream->runtime; + + epcm = kzalloc_obj(*epcm); + if (epcm == NULL) + return -ENOMEM; + epcm->emu = emu; + epcm->type = CAPTURE_AC97MIC; + epcm->substream = substream; + epcm->capture_ipr = IPR_MICBUFFULL|IPR_MICBUFHALFFULL; + epcm->capture_inte = INTE_MICBUFENABLE; + epcm->capture_ba_reg = MICBA; + epcm->capture_bs_reg = MICBS; + epcm->capture_idx_reg = emu->audigy ? A_MICIDX : MICIDX; + substream->runtime->private_data = epcm; + substream->runtime->private_free = snd_emu10k1_pcm_free_substream; + runtime->hw = snd_emu10k1_capture; + runtime->hw.rates = SNDRV_PCM_RATE_8000; + runtime->hw.rate_min = runtime->hw.rate_max = 8000; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &hw_constraints_capture_buffer_sizes); + emu->capture_mic_interrupt = snd_emu10k1_pcm_ac97mic_interrupt; + emu->pcm_capture_mic_substream = substream; + return 0; +} + +static int snd_emu10k1_capture_mic_close(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + + emu->capture_mic_interrupt = NULL; + emu->pcm_capture_mic_substream = NULL; + return 0; +} + +static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_pcm *epcm; + struct snd_pcm_runtime *runtime = substream->runtime; + int nefx = emu->audigy ? 64 : 32; + int idx, err; + + epcm = kzalloc_obj(*epcm); + if (epcm == NULL) + return -ENOMEM; + epcm->emu = emu; + epcm->type = CAPTURE_EFX; + epcm->substream = substream; + epcm->capture_ipr = IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL; + epcm->capture_inte = INTE_EFXBUFENABLE; + epcm->capture_ba_reg = FXBA; + epcm->capture_bs_reg = FXBS; + epcm->capture_idx_reg = FXIDX; + substream->runtime->private_data = epcm; + substream->runtime->private_free = snd_emu10k1_pcm_free_substream; + runtime->hw = snd_emu10k1_capture_efx; + if (emu->card_capabilities->emu_model) { + snd_emu1010_constrain_efx_rate(emu, runtime); + /* + * There are 32 mono channels of 16bits each. + * 24bit Audio uses 2x channels over 16bit, + * 96kHz uses 2x channels over 48kHz, + * 192kHz uses 4x channels over 48kHz. + * So, for 48kHz 24bit, one has 16 channels, + * for 96kHz 24bit, one has 8 channels, + * for 192kHz 24bit, one has 4 channels. + * 1010rev2 and 1616(m) cards have double that, + * but we don't exceed 16 channels anyway. + */ +#if 0 + /* For 96kHz */ + runtime->hw.channels_min = runtime->hw.channels_max = 4; +#endif +#if 0 + /* For 192kHz */ + runtime->hw.channels_min = runtime->hw.channels_max = 2; +#endif + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; + } else { + guard(spinlock_irq)(&emu->reg_lock); + runtime->hw.channels_min = runtime->hw.channels_max = 0; + for (idx = 0; idx < nefx; idx++) { + if (emu->efx_voices_mask[idx/32] & (1 << (idx%32))) { + runtime->hw.channels_min++; + runtime->hw.channels_max++; + } + } + epcm->capture_cr_val = emu->efx_voices_mask[0]; + epcm->capture_cr_val2 = emu->efx_voices_mask[1]; + } + err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_constraints_efx_capture_channels); + if (err < 0) { + kfree(epcm); + return err; + } + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &hw_constraints_capture_buffer_sizes); + emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt; + emu->pcm_capture_efx_substream = substream; + return 0; +} + +static int snd_emu10k1_capture_efx_close(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + + emu->capture_efx_interrupt = NULL; + emu->pcm_capture_efx_substream = NULL; + return 0; +} + +static const struct snd_pcm_ops snd_emu10k1_playback_ops = { + .open = snd_emu10k1_playback_open, + .close = snd_emu10k1_playback_close, + .hw_params = snd_emu10k1_playback_hw_params, + .hw_free = snd_emu10k1_playback_hw_free, + .prepare = snd_emu10k1_playback_prepare, + .trigger = snd_emu10k1_playback_trigger, + .pointer = snd_emu10k1_playback_pointer, +}; + +static const struct snd_pcm_ops snd_emu10k1_capture_ops = { + .open = snd_emu10k1_capture_open, + .close = snd_emu10k1_capture_close, + .prepare = snd_emu10k1_capture_prepare, + .trigger = snd_emu10k1_capture_trigger, + .pointer = snd_emu10k1_capture_pointer, +}; + +/* EFX playback */ +static const struct snd_pcm_ops snd_emu10k1_efx_playback_ops = { + .open = snd_emu10k1_efx_playback_open, + .close = snd_emu10k1_efx_playback_close, + .hw_params = snd_emu10k1_playback_hw_params, + .hw_free = snd_emu10k1_playback_hw_free, + .prepare = snd_emu10k1_efx_playback_prepare, + .trigger = snd_emu10k1_efx_playback_trigger, + .pointer = snd_emu10k1_playback_pointer, +}; + +int snd_emu10k1_pcm(struct snd_emu10k1 *emu, int device) +{ + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_new(emu->card, "emu10k1", device, 32, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = emu; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_ops); + + pcm->info_flags = 0; + pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + strscpy(pcm->name, "ADC Capture/Standard PCM Playback"); + emu->pcm = pcm; + + /* playback substream can't use managed buffers due to alignment */ + for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) + snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, + &emu->pci->dev, + 64*1024, 64*1024); + + for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next) + snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV, + &emu->pci->dev, 64*1024, 64*1024); + + return 0; +} + +int snd_emu10k1_pcm_multi(struct snd_emu10k1 *emu, int device) +{ + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_new(emu->card, "emu10k1", device, 1, 0, &pcm); + if (err < 0) + return err; + + pcm->private_data = emu; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_efx_playback_ops); + + pcm->info_flags = 0; + pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + strscpy(pcm->name, "Multichannel Playback"); + emu->pcm_multi = pcm; + + for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) + snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, + &emu->pci->dev, + 64*1024, 64*1024); + + return 0; +} + + +static const struct snd_pcm_ops snd_emu10k1_capture_mic_ops = { + .open = snd_emu10k1_capture_mic_open, + .close = snd_emu10k1_capture_mic_close, + .prepare = snd_emu10k1_capture_prepare, + .trigger = snd_emu10k1_capture_trigger, + .pointer = snd_emu10k1_capture_pointer, +}; + +int snd_emu10k1_pcm_mic(struct snd_emu10k1 *emu, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(emu->card, "emu10k1 mic", device, 0, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = emu; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_mic_ops); + + pcm->info_flags = 0; + strscpy(pcm->name, "Mic Capture"); + emu->pcm_mic = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &emu->pci->dev, + 64*1024, 64*1024); + + return 0; +} + +static int snd_emu10k1_pcm_efx_voices_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + int nefx = emu->audigy ? 64 : 32; + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = nefx; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_emu10k1_pcm_efx_voices_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + int nefx = emu->audigy ? 64 : 32; + int idx; + + for (idx = 0; idx < nefx; idx++) + ucontrol->value.integer.value[idx] = (emu->efx_voices_mask[idx / 32] & (1 << (idx % 32))) ? 1 : 0; + return 0; +} + +static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int nval[2], bits; + int nefx = emu->audigy ? 64 : 32; + int change, idx; + + nval[0] = nval[1] = 0; + for (idx = 0, bits = 0; idx < nefx; idx++) + if (ucontrol->value.integer.value[idx]) { + nval[idx / 32] |= 1 << (idx % 32); + bits++; + } + + if (bits == 9 || bits == 11 || bits == 13 || bits == 15 || bits > 16) + return -EINVAL; + + guard(spinlock_irq)(&emu->reg_lock); + change = (nval[0] != emu->efx_voices_mask[0]) || + (nval[1] != emu->efx_voices_mask[1]); + emu->efx_voices_mask[0] = nval[0]; + emu->efx_voices_mask[1] = nval[1]; + return change; +} + +static const struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Captured FX8010 Outputs", + .info = snd_emu10k1_pcm_efx_voices_mask_info, + .get = snd_emu10k1_pcm_efx_voices_mask_get, + .put = snd_emu10k1_pcm_efx_voices_mask_put +}; + +static const struct snd_pcm_ops snd_emu10k1_capture_efx_ops = { + .open = snd_emu10k1_capture_efx_open, + .close = snd_emu10k1_capture_efx_close, + .prepare = snd_emu10k1_capture_prepare, + .trigger = snd_emu10k1_capture_trigger, + .pointer = snd_emu10k1_capture_pointer, +}; + + +/* EFX playback */ + +#define INITIAL_TRAM_SHIFT 14 +#define INITIAL_TRAM_POS(size) ((((size) / 2) - INITIAL_TRAM_SHIFT) - 1) + +static void snd_emu10k1_fx8010_playback_irq(struct snd_emu10k1 *emu, void *private_data) +{ + struct snd_pcm_substream *substream = private_data; + snd_pcm_period_elapsed(substream); +} + +static void snd_emu10k1_fx8010_playback_tram_poke1(unsigned short *dst_left, + unsigned short *dst_right, + unsigned short *src, + unsigned int count, + unsigned int tram_shift) +{ + /* + dev_dbg(emu->card->dev, + "tram_poke1: dst_left = 0x%p, dst_right = 0x%p, " + "src = 0x%p, count = 0x%x\n", + dst_left, dst_right, src, count); + */ + if ((tram_shift & 1) == 0) { + while (count--) { + *dst_left-- = *src++; + *dst_right-- = *src++; + } + } else { + while (count--) { + *dst_right-- = *src++; + *dst_left-- = *src++; + } + } +} + +static void fx8010_pb_trans_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect *rec, size_t bytes) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number]; + unsigned int tram_size = pcm->buffer_size; + unsigned short *src = (unsigned short *)(substream->runtime->dma_area + rec->sw_data); + unsigned int frames = bytes >> 2, count; + unsigned int tram_pos = pcm->tram_pos; + unsigned int tram_shift = pcm->tram_shift; + + while (frames > tram_pos) { + count = tram_pos + 1; + snd_emu10k1_fx8010_playback_tram_poke1((unsigned short *)emu->fx8010.etram_pages.area + tram_pos, + (unsigned short *)emu->fx8010.etram_pages.area + tram_pos + tram_size / 2, + src, count, tram_shift); + src += count * 2; + frames -= count; + tram_pos = (tram_size / 2) - 1; + tram_shift++; + } + snd_emu10k1_fx8010_playback_tram_poke1((unsigned short *)emu->fx8010.etram_pages.area + tram_pos, + (unsigned short *)emu->fx8010.etram_pages.area + tram_pos + tram_size / 2, + src, frames, tram_shift); + tram_pos -= frames; + pcm->tram_pos = tram_pos; + pcm->tram_shift = tram_shift; +} + +static int snd_emu10k1_fx8010_playback_transfer(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number]; + + return snd_pcm_indirect_playback_transfer(substream, &pcm->pcm_rec, + fx8010_pb_trans_copy); +} + +static int snd_emu10k1_fx8010_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number]; + unsigned int i; + + for (i = 0; i < pcm->channels; i++) + snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, 0); + return 0; +} + +static int snd_emu10k1_fx8010_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number]; + unsigned int i; + + /* + dev_dbg(emu->card->dev, "prepare: etram_pages = 0x%p, dma_area = 0x%x, " + "buffer_size = 0x%x (0x%x)\n", + emu->fx8010.etram_pages, runtime->dma_area, + runtime->buffer_size, runtime->buffer_size << 2); + */ + memset(&pcm->pcm_rec, 0, sizeof(pcm->pcm_rec)); + pcm->pcm_rec.hw_buffer_size = pcm->buffer_size * 2; /* byte size */ + pcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream); + pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size); + pcm->tram_shift = 0; + snd_emu10k1_ptr_write_multiple(emu, 0, + emu->gpr_base + pcm->gpr_running, 0, /* reset */ + emu->gpr_base + pcm->gpr_trigger, 0, /* reset */ + emu->gpr_base + pcm->gpr_size, runtime->buffer_size, + emu->gpr_base + pcm->gpr_ptr, 0, /* reset ptr number */ + emu->gpr_base + pcm->gpr_count, runtime->period_size, + emu->gpr_base + pcm->gpr_tmpcount, runtime->period_size, + REGLIST_END); + for (i = 0; i < pcm->channels; i++) + snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, (TANKMEMADDRREG_READ|TANKMEMADDRREG_ALIGN) + i * (runtime->buffer_size / pcm->channels)); + return 0; +} + +static int snd_emu10k1_fx8010_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number]; + int result; + + guard(spinlock)(&emu->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* follow thru */ + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: +#ifdef EMU10K1_SET_AC3_IEC958 + { + int i; + for (i = 0; i < 3; i++) { + unsigned int bits; + bits = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | SPCS_GENERATIONSTATUS | + 0x00001200 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT | SPCS_NOTAUDIODATA; + snd_emu10k1_ptr_write(emu, SPCS0 + i, 0, bits); + } + } +#endif + result = snd_emu10k1_fx8010_register_irq_handler(emu, snd_emu10k1_fx8010_playback_irq, pcm->gpr_running, substream, &pcm->irq); + if (result < 0) + return result; + snd_emu10k1_fx8010_playback_transfer(substream); /* roll the ball */ + snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + snd_emu10k1_fx8010_unregister_irq_handler(emu, &pcm->irq); + snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0); + pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size); + pcm->tram_shift = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t snd_emu10k1_fx8010_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number]; + size_t ptr; /* byte pointer */ + + if (!snd_emu10k1_ptr_read(emu, emu->gpr_base + pcm->gpr_trigger, 0)) + return 0; + ptr = snd_emu10k1_ptr_read(emu, emu->gpr_base + pcm->gpr_ptr, 0) << 2; + return snd_pcm_indirect_playback_pointer(substream, &pcm->pcm_rec, ptr); +} + +static const struct snd_pcm_hardware snd_emu10k1_fx8010_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + /* SNDRV_PCM_INFO_MMAP_VALID | */ SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_APPLPTR), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 1024, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_emu10k1_fx8010_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number]; + + runtime->hw = snd_emu10k1_fx8010_playback; + runtime->hw.channels_min = runtime->hw.channels_max = pcm->channels; + runtime->hw.period_bytes_max = (pcm->buffer_size * 2) / 2; + guard(spinlock_irq)(&emu->reg_lock); + if (pcm->valid == 0) + return -ENODEV; + pcm->opened = 1; + return 0; +} + +static int snd_emu10k1_fx8010_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number]; + + guard(spinlock_irq)(&emu->reg_lock); + pcm->opened = 0; + return 0; +} + +static const struct snd_pcm_ops snd_emu10k1_fx8010_playback_ops = { + .open = snd_emu10k1_fx8010_playback_open, + .close = snd_emu10k1_fx8010_playback_close, + .hw_free = snd_emu10k1_fx8010_playback_hw_free, + .prepare = snd_emu10k1_fx8010_playback_prepare, + .trigger = snd_emu10k1_fx8010_playback_trigger, + .pointer = snd_emu10k1_fx8010_playback_pointer, + .ack = snd_emu10k1_fx8010_playback_transfer, +}; + +int snd_emu10k1_pcm_efx(struct snd_emu10k1 *emu, int device) +{ + struct snd_pcm *pcm; + struct snd_kcontrol *kctl; + int err; + + err = snd_pcm_new(emu->card, "emu10k1 efx", device, emu->audigy ? 0 : 8, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = emu; + + if (!emu->audigy) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_fx8010_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_efx_ops); + + pcm->info_flags = 0; + if (emu->audigy) + strscpy(pcm->name, "Multichannel Capture"); + else + strscpy(pcm->name, "Multichannel Capture/PT Playback"); + emu->pcm_efx = pcm; + + if (!emu->card_capabilities->emu_model) { + // On Sound Blasters, the DSP code copies the EXTINs to FXBUS2. + // The mask determines which of these and the EXTOUTs the multi- + // channel capture actually records (the channel order is fixed). + if (emu->audigy) { + emu->efx_voices_mask[0] = 0; + emu->efx_voices_mask[1] = 0xffff; + } else { + emu->efx_voices_mask[0] = 0xffff0000; + emu->efx_voices_mask[1] = 0; + } + kctl = snd_ctl_new1(&snd_emu10k1_pcm_efx_voices_mask, emu); + if (!kctl) + return -ENOMEM; + kctl->id.device = device; + err = snd_ctl_add(emu->card, kctl); + if (err < 0) + return err; + } else { + // On E-MU cards, the DSP code copies the P16VINs/EMU32INs to + // FXBUS2. These are already selected & routed by the FPGA, + // so there is no need to apply additional masking. + } + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &emu->pci->dev, + 64*1024, 64*1024); + + return 0; +} diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c new file mode 100644 index 000000000..f6186b5be --- /dev/null +++ b/sound/pci/emu10k1/emuproc.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Lee Revell + * James Courtier-Dutton + * Oswald Buddenhagen + * Creative Labs, Inc. + * + * Routines for control of EMU10K1 chips / proc interface routines + */ + +#include +#include +#include +#include +#include +#include "p16v.h" + +static void snd_emu10k1_proc_spdif_status(struct snd_emu10k1 * emu, + struct snd_info_buffer *buffer, + char *title, + int status_reg, + int rate_reg) +{ + static const char * const clkaccy[4] = { "1000ppm", "50ppm", "variable", "unknown" }; + static const int samplerate[16] = { 44100, 1, 48000, 32000, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + static const char * const channel[16] = { "unspec", "left", "right", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" }; + static const char * const emphasis[8] = { "none", "50/15 usec 2 channel", "2", "3", "4", "5", "6", "7" }; + unsigned int status, rate = 0; + + status = snd_emu10k1_ptr_read(emu, status_reg, 0); + + snd_iprintf(buffer, "\n%s\n", title); + + if (status != 0xffffffff) { + snd_iprintf(buffer, "Professional Mode : %s\n", str_yes_no(status & SPCS_PROFESSIONAL)); + snd_iprintf(buffer, "Not Audio Data : %s\n", str_yes_no(status & SPCS_NOTAUDIODATA)); + snd_iprintf(buffer, "Copyright : %s\n", str_yes_no(status & SPCS_COPYRIGHT)); + snd_iprintf(buffer, "Emphasis : %s\n", emphasis[(status & SPCS_EMPHASISMASK) >> 3]); + snd_iprintf(buffer, "Mode : %i\n", (status & SPCS_MODEMASK) >> 6); + snd_iprintf(buffer, "Category Code : 0x%x\n", (status & SPCS_CATEGORYCODEMASK) >> 8); + snd_iprintf(buffer, "Generation Status : %s\n", status & SPCS_GENERATIONSTATUS ? "original" : "copy"); + snd_iprintf(buffer, "Source Mask : %i\n", (status & SPCS_SOURCENUMMASK) >> 16); + snd_iprintf(buffer, "Channel Number : %s\n", channel[(status & SPCS_CHANNELNUMMASK) >> 20]); + snd_iprintf(buffer, "Sample Rate : %iHz\n", samplerate[(status & SPCS_SAMPLERATEMASK) >> 24]); + snd_iprintf(buffer, "Clock Accuracy : %s\n", clkaccy[(status & SPCS_CLKACCYMASK) >> 28]); + + if (rate_reg > 0) { + rate = snd_emu10k1_ptr_read(emu, rate_reg, 0); + snd_iprintf(buffer, "S/PDIF Valid : %s\n", str_on_off(rate & SRCS_SPDIFVALID)); + snd_iprintf(buffer, "S/PDIF Locked : %s\n", str_on_off(rate & SRCS_SPDIFLOCKED)); + snd_iprintf(buffer, "Rate Locked : %s\n", str_on_off(rate & SRCS_RATELOCKED)); + /* From ((Rate * 48000 ) / 262144); */ + snd_iprintf(buffer, "Estimated Sample Rate : %d\n", ((rate & 0xFFFFF ) * 375) >> 11); + } + } else { + snd_iprintf(buffer, "No signal detected.\n"); + } + +} + +static void snd_emu10k1_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_emu10k1 *emu = entry->private_data; + const char * const *inputs = emu->audigy ? + snd_emu10k1_audigy_ins : snd_emu10k1_sblive_ins; + const char * const *outputs = emu->audigy ? + snd_emu10k1_audigy_outs : snd_emu10k1_sblive_outs; + unsigned short extin_mask = emu->audigy ? ~0 : emu->fx8010.extin_mask; + unsigned short extout_mask = emu->audigy ? ~0 : emu->fx8010.extout_mask; + unsigned int val, val1, ptrx, psst, dsl, snda; + int nefx = emu->audigy ? 32 : 16; + int idx; + + snd_iprintf(buffer, "EMU10K1\n\n"); + snd_iprintf(buffer, "Card : %s\n", + emu->card_capabilities->emu_model ? "E-MU D.A.S." : + emu->card_capabilities->ecard ? "E-MU A.P.S." : + emu->audigy ? "SB Audigy" : "SB Live!"); + snd_iprintf(buffer, "Internal TRAM (words) : 0x%x\n", emu->fx8010.itram_size); + snd_iprintf(buffer, "External TRAM (words) : 0x%x\n", (int)emu->fx8010.etram_pages.bytes / 2); + + snd_iprintf(buffer, "\nEffect Send Routing & Amounts:\n"); + for (idx = 0; idx < NUM_G; idx++) { + ptrx = snd_emu10k1_ptr_read(emu, PTRX, idx); + psst = snd_emu10k1_ptr_read(emu, PSST, idx); + dsl = snd_emu10k1_ptr_read(emu, DSL, idx); + if (emu->audigy) { + val = snd_emu10k1_ptr_read(emu, A_FXRT1, idx); + val1 = snd_emu10k1_ptr_read(emu, A_FXRT2, idx); + snda = snd_emu10k1_ptr_read(emu, A_SENDAMOUNTS, idx); + snd_iprintf(buffer, "Ch%-2i: A=%2i:%02x, B=%2i:%02x, C=%2i:%02x, D=%2i:%02x, ", + idx, + val & 0x3f, REG_VAL_GET(PTRX_FXSENDAMOUNT_A, ptrx), + (val >> 8) & 0x3f, REG_VAL_GET(PTRX_FXSENDAMOUNT_B, ptrx), + (val >> 16) & 0x3f, REG_VAL_GET(PSST_FXSENDAMOUNT_C, psst), + (val >> 24) & 0x3f, REG_VAL_GET(DSL_FXSENDAMOUNT_D, dsl)); + snd_iprintf(buffer, "E=%2i:%02x, F=%2i:%02x, G=%2i:%02x, H=%2i:%02x\n", + val1 & 0x3f, (snda >> 24) & 0xff, + (val1 >> 8) & 0x3f, (snda >> 16) & 0xff, + (val1 >> 16) & 0x3f, (snda >> 8) & 0xff, + (val1 >> 24) & 0x3f, snda & 0xff); + } else { + val = snd_emu10k1_ptr_read(emu, FXRT, idx); + snd_iprintf(buffer, "Ch%-2i: A=%2i:%02x, B=%2i:%02x, C=%2i:%02x, D=%2i:%02x\n", + idx, + (val >> 16) & 0x0f, REG_VAL_GET(PTRX_FXSENDAMOUNT_A, ptrx), + (val >> 20) & 0x0f, REG_VAL_GET(PTRX_FXSENDAMOUNT_B, ptrx), + (val >> 24) & 0x0f, REG_VAL_GET(PSST_FXSENDAMOUNT_C, psst), + (val >> 28) & 0x0f, REG_VAL_GET(DSL_FXSENDAMOUNT_D, dsl)); + } + } + snd_iprintf(buffer, "\nEffect Send Targets:\n"); + // Audigy actually has 64, but we don't use them all. + for (idx = 0; idx < 32; idx++) { + const char *c = snd_emu10k1_fxbus[idx]; + if (c) + snd_iprintf(buffer, " Channel %02i [%s]\n", idx, c); + } + if (!emu->card_capabilities->emu_model) { + snd_iprintf(buffer, "\nOutput Channels:\n"); + for (idx = 0; idx < 32; idx++) + if (outputs[idx] && (extout_mask & (1 << idx))) + snd_iprintf(buffer, " Channel %02i [%s]\n", idx, outputs[idx]); + snd_iprintf(buffer, "\nInput Channels:\n"); + for (idx = 0; idx < 16; idx++) + if (inputs[idx] && (extin_mask & (1 << idx))) + snd_iprintf(buffer, " Channel %02i [%s]\n", idx, inputs[idx]); + snd_iprintf(buffer, "\nMultichannel Capture Sources:\n"); + for (idx = 0; idx < nefx; idx++) + if (emu->efx_voices_mask[0] & (1 << idx)) + snd_iprintf(buffer, " Channel %02i [Output: %s]\n", + idx, outputs[idx] ? outputs[idx] : "???"); + if (emu->audigy) { + for (idx = 0; idx < 32; idx++) + if (emu->efx_voices_mask[1] & (1 << idx)) + snd_iprintf(buffer, " Channel %02i [Input: %s]\n", + idx + 32, inputs[idx] ? inputs[idx] : "???"); + } else { + for (idx = 0; idx < 16; idx++) { + if (emu->efx_voices_mask[0] & ((1 << 16) << idx)) { + if (emu->card_capabilities->sblive51) { + s8 c = snd_emu10k1_sblive51_fxbus2_map[idx]; + if (c == -1) + snd_iprintf(buffer, " Channel %02i [Output: %s]\n", + idx + 16, outputs[idx + 16]); + else + snd_iprintf(buffer, " Channel %02i [Input: %s]\n", + idx + 16, inputs[c]); + } else { + snd_iprintf(buffer, " Channel %02i [Input: %s]\n", + idx + 16, inputs[idx] ? inputs[idx] : "???"); + } + } + } + } + } +} + +static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_emu10k1 *emu = entry->private_data; + u32 value; + u32 value2; + + if (emu->card_capabilities->emu_model) { + guard(snd_emu1010_fpga_lock)(emu); + + // This represents the S/PDIF lock status on 0404b, which is + // kinda weird and unhelpful, because monitoring it via IRQ is + // impractical (one gets an IRQ flood as long as it is desynced). + snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, &value); + snd_iprintf(buffer, "Lock status 1: %#x\n", value & 0x10); + + // Bit 0x1 in LO being 0 is supposedly for ADAT lock. + // The registers are always all zero on 0404b. + snd_emu1010_fpga_read(emu, EMU_HANA_LOCK_STS_LO, &value); + snd_emu1010_fpga_read(emu, EMU_HANA_LOCK_STS_HI, &value2); + snd_iprintf(buffer, "Lock status 2: %#x %#x\n", value, value2); + + snd_iprintf(buffer, "S/PDIF rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_HANA_SPDIF_IN)); + if (emu->card_capabilities->emu_model != EMU_MODEL_EMU0404) { + snd_iprintf(buffer, "ADAT rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_HANA_ADAT_IN)); + snd_iprintf(buffer, "Dock rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_2ND_HANA)); + } + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU0404 || + emu->card_capabilities->emu_model == EMU_MODEL_EMU1010) + snd_iprintf(buffer, "BNC rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_SYNC_BNC)); + + snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &value); + if (value & EMU_HANA_SPDIF_MODE_RX_INVALID) + snd_iprintf(buffer, "\nS/PDIF input invalid\n"); + else + snd_iprintf(buffer, "\nS/PDIF mode: %s%s\n", + value & EMU_HANA_SPDIF_MODE_RX_PRO ? "professional" : "consumer", + value & EMU_HANA_SPDIF_MODE_RX_NOCOPY ? ", no copy" : ""); + } else { + snd_emu10k1_proc_spdif_status(emu, buffer, "CD-ROM S/PDIF In", CDCS, CDSRCS); + snd_emu10k1_proc_spdif_status(emu, buffer, "Optical or Coax S/PDIF In", GPSCS, GPSRCS); + } +#if 0 + val = snd_emu10k1_ptr_read(emu, ZVSRCS, 0); + snd_iprintf(buffer, "\nZoomed Video\n"); + snd_iprintf(buffer, "Rate Locked : %s\n", str_on_off(val & SRCS_RATELOCKED)); + snd_iprintf(buffer, "Estimated Sample Rate : 0x%x\n", val & SRCS_ESTSAMPLERATE); +#endif +} + +static void snd_emu10k1_proc_rates_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const int samplerate[8] = { 44100, 48000, 96000, 192000, 4, 5, 6, 7 }; + struct snd_emu10k1 *emu = entry->private_data; + unsigned int val, tmp, n; + val = snd_emu10k1_ptr20_read(emu, CAPTURE_RATE_STATUS, 0); + for (n = 0; n < 4; n++) { + tmp = val >> (16 + (n*4)); + if (tmp & 0x8) snd_iprintf(buffer, "Channel %d: Rate=%d\n", n, samplerate[tmp & 0x7]); + else snd_iprintf(buffer, "Channel %d: No input\n", n); + } +} + +struct emu10k1_reg_entry { + unsigned short base, size; + const char *name; +}; + +static const struct emu10k1_reg_entry sblive_reg_entries[] = { + { 0, 0x10, "FXBUS" }, + { 0x10, 0x10, "EXTIN" }, + { 0x20, 0x10, "EXTOUT" }, + { 0x30, 0x10, "FXBUS2" }, + { 0x40, 0x20, NULL }, // Constants + { 0x100, 0x100, "GPR" }, + { 0x200, 0x80, "ITRAM_DATA" }, + { 0x280, 0x20, "ETRAM_DATA" }, + { 0x300, 0x80, "ITRAM_ADDR" }, + { 0x380, 0x20, "ETRAM_ADDR" }, + { 0x400, 0, NULL } +}; + +static const struct emu10k1_reg_entry audigy_reg_entries[] = { + { 0, 0x40, "FXBUS" }, + { 0x40, 0x10, "EXTIN" }, + { 0x50, 0x10, "P16VIN" }, + { 0x60, 0x20, "EXTOUT" }, + { 0x80, 0x20, "FXBUS2" }, + { 0xa0, 0x10, "EMU32OUTH" }, + { 0xb0, 0x10, "EMU32OUTL" }, + { 0xc0, 0x20, NULL }, // Constants + // This can't be quite right - overlap. + //{ 0x100, 0xc0, "ITRAM_CTL" }, + //{ 0x1c0, 0x40, "ETRAM_CTL" }, + { 0x160, 0x20, "A3_EMU32IN" }, + { 0x1e0, 0x20, "A3_EMU32OUT" }, + { 0x200, 0xc0, "ITRAM_DATA" }, + { 0x2c0, 0x40, "ETRAM_DATA" }, + { 0x300, 0xc0, "ITRAM_ADDR" }, + { 0x3c0, 0x40, "ETRAM_ADDR" }, + { 0x400, 0x200, "GPR" }, + { 0x600, 0, NULL } +}; + +static const char * const emu10k1_const_entries[] = { + "C_00000000", + "C_00000001", + "C_00000002", + "C_00000003", + "C_00000004", + "C_00000008", + "C_00000010", + "C_00000020", + "C_00000100", + "C_00010000", + "C_00000800", + "C_10000000", + "C_20000000", + "C_40000000", + "C_80000000", + "C_7fffffff", + "C_ffffffff", + "C_fffffffe", + "C_c0000000", + "C_4f1bbcdc", + "C_5a7ef9db", + "C_00100000", + "GPR_ACCU", + "GPR_COND", + "GPR_NOISE0", + "GPR_NOISE1", + "GPR_IRQ", + "GPR_DBAC", + "GPR_DBACE", + "???", +}; + +static int disasm_emu10k1_reg(char *buffer, + const struct emu10k1_reg_entry *entries, + unsigned reg, const char *pfx) +{ + for (int i = 0; ; i++) { + unsigned base = entries[i].base; + unsigned size = entries[i].size; + if (!size) + return sprintf(buffer, "%s0x%03x", pfx, reg); + if (reg >= base && reg < base + size) { + const char *name = entries[i].name; + reg -= base; + if (name) + return sprintf(buffer, "%s%s(%u)", pfx, name, reg); + return sprintf(buffer, "%s%s", pfx, emu10k1_const_entries[reg]); + } + } +} + +static int disasm_sblive_reg(char *buffer, unsigned reg, const char *pfx) +{ + return disasm_emu10k1_reg(buffer, sblive_reg_entries, reg, pfx); +} + +static int disasm_audigy_reg(char *buffer, unsigned reg, const char *pfx) +{ + return disasm_emu10k1_reg(buffer, audigy_reg_entries, reg, pfx); +} + +static void snd_emu10k1_proc_acode_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + u32 pc; + struct snd_emu10k1 *emu = entry->private_data; + static const char * const insns[16] = { + "MAC0", "MAC1", "MAC2", "MAC3", "MACINT0", "MACINT1", "ACC3", "MACMV", + "ANDXOR", "TSTNEG", "LIMITGE", "LIMITLT", "LOG", "EXP", "INTERP", "SKIP", + }; + static const char spaces[] = " "; + const int nspaces = sizeof(spaces) - 1; + + snd_iprintf(buffer, "FX8010 Instruction List '%s'\n", emu->fx8010.name); + snd_iprintf(buffer, " Code dump :\n"); + for (pc = 0; pc < (emu->audigy ? 1024 : 512); pc++) { + u32 low, high; + int len; + char buf[100]; + char *bufp = buf; + + low = snd_emu10k1_efx_read(emu, pc * 2); + high = snd_emu10k1_efx_read(emu, pc * 2 + 1); + if (emu->audigy) { + bufp += sprintf(bufp, " %-7s ", insns[(high >> 24) & 0x0f]); + bufp += disasm_audigy_reg(bufp, (high >> 12) & 0x7ff, ""); + bufp += disasm_audigy_reg(bufp, (high >> 0) & 0x7ff, ", "); + bufp += disasm_audigy_reg(bufp, (low >> 12) & 0x7ff, ", "); + bufp += disasm_audigy_reg(bufp, (low >> 0) & 0x7ff, ", "); + } else { + bufp += sprintf(bufp, " %-7s ", insns[(high >> 20) & 0x0f]); + bufp += disasm_sblive_reg(bufp, (high >> 10) & 0x3ff, ""); + bufp += disasm_sblive_reg(bufp, (high >> 0) & 0x3ff, ", "); + bufp += disasm_sblive_reg(bufp, (low >> 10) & 0x3ff, ", "); + bufp += disasm_sblive_reg(bufp, (low >> 0) & 0x3ff, ", "); + } + len = (int)(ptrdiff_t)(bufp - buf); + snd_iprintf(buffer, "%s %s /* 0x%04x: 0x%08x%08x */\n", + buf, &spaces[nspaces - clamp(65 - len, 0, nspaces)], + pc, high, low); + } +} + +#define TOTAL_SIZE_GPR (0x100*4) +#define A_TOTAL_SIZE_GPR (0x200*4) +#define TOTAL_SIZE_TANKMEM_DATA (0xa0*4) +#define TOTAL_SIZE_TANKMEM_ADDR (0xa0*4) +#define A_TOTAL_SIZE_TANKMEM_DATA (0x100*4) +#define A_TOTAL_SIZE_TANKMEM_ADDR (0x100*4) +#define TOTAL_SIZE_CODE (0x200*8) +#define A_TOTAL_SIZE_CODE (0x400*8) + +static ssize_t snd_emu10k1_fx8010_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) +{ + struct snd_emu10k1 *emu = entry->private_data; + unsigned int offset; + int tram_addr = 0; + unsigned int *tmp; + long res; + unsigned int idx; + + if (!strcmp(entry->name, "fx8010_tram_addr")) { + offset = TANKMEMADDRREGBASE; + tram_addr = 1; + } else if (!strcmp(entry->name, "fx8010_tram_data")) { + offset = TANKMEMDATAREGBASE; + } else if (!strcmp(entry->name, "fx8010_code")) { + offset = emu->audigy ? A_MICROCODEBASE : MICROCODEBASE; + } else { + offset = emu->audigy ? A_FXGPREGBASE : FXGPREGBASE; + } + + tmp = kmalloc(count + 8, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + for (idx = 0; idx < ((pos & 3) + count + 3) >> 2; idx++) { + unsigned int val; + val = snd_emu10k1_ptr_read(emu, offset + idx + (pos >> 2), 0); + if (tram_addr && emu->audigy) { + val >>= 11; + val |= snd_emu10k1_ptr_read(emu, 0x100 + idx + (pos >> 2), 0) << 20; + } + tmp[idx] = val; + } + if (copy_to_user(buf, ((char *)tmp) + (pos & 3), count)) + res = -EFAULT; + else + res = count; + kfree(tmp); + return res; +} + +static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_emu10k1 *emu = entry->private_data; + struct snd_emu10k1_voice *voice; + int idx; + static const char * const types[] = { + "Unused", "EFX", "EFX IRQ", "PCM", "PCM IRQ", "Synth" + }; + static_assert(ARRAY_SIZE(types) == EMU10K1_NUM_TYPES); + + snd_iprintf(buffer, "ch\tdirty\tlast\tuse\n"); + for (idx = 0; idx < NUM_G; idx++) { + voice = &emu->voices[idx]; + snd_iprintf(buffer, "%i\t%u\t%u\t%s\n", + idx, + voice->dirty, + voice->last, + types[voice->use]); + } +} + +#ifdef CONFIG_SND_DEBUG + +static void snd_emu_proc_emu1010_link_read(struct snd_emu10k1 *emu, + struct snd_info_buffer *buffer, + u32 dst) +{ + u32 src = snd_emu1010_fpga_link_dst_src_read(emu, dst); + snd_iprintf(buffer, "%04x: %04x\n", dst, src); +} + +static void snd_emu_proc_emu1010_reg_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_emu10k1 *emu = entry->private_data; + u32 value; + int i; + + guard(snd_emu1010_fpga_lock)(emu); + + snd_iprintf(buffer, "EMU1010 Registers:\n\n"); + + for(i = 0; i < 0x40; i+=1) { + snd_emu1010_fpga_read(emu, i, &value); + snd_iprintf(buffer, "%02x: %02x\n", i, value); + } + + snd_iprintf(buffer, "\nEMU1010 Routes:\n\n"); + + for (i = 0; i < 16; i++) // To Alice2/Tina[2] via EMU32 + snd_emu_proc_emu1010_link_read(emu, buffer, i); + if (emu->card_capabilities->emu_model != EMU_MODEL_EMU0404) + for (i = 0; i < 32; i++) // To Dock via EDI + snd_emu_proc_emu1010_link_read(emu, buffer, 0x100 + i); + if (emu->card_capabilities->emu_model != EMU_MODEL_EMU1616) + for (i = 0; i < 8; i++) // To Hamoa/local + snd_emu_proc_emu1010_link_read(emu, buffer, 0x200 + i); + for (i = 0; i < 8; i++) // To Hamoa/Mana/local + snd_emu_proc_emu1010_link_read(emu, buffer, 0x300 + i); + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { + for (i = 0; i < 16; i++) // To Tina2 via EMU32 + snd_emu_proc_emu1010_link_read(emu, buffer, 0x400 + i); + } else if (emu->card_capabilities->emu_model != EMU_MODEL_EMU0404) { + for (i = 0; i < 8; i++) // To Hana ADAT + snd_emu_proc_emu1010_link_read(emu, buffer, 0x400 + i); + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1010B) { + for (i = 0; i < 16; i++) // To Tina via EMU32 + snd_emu_proc_emu1010_link_read(emu, buffer, 0x500 + i); + } else { + // To Alice2 via I2S + snd_emu_proc_emu1010_link_read(emu, buffer, 0x500); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x501); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x600); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x601); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x700); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x701); + } + } +} + +static void snd_emu_proc_io_reg_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_emu10k1 *emu = entry->private_data; + unsigned long value; + int i; + snd_iprintf(buffer, "IO Registers:\n\n"); + for(i = 0; i < 0x40; i+=4) { + value = inl(emu->port + i); + snd_iprintf(buffer, "%02X: %08lX\n", i, value); + } +} + +static void snd_emu_proc_io_reg_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_emu10k1 *emu = entry->private_data; + char line[64]; + u32 reg, val; + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x", ®, &val) != 2) + continue; + if (reg < 0x40 && val <= 0xffffffff) { + outl(val, emu->port + (reg & 0xfffffffc)); + } + } +} + +static unsigned int snd_ptr_read(struct snd_emu10k1 * emu, + unsigned int iobase, + unsigned int reg, + unsigned int chn) +{ + unsigned int regptr; + + regptr = (reg << 16) | chn; + + guard(spinlock_irq)(&emu->emu_lock); + outl(regptr, emu->port + iobase + PTR); + return inl(emu->port + iobase + DATA); +} + +static void snd_ptr_write(struct snd_emu10k1 *emu, + unsigned int iobase, + unsigned int reg, + unsigned int chn, + unsigned int data) +{ + unsigned int regptr; + + regptr = (reg << 16) | chn; + + guard(spinlock_irq)(&emu->emu_lock); + outl(regptr, emu->port + iobase + PTR); + outl(data, emu->port + iobase + DATA); +} + + +static void snd_emu_proc_ptr_reg_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer, int iobase, int offset, int length, int voices) +{ + struct snd_emu10k1 *emu = entry->private_data; + unsigned long value; + int i,j; + if (offset+length > 0xa0) { + snd_iprintf(buffer, "Input values out of range\n"); + return; + } + snd_iprintf(buffer, "Registers 0x%x\n", iobase); + for(i = offset; i < offset+length; i++) { + snd_iprintf(buffer, "%02X: ",i); + for (j = 0; j < voices; j++) { + value = snd_ptr_read(emu, iobase, i, j); + snd_iprintf(buffer, "%08lX ", value); + } + snd_iprintf(buffer, "\n"); + } +} + +static void snd_emu_proc_ptr_reg_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer, + int iobase, int length, int voices) +{ + struct snd_emu10k1 *emu = entry->private_data; + char line[64]; + unsigned int reg, channel_id , val; + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x %x", ®, &channel_id, &val) != 3) + continue; + if (reg < length && channel_id < voices) + snd_ptr_write(emu, iobase, reg, channel_id, val); + } +} + +static void snd_emu_proc_ptr_reg_write00(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_emu_proc_ptr_reg_write(entry, buffer, 0, 0x80, 64); +} + +static void snd_emu_proc_ptr_reg_write20(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_emu10k1 *emu = entry->private_data; + snd_emu_proc_ptr_reg_write(entry, buffer, 0x20, + emu->card_capabilities->ca0108_chip ? 0xa0 : 0x80, 4); +} + + +static void snd_emu_proc_ptr_reg_read00a(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_emu_proc_ptr_reg_read(entry, buffer, 0, 0, 0x40, 64); +} + +static void snd_emu_proc_ptr_reg_read00b(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_emu_proc_ptr_reg_read(entry, buffer, 0, 0x40, 0x40, 64); +} + +static void snd_emu_proc_ptr_reg_read20a(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0, 0x40, 4); +} + +static void snd_emu_proc_ptr_reg_read20b(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0x40, 0x40, 4); +} + +static void snd_emu_proc_ptr_reg_read20c(struct snd_info_entry *entry, + struct snd_info_buffer * buffer) +{ + snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0x80, 0x20, 4); +} +#endif + +static const struct snd_info_entry_ops snd_emu10k1_proc_ops_fx8010 = { + .read = snd_emu10k1_fx8010_read, +}; + +int snd_emu10k1_proc_init(struct snd_emu10k1 *emu) +{ + struct snd_info_entry *entry; +#ifdef CONFIG_SND_DEBUG + if (emu->card_capabilities->emu_model) { + snd_card_ro_proc_new(emu->card, "emu1010_regs", + emu, snd_emu_proc_emu1010_reg_read); + } + snd_card_rw_proc_new(emu->card, "io_regs", emu, + snd_emu_proc_io_reg_read, + snd_emu_proc_io_reg_write); + snd_card_rw_proc_new(emu->card, "ptr_regs00a", emu, + snd_emu_proc_ptr_reg_read00a, + snd_emu_proc_ptr_reg_write00); + snd_card_rw_proc_new(emu->card, "ptr_regs00b", emu, + snd_emu_proc_ptr_reg_read00b, + snd_emu_proc_ptr_reg_write00); + if (!emu->card_capabilities->emu_model && + (emu->card_capabilities->ca0151_chip || emu->card_capabilities->ca0108_chip)) { + snd_card_rw_proc_new(emu->card, "ptr_regs20a", emu, + snd_emu_proc_ptr_reg_read20a, + snd_emu_proc_ptr_reg_write20); + snd_card_rw_proc_new(emu->card, "ptr_regs20b", emu, + snd_emu_proc_ptr_reg_read20b, + snd_emu_proc_ptr_reg_write20); + if (emu->card_capabilities->ca0108_chip) + snd_card_rw_proc_new(emu->card, "ptr_regs20c", emu, + snd_emu_proc_ptr_reg_read20c, + snd_emu_proc_ptr_reg_write20); + } +#endif + + snd_card_ro_proc_new(emu->card, "emu10k1", emu, snd_emu10k1_proc_read); + + if (emu->card_capabilities->emu10k2_chip) + snd_card_ro_proc_new(emu->card, "spdif-in", emu, + snd_emu10k1_proc_spdif_read); + if (emu->card_capabilities->ca0151_chip) + snd_card_ro_proc_new(emu->card, "capture-rates", emu, + snd_emu10k1_proc_rates_read); + + snd_card_ro_proc_new(emu->card, "voices", emu, + snd_emu10k1_proc_voices_read); + + if (! snd_card_proc_new(emu->card, "fx8010_gpr", &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = emu; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; + entry->size = emu->audigy ? A_TOTAL_SIZE_GPR : TOTAL_SIZE_GPR; + entry->c.ops = &snd_emu10k1_proc_ops_fx8010; + } + if (! snd_card_proc_new(emu->card, "fx8010_tram_data", &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = emu; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; + entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_DATA : TOTAL_SIZE_TANKMEM_DATA ; + entry->c.ops = &snd_emu10k1_proc_ops_fx8010; + } + if (! snd_card_proc_new(emu->card, "fx8010_tram_addr", &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = emu; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; + entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_ADDR : TOTAL_SIZE_TANKMEM_ADDR ; + entry->c.ops = &snd_emu10k1_proc_ops_fx8010; + } + if (! snd_card_proc_new(emu->card, "fx8010_code", &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = emu; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; + entry->size = emu->audigy ? A_TOTAL_SIZE_CODE : TOTAL_SIZE_CODE; + entry->c.ops = &snd_emu10k1_proc_ops_fx8010; + } + snd_card_ro_proc_new(emu->card, "fx8010_acode", emu, + snd_emu10k1_proc_acode_read); + return 0; +} diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c new file mode 100644 index 000000000..9c897c3e8 --- /dev/null +++ b/sound/pci/emu10k1/io.c @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Lee Revell + * James Courtier-Dutton + * Oswald Buddenhagen + * Creative Labs, Inc. + * + * Routines for control of EMU10K1 chips + */ + +#include +#include +#include +#include +#include +#include "p17v.h" + +static inline bool check_ptr_reg(struct snd_emu10k1 *emu, unsigned int reg) +{ + if (snd_BUG_ON(!emu)) + return false; + if (snd_BUG_ON(reg & (emu->audigy ? (0xffff0000 & ~A_PTR_ADDRESS_MASK) + : (0xffff0000 & ~PTR_ADDRESS_MASK)))) + return false; + if (snd_BUG_ON(reg & 0x0000ffff & ~PTR_CHANNELNUM_MASK)) + return false; + return true; +} + +unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn) +{ + unsigned int regptr, val; + unsigned int mask; + + regptr = (reg << 16) | chn; + if (!check_ptr_reg(emu, regptr)) + return 0; + + scoped_guard(spinlock_irqsave, &emu->emu_lock) { + outl(regptr, emu->port + PTR); + val = inl(emu->port + DATA); + } + + if (reg & 0xff000000) { + unsigned char size, offset; + + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = (1 << size) - 1; + + return (val >> offset) & mask; + } else { + return val; + } +} + +EXPORT_SYMBOL(snd_emu10k1_ptr_read); + +void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data) +{ + unsigned int regptr; + unsigned int mask; + + regptr = (reg << 16) | chn; + if (!check_ptr_reg(emu, regptr)) + return; + + guard(spinlock_irqsave)(&emu->emu_lock); + if (reg & 0xff000000) { + unsigned char size, offset; + + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = (1 << size) - 1; + if (snd_BUG_ON(data & ~mask)) + return; + mask <<= offset; + data <<= offset; + + outl(regptr, emu->port + PTR); + data |= inl(emu->port + DATA) & ~mask; + } else { + outl(regptr, emu->port + PTR); + } + outl(data, emu->port + DATA); +} + +EXPORT_SYMBOL(snd_emu10k1_ptr_write); + +void snd_emu10k1_ptr_write_multiple(struct snd_emu10k1 *emu, unsigned int chn, ...) +{ + va_list va; + u32 addr_mask; + + if (snd_BUG_ON(!emu)) + return; + if (snd_BUG_ON(chn & ~PTR_CHANNELNUM_MASK)) + return; + addr_mask = ~((emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK) >> 16); + + va_start(va, chn); + guard(spinlock_irqsave)(&emu->emu_lock); + for (;;) { + u32 data; + u32 reg = va_arg(va, u32); + if (reg == REGLIST_END) + break; + data = va_arg(va, u32); + if (snd_BUG_ON(reg & addr_mask)) // Only raw registers supported here + continue; + outl((reg << 16) | chn, emu->port + PTR); + outl(data, emu->port + DATA); + } + va_end(va); +} + +EXPORT_SYMBOL(snd_emu10k1_ptr_write_multiple); + +unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu, + unsigned int reg, + unsigned int chn) +{ + unsigned int regptr; + + regptr = (reg << 16) | chn; + + guard(spinlock_irqsave)(&emu->emu_lock); + outl(regptr, emu->port + PTR2); + return inl(emu->port + DATA2); +} + +void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu, + unsigned int reg, + unsigned int chn, + unsigned int data) +{ + unsigned int regptr; + + regptr = (reg << 16) | chn; + + guard(spinlock_irqsave)(&emu->emu_lock); + outl(regptr, emu->port + PTR2); + outl(data, emu->port + DATA2); +} + +int snd_emu10k1_spi_write(struct snd_emu10k1 * emu, + unsigned int data) +{ + unsigned int reset, set; + unsigned int reg, tmp; + int n, result; + + /* This function is not re-entrant, so protect against it. */ + guard(spinlock)(&emu->spi_lock); + if (emu->card_capabilities->ca0108_chip) + reg = P17V_SPI; + else { + /* For other chip types the SPI register + * is currently unknown. */ + return 1; + } + if (data > 0xffff) { + /* Only 16bit values allowed */ + return 1; + } + + tmp = snd_emu10k1_ptr20_read(emu, reg, 0); + reset = (tmp & ~0x3ffff) | 0x20000; /* Set xxx20000 */ + set = reset | 0x10000; /* Set xxx1xxxx */ + snd_emu10k1_ptr20_write(emu, reg, 0, reset | data); + tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* write post */ + snd_emu10k1_ptr20_write(emu, reg, 0, set | data); + result = 1; + /* Wait for status bit to return to 0 */ + for (n = 0; n < 100; n++) { + udelay(10); + tmp = snd_emu10k1_ptr20_read(emu, reg, 0); + if (!(tmp & 0x10000)) { + result = 0; + break; + } + } + if (result) { + /* Timed out */ + return 1; + } + snd_emu10k1_ptr20_write(emu, reg, 0, reset | data); + tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* Write post */ + return 0; +} + +/* The ADC does not support i2c read, so only write is implemented */ +int snd_emu10k1_i2c_write(struct snd_emu10k1 *emu, + u32 reg, + u32 value) +{ + u32 tmp; + int timeout = 0; + int status; + int retry; + + if ((reg > 0x7f) || (value > 0x1ff)) { + dev_err(emu->card->dev, "i2c_write: invalid values.\n"); + return -EINVAL; + } + + /* This function is not re-entrant, so protect against it. */ + guard(spinlock)(&emu->i2c_lock); + + tmp = reg << 25 | value << 16; + + /* This controls the I2C connected to the WM8775 ADC Codec */ + snd_emu10k1_ptr20_write(emu, P17V_I2C_1, 0, tmp); + tmp = snd_emu10k1_ptr20_read(emu, P17V_I2C_1, 0); /* write post */ + + for (retry = 0; retry < 10; retry++) { + /* Send the data to i2c */ + tmp = 0; + tmp = tmp | (I2C_A_ADC_LAST|I2C_A_ADC_START|I2C_A_ADC_ADD); + snd_emu10k1_ptr20_write(emu, P17V_I2C_ADDR, 0, tmp); + + /* Wait till the transaction ends */ + while (1) { + mdelay(1); + status = snd_emu10k1_ptr20_read(emu, P17V_I2C_ADDR, 0); + timeout++; + if ((status & I2C_A_ADC_START) == 0) + break; + + if (timeout > 1000) { + dev_warn(emu->card->dev, + "emu10k1:I2C:timeout status=0x%x\n", + status); + break; + } + } + //Read back and see if the transaction is successful + if ((status & I2C_A_ADC_ABORT) == 0) + break; + } + + if (retry == 10) { + dev_err(emu->card->dev, "Writing to ADC failed!\n"); + dev_err(emu->card->dev, "status=0x%x, reg=%d, value=%d\n", + status, reg, value); + /* dump_stack(); */ + return -EINVAL; + } + + return 0; +} + +static void snd_emu1010_fpga_write_locked(struct snd_emu10k1 *emu, u32 reg, u32 value) +{ + if (snd_BUG_ON(reg > 0x3f)) + return; + reg += 0x40; /* 0x40 upwards are registers. */ + if (snd_BUG_ON(value > 0x3f)) /* 0 to 0x3f are values */ + return; + outw(reg, emu->port + A_GPIO); + udelay(10); + outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */ + udelay(10); + outw(value, emu->port + A_GPIO); + udelay(10); + outw(value | 0x80 , emu->port + A_GPIO); /* High bit clocks the value into the fpga. */ + udelay(10); +} + +void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value) +{ + if (snd_BUG_ON(!mutex_is_locked(&emu->emu1010.lock))) + return; + snd_emu1010_fpga_write_locked(emu, reg, value); +} + +void snd_emu1010_fpga_write_lock(struct snd_emu10k1 *emu, u32 reg, u32 value) +{ + guard(snd_emu1010_fpga_lock)(emu); + snd_emu1010_fpga_write_locked(emu, reg, value); +} + +void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value) +{ + // The higest input pin is used as the designated interrupt trigger, + // so it needs to be masked out. + // But note that any other input pin change will also cause an IRQ, + // so using this function often causes an IRQ as a side effect. + u32 mask = emu->card_capabilities->ca0108_chip ? 0x1f : 0x7f; + + if (snd_BUG_ON(!mutex_is_locked(&emu->emu1010.lock))) + return; + if (snd_BUG_ON(reg > 0x3f)) + return; + reg += 0x40; /* 0x40 upwards are registers. */ + outw(reg, emu->port + A_GPIO); + udelay(10); + outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */ + udelay(10); + *value = ((inw(emu->port + A_GPIO) >> 8) & mask); +} + +/* Each Destination has one and only one Source, + * but one Source can feed any number of Destinations simultaneously. + */ +void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src) +{ + if (snd_BUG_ON(dst & ~0x71f)) + return; + if (snd_BUG_ON(src & ~0x71f)) + return; + snd_emu1010_fpga_write(emu, EMU_HANA_DESTHI, dst >> 8); + snd_emu1010_fpga_write(emu, EMU_HANA_DESTLO, dst & 0x1f); + snd_emu1010_fpga_write(emu, EMU_HANA_SRCHI, src >> 8); + snd_emu1010_fpga_write(emu, EMU_HANA_SRCLO, src & 0x1f); +} + +u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst) +{ + u32 hi, lo; + + if (snd_BUG_ON(dst & ~0x71f)) + return 0; + snd_emu1010_fpga_write(emu, EMU_HANA_DESTHI, dst >> 8); + snd_emu1010_fpga_write(emu, EMU_HANA_DESTLO, dst & 0x1f); + snd_emu1010_fpga_read(emu, EMU_HANA_SRCHI, &hi); + snd_emu1010_fpga_read(emu, EMU_HANA_SRCLO, &lo); + return (hi << 8) | lo; +} + +int snd_emu1010_get_raw_rate(struct snd_emu10k1 *emu, u8 src) +{ + u32 reg_lo, reg_hi, value, value2; + + switch (src) { + case EMU_HANA_WCLOCK_HANA_SPDIF_IN: + snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &value); + if (value & EMU_HANA_SPDIF_MODE_RX_INVALID) + return 0; + reg_lo = EMU_HANA_WC_SPDIF_LO; + reg_hi = EMU_HANA_WC_SPDIF_HI; + break; + case EMU_HANA_WCLOCK_HANA_ADAT_IN: + reg_lo = EMU_HANA_WC_ADAT_LO; + reg_hi = EMU_HANA_WC_ADAT_HI; + break; + case EMU_HANA_WCLOCK_SYNC_BNC: + reg_lo = EMU_HANA_WC_BNC_LO; + reg_hi = EMU_HANA_WC_BNC_HI; + break; + case EMU_HANA_WCLOCK_2ND_HANA: + reg_lo = EMU_HANA2_WC_SPDIF_LO; + reg_hi = EMU_HANA2_WC_SPDIF_HI; + break; + default: + return 0; + } + snd_emu1010_fpga_read(emu, reg_hi, &value); + snd_emu1010_fpga_read(emu, reg_lo, &value2); + // FIXME: The /4 is valid for 0404b, but contradicts all other info. + return 0x1770000 / 4 / (((value << 5) | value2) + 1); +} + +void snd_emu1010_update_clock(struct snd_emu10k1 *emu) +{ + int clock; + u32 leds; + + switch (emu->emu1010.wclock) { + case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X: + clock = 44100; + leds = EMU_HANA_DOCK_LEDS_2_44K; + break; + case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X: + clock = 48000; + leds = EMU_HANA_DOCK_LEDS_2_48K; + break; + default: + clock = snd_emu1010_get_raw_rate( + emu, emu->emu1010.wclock & EMU_HANA_WCLOCK_SRC_MASK); + // The raw rate reading is rather coarse (it cannot accurately + // represent 44.1 kHz) and fluctuates slightly. Luckily, the + // clock comes from digital inputs, which use standardized rates. + // So we round to the closest standard rate and ignore discrepancies. + if (clock < 46000) { + clock = 44100; + leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_44K; + } else { + clock = 48000; + leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_48K; + } + break; + } + emu->emu1010.word_clock = clock; + + // FIXME: this should probably represent the AND of all currently + // used sources' lock status. But we don't know how to get that ... + leds |= EMU_HANA_DOCK_LEDS_2_LOCK; + + snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, leds); +} + +void snd_emu1010_load_firmware_entry(struct snd_emu10k1 *emu, int dock, + const struct firmware *fw_entry) +{ + __always_unused u16 write_post; + + // On E-MU 1010 rev1 the FPGA is a Xilinx Spartan IIE XC2S50E. + // On E-MU 0404b it is a Xilinx Spartan III XC3S50. + // The wiring is as follows: + // GPO7 -> FPGA input & 1K resistor -> FPGA /PGMN <- FPGA output + // In normal operation, the active low reset line is held up by + // an FPGA output, while the GPO pin performs its duty as control + // register access strobe signal. Writing the respective bit to + // EMU_HANA_FPGA_CONFIG puts the FPGA output into high-Z mode, at + // which point the GPO pin can control the reset line through the + // resistor. + // GPO6 -> FPGA CCLK & FPGA input + // GPO5 -> FPGA DIN (dual function) + + // If the FPGA is already programmed, return it to programming mode + snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG, + dock ? EMU_HANA_FPGA_CONFIG_AUDIODOCK : + EMU_HANA_FPGA_CONFIG_HANA); + + // Assert reset line for 100uS + outw(0x00, emu->port + A_GPIO); + write_post = inw(emu->port + A_GPIO); + udelay(100); + outw(0x80, emu->port + A_GPIO); + write_post = inw(emu->port + A_GPIO); + udelay(100); // Allow FPGA memory to clean + + // Upload the netlist. Keep reset line high! + for (int n = 0; n < fw_entry->size; n++) { + u8 value = fw_entry->data[n]; + for (int i = 0; i < 8; i++) { + u16 reg = 0x80; + if (value & 1) + reg |= 0x20; + value >>= 1; + outw(reg, emu->port + A_GPIO); + write_post = inw(emu->port + A_GPIO); + outw(reg | 0x40, emu->port + A_GPIO); + write_post = inw(emu->port + A_GPIO); + } + } + + // After programming, set GPIO bit 4 high again. + // This appears to be a config word that the rev1 Hana + // firmware reads; weird things happen without this. + outw(0x10, emu->port + A_GPIO); + write_post = inw(emu->port + A_GPIO); +} + +void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb) +{ + unsigned int enable; + + guard(spinlock_irqsave)(&emu->emu_lock); + enable = inl(emu->port + INTE) | intrenb; + outl(enable, emu->port + INTE); +} + +void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb) +{ + unsigned int enable; + + guard(spinlock_irqsave)(&emu->emu_lock); + enable = inl(emu->port + INTE) & ~intrenb; + outl(enable, emu->port + INTE); +} + +void snd_emu10k1_voice_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum) +{ + unsigned int val; + + guard(spinlock_irqsave)(&emu->emu_lock); + if (voicenum >= 32) { + outl(CLIEH << 16, emu->port + PTR); + val = inl(emu->port + DATA); + val |= 1 << (voicenum - 32); + } else { + outl(CLIEL << 16, emu->port + PTR); + val = inl(emu->port + DATA); + val |= 1 << voicenum; + } + outl(val, emu->port + DATA); +} + +void snd_emu10k1_voice_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum) +{ + unsigned int val; + + guard(spinlock_irqsave)(&emu->emu_lock); + if (voicenum >= 32) { + outl(CLIEH << 16, emu->port + PTR); + val = inl(emu->port + DATA); + val &= ~(1 << (voicenum - 32)); + } else { + outl(CLIEL << 16, emu->port + PTR); + val = inl(emu->port + DATA); + val &= ~(1 << voicenum); + } + outl(val, emu->port + DATA); +} + +void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum) +{ + guard(spinlock_irqsave)(&emu->emu_lock); + if (voicenum >= 32) { + outl(CLIPH << 16, emu->port + PTR); + voicenum = 1 << (voicenum - 32); + } else { + outl(CLIPL << 16, emu->port + PTR); + voicenum = 1 << voicenum; + } + outl(voicenum, emu->port + DATA); +} + +void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum) +{ + unsigned int val; + + guard(spinlock_irqsave)(&emu->emu_lock); + if (voicenum >= 32) { + outl(HLIEH << 16, emu->port + PTR); + val = inl(emu->port + DATA); + val |= 1 << (voicenum - 32); + } else { + outl(HLIEL << 16, emu->port + PTR); + val = inl(emu->port + DATA); + val |= 1 << voicenum; + } + outl(val, emu->port + DATA); +} + +void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum) +{ + unsigned int val; + + guard(spinlock_irqsave)(&emu->emu_lock); + if (voicenum >= 32) { + outl(HLIEH << 16, emu->port + PTR); + val = inl(emu->port + DATA); + val &= ~(1 << (voicenum - 32)); + } else { + outl(HLIEL << 16, emu->port + PTR); + val = inl(emu->port + DATA); + val &= ~(1 << voicenum); + } + outl(val, emu->port + DATA); +} + +void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum) +{ + guard(spinlock_irqsave)(&emu->emu_lock); + if (voicenum >= 32) { + outl(HLIPH << 16, emu->port + PTR); + voicenum = 1 << (voicenum - 32); + } else { + outl(HLIPL << 16, emu->port + PTR); + voicenum = 1 << voicenum; + } + outl(voicenum, emu->port + DATA); +} + +#if 0 +void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum) +{ + unsigned int sol; + + guard(spinlock_irqsave)(&emu->emu_lock); + if (voicenum >= 32) { + outl(SOLEH << 16, emu->port + PTR); + sol = inl(emu->port + DATA); + sol |= 1 << (voicenum - 32); + } else { + outl(SOLEL << 16, emu->port + PTR); + sol = inl(emu->port + DATA); + sol |= 1 << voicenum; + } + outl(sol, emu->port + DATA); +} + +void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum) +{ + unsigned int sol; + + guard(spinlock_irqsave)(&emu->emu_lock); + if (voicenum >= 32) { + outl(SOLEH << 16, emu->port + PTR); + sol = inl(emu->port + DATA); + sol &= ~(1 << (voicenum - 32)); + } else { + outl(SOLEL << 16, emu->port + PTR); + sol = inl(emu->port + DATA); + sol &= ~(1 << voicenum); + } + outl(sol, emu->port + DATA); +} +#endif + +void snd_emu10k1_voice_set_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices) +{ + guard(spinlock_irqsave)(&emu->emu_lock); + outl(SOLEL << 16, emu->port + PTR); + outl(inl(emu->port + DATA) | (u32)voices, emu->port + DATA); + outl(SOLEH << 16, emu->port + PTR); + outl(inl(emu->port + DATA) | (u32)(voices >> 32), emu->port + DATA); +} + +void snd_emu10k1_voice_clear_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices) +{ + guard(spinlock_irqsave)(&emu->emu_lock); + outl(SOLEL << 16, emu->port + PTR); + outl(inl(emu->port + DATA) & (u32)~voices, emu->port + DATA); + outl(SOLEH << 16, emu->port + PTR); + outl(inl(emu->port + DATA) & (u32)(~voices >> 32), emu->port + DATA); +} + +int snd_emu10k1_voice_clear_loop_stop_multiple_atomic(struct snd_emu10k1 *emu, u64 voices) +{ + unsigned long flags; + u32 soll, solh; + int ret = -EIO; + + spin_lock_irqsave(&emu->emu_lock, flags); + + outl(SOLEL << 16, emu->port + PTR); + soll = inl(emu->port + DATA); + outl(SOLEH << 16, emu->port + PTR); + solh = inl(emu->port + DATA); + + soll &= (u32)~voices; + solh &= (u32)(~voices >> 32); + + for (int tries = 0; tries < 1000; tries++) { + const u32 quart = 1U << (REG_SIZE(WC_CURRENTCHANNEL) - 2); + // First we wait for the third quarter of the sample cycle ... + u32 wc = inl(emu->port + WC); + u32 cc = REG_VAL_GET(WC_CURRENTCHANNEL, wc); + if (cc >= quart * 2 && cc < quart * 3) { + // ... and release the low voices, while the high ones are serviced. + outl(SOLEL << 16, emu->port + PTR); + outl(soll, emu->port + DATA); + // Then we wait for the first quarter of the next sample cycle ... + for (; tries < 1000; tries++) { + cc = REG_VAL_GET(WC_CURRENTCHANNEL, inl(emu->port + WC)); + if (cc < quart) + goto good; + // We will block for 10+ us with interrupts disabled. This is + // not nice at all, but necessary for reasonable reliability. + udelay(1); + } + break; + good: + // ... and release the high voices, while the low ones are serviced. + outl(SOLEH << 16, emu->port + PTR); + outl(solh, emu->port + DATA); + // Finally we verify that nothing interfered in fact. + if (REG_VAL_GET(WC_SAMPLECOUNTER, inl(emu->port + WC)) == + ((REG_VAL_GET(WC_SAMPLECOUNTER, wc) + 1) & REG_MASK0(WC_SAMPLECOUNTER))) { + ret = 0; + } else { + ret = -EAGAIN; + } + break; + } + // Don't block for too long + spin_unlock_irqrestore(&emu->emu_lock, flags); + udelay(1); + spin_lock_irqsave(&emu->emu_lock, flags); + } + + spin_unlock_irqrestore(&emu->emu_lock, flags); + return ret; +} + +void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait) +{ + volatile unsigned count; + unsigned int newtime = 0, curtime; + + curtime = inl(emu->port + WC) >> 6; + while (wait-- > 0) { + count = 0; + while (count++ < 16384) { + newtime = inl(emu->port + WC) >> 6; + if (newtime != curtime) + break; + } + if (count > 16384) + break; + curtime = newtime; + } +} + +unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct snd_emu10k1 *emu = ac97->private_data; + + guard(spinlock_irqsave)(&emu->emu_lock); + outb(reg, emu->port + AC97ADDRESS); + return inw(emu->port + AC97DATA); +} + +void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data) +{ + struct snd_emu10k1 *emu = ac97->private_data; + + guard(spinlock_irqsave)(&emu->emu_lock); + outb(reg, emu->port + AC97ADDRESS); + outw(data, emu->port + AC97DATA); +} diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c new file mode 100644 index 000000000..71aa90b9c --- /dev/null +++ b/sound/pci/emu10k1/irq.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Creative Labs, Inc. + * Routines for IRQ control of EMU10K1 chips + */ + +#include +#include +#include + +irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) +{ + struct snd_emu10k1 *emu = dev_id; + unsigned int status, orig_status; + int handled = 0; + int timeout = 0; + + while ((status = inl(emu->port + IPR)) != 0) { + handled = 1; + if ((status & 0xffffffff) == 0xffffffff) { + dev_info(emu->card->dev, + "Suspected sound card removal\n"); + break; + } + if (++timeout == 1000) { + dev_info(emu->card->dev, "emu10k1 irq routine failure\n"); + break; + } + orig_status = status; + if (status & IPR_PCIERROR) { + dev_err(emu->card->dev, "interrupt: PCI error\n"); + snd_emu10k1_intr_disable(emu, INTE_PCIERRORENABLE); + status &= ~IPR_PCIERROR; + } + if (status & (IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE)) { + if (emu->hwvol_interrupt) + emu->hwvol_interrupt(emu, status); + else + snd_emu10k1_intr_disable(emu, INTE_VOLINCRENABLE|INTE_VOLDECRENABLE|INTE_MUTEENABLE); + status &= ~(IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE); + } + if (status & IPR_CHANNELLOOP) { + struct snd_emu10k1_voice *pvoice; + int voice; + int voice_max = status & IPR_CHANNELNUMBERMASK; + u32 val; + + val = snd_emu10k1_ptr_read(emu, CLIPL, 0); + pvoice = emu->voices; + for (voice = 0; voice <= voice_max; voice++) { + if (voice == 0x20) + val = snd_emu10k1_ptr_read(emu, CLIPH, 0); + if (val & 1) { + if (pvoice->use && pvoice->interrupt != NULL) { + pvoice->interrupt(emu, pvoice); + snd_emu10k1_voice_intr_ack(emu, voice); + } else { + snd_emu10k1_voice_intr_disable(emu, voice); + } + } + val >>= 1; + pvoice++; + } + val = snd_emu10k1_ptr_read(emu, HLIPL, 0); + pvoice = emu->voices; + for (voice = 0; voice <= voice_max; voice++) { + if (voice == 0x20) + val = snd_emu10k1_ptr_read(emu, HLIPH, 0); + if (val & 1) { + if (pvoice->use && pvoice->interrupt != NULL) { + pvoice->interrupt(emu, pvoice); + snd_emu10k1_voice_half_loop_intr_ack(emu, voice); + } else { + snd_emu10k1_voice_half_loop_intr_disable(emu, voice); + } + } + val >>= 1; + pvoice++; + } + status &= ~(IPR_CHANNELLOOP | IPR_CHANNELNUMBERMASK); + } + if (status & (IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL)) { + if (emu->capture_interrupt) + emu->capture_interrupt(emu, status); + else + snd_emu10k1_intr_disable(emu, INTE_ADCBUFENABLE); + status &= ~(IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL); + } + if (status & (IPR_MICBUFFULL|IPR_MICBUFHALFFULL)) { + if (emu->capture_mic_interrupt) + emu->capture_mic_interrupt(emu, status); + else + snd_emu10k1_intr_disable(emu, INTE_MICBUFENABLE); + status &= ~(IPR_MICBUFFULL|IPR_MICBUFHALFFULL); + } + if (status & (IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL)) { + if (emu->capture_efx_interrupt) + emu->capture_efx_interrupt(emu, status); + else + snd_emu10k1_intr_disable(emu, INTE_EFXBUFENABLE); + status &= ~(IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL); + } + if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) { + if (emu->midi.interrupt) + emu->midi.interrupt(emu, status); + else + snd_emu10k1_intr_disable(emu, INTE_MIDITXENABLE|INTE_MIDIRXENABLE); + status &= ~(IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY); + } + if (status & (IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2)) { + if (emu->midi2.interrupt) + emu->midi2.interrupt(emu, status); + else + snd_emu10k1_intr_disable(emu, INTE_A_MIDITXENABLE2|INTE_A_MIDIRXENABLE2); + status &= ~(IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2); + } + if (status & IPR_INTERVALTIMER) { + if (emu->timer) + snd_timer_interrupt(emu->timer, emu->timer->sticks); + else + snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB); + status &= ~IPR_INTERVALTIMER; + } + if (status & (IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE)) { + if (emu->spdif_interrupt) + emu->spdif_interrupt(emu, status); + else + snd_emu10k1_intr_disable(emu, INTE_GPSPDIFENABLE|INTE_CDSPDIFENABLE); + status &= ~(IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE); + } + if (status & IPR_FXDSP) { + if (emu->dsp_interrupt) + emu->dsp_interrupt(emu); + else + snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE); + status &= ~IPR_FXDSP; + } + if (status & IPR_P16V) { + if (emu->p16v_interrupt) + emu->p16v_interrupt(emu); + else + outl(0, emu->port + INTE2); + status &= ~IPR_P16V; + } + if (status & IPR_A_GPIO) { + if (emu->gpio_interrupt) + emu->gpio_interrupt(emu); + else + snd_emu10k1_intr_disable(emu, INTE_A_GPIOENABLE); + status &= ~IPR_A_GPIO; + } + + if (status) { + dev_err(emu->card->dev, + "unhandled interrupt: 0x%08x\n", status); + } + outl(orig_status, emu->port + IPR); /* ack all */ + } + + return IRQ_RETVAL(handled); +} diff --git a/sound/pci/emu10k1/memory.c b/sound/pci/emu10k1/memory.c new file mode 100644 index 000000000..be889a4cc --- /dev/null +++ b/sound/pci/emu10k1/memory.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Copyright (c) by Takashi Iwai + * + * EMU10K1 memory page allocation (PTB area) + */ + +#include +#include +#include +#include +#include + +#include +#include + +/* page arguments of these two macros are Emu page (4096 bytes), not like + * aligned pages in others + */ +#define __set_ptb_entry(emu,page,addr) \ + (((__le32 *)(emu)->ptb_pages.area)[page] = \ + cpu_to_le32(((addr) << (emu->address_mode)) | (page))) +#define __get_ptb_entry(emu, page) \ + (le32_to_cpu(((__le32 *)(emu)->ptb_pages.area)[page])) + +#define UNIT_PAGES (PAGE_SIZE / EMUPAGESIZE) +#define MAX_ALIGN_PAGES0 (MAXPAGES0 / UNIT_PAGES) +#define MAX_ALIGN_PAGES1 (MAXPAGES1 / UNIT_PAGES) +/* get aligned page from offset address */ +#define get_aligned_page(offset) ((offset) >> PAGE_SHIFT) +/* get offset address from aligned page */ +#define aligned_page_offset(page) ((page) << PAGE_SHIFT) + +#if PAGE_SIZE == EMUPAGESIZE && !IS_ENABLED(CONFIG_DYNAMIC_DEBUG) +/* fill PTB entrie(s) corresponding to page with addr */ +#define set_ptb_entry(emu,page,addr) __set_ptb_entry(emu,page,addr) +/* fill PTB entrie(s) corresponding to page with silence pointer */ +#define set_silent_ptb(emu,page) __set_ptb_entry(emu,page,emu->silent_page.addr) +#else +/* fill PTB entries -- we need to fill UNIT_PAGES entries */ +static inline void set_ptb_entry(struct snd_emu10k1 *emu, int page, dma_addr_t addr) +{ + int i; + page *= UNIT_PAGES; + for (i = 0; i < UNIT_PAGES; i++, page++) { + __set_ptb_entry(emu, page, addr); + dev_dbg(emu->card->dev, "mapped page %d to entry %.8x\n", page, + (unsigned int)__get_ptb_entry(emu, page)); + addr += EMUPAGESIZE; + } +} +static inline void set_silent_ptb(struct snd_emu10k1 *emu, int page) +{ + int i; + page *= UNIT_PAGES; + for (i = 0; i < UNIT_PAGES; i++, page++) { + /* do not increment ptr */ + __set_ptb_entry(emu, page, emu->silent_page.addr); + dev_dbg(emu->card->dev, "mapped silent page %d to entry %.8x\n", + page, (unsigned int)__get_ptb_entry(emu, page)); + } +} +#endif /* PAGE_SIZE */ + + +/* + */ +static int synth_alloc_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk); +static int synth_free_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk); + +#define get_emu10k1_memblk(l,member) list_entry(l, struct snd_emu10k1_memblk, member) + + +/* initialize emu10k1 part */ +static void emu10k1_memblk_init(struct snd_emu10k1_memblk *blk) +{ + blk->mapped_page = -1; + INIT_LIST_HEAD(&blk->mapped_link); + INIT_LIST_HEAD(&blk->mapped_order_link); + blk->map_locked = 0; + + blk->first_page = get_aligned_page(blk->mem.offset); + blk->last_page = get_aligned_page(blk->mem.offset + blk->mem.size - 1); + blk->pages = blk->last_page - blk->first_page + 1; +} + +/* + * search empty region on PTB with the given size + * + * if an empty region is found, return the page and store the next mapped block + * in nextp + * if not found, return a negative error code. + */ +static int search_empty_map_area(struct snd_emu10k1 *emu, int npages, struct list_head **nextp) +{ + int page = 1, found_page = -ENOMEM; + int max_size = npages; + int size; + struct list_head *candidate = &emu->mapped_link_head; + struct list_head *pos; + + list_for_each (pos, &emu->mapped_link_head) { + struct snd_emu10k1_memblk *blk = get_emu10k1_memblk(pos, mapped_link); + if (blk->mapped_page < 0) + continue; + size = blk->mapped_page - page; + if (size == npages) { + *nextp = pos; + return page; + } + else if (size > max_size) { + /* we look for the maximum empty hole */ + max_size = size; + candidate = pos; + found_page = page; + } + page = blk->mapped_page + blk->pages; + } + size = (emu->address_mode ? MAX_ALIGN_PAGES1 : MAX_ALIGN_PAGES0) - page; + if (size >= max_size) { + *nextp = pos; + return page; + } + *nextp = candidate; + return found_page; +} + +/* + * map a memory block onto emu10k1's PTB + * + * call with memblk_lock held + */ +static int map_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk) +{ + int page, pg; + struct list_head *next; + + page = search_empty_map_area(emu, blk->pages, &next); + if (page < 0) /* not found */ + return page; + if (page == 0) { + dev_err(emu->card->dev, "trying to map zero (reserved) page\n"); + return -EINVAL; + } + /* insert this block in the proper position of mapped list */ + list_add_tail(&blk->mapped_link, next); + /* append this as a newest block in order list */ + list_add_tail(&blk->mapped_order_link, &emu->mapped_order_link_head); + blk->mapped_page = page; + /* fill PTB */ + for (pg = blk->first_page; pg <= blk->last_page; pg++) { + set_ptb_entry(emu, page, emu->page_addr_table[pg]); + page++; + } + return 0; +} + +/* + * unmap the block + * return the size of resultant empty pages + * + * call with memblk_lock held + */ +static int unmap_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk) +{ + int start_page, end_page, mpage, pg; + struct list_head *p; + struct snd_emu10k1_memblk *q; + + /* calculate the expected size of empty region */ + p = blk->mapped_link.prev; + if (p != &emu->mapped_link_head) { + q = get_emu10k1_memblk(p, mapped_link); + start_page = q->mapped_page + q->pages; + } else { + start_page = 1; + } + p = blk->mapped_link.next; + if (p != &emu->mapped_link_head) { + q = get_emu10k1_memblk(p, mapped_link); + end_page = q->mapped_page; + } else { + end_page = (emu->address_mode ? MAX_ALIGN_PAGES1 : MAX_ALIGN_PAGES0); + } + + /* remove links */ + list_del(&blk->mapped_link); + list_del(&blk->mapped_order_link); + /* clear PTB */ + mpage = blk->mapped_page; + for (pg = blk->first_page; pg <= blk->last_page; pg++) { + set_silent_ptb(emu, mpage); + mpage++; + } + blk->mapped_page = -1; + return end_page - start_page; /* return the new empty size */ +} + +/* + * search empty pages with the given size, and create a memory block + * + * unlike synth_alloc the memory block is aligned to the page start + */ +static struct snd_emu10k1_memblk * +search_empty(struct snd_emu10k1 *emu, int size) +{ + struct list_head *p; + struct snd_emu10k1_memblk *blk; + int page, psize; + + psize = get_aligned_page(size + PAGE_SIZE -1); + page = 0; + list_for_each(p, &emu->memhdr->block) { + blk = get_emu10k1_memblk(p, mem.list); + if (page + psize <= blk->first_page) + goto __found_pages; + page = blk->last_page + 1; + } + if (page + psize > emu->max_cache_pages) + return NULL; + +__found_pages: + /* create a new memory block */ + blk = (struct snd_emu10k1_memblk *)__snd_util_memblk_new(emu->memhdr, psize << PAGE_SHIFT, p->prev); + if (blk == NULL) + return NULL; + blk->mem.offset = aligned_page_offset(page); /* set aligned offset */ + emu10k1_memblk_init(blk); + return blk; +} + + +/* + * check if the given pointer is valid for pages + */ +static int is_valid_page(struct snd_emu10k1 *emu, dma_addr_t addr) +{ + if (addr & ~emu->dma_mask) { + dev_err_ratelimited(emu->card->dev, + "max memory size is 0x%lx (addr = 0x%lx)!!\n", + emu->dma_mask, (unsigned long)addr); + return 0; + } + if (addr & (EMUPAGESIZE-1)) { + dev_err_ratelimited(emu->card->dev, "page is not aligned\n"); + return 0; + } + return 1; +} + +/* + * map the given memory block on PTB. + * if the block is already mapped, update the link order. + * if no empty pages are found, tries to release unused memory blocks + * and retry the mapping. + */ +int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk) +{ + int err; + int size; + struct list_head *p, *nextp; + struct snd_emu10k1_memblk *deleted; + + guard(spinlock_irqsave)(&emu->memblk_lock); + if (blk->mapped_page >= 0) { + /* update order link */ + list_move_tail(&blk->mapped_order_link, + &emu->mapped_order_link_head); + return 0; + } + err = map_memblk(emu, blk); + if (err < 0) { + /* no enough page - try to unmap some blocks */ + /* starting from the oldest block */ + p = emu->mapped_order_link_head.next; + for (; p != &emu->mapped_order_link_head; p = nextp) { + nextp = p->next; + deleted = get_emu10k1_memblk(p, mapped_order_link); + if (deleted->map_locked) + continue; + size = unmap_memblk(emu, deleted); + if (size >= blk->pages) { + /* ok the empty region is enough large */ + err = map_memblk(emu, blk); + break; + } + } + } + return err; +} + +EXPORT_SYMBOL(snd_emu10k1_memblk_map); + +/* + * page allocation for DMA + */ +struct snd_util_memblk * +snd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_util_memhdr *hdr; + struct snd_emu10k1_memblk *blk; + int page, err, idx; + + if (snd_BUG_ON(!emu)) + return NULL; + if (snd_BUG_ON(runtime->dma_bytes <= 0 || + runtime->dma_bytes >= (emu->address_mode ? MAXPAGES1 : MAXPAGES0) * EMUPAGESIZE)) + return NULL; + hdr = emu->memhdr; + if (snd_BUG_ON(!hdr)) + return NULL; + + guard(mutex)(&hdr->block_mutex); + blk = search_empty(emu, runtime->dma_bytes); + if (blk == NULL) + return NULL; + /* fill buffer addresses but pointers are not stored so that + * snd_free_pci_page() is not called in synth_free() + */ + idx = 0; + for (page = blk->first_page; page <= blk->last_page; page++, idx++) { + unsigned long ofs = idx << PAGE_SHIFT; + dma_addr_t addr; + if (ofs >= runtime->dma_bytes) + addr = emu->silent_page.addr; + else + addr = snd_pcm_sgbuf_get_addr(substream, ofs); + if (! is_valid_page(emu, addr)) { + dev_err_ratelimited(emu->card->dev, + "emu: failure page = %d\n", idx); + return NULL; + } + emu->page_addr_table[page] = addr; + emu->page_ptr_table[page] = NULL; + } + + /* set PTB entries */ + blk->map_locked = 1; /* do not unmap this block! */ + err = snd_emu10k1_memblk_map(emu, blk); + if (err < 0) { + __snd_util_mem_free(hdr, (struct snd_util_memblk *)blk); + return NULL; + } + return (struct snd_util_memblk *)blk; +} + + +/* + * release DMA buffer from page table + */ +int snd_emu10k1_free_pages(struct snd_emu10k1 *emu, struct snd_util_memblk *blk) +{ + if (snd_BUG_ON(!emu || !blk)) + return -EINVAL; + return snd_emu10k1_synth_free(emu, blk); +} + +/* + * allocate DMA pages, widening the allocation if necessary + * + * See the comment above snd_emu10k1_detect_iommu() in emu10k1_main.c why + * this might be needed. + * + * If you modify this function check whether __synth_free_pages() also needs + * changes. + */ +int snd_emu10k1_alloc_pages_maybe_wider(struct snd_emu10k1 *emu, size_t size, + struct snd_dma_buffer *dmab) +{ + if (emu->iommu_workaround) { + size_t npages = DIV_ROUND_UP(size, PAGE_SIZE); + size_t size_real = npages * PAGE_SIZE; + + /* + * The device has been observed to accesses up to 256 extra + * bytes, but use 1k to be safe. + */ + if (size_real < size + 1024) + size += PAGE_SIZE; + } + + return snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + &emu->pci->dev, size, dmab); +} + +/* + * memory allocation using multiple pages (for synth) + * Unlike the DMA allocation above, non-contiguous pages are assined. + */ + +/* + * allocate a synth sample area + */ +struct snd_util_memblk * +snd_emu10k1_synth_alloc(struct snd_emu10k1 *hw, unsigned int size) +{ + struct snd_emu10k1_memblk *blk; + struct snd_util_memhdr *hdr = hw->memhdr; + + guard(mutex)(&hdr->block_mutex); + blk = (struct snd_emu10k1_memblk *)__snd_util_mem_alloc(hdr, size); + if (blk == NULL) + return NULL; + if (synth_alloc_pages(hw, blk)) { + __snd_util_mem_free(hdr, (struct snd_util_memblk *)blk); + return NULL; + } + snd_emu10k1_memblk_map(hw, blk); + return (struct snd_util_memblk *)blk; +} + +EXPORT_SYMBOL(snd_emu10k1_synth_alloc); + +/* + * free a synth sample area + */ +int +snd_emu10k1_synth_free(struct snd_emu10k1 *emu, struct snd_util_memblk *memblk) +{ + struct snd_util_memhdr *hdr = emu->memhdr; + struct snd_emu10k1_memblk *blk = (struct snd_emu10k1_memblk *)memblk; + + guard(mutex)(&hdr->block_mutex); + scoped_guard(spinlock_irqsave, &emu->memblk_lock) { + if (blk->mapped_page >= 0) + unmap_memblk(emu, blk); + } + synth_free_pages(emu, blk); + __snd_util_mem_free(hdr, memblk); + return 0; +} + +EXPORT_SYMBOL(snd_emu10k1_synth_free); + +/* check new allocation range */ +static void get_single_page_range(struct snd_util_memhdr *hdr, + struct snd_emu10k1_memblk *blk, + int *first_page_ret, int *last_page_ret) +{ + struct list_head *p; + struct snd_emu10k1_memblk *q; + int first_page, last_page; + first_page = blk->first_page; + p = blk->mem.list.prev; + if (p != &hdr->block) { + q = get_emu10k1_memblk(p, mem.list); + if (q->last_page == first_page) + first_page++; /* first page was already allocated */ + } + last_page = blk->last_page; + p = blk->mem.list.next; + if (p != &hdr->block) { + q = get_emu10k1_memblk(p, mem.list); + if (q->first_page == last_page) + last_page--; /* last page was already allocated */ + } + *first_page_ret = first_page; + *last_page_ret = last_page; +} + +/* release allocated pages */ +static void __synth_free_pages(struct snd_emu10k1 *emu, int first_page, + int last_page) +{ + struct snd_dma_buffer dmab; + int page; + + dmab.dev.type = SNDRV_DMA_TYPE_DEV; + dmab.dev.dev = &emu->pci->dev; + + for (page = first_page; page <= last_page; page++) { + if (emu->page_ptr_table[page] == NULL) + continue; + dmab.area = emu->page_ptr_table[page]; + dmab.addr = emu->page_addr_table[page]; + + /* + * please keep me in sync with logic in + * snd_emu10k1_alloc_pages_maybe_wider() + */ + dmab.bytes = PAGE_SIZE; + if (emu->iommu_workaround) + dmab.bytes *= 2; + + snd_dma_free_pages(&dmab); + emu->page_addr_table[page] = 0; + emu->page_ptr_table[page] = NULL; + } +} + +/* + * allocate kernel pages + */ +static int synth_alloc_pages(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk) +{ + int page, first_page, last_page; + struct snd_dma_buffer dmab; + + emu10k1_memblk_init(blk); + get_single_page_range(emu->memhdr, blk, &first_page, &last_page); + /* allocate kernel pages */ + for (page = first_page; page <= last_page; page++) { + if (snd_emu10k1_alloc_pages_maybe_wider(emu, PAGE_SIZE, + &dmab) < 0) + goto __fail; + if (!is_valid_page(emu, dmab.addr)) { + snd_dma_free_pages(&dmab); + goto __fail; + } + emu->page_addr_table[page] = dmab.addr; + emu->page_ptr_table[page] = dmab.area; + } + return 0; + +__fail: + /* release allocated pages */ + last_page = page - 1; + __synth_free_pages(emu, first_page, last_page); + + return -ENOMEM; +} + +/* + * free pages + */ +static int synth_free_pages(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk) +{ + int first_page, last_page; + + get_single_page_range(emu->memhdr, blk, &first_page, &last_page); + __synth_free_pages(emu, first_page, last_page); + return 0; +} + +/* calculate buffer pointer from offset address */ +static inline void *offset_ptr(struct snd_emu10k1 *emu, int page, int offset) +{ + char *ptr; + if (snd_BUG_ON(page < 0 || page >= emu->max_cache_pages)) + return NULL; + ptr = emu->page_ptr_table[page]; + if (! ptr) { + dev_err(emu->card->dev, + "access to NULL ptr: page = %d\n", page); + return NULL; + } + ptr += offset & (PAGE_SIZE - 1); + return (void*)ptr; +} + +/* + * memset(blk + offset, value, size) + */ +int snd_emu10k1_synth_memset(struct snd_emu10k1 *emu, struct snd_util_memblk *blk, + int offset, int size, u8 value) +{ + int page, nextofs, end_offset, temp, temp1; + void *ptr; + struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk; + + if (snd_BUG_ON(offset + size > p->mem.size)) + return -EFAULT; + + offset += blk->offset & (PAGE_SIZE - 1); + end_offset = offset + size; + page = get_aligned_page(offset); + do { + nextofs = aligned_page_offset(page + 1); + temp = nextofs - offset; + temp1 = end_offset - offset; + if (temp1 < temp) + temp = temp1; + ptr = offset_ptr(emu, page + p->first_page, offset); + if (ptr) + memset(ptr, value, temp); + offset = nextofs; + page++; + } while (offset < end_offset); + return 0; +} + +EXPORT_SYMBOL(snd_emu10k1_synth_memset); + +// Note that the value is assumed to be suitably repetitive. +static void xor_range(void *ptr, int size, u32 value) +{ + if ((long)ptr & 1) { + *(u8 *)ptr ^= (u8)value; + ptr++; + size--; + } + if (size > 1 && ((long)ptr & 2)) { + *(u16 *)ptr ^= (u16)value; + ptr += 2; + size -= 2; + } + while (size > 3) { + *(u32 *)ptr ^= value; + ptr += 4; + size -= 4; + } + if (size > 1) { + *(u16 *)ptr ^= (u16)value; + ptr += 2; + size -= 2; + } + if (size > 0) + *(u8 *)ptr ^= (u8)value; +} + +/* + * copy_from_user(blk + offset, data, size) ^ xor + */ +int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_memblk *blk, + int offset, const char __user *data, int size, u32 xor) +{ + int page, nextofs, end_offset, temp, temp1; + void *ptr; + struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk; + + if (snd_BUG_ON(offset + size > p->mem.size)) + return -EFAULT; + + offset += blk->offset & (PAGE_SIZE - 1); + end_offset = offset + size; + page = get_aligned_page(offset); + do { + nextofs = aligned_page_offset(page + 1); + temp = nextofs - offset; + temp1 = end_offset - offset; + if (temp1 < temp) + temp = temp1; + ptr = offset_ptr(emu, page + p->first_page, offset); + if (ptr) { + if (copy_from_user(ptr, data, temp)) + return -EFAULT; + if (xor) + xor_range(ptr, temp, xor); + } + offset = nextofs; + data += temp; + page++; + } while (offset < end_offset); + return 0; +} + +EXPORT_SYMBOL(snd_emu10k1_synth_copy_from_user); diff --git a/sound/pci/emu10k1/p16v.c b/sound/pci/emu10k1/p16v.c new file mode 100644 index 000000000..b74128e61 --- /dev/null +++ b/sound/pci/emu10k1/p16v.c @@ -0,0 +1,826 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by James Courtier-Dutton + * Driver p16v chips + * Version: 0.25 + * + * FEATURES currently supported: + * Output fixed at S32_LE, 2 channel to hw:0,0 + * Rates: 44.1, 48, 96, 192. + * + * Changelog: + * 0.8 + * Use separate card based buffer for periods table. + * 0.9 + * Use 2 channel output streams instead of 8 channel. + * (8 channel output streams might be good for ASIO type output) + * Corrected speaker output, so Front -> Front etc. + * 0.10 + * Fixed missed interrupts. + * 0.11 + * Add Sound card model number and names. + * Add Analog volume controls. + * 0.12 + * Corrected playback interrupts. Now interrupt per period, instead of half period. + * 0.13 + * Use single trigger for multichannel. + * 0.14 + * Mic capture now works at fixed: S32_LE, 96000Hz, Stereo. + * 0.15 + * Force buffer_size / period_size == INTEGER. + * 0.16 + * Update p16v.c to work with changed alsa api. + * 0.17 + * Update p16v.c to work with changed alsa api. Removed boot_devs. + * 0.18 + * Merging with snd-emu10k1 driver. + * 0.19 + * One stereo channel at 24bit now works. + * 0.20 + * Added better register defines. + * 0.21 + * Integrated with snd-emu10k1 driver. + * 0.22 + * Removed #if 0 ... #endif + * 0.23 + * Implement different capture rates. + * 0.24 + * Implement different capture source channels. + * e.g. When HD Capture source is set to SPDIF, + * setting HD Capture channel to 0 captures from CDROM digital input. + * setting HD Capture channel to 1 captures from SPDIF in. + * 0.25 + * Include capture buffer sizes. + * + * BUGS: + * Some stability problems when unloading the snd-p16v kernel module. + * -- + * + * TODO: + * SPDIF out. + * Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz. + * Currently capture fixed at 48000Hz. + * + * -- + * GENERAL INFO: + * Model: SB0240 + * P16V Chip: CA0151-DBS + * Audigy 2 Chip: CA0102-IAT + * AC97 Codec: STAC 9721 + * ADC: Philips 1361T (Stereo 24bit) + * DAC: CS4382-K (8-channel, 24bit, 192Khz) + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "p16v.h" + +#define SET_CHANNEL 0 /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */ +#define PCM_FRONT_CHANNEL 0 +#define PCM_REAR_CHANNEL 1 +#define PCM_CENTER_LFE_CHANNEL 2 +#define PCM_SIDE_CHANNEL 3 +#define CONTROL_FRONT_CHANNEL 0 +#define CONTROL_REAR_CHANNEL 3 +#define CONTROL_CENTER_LFE_CHANNEL 1 +#define CONTROL_SIDE_CHANNEL 2 + +/* Card IDs: + * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2002 -> Audigy2 ZS 7.1 Model:SB0350 + * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1007 -> Audigy2 6.1 Model:SB0240 + * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1002 -> Audigy2 Platinum Model:SB msb0240230009266 + * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2007 -> Audigy4 Pro Model:SB0380 M1SB0380472001901E + * + */ + + /* hardware definition */ +static const struct snd_pcm_hardware snd_p16v_playback_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_S32_LE, /* Only supports 24-bit samples padded to 32 bits. */ + .rates = SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 192000, + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = ((65536 - 64) * 8), + .period_bytes_min = 64, + .period_bytes_max = (65536 - 64), + .periods_min = 2, + .periods_max = 8, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_p16v_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (65536 - 64), + .period_bytes_min = 64, + .period_bytes_max = (65536 - 128) >> 1, /* size has to be N*64 bytes */ + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +/* open_playback callback */ +static int snd_p16v_pcm_open_playback_channel(struct snd_pcm_substream *substream, int channel_id) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + /* + dev_dbg(emu->card->dev, "epcm device=%d, channel_id=%d\n", + substream->pcm->device, channel_id); + */ + + runtime->hw = snd_p16v_playback_hw; + +#if 0 /* debug */ + dev_dbg(emu->card->dev, + "p16v: open channel_id=%d, channel=%p, use=0x%x\n", + channel_id, channel, channel->use); + dev_dbg(emu->card->dev, "open:channel_id=%d, chip=%p, channel=%p\n", + channel_id, chip, channel); +#endif /* debug */ + /* channel->interrupt = snd_p16v_pcm_channel_interrupt; */ + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + return 0; +} + +/* open_capture callback */ +static int snd_p16v_pcm_open_capture_channel(struct snd_pcm_substream *substream, int channel_id) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + /* + dev_dbg(emu->card->dev, "epcm device=%d, channel_id=%d\n", + substream->pcm->device, channel_id); + */ + + runtime->hw = snd_p16v_capture_hw; + + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + return 0; +} + + +/* close callback */ +static int snd_p16v_pcm_close_playback(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* close callback */ +static int snd_p16v_pcm_close_capture(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int snd_p16v_pcm_open_playback_front(struct snd_pcm_substream *substream) +{ + return snd_p16v_pcm_open_playback_channel(substream, PCM_FRONT_CHANNEL); +} + +static int snd_p16v_pcm_open_capture(struct snd_pcm_substream *substream) +{ + // Only using channel 0 for now, but the card has 2 channels. + return snd_p16v_pcm_open_capture_channel(substream, 0); +} + +static int snd_p16v_pcm_ioctl_playback(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + if (cmd == SNDRV_PCM_IOCTL1_SYNC_ID) { + static const unsigned char id[4] = { 'P', '1', '6', 'V' }; + snd_pcm_set_sync_per_card(substream, arg, id, 4); + return 0; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +/* prepare playback callback */ +static int snd_p16v_pcm_prepare_playback(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int channel = substream->pcm->device - emu->p16v_device_offset; + u32 *table_base = (u32 *)(emu->p16v_buffer->area+(8*16*channel)); + u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size); + int i; + u32 tmp; + +#if 0 /* debug */ + dev_dbg(emu->card->dev, + "prepare:channel_number=%d, rate=%d, " + "format=0x%x, channels=%d, buffer_size=%ld, " + "period_size=%ld, periods=%u, frames_to_bytes=%d\n", + channel, runtime->rate, runtime->format, runtime->channels, + runtime->buffer_size, runtime->period_size, + runtime->periods, frames_to_bytes(runtime, 1)); + dev_dbg(emu->card->dev, + "dma_addr=%x, dma_area=%p, table_base=%p\n", + runtime->dma_addr, runtime->dma_area, table_base); + dev_dbg(emu->card->dev, + "dma_addr=%x, dma_area=%p, dma_bytes(size)=%x\n", + emu->p16v_buffer->addr, emu->p16v_buffer->area, + emu->p16v_buffer->bytes); +#endif /* debug */ + tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, channel); + tmp &= ~(A_SPDIF_RATE_MASK | A_EHC_SRC48_MASK); + switch (runtime->rate) { + case 44100: + snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, + tmp | A_SPDIF_44100 | A_EHC_SRC48_44); + break; + case 96000: + snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, + tmp | A_SPDIF_96000 | A_EHC_SRC48_96); + break; + case 192000: + snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, + tmp | A_SPDIF_192000 | A_EHC_SRC48_192); + break; + case 48000: + default: + snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, + tmp | A_SPDIF_48000 | A_EHC_SRC48_BYPASS); + break; + } + /* FIXME: Check emu->buffer.size before actually writing to it. */ + for(i = 0; i < runtime->periods; i++) { + table_base[i*2]=runtime->dma_addr+(i*period_size_bytes); + table_base[(i*2)+1]=period_size_bytes<<16; + } + + snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_ADDR, channel, emu->p16v_buffer->addr+(8*16*channel)); + snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_SIZE, channel, (runtime->periods - 1) << 19); + snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_PTR, channel, 0); + snd_emu10k1_ptr20_write(emu, PLAYBACK_DMA_ADDR, channel, runtime->dma_addr); + //snd_emu10k1_ptr20_write(emu, PLAYBACK_PERIOD_SIZE, channel, frames_to_bytes(runtime, runtime->period_size)<<16); // buffer size in bytes + snd_emu10k1_ptr20_write(emu, PLAYBACK_PERIOD_SIZE, channel, 0); // buffer size in bytes + snd_emu10k1_ptr20_write(emu, PLAYBACK_POINTER, channel, 0); + snd_emu10k1_ptr20_write(emu, PLAYBACK_FIFO_END_ADDRESS, channel, 0); + snd_emu10k1_ptr20_write(emu, PLAYBACK_FIFO_POINTER, channel, 0); + + return 0; +} + +/* prepare capture callback */ +static int snd_p16v_pcm_prepare_capture(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int channel = substream->pcm->device - emu->p16v_device_offset; + + /* + dev_dbg(emu->card->dev, "prepare capture:channel_number=%d, rate=%d, " + "format=0x%x, channels=%d, buffer_size=%ld, period_size=%ld, " + "frames_to_bytes=%d\n", + channel, runtime->rate, runtime->format, runtime->channels, + runtime->buffer_size, runtime->period_size, + frames_to_bytes(runtime, 1)); + */ + switch (runtime->rate) { + case 44100: + snd_emu10k1_ptr_write(emu, A_I2S_CAPTURE_RATE, channel, A_I2S_CAPTURE_44100); + break; + case 96000: + snd_emu10k1_ptr_write(emu, A_I2S_CAPTURE_RATE, channel, A_I2S_CAPTURE_96000); + break; + case 192000: + snd_emu10k1_ptr_write(emu, A_I2S_CAPTURE_RATE, channel, A_I2S_CAPTURE_192000); + break; + case 48000: + default: + snd_emu10k1_ptr_write(emu, A_I2S_CAPTURE_RATE, channel, A_I2S_CAPTURE_48000); + break; + } + /* FIXME: Check emu->buffer.size before actually writing to it. */ + snd_emu10k1_ptr20_write(emu, CAPTURE_FIFO_POINTER, channel, 0); + snd_emu10k1_ptr20_write(emu, CAPTURE_DMA_ADDR, channel, runtime->dma_addr); + snd_emu10k1_ptr20_write(emu, CAPTURE_BUFFER_SIZE, channel, frames_to_bytes(runtime, runtime->buffer_size) << 16); // buffer size in bytes + snd_emu10k1_ptr20_write(emu, CAPTURE_POINTER, channel, 0); + //snd_emu10k1_ptr20_write(emu, CAPTURE_SOURCE, 0x0, 0x333300e4); /* Select MIC or Line in */ + //snd_emu10k1_ptr20_write(emu, EXTENDED_INT_MASK, 0, snd_emu10k1_ptr20_read(emu, EXTENDED_INT_MASK, 0) | (0x110000<emu_lock); + enable = inl(emu->port + INTE2) | intrenb; + outl(enable, emu->port + INTE2); +} + +static void snd_p16v_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb) +{ + unsigned int disable; + + guard(spinlock_irqsave)(&emu->emu_lock); + disable = inl(emu->port + INTE2) & (~intrenb); + outl(disable, emu->port + INTE2); +} + +static void snd_p16v_interrupt(struct snd_emu10k1 *emu) +{ + unsigned int status; + + while ((status = inl(emu->port + IPR2)) != 0) { + u32 mask = INTE2_PLAYBACK_CH_0_LOOP; /* Full Loop */ + + /* dev_dbg(emu->card->dev, "p16v status=0x%x\n", status); */ + if (status & mask) { + struct snd_pcm_substream *substream = + emu->pcm_p16v->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (runtime && runtime->private_data) { + snd_pcm_period_elapsed(substream); + } else { + dev_err(emu->card->dev, + "p16v: status: 0x%08x, mask=0x%08x\n", + status, mask); + } + } + if (status & 0x110000) { + struct snd_pcm_substream *substream = + emu->pcm_p16v->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + /* dev_info(emu->card->dev, "capture int found\n"); */ + if (runtime && runtime->private_data) { + /* dev_info(emu->card->dev, "capture period_elapsed\n"); */ + snd_pcm_period_elapsed(substream); + } + } + outl(status, emu->port + IPR2); /* ack all */ + } +} + +/* trigger_playback callback */ +static int snd_p16v_pcm_trigger_playback(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime; + int channel; + int result = 0; + struct snd_pcm_substream *s; + u32 basic = 0; + u32 inte = 0; + int running = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + running=1; + break; + case SNDRV_PCM_TRIGGER_STOP: + default: + running = 0; + break; + } + snd_pcm_group_for_each_entry(s, substream) { + if (snd_pcm_substream_chip(s) != emu || + s->stream != SNDRV_PCM_STREAM_PLAYBACK) + continue; + runtime = s->runtime; + channel = substream->pcm->device-emu->p16v_device_offset; + /* dev_dbg(emu->card->dev, "p16v channel=%d\n", channel); */ + runtime->private_data = (void *)(ptrdiff_t)running; + basic |= (0x1<card->dev, "basic=0x%x, inte=0x%x\n", basic, inte); */ + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_p16v_intr_enable(emu, inte); + snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)| (basic)); + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(basic)); + snd_p16v_intr_disable(emu, inte); + break; + default: + result = -EINVAL; + break; + } + return result; +} + +/* trigger_capture callback */ +static int snd_p16v_pcm_trigger_capture(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int channel = 0; + int result = 0; + u32 inte = INTE2_CAPTURE_CH_0_LOOP | INTE2_CAPTURE_CH_0_HALF_LOOP; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_p16v_intr_enable(emu, inte); + snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)|(0x100<private_data = (void *)1; + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(0x100<private_data = NULL; + break; + default: + result = -EINVAL; + break; + } + return result; +} + +/* pointer_playback callback */ +static snd_pcm_uframes_t +snd_p16v_pcm_pointer_playback(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t ptr, ptr1, ptr2,ptr3,ptr4 = 0; + int channel = substream->pcm->device - emu->p16v_device_offset; + + if (!runtime->private_data) + return 0; + + ptr3 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel); + ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel); + ptr4 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel); + if (ptr3 != ptr4) ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel); + ptr2 = bytes_to_frames(runtime, ptr1); + ptr2+= (ptr4 >> 3) * runtime->period_size; + ptr=ptr2; + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + + return ptr; +} + +/* pointer_capture callback */ +static snd_pcm_uframes_t +snd_p16v_pcm_pointer_capture(struct snd_pcm_substream *substream) +{ + struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t ptr, ptr1, ptr2 = 0; + int channel = 0; + + if (!runtime->private_data) + return 0; + + ptr1 = snd_emu10k1_ptr20_read(emu, CAPTURE_POINTER, channel); + ptr2 = bytes_to_frames(runtime, ptr1); + ptr=ptr2; + if (ptr >= runtime->buffer_size) { + ptr -= runtime->buffer_size; + dev_warn(emu->card->dev, "buffer capture limited!\n"); + } + /* + dev_dbg(emu->card->dev, "ptr1 = 0x%lx, ptr2=0x%lx, ptr=0x%lx, " + "buffer_size = 0x%x, period_size = 0x%x, bits=%d, rate=%d\n", + ptr1, ptr2, ptr, (int)runtime->buffer_size, + (int)runtime->period_size, (int)runtime->frame_bits, + (int)runtime->rate); + */ + return ptr; +} + +/* operators */ +static const struct snd_pcm_ops snd_p16v_playback_front_ops = { + .open = snd_p16v_pcm_open_playback_front, + .close = snd_p16v_pcm_close_playback, + .ioctl = snd_p16v_pcm_ioctl_playback, + .prepare = snd_p16v_pcm_prepare_playback, + .trigger = snd_p16v_pcm_trigger_playback, + .pointer = snd_p16v_pcm_pointer_playback, +}; + +static const struct snd_pcm_ops snd_p16v_capture_ops = { + .open = snd_p16v_pcm_open_capture, + .close = snd_p16v_pcm_close_capture, + .prepare = snd_p16v_pcm_prepare_capture, + .trigger = snd_p16v_pcm_trigger_capture, + .pointer = snd_p16v_pcm_pointer_capture, +}; + +int snd_p16v_pcm(struct snd_emu10k1 *emu, int device) +{ + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + int err; + int capture=1; + + /* dev_dbg(emu->card->dev, "snd_p16v_pcm called. device=%d\n", device); */ + emu->p16v_device_offset = device; + + err = snd_pcm_new(emu->card, "p16v", device, 1, capture, &pcm); + if (err < 0) + return err; + + pcm->private_data = emu; + // Single playback 8 channel device. + // Single capture 2 channel device. + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_p16v_playback_front_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_p16v_capture_ops); + + pcm->info_flags = 0; + pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + strscpy(pcm->name, "p16v"); + emu->pcm_p16v = pcm; + emu->p16v_interrupt = snd_p16v_interrupt; + + for(substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + substream; + substream = substream->next) { + snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV, + &emu->pci->dev, + (65536 - 64) * 8, + (65536 - 64) * 8); + /* + dev_dbg(emu->card->dev, + "preallocate playback substream: err=%d\n", err); + */ + } + + for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + substream; + substream = substream->next) { + snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV, + &emu->pci->dev, + 65536 - 64, 65536 - 64); + /* + dev_dbg(emu->card->dev, + "preallocate capture substream: err=%d\n", err); + */ + } + + return 0; +} + +static int snd_p16v_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 = 255; + return 0; +} + +static int snd_p16v_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + int high_low = (kcontrol->private_value >> 8) & 0xff; + int reg = kcontrol->private_value & 0xff; + u32 value; + + value = snd_emu10k1_ptr20_read(emu, reg, high_low); + if (high_low) { + ucontrol->value.integer.value[0] = 0xff - ((value >> 24) & 0xff); /* Left */ + ucontrol->value.integer.value[1] = 0xff - ((value >> 16) & 0xff); /* Right */ + } else { + ucontrol->value.integer.value[0] = 0xff - ((value >> 8) & 0xff); /* Left */ + ucontrol->value.integer.value[1] = 0xff - ((value >> 0) & 0xff); /* Right */ + } + return 0; +} + +static int snd_p16v_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + int high_low = (kcontrol->private_value >> 8) & 0xff; + int reg = kcontrol->private_value & 0xff; + u32 value, oval; + + oval = value = snd_emu10k1_ptr20_read(emu, reg, 0); + if (high_low == 1) { + value &= 0xffff; + value |= ((0xff - ucontrol->value.integer.value[0]) << 24) | + ((0xff - ucontrol->value.integer.value[1]) << 16); + } else { + value &= 0xffff0000; + value |= ((0xff - ucontrol->value.integer.value[0]) << 8) | + ((0xff - ucontrol->value.integer.value[1]) ); + } + if (value != oval) { + snd_emu10k1_ptr20_write(emu, reg, 0, value); + return 1; + } + return 0; +} + +static int snd_p16v_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[8] = { + "SPDIF", "I2S", "SRC48", "SRCMulti_SPDIF", "SRCMulti_I2S", + "CDIF", "FX", "AC97" + }; + + return snd_ctl_enum_info(uinfo, 1, 8, texts); +} + +static int snd_p16v_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->p16v_capture_source; + return 0; +} + +static int snd_p16v_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + u32 mask; + u32 source; + + val = ucontrol->value.enumerated.item[0] ; + if (val > 7) + return -EINVAL; + change = (emu->p16v_capture_source != val); + if (change) { + emu->p16v_capture_source = val; + source = (val << 28) | (val << 24) | (val << 20) | (val << 16); + mask = snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & 0xffff; + snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, source | mask); + } + return change; +} + +static int snd_p16v_capture_channel_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { "0", "1", "2", "3", }; + + return snd_ctl_enum_info(uinfo, 1, 4, texts); +} + +static int snd_p16v_capture_channel_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->p16v_capture_channel; + return 0; +} + +static int snd_p16v_capture_channel_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + u32 tmp; + + val = ucontrol->value.enumerated.item[0] ; + if (val > 3) + return -EINVAL; + change = (emu->p16v_capture_channel != val); + if (change) { + emu->p16v_capture_channel = val; + tmp = snd_emu10k1_ptr20_read(emu, CAPTURE_P16V_SOURCE, 0) & 0xfffc; + snd_emu10k1_ptr20_write(emu, CAPTURE_P16V_SOURCE, 0, tmp | val); + } + return change; +} +static const DECLARE_TLV_DB_SCALE(snd_p16v_db_scale1, -5175, 25, 1); + +#define P16V_VOL(xname,xreg,xhl) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = snd_p16v_volume_info, \ + .get = snd_p16v_volume_get, \ + .put = snd_p16v_volume_put, \ + .tlv = { .p = snd_p16v_db_scale1 }, \ + .private_value = ((xreg) | ((xhl) << 8)) \ +} + +static const struct snd_kcontrol_new p16v_mixer_controls[] = { + P16V_VOL("HD Analog Front Playback Volume", PLAYBACK_VOLUME_MIXER9, 0), + P16V_VOL("HD Analog Rear Playback Volume", PLAYBACK_VOLUME_MIXER10, 1), + P16V_VOL("HD Analog Center/LFE Playback Volume", PLAYBACK_VOLUME_MIXER9, 1), + P16V_VOL("HD Analog Side Playback Volume", PLAYBACK_VOLUME_MIXER10, 0), + P16V_VOL("HD SPDIF Front Playback Volume", PLAYBACK_VOLUME_MIXER7, 0), + P16V_VOL("HD SPDIF Rear Playback Volume", PLAYBACK_VOLUME_MIXER8, 1), + P16V_VOL("HD SPDIF Center/LFE Playback Volume", PLAYBACK_VOLUME_MIXER7, 1), + P16V_VOL("HD SPDIF Side Playback Volume", PLAYBACK_VOLUME_MIXER8, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HD source Capture", + .info = snd_p16v_capture_source_info, + .get = snd_p16v_capture_source_get, + .put = snd_p16v_capture_source_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HD channel Capture", + .info = snd_p16v_capture_channel_info, + .get = snd_p16v_capture_channel_get, + .put = snd_p16v_capture_channel_put + }, +}; + + +int snd_p16v_mixer(struct snd_emu10k1 *emu) +{ + int i, err; + struct snd_card *card = emu->card; + + for (i = 0; i < ARRAY_SIZE(p16v_mixer_controls); i++) { + err = snd_ctl_add(card, snd_ctl_new1(&p16v_mixer_controls[i], emu)); + if (err < 0) + return err; + } + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +#define NUM_CHS 1 /* up to 4, but only first channel is used */ + +int snd_p16v_alloc_pm_buffer(struct snd_emu10k1 *emu) +{ + emu->p16v_saved = vmalloc(array_size(NUM_CHS * 4, 0x80)); + if (! emu->p16v_saved) + return -ENOMEM; + return 0; +} + +void snd_p16v_free_pm_buffer(struct snd_emu10k1 *emu) +{ + vfree(emu->p16v_saved); +} + +void snd_p16v_suspend(struct snd_emu10k1 *emu) +{ + int i, ch; + unsigned int *val; + + val = emu->p16v_saved; + for (ch = 0; ch < NUM_CHS; ch++) + for (i = 0; i < 0x80; i++, val++) + *val = snd_emu10k1_ptr20_read(emu, i, ch); +} + +void snd_p16v_resume(struct snd_emu10k1 *emu) +{ + int i, ch; + unsigned int *val; + + val = emu->p16v_saved; + for (ch = 0; ch < NUM_CHS; ch++) + for (i = 0; i < 0x80; i++, val++) + snd_emu10k1_ptr20_write(emu, i, ch, *val); +} +#endif diff --git a/sound/pci/emu10k1/p16v.h b/sound/pci/emu10k1/p16v.h new file mode 100644 index 000000000..95ab80717 --- /dev/null +++ b/sound/pci/emu10k1/p16v.h @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) by James Courtier-Dutton + * Driver p16v chips + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + */ + +/********************************************************************************************************/ +/* Audigy2 P16V pointer-offset register set, accessed through the PTR2 and DATA2 registers */ +/********************************************************************************************************/ + +/* The sample rate of the SPDIF outputs is set by modifying a register in the EMU10K2 PTR register A_SPDIF_SAMPLERATE. + * The sample rate is also controlled by the same registers that control the rate of the EMU10K2 sample rate converters. + */ + +/* Initially all registers from 0x00 to 0x3f have zero contents. */ +#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */ + /* One list entry: 4 bytes for DMA address, + * 4 bytes for period_size << 16. + * One list entry is 8 bytes long. + * One list entry for each period in the buffer. + */ +#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */ +#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */ +#define PLAYBACK_UNKNOWN3 0x03 /* Not used */ +#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA address */ +#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size. win2000 uses 0x04000000 */ +#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */ +#define PLAYBACK_FIFO_END_ADDRESS 0x07 /* Playback FIFO end address */ +#define PLAYBACK_FIFO_POINTER 0x08 /* Playback FIFO pointer and number of valid sound samples in cache */ +#define PLAYBACK_UNKNOWN9 0x09 /* Not used */ +#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */ +#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */ +#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */ +#define CAPTURE_FIFO_POINTER 0x13 /* Capture FIFO pointer and number of valid sound samples in cache */ +#define CAPTURE_P16V_VOLUME1 0x14 /* Low: Capture volume 0xXXXX3030 */ +#define CAPTURE_P16V_VOLUME2 0x15 /* High:Has no effect on capture volume */ +#define CAPTURE_P16V_SOURCE 0x16 /* P16V source select. Set to 0x0700E4E5 for AC97 CAPTURE */ + /* [0:1] Capture input 0 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [3:2] Capture input 1 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [5:4] Capture input 2 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [7:6] Capture input 3 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [9:8] Playback input 0 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [11:10] Playback input 1 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [13:12] Playback input 2 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [15:14] Playback input 3 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [19:16] Playback mixer output enable. 1 bit per channel. + * [23:20] Capture mixer output enable. 1 bit per channel. + * [26:24] FX engine channel capture 0 = 0x60-0x67. + * 1 = 0x68-0x6f. + * 2 = 0x70-0x77. + * 3 = 0x78-0x7f. + * 4 = 0x80-0x87. + * 5 = 0x88-0x8f. + * 6 = 0x90-0x97. + * 7 = 0x98-0x9f. + * [31:27] Not used. + */ + + /* 0x1 = capture on. + * 0x100 = capture off. + * 0x200 = capture off. + * 0x1000 = capture off. + */ +#define CAPTURE_RATE_STATUS 0x17 /* Capture sample rate. Read only */ + /* [15:0] Not used. + * [18:16] Channel 0 Detected sample rate. 0 - 44.1khz + * 1 - 48 khz + * 2 - 96 khz + * 3 - 192 khz + * 7 - undefined rate. + * [19] Channel 0. 1 - Valid, 0 - Not Valid. + * [22:20] Channel 1 Detected sample rate. + * [23] Channel 1. 1 - Valid, 0 - Not Valid. + * [26:24] Channel 2 Detected sample rate. + * [27] Channel 2. 1 - Valid, 0 - Not Valid. + * [30:28] Channel 3 Detected sample rate. + * [31] Channel 3. 1 - Valid, 0 - Not Valid. + */ +/* 0x18 - 0x1f unused */ +#define PLAYBACK_LAST_SAMPLE 0x20 /* The sample currently being played. Read only */ +/* 0x21 - 0x3f unused */ +#define BASIC_INTERRUPT 0x40 /* Used by both playback and capture interrupt handler */ + /* Playback (0x1< 77770000 so it must be some sort of route. + * bit 0x1 starts DMA playback on channel_id 0 + */ +/* 0x41,42 take values from 0 - 0xffffffff, but have no effect on playback */ +/* 0x43,0x48 do not remember settings */ +/* 0x41-45 unused */ +#define WATERMARK 0x46 /* Test bit to indicate cache level usage */ + /* Values it can have while playing on channel 0. + * 0000f000, 0000f004, 0000f008, 0000f00c. + * Readonly. + */ +/* 0x47-0x4f unused */ +/* 0x50-0x5f Capture cache data */ +#define SRCSel 0x60 /* SRCSel. Default 0x4. Bypass P16V 0x14 */ + /* [0] 0 = 10K2 audio, 1 = SRC48 mixer output. + * [2] 0 = 10K2 audio, 1 = SRCMulti SPDIF mixer output. + * [4] 0 = 10K2 audio, 1 = SRCMulti I2S mixer output. + */ + /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */ + /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */ + /* SRC48 and SRCMULTI sample rate select and output select. */ + /* 0xffffffff -> 0xC0000015 + * 0xXXXXXXX4 = Enable Front Left/Right + * Enable PCMs + */ + +/* 0x61 -> 0x6c are Volume controls */ +#define PLAYBACK_VOLUME_MIXER1 0x61 /* SRC48 Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER2 0x62 /* SRC48 High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER3 0x63 /* SRCMULTI SPDIF Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER4 0x64 /* SRCMULTI SPDIF High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER5 0x65 /* SRCMULTI I2S Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER6 0x66 /* SRCMULTI I2S High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER7 0x67 /* P16V Low to SRCMULTI SPDIF mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER8 0x68 /* P16V High to SRCMULTI SPDIF mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER9 0x69 /* P16V Low to SRCMULTI I2S mixer input volume control. */ + /* 0xXXXX3030 = PCM0 Volume (Front). + * 0x3030XXXX = PCM1 Volume (Center) + */ +#define PLAYBACK_VOLUME_MIXER10 0x6a /* P16V High to SRCMULTI I2S mixer input volume control. */ + /* 0x3030XXXX = PCM3 Volume (Rear). */ +#define PLAYBACK_VOLUME_MIXER11 0x6b /* E10K2 Low to SRC48 mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER12 0x6c /* E10K2 High to SRC48 mixer input volume control. */ + +#define SRC48_ENABLE 0x6d /* SRC48 input audio enable */ + /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */ + /* [23:16] The corresponding P16V channel to SRC48 enabled if == 1. + * [31:24] The corresponding E10K2 channel to SRC48 enabled. + */ +#define SRCMULTI_ENABLE 0x6e /* SRCMulti input audio enable. Default 0xffffffff */ + /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */ + /* [7:0] The corresponding P16V channel to SRCMulti_I2S enabled if == 1. + * [15:8] The corresponding E10K2 channel to SRCMulti I2S enabled. + * [23:16] The corresponding P16V channel to SRCMulti SPDIF enabled. + * [31:24] The corresponding E10K2 channel to SRCMulti SPDIF enabled. + */ + /* Bypass P16V 0xff00ff00 + * Bitmap. 0 = Off, 1 = On. + * P16V playback outputs: + * 0xXXXXXXX1 = PCM0 Left. (Front) + * 0xXXXXXXX2 = PCM0 Right. + * 0xXXXXXXX4 = PCM1 Left. (Center/LFE) + * 0xXXXXXXX8 = PCM1 Right. + * 0xXXXXXX1X = PCM2 Left. (Unknown) + * 0xXXXXXX2X = PCM2 Right. + * 0xXXXXXX4X = PCM3 Left. (Rear) + * 0xXXXXXX8X = PCM3 Right. + */ +#define AUDIO_OUT_ENABLE 0x6f /* Default: 000100FF */ + /* [3:0] Does something, but not documented. Probably capture enable. + * [7:4] Playback channels enable. not documented. + * [16] AC97 output enable if == 1 + * [30] 0 = SRCMulti_I2S input from fxengine 0x68-0x6f. + * 1 = SRCMulti_I2S input from SRC48 output. + * [31] 0 = SRCMulti_SPDIF input from fxengine 0x60-0x67. + * 1 = SRCMulti_SPDIF input from SRC48 output. + */ + /* 0xffffffff -> C00100FF */ + /* 0 -> Not playback sound, irq still running */ + /* 0xXXXXXX10 = PCM0 Left/Right On. (Front) + * 0xXXXXXX20 = PCM1 Left/Right On. (Center/LFE) + * 0xXXXXXX40 = PCM2 Left/Right On. (Unknown) + * 0xXXXXXX80 = PCM3 Left/Right On. (Rear) + */ +#define PLAYBACK_SPDIF_SELECT 0x70 /* Default: 12030F00 */ + /* 0xffffffff -> 3FF30FFF */ + /* 0x00000001 pauses stream/irq fail. */ + /* All other bits do not effect playback */ +#define PLAYBACK_SPDIF_SRC_SELECT 0x71 /* Default: 0000E4E4 */ + /* 0xffffffff -> F33FFFFF */ + /* All bits do not effect playback */ +#define PLAYBACK_SPDIF_USER_DATA0 0x72 /* SPDIF out user data 0 */ +#define PLAYBACK_SPDIF_USER_DATA1 0x73 /* SPDIF out user data 1 */ +/* 0x74-0x75 unknown */ +#define CAPTURE_SPDIF_CONTROL 0x76 /* SPDIF in control setting */ +#define CAPTURE_SPDIF_STATUS 0x77 /* SPDIF in status */ +#define CAPURE_SPDIF_USER_DATA0 0x78 /* SPDIF in user data 0 */ +#define CAPURE_SPDIF_USER_DATA1 0x79 /* SPDIF in user data 1 */ +#define CAPURE_SPDIF_USER_DATA2 0x7a /* SPDIF in user data 2 */ + diff --git a/sound/pci/emu10k1/p17v.h b/sound/pci/emu10k1/p17v.h new file mode 100644 index 000000000..ee4f4ab4b --- /dev/null +++ b/sound/pci/emu10k1/p17v.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) by James Courtier-Dutton + * Driver p17v chips + */ + +/******************************************************************************/ +/* Audigy2Value Tina (P17V) pointer-offset register set, */ +/* accessed through the PTR2 and DATA2 registers */ +/******************************************************************************/ + +/* 00 - 07: Not used */ +#define P17V_PLAYBACK_FIFO_PTR 0x08 /* Current playback fifo pointer + * and number of sound samples in cache. + */ +/* 09 - 12: Not used */ +#define P17V_CAPTURE_FIFO_PTR 0x13 /* Current capture fifo pointer + * and number of sound samples in cache. + */ +/* 14 - 17: Not used */ +#define P17V_PB_CHN_SEL 0x18 /* P17v playback channel select */ +#define P17V_SE_SLOT_SEL_L 0x19 /* Sound Engine slot select low */ +#define P17V_SE_SLOT_SEL_H 0x1a /* Sound Engine slot select high */ +/* 1b - 1f: Not used */ +/* 20 - 2f: Not used */ +/* 30 - 3b: Not used */ +#define P17V_SPI 0x3c /* SPI interface register */ +#define P17V_I2C_ADDR 0x3d /* I2C Address */ +#define P17V_I2C_0 0x3e /* I2C Data */ +#define P17V_I2C_1 0x3f /* I2C Data */ +/* I2C values */ +#define I2C_A_ADC_ADD_MASK 0x000000fe /*The address is a 7 bit address */ +#define I2C_A_ADC_RW_MASK 0x00000001 /*bit mask for R/W */ +#define I2C_A_ADC_TRANS_MASK 0x00000010 /*Bit mask for I2c address DAC value */ +#define I2C_A_ADC_ABORT_MASK 0x00000020 /*Bit mask for I2C transaction abort flag */ +#define I2C_A_ADC_LAST_MASK 0x00000040 /*Bit mask for Last word transaction */ +#define I2C_A_ADC_BYTE_MASK 0x00000080 /*Bit mask for Byte Mode */ + +#define I2C_A_ADC_ADD 0x00000034 /*This is the Device address for ADC */ +#define I2C_A_ADC_READ 0x00000001 /*To perform a read operation */ +#define I2C_A_ADC_START 0x00000100 /*Start I2C transaction */ +#define I2C_A_ADC_ABORT 0x00000200 /*I2C transaction abort */ +#define I2C_A_ADC_LAST 0x00000400 /*I2C last transaction */ +#define I2C_A_ADC_BYTE 0x00000800 /*I2C one byte mode */ + +#define I2C_D_ADC_REG_MASK 0xfe000000 /*ADC address register */ +#define I2C_D_ADC_DAT_MASK 0x01ff0000 /*ADC data register */ + +#define ADC_TIMEOUT 0x00000007 /*ADC Timeout Clock Disable */ +#define ADC_IFC_CTRL 0x0000000b /*ADC Interface Control */ +#define ADC_MASTER 0x0000000c /*ADC Master Mode Control */ +#define ADC_POWER 0x0000000d /*ADC PowerDown Control */ +#define ADC_ATTEN_ADCL 0x0000000e /*ADC Attenuation ADCL */ +#define ADC_ATTEN_ADCR 0x0000000f /*ADC Attenuation ADCR */ +#define ADC_ALC_CTRL1 0x00000010 /*ADC ALC Control 1 */ +#define ADC_ALC_CTRL2 0x00000011 /*ADC ALC Control 2 */ +#define ADC_ALC_CTRL3 0x00000012 /*ADC ALC Control 3 */ +#define ADC_NOISE_CTRL 0x00000013 /*ADC Noise Gate Control */ +#define ADC_LIMIT_CTRL 0x00000014 /*ADC Limiter Control */ +#define ADC_MUX 0x00000015 /*ADC Mux offset */ +#if 0 +/* FIXME: Not tested yet. */ +#define ADC_GAIN_MASK 0x000000ff //Mask for ADC Gain +#define ADC_ZERODB 0x000000cf //Value to set ADC to 0dB +#define ADC_MUTE_MASK 0x000000c0 //Mask for ADC mute +#define ADC_MUTE 0x000000c0 //Value to mute ADC +#define ADC_OSR 0x00000008 //Mask for ADC oversample rate select +#define ADC_TIMEOUT_DISABLE 0x00000008 //Value and mask to disable Timeout clock +#define ADC_HPF_DISABLE 0x00000100 //Value and mask to disable High pass filter +#define ADC_TRANWIN_MASK 0x00000070 //Mask for Length of Transient Window +#endif + +#define ADC_MUX_MASK 0x0000000f //Mask for ADC Mux +#define ADC_MUX_0 0x00000001 //Value to select Unknown at ADC Mux (Not used) +#define ADC_MUX_1 0x00000002 //Value to select Unknown at ADC Mux (Not used) +#define ADC_MUX_2 0x00000004 //Value to select Mic at ADC Mux +#define ADC_MUX_3 0x00000008 //Value to select Line-In at ADC Mux + +#define P17V_START_AUDIO 0x40 /* Start Audio bit */ +/* 41 - 47: Reserved */ +#define P17V_START_CAPTURE 0x48 /* Start Capture bit */ +#define P17V_CAPTURE_FIFO_BASE 0x49 /* Record FIFO base address */ +#define P17V_CAPTURE_FIFO_SIZE 0x4a /* Record FIFO buffer size */ +#define P17V_CAPTURE_FIFO_INDEX 0x4b /* Record FIFO capture index */ +#define P17V_CAPTURE_VOL_H 0x4c /* P17v capture volume control */ +#define P17V_CAPTURE_VOL_L 0x4d /* P17v capture volume control */ +/* 4e - 4f: Not used */ +/* 50 - 5f: Not used */ +#define P17V_SRCSel 0x60 /* SRC48 and SRCMulti sample rate select + * and output select + */ +#define P17V_MIXER_AC97_10K1_VOL_L 0x61 /* 10K to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_10K1_VOL_H 0x62 /* 10K to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_P17V_VOL_L 0x63 /* P17V to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_P17V_VOL_H 0x64 /* P17V to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_SRP_REC_VOL_L 0x65 /* SRP Record to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_SRP_REC_VOL_H 0x66 /* SRP Record to Mixer_AC97 input volume control */ +/* 67 - 68: Reserved */ +#define P17V_MIXER_Spdif_10K1_VOL_L 0x69 /* 10K to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_10K1_VOL_H 0x6A /* 10K to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_P17V_VOL_L 0x6B /* P17V to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_P17V_VOL_H 0x6C /* P17V to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_SRP_REC_VOL_L 0x6D /* SRP Record to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_SRP_REC_VOL_H 0x6E /* SRP Record to Mixer_Spdif input volume control */ +/* 6f - 70: Reserved */ +#define P17V_MIXER_I2S_10K1_VOL_L 0x71 /* 10K to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_10K1_VOL_H 0x72 /* 10K to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_P17V_VOL_L 0x73 /* P17V to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_P17V_VOL_H 0x74 /* P17V to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_SRP_REC_VOL_L 0x75 /* SRP Record to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_SRP_REC_VOL_H 0x76 /* SRP Record to Mixer_I2S input volume control */ +/* 77 - 78: Reserved */ +#define P17V_MIXER_AC97_ENABLE 0x79 /* Mixer AC97 input audio enable */ +#define P17V_MIXER_SPDIF_ENABLE 0x7A /* Mixer SPDIF input audio enable */ +#define P17V_MIXER_I2S_ENABLE 0x7B /* Mixer I2S input audio enable */ +#define P17V_AUDIO_OUT_ENABLE 0x7C /* Audio out enable */ +#define P17V_MIXER_ATT 0x7D /* SRP Mixer Attenuation Select */ +#define P17V_SRP_RECORD_SRR 0x7E /* SRP Record channel source Select */ +#define P17V_SOFT_RESET_SRP_MIXER 0x7F /* SRP and mixer soft reset */ + +#define P17V_AC97_OUT_MASTER_VOL_L 0x80 /* AC97 Output master volume control */ +#define P17V_AC97_OUT_MASTER_VOL_H 0x81 /* AC97 Output master volume control */ +#define P17V_SPDIF_OUT_MASTER_VOL_L 0x82 /* SPDIF Output master volume control */ +#define P17V_SPDIF_OUT_MASTER_VOL_H 0x83 /* SPDIF Output master volume control */ +#define P17V_I2S_OUT_MASTER_VOL_L 0x84 /* I2S Output master volume control */ +#define P17V_I2S_OUT_MASTER_VOL_H 0x85 /* I2S Output master volume control */ +/* 86 - 87: Not used */ +#define P17V_I2S_CHANNEL_SWAP_PHASE_INVERSE 0x88 /* I2S out mono channel swap + * and phase inverse */ +#define P17V_SPDIF_CHANNEL_SWAP_PHASE_INVERSE 0x89 /* SPDIF out mono channel swap + * and phase inverse */ +/* 8A: Not used */ +#define P17V_SRP_P17V_ESR 0x8B /* SRP_P17V estimated sample rate and rate lock */ +#define P17V_SRP_REC_ESR 0x8C /* SRP_REC estimated sample rate and rate lock */ +#define P17V_SRP_BYPASS 0x8D /* srps channel bypass and srps bypass */ +/* 8E - 92: Not used */ +#define P17V_I2S_SRC_SEL 0x93 /* I2SIN mode sel */ + + + + + + diff --git a/sound/pci/emu10k1/timer.c b/sound/pci/emu10k1/timer.c new file mode 100644 index 000000000..1231ae2bf --- /dev/null +++ b/sound/pci/emu10k1/timer.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Lee Revell + * Clemens Ladisch + * Oswald Buddenhagen + * + * Routines for control of EMU10K1 chips + */ + +#include +#include +#include + +static int snd_emu10k1_timer_start(struct snd_timer *timer) +{ + struct snd_emu10k1 *emu; + unsigned int delay; + + emu = snd_timer_chip(timer); + delay = timer->sticks - 1; + if (delay < 5 ) /* minimum time is 5 ticks */ + delay = 5; + snd_emu10k1_intr_enable(emu, INTE_INTERVALTIMERENB); + outw(delay & TIMER_RATE_MASK, emu->port + TIMER); + return 0; +} + +static int snd_emu10k1_timer_stop(struct snd_timer *timer) +{ + struct snd_emu10k1 *emu; + + emu = snd_timer_chip(timer); + snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB); + return 0; +} + +static unsigned long snd_emu10k1_timer_c_resolution(struct snd_timer *timer) +{ + struct snd_emu10k1 *emu = snd_timer_chip(timer); + + if (emu->card_capabilities->emu_model && + emu->emu1010.word_clock == 44100) + return 22676; // 1 sample @ 44.1 kHz = 22.675736...us + else + return 20833; // 1 sample @ 48 kHz = 20.833...us +} + +static int snd_emu10k1_timer_precise_resolution(struct snd_timer *timer, + unsigned long *num, unsigned long *den) +{ + struct snd_emu10k1 *emu = snd_timer_chip(timer); + + *num = 1; + if (emu->card_capabilities->emu_model) + *den = emu->emu1010.word_clock; + else + *den = 48000; + return 0; +} + +static const struct snd_timer_hardware snd_emu10k1_timer_hw = { + .flags = SNDRV_TIMER_HW_AUTO, + .ticks = 1024, + .start = snd_emu10k1_timer_start, + .stop = snd_emu10k1_timer_stop, + .c_resolution = snd_emu10k1_timer_c_resolution, + .precise_resolution = snd_emu10k1_timer_precise_resolution, +}; + +int snd_emu10k1_timer(struct snd_emu10k1 *emu, int device) +{ + struct snd_timer *timer = NULL; + struct snd_timer_id tid; + int err; + + tid.dev_class = SNDRV_TIMER_CLASS_CARD; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = emu->card->number; + tid.device = device; + tid.subdevice = 0; + err = snd_timer_new(emu->card, "EMU10K1", &tid, &timer); + if (err >= 0) { + strscpy(timer->name, "EMU10K1 timer"); + timer->private_data = emu; + timer->hw = snd_emu10k1_timer_hw; + } + emu->timer = timer; + return err; +} diff --git a/sound/pci/emu10k1/tina2.h b/sound/pci/emu10k1/tina2.h new file mode 100644 index 000000000..e3fcb2902 --- /dev/null +++ b/sound/pci/emu10k1/tina2.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) by James Courtier-Dutton + * Driver tina2 chips + */ + +/********************************************************************************************************/ +/* Audigy2 Tina2 (notebook) pointer-offset register set, accessed through the PTR2 and DATA2 registers */ +/********************************************************************************************************/ + +#define TINA2_VOLUME 0x71 /* Attenuate playback volume to prevent distortion. */ + /* The windows driver does not use this register, + * so it must use some other attenuation method. + * Without this, the output is 12dB too loud, + * resulting in distortion. + */ + diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c new file mode 100644 index 000000000..7fe1d1727 --- /dev/null +++ b/sound/pci/emu10k1/voice.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Lee Revell + * Oswald Buddenhagen + * Creative Labs, Inc. + * + * Routines for control of EMU10K1 chips - voice manager + */ + +#include +#include +#include +#include + +/* Previously the voice allocator started at 0 every time. The new voice + * allocator uses a round robin scheme. The next free voice is tracked in + * the card record and each allocation begins where the last left off. The + * hardware requires stereo interleaved voices be aligned to an even/odd + * boundary. + * --rlrevell + */ + +static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, + struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) +{ + struct snd_emu10k1_voice *voice; + int i, j, k, skip; + + for (i = emu->next_free_voice, j = 0; j < NUM_G; i = (i + skip) % NUM_G, j += skip) { + /* + dev_dbg(emu->card->dev, "i %d j %d next free %d!\n", + i, j, emu->next_free_voice); + */ + + /* stereo voices must be even/odd */ + if ((number > 1) && (i % 2)) { + skip = 1; + continue; + } + + for (k = 0; k < number; k++) { + voice = &emu->voices[i + k]; + if (voice->use) { + skip = k + 1; + goto next; + } + } + + for (k = 0; k < number; k++) { + voice = &emu->voices[i + k]; + voice->use = type; + voice->epcm = epcm; + /* dev_dbg(emu->card->dev, "allocated voice %d\n", i + k); */ + } + voice->last = 1; + + *rvoice = &emu->voices[i]; + emu->next_free_voice = (i + number) % NUM_G; + return 0; + + next: ; + } + return -ENOMEM; // -EBUSY would have been better +} + +static void voice_free(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *pvoice) +{ + if (pvoice->dirty) + snd_emu10k1_voice_init(emu, pvoice->number); + pvoice->interrupt = NULL; + pvoice->use = pvoice->dirty = pvoice->last = 0; + pvoice->epcm = NULL; +} + +int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels, + struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) +{ + int result; + + if (snd_BUG_ON(!rvoice)) + return -EINVAL; + if (snd_BUG_ON(!count)) + return -EINVAL; + if (snd_BUG_ON(!channels)) + return -EINVAL; + + guard(spinlock_irqsave)(&emu->voice_lock); + for (int got = 0; got < channels; ) { + result = voice_alloc(emu, type, count, epcm, &rvoice[got]); + if (result == 0) { + got++; + /* + dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n", + rvoice[got - 1]->number, got, want); + */ + continue; + } + if (type != EMU10K1_SYNTH && emu->get_synth_voice) { + /* free a voice from synth */ + result = emu->get_synth_voice(emu); + if (result >= 0) { + voice_free(emu, &emu->voices[result]); + continue; + } + } + for (int i = 0; i < got; i++) { + for (int j = 0; j < count; j++) + voice_free(emu, rvoice[i] + j); + rvoice[i] = NULL; + } + break; + } + + return result; +} + +EXPORT_SYMBOL(snd_emu10k1_voice_alloc); + +int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *pvoice) +{ + int last; + + if (snd_BUG_ON(!pvoice)) + return -EINVAL; + guard(spinlock_irqsave)(&emu->voice_lock); + do { + last = pvoice->last; + voice_free(emu, pvoice++); + } while (!last); + return 0; +} + +EXPORT_SYMBOL(snd_emu10k1_voice_free); diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c new file mode 100644 index 000000000..0b9eb55a2 --- /dev/null +++ b/sound/pci/ens1370.c @@ -0,0 +1,2350 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Ensoniq ES1370/ES1371 AudioPCI soundcard + * Copyright (c) by Jaroslav Kysela , + * Thomas Sailer + */ + +/* Power-Management-Code ( CONFIG_PM ) + * for ens1371 only ( FIXME ) + * derived from cs4281.c, atiixp.c and via82xx.c + * using https://www.kernel.org/doc/html/latest/sound/kernel-api/writing-an-alsa-driver.html + * by Kurt J. Bosch + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#ifdef CHIP1371 +#include +#else +#include +#endif +#include +#include + +#ifndef CHIP1371 +#undef CHIP1370 +#define CHIP1370 +#endif + +#ifdef CHIP1370 +#define DRIVER_NAME "ENS1370" +#define CHIP_NAME "ES1370" /* it can be ENS but just to keep compatibility... */ +#else +#define DRIVER_NAME "ENS1371" +#define CHIP_NAME "ES1371" +#endif + + +MODULE_AUTHOR("Jaroslav Kysela , Thomas Sailer "); +MODULE_LICENSE("GPL"); +#ifdef CHIP1370 +MODULE_DESCRIPTION("Ensoniq AudioPCI ES1370"); +#endif +#ifdef CHIP1371 +MODULE_DESCRIPTION("Ensoniq/Creative AudioPCI ES1371+"); +#endif + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK +#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_PNP; /* Enable switches */ +#ifdef SUPPORT_JOYSTICK +#ifdef CHIP1371 +static int joystick_port[SNDRV_CARDS]; +#else +static bool joystick[SNDRV_CARDS]; +#endif +#endif +#ifdef CHIP1371 +static int spdif[SNDRV_CARDS]; +static int lineio[SNDRV_CARDS]; +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Ensoniq AudioPCI soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Ensoniq AudioPCI soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Ensoniq AudioPCI soundcard."); +#ifdef SUPPORT_JOYSTICK +#ifdef CHIP1371 +module_param_hw_array(joystick_port, int, ioport, NULL, 0444); +MODULE_PARM_DESC(joystick_port, "Joystick port address."); +#else +module_param_array(joystick, bool, NULL, 0444); +MODULE_PARM_DESC(joystick, "Enable joystick."); +#endif +#endif /* SUPPORT_JOYSTICK */ +#ifdef CHIP1371 +module_param_array(spdif, int, NULL, 0444); +MODULE_PARM_DESC(spdif, "S/PDIF output (-1 = none, 0 = auto, 1 = force)."); +module_param_array(lineio, int, NULL, 0444); +MODULE_PARM_DESC(lineio, "Line In to Rear Out (0 = auto, 1 = force)."); +#endif + +/* ES1371 chip ID */ +/* This is a little confusing because all ES1371 compatible chips have the + same DEVICE_ID, the only thing differentiating them is the REV_ID field. + This is only significant if you want to enable features on the later parts. + Yes, I know it's stupid and why didn't we use the sub IDs? +*/ +#define ES1371REV_ES1373_A 0x04 +#define ES1371REV_ES1373_B 0x06 +#define ES1371REV_CT5880_A 0x07 +#define CT5880REV_CT5880_C 0x02 +#define CT5880REV_CT5880_D 0x03 /* ??? -jk */ +#define CT5880REV_CT5880_E 0x04 /* mw */ +#define ES1371REV_ES1371_B 0x09 +#define EV1938REV_EV1938_A 0x00 +#define ES1371REV_ES1373_8 0x08 + +/* + * Direct registers + */ + +#define ES_REG(ensoniq, x) ((ensoniq)->port + ES_REG_##x) + +#define ES_REG_CONTROL 0x00 /* R/W: Interrupt/Chip select control register */ +#define ES_1370_ADC_STOP (1<<31) /* disable capture buffer transfers */ +#define ES_1370_XCTL1 (1<<30) /* general purpose output bit */ +#define ES_1373_BYPASS_P1 (1<<31) /* bypass SRC for PB1 */ +#define ES_1373_BYPASS_P2 (1<<30) /* bypass SRC for PB2 */ +#define ES_1373_BYPASS_R (1<<29) /* bypass SRC for REC */ +#define ES_1373_TEST_BIT (1<<28) /* should be set to 0 for normal operation */ +#define ES_1373_RECEN_B (1<<27) /* mix record with playback for I2S/SPDIF out */ +#define ES_1373_SPDIF_THRU (1<<26) /* 0 = SPDIF thru mode, 1 = SPDIF == dig out */ +#define ES_1371_JOY_ASEL(o) (((o)&0x03)<<24)/* joystick port mapping */ +#define ES_1371_JOY_ASELM (0x03<<24) /* mask for above */ +#define ES_1371_JOY_ASELI(i) (((i)>>24)&0x03) +#define ES_1371_GPIO_IN(i) (((i)>>20)&0x0f)/* GPIO in [3:0] pins - R/O */ +#define ES_1370_PCLKDIVO(o) (((o)&0x1fff)<<16)/* clock divide ratio for DAC2 */ +#define ES_1370_PCLKDIVM ((0x1fff)<<16) /* mask for above */ +#define ES_1370_PCLKDIVI(i) (((i)>>16)&0x1fff)/* clock divide ratio for DAC2 */ +#define ES_1371_GPIO_OUT(o) (((o)&0x0f)<<16)/* GPIO out [3:0] pins - W/R */ +#define ES_1371_GPIO_OUTM (0x0f<<16) /* mask for above */ +#define ES_MSFMTSEL (1<<15) /* MPEG serial data format; 0 = SONY, 1 = I2S */ +#define ES_1370_M_SBB (1<<14) /* clock source for DAC - 0 = clock generator; 1 = MPEG clocks */ +#define ES_1371_SYNC_RES (1<<14) /* Warm AC97 reset */ +#define ES_1370_WTSRSEL(o) (((o)&0x03)<<12)/* fixed frequency clock for DAC1 */ +#define ES_1370_WTSRSELM (0x03<<12) /* mask for above */ +#define ES_1371_ADC_STOP (1<<13) /* disable CCB transfer capture information */ +#define ES_1371_PWR_INTRM (1<<12) /* power level change interrupts enable */ +#define ES_1370_DAC_SYNC (1<<11) /* DAC's are synchronous */ +#define ES_1371_M_CB (1<<11) /* capture clock source; 0 = AC'97 ADC; 1 = I2S */ +#define ES_CCB_INTRM (1<<10) /* CCB voice interrupts enable */ +#define ES_1370_M_CB (1<<9) /* capture clock source; 0 = ADC; 1 = MPEG */ +#define ES_1370_XCTL0 (1<<8) /* generap purpose output bit */ +#define ES_1371_PDLEV(o) (((o)&0x03)<<8) /* current power down level */ +#define ES_1371_PDLEVM (0x03<<8) /* mask for above */ +#define ES_BREQ (1<<7) /* memory bus request enable */ +#define ES_DAC1_EN (1<<6) /* DAC1 playback channel enable */ +#define ES_DAC2_EN (1<<5) /* DAC2 playback channel enable */ +#define ES_ADC_EN (1<<4) /* ADC capture channel enable */ +#define ES_UART_EN (1<<3) /* UART enable */ +#define ES_JYSTK_EN (1<<2) /* Joystick module enable */ +#define ES_1370_CDC_EN (1<<1) /* Codec interface enable */ +#define ES_1371_XTALCKDIS (1<<1) /* Xtal clock disable */ +#define ES_1370_SERR_DISABLE (1<<0) /* PCI serr signal disable */ +#define ES_1371_PCICLKDIS (1<<0) /* PCI clock disable */ +#define ES_REG_STATUS 0x04 /* R/O: Interrupt/Chip select status register */ +#define ES_INTR (1<<31) /* Interrupt is pending */ +#define ES_1371_ST_AC97_RST (1<<29) /* CT5880 AC'97 Reset bit */ +#define ES_1373_REAR_BIT27 (1<<27) /* rear bits: 000 - front, 010 - mirror, 101 - separate */ +#define ES_1373_REAR_BIT26 (1<<26) +#define ES_1373_REAR_BIT24 (1<<24) +#define ES_1373_GPIO_INT_EN(o)(((o)&0x0f)<<20)/* GPIO [3:0] pins - interrupt enable */ +#define ES_1373_SPDIF_EN (1<<18) /* SPDIF enable */ +#define ES_1373_SPDIF_TEST (1<<17) /* SPDIF test */ +#define ES_1371_TEST (1<<16) /* test ASIC */ +#define ES_1373_GPIO_INT(i) (((i)&0x0f)>>12)/* GPIO [3:0] pins - interrupt pending */ +#define ES_1370_CSTAT (1<<10) /* CODEC is busy or register write in progress */ +#define ES_1370_CBUSY (1<<9) /* CODEC is busy */ +#define ES_1370_CWRIP (1<<8) /* CODEC register write in progress */ +#define ES_1371_SYNC_ERR (1<<8) /* CODEC synchronization error occurred */ +#define ES_1371_VC(i) (((i)>>6)&0x03) /* voice code from CCB module */ +#define ES_1370_VC(i) (((i)>>5)&0x03) /* voice code from CCB module */ +#define ES_1371_MPWR (1<<5) /* power level interrupt pending */ +#define ES_MCCB (1<<4) /* CCB interrupt pending */ +#define ES_UART (1<<3) /* UART interrupt pending */ +#define ES_DAC1 (1<<2) /* DAC1 channel interrupt pending */ +#define ES_DAC2 (1<<1) /* DAC2 channel interrupt pending */ +#define ES_ADC (1<<0) /* ADC channel interrupt pending */ +#define ES_REG_UART_DATA 0x08 /* R/W: UART data register */ +#define ES_REG_UART_STATUS 0x09 /* R/O: UART status register */ +#define ES_RXINT (1<<7) /* RX interrupt occurred */ +#define ES_TXINT (1<<2) /* TX interrupt occurred */ +#define ES_TXRDY (1<<1) /* transmitter ready */ +#define ES_RXRDY (1<<0) /* receiver ready */ +#define ES_REG_UART_CONTROL 0x09 /* W/O: UART control register */ +#define ES_RXINTEN (1<<7) /* RX interrupt enable */ +#define ES_TXINTENO(o) (((o)&0x03)<<5) /* TX interrupt enable */ +#define ES_TXINTENM (0x03<<5) /* mask for above */ +#define ES_TXINTENI(i) (((i)>>5)&0x03) +#define ES_CNTRL(o) (((o)&0x03)<<0) /* control */ +#define ES_CNTRLM (0x03<<0) /* mask for above */ +#define ES_REG_UART_RES 0x0a /* R/W: UART reserver register */ +#define ES_TEST_MODE (1<<0) /* test mode enabled */ +#define ES_REG_MEM_PAGE 0x0c /* R/W: Memory page register */ +#define ES_MEM_PAGEO(o) (((o)&0x0f)<<0) /* memory page select - out */ +#define ES_MEM_PAGEM (0x0f<<0) /* mask for above */ +#define ES_MEM_PAGEI(i) (((i)>>0)&0x0f) /* memory page select - in */ +#define ES_REG_1370_CODEC 0x10 /* W/O: Codec write register address */ +#define ES_1370_CODEC_WRITE(a,d) ((((a)&0xff)<<8)|(((d)&0xff)<<0)) +#define ES_REG_1371_CODEC 0x14 /* W/R: Codec Read/Write register address */ +#define ES_1371_CODEC_RDY (1<<31) /* codec ready */ +#define ES_1371_CODEC_WIP (1<<30) /* codec register access in progress */ +#define EV_1938_CODEC_MAGIC (1<<26) +#define ES_1371_CODEC_PIRD (1<<23) /* codec read/write select register */ +#define ES_1371_CODEC_WRITE(a,d) ((((a)&0x7f)<<16)|(((d)&0xffff)<<0)) +#define ES_1371_CODEC_READS(a) ((((a)&0x7f)<<16)|ES_1371_CODEC_PIRD) +#define ES_1371_CODEC_READ(i) (((i)>>0)&0xffff) + +#define ES_REG_1371_SMPRATE 0x10 /* W/R: Codec rate converter interface register */ +#define ES_1371_SRC_RAM_ADDRO(o) (((o)&0x7f)<<25)/* address of the sample rate converter */ +#define ES_1371_SRC_RAM_ADDRM (0x7f<<25) /* mask for above */ +#define ES_1371_SRC_RAM_ADDRI(i) (((i)>>25)&0x7f)/* address of the sample rate converter */ +#define ES_1371_SRC_RAM_WE (1<<24) /* R/W: read/write control for sample rate converter */ +#define ES_1371_SRC_RAM_BUSY (1<<23) /* R/O: sample rate memory is busy */ +#define ES_1371_SRC_DISABLE (1<<22) /* sample rate converter disable */ +#define ES_1371_DIS_P1 (1<<21) /* playback channel 1 accumulator update disable */ +#define ES_1371_DIS_P2 (1<<20) /* playback channel 1 accumulator update disable */ +#define ES_1371_DIS_R1 (1<<19) /* capture channel accumulator update disable */ +#define ES_1371_SRC_RAM_DATAO(o) (((o)&0xffff)<<0)/* current value of the sample rate converter */ +#define ES_1371_SRC_RAM_DATAM (0xffff<<0) /* mask for above */ +#define ES_1371_SRC_RAM_DATAI(i) (((i)>>0)&0xffff)/* current value of the sample rate converter */ + +#define ES_REG_1371_LEGACY 0x18 /* W/R: Legacy control/status register */ +#define ES_1371_JFAST (1<<31) /* fast joystick timing */ +#define ES_1371_HIB (1<<30) /* host interrupt blocking enable */ +#define ES_1371_VSB (1<<29) /* SB; 0 = addr 0x220xH, 1 = 0x22FxH */ +#define ES_1371_VMPUO(o) (((o)&0x03)<<27)/* base register address; 0 = 0x320xH; 1 = 0x330xH; 2 = 0x340xH; 3 = 0x350xH */ +#define ES_1371_VMPUM (0x03<<27) /* mask for above */ +#define ES_1371_VMPUI(i) (((i)>>27)&0x03)/* base register address */ +#define ES_1371_VCDCO(o) (((o)&0x03)<<25)/* CODEC; 0 = 0x530xH; 1 = undefined; 2 = 0xe80xH; 3 = 0xF40xH */ +#define ES_1371_VCDCM (0x03<<25) /* mask for above */ +#define ES_1371_VCDCI(i) (((i)>>25)&0x03)/* CODEC address */ +#define ES_1371_FIRQ (1<<24) /* force an interrupt */ +#define ES_1371_SDMACAP (1<<23) /* enable event capture for slave DMA controller */ +#define ES_1371_SPICAP (1<<22) /* enable event capture for slave IRQ controller */ +#define ES_1371_MDMACAP (1<<21) /* enable event capture for master DMA controller */ +#define ES_1371_MPICAP (1<<20) /* enable event capture for master IRQ controller */ +#define ES_1371_ADCAP (1<<19) /* enable event capture for ADLIB register; 0x388xH */ +#define ES_1371_SVCAP (1<<18) /* enable event capture for SB registers */ +#define ES_1371_CDCCAP (1<<17) /* enable event capture for CODEC registers */ +#define ES_1371_BACAP (1<<16) /* enable event capture for SoundScape base address */ +#define ES_1371_EXI(i) (((i)>>8)&0x07) /* event number */ +#define ES_1371_AI(i) (((i)>>3)&0x1f) /* event significant I/O address */ +#define ES_1371_WR (1<<2) /* event capture; 0 = read; 1 = write */ +#define ES_1371_LEGINT (1<<0) /* interrupt for legacy events; 0 = interrupt did occur */ + +#define ES_REG_CHANNEL_STATUS 0x1c /* R/W: first 32-bits from S/PDIF channel status block, es1373 */ + +#define ES_REG_SERIAL 0x20 /* R/W: Serial interface control register */ +#define ES_1371_DAC_TEST (1<<22) /* DAC test mode enable */ +#define ES_P2_END_INCO(o) (((o)&0x07)<<19)/* binary offset value to increment / loop end */ +#define ES_P2_END_INCM (0x07<<19) /* mask for above */ +#define ES_P2_END_INCI(i) (((i)>>16)&0x07)/* binary offset value to increment / loop end */ +#define ES_P2_ST_INCO(o) (((o)&0x07)<<16)/* binary offset value to increment / start */ +#define ES_P2_ST_INCM (0x07<<16) /* mask for above */ +#define ES_P2_ST_INCI(i) (((i)<<16)&0x07)/* binary offset value to increment / start */ +#define ES_R1_LOOP_SEL (1<<15) /* ADC; 0 - loop mode; 1 = stop mode */ +#define ES_P2_LOOP_SEL (1<<14) /* DAC2; 0 - loop mode; 1 = stop mode */ +#define ES_P1_LOOP_SEL (1<<13) /* DAC1; 0 - loop mode; 1 = stop mode */ +#define ES_P2_PAUSE (1<<12) /* DAC2; 0 - play mode; 1 = pause mode */ +#define ES_P1_PAUSE (1<<11) /* DAC1; 0 - play mode; 1 = pause mode */ +#define ES_R1_INT_EN (1<<10) /* ADC interrupt enable */ +#define ES_P2_INT_EN (1<<9) /* DAC2 interrupt enable */ +#define ES_P1_INT_EN (1<<8) /* DAC1 interrupt enable */ +#define ES_P1_SCT_RLD (1<<7) /* force sample counter reload for DAC1 */ +#define ES_P2_DAC_SEN (1<<6) /* when stop mode: 0 - DAC2 play back zeros; 1 = DAC2 play back last sample */ +#define ES_R1_MODEO(o) (((o)&0x03)<<4) /* ADC mode; 0 = 8-bit mono; 1 = 8-bit stereo; 2 = 16-bit mono; 3 = 16-bit stereo */ +#define ES_R1_MODEM (0x03<<4) /* mask for above */ +#define ES_R1_MODEI(i) (((i)>>4)&0x03) +#define ES_P2_MODEO(o) (((o)&0x03)<<2) /* DAC2 mode; -- '' -- */ +#define ES_P2_MODEM (0x03<<2) /* mask for above */ +#define ES_P2_MODEI(i) (((i)>>2)&0x03) +#define ES_P1_MODEO(o) (((o)&0x03)<<0) /* DAC1 mode; -- '' -- */ +#define ES_P1_MODEM (0x03<<0) /* mask for above */ +#define ES_P1_MODEI(i) (((i)>>0)&0x03) + +#define ES_REG_DAC1_COUNT 0x24 /* R/W: DAC1 sample count register */ +#define ES_REG_DAC2_COUNT 0x28 /* R/W: DAC2 sample count register */ +#define ES_REG_ADC_COUNT 0x2c /* R/W: ADC sample count register */ +#define ES_REG_CURR_COUNT(i) (((i)>>16)&0xffff) +#define ES_REG_COUNTO(o) (((o)&0xffff)<<0) +#define ES_REG_COUNTM (0xffff<<0) +#define ES_REG_COUNTI(i) (((i)>>0)&0xffff) + +#define ES_REG_DAC1_FRAME 0x30 /* R/W: PAGE 0x0c; DAC1 frame address */ +#define ES_REG_DAC1_SIZE 0x34 /* R/W: PAGE 0x0c; DAC1 frame size */ +#define ES_REG_DAC2_FRAME 0x38 /* R/W: PAGE 0x0c; DAC2 frame address */ +#define ES_REG_DAC2_SIZE 0x3c /* R/W: PAGE 0x0c; DAC2 frame size */ +#define ES_REG_ADC_FRAME 0x30 /* R/W: PAGE 0x0d; ADC frame address */ +#define ES_REG_ADC_SIZE 0x34 /* R/W: PAGE 0x0d; ADC frame size */ +#define ES_REG_FCURR_COUNTO(o) (((o)&0xffff)<<16) +#define ES_REG_FCURR_COUNTM (0xffff<<16) +#define ES_REG_FCURR_COUNTI(i) (((i)>>14)&0x3fffc) +#define ES_REG_FSIZEO(o) (((o)&0xffff)<<0) +#define ES_REG_FSIZEM (0xffff<<0) +#define ES_REG_FSIZEI(i) (((i)>>0)&0xffff) +#define ES_REG_PHANTOM_FRAME 0x38 /* R/W: PAGE 0x0d: phantom frame address */ +#define ES_REG_PHANTOM_COUNT 0x3c /* R/W: PAGE 0x0d: phantom frame count */ + +#define ES_REG_UART_FIFO 0x30 /* R/W: PAGE 0x0e; UART FIFO register */ +#define ES_REG_UF_VALID (1<<8) +#define ES_REG_UF_BYTEO(o) (((o)&0xff)<<0) +#define ES_REG_UF_BYTEM (0xff<<0) +#define ES_REG_UF_BYTEI(i) (((i)>>0)&0xff) + + +/* + * Pages + */ + +#define ES_PAGE_DAC 0x0c +#define ES_PAGE_ADC 0x0d +#define ES_PAGE_UART 0x0e +#define ES_PAGE_UART1 0x0f + +/* + * Sample rate converter addresses + */ + +#define ES_SMPREG_DAC1 0x70 +#define ES_SMPREG_DAC2 0x74 +#define ES_SMPREG_ADC 0x78 +#define ES_SMPREG_VOL_ADC 0x6c +#define ES_SMPREG_VOL_DAC1 0x7c +#define ES_SMPREG_VOL_DAC2 0x7e +#define ES_SMPREG_TRUNC_N 0x00 +#define ES_SMPREG_INT_REGS 0x01 +#define ES_SMPREG_ACCUM_FRAC 0x02 +#define ES_SMPREG_VFREQ_FRAC 0x03 + +/* + * Some contants + */ + +#define ES_1370_SRCLOCK 1411200 +#define ES_1370_SRTODIV(x) (ES_1370_SRCLOCK/(x)-2) + +/* + * Open modes + */ + +#define ES_MODE_PLAY1 0x0001 +#define ES_MODE_PLAY2 0x0002 +#define ES_MODE_CAPTURE 0x0004 + +#define ES_MODE_OUTPUT 0x0001 /* for MIDI */ +#define ES_MODE_INPUT 0x0002 /* for MIDI */ + +/* + + */ + +struct ensoniq { + spinlock_t reg_lock; + struct mutex src_mutex; + + int irq; + + unsigned long playback1size; + unsigned long playback2size; + unsigned long capture3size; + + unsigned long port; + unsigned int mode; + unsigned int uartm; /* UART mode */ + + unsigned int ctrl; /* control register */ + unsigned int sctrl; /* serial control register */ + unsigned int cssr; /* control status register */ + unsigned int uartc; /* uart control register */ + unsigned int rev; /* chip revision */ + + union { +#ifdef CHIP1371 + struct { + struct snd_ac97 *ac97; + } es1371; +#else + struct { + int pclkdiv_lock; + struct snd_ak4531 *ak4531; + } es1370; +#endif + } u; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm1; /* DAC1/ADC PCM */ + struct snd_pcm *pcm2; /* DAC2 PCM */ + struct snd_pcm_substream *playback1_substream; + struct snd_pcm_substream *playback2_substream; + struct snd_pcm_substream *capture_substream; + unsigned int p1_dma_size; + unsigned int p2_dma_size; + unsigned int c_dma_size; + unsigned int p1_period_size; + unsigned int p2_period_size; + unsigned int c_period_size; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_input; + struct snd_rawmidi_substream *midi_output; + + unsigned int spdif; + unsigned int spdif_default; + unsigned int spdif_stream; + +#ifdef CHIP1370 + struct snd_dma_buffer *dma_bug; +#endif + +#ifdef SUPPORT_JOYSTICK + struct gameport *gameport; +#endif +}; + +static irqreturn_t snd_audiopci_interrupt(int irq, void *dev_id); + +static const struct pci_device_id snd_audiopci_ids[] = { +#ifdef CHIP1370 + { PCI_VDEVICE(ENSONIQ, 0x5000) }, /* ES1370 */ +#endif +#ifdef CHIP1371 + { PCI_VDEVICE(ENSONIQ, 0x1371) }, /* ES1371 */ + { PCI_VDEVICE(ENSONIQ, 0x5880) }, /* ES1373 - CT5880 */ + { PCI_VDEVICE(ECTIVA, 0x8938) }, /* Ectiva EV1938 */ +#endif + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_audiopci_ids); + +/* + * constants + */ + +#define POLL_COUNT 0xa000 + +#ifdef CHIP1370 +static const unsigned int snd_es1370_fixed_rates[] = + {5512, 11025, 22050, 44100}; +static const struct snd_pcm_hw_constraint_list snd_es1370_hw_constraints_rates = { + .count = 4, + .list = snd_es1370_fixed_rates, + .mask = 0, +}; +static const struct snd_ratnum es1370_clock = { + .num = ES_1370_SRCLOCK, + .den_min = 29, + .den_max = 353, + .den_step = 1, +}; +static const struct snd_pcm_hw_constraint_ratnums snd_es1370_hw_constraints_clock = { + .nrats = 1, + .rats = &es1370_clock, +}; +#else +static const struct snd_ratden es1371_dac_clock = { + .num_min = 3000 * (1 << 15), + .num_max = 48000 * (1 << 15), + .num_step = 3000, + .den = 1 << 15, +}; +static const struct snd_pcm_hw_constraint_ratdens snd_es1371_hw_constraints_dac_clock = { + .nrats = 1, + .rats = &es1371_dac_clock, +}; +static const struct snd_ratnum es1371_adc_clock = { + .num = 48000 << 15, + .den_min = 32768, + .den_max = 393216, + .den_step = 1, +}; +static const struct snd_pcm_hw_constraint_ratnums snd_es1371_hw_constraints_adc_clock = { + .nrats = 1, + .rats = &es1371_adc_clock, +}; +#endif +static const unsigned int snd_ensoniq_sample_shift[] = + {0, 1, 1, 2}; + +/* + * common I/O routines + */ + +#ifdef CHIP1371 + +static unsigned int snd_es1371_wait_src_ready(struct ensoniq * ensoniq) +{ + unsigned int t, r = 0; + + for (t = 0; t < POLL_COUNT; t++) { + r = inl(ES_REG(ensoniq, 1371_SMPRATE)); + if ((r & ES_1371_SRC_RAM_BUSY) == 0) + return r; + cond_resched(); + } + dev_err(ensoniq->card->dev, "wait src ready timeout 0x%lx [0x%x]\n", + ES_REG(ensoniq, 1371_SMPRATE), r); + return 0; +} + +static unsigned int snd_es1371_src_read(struct ensoniq * ensoniq, unsigned short reg) +{ + unsigned int temp, i, orig, r; + + /* wait for ready */ + temp = orig = snd_es1371_wait_src_ready(ensoniq); + + /* expose the SRC state bits */ + r = temp & (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 | + ES_1371_DIS_P2 | ES_1371_DIS_R1); + r |= ES_1371_SRC_RAM_ADDRO(reg) | 0x10000; + outl(r, ES_REG(ensoniq, 1371_SMPRATE)); + + /* now, wait for busy and the correct time to read */ + temp = snd_es1371_wait_src_ready(ensoniq); + + if ((temp & 0x00870000) != 0x00010000) { + /* wait for the right state */ + for (i = 0; i < POLL_COUNT; i++) { + temp = inl(ES_REG(ensoniq, 1371_SMPRATE)); + if ((temp & 0x00870000) == 0x00010000) + break; + } + } + + /* hide the state bits */ + r = orig & (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 | + ES_1371_DIS_P2 | ES_1371_DIS_R1); + r |= ES_1371_SRC_RAM_ADDRO(reg); + outl(r, ES_REG(ensoniq, 1371_SMPRATE)); + + return temp; +} + +static void snd_es1371_src_write(struct ensoniq * ensoniq, + unsigned short reg, unsigned short data) +{ + unsigned int r; + + r = snd_es1371_wait_src_ready(ensoniq) & + (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 | + ES_1371_DIS_P2 | ES_1371_DIS_R1); + r |= ES_1371_SRC_RAM_ADDRO(reg) | ES_1371_SRC_RAM_DATAO(data); + outl(r | ES_1371_SRC_RAM_WE, ES_REG(ensoniq, 1371_SMPRATE)); +} + +#endif /* CHIP1371 */ + +#ifdef CHIP1370 + +static void snd_es1370_codec_write(struct snd_ak4531 *ak4531, + unsigned short reg, unsigned short val) +{ + struct ensoniq *ensoniq = ak4531->private_data; + unsigned long end_time = jiffies + HZ / 10; + +#if 0 + dev_dbg(ensoniq->card->dev, + "CODEC WRITE: reg = 0x%x, val = 0x%x (0x%x), creg = 0x%x\n", + reg, val, ES_1370_CODEC_WRITE(reg, val), ES_REG(ensoniq, 1370_CODEC)); +#endif + do { + if (!(inl(ES_REG(ensoniq, STATUS)) & ES_1370_CSTAT)) { + outw(ES_1370_CODEC_WRITE(reg, val), ES_REG(ensoniq, 1370_CODEC)); + return; + } + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); + dev_err(ensoniq->card->dev, "codec write timeout, status = 0x%x\n", + inl(ES_REG(ensoniq, STATUS))); +} + +#endif /* CHIP1370 */ + +#ifdef CHIP1371 + +static inline bool is_ev1938(struct ensoniq *ensoniq) +{ + return ensoniq->pci->device == 0x8938; +} + +static void snd_es1371_codec_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + struct ensoniq *ensoniq = ac97->private_data; + unsigned int t, x, flag; + + flag = is_ev1938(ensoniq) ? EV_1938_CODEC_MAGIC : 0; + guard(mutex)(&ensoniq->src_mutex); + for (t = 0; t < POLL_COUNT; t++) { + if (!(inl(ES_REG(ensoniq, 1371_CODEC)) & ES_1371_CODEC_WIP)) { + /* save the current state for latter */ + x = snd_es1371_wait_src_ready(ensoniq); + outl((x & (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 | + ES_1371_DIS_P2 | ES_1371_DIS_R1)) | 0x00010000, + ES_REG(ensoniq, 1371_SMPRATE)); + /* wait for not busy (state 0) first to avoid + transition states */ + for (t = 0; t < POLL_COUNT; t++) { + if ((inl(ES_REG(ensoniq, 1371_SMPRATE)) & 0x00870000) == + 0x00000000) + break; + } + /* wait for a SAFE time to write addr/data and then do it, dammit */ + for (t = 0; t < POLL_COUNT; t++) { + if ((inl(ES_REG(ensoniq, 1371_SMPRATE)) & 0x00870000) == + 0x00010000) + break; + } + outl(ES_1371_CODEC_WRITE(reg, val) | flag, + ES_REG(ensoniq, 1371_CODEC)); + /* restore SRC reg */ + snd_es1371_wait_src_ready(ensoniq); + outl(x, ES_REG(ensoniq, 1371_SMPRATE)); + return; + } + } + dev_err(ensoniq->card->dev, "codec write timeout at 0x%lx [0x%x]\n", + ES_REG(ensoniq, 1371_CODEC), inl(ES_REG(ensoniq, 1371_CODEC))); +} + +static unsigned short snd_es1371_codec_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct ensoniq *ensoniq = ac97->private_data; + unsigned int t, x, flag, fail = 0; + + flag = is_ev1938(ensoniq) ? EV_1938_CODEC_MAGIC : 0; + __again: + mutex_lock(&ensoniq->src_mutex); + for (t = 0; t < POLL_COUNT; t++) { + if (!(inl(ES_REG(ensoniq, 1371_CODEC)) & ES_1371_CODEC_WIP)) { + /* save the current state for latter */ + x = snd_es1371_wait_src_ready(ensoniq); + outl((x & (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 | + ES_1371_DIS_P2 | ES_1371_DIS_R1)) | 0x00010000, + ES_REG(ensoniq, 1371_SMPRATE)); + /* wait for not busy (state 0) first to avoid + transition states */ + for (t = 0; t < POLL_COUNT; t++) { + if ((inl(ES_REG(ensoniq, 1371_SMPRATE)) & 0x00870000) == + 0x00000000) + break; + } + /* wait for a SAFE time to write addr/data and then do it, dammit */ + for (t = 0; t < POLL_COUNT; t++) { + if ((inl(ES_REG(ensoniq, 1371_SMPRATE)) & 0x00870000) == + 0x00010000) + break; + } + outl(ES_1371_CODEC_READS(reg) | flag, + ES_REG(ensoniq, 1371_CODEC)); + /* restore SRC reg */ + snd_es1371_wait_src_ready(ensoniq); + outl(x, ES_REG(ensoniq, 1371_SMPRATE)); + /* wait for WIP again */ + for (t = 0; t < POLL_COUNT; t++) { + if (!(inl(ES_REG(ensoniq, 1371_CODEC)) & ES_1371_CODEC_WIP)) + break; + } + /* now wait for the stinkin' data (RDY) */ + for (t = 0; t < POLL_COUNT; t++) { + x = inl(ES_REG(ensoniq, 1371_CODEC)); + if (x & ES_1371_CODEC_RDY) { + if (is_ev1938(ensoniq)) { + for (t = 0; t < 100; t++) + inl(ES_REG(ensoniq, CONTROL)); + x = inl(ES_REG(ensoniq, 1371_CODEC)); + } + mutex_unlock(&ensoniq->src_mutex); + return ES_1371_CODEC_READ(x); + } + } + mutex_unlock(&ensoniq->src_mutex); + if (++fail > 10) { + dev_err(ensoniq->card->dev, + "codec read timeout (final) at 0x%lx, reg = 0x%x [0x%x]\n", + ES_REG(ensoniq, 1371_CODEC), reg, + inl(ES_REG(ensoniq, 1371_CODEC))); + return 0; + } + goto __again; + } + } + mutex_unlock(&ensoniq->src_mutex); + dev_err(ensoniq->card->dev, "codec read timeout at 0x%lx [0x%x]\n", + ES_REG(ensoniq, 1371_CODEC), inl(ES_REG(ensoniq, 1371_CODEC))); + return 0; +} + +static void snd_es1371_codec_wait(struct snd_ac97 *ac97) +{ + msleep(750); + snd_es1371_codec_read(ac97, AC97_RESET); + snd_es1371_codec_read(ac97, AC97_VENDOR_ID1); + snd_es1371_codec_read(ac97, AC97_VENDOR_ID2); + msleep(50); +} + +static void snd_es1371_adc_rate(struct ensoniq * ensoniq, unsigned int rate) +{ + unsigned int n, truncm, freq; + + guard(mutex)(&ensoniq->src_mutex); + n = rate / 3000; + if ((1 << n) & ((1 << 15) | (1 << 13) | (1 << 11) | (1 << 9))) + n--; + truncm = (21 * n - 1) | 1; + freq = ((48000UL << 15) / rate) * n; + if (rate >= 24000) { + if (truncm > 239) + truncm = 239; + snd_es1371_src_write(ensoniq, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, + (((239 - truncm) >> 1) << 9) | (n << 4)); + } else { + if (truncm > 119) + truncm = 119; + snd_es1371_src_write(ensoniq, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, + 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); + } + snd_es1371_src_write(ensoniq, ES_SMPREG_ADC + ES_SMPREG_INT_REGS, + (snd_es1371_src_read(ensoniq, ES_SMPREG_ADC + + ES_SMPREG_INT_REGS) & 0x00ff) | + ((freq >> 5) & 0xfc00)); + snd_es1371_src_write(ensoniq, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); + snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_ADC, n << 8); + snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_ADC + 1, n << 8); +} + +static void snd_es1371_dac1_rate(struct ensoniq * ensoniq, unsigned int rate) +{ + unsigned int freq, r; + + guard(mutex)(&ensoniq->src_mutex); + freq = DIV_ROUND_CLOSEST(rate << 15, 3000); + r = (snd_es1371_wait_src_ready(ensoniq) & (ES_1371_SRC_DISABLE | + ES_1371_DIS_P2 | ES_1371_DIS_R1)) | + ES_1371_DIS_P1; + outl(r, ES_REG(ensoniq, 1371_SMPRATE)); + snd_es1371_src_write(ensoniq, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS, + (snd_es1371_src_read(ensoniq, ES_SMPREG_DAC1 + + ES_SMPREG_INT_REGS) & 0x00ff) | + ((freq >> 5) & 0xfc00)); + snd_es1371_src_write(ensoniq, ES_SMPREG_DAC1 + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); + r = (snd_es1371_wait_src_ready(ensoniq) & (ES_1371_SRC_DISABLE | + ES_1371_DIS_P2 | ES_1371_DIS_R1)); + outl(r, ES_REG(ensoniq, 1371_SMPRATE)); +} + +static void snd_es1371_dac2_rate(struct ensoniq * ensoniq, unsigned int rate) +{ + unsigned int freq, r; + + guard(mutex)(&ensoniq->src_mutex); + freq = DIV_ROUND_CLOSEST(rate << 15, 3000); + r = (snd_es1371_wait_src_ready(ensoniq) & (ES_1371_SRC_DISABLE | + ES_1371_DIS_P1 | ES_1371_DIS_R1)) | + ES_1371_DIS_P2; + outl(r, ES_REG(ensoniq, 1371_SMPRATE)); + snd_es1371_src_write(ensoniq, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS, + (snd_es1371_src_read(ensoniq, ES_SMPREG_DAC2 + + ES_SMPREG_INT_REGS) & 0x00ff) | + ((freq >> 5) & 0xfc00)); + snd_es1371_src_write(ensoniq, ES_SMPREG_DAC2 + ES_SMPREG_VFREQ_FRAC, + freq & 0x7fff); + r = (snd_es1371_wait_src_ready(ensoniq) & (ES_1371_SRC_DISABLE | + ES_1371_DIS_P1 | ES_1371_DIS_R1)); + outl(r, ES_REG(ensoniq, 1371_SMPRATE)); +} + +#endif /* CHIP1371 */ + +static int snd_ensoniq_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + { + unsigned int what = 0; + struct snd_pcm_substream *s; + snd_pcm_group_for_each_entry(s, substream) { + if (s == ensoniq->playback1_substream) { + what |= ES_P1_PAUSE; + snd_pcm_trigger_done(s, substream); + } else if (s == ensoniq->playback2_substream) { + what |= ES_P2_PAUSE; + snd_pcm_trigger_done(s, substream); + } else if (s == ensoniq->capture_substream) + return -EINVAL; + } + scoped_guard(spinlock, &ensoniq->reg_lock) { + if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) + ensoniq->sctrl |= what; + else + ensoniq->sctrl &= ~what; + outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL)); + } + break; + } + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + { + unsigned int what = 0; + struct snd_pcm_substream *s; + snd_pcm_group_for_each_entry(s, substream) { + if (s == ensoniq->playback1_substream) { + what |= ES_DAC1_EN; + snd_pcm_trigger_done(s, substream); + } else if (s == ensoniq->playback2_substream) { + what |= ES_DAC2_EN; + snd_pcm_trigger_done(s, substream); + } else if (s == ensoniq->capture_substream) { + what |= ES_ADC_EN; + snd_pcm_trigger_done(s, substream); + } + } + scoped_guard(spinlock, &ensoniq->reg_lock) { + if (cmd == SNDRV_PCM_TRIGGER_START) + ensoniq->ctrl |= what; + else + ensoniq->ctrl &= ~what; + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + } + break; + } + default: + return -EINVAL; + } + return 0; +} + +/* + * PCM part + */ + +static int snd_ensoniq_playback1_prepare(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int mode = 0; + + ensoniq->p1_dma_size = snd_pcm_lib_buffer_bytes(substream); + ensoniq->p1_period_size = snd_pcm_lib_period_bytes(substream); + if (snd_pcm_format_width(runtime->format) == 16) + mode |= 0x02; + if (runtime->channels > 1) + mode |= 0x01; + scoped_guard(spinlock_irq, &ensoniq->reg_lock) { + ensoniq->ctrl &= ~ES_DAC1_EN; +#ifdef CHIP1371 + /* 48k doesn't need SRC (it breaks AC3-passthru) */ + if (runtime->rate == 48000) + ensoniq->ctrl |= ES_1373_BYPASS_P1; + else + ensoniq->ctrl &= ~ES_1373_BYPASS_P1; +#endif + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + outl(ES_MEM_PAGEO(ES_PAGE_DAC), ES_REG(ensoniq, MEM_PAGE)); + outl(runtime->dma_addr, ES_REG(ensoniq, DAC1_FRAME)); + outl((ensoniq->p1_dma_size >> 2) - 1, ES_REG(ensoniq, DAC1_SIZE)); + ensoniq->sctrl &= ~(ES_P1_LOOP_SEL | ES_P1_PAUSE | ES_P1_SCT_RLD | ES_P1_MODEM); + ensoniq->sctrl |= ES_P1_INT_EN | ES_P1_MODEO(mode); + outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL)); + outl((ensoniq->p1_period_size >> snd_ensoniq_sample_shift[mode]) - 1, + ES_REG(ensoniq, DAC1_COUNT)); +#ifdef CHIP1370 + ensoniq->ctrl &= ~ES_1370_WTSRSELM; + switch (runtime->rate) { + case 5512: ensoniq->ctrl |= ES_1370_WTSRSEL(0); break; + case 11025: ensoniq->ctrl |= ES_1370_WTSRSEL(1); break; + case 22050: ensoniq->ctrl |= ES_1370_WTSRSEL(2); break; + case 44100: ensoniq->ctrl |= ES_1370_WTSRSEL(3); break; + default: snd_BUG(); + } +#endif + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + } +#ifndef CHIP1370 + snd_es1371_dac1_rate(ensoniq, runtime->rate); +#endif + return 0; +} + +static int snd_ensoniq_playback2_prepare(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int mode = 0; + + ensoniq->p2_dma_size = snd_pcm_lib_buffer_bytes(substream); + ensoniq->p2_period_size = snd_pcm_lib_period_bytes(substream); + if (snd_pcm_format_width(runtime->format) == 16) + mode |= 0x02; + if (runtime->channels > 1) + mode |= 0x01; + scoped_guard(spinlock_irq, &ensoniq->reg_lock) { + ensoniq->ctrl &= ~ES_DAC2_EN; + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + outl(ES_MEM_PAGEO(ES_PAGE_DAC), ES_REG(ensoniq, MEM_PAGE)); + outl(runtime->dma_addr, ES_REG(ensoniq, DAC2_FRAME)); + outl((ensoniq->p2_dma_size >> 2) - 1, ES_REG(ensoniq, DAC2_SIZE)); + ensoniq->sctrl &= ~(ES_P2_LOOP_SEL | ES_P2_PAUSE | ES_P2_DAC_SEN | + ES_P2_END_INCM | ES_P2_ST_INCM | ES_P2_MODEM); + ensoniq->sctrl |= ES_P2_INT_EN | ES_P2_MODEO(mode) | + ES_P2_END_INCO(mode & 2 ? 2 : 1) | ES_P2_ST_INCO(0); + outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL)); + outl((ensoniq->p2_period_size >> snd_ensoniq_sample_shift[mode]) - 1, + ES_REG(ensoniq, DAC2_COUNT)); +#ifdef CHIP1370 + if (!(ensoniq->u.es1370.pclkdiv_lock & ES_MODE_CAPTURE)) { + ensoniq->ctrl &= ~ES_1370_PCLKDIVM; + ensoniq->ctrl |= ES_1370_PCLKDIVO(ES_1370_SRTODIV(runtime->rate)); + ensoniq->u.es1370.pclkdiv_lock |= ES_MODE_PLAY2; + } +#endif + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + } +#ifndef CHIP1370 + snd_es1371_dac2_rate(ensoniq, runtime->rate); +#endif + return 0; +} + +static int snd_ensoniq_capture_prepare(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int mode = 0; + + ensoniq->c_dma_size = snd_pcm_lib_buffer_bytes(substream); + ensoniq->c_period_size = snd_pcm_lib_period_bytes(substream); + if (snd_pcm_format_width(runtime->format) == 16) + mode |= 0x02; + if (runtime->channels > 1) + mode |= 0x01; + scoped_guard(spinlock_irq, &ensoniq->reg_lock) { + ensoniq->ctrl &= ~ES_ADC_EN; + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + outl(ES_MEM_PAGEO(ES_PAGE_ADC), ES_REG(ensoniq, MEM_PAGE)); + outl(runtime->dma_addr, ES_REG(ensoniq, ADC_FRAME)); + outl((ensoniq->c_dma_size >> 2) - 1, ES_REG(ensoniq, ADC_SIZE)); + ensoniq->sctrl &= ~(ES_R1_LOOP_SEL | ES_R1_MODEM); + ensoniq->sctrl |= ES_R1_INT_EN | ES_R1_MODEO(mode); + outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL)); + outl((ensoniq->c_period_size >> snd_ensoniq_sample_shift[mode]) - 1, + ES_REG(ensoniq, ADC_COUNT)); +#ifdef CHIP1370 + if (!(ensoniq->u.es1370.pclkdiv_lock & ES_MODE_PLAY2)) { + ensoniq->ctrl &= ~ES_1370_PCLKDIVM; + ensoniq->ctrl |= ES_1370_PCLKDIVO(ES_1370_SRTODIV(runtime->rate)); + ensoniq->u.es1370.pclkdiv_lock |= ES_MODE_CAPTURE; + } +#endif + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + } +#ifndef CHIP1370 + snd_es1371_adc_rate(ensoniq, runtime->rate); +#endif + return 0; +} + +static snd_pcm_uframes_t snd_ensoniq_playback1_pointer(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + size_t ptr; + + guard(spinlock)(&ensoniq->reg_lock); + if (inl(ES_REG(ensoniq, CONTROL)) & ES_DAC1_EN) { + outl(ES_MEM_PAGEO(ES_PAGE_DAC), ES_REG(ensoniq, MEM_PAGE)); + ptr = ES_REG_FCURR_COUNTI(inl(ES_REG(ensoniq, DAC1_SIZE))); + return bytes_to_frames(substream->runtime, ptr); + } else { + return 0; + } +} + +static snd_pcm_uframes_t snd_ensoniq_playback2_pointer(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + size_t ptr; + + guard(spinlock)(&ensoniq->reg_lock); + if (inl(ES_REG(ensoniq, CONTROL)) & ES_DAC2_EN) { + outl(ES_MEM_PAGEO(ES_PAGE_DAC), ES_REG(ensoniq, MEM_PAGE)); + ptr = ES_REG_FCURR_COUNTI(inl(ES_REG(ensoniq, DAC2_SIZE))); + return bytes_to_frames(substream->runtime, ptr); + } else { + return 0; + } +} + +static snd_pcm_uframes_t snd_ensoniq_capture_pointer(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + size_t ptr; + + guard(spinlock)(&ensoniq->reg_lock); + if (inl(ES_REG(ensoniq, CONTROL)) & ES_ADC_EN) { + outl(ES_MEM_PAGEO(ES_PAGE_ADC), ES_REG(ensoniq, MEM_PAGE)); + ptr = ES_REG_FCURR_COUNTI(inl(ES_REG(ensoniq, ADC_SIZE))); + return bytes_to_frames(substream->runtime, ptr); + } else { + return 0; + } +} + +static const struct snd_pcm_hardware snd_ensoniq_playback1 = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = +#ifndef CHIP1370 + SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, +#else + (SNDRV_PCM_RATE_KNOT | /* 5512Hz rate */ + SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_44100), +#endif + .rate_min = 4000, + .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_ensoniq_playback2 = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START), + .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 = (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_ensoniq_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START), + .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 = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_ensoniq_playback1_open(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + ensoniq->mode |= ES_MODE_PLAY1; + ensoniq->playback1_substream = substream; + runtime->hw = snd_ensoniq_playback1; + snd_pcm_set_sync(substream); + scoped_guard(spinlock_irq, &ensoniq->reg_lock) { + if (ensoniq->spdif && ensoniq->playback2_substream == NULL) + ensoniq->spdif_stream = ensoniq->spdif_default; + } +#ifdef CHIP1370 + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &snd_es1370_hw_constraints_rates); +#else + snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &snd_es1371_hw_constraints_dac_clock); +#endif + return 0; +} + +static int snd_ensoniq_playback2_open(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + ensoniq->mode |= ES_MODE_PLAY2; + ensoniq->playback2_substream = substream; + runtime->hw = snd_ensoniq_playback2; + snd_pcm_set_sync(substream); + scoped_guard(spinlock_irq, &ensoniq->reg_lock) { + if (ensoniq->spdif && ensoniq->playback1_substream == NULL) + ensoniq->spdif_stream = ensoniq->spdif_default; + } +#ifdef CHIP1370 + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &snd_es1370_hw_constraints_clock); +#else + snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &snd_es1371_hw_constraints_dac_clock); +#endif + return 0; +} + +static int snd_ensoniq_capture_open(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + ensoniq->mode |= ES_MODE_CAPTURE; + ensoniq->capture_substream = substream; + runtime->hw = snd_ensoniq_capture; + snd_pcm_set_sync(substream); +#ifdef CHIP1370 + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &snd_es1370_hw_constraints_clock); +#else + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &snd_es1371_hw_constraints_adc_clock); +#endif + return 0; +} + +static int snd_ensoniq_playback1_close(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + + ensoniq->playback1_substream = NULL; + ensoniq->mode &= ~ES_MODE_PLAY1; + return 0; +} + +static int snd_ensoniq_playback2_close(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + + ensoniq->playback2_substream = NULL; + guard(spinlock_irq)(&ensoniq->reg_lock); +#ifdef CHIP1370 + ensoniq->u.es1370.pclkdiv_lock &= ~ES_MODE_PLAY2; +#endif + ensoniq->mode &= ~ES_MODE_PLAY2; + return 0; +} + +static int snd_ensoniq_capture_close(struct snd_pcm_substream *substream) +{ + struct ensoniq *ensoniq = snd_pcm_substream_chip(substream); + + ensoniq->capture_substream = NULL; + guard(spinlock_irq)(&ensoniq->reg_lock); +#ifdef CHIP1370 + ensoniq->u.es1370.pclkdiv_lock &= ~ES_MODE_CAPTURE; +#endif + ensoniq->mode &= ~ES_MODE_CAPTURE; + return 0; +} + +static const struct snd_pcm_ops snd_ensoniq_playback1_ops = { + .open = snd_ensoniq_playback1_open, + .close = snd_ensoniq_playback1_close, + .prepare = snd_ensoniq_playback1_prepare, + .trigger = snd_ensoniq_trigger, + .pointer = snd_ensoniq_playback1_pointer, +}; + +static const struct snd_pcm_ops snd_ensoniq_playback2_ops = { + .open = snd_ensoniq_playback2_open, + .close = snd_ensoniq_playback2_close, + .prepare = snd_ensoniq_playback2_prepare, + .trigger = snd_ensoniq_trigger, + .pointer = snd_ensoniq_playback2_pointer, +}; + +static const struct snd_pcm_ops snd_ensoniq_capture_ops = { + .open = snd_ensoniq_capture_open, + .close = snd_ensoniq_capture_close, + .prepare = snd_ensoniq_capture_prepare, + .trigger = snd_ensoniq_trigger, + .pointer = snd_ensoniq_capture_pointer, +}; + +static const struct snd_pcm_chmap_elem surround_map[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, + .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +static int snd_ensoniq_pcm(struct ensoniq *ensoniq, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ensoniq->card, CHIP_NAME "/1", device, 1, 1, &pcm); + if (err < 0) + return err; + +#ifdef CHIP1370 + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ensoniq_playback2_ops); +#else + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ensoniq_playback1_ops); +#endif + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ensoniq_capture_ops); + + pcm->private_data = ensoniq; + pcm->info_flags = 0; + strscpy(pcm->name, CHIP_NAME " DAC2/ADC"); + ensoniq->pcm1 = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &ensoniq->pci->dev, 64*1024, 128*1024); + +#ifdef CHIP1370 + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + surround_map, 2, 0, NULL); +#else + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_std_chmaps, 2, 0, NULL); +#endif + return err; +} + +static int snd_ensoniq_pcm2(struct ensoniq *ensoniq, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ensoniq->card, CHIP_NAME "/2", device, 1, 0, &pcm); + if (err < 0) + return err; + +#ifdef CHIP1370 + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ensoniq_playback1_ops); +#else + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ensoniq_playback2_ops); +#endif + pcm->private_data = ensoniq; + pcm->info_flags = 0; + strscpy(pcm->name, CHIP_NAME " DAC1"); + ensoniq->pcm2 = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &ensoniq->pci->dev, 64*1024, 128*1024); + +#ifdef CHIP1370 + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_std_chmaps, 2, 0, NULL); +#else + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + surround_map, 2, 0, NULL); +#endif + return err; +} + +/* + * Mixer section + */ + +/* + * ENS1371 mixer (including SPDIF interface) + */ +#ifdef CHIP1371 +static int snd_ens1373_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_ens1373_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&ensoniq->reg_lock); + ucontrol->value.iec958.status[0] = (ensoniq->spdif_default >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (ensoniq->spdif_default >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (ensoniq->spdif_default >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (ensoniq->spdif_default >> 24) & 0xff; + return 0; +} + +static int snd_ens1373_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ((u32)ucontrol->value.iec958.status[0] << 0) | + ((u32)ucontrol->value.iec958.status[1] << 8) | + ((u32)ucontrol->value.iec958.status[2] << 16) | + ((u32)ucontrol->value.iec958.status[3] << 24); + guard(spinlock_irq)(&ensoniq->reg_lock); + change = ensoniq->spdif_default != val; + ensoniq->spdif_default = val; + if (change && ensoniq->playback1_substream == NULL && + ensoniq->playback2_substream == NULL) + outl(val, ES_REG(ensoniq, CHANNEL_STATUS)); + return change; +} + +static int snd_ens1373_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static int snd_ens1373_spdif_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&ensoniq->reg_lock); + ucontrol->value.iec958.status[0] = (ensoniq->spdif_stream >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (ensoniq->spdif_stream >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (ensoniq->spdif_stream >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (ensoniq->spdif_stream >> 24) & 0xff; + return 0; +} + +static int snd_ens1373_spdif_stream_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ((u32)ucontrol->value.iec958.status[0] << 0) | + ((u32)ucontrol->value.iec958.status[1] << 8) | + ((u32)ucontrol->value.iec958.status[2] << 16) | + ((u32)ucontrol->value.iec958.status[3] << 24); + guard(spinlock_irq)(&ensoniq->reg_lock); + change = ensoniq->spdif_stream != val; + ensoniq->spdif_stream = val; + if (change && (ensoniq->playback1_substream != NULL || + ensoniq->playback2_substream != NULL)) + outl(val, ES_REG(ensoniq, CHANNEL_STATUS)); + return change; +} + +#define ES1371_SPDIF(xname) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_es1371_spdif_info, \ + .get = snd_es1371_spdif_get, .put = snd_es1371_spdif_put } + +#define snd_es1371_spdif_info snd_ctl_boolean_mono_info + +static int snd_es1371_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&ensoniq->reg_lock); + ucontrol->value.integer.value[0] = ensoniq->ctrl & ES_1373_SPDIF_THRU ? 1 : 0; + return 0; +} + +static int snd_es1371_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + unsigned int nval1, nval2; + int change; + + nval1 = ucontrol->value.integer.value[0] ? ES_1373_SPDIF_THRU : 0; + nval2 = ucontrol->value.integer.value[0] ? ES_1373_SPDIF_EN : 0; + guard(spinlock_irq)(&ensoniq->reg_lock); + change = (ensoniq->ctrl & ES_1373_SPDIF_THRU) != nval1; + ensoniq->ctrl &= ~ES_1373_SPDIF_THRU; + ensoniq->ctrl |= nval1; + ensoniq->cssr &= ~ES_1373_SPDIF_EN; + ensoniq->cssr |= nval2; + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + outl(ensoniq->cssr, ES_REG(ensoniq, STATUS)); + return change; +} + + +/* spdif controls */ +static const struct snd_kcontrol_new snd_es1371_mixer_spdif[] = { + ES1371_SPDIF(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH)), + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_ens1373_spdif_info, + .get = snd_ens1373_spdif_default_get, + .put = snd_ens1373_spdif_default_put, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = snd_ens1373_spdif_info, + .get = snd_ens1373_spdif_mask_get + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .info = snd_ens1373_spdif_info, + .get = snd_ens1373_spdif_stream_get, + .put = snd_ens1373_spdif_stream_put + }, +}; + + +#define snd_es1373_rear_info snd_ctl_boolean_mono_info + +static int snd_es1373_rear_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + int val = 0; + + guard(spinlock_irq)(&ensoniq->reg_lock); + if ((ensoniq->cssr & (ES_1373_REAR_BIT27|ES_1373_REAR_BIT26| + ES_1373_REAR_BIT24)) == ES_1373_REAR_BIT26) + val = 1; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_es1373_rear_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + unsigned int nval1; + int change; + + nval1 = ucontrol->value.integer.value[0] ? + ES_1373_REAR_BIT26 : (ES_1373_REAR_BIT27|ES_1373_REAR_BIT24); + guard(spinlock_irq)(&ensoniq->reg_lock); + change = (ensoniq->cssr & (ES_1373_REAR_BIT27| + ES_1373_REAR_BIT26|ES_1373_REAR_BIT24)) != nval1; + ensoniq->cssr &= ~(ES_1373_REAR_BIT27|ES_1373_REAR_BIT26|ES_1373_REAR_BIT24); + ensoniq->cssr |= nval1; + outl(ensoniq->cssr, ES_REG(ensoniq, STATUS)); + return change; +} + +static const struct snd_kcontrol_new snd_ens1373_rear = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "AC97 2ch->4ch Copy Switch", + .info = snd_es1373_rear_info, + .get = snd_es1373_rear_get, + .put = snd_es1373_rear_put, +}; + +#define snd_es1373_line_info snd_ctl_boolean_mono_info + +static int snd_es1373_line_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + int val = 0; + + guard(spinlock_irq)(&ensoniq->reg_lock); + if (ensoniq->ctrl & ES_1371_GPIO_OUT(4)) + val = 1; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_es1373_line_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + int changed; + unsigned int ctrl; + + guard(spinlock_irq)(&ensoniq->reg_lock); + ctrl = ensoniq->ctrl; + if (ucontrol->value.integer.value[0]) + ensoniq->ctrl |= ES_1371_GPIO_OUT(4); /* switch line-in -> rear out */ + else + ensoniq->ctrl &= ~ES_1371_GPIO_OUT(4); + changed = (ctrl != ensoniq->ctrl); + if (changed) + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + return changed; +} + +static const struct snd_kcontrol_new snd_ens1373_line = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line In->Rear Out Switch", + .info = snd_es1373_line_info, + .get = snd_es1373_line_get, + .put = snd_es1373_line_put, +}; + +static void snd_ensoniq_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct ensoniq *ensoniq = ac97->private_data; + ensoniq->u.es1371.ac97 = NULL; +} + +struct es1371_quirk { + unsigned short vid; /* vendor ID */ + unsigned short did; /* device ID */ + unsigned char rev; /* revision */ +}; + +static int es1371_quirk_lookup(struct ensoniq *ensoniq, + const struct es1371_quirk *list) +{ + while (list->vid != (unsigned short)PCI_ANY_ID) { + if (ensoniq->pci->vendor == list->vid && + ensoniq->pci->device == list->did && + ensoniq->rev == list->rev) + return 1; + list++; + } + return 0; +} + +static const struct es1371_quirk es1371_spdif_present[] = { + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_C }, + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_D }, + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_E }, + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_ES1371, .rev = ES1371REV_CT5880_A }, + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_ES1371, .rev = ES1371REV_ES1373_8 }, + { .vid = PCI_ANY_ID, .did = PCI_ANY_ID } +}; + +static const struct snd_pci_quirk ens1373_line_quirk[] = { + SND_PCI_QUIRK_ID(0x1274, 0x2000), /* GA-7DXR */ + SND_PCI_QUIRK_ID(0x1458, 0xa000), /* GA-8IEXP */ + { } /* end */ +}; + +static int snd_ensoniq_1371_mixer(struct ensoniq *ensoniq, + int has_spdif, int has_line) +{ + struct snd_card *card = ensoniq->card; + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_es1371_codec_write, + .read = snd_es1371_codec_read, + .wait = snd_es1371_codec_wait, + }; + + err = snd_ac97_bus(card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = ensoniq; + ac97.private_free = snd_ensoniq_mixer_free_ac97; + ac97.pci = ensoniq->pci; + ac97.scaps = AC97_SCAP_AUDIO; + err = snd_ac97_mixer(pbus, &ac97, &ensoniq->u.es1371.ac97); + if (err < 0) + return err; + if (has_spdif > 0 || + (!has_spdif && es1371_quirk_lookup(ensoniq, es1371_spdif_present))) { + struct snd_kcontrol *kctl; + int i, is_spdif = 0; + + ensoniq->spdif_default = ensoniq->spdif_stream = + SNDRV_PCM_DEFAULT_CON_SPDIF; + outl(ensoniq->spdif_default, ES_REG(ensoniq, CHANNEL_STATUS)); + + if (ensoniq->u.es1371.ac97->ext_id & AC97_EI_SPDIF) + is_spdif++; + + for (i = 0; i < ARRAY_SIZE(snd_es1371_mixer_spdif); i++) { + kctl = snd_ctl_new1(&snd_es1371_mixer_spdif[i], ensoniq); + if (!kctl) + return -ENOMEM; + kctl->id.index = is_spdif; + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + } + if (ensoniq->u.es1371.ac97->ext_id & AC97_EI_SDAC) { + /* mirror rear to front speakers */ + ensoniq->cssr &= ~(ES_1373_REAR_BIT27|ES_1373_REAR_BIT24); + ensoniq->cssr |= ES_1373_REAR_BIT26; + err = snd_ctl_add(card, snd_ctl_new1(&snd_ens1373_rear, ensoniq)); + if (err < 0) + return err; + } + if (has_line > 0 || + snd_pci_quirk_lookup(ensoniq->pci, ens1373_line_quirk)) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_ens1373_line, + ensoniq)); + if (err < 0) + return err; + } + + return 0; +} + +#endif /* CHIP1371 */ + +/* generic control callbacks for ens1370 */ +#ifdef CHIP1370 +#define ENSONIQ_CONTROL(xname, mask) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = xname, .info = snd_ensoniq_control_info, \ + .get = snd_ensoniq_control_get, .put = snd_ensoniq_control_put, \ + .private_value = mask } + +#define snd_ensoniq_control_info snd_ctl_boolean_mono_info + +static int snd_ensoniq_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + int mask = kcontrol->private_value; + + guard(spinlock_irq)(&ensoniq->reg_lock); + ucontrol->value.integer.value[0] = ensoniq->ctrl & mask ? 1 : 0; + return 0; +} + +static int snd_ensoniq_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol); + int mask = kcontrol->private_value; + unsigned int nval; + int change; + + nval = ucontrol->value.integer.value[0] ? mask : 0; + guard(spinlock_irq)(&ensoniq->reg_lock); + change = (ensoniq->ctrl & mask) != nval; + ensoniq->ctrl &= ~mask; + ensoniq->ctrl |= nval; + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + return change; +} + +/* + * ENS1370 mixer + */ + +static const struct snd_kcontrol_new snd_es1370_controls[2] = { +ENSONIQ_CONTROL("PCM 0 Output also on Line-In Jack", ES_1370_XCTL0), +ENSONIQ_CONTROL("Mic +5V bias", ES_1370_XCTL1) +}; + +#define ES1370_CONTROLS ARRAY_SIZE(snd_es1370_controls) + +static void snd_ensoniq_mixer_free_ak4531(struct snd_ak4531 *ak4531) +{ + struct ensoniq *ensoniq = ak4531->private_data; + ensoniq->u.es1370.ak4531 = NULL; +} + +static int snd_ensoniq_1370_mixer(struct ensoniq *ensoniq) +{ + struct snd_card *card = ensoniq->card; + struct snd_ak4531 ak4531; + unsigned int idx; + int err; + + /* try reset AK4531 */ + outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x02), ES_REG(ensoniq, 1370_CODEC)); + inw(ES_REG(ensoniq, 1370_CODEC)); + udelay(100); + outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x03), ES_REG(ensoniq, 1370_CODEC)); + inw(ES_REG(ensoniq, 1370_CODEC)); + udelay(100); + + memset(&ak4531, 0, sizeof(ak4531)); + ak4531.write = snd_es1370_codec_write; + ak4531.private_data = ensoniq; + ak4531.private_free = snd_ensoniq_mixer_free_ak4531; + err = snd_ak4531_mixer(card, &ak4531, &ensoniq->u.es1370.ak4531); + if (err < 0) + return err; + for (idx = 0; idx < ES1370_CONTROLS; idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_es1370_controls[idx], ensoniq)); + if (err < 0) + return err; + } + return 0; +} + +#endif /* CHIP1370 */ + +#ifdef SUPPORT_JOYSTICK + +#ifdef CHIP1371 +static int snd_ensoniq_get_joystick_port(struct ensoniq *ensoniq, int dev) +{ + switch (joystick_port[dev]) { + case 0: /* disabled */ + case 1: /* auto-detect */ + case 0x200: + case 0x208: + case 0x210: + case 0x218: + return joystick_port[dev]; + + default: + dev_err(ensoniq->card->dev, + "invalid joystick port %#x", joystick_port[dev]); + return 0; + } +} +#else +static int snd_ensoniq_get_joystick_port(struct ensoniq *ensoniq, int dev) +{ + return joystick[dev] ? 0x200 : 0; +} +#endif + +static int snd_ensoniq_create_gameport(struct ensoniq *ensoniq, int dev) +{ + struct gameport *gp; + int io_port; + + io_port = snd_ensoniq_get_joystick_port(ensoniq, dev); + + switch (io_port) { + case 0: + return -ENOSYS; + + case 1: /* auto_detect */ + for (io_port = 0x200; io_port <= 0x218; io_port += 8) + if (request_region(io_port, 8, "ens137x: gameport")) + break; + if (io_port > 0x218) { + dev_warn(ensoniq->card->dev, + "no gameport ports available\n"); + return -EBUSY; + } + break; + + default: + if (!request_region(io_port, 8, "ens137x: gameport")) { + dev_warn(ensoniq->card->dev, + "gameport io port %#x in use\n", + io_port); + return -EBUSY; + } + break; + } + + ensoniq->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(ensoniq->card->dev, + "cannot allocate memory for gameport\n"); + release_region(io_port, 8); + return -ENOMEM; + } + + gameport_set_name(gp, "ES137x"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(ensoniq->pci)); + gameport_set_dev_parent(gp, &ensoniq->pci->dev); + gp->io = io_port; + + ensoniq->ctrl |= ES_JYSTK_EN; +#ifdef CHIP1371 + ensoniq->ctrl &= ~ES_1371_JOY_ASELM; + ensoniq->ctrl |= ES_1371_JOY_ASEL((io_port - 0x200) / 8); +#endif + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + + gameport_register_port(ensoniq->gameport); + + return 0; +} + +static void snd_ensoniq_free_gameport(struct ensoniq *ensoniq) +{ + if (ensoniq->gameport) { + int port = ensoniq->gameport->io; + + gameport_unregister_port(ensoniq->gameport); + ensoniq->gameport = NULL; + ensoniq->ctrl &= ~ES_JYSTK_EN; + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + release_region(port, 8); + } +} +#else +static inline int snd_ensoniq_create_gameport(struct ensoniq *ensoniq, long port) { return -ENOSYS; } +static inline void snd_ensoniq_free_gameport(struct ensoniq *ensoniq) { } +#endif /* SUPPORT_JOYSTICK */ + +/* + + */ + +static void snd_ensoniq_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct ensoniq *ensoniq = entry->private_data; + + snd_iprintf(buffer, "Ensoniq AudioPCI " CHIP_NAME "\n\n"); + snd_iprintf(buffer, "Joystick enable : %s\n", + str_on_off(ensoniq->ctrl & ES_JYSTK_EN)); +#ifdef CHIP1370 + snd_iprintf(buffer, "MIC +5V bias : %s\n", + str_on_off(ensoniq->ctrl & ES_1370_XCTL1)); + snd_iprintf(buffer, "Line In to AOUT : %s\n", + str_on_off(ensoniq->ctrl & ES_1370_XCTL0)); +#else + snd_iprintf(buffer, "Joystick port : 0x%x\n", + (ES_1371_JOY_ASELI(ensoniq->ctrl) * 8) + 0x200); +#endif +} + +static void snd_ensoniq_proc_init(struct ensoniq *ensoniq) +{ + snd_card_ro_proc_new(ensoniq->card, "audiopci", ensoniq, + snd_ensoniq_proc_read); +} + +/* + + */ + +static void snd_ensoniq_free(struct snd_card *card) +{ + struct ensoniq *ensoniq = card->private_data; + + snd_ensoniq_free_gameport(ensoniq); +#ifdef CHIP1370 + outl(ES_1370_SERR_DISABLE, ES_REG(ensoniq, CONTROL)); /* switch everything off */ + outl(0, ES_REG(ensoniq, SERIAL)); /* clear serial interface */ +#else + outl(0, ES_REG(ensoniq, CONTROL)); /* switch everything off */ + outl(0, ES_REG(ensoniq, SERIAL)); /* clear serial interface */ +#endif +} + +#ifdef CHIP1371 +static const struct snd_pci_quirk es1371_amplifier_hack[] = { + SND_PCI_QUIRK_ID(0x107b, 0x2150), /* Gateway Solo 2150 */ + SND_PCI_QUIRK_ID(0x13bd, 0x100c), /* EV1938 on Mebius PC-MJ100V */ + SND_PCI_QUIRK_ID(0x1102, 0x5938), /* Targa Xtender300 */ + SND_PCI_QUIRK_ID(0x1102, 0x8938), /* IPC Topnote G notebook */ + { } /* end */ +}; + +static const struct es1371_quirk es1371_ac97_reset_hack[] = { + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_C }, + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_D }, + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_E }, + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_ES1371, .rev = ES1371REV_CT5880_A }, + { .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_ES1371, .rev = ES1371REV_ES1373_8 }, + { .vid = PCI_ANY_ID, .did = PCI_ANY_ID } +}; +#endif + +static void snd_ensoniq_chip_init(struct ensoniq *ensoniq) +{ +#ifdef CHIP1371 + int idx; +#endif + /* this code was part of snd_ensoniq_create before intruduction + * of suspend/resume + */ +#ifdef CHIP1370 + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL)); + outl(ES_MEM_PAGEO(ES_PAGE_ADC), ES_REG(ensoniq, MEM_PAGE)); + outl(ensoniq->dma_bug->addr, ES_REG(ensoniq, PHANTOM_FRAME)); + outl(0, ES_REG(ensoniq, PHANTOM_COUNT)); +#else + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL)); + outl(0, ES_REG(ensoniq, 1371_LEGACY)); + if (es1371_quirk_lookup(ensoniq, es1371_ac97_reset_hack)) { + outl(ensoniq->cssr, ES_REG(ensoniq, STATUS)); + /* need to delay around 20ms(bleech) to give + some CODECs enough time to wakeup */ + msleep(20); + } + /* AC'97 warm reset to start the bitclk */ + outl(ensoniq->ctrl | ES_1371_SYNC_RES, ES_REG(ensoniq, CONTROL)); + inl(ES_REG(ensoniq, CONTROL)); + udelay(20); + outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); + /* Init the sample rate converter */ + snd_es1371_wait_src_ready(ensoniq); + outl(ES_1371_SRC_DISABLE, ES_REG(ensoniq, 1371_SMPRATE)); + for (idx = 0; idx < 0x80; idx++) + snd_es1371_src_write(ensoniq, idx, 0); + snd_es1371_src_write(ensoniq, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); + snd_es1371_src_write(ensoniq, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS, 16 << 10); + snd_es1371_src_write(ensoniq, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); + snd_es1371_src_write(ensoniq, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS, 16 << 10); + snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_ADC, 1 << 12); + snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_ADC + 1, 1 << 12); + snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_DAC1, 1 << 12); + snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); + snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_DAC2, 1 << 12); + snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); + snd_es1371_adc_rate(ensoniq, 22050); + snd_es1371_dac1_rate(ensoniq, 22050); + snd_es1371_dac2_rate(ensoniq, 22050); + /* WARNING: + * enabling the sample rate converter without properly programming + * its parameters causes the chip to lock up (the SRC busy bit will + * be stuck high, and I've found no way to rectify this other than + * power cycle) - Thomas Sailer + */ + snd_es1371_wait_src_ready(ensoniq); + outl(0, ES_REG(ensoniq, 1371_SMPRATE)); + /* try reset codec directly */ + outl(ES_1371_CODEC_WRITE(0, 0), ES_REG(ensoniq, 1371_CODEC)); +#endif + outb(ensoniq->uartc = 0x00, ES_REG(ensoniq, UART_CONTROL)); + outb(0x00, ES_REG(ensoniq, UART_RES)); + outl(ensoniq->cssr, ES_REG(ensoniq, STATUS)); +} + +static int snd_ensoniq_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct ensoniq *ensoniq = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + +#ifdef CHIP1371 + snd_ac97_suspend(ensoniq->u.es1371.ac97); +#else + /* try to reset AK4531 */ + outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x02), ES_REG(ensoniq, 1370_CODEC)); + inw(ES_REG(ensoniq, 1370_CODEC)); + udelay(100); + outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x03), ES_REG(ensoniq, 1370_CODEC)); + inw(ES_REG(ensoniq, 1370_CODEC)); + udelay(100); + snd_ak4531_suspend(ensoniq->u.es1370.ak4531); +#endif + return 0; +} + +static int snd_ensoniq_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct ensoniq *ensoniq = card->private_data; + + snd_ensoniq_chip_init(ensoniq); + +#ifdef CHIP1371 + snd_ac97_resume(ensoniq->u.es1371.ac97); +#else + snd_ak4531_resume(ensoniq->u.es1370.ak4531); +#endif + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_ensoniq_pm, snd_ensoniq_suspend, snd_ensoniq_resume); + +static int snd_ensoniq_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct ensoniq *ensoniq = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + spin_lock_init(&ensoniq->reg_lock); + mutex_init(&ensoniq->src_mutex); + ensoniq->card = card; + ensoniq->pci = pci; + ensoniq->irq = -1; + err = pcim_request_all_regions(pci, "Ensoniq AudioPCI"); + if (err < 0) + return err; + ensoniq->port = pci_resource_start(pci, 0); + if (devm_request_irq(&pci->dev, pci->irq, snd_audiopci_interrupt, + IRQF_SHARED, KBUILD_MODNAME, ensoniq)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + ensoniq->irq = pci->irq; + card->sync_irq = ensoniq->irq; +#ifdef CHIP1370 + ensoniq->dma_bug = + snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, 16); + if (!ensoniq->dma_bug) + return -ENOMEM; +#endif + pci_set_master(pci); + ensoniq->rev = pci->revision; +#ifdef CHIP1370 +#if 0 + ensoniq->ctrl = ES_1370_CDC_EN | ES_1370_SERR_DISABLE | + ES_1370_PCLKDIVO(ES_1370_SRTODIV(8000)); +#else /* get microphone working */ + ensoniq->ctrl = ES_1370_CDC_EN | ES_1370_PCLKDIVO(ES_1370_SRTODIV(8000)); +#endif + ensoniq->sctrl = 0; +#else + ensoniq->ctrl = 0; + ensoniq->sctrl = 0; + ensoniq->cssr = 0; + if (snd_pci_quirk_lookup(pci, es1371_amplifier_hack)) + ensoniq->ctrl |= ES_1371_GPIO_OUT(1); /* turn amplifier on */ + + if (es1371_quirk_lookup(ensoniq, es1371_ac97_reset_hack)) + ensoniq->cssr |= ES_1371_ST_AC97_RST; +#endif + + card->private_free = snd_ensoniq_free; + snd_ensoniq_chip_init(ensoniq); + + snd_ensoniq_proc_init(ensoniq); + return 0; +} + +/* + * MIDI section + */ + +static void snd_ensoniq_midi_interrupt(struct ensoniq * ensoniq) +{ + struct snd_rawmidi *rmidi = ensoniq->rmidi; + unsigned char status, mask, byte; + + if (rmidi == NULL) + return; + /* do Rx at first */ + scoped_guard(spinlock, &ensoniq->reg_lock) { + mask = ensoniq->uartm & ES_MODE_INPUT ? ES_RXRDY : 0; + while (mask) { + status = inb(ES_REG(ensoniq, UART_STATUS)); + if ((status & mask) == 0) + break; + byte = inb(ES_REG(ensoniq, UART_DATA)); + snd_rawmidi_receive(ensoniq->midi_input, &byte, 1); + } + } + + /* do Tx at second */ + guard(spinlock)(&ensoniq->reg_lock); + mask = ensoniq->uartm & ES_MODE_OUTPUT ? ES_TXRDY : 0; + while (mask) { + status = inb(ES_REG(ensoniq, UART_STATUS)); + if ((status & mask) == 0) + break; + if (snd_rawmidi_transmit(ensoniq->midi_output, &byte, 1) != 1) { + ensoniq->uartc &= ~ES_TXINTENM; + outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL)); + mask &= ~ES_TXRDY; + } else { + outb(byte, ES_REG(ensoniq, UART_DATA)); + } + } +} + +static int snd_ensoniq_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct ensoniq *ensoniq = substream->rmidi->private_data; + + guard(spinlock_irq)(&ensoniq->reg_lock); + ensoniq->uartm |= ES_MODE_INPUT; + ensoniq->midi_input = substream; + if (!(ensoniq->uartm & ES_MODE_OUTPUT)) { + outb(ES_CNTRL(3), ES_REG(ensoniq, UART_CONTROL)); + outb(ensoniq->uartc = 0, ES_REG(ensoniq, UART_CONTROL)); + outl(ensoniq->ctrl |= ES_UART_EN, ES_REG(ensoniq, CONTROL)); + } + return 0; +} + +static int snd_ensoniq_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct ensoniq *ensoniq = substream->rmidi->private_data; + + guard(spinlock_irq)(&ensoniq->reg_lock); + if (!(ensoniq->uartm & ES_MODE_OUTPUT)) { + outb(ensoniq->uartc = 0, ES_REG(ensoniq, UART_CONTROL)); + outl(ensoniq->ctrl &= ~ES_UART_EN, ES_REG(ensoniq, CONTROL)); + } else { + outb(ensoniq->uartc &= ~ES_RXINTEN, ES_REG(ensoniq, UART_CONTROL)); + } + ensoniq->midi_input = NULL; + ensoniq->uartm &= ~ES_MODE_INPUT; + return 0; +} + +static int snd_ensoniq_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct ensoniq *ensoniq = substream->rmidi->private_data; + + guard(spinlock_irq)(&ensoniq->reg_lock); + ensoniq->uartm |= ES_MODE_OUTPUT; + ensoniq->midi_output = substream; + if (!(ensoniq->uartm & ES_MODE_INPUT)) { + outb(ES_CNTRL(3), ES_REG(ensoniq, UART_CONTROL)); + outb(ensoniq->uartc = 0, ES_REG(ensoniq, UART_CONTROL)); + outl(ensoniq->ctrl |= ES_UART_EN, ES_REG(ensoniq, CONTROL)); + } + return 0; +} + +static int snd_ensoniq_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct ensoniq *ensoniq = substream->rmidi->private_data; + + guard(spinlock_irq)(&ensoniq->reg_lock); + if (!(ensoniq->uartm & ES_MODE_INPUT)) { + outb(ensoniq->uartc = 0, ES_REG(ensoniq, UART_CONTROL)); + outl(ensoniq->ctrl &= ~ES_UART_EN, ES_REG(ensoniq, CONTROL)); + } else { + outb(ensoniq->uartc &= ~ES_TXINTENM, ES_REG(ensoniq, UART_CONTROL)); + } + ensoniq->midi_output = NULL; + ensoniq->uartm &= ~ES_MODE_OUTPUT; + return 0; +} + +static void snd_ensoniq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct ensoniq *ensoniq = substream->rmidi->private_data; + int idx; + + guard(spinlock_irqsave)(&ensoniq->reg_lock); + if (up) { + if ((ensoniq->uartc & ES_RXINTEN) == 0) { + /* empty input FIFO */ + for (idx = 0; idx < 32; idx++) + inb(ES_REG(ensoniq, UART_DATA)); + ensoniq->uartc |= ES_RXINTEN; + outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL)); + } + } else { + if (ensoniq->uartc & ES_RXINTEN) { + ensoniq->uartc &= ~ES_RXINTEN; + outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL)); + } + } +} + +static void snd_ensoniq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct ensoniq *ensoniq = substream->rmidi->private_data; + unsigned char byte; + + guard(spinlock_irqsave)(&ensoniq->reg_lock); + if (up) { + if (ES_TXINTENI(ensoniq->uartc) == 0) { + ensoniq->uartc |= ES_TXINTENO(1); + /* fill UART FIFO buffer at first, and turn Tx interrupts only if necessary */ + while (ES_TXINTENI(ensoniq->uartc) == 1 && + (inb(ES_REG(ensoniq, UART_STATUS)) & ES_TXRDY)) { + if (snd_rawmidi_transmit(substream, &byte, 1) != 1) { + ensoniq->uartc &= ~ES_TXINTENM; + } else { + outb(byte, ES_REG(ensoniq, UART_DATA)); + } + } + outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL)); + } + } else { + if (ES_TXINTENI(ensoniq->uartc) == 1) { + ensoniq->uartc &= ~ES_TXINTENM; + outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL)); + } + } +} + +static const struct snd_rawmidi_ops snd_ensoniq_midi_output = +{ + .open = snd_ensoniq_midi_output_open, + .close = snd_ensoniq_midi_output_close, + .trigger = snd_ensoniq_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_ensoniq_midi_input = +{ + .open = snd_ensoniq_midi_input_open, + .close = snd_ensoniq_midi_input_close, + .trigger = snd_ensoniq_midi_input_trigger, +}; + +static int snd_ensoniq_midi(struct ensoniq *ensoniq, int device) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(ensoniq->card, "ES1370/1", device, 1, 1, &rmidi); + if (err < 0) + return err; + strscpy(rmidi->name, CHIP_NAME); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_ensoniq_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_ensoniq_midi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = ensoniq; + ensoniq->rmidi = rmidi; + return 0; +} + +/* + * Interrupt handler + */ + +static irqreturn_t snd_audiopci_interrupt(int irq, void *dev_id) +{ + struct ensoniq *ensoniq = dev_id; + unsigned int status, sctrl; + + if (ensoniq == NULL) + return IRQ_NONE; + + status = inl(ES_REG(ensoniq, STATUS)); + if (!(status & ES_INTR)) + return IRQ_NONE; + + scoped_guard(spinlock, &ensoniq->reg_lock) { + sctrl = ensoniq->sctrl; + if (status & ES_DAC1) + sctrl &= ~ES_P1_INT_EN; + if (status & ES_DAC2) + sctrl &= ~ES_P2_INT_EN; + if (status & ES_ADC) + sctrl &= ~ES_R1_INT_EN; + outl(sctrl, ES_REG(ensoniq, SERIAL)); + outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL)); + } + + if (status & ES_UART) + snd_ensoniq_midi_interrupt(ensoniq); + if ((status & ES_DAC2) && ensoniq->playback2_substream) + snd_pcm_period_elapsed(ensoniq->playback2_substream); + if ((status & ES_ADC) && ensoniq->capture_substream) + snd_pcm_period_elapsed(ensoniq->capture_substream); + if ((status & ES_DAC1) && ensoniq->playback1_substream) + snd_pcm_period_elapsed(ensoniq->playback1_substream); + return IRQ_HANDLED; +} + +static int __snd_audiopci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct ensoniq *ensoniq; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*ensoniq), &card); + if (err < 0) + return err; + ensoniq = card->private_data; + + err = snd_ensoniq_create(card, pci); + if (err < 0) + return err; + +#ifdef CHIP1370 + err = snd_ensoniq_1370_mixer(ensoniq); + if (err < 0) + return err; +#endif +#ifdef CHIP1371 + err = snd_ensoniq_1371_mixer(ensoniq, spdif[dev], lineio[dev]); + if (err < 0) + return err; +#endif + err = snd_ensoniq_pcm(ensoniq, 0); + if (err < 0) + return err; + err = snd_ensoniq_pcm2(ensoniq, 1); + if (err < 0) + return err; + err = snd_ensoniq_midi(ensoniq, 0); + if (err < 0) + return err; + + snd_ensoniq_create_gameport(ensoniq, dev); + + strscpy(card->driver, DRIVER_NAME); + + strscpy(card->shortname, "Ensoniq AudioPCI"); + sprintf(card->longname, "%s %s at 0x%lx, irq %i", + card->shortname, + card->driver, + ensoniq->port, + ensoniq->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_audiopci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_audiopci_probe(pci, pci_id)); +} + +static struct pci_driver ens137x_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_audiopci_ids, + .probe = snd_audiopci_probe, + .driver = { + .pm = &snd_ensoniq_pm, + }, +}; + +module_pci_driver(ens137x_driver); diff --git a/sound/pci/ens1371.c b/sound/pci/ens1371.c new file mode 100644 index 000000000..694978c4b --- /dev/null +++ b/sound/pci/ens1371.c @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-2.0 +#define CHIP1371 +#include "ens1370.c" diff --git a/sound/pci/es1938.c b/sound/pci/es1938.c new file mode 100644 index 000000000..217beb937 --- /dev/null +++ b/sound/pci/es1938.c @@ -0,0 +1,1781 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for ESS Solo-1 (ES1938, ES1946, ES1969) soundcard + * Copyright (c) by Jaromir Koutek , + * Jaroslav Kysela , + * Thomas Sailer , + * Abramo Bagnara , + * Markus Gruber + * + * Rewritten from sonicvibes.c source. + * + * TODO: + * Rewrite better spinlocks + */ + +/* + NOTES: + - Capture data is written unaligned starting from dma_base + 1 so I need to + disable mmap and to add a copy callback. + - After several cycle of the following: + while : ; do arecord -d1 -f cd -t raw | aplay -f cd ; done + a "playback write error (DMA or IRQ trouble?)" may happen. + This is due to playback interrupts not generated. + I suspect a timing issue. + - Sometimes the interrupt handler is invoked wrongly during playback. + This generates some harmless "Unexpected hw_pointer: wrong interrupt + acknowledge". + I've seen that using small period sizes. + Reproducible with: + mpg123 test.mp3 & + hdparm -t -T /dev/hda +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaromir Koutek "); +MODULE_DESCRIPTION("ESS Solo-1"); +MODULE_LICENSE("GPL"); + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK 1 +#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_PNP; /* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ESS Solo-1 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ESS Solo-1 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ESS Solo-1 soundcard."); + +#define SLIO_REG(chip, x) ((chip)->io_port + ESSIO_REG_##x) + +#define SLDM_REG(chip, x) ((chip)->ddma_port + ESSDM_REG_##x) + +#define SLSB_REG(chip, x) ((chip)->sb_port + ESSSB_REG_##x) + +#define SL_PCI_LEGACYCONTROL 0x40 +#define SL_PCI_CONFIG 0x50 +#define SL_PCI_DDMACONTROL 0x60 + +#define ESSIO_REG_AUDIO2DMAADDR 0 +#define ESSIO_REG_AUDIO2DMACOUNT 4 +#define ESSIO_REG_AUDIO2MODE 6 +#define ESSIO_REG_IRQCONTROL 7 + +#define ESSDM_REG_DMAADDR 0x00 +#define ESSDM_REG_DMACOUNT 0x04 +#define ESSDM_REG_DMACOMMAND 0x08 +#define ESSDM_REG_DMASTATUS 0x08 +#define ESSDM_REG_DMAMODE 0x0b +#define ESSDM_REG_DMACLEAR 0x0d +#define ESSDM_REG_DMAMASK 0x0f + +#define ESSSB_REG_FMLOWADDR 0x00 +#define ESSSB_REG_FMHIGHADDR 0x02 +#define ESSSB_REG_MIXERADDR 0x04 +#define ESSSB_REG_MIXERDATA 0x05 + +#define ESSSB_IREG_AUDIO1 0x14 +#define ESSSB_IREG_MICMIX 0x1a +#define ESSSB_IREG_RECSRC 0x1c +#define ESSSB_IREG_MASTER 0x32 +#define ESSSB_IREG_FM 0x36 +#define ESSSB_IREG_AUXACD 0x38 +#define ESSSB_IREG_AUXB 0x3a +#define ESSSB_IREG_PCSPEAKER 0x3c +#define ESSSB_IREG_LINE 0x3e +#define ESSSB_IREG_SPATCONTROL 0x50 +#define ESSSB_IREG_SPATLEVEL 0x52 +#define ESSSB_IREG_MASTER_LEFT 0x60 +#define ESSSB_IREG_MASTER_RIGHT 0x62 +#define ESSSB_IREG_MPU401CONTROL 0x64 +#define ESSSB_IREG_MICMIXRECORD 0x68 +#define ESSSB_IREG_AUDIO2RECORD 0x69 +#define ESSSB_IREG_AUXACDRECORD 0x6a +#define ESSSB_IREG_FMRECORD 0x6b +#define ESSSB_IREG_AUXBRECORD 0x6c +#define ESSSB_IREG_MONO 0x6d +#define ESSSB_IREG_LINERECORD 0x6e +#define ESSSB_IREG_MONORECORD 0x6f +#define ESSSB_IREG_AUDIO2SAMPLE 0x70 +#define ESSSB_IREG_AUDIO2MODE 0x71 +#define ESSSB_IREG_AUDIO2FILTER 0x72 +#define ESSSB_IREG_AUDIO2TCOUNTL 0x74 +#define ESSSB_IREG_AUDIO2TCOUNTH 0x76 +#define ESSSB_IREG_AUDIO2CONTROL1 0x78 +#define ESSSB_IREG_AUDIO2CONTROL2 0x7a +#define ESSSB_IREG_AUDIO2 0x7c + +#define ESSSB_REG_RESET 0x06 + +#define ESSSB_REG_READDATA 0x0a +#define ESSSB_REG_WRITEDATA 0x0c +#define ESSSB_REG_READSTATUS 0x0c + +#define ESSSB_REG_STATUS 0x0e + +#define ESS_CMD_EXTSAMPLERATE 0xa1 +#define ESS_CMD_FILTERDIV 0xa2 +#define ESS_CMD_DMACNTRELOADL 0xa4 +#define ESS_CMD_DMACNTRELOADH 0xa5 +#define ESS_CMD_ANALOGCONTROL 0xa8 +#define ESS_CMD_IRQCONTROL 0xb1 +#define ESS_CMD_DRQCONTROL 0xb2 +#define ESS_CMD_RECLEVEL 0xb4 +#define ESS_CMD_SETFORMAT 0xb6 +#define ESS_CMD_SETFORMAT2 0xb7 +#define ESS_CMD_DMACONTROL 0xb8 +#define ESS_CMD_DMATYPE 0xb9 +#define ESS_CMD_OFFSETLEFT 0xba +#define ESS_CMD_OFFSETRIGHT 0xbb +#define ESS_CMD_READREG 0xc0 +#define ESS_CMD_ENABLEEXT 0xc6 +#define ESS_CMD_PAUSEDMA 0xd0 +#define ESS_CMD_ENABLEAUDIO1 0xd1 +#define ESS_CMD_STOPAUDIO1 0xd3 +#define ESS_CMD_AUDIO1STATUS 0xd8 +#define ESS_CMD_CONTDMA 0xd4 +#define ESS_CMD_TESTIRQ 0xf2 + +#define ESS_RECSRC_MIC 0 +#define ESS_RECSRC_AUXACD 2 +#define ESS_RECSRC_AUXB 5 +#define ESS_RECSRC_LINE 6 +#define ESS_RECSRC_NONE 7 + +#define DAC1 0x01 +#define ADC1 0x02 +#define DAC2 0x04 + +/* + + */ + +#define SAVED_REG_SIZE 32 /* max. number of registers to save */ + +struct es1938 { + int irq; + + unsigned long io_port; + unsigned long sb_port; + unsigned long vc_port; + unsigned long mpu_port; + unsigned long game_port; + unsigned long ddma_port; + + unsigned char irqmask; + unsigned char revision; + + struct snd_kcontrol *hw_volume; + struct snd_kcontrol *hw_switch; + struct snd_kcontrol *master_volume; + struct snd_kcontrol *master_switch; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *capture_substream; + struct snd_pcm_substream *playback1_substream; + struct snd_pcm_substream *playback2_substream; + struct snd_rawmidi *rmidi; + + unsigned int dma1_size; + unsigned int dma2_size; + unsigned int dma1_start; + unsigned int dma2_start; + unsigned int dma1_shift; + unsigned int dma2_shift; + unsigned int last_capture_dmaaddr; + unsigned int active; + + spinlock_t reg_lock; + spinlock_t mixer_lock; + struct snd_info_entry *proc_entry; + +#ifdef SUPPORT_JOYSTICK + struct gameport *gameport; +#endif + unsigned char saved_regs[SAVED_REG_SIZE]; +}; + +static irqreturn_t snd_es1938_interrupt(int irq, void *dev_id); + +static const struct pci_device_id snd_es1938_ids[] = { + { PCI_VDEVICE(ESS, 0x1969) }, /* Solo-1 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_es1938_ids); + +#define RESET_LOOP_TIMEOUT 0x10000 +#define WRITE_LOOP_TIMEOUT 0x10000 +#define GET_LOOP_TIMEOUT 0x01000 + +/* ----------------------------------------------------------------- + * Write to a mixer register + * -----------------------------------------------------------------*/ +static void snd_es1938_mixer_write(struct es1938 *chip, unsigned char reg, unsigned char val) +{ + guard(spinlock_irqsave)(&chip->mixer_lock); + outb(reg, SLSB_REG(chip, MIXERADDR)); + outb(val, SLSB_REG(chip, MIXERDATA)); + dev_dbg(chip->card->dev, "Mixer reg %02x set to %02x\n", reg, val); +} + +/* ----------------------------------------------------------------- + * Read from a mixer register + * -----------------------------------------------------------------*/ +static int snd_es1938_mixer_read(struct es1938 *chip, unsigned char reg) +{ + int data; + + guard(spinlock_irqsave)(&chip->mixer_lock); + outb(reg, SLSB_REG(chip, MIXERADDR)); + data = inb(SLSB_REG(chip, MIXERDATA)); + dev_dbg(chip->card->dev, "Mixer reg %02x now is %02x\n", reg, data); + return data; +} + +/* ----------------------------------------------------------------- + * Write to some bits of a mixer register (return old value) + * -----------------------------------------------------------------*/ +static int snd_es1938_mixer_bits(struct es1938 *chip, unsigned char reg, + unsigned char mask, unsigned char val) +{ + unsigned char old, new, oval; + + guard(spinlock_irqsave)(&chip->mixer_lock); + outb(reg, SLSB_REG(chip, MIXERADDR)); + old = inb(SLSB_REG(chip, MIXERDATA)); + oval = old & mask; + if (val != oval) { + new = (old & ~mask) | (val & mask); + outb(new, SLSB_REG(chip, MIXERDATA)); + dev_dbg(chip->card->dev, + "Mixer reg %02x was %02x, set to %02x\n", + reg, old, new); + } + return oval; +} + +/* ----------------------------------------------------------------- + * Write command to Controller Registers + * -----------------------------------------------------------------*/ +static void snd_es1938_write_cmd(struct es1938 *chip, unsigned char cmd) +{ + int i; + unsigned char v; + for (i = 0; i < WRITE_LOOP_TIMEOUT; i++) { + v = inb(SLSB_REG(chip, READSTATUS)); + if (!(v & 0x80)) { + outb(cmd, SLSB_REG(chip, WRITEDATA)); + return; + } + } + dev_err(chip->card->dev, + "snd_es1938_write_cmd timeout (0x02%x/0x02%x)\n", cmd, v); +} + +/* ----------------------------------------------------------------- + * Read the Read Data Buffer + * -----------------------------------------------------------------*/ +static int snd_es1938_get_byte(struct es1938 *chip) +{ + int i; + unsigned char v; + for (i = GET_LOOP_TIMEOUT; i; i--) { + v = inb(SLSB_REG(chip, STATUS)); + if (v & 0x80) + return inb(SLSB_REG(chip, READDATA)); + } + dev_err(chip->card->dev, "get_byte timeout: status 0x02%x\n", v); + return -ENODEV; +} + +/* ----------------------------------------------------------------- + * Write value cmd register + * -----------------------------------------------------------------*/ +static void snd_es1938_write(struct es1938 *chip, unsigned char reg, unsigned char val) +{ + guard(spinlock_irqsave)(&chip->reg_lock); + snd_es1938_write_cmd(chip, reg); + snd_es1938_write_cmd(chip, val); + dev_dbg(chip->card->dev, "Reg %02x set to %02x\n", reg, val); +} + +/* ----------------------------------------------------------------- + * Read data from cmd register and return it + * -----------------------------------------------------------------*/ +static unsigned char snd_es1938_read(struct es1938 *chip, unsigned char reg) +{ + unsigned char val; + + guard(spinlock_irqsave)(&chip->reg_lock); + snd_es1938_write_cmd(chip, ESS_CMD_READREG); + snd_es1938_write_cmd(chip, reg); + val = snd_es1938_get_byte(chip); + dev_dbg(chip->card->dev, "Reg %02x now is %02x\n", reg, val); + return val; +} + +/* ----------------------------------------------------------------- + * Write data to cmd register and return old value + * -----------------------------------------------------------------*/ +static int snd_es1938_bits(struct es1938 *chip, unsigned char reg, unsigned char mask, + unsigned char val) +{ + unsigned char old, new, oval; + + guard(spinlock_irqsave)(&chip->reg_lock); + snd_es1938_write_cmd(chip, ESS_CMD_READREG); + snd_es1938_write_cmd(chip, reg); + old = snd_es1938_get_byte(chip); + oval = old & mask; + if (val != oval) { + snd_es1938_write_cmd(chip, reg); + new = (old & ~mask) | (val & mask); + snd_es1938_write_cmd(chip, new); + dev_dbg(chip->card->dev, "Reg %02x was %02x, set to %02x\n", + reg, old, new); + } + return oval; +} + +/* -------------------------------------------------------------------- + * Reset the chip + * --------------------------------------------------------------------*/ +static void snd_es1938_reset(struct es1938 *chip) +{ + int i; + + outb(3, SLSB_REG(chip, RESET)); + inb(SLSB_REG(chip, RESET)); + outb(0, SLSB_REG(chip, RESET)); + for (i = 0; i < RESET_LOOP_TIMEOUT; i++) { + if (inb(SLSB_REG(chip, STATUS)) & 0x80) { + if (inb(SLSB_REG(chip, READDATA)) == 0xaa) + goto __next; + } + } + dev_err(chip->card->dev, "ESS Solo-1 reset failed\n"); + + __next: + snd_es1938_write_cmd(chip, ESS_CMD_ENABLEEXT); + + /* Demand transfer DMA: 4 bytes per DMA request */ + snd_es1938_write(chip, ESS_CMD_DMATYPE, 2); + + /* Change behaviour of register A1 + 4x oversampling + 2nd channel DAC asynchronous */ + snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2MODE, 0x32); + /* enable/select DMA channel and IRQ channel */ + snd_es1938_bits(chip, ESS_CMD_IRQCONTROL, 0xf0, 0x50); + snd_es1938_bits(chip, ESS_CMD_DRQCONTROL, 0xf0, 0x50); + snd_es1938_write_cmd(chip, ESS_CMD_ENABLEAUDIO1); + /* Set spatializer parameters to recommended values */ + snd_es1938_mixer_write(chip, 0x54, 0x8f); + snd_es1938_mixer_write(chip, 0x56, 0x95); + snd_es1938_mixer_write(chip, 0x58, 0x94); + snd_es1938_mixer_write(chip, 0x5a, 0x80); +} + +/* -------------------------------------------------------------------- + * Reset the FIFOs + * --------------------------------------------------------------------*/ +static void snd_es1938_reset_fifo(struct es1938 *chip) +{ + outb(2, SLSB_REG(chip, RESET)); + outb(0, SLSB_REG(chip, RESET)); +} + +static const struct snd_ratnum 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 hw_constraints_clocks = { + .nrats = 2, + .rats = clocks, +}; + + +static void snd_es1938_rate_set(struct es1938 *chip, + struct snd_pcm_substream *substream, + int mode) +{ + unsigned int bits, div0; + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->rate_num == clocks[0].num) + bits = 128 - runtime->rate_den; + else + bits = 256 - runtime->rate_den; + + /* set filter register */ + div0 = 256 - 7160000*20/(8*82*runtime->rate); + + if (mode == DAC2) { + snd_es1938_mixer_write(chip, 0x70, bits); + snd_es1938_mixer_write(chip, 0x72, div0); + } else { + snd_es1938_write(chip, 0xA1, bits); + snd_es1938_write(chip, 0xA2, div0); + } +} + +/* -------------------------------------------------------------------- + * Configure Solo1 builtin DMA Controller + * --------------------------------------------------------------------*/ + +static void snd_es1938_playback1_setdma(struct es1938 *chip) +{ + outb(0x00, SLIO_REG(chip, AUDIO2MODE)); + outl(chip->dma2_start, SLIO_REG(chip, AUDIO2DMAADDR)); + outw(0, SLIO_REG(chip, AUDIO2DMACOUNT)); + outw(chip->dma2_size, SLIO_REG(chip, AUDIO2DMACOUNT)); +} + +static void snd_es1938_playback2_setdma(struct es1938 *chip) +{ + /* Enable DMA controller */ + outb(0xc4, SLDM_REG(chip, DMACOMMAND)); + /* 1. Master reset */ + outb(0, SLDM_REG(chip, DMACLEAR)); + /* 2. Mask DMA */ + outb(1, SLDM_REG(chip, DMAMASK)); + outb(0x18, SLDM_REG(chip, DMAMODE)); + outl(chip->dma1_start, SLDM_REG(chip, DMAADDR)); + outw(chip->dma1_size - 1, SLDM_REG(chip, DMACOUNT)); + /* 3. Unmask DMA */ + outb(0, SLDM_REG(chip, DMAMASK)); +} + +static void snd_es1938_capture_setdma(struct es1938 *chip) +{ + /* Enable DMA controller */ + outb(0xc4, SLDM_REG(chip, DMACOMMAND)); + /* 1. Master reset */ + outb(0, SLDM_REG(chip, DMACLEAR)); + /* 2. Mask DMA */ + outb(1, SLDM_REG(chip, DMAMASK)); + outb(0x14, SLDM_REG(chip, DMAMODE)); + outl(chip->dma1_start, SLDM_REG(chip, DMAADDR)); + chip->last_capture_dmaaddr = chip->dma1_start; + outw(chip->dma1_size - 1, SLDM_REG(chip, DMACOUNT)); + /* 3. Unmask DMA */ + outb(0, SLDM_REG(chip, DMAMASK)); +} + +/* ---------------------------------------------------------------------- + * + * *** PCM part *** + */ + +static int snd_es1938_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + int val; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + val = 0x0f; + chip->active |= ADC1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + val = 0x00; + chip->active &= ~ADC1; + break; + default: + return -EINVAL; + } + snd_es1938_write(chip, ESS_CMD_DMACONTROL, val); + return 0; +} + +static int snd_es1938_playback1_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + /* According to the documentation this should be: + 0x13 but that value may randomly swap stereo channels */ + snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2CONTROL1, 0x92); + udelay(10); + snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2CONTROL1, 0x93); + /* This two stage init gives the FIFO -> DAC connection time to + * settle before first data from DMA flows in. This should ensure + * no swapping of stereo channels. Report a bug if otherwise :-) */ + outb(0x0a, SLIO_REG(chip, AUDIO2MODE)); + chip->active |= DAC2; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + outb(0, SLIO_REG(chip, AUDIO2MODE)); + snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2CONTROL1, 0); + chip->active &= ~DAC2; + break; + default: + return -EINVAL; + } + return 0; +} + +static int snd_es1938_playback2_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + int val; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + val = 5; + chip->active |= DAC1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + val = 0; + chip->active &= ~DAC1; + break; + default: + return -EINVAL; + } + snd_es1938_write(chip, ESS_CMD_DMACONTROL, val); + return 0; +} + +static int snd_es1938_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + switch (substream->number) { + case 0: + return snd_es1938_playback1_trigger(substream, cmd); + case 1: + return snd_es1938_playback2_trigger(substream, cmd); + } + snd_BUG(); + return -EINVAL; +} + +/* -------------------------------------------------------------------- + * First channel for Extended Mode Audio 1 ADC Operation + * --------------------------------------------------------------------*/ +static int snd_es1938_capture_prepare(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int u, is8, mono; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma1_size = size; + chip->dma1_start = runtime->dma_addr; + + mono = (runtime->channels > 1) ? 0 : 1; + is8 = snd_pcm_format_width(runtime->format) == 16 ? 0 : 1; + u = snd_pcm_format_unsigned(runtime->format); + + chip->dma1_shift = 2 - mono - is8; + + snd_es1938_reset_fifo(chip); + + /* program type */ + snd_es1938_bits(chip, ESS_CMD_ANALOGCONTROL, 0x03, (mono ? 2 : 1)); + + /* set clock and counters */ + snd_es1938_rate_set(chip, substream, ADC1); + + count = 0x10000 - count; + snd_es1938_write(chip, ESS_CMD_DMACNTRELOADL, count & 0xff); + snd_es1938_write(chip, ESS_CMD_DMACNTRELOADH, count >> 8); + + /* initialize and configure ADC */ + snd_es1938_write(chip, ESS_CMD_SETFORMAT2, u ? 0x51 : 0x71); + snd_es1938_write(chip, ESS_CMD_SETFORMAT2, 0x90 | + (u ? 0x00 : 0x20) | + (is8 ? 0x00 : 0x04) | + (mono ? 0x40 : 0x08)); + + // snd_es1938_reset_fifo(chip); + + /* 11. configure system interrupt controller and DMA controller */ + snd_es1938_capture_setdma(chip); + + return 0; +} + + +/* ------------------------------------------------------------------------------ + * Second Audio channel DAC Operation + * ------------------------------------------------------------------------------*/ +static int snd_es1938_playback1_prepare(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int u, is8, mono; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma2_size = size; + chip->dma2_start = runtime->dma_addr; + + mono = (runtime->channels > 1) ? 0 : 1; + is8 = snd_pcm_format_width(runtime->format) == 16 ? 0 : 1; + u = snd_pcm_format_unsigned(runtime->format); + + chip->dma2_shift = 2 - mono - is8; + + snd_es1938_reset_fifo(chip); + + /* set clock and counters */ + snd_es1938_rate_set(chip, substream, DAC2); + + count >>= 1; + count = 0x10000 - count; + snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2TCOUNTL, count & 0xff); + snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2TCOUNTH, count >> 8); + + /* initialize and configure Audio 2 DAC */ + snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2CONTROL2, 0x40 | (u ? 0 : 4) | + (mono ? 0 : 2) | (is8 ? 0 : 1)); + + /* program DMA */ + snd_es1938_playback1_setdma(chip); + + return 0; +} + +static int snd_es1938_playback2_prepare(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int u, is8, mono; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma1_size = size; + chip->dma1_start = runtime->dma_addr; + + mono = (runtime->channels > 1) ? 0 : 1; + is8 = snd_pcm_format_width(runtime->format) == 16 ? 0 : 1; + u = snd_pcm_format_unsigned(runtime->format); + + chip->dma1_shift = 2 - mono - is8; + + count = 0x10000 - count; + + /* reset */ + snd_es1938_reset_fifo(chip); + + snd_es1938_bits(chip, ESS_CMD_ANALOGCONTROL, 0x03, (mono ? 2 : 1)); + + /* set clock and counters */ + snd_es1938_rate_set(chip, substream, DAC1); + snd_es1938_write(chip, ESS_CMD_DMACNTRELOADL, count & 0xff); + snd_es1938_write(chip, ESS_CMD_DMACNTRELOADH, count >> 8); + + /* initialized and configure DAC */ + snd_es1938_write(chip, ESS_CMD_SETFORMAT, u ? 0x80 : 0x00); + snd_es1938_write(chip, ESS_CMD_SETFORMAT, u ? 0x51 : 0x71); + snd_es1938_write(chip, ESS_CMD_SETFORMAT2, + 0x90 | (mono ? 0x40 : 0x08) | + (is8 ? 0x00 : 0x04) | (u ? 0x00 : 0x20)); + + /* program DMA */ + snd_es1938_playback2_setdma(chip); + + return 0; +} + +static int snd_es1938_playback_prepare(struct snd_pcm_substream *substream) +{ + switch (substream->number) { + case 0: + return snd_es1938_playback1_prepare(substream); + case 1: + return snd_es1938_playback2_prepare(substream); + } + snd_BUG(); + return -EINVAL; +} + +/* during the incrementing of dma counters the DMA register reads sometimes + returns garbage. To ensure a valid hw pointer, the following checks which + should be very unlikely to fail are used: + - is the current DMA address in the valid DMA range ? + - is the sum of DMA address and DMA counter pointing to the last DMA byte ? + One can argue this could differ by one byte depending on which register is + updated first, so the implementation below allows for that. +*/ +static snd_pcm_uframes_t snd_es1938_capture_pointer(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + size_t ptr; +#if 0 + size_t old, new; + /* This stuff is *needed*, don't ask why - AB */ + old = inw(SLDM_REG(chip, DMACOUNT)); + while ((new = inw(SLDM_REG(chip, DMACOUNT))) != old) + old = new; + ptr = chip->dma1_size - 1 - new; +#else + size_t count; + unsigned int diff; + + ptr = inl(SLDM_REG(chip, DMAADDR)); + count = inw(SLDM_REG(chip, DMACOUNT)); + diff = chip->dma1_start + chip->dma1_size - ptr - count; + + if (diff > 3 || ptr < chip->dma1_start + || ptr >= chip->dma1_start+chip->dma1_size) + ptr = chip->last_capture_dmaaddr; /* bad, use last saved */ + else + chip->last_capture_dmaaddr = ptr; /* good, remember it */ + + ptr -= chip->dma1_start; +#endif + return ptr >> chip->dma1_shift; +} + +static snd_pcm_uframes_t snd_es1938_playback1_pointer(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + size_t ptr; +#if 1 + ptr = chip->dma2_size - inw(SLIO_REG(chip, AUDIO2DMACOUNT)); +#else + ptr = inl(SLIO_REG(chip, AUDIO2DMAADDR)) - chip->dma2_start; +#endif + return ptr >> chip->dma2_shift; +} + +static snd_pcm_uframes_t snd_es1938_playback2_pointer(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + size_t ptr; + size_t old, new; +#if 1 + /* This stuff is *needed*, don't ask why - AB */ + old = inw(SLDM_REG(chip, DMACOUNT)); + while ((new = inw(SLDM_REG(chip, DMACOUNT))) != old) + old = new; + ptr = chip->dma1_size - 1 - new; +#else + ptr = inl(SLDM_REG(chip, DMAADDR)) - chip->dma1_start; +#endif + return ptr >> chip->dma1_shift; +} + +static snd_pcm_uframes_t snd_es1938_playback_pointer(struct snd_pcm_substream *substream) +{ + switch (substream->number) { + case 0: + return snd_es1938_playback1_pointer(substream); + case 1: + return snd_es1938_playback2_pointer(substream); + } + snd_BUG(); + return -EINVAL; +} + +static int snd_es1938_capture_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *dst, unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct es1938 *chip = snd_pcm_substream_chip(substream); + + if (snd_BUG_ON(pos + count > chip->dma1_size)) + return -EINVAL; + if (pos + count < chip->dma1_size) { + if (copy_to_iter(runtime->dma_area + pos + 1, count, dst) != count) + return -EFAULT; + } else { + if (copy_to_iter(runtime->dma_area + pos + 1, count - 1, dst) != count - 1) + return -EFAULT; + if (copy_to_iter(runtime->dma_area, 1, dst) != 1) + return -EFAULT; + } + return 0; +} + +/* ---------------------------------------------------------------------- + * Audio1 Capture (ADC) + * ----------------------------------------------------------------------*/ +static const struct snd_pcm_hardware snd_es1938_capture = +{ + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 6000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x8000, /* DMA controller screws on higher values */ + .period_bytes_min = 64, + .period_bytes_max = 0x8000, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 256, +}; + +/* ----------------------------------------------------------------------- + * Audio2 Playback (DAC) + * -----------------------------------------------------------------------*/ +static const struct snd_pcm_hardware snd_es1938_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 6000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x8000, /* DMA controller screws on higher values */ + .period_bytes_min = 64, + .period_bytes_max = 0x8000, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 256, +}; + +static int snd_es1938_capture_open(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (chip->playback2_substream) + return -EAGAIN; + chip->capture_substream = substream; + runtime->hw = snd_es1938_capture; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, 0xff00); + return 0; +} + +static int snd_es1938_playback_open(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + switch (substream->number) { + case 0: + chip->playback1_substream = substream; + break; + case 1: + if (chip->capture_substream) + return -EAGAIN; + chip->playback2_substream = substream; + break; + default: + snd_BUG(); + return -EINVAL; + } + runtime->hw = snd_es1938_playback; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, 0xff00); + return 0; +} + +static int snd_es1938_capture_close(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + return 0; +} + +static int snd_es1938_playback_close(struct snd_pcm_substream *substream) +{ + struct es1938 *chip = snd_pcm_substream_chip(substream); + + switch (substream->number) { + case 0: + chip->playback1_substream = NULL; + break; + case 1: + chip->playback2_substream = NULL; + break; + default: + snd_BUG(); + return -EINVAL; + } + return 0; +} + +static const struct snd_pcm_ops snd_es1938_playback_ops = { + .open = snd_es1938_playback_open, + .close = snd_es1938_playback_close, + .prepare = snd_es1938_playback_prepare, + .trigger = snd_es1938_playback_trigger, + .pointer = snd_es1938_playback_pointer, +}; + +static const struct snd_pcm_ops snd_es1938_capture_ops = { + .open = snd_es1938_capture_open, + .close = snd_es1938_capture_close, + .prepare = snd_es1938_capture_prepare, + .trigger = snd_es1938_capture_trigger, + .pointer = snd_es1938_capture_pointer, + .copy = snd_es1938_capture_copy, +}; + +static int snd_es1938_new_pcm(struct es1938 *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "es-1938-1946", device, 2, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1938_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1938_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + strscpy(pcm->name, "ESS Solo-1"); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 64*1024); + + chip->pcm = pcm; + return 0; +} + +/* ------------------------------------------------------------------- + * + * *** Mixer part *** + */ + +static int snd_es1938_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_es1938_get_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = snd_es1938_mixer_read(chip, 0x1c) & 0x07; + return 0; +} + +static int snd_es1938_put_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *chip = snd_kcontrol_chip(kcontrol); + unsigned char val = ucontrol->value.enumerated.item[0]; + + if (val > 7) + return -EINVAL; + return snd_es1938_mixer_bits(chip, 0x1c, 0x07, val) != val; +} + +#define snd_es1938_info_spatializer_enable snd_ctl_boolean_mono_info + +static int snd_es1938_get_spatializer_enable(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *chip = snd_kcontrol_chip(kcontrol); + unsigned char val = snd_es1938_mixer_read(chip, 0x50); + ucontrol->value.integer.value[0] = !!(val & 8); + return 0; +} + +static int snd_es1938_put_spatializer_enable(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *chip = snd_kcontrol_chip(kcontrol); + unsigned char oval, nval; + int change; + nval = ucontrol->value.integer.value[0] ? 0x0c : 0x04; + oval = snd_es1938_mixer_read(chip, 0x50) & 0x0c; + change = nval != oval; + if (change) { + snd_es1938_mixer_write(chip, 0x50, nval & ~0x04); + snd_es1938_mixer_write(chip, 0x50, nval); + } + return change; +} + +static int snd_es1938_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_es1938_get_hw_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = snd_es1938_mixer_read(chip, 0x61) & 0x3f; + ucontrol->value.integer.value[1] = snd_es1938_mixer_read(chip, 0x63) & 0x3f; + return 0; +} + +#define snd_es1938_info_hw_switch snd_ctl_boolean_stereo_info + +static int snd_es1938_get_hw_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = !(snd_es1938_mixer_read(chip, 0x61) & 0x40); + ucontrol->value.integer.value[1] = !(snd_es1938_mixer_read(chip, 0x63) & 0x40); + return 0; +} + +static void snd_es1938_hwv_free(struct snd_kcontrol *kcontrol) +{ + struct es1938 *chip = snd_kcontrol_chip(kcontrol); + chip->master_volume = NULL; + chip->master_switch = NULL; + chip->hw_volume = NULL; + chip->hw_switch = NULL; +} + +static int snd_es1938_reg_bits(struct es1938 *chip, unsigned char reg, + unsigned char mask, unsigned char val) +{ + if (reg < 0xa0) + return snd_es1938_mixer_bits(chip, reg, mask, val); + else + return snd_es1938_bits(chip, reg, mask, val); +} + +static int snd_es1938_reg_read(struct es1938 *chip, unsigned char reg) +{ + if (reg < 0xa0) + return snd_es1938_mixer_read(chip, reg); + else + return snd_es1938_read(chip, reg); +} + +#define ES1938_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_es1938_info_single, \ + .get = snd_es1938_get_single, .put = snd_es1938_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \ + .tlv = { .p = xtlv } } +#define ES1938_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_es1938_info_single, \ + .get = snd_es1938_get_single, .put = snd_es1938_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_es1938_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_es1938_get_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *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) & 0xff; + int val; + + val = snd_es1938_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_es1938_put_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *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) & 0xff; + unsigned char val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + mask <<= shift; + val <<= shift; + return snd_es1938_reg_bits(chip, reg, mask, val) != val; +} + +#define ES1938_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_es1938_info_double, \ + .get = snd_es1938_get_double, .put = snd_es1938_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22), \ + .tlv = { .p = xtlv } } +#define ES1938_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_es1938_info_double, \ + .get = snd_es1938_get_double, .put = snd_es1938_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +static int snd_es1938_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_es1938_get_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *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_es1938_reg_read(chip, left_reg); + if (left_reg != right_reg) + right = snd_es1938_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_es1938_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct es1938 *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_es1938_reg_bits(chip, left_reg, mask1, val1) != val1) + change = 1; + if (snd_es1938_reg_bits(chip, right_reg, mask2, val2) != val2) + change = 1; + } else { + change = (snd_es1938_reg_bits(chip, left_reg, mask1 | mask2, + val1 | val2) != (val1 | val2)); + } + return change; +} + +static const DECLARE_TLV_DB_RANGE(db_scale_master, + 0, 54, TLV_DB_SCALE_ITEM(-3600, 50, 1), + 54, 63, TLV_DB_SCALE_ITEM(-900, 100, 0), +); + +static const DECLARE_TLV_DB_RANGE(db_scale_audio1, + 0, 8, TLV_DB_SCALE_ITEM(-3300, 300, 1), + 8, 15, TLV_DB_SCALE_ITEM(-900, 150, 0), +); + +static const DECLARE_TLV_DB_RANGE(db_scale_audio2, + 0, 8, TLV_DB_SCALE_ITEM(-3450, 300, 1), + 8, 15, TLV_DB_SCALE_ITEM(-1050, 150, 0), +); + +static const DECLARE_TLV_DB_RANGE(db_scale_mic, + 0, 8, TLV_DB_SCALE_ITEM(-2400, 300, 1), + 8, 15, TLV_DB_SCALE_ITEM(0, 150, 0), +); + +static const DECLARE_TLV_DB_RANGE(db_scale_line, + 0, 8, TLV_DB_SCALE_ITEM(-3150, 300, 1), + 8, 15, TLV_DB_SCALE_ITEM(-750, 150, 0), +); + +static const DECLARE_TLV_DB_SCALE(db_scale_capture, 0, 150, 0); + +static const struct snd_kcontrol_new snd_es1938_controls[] = { +ES1938_DOUBLE_TLV("Master Playback Volume", 0, 0x60, 0x62, 0, 0, 63, 0, + db_scale_master), +ES1938_DOUBLE("Master Playback Switch", 0, 0x60, 0x62, 6, 6, 1, 1), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Hardware Master Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_es1938_info_hw_volume, + .get = snd_es1938_get_hw_volume, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Hardware Master Playback Switch", + .info = snd_es1938_info_hw_switch, + .get = snd_es1938_get_hw_switch, + .tlv = { .p = db_scale_master }, +}, +ES1938_SINGLE("Hardware Volume Split", 0, 0x64, 7, 1, 0), +ES1938_DOUBLE_TLV("Line Playback Volume", 0, 0x3e, 0x3e, 4, 0, 15, 0, + db_scale_line), +ES1938_DOUBLE("CD Playback Volume", 0, 0x38, 0x38, 4, 0, 15, 0), +ES1938_DOUBLE_TLV("FM Playback Volume", 0, 0x36, 0x36, 4, 0, 15, 0, + db_scale_mic), +ES1938_DOUBLE_TLV("Mono Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0, + db_scale_line), +ES1938_DOUBLE_TLV("Mic Playback Volume", 0, 0x1a, 0x1a, 4, 0, 15, 0, + db_scale_mic), +ES1938_DOUBLE_TLV("Aux Playback Volume", 0, 0x3a, 0x3a, 4, 0, 15, 0, + db_scale_line), +ES1938_DOUBLE_TLV("Capture Volume", 0, 0xb4, 0xb4, 4, 0, 15, 0, + db_scale_capture), +ES1938_SINGLE("Beep Volume", 0, 0x3c, 0, 7, 0), +ES1938_SINGLE("Record Monitor", 0, 0xa8, 3, 1, 0), +ES1938_SINGLE("Capture Switch", 0, 0x1c, 4, 1, 1), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_es1938_info_mux, + .get = snd_es1938_get_mux, + .put = snd_es1938_put_mux, +}, +ES1938_DOUBLE_TLV("Mono Input Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0, + db_scale_line), +ES1938_DOUBLE_TLV("PCM Capture Volume", 0, 0x69, 0x69, 4, 0, 15, 0, + db_scale_audio2), +ES1938_DOUBLE_TLV("Mic Capture Volume", 0, 0x68, 0x68, 4, 0, 15, 0, + db_scale_mic), +ES1938_DOUBLE_TLV("Line Capture Volume", 0, 0x6e, 0x6e, 4, 0, 15, 0, + db_scale_line), +ES1938_DOUBLE_TLV("FM Capture Volume", 0, 0x6b, 0x6b, 4, 0, 15, 0, + db_scale_mic), +ES1938_DOUBLE_TLV("Mono Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0, + db_scale_line), +ES1938_DOUBLE_TLV("CD Capture Volume", 0, 0x6a, 0x6a, 4, 0, 15, 0, + db_scale_line), +ES1938_DOUBLE_TLV("Aux Capture Volume", 0, 0x6c, 0x6c, 4, 0, 15, 0, + db_scale_line), +ES1938_DOUBLE_TLV("PCM Playback Volume", 0, 0x7c, 0x7c, 4, 0, 15, 0, + db_scale_audio2), +ES1938_DOUBLE_TLV("PCM Playback Volume", 1, 0x14, 0x14, 4, 0, 15, 0, + db_scale_audio1), +ES1938_SINGLE("3D Control - Level", 0, 0x52, 0, 63, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "3D Control - Switch", + .info = snd_es1938_info_spatializer_enable, + .get = snd_es1938_get_spatializer_enable, + .put = snd_es1938_put_spatializer_enable, +}, +ES1938_SINGLE("Mic Boost (+26dB)", 0, 0x7d, 3, 1, 0) +}; + + +/* ---------------------------------------------------------------------------- */ +/* ---------------------------------------------------------------------------- */ + +/* + * initialize the chip - used by resume callback, too + */ +static void snd_es1938_chip_init(struct es1938 *chip) +{ + /* reset chip */ + snd_es1938_reset(chip); + + /* configure native mode */ + + /* enable bus master */ + pci_set_master(chip->pci); + + /* disable legacy audio */ + pci_write_config_word(chip->pci, SL_PCI_LEGACYCONTROL, 0x805f); + + /* set DDMA base */ + pci_write_config_word(chip->pci, SL_PCI_DDMACONTROL, chip->ddma_port | 1); + + /* set DMA/IRQ policy */ + pci_write_config_dword(chip->pci, SL_PCI_CONFIG, 0); + + /* enable Audio 1, Audio 2, MPU401 IRQ and HW volume IRQ*/ + outb(0xf0, SLIO_REG(chip, IRQCONTROL)); + + /* reset DMA */ + outb(0, SLDM_REG(chip, DMACLEAR)); +} + +/* + * PM support + */ + +static const unsigned char saved_regs[SAVED_REG_SIZE+1] = { + 0x14, 0x1a, 0x1c, 0x3a, 0x3c, 0x3e, 0x36, 0x38, + 0x50, 0x52, 0x60, 0x61, 0x62, 0x63, 0x64, 0x68, + 0x69, 0x6a, 0x6b, 0x6d, 0x6e, 0x6f, 0x7c, 0x7d, + 0xa8, 0xb4, +}; + + +static int es1938_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct es1938 *chip = card->private_data; + const unsigned char *s; + unsigned char *d; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + /* save mixer-related registers */ + for (s = saved_regs, d = chip->saved_regs; *s; s++, d++) + *d = snd_es1938_reg_read(chip, *s); + + outb(0x00, SLIO_REG(chip, IRQCONTROL)); /* disable irqs */ + if (chip->irq >= 0) { + free_irq(chip->irq, chip); + chip->irq = -1; + card->sync_irq = -1; + } + return 0; +} + +static int es1938_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct snd_card *card = dev_get_drvdata(dev); + struct es1938 *chip = card->private_data; + const unsigned char *s; + unsigned char *d; + + if (request_irq(pci->irq, snd_es1938_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(dev, "unable to grab IRQ %d, disabling device\n", + pci->irq); + snd_card_disconnect(card); + return -EIO; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + snd_es1938_chip_init(chip); + + /* restore mixer-related registers */ + for (s = saved_regs, d = chip->saved_regs; *s; s++, d++) { + if (*s < 0xa0) + snd_es1938_mixer_write(chip, *s, *d); + else + snd_es1938_write(chip, *s, *d); + } + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(es1938_pm, es1938_suspend, es1938_resume); + +#ifdef SUPPORT_JOYSTICK +static int snd_es1938_create_gameport(struct es1938 *chip) +{ + struct gameport *gp; + + chip->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(chip->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "ES1938"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci)); + gameport_set_dev_parent(gp, &chip->pci->dev); + gp->io = chip->game_port; + + gameport_register_port(gp); + + return 0; +} + +static void snd_es1938_free_gameport(struct es1938 *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } +} +#else +static inline int snd_es1938_create_gameport(struct es1938 *chip) { return -ENOSYS; } +static inline void snd_es1938_free_gameport(struct es1938 *chip) { } +#endif /* SUPPORT_JOYSTICK */ + +static void snd_es1938_free(struct snd_card *card) +{ + struct es1938 *chip = card->private_data; + + /* disable irqs */ + outb(0x00, SLIO_REG(chip, IRQCONTROL)); + if (chip->rmidi) + snd_es1938_mixer_bits(chip, ESSSB_IREG_MPU401CONTROL, 0x40, 0); + + snd_es1938_free_gameport(chip); + + if (chip->irq >= 0) + free_irq(chip->irq, chip); +} + +static int snd_es1938_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct es1938 *chip = card->private_data; + int err; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + /* check, if we can restrict PCI DMA transfers to 24 bits */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(24))) { + dev_err(card->dev, + "architecture does not support 24bit PCI busmaster DMA\n"); + return -ENXIO; + } + + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->mixer_lock); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + err = pcim_request_all_regions(pci, "ESS Solo-1"); + if (err < 0) + return err; + chip->io_port = pci_resource_start(pci, 0); + chip->sb_port = pci_resource_start(pci, 1); + chip->vc_port = pci_resource_start(pci, 2); + chip->mpu_port = pci_resource_start(pci, 3); + chip->game_port = pci_resource_start(pci, 4); + /* still use non-managed irq handler as it's re-acquired at PM resume */ + if (request_irq(pci->irq, snd_es1938_interrupt, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_es1938_free; + dev_dbg(card->dev, + "create: io: 0x%lx, sb: 0x%lx, vc: 0x%lx, mpu: 0x%lx, game: 0x%lx\n", + chip->io_port, chip->sb_port, chip->vc_port, chip->mpu_port, chip->game_port); + + chip->ddma_port = chip->vc_port + 0x00; /* fix from Thomas Sailer */ + + snd_es1938_chip_init(chip); + return 0; +} + +/* -------------------------------------------------------------------- + * Interrupt handler + * -------------------------------------------------------------------- */ +static irqreturn_t snd_es1938_interrupt(int irq, void *dev_id) +{ + struct es1938 *chip = dev_id; + unsigned char status; + __always_unused unsigned char audiostatus; + int handled = 0; + + status = inb(SLIO_REG(chip, IRQCONTROL)); +#if 0 + dev_dbg(chip->card->dev, + "Es1938debug - interrupt status: =0x%x\n", status); +#endif + + /* AUDIO 1 */ + if (status & 0x10) { +#if 0 + dev_dbg(chip->card->dev, + "Es1938debug - AUDIO channel 1 interrupt\n"); + dev_dbg(chip->card->dev, + "Es1938debug - AUDIO channel 1 DMAC DMA count: %u\n", + inw(SLDM_REG(chip, DMACOUNT))); + dev_dbg(chip->card->dev, + "Es1938debug - AUDIO channel 1 DMAC DMA base: %u\n", + inl(SLDM_REG(chip, DMAADDR))); + dev_dbg(chip->card->dev, + "Es1938debug - AUDIO channel 1 DMAC DMA status: 0x%x\n", + inl(SLDM_REG(chip, DMASTATUS))); +#endif + /* clear irq */ + handled = 1; + audiostatus = inb(SLSB_REG(chip, STATUS)); + if (chip->active & ADC1) + snd_pcm_period_elapsed(chip->capture_substream); + else if (chip->active & DAC1) + snd_pcm_period_elapsed(chip->playback2_substream); + } + + /* AUDIO 2 */ + if (status & 0x20) { +#if 0 + dev_dbg(chip->card->dev, + "Es1938debug - AUDIO channel 2 interrupt\n"); + dev_dbg(chip->card->dev, + "Es1938debug - AUDIO channel 2 DMAC DMA count: %u\n", + inw(SLIO_REG(chip, AUDIO2DMACOUNT))); + dev_dbg(chip->card->dev, + "Es1938debug - AUDIO channel 2 DMAC DMA base: %u\n", + inl(SLIO_REG(chip, AUDIO2DMAADDR))); + +#endif + /* clear irq */ + handled = 1; + snd_es1938_mixer_bits(chip, ESSSB_IREG_AUDIO2CONTROL2, 0x80, 0); + if (chip->active & DAC2) + snd_pcm_period_elapsed(chip->playback1_substream); + } + + /* Hardware volume */ + if (status & 0x40) { + int split = snd_es1938_mixer_read(chip, 0x64) & 0x80; + handled = 1; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_switch->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_volume->id); + if (!split) { + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_switch->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_volume->id); + } + /* ack interrupt */ + snd_es1938_mixer_write(chip, 0x66, 0x00); + } + + /* MPU401 */ + if (status & 0x80) { + // the following line is evil! It switches off MIDI interrupt handling after the first interrupt received. + // replacing the last 0 by 0x40 works for ESS-Solo1, but just doing nothing works as well! + // andreas@flying-snail.de + // snd_es1938_mixer_bits(chip, ESSSB_IREG_MPU401CONTROL, 0x40, 0); /* ack? */ + if (chip->rmidi) { + handled = 1; + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); + } + } + return IRQ_RETVAL(handled); +} + +#define ES1938_DMA_SIZE 64 + +static int snd_es1938_mixer(struct es1938 *chip) +{ + struct snd_card *card; + unsigned int idx; + int err; + + card = chip->card; + + strscpy(card->mixername, "ESS Solo-1"); + + for (idx = 0; idx < ARRAY_SIZE(snd_es1938_controls); idx++) { + struct snd_kcontrol *kctl; + kctl = snd_ctl_new1(&snd_es1938_controls[idx], chip); + if (!kctl) + return -ENOMEM; + switch (idx) { + case 0: + chip->master_volume = kctl; + kctl->private_free = snd_es1938_hwv_free; + break; + case 1: + chip->master_switch = kctl; + kctl->private_free = snd_es1938_hwv_free; + break; + case 2: + chip->hw_volume = kctl; + kctl->private_free = snd_es1938_hwv_free; + break; + case 3: + chip->hw_switch = kctl; + kctl->private_free = snd_es1938_hwv_free; + break; + } + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + return 0; +} + + +static int __snd_es1938_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct es1938 *chip; + struct snd_opl3 *opl3; + int idx, err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + for (idx = 0; idx < 5; idx++) + if (pci_resource_start(pci, idx) == 0 || + !(pci_resource_flags(pci, idx) & IORESOURCE_IO)) + return -ENODEV; + + err = snd_es1938_create(card, pci); + if (err < 0) + return err; + + strscpy(card->driver, "ES1938"); + strscpy(card->shortname, "ESS ES1938 (Solo-1)"); + sprintf(card->longname, "%s rev %i, irq %i", + card->shortname, + chip->revision, + chip->irq); + + err = snd_es1938_new_pcm(chip, 0); + if (err < 0) + return err; + err = snd_es1938_mixer(chip); + if (err < 0) + return err; + if (snd_opl3_create(card, + SLSB_REG(chip, FMLOWADDR), + SLSB_REG(chip, FMHIGHADDR), + OPL3_HW_OPL3, 1, &opl3) < 0) { + dev_err(card->dev, "OPL3 not detected at 0x%lx\n", + SLSB_REG(chip, FMLOWADDR)); + } else { + err = snd_opl3_timer_new(opl3, 0, 1); + if (err < 0) + return err; + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + return err; + } + if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + chip->mpu_port, + MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK, + -1, &chip->rmidi) < 0) { + dev_err(card->dev, "unable to initialize MPU-401\n"); + } else { + // this line is vital for MIDI interrupt handling on ess-solo1 + // andreas@flying-snail.de + snd_es1938_mixer_bits(chip, ESSSB_IREG_MPU401CONTROL, 0x40, 0x40); + } + + snd_es1938_create_gameport(chip); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_es1938_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_es1938_probe(pci, pci_id)); +} + +static struct pci_driver es1938_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_es1938_ids, + .probe = snd_es1938_probe, + .driver = { + .pm = &es1938_pm, + }, +}; + +module_pci_driver(es1938_driver); diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c new file mode 100644 index 000000000..f04628c8c --- /dev/null +++ b/sound/pci/es1968.c @@ -0,0 +1,2817 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for ESS Maestro 1/2/2E Sound Card (started 21.8.99) + * Copyright (c) by Matze Braun . + * Takashi Iwai + * + * Most of the driver code comes from Zach Brown(zab@redhat.com) + * Alan Cox OSS Driver + * Rewritted from card-es1938.c source. + * + * TODO: + * Perhaps Synth + * + * Notes from Zach Brown about the driver code + * + * Hardware Description + * + * A working Maestro setup contains the Maestro chip wired to a + * codec or 2. In the Maestro we have the APUs, the ASSP, and the + * Wavecache. The APUs can be though of as virtual audio routing + * channels. They can take data from a number of sources and perform + * basic encodings of the data. The wavecache is a storehouse for + * PCM data. Typically it deals with PCI and interracts with the + * APUs. The ASSP is a wacky DSP like device that ESS is loth + * to release docs on. Thankfully it isn't required on the Maestro + * until you start doing insane things like FM emulation and surround + * encoding. The codecs are almost always AC-97 compliant codecs, + * but it appears that early Maestros may have had PT101 (an ESS + * part?) wired to them. The only real difference in the Maestro + * families is external goop like docking capability, memory for + * the ASSP, and initialization differences. + * + * Driver Operation + * + * We only drive the APU/Wavecache as typical DACs and drive the + * mixers in the codecs. There are 64 APUs. We assign 6 to each + * /dev/dsp? device. 2 channels for output, and 4 channels for + * input. + * + * Each APU can do a number of things, but we only really use + * 3 basic functions. For playback we use them to convert PCM + * data fetched over PCI by the wavecahche into analog data that + * is handed to the codec. One APU for mono, and a pair for stereo. + * When in stereo, the combination of smarts in the APU and Wavecache + * decide which wavecache gets the left or right channel. + * + * For record we still use the old overly mono system. For each in + * coming channel the data comes in from the codec, through a 'input' + * APU, through another rate converter APU, and then into memory via + * the wavecache and PCI. If its stereo, we mash it back into LRLR in + * software. The pass between the 2 APUs is supposedly what requires us + * to have a 512 byte buffer sitting around in wavecache/memory. + * + * The wavecache makes our life even more fun. First off, it can + * only address the first 28 bits of PCI address space, making it + * useless on quite a few architectures. Secondly, its insane. + * It claims to fetch from 4 regions of PCI space, each 4 meg in length. + * But that doesn't really work. You can only use 1 region. So all our + * allocations have to be in 4meg of each other. Booo. Hiss. + * So we have a module parameter, dsps_order, that is the order of + * the number of dsps to provide. All their buffer space is allocated + * on open time. The sonicvibes OSS routines we inherited really want + * power of 2 buffers, so we have all those next to each other, then + * 512 byte regions for the recording wavecaches. This ends up + * wasting quite a bit of memory. The only fixes I can see would be + * getting a kernel allocator that could work in zones, or figuring out + * just how to coerce the WP into doing what we want. + * + * The indirection of the various registers means we have to spinlock + * nearly all register accesses. We have the main register indirection + * like the wave cache, maestro registers, etc. Then we have beasts + * like the APU interface that is indirect registers gotten at through + * the main maestro indirection. Ouch. We spinlock around the actual + * ports on a per card basis. This means spinlock activity at each IO + * operation, but the only IO operation clusters are in non critical + * paths and it makes the code far easier to follow. Interrupts are + * blocked while holding the locks because the int handler has to + * get at some of them :(. The mixer interface doesn't, however. + * We also have an OSS state lock that is thrown around in a few + * places. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef CONFIG_SND_ES1968_RADIO +#include +#endif + +#define CARD_NAME "ESS Maestro1/2" +#define DRIVER_NAME "ES1968" + +MODULE_DESCRIPTION("ESS Maestro"); +MODULE_LICENSE("GPL"); + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK 1 +#endif + +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_PNP; /* Enable this card */ +static int total_bufsize[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1024 }; +static int pcm_substreams_p[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4 }; +static int pcm_substreams_c[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1 }; +static int clock[SNDRV_CARDS]; +static int use_pm[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; +static int enable_mpu[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; +#ifdef SUPPORT_JOYSTICK +static bool joystick[SNDRV_CARDS]; +#endif +static int radio_nr[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); +module_param_array(total_bufsize, int, NULL, 0444); +MODULE_PARM_DESC(total_bufsize, "Total buffer size in kB."); +module_param_array(pcm_substreams_p, int, NULL, 0444); +MODULE_PARM_DESC(pcm_substreams_p, "PCM Playback substreams for " CARD_NAME " soundcard."); +module_param_array(pcm_substreams_c, int, NULL, 0444); +MODULE_PARM_DESC(pcm_substreams_c, "PCM Capture substreams for " CARD_NAME " soundcard."); +module_param_array(clock, int, NULL, 0444); +MODULE_PARM_DESC(clock, "Clock on " CARD_NAME " soundcard. (0 = auto-detect)"); +module_param_array(use_pm, int, NULL, 0444); +MODULE_PARM_DESC(use_pm, "Toggle power-management. (0 = off, 1 = on, 2 = auto)"); +module_param_array(enable_mpu, int, NULL, 0444); +MODULE_PARM_DESC(enable_mpu, "Enable MPU401. (0 = off, 1 = on, 2 = auto)"); +#ifdef SUPPORT_JOYSTICK +module_param_array(joystick, bool, NULL, 0444); +MODULE_PARM_DESC(joystick, "Enable joystick."); +#endif +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); + + + +#define NR_APUS 64 +#define NR_APU_REGS 16 + +/* NEC Versas ? */ +#define NEC_VERSA_SUBID1 0x80581033 +#define NEC_VERSA_SUBID2 0x803c1033 + +/* Mode Flags */ +#define ESS_FMT_STEREO 0x01 +#define ESS_FMT_16BIT 0x02 + +#define DAC_RUNNING 1 +#define ADC_RUNNING 2 + +/* Values for the ESM_LEGACY_AUDIO_CONTROL */ + +#define ESS_DISABLE_AUDIO 0x8000 +#define ESS_ENABLE_SERIAL_IRQ 0x4000 +#define IO_ADRESS_ALIAS 0x0020 +#define MPU401_IRQ_ENABLE 0x0010 +#define MPU401_IO_ENABLE 0x0008 +#define GAME_IO_ENABLE 0x0004 +#define FM_IO_ENABLE 0x0002 +#define SB_IO_ENABLE 0x0001 + +/* Values for the ESM_CONFIG_A */ + +#define PIC_SNOOP1 0x4000 +#define PIC_SNOOP2 0x2000 +#define SAFEGUARD 0x0800 +#define DMA_CLEAR 0x0700 +#define DMA_DDMA 0x0000 +#define DMA_TDMA 0x0100 +#define DMA_PCPCI 0x0200 +#define POST_WRITE 0x0080 +#define PCI_TIMING 0x0040 +#define SWAP_LR 0x0020 +#define SUBTR_DECODE 0x0002 + +/* Values for the ESM_CONFIG_B */ + +#define SPDIF_CONFB 0x0100 +#define HWV_CONFB 0x0080 +#define DEBOUNCE 0x0040 +#define GPIO_CONFB 0x0020 +#define CHI_CONFB 0x0010 +#define IDMA_CONFB 0x0008 /*undoc */ +#define MIDI_FIX 0x0004 /*undoc */ +#define IRQ_TO_ISA 0x0001 /*undoc */ + +/* Values for Ring Bus Control B */ +#define RINGB_2CODEC_ID_MASK 0x0003 +#define RINGB_DIS_VALIDATION 0x0008 +#define RINGB_EN_SPDIF 0x0010 +#define RINGB_EN_2CODEC 0x0020 +#define RINGB_SING_BIT_DUAL 0x0040 + +/* ****Port Addresses**** */ + +/* Write & Read */ +#define ESM_INDEX 0x02 +#define ESM_DATA 0x00 + +/* AC97 + RingBus */ +#define ESM_AC97_INDEX 0x30 +#define ESM_AC97_DATA 0x32 +#define ESM_RING_BUS_DEST 0x34 +#define ESM_RING_BUS_CONTR_A 0x36 +#define ESM_RING_BUS_CONTR_B 0x38 +#define ESM_RING_BUS_SDO 0x3A + +/* WaveCache*/ +#define WC_INDEX 0x10 +#define WC_DATA 0x12 +#define WC_CONTROL 0x14 + +/* ASSP*/ +#define ASSP_INDEX 0x80 +#define ASSP_MEMORY 0x82 +#define ASSP_DATA 0x84 +#define ASSP_CONTROL_A 0xA2 +#define ASSP_CONTROL_B 0xA4 +#define ASSP_CONTROL_C 0xA6 +#define ASSP_HOSTW_INDEX 0xA8 +#define ASSP_HOSTW_DATA 0xAA +#define ASSP_HOSTW_IRQ 0xAC +/* Midi */ +#define ESM_MPU401_PORT 0x98 +/* Others */ +#define ESM_PORT_HOST_IRQ 0x18 + +#define IDR0_DATA_PORT 0x00 +#define IDR1_CRAM_POINTER 0x01 +#define IDR2_CRAM_DATA 0x02 +#define IDR3_WAVE_DATA 0x03 +#define IDR4_WAVE_PTR_LOW 0x04 +#define IDR5_WAVE_PTR_HI 0x05 +#define IDR6_TIMER_CTRL 0x06 +#define IDR7_WAVE_ROMRAM 0x07 + +#define WRITEABLE_MAP 0xEFFFFF +#define READABLE_MAP 0x64003F + +/* PCI Register */ + +#define ESM_LEGACY_AUDIO_CONTROL 0x40 +#define ESM_ACPI_COMMAND 0x54 +#define ESM_CONFIG_A 0x50 +#define ESM_CONFIG_B 0x52 +#define ESM_DDMA 0x60 + +/* Bob Bits */ +#define ESM_BOB_ENABLE 0x0001 +#define ESM_BOB_START 0x0001 + +/* Host IRQ Control Bits */ +#define ESM_RESET_MAESTRO 0x8000 +#define ESM_RESET_DIRECTSOUND 0x4000 +#define ESM_HIRQ_ClkRun 0x0100 +#define ESM_HIRQ_HW_VOLUME 0x0040 +#define ESM_HIRQ_HARPO 0x0030 /* What's that? */ +#define ESM_HIRQ_ASSP 0x0010 +#define ESM_HIRQ_DSIE 0x0004 +#define ESM_HIRQ_MPU401 0x0002 +#define ESM_HIRQ_SB 0x0001 + +/* Host IRQ Status Bits */ +#define ESM_MPU401_IRQ 0x02 +#define ESM_SB_IRQ 0x01 +#define ESM_SOUND_IRQ 0x04 +#define ESM_ASSP_IRQ 0x10 +#define ESM_HWVOL_IRQ 0x40 + +#define ESS_SYSCLK 50000000 +#define ESM_BOB_FREQ 200 +#define ESM_BOB_FREQ_MAX 800 + +#define ESM_FREQ_ESM1 (49152000L / 1024L) /* default rate 48000 */ +#define ESM_FREQ_ESM2 (50000000L / 1024L) + +/* APU Modes: reg 0x00, bit 4-7 */ +#define ESM_APU_MODE_SHIFT 4 +#define ESM_APU_MODE_MASK (0xf << 4) +#define ESM_APU_OFF 0x00 +#define ESM_APU_16BITLINEAR 0x01 /* 16-Bit Linear Sample Player */ +#define ESM_APU_16BITSTEREO 0x02 /* 16-Bit Stereo Sample Player */ +#define ESM_APU_8BITLINEAR 0x03 /* 8-Bit Linear Sample Player */ +#define ESM_APU_8BITSTEREO 0x04 /* 8-Bit Stereo Sample Player */ +#define ESM_APU_8BITDIFF 0x05 /* 8-Bit Differential Sample Playrer */ +#define ESM_APU_DIGITALDELAY 0x06 /* Digital Delay Line */ +#define ESM_APU_DUALTAP 0x07 /* Dual Tap Reader */ +#define ESM_APU_CORRELATOR 0x08 /* Correlator */ +#define ESM_APU_INPUTMIXER 0x09 /* Input Mixer */ +#define ESM_APU_WAVETABLE 0x0A /* Wave Table Mode */ +#define ESM_APU_SRCONVERTOR 0x0B /* Sample Rate Convertor */ +#define ESM_APU_16BITPINGPONG 0x0C /* 16-Bit Ping-Pong Sample Player */ +#define ESM_APU_RESERVED1 0x0D /* Reserved 1 */ +#define ESM_APU_RESERVED2 0x0E /* Reserved 2 */ +#define ESM_APU_RESERVED3 0x0F /* Reserved 3 */ + +/* reg 0x00 */ +#define ESM_APU_FILTER_Q_SHIFT 0 +#define ESM_APU_FILTER_Q_MASK (3 << 0) +/* APU Filtey Q Control */ +#define ESM_APU_FILTER_LESSQ 0x00 +#define ESM_APU_FILTER_MOREQ 0x03 + +#define ESM_APU_FILTER_TYPE_SHIFT 2 +#define ESM_APU_FILTER_TYPE_MASK (3 << 2) +#define ESM_APU_ENV_TYPE_SHIFT 8 +#define ESM_APU_ENV_TYPE_MASK (3 << 8) +#define ESM_APU_ENV_STATE_SHIFT 10 +#define ESM_APU_ENV_STATE_MASK (3 << 10) +#define ESM_APU_END_CURVE (1 << 12) +#define ESM_APU_INT_ON_LOOP (1 << 13) +#define ESM_APU_DMA_ENABLE (1 << 14) + +/* reg 0x02 */ +#define ESM_APU_SUBMIX_GROUP_SHIRT 0 +#define ESM_APU_SUBMIX_GROUP_MASK (7 << 0) +#define ESM_APU_SUBMIX_MODE (1 << 3) +#define ESM_APU_6dB (1 << 4) +#define ESM_APU_DUAL_EFFECT (1 << 5) +#define ESM_APU_EFFECT_CHANNELS_SHIFT 6 +#define ESM_APU_EFFECT_CHANNELS_MASK (3 << 6) + +/* reg 0x03 */ +#define ESM_APU_STEP_SIZE_MASK 0x0fff + +/* reg 0x04 */ +#define ESM_APU_PHASE_SHIFT 0 +#define ESM_APU_PHASE_MASK (0xff << 0) +#define ESM_APU_WAVE64K_PAGE_SHIFT 8 /* most 8bit of wave start offset */ +#define ESM_APU_WAVE64K_PAGE_MASK (0xff << 8) + +/* reg 0x05 - wave start offset */ +/* reg 0x06 - wave end offset */ +/* reg 0x07 - wave loop length */ + +/* reg 0x08 */ +#define ESM_APU_EFFECT_GAIN_SHIFT 0 +#define ESM_APU_EFFECT_GAIN_MASK (0xff << 0) +#define ESM_APU_TREMOLO_DEPTH_SHIFT 8 +#define ESM_APU_TREMOLO_DEPTH_MASK (0xf << 8) +#define ESM_APU_TREMOLO_RATE_SHIFT 12 +#define ESM_APU_TREMOLO_RATE_MASK (0xf << 12) + +/* reg 0x09 */ +/* bit 0-7 amplitude dest? */ +#define ESM_APU_AMPLITUDE_NOW_SHIFT 8 +#define ESM_APU_AMPLITUDE_NOW_MASK (0xff << 8) + +/* reg 0x0a */ +#define ESM_APU_POLAR_PAN_SHIFT 0 +#define ESM_APU_POLAR_PAN_MASK (0x3f << 0) +/* Polar Pan Control */ +#define ESM_APU_PAN_CENTER_CIRCLE 0x00 +#define ESM_APU_PAN_MIDDLE_RADIUS 0x01 +#define ESM_APU_PAN_OUTSIDE_RADIUS 0x02 + +#define ESM_APU_FILTER_TUNING_SHIFT 8 +#define ESM_APU_FILTER_TUNING_MASK (0xff << 8) + +/* reg 0x0b */ +#define ESM_APU_DATA_SRC_A_SHIFT 0 +#define ESM_APU_DATA_SRC_A_MASK (0x7f << 0) +#define ESM_APU_INV_POL_A (1 << 7) +#define ESM_APU_DATA_SRC_B_SHIFT 8 +#define ESM_APU_DATA_SRC_B_MASK (0x7f << 8) +#define ESM_APU_INV_POL_B (1 << 15) + +#define ESM_APU_VIBRATO_RATE_SHIFT 0 +#define ESM_APU_VIBRATO_RATE_MASK (0xf << 0) +#define ESM_APU_VIBRATO_DEPTH_SHIFT 4 +#define ESM_APU_VIBRATO_DEPTH_MASK (0xf << 4) +#define ESM_APU_VIBRATO_PHASE_SHIFT 8 +#define ESM_APU_VIBRATO_PHASE_MASK (0xff << 8) + +/* reg 0x0c */ +#define ESM_APU_RADIUS_SELECT (1 << 6) + +/* APU Filter Control */ +#define ESM_APU_FILTER_2POLE_LOPASS 0x00 +#define ESM_APU_FILTER_2POLE_BANDPASS 0x01 +#define ESM_APU_FILTER_2POLE_HIPASS 0x02 +#define ESM_APU_FILTER_1POLE_LOPASS 0x03 +#define ESM_APU_FILTER_1POLE_HIPASS 0x04 +#define ESM_APU_FILTER_OFF 0x05 + +/* APU ATFP Type */ +#define ESM_APU_ATFP_AMPLITUDE 0x00 +#define ESM_APU_ATFP_TREMELO 0x01 +#define ESM_APU_ATFP_FILTER 0x02 +#define ESM_APU_ATFP_PAN 0x03 + +/* APU ATFP Flags */ +#define ESM_APU_ATFP_FLG_OFF 0x00 +#define ESM_APU_ATFP_FLG_WAIT 0x01 +#define ESM_APU_ATFP_FLG_DONE 0x02 +#define ESM_APU_ATFP_FLG_INPROCESS 0x03 + + +/* capture mixing buffer size */ +#define ESM_MEM_ALIGN 0x1000 +#define ESM_MIXBUF_SIZE 0x400 + +#define ESM_MODE_PLAY 0 +#define ESM_MODE_CAPTURE 1 + + +/* APU use in the driver */ +enum snd_enum_apu_type { + ESM_APU_PCM_PLAY, + ESM_APU_PCM_CAPTURE, + ESM_APU_PCM_RATECONV, + ESM_APU_FREE +}; + +/* chip type */ +enum { + TYPE_MAESTRO, TYPE_MAESTRO2, TYPE_MAESTRO2E +}; + +/* DMA Hack! */ +struct esm_memory { + struct snd_dma_buffer buf; + int empty; /* status */ + struct list_head list; +}; + +/* Playback Channel */ +struct esschan { + int running; + + u8 apu[4]; + u8 apu_mode[4]; + + /* playback/capture pcm buffer */ + struct esm_memory *memory; + /* capture mixer buffer */ + struct esm_memory *mixbuf; + + unsigned int hwptr; /* current hw pointer in bytes */ + unsigned int count; /* sample counter in bytes */ + unsigned int dma_size; /* total buffer size in bytes */ + unsigned int frag_size; /* period size in bytes */ + unsigned int wav_shift; + u16 base[4]; /* offset for ptr */ + + /* stereo/16bit flag */ + unsigned char fmt; + int mode; /* playback / capture */ + + int bob_freq; /* required timer frequency */ + + struct snd_pcm_substream *substream; + + /* linked list */ + struct list_head list; + + u16 wc_map[4]; +}; + +struct es1968 { + /* Module Config */ + int total_bufsize; /* in bytes */ + + int playback_streams, capture_streams; + + unsigned int clock; /* clock */ + /* for clock measurement */ + unsigned int in_measurement: 1; + unsigned int measure_apu; + unsigned int measure_lastpos; + unsigned int measure_count; + + /* buffer */ + struct snd_dma_buffer dma; + + /* Resources... */ + int irq; + unsigned long io_port; + int type; + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; + int do_pm; /* power-management enabled */ + + /* DMA memory block */ + struct list_head buf_list; + + /* ALSA Stuff */ + struct snd_ac97 *ac97; + struct snd_rawmidi *rmidi; + + spinlock_t reg_lock; + unsigned int in_suspend; + + /* Maestro Stuff */ + u16 maestro_map[32]; + int bobclient; /* active timer instancs */ + int bob_freq; /* timer frequency */ + struct mutex memory_mutex; /* memory lock */ + + /* APU states */ + unsigned char apu[NR_APUS]; + + /* active substreams */ + struct list_head substream_list; + spinlock_t substream_lock; + + u16 apu_map[NR_APUS][NR_APU_REGS]; + +#ifdef SUPPORT_JOYSTICK + struct gameport *gameport; +#endif + +#ifdef CONFIG_SND_ES1968_INPUT + struct input_dev *input_dev; + char phys[64]; /* physical device path */ +#else + struct snd_kcontrol *master_switch; /* for h/w volume control */ + struct snd_kcontrol *master_volume; +#endif + struct work_struct hwvol_work; + +#ifdef CONFIG_SND_ES1968_RADIO + struct v4l2_device v4l2_dev; + struct snd_tea575x tea; + unsigned int tea575x_tuner; +#endif +}; + +static irqreturn_t snd_es1968_interrupt(int irq, void *dev_id); + +static const struct pci_device_id snd_es1968_ids[] = { + { + /* Maestro 1 */ + PCI_DEVICE(0x1285, 0x0100), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + .driver_data = TYPE_MAESTRO, + }, { + /* Maestro 2 */ + PCI_DEVICE(0x125d, 0x1968), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + .driver_data = TYPE_MAESTRO2, + }, { + /* Maestro 2E */ + PCI_DEVICE(0x125d, 0x1978), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + .driver_data = TYPE_MAESTRO2E, + }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_es1968_ids); + +/* ********************* + * Low Level Funcs! * + *********************/ + +/* no spinlock */ +static void __maestro_write(struct es1968 *chip, u16 reg, u16 data) +{ + outw(reg, chip->io_port + ESM_INDEX); + outw(data, chip->io_port + ESM_DATA); + chip->maestro_map[reg] = data; +} + +static inline void maestro_write(struct es1968 *chip, u16 reg, u16 data) +{ + guard(spinlock_irqsave)(&chip->reg_lock); + __maestro_write(chip, reg, data); +} + +/* no spinlock */ +static u16 __maestro_read(struct es1968 *chip, u16 reg) +{ + if (READABLE_MAP & (1 << reg)) { + outw(reg, chip->io_port + ESM_INDEX); + chip->maestro_map[reg] = inw(chip->io_port + ESM_DATA); + } + return chip->maestro_map[reg]; +} + +static inline u16 maestro_read(struct es1968 *chip, u16 reg) +{ + guard(spinlock_irqsave)(&chip->reg_lock); + return __maestro_read(chip, reg); +} + +/* Wait for the codec bus to be free */ +static int snd_es1968_ac97_wait(struct es1968 *chip) +{ + int timeout = 100000; + + while (timeout-- > 0) { + if (!(inb(chip->io_port + ESM_AC97_INDEX) & 1)) + return 0; + cond_resched(); + } + dev_dbg(chip->card->dev, "ac97 timeout\n"); + return 1; /* timeout */ +} + +static int snd_es1968_ac97_wait_poll(struct es1968 *chip) +{ + int timeout = 100000; + + while (timeout-- > 0) { + if (!(inb(chip->io_port + ESM_AC97_INDEX) & 1)) + return 0; + } + dev_dbg(chip->card->dev, "ac97 timeout\n"); + return 1; /* timeout */ +} + +static void snd_es1968_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) +{ + struct es1968 *chip = ac97->private_data; + + snd_es1968_ac97_wait(chip); + + /* Write the bus */ + outw(val, chip->io_port + ESM_AC97_DATA); + /*msleep(1);*/ + outb(reg, chip->io_port + ESM_AC97_INDEX); + /*msleep(1);*/ +} + +static unsigned short snd_es1968_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + u16 data = 0; + struct es1968 *chip = ac97->private_data; + + snd_es1968_ac97_wait(chip); + + outb(reg | 0x80, chip->io_port + ESM_AC97_INDEX); + /*msleep(1);*/ + + if (!snd_es1968_ac97_wait_poll(chip)) { + data = inw(chip->io_port + ESM_AC97_DATA); + /*msleep(1);*/ + } + + return data; +} + +/* no spinlock */ +static void apu_index_set(struct es1968 *chip, u16 index) +{ + int i; + __maestro_write(chip, IDR1_CRAM_POINTER, index); + for (i = 0; i < 1000; i++) + if (__maestro_read(chip, IDR1_CRAM_POINTER) == index) + return; + dev_dbg(chip->card->dev, "APU register select failed. (Timeout)\n"); +} + +/* no spinlock */ +static void apu_data_set(struct es1968 *chip, u16 data) +{ + int i; + for (i = 0; i < 1000; i++) { + if (__maestro_read(chip, IDR0_DATA_PORT) == data) + return; + __maestro_write(chip, IDR0_DATA_PORT, data); + } + dev_dbg(chip->card->dev, "APU register set probably failed (Timeout)!\n"); +} + +/* no spinlock */ +static void __apu_set_register(struct es1968 *chip, u16 channel, u8 reg, u16 data) +{ + if (snd_BUG_ON(channel >= NR_APUS)) + return; + chip->apu_map[channel][reg] = data; + reg |= (channel << 4); + apu_index_set(chip, reg); + apu_data_set(chip, data); +} + +static void apu_set_register(struct es1968 *chip, u16 channel, u8 reg, u16 data) +{ + guard(spinlock_irqsave)(&chip->reg_lock); + __apu_set_register(chip, channel, reg, data); +} + +static u16 __apu_get_register(struct es1968 *chip, u16 channel, u8 reg) +{ + if (snd_BUG_ON(channel >= NR_APUS)) + return 0; + reg |= (channel << 4); + apu_index_set(chip, reg); + return __maestro_read(chip, IDR0_DATA_PORT); +} + +static u16 apu_get_register(struct es1968 *chip, u16 channel, u8 reg) +{ + guard(spinlock_irqsave)(&chip->reg_lock); + return __apu_get_register(chip, channel, reg); +} + +#if 0 /* ASSP is not supported */ + +static void assp_set_register(struct es1968 *chip, u32 reg, u32 value) +{ + guard(spinlock_irqsave),(&chip->reg_lock); + outl(reg, chip->io_port + ASSP_INDEX); + outl(value, chip->io_port + ASSP_DATA); +} + +static u32 assp_get_register(struct es1968 *chip, u32 reg) +{ + guard(spinlock_irqsave)(&chip->reg_lock); + outl(reg, chip->io_port + ASSP_INDEX); + return inl(chip->io_port + ASSP_DATA); +} + +#endif + +static void wave_set_register(struct es1968 *chip, u16 reg, u16 value) +{ + guard(spinlock_irqsave)(&chip->reg_lock); + outw(reg, chip->io_port + WC_INDEX); + outw(value, chip->io_port + WC_DATA); +} + +static u16 wave_get_register(struct es1968 *chip, u16 reg) +{ + guard(spinlock_irqsave)(&chip->reg_lock); + outw(reg, chip->io_port + WC_INDEX); + return inw(chip->io_port + WC_DATA); +} + +/* ******************* + * Bob the Timer! * + *******************/ + +static void snd_es1968_bob_stop(struct es1968 *chip) +{ + u16 reg; + + reg = __maestro_read(chip, 0x11); + reg &= ~ESM_BOB_ENABLE; + __maestro_write(chip, 0x11, reg); + reg = __maestro_read(chip, 0x17); + reg &= ~ESM_BOB_START; + __maestro_write(chip, 0x17, reg); +} + +static void snd_es1968_bob_start(struct es1968 *chip) +{ + int prescale; + int divide; + + /* compute ideal interrupt frequency for buffer size & play rate */ + /* first, find best prescaler value to match freq */ + for (prescale = 5; prescale < 12; prescale++) + if (chip->bob_freq > (ESS_SYSCLK >> (prescale + 9))) + break; + + /* next, back off prescaler whilst getting divider into optimum range */ + divide = 1; + while ((prescale > 5) && (divide < 32)) { + prescale--; + divide <<= 1; + } + divide >>= 1; + + /* now fine-tune the divider for best match */ + for (; divide < 31; divide++) + if (chip->bob_freq > + ((ESS_SYSCLK >> (prescale + 9)) / (divide + 1))) break; + + /* divide = 0 is illegal, but don't let prescale = 4! */ + if (divide == 0) { + divide++; + if (prescale > 5) + prescale--; + } else if (divide > 1) + divide--; + + __maestro_write(chip, 6, 0x9000 | (prescale << 5) | divide); /* set reg */ + + /* Now set IDR 11/17 */ + __maestro_write(chip, 0x11, __maestro_read(chip, 0x11) | 1); + __maestro_write(chip, 0x17, __maestro_read(chip, 0x17) | 1); +} + +/* call with substream spinlock */ +static void snd_es1968_bob_inc(struct es1968 *chip, int freq) +{ + chip->bobclient++; + if (chip->bobclient == 1) { + chip->bob_freq = freq; + snd_es1968_bob_start(chip); + } else if (chip->bob_freq < freq) { + snd_es1968_bob_stop(chip); + chip->bob_freq = freq; + snd_es1968_bob_start(chip); + } +} + +/* call with substream spinlock */ +static void snd_es1968_bob_dec(struct es1968 *chip) +{ + chip->bobclient--; + if (chip->bobclient <= 0) + snd_es1968_bob_stop(chip); + else if (chip->bob_freq > ESM_BOB_FREQ) { + /* check reduction of timer frequency */ + int max_freq = ESM_BOB_FREQ; + struct esschan *es; + list_for_each_entry(es, &chip->substream_list, list) { + if (max_freq < es->bob_freq) + max_freq = es->bob_freq; + } + if (max_freq != chip->bob_freq) { + snd_es1968_bob_stop(chip); + chip->bob_freq = max_freq; + snd_es1968_bob_start(chip); + } + } +} + +static int +snd_es1968_calc_bob_rate(struct es1968 *chip, struct esschan *es, + struct snd_pcm_runtime *runtime) +{ + /* we acquire 4 interrupts per period for precise control.. */ + int freq = runtime->rate * 4; + if (es->fmt & ESS_FMT_STEREO) + freq <<= 1; + if (es->fmt & ESS_FMT_16BIT) + freq <<= 1; + freq /= es->frag_size; + if (freq < ESM_BOB_FREQ) + freq = ESM_BOB_FREQ; + else if (freq > ESM_BOB_FREQ_MAX) + freq = ESM_BOB_FREQ_MAX; + return freq; +} + + +/************* + * PCM Part * + *************/ + +static u32 snd_es1968_compute_rate(struct es1968 *chip, u32 freq) +{ + u32 rate = (freq << 16) / chip->clock; +#if 0 /* XXX: do we need this? */ + if (rate > 0x10000) + rate = 0x10000; +#endif + return rate; +} + +/* get current pointer */ +static inline unsigned int +snd_es1968_get_dma_ptr(struct es1968 *chip, struct esschan *es) +{ + unsigned int offset; + + offset = apu_get_register(chip, es->apu[0], 5); + + offset -= es->base[0]; + + return (offset & 0xFFFE); /* hardware is in words */ +} + +static void snd_es1968_apu_set_freq(struct es1968 *chip, int apu, int freq) +{ + apu_set_register(chip, apu, 2, + (apu_get_register(chip, apu, 2) & 0x00FF) | + ((freq & 0xff) << 8) | 0x10); + apu_set_register(chip, apu, 3, freq >> 8); +} + +/* spin lock held */ +static inline void snd_es1968_trigger_apu(struct es1968 *esm, int apu, int mode) +{ + /* set the APU mode */ + __apu_set_register(esm, apu, 0, + (__apu_get_register(esm, apu, 0) & 0xff0f) | + (mode << 4)); +} + +static void snd_es1968_pcm_start(struct es1968 *chip, struct esschan *es) +{ + guard(spinlock)(&chip->reg_lock); + __apu_set_register(chip, es->apu[0], 5, es->base[0]); + snd_es1968_trigger_apu(chip, es->apu[0], es->apu_mode[0]); + if (es->mode == ESM_MODE_CAPTURE) { + __apu_set_register(chip, es->apu[2], 5, es->base[2]); + snd_es1968_trigger_apu(chip, es->apu[2], es->apu_mode[2]); + } + if (es->fmt & ESS_FMT_STEREO) { + __apu_set_register(chip, es->apu[1], 5, es->base[1]); + snd_es1968_trigger_apu(chip, es->apu[1], es->apu_mode[1]); + if (es->mode == ESM_MODE_CAPTURE) { + __apu_set_register(chip, es->apu[3], 5, es->base[3]); + snd_es1968_trigger_apu(chip, es->apu[3], es->apu_mode[3]); + } + } +} + +static void snd_es1968_pcm_stop(struct es1968 *chip, struct esschan *es) +{ + guard(spinlock)(&chip->reg_lock); + snd_es1968_trigger_apu(chip, es->apu[0], 0); + snd_es1968_trigger_apu(chip, es->apu[1], 0); + if (es->mode == ESM_MODE_CAPTURE) { + snd_es1968_trigger_apu(chip, es->apu[2], 0); + snd_es1968_trigger_apu(chip, es->apu[3], 0); + } +} + +/* set the wavecache control reg */ +static void snd_es1968_program_wavecache(struct es1968 *chip, struct esschan *es, + int channel, u32 addr, int capture) +{ + u32 tmpval = (addr - 0x10) & 0xFFF8; + + if (! capture) { + if (!(es->fmt & ESS_FMT_16BIT)) + tmpval |= 4; /* 8bit */ + if (es->fmt & ESS_FMT_STEREO) + tmpval |= 2; /* stereo */ + } + + /* set the wavecache control reg */ + wave_set_register(chip, es->apu[channel] << 3, tmpval); + + es->wc_map[channel] = tmpval; +} + + +static void snd_es1968_playback_setup(struct es1968 *chip, struct esschan *es, + struct snd_pcm_runtime *runtime) +{ + u32 pa; + int high_apu = 0; + int channel, apu; + int i, size; + u32 freq; + + size = es->dma_size >> es->wav_shift; + + if (es->fmt & ESS_FMT_STEREO) + high_apu++; + + for (channel = 0; channel <= high_apu; channel++) { + apu = es->apu[channel]; + + snd_es1968_program_wavecache(chip, es, channel, es->memory->buf.addr, 0); + + /* Offset to PCMBAR */ + pa = es->memory->buf.addr; + pa -= chip->dma.addr; + pa >>= 1; /* words */ + + pa |= 0x00400000; /* System RAM (Bit 22) */ + + if (es->fmt & ESS_FMT_STEREO) { + /* Enable stereo */ + if (channel) + pa |= 0x00800000; /* (Bit 23) */ + if (es->fmt & ESS_FMT_16BIT) + pa >>= 1; + } + + /* base offset of dma calcs when reading the pointer + on this left one */ + es->base[channel] = pa & 0xFFFF; + + for (i = 0; i < 16; i++) + apu_set_register(chip, apu, i, 0x0000); + + /* Load the buffer into the wave engine */ + apu_set_register(chip, apu, 4, ((pa >> 16) & 0xFF) << 8); + apu_set_register(chip, apu, 5, pa & 0xFFFF); + apu_set_register(chip, apu, 6, (pa + size) & 0xFFFF); + /* setting loop == sample len */ + apu_set_register(chip, apu, 7, size); + + /* clear effects/env.. */ + apu_set_register(chip, apu, 8, 0x0000); + /* set amp now to 0xd0 (?), low byte is 'amplitude dest'? */ + apu_set_register(chip, apu, 9, 0xD000); + + /* clear routing stuff */ + apu_set_register(chip, apu, 11, 0x0000); + /* dma on, no envelopes, filter to all 1s) */ + apu_set_register(chip, apu, 0, 0x400F); + + if (es->fmt & ESS_FMT_16BIT) + es->apu_mode[channel] = ESM_APU_16BITLINEAR; + else + es->apu_mode[channel] = ESM_APU_8BITLINEAR; + + if (es->fmt & ESS_FMT_STEREO) { + /* set panning: left or right */ + /* Check: different panning. On my Canyon 3D Chipset the + Channels are swapped. I don't know, about the output + to the SPDif Link. Perhaps you have to change this + and not the APU Regs 4-5. */ + apu_set_register(chip, apu, 10, + 0x8F00 | (channel ? 0 : 0x10)); + es->apu_mode[channel] += 1; /* stereo */ + } else + apu_set_register(chip, apu, 10, 0x8F08); + } + + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + /* clear WP interrupts */ + outw(1, chip->io_port + 0x04); + /* enable WP ints */ + outw(inw(chip->io_port + ESM_PORT_HOST_IRQ) | ESM_HIRQ_DSIE, chip->io_port + ESM_PORT_HOST_IRQ); + } + + freq = runtime->rate; + /* set frequency */ + if (freq > 48000) + freq = 48000; + if (freq < 4000) + freq = 4000; + + /* hmmm.. */ + if (!(es->fmt & ESS_FMT_16BIT) && !(es->fmt & ESS_FMT_STEREO)) + freq >>= 1; + + freq = snd_es1968_compute_rate(chip, freq); + + /* Load the frequency, turn on 6dB */ + snd_es1968_apu_set_freq(chip, es->apu[0], freq); + snd_es1968_apu_set_freq(chip, es->apu[1], freq); +} + + +static void init_capture_apu(struct es1968 *chip, struct esschan *es, int channel, + unsigned int pa, unsigned int bsize, + int mode, int route) +{ + int i, apu = es->apu[channel]; + + es->apu_mode[channel] = mode; + + /* set the wavecache control reg */ + snd_es1968_program_wavecache(chip, es, channel, pa, 1); + + /* Offset to PCMBAR */ + pa -= chip->dma.addr; + pa >>= 1; /* words */ + + /* base offset of dma calcs when reading the pointer + on this left one */ + es->base[channel] = pa & 0xFFFF; + pa |= 0x00400000; /* bit 22 -> System RAM */ + + /* Begin loading the APU */ + for (i = 0; i < 16; i++) + apu_set_register(chip, apu, i, 0x0000); + + /* need to enable subgroups.. and we should probably + have different groups for different /dev/dsps.. */ + apu_set_register(chip, apu, 2, 0x8); + + /* Load the buffer into the wave engine */ + apu_set_register(chip, apu, 4, ((pa >> 16) & 0xFF) << 8); + apu_set_register(chip, apu, 5, pa & 0xFFFF); + apu_set_register(chip, apu, 6, (pa + bsize) & 0xFFFF); + apu_set_register(chip, apu, 7, bsize); + /* clear effects/env.. */ + apu_set_register(chip, apu, 8, 0x00F0); + /* amplitude now? sure. why not. */ + apu_set_register(chip, apu, 9, 0x0000); + /* set filter tune, radius, polar pan */ + apu_set_register(chip, apu, 10, 0x8F08); + /* route input */ + apu_set_register(chip, apu, 11, route); + /* dma on, no envelopes, filter to all 1s) */ + apu_set_register(chip, apu, 0, 0x400F); +} + +static void snd_es1968_capture_setup(struct es1968 *chip, struct esschan *es, + struct snd_pcm_runtime *runtime) +{ + int size; + u32 freq; + + size = es->dma_size >> es->wav_shift; + + /* APU assignments: + 0 = mono/left SRC + 1 = right SRC + 2 = mono/left Input Mixer + 3 = right Input Mixer + */ + /* data seems to flow from the codec, through an apu into + the 'mixbuf' bit of page, then through the SRC apu + and out to the real 'buffer'. ok. sure. */ + + /* input mixer (left/mono) */ + /* parallel in crap, see maestro reg 0xC [8-11] */ + init_capture_apu(chip, es, 2, + es->mixbuf->buf.addr, ESM_MIXBUF_SIZE/4, /* in words */ + ESM_APU_INPUTMIXER, 0x14); + /* SRC (left/mono); get input from inputing apu */ + init_capture_apu(chip, es, 0, es->memory->buf.addr, size, + ESM_APU_SRCONVERTOR, es->apu[2]); + if (es->fmt & ESS_FMT_STEREO) { + /* input mixer (right) */ + init_capture_apu(chip, es, 3, + es->mixbuf->buf.addr + ESM_MIXBUF_SIZE/2, + ESM_MIXBUF_SIZE/4, /* in words */ + ESM_APU_INPUTMIXER, 0x15); + /* SRC (right) */ + init_capture_apu(chip, es, 1, + es->memory->buf.addr + size*2, size, + ESM_APU_SRCONVERTOR, es->apu[3]); + } + + freq = runtime->rate; + /* Sample Rate conversion APUs don't like 0x10000 for their rate */ + if (freq > 47999) + freq = 47999; + if (freq < 4000) + freq = 4000; + + freq = snd_es1968_compute_rate(chip, freq); + + /* Load the frequency, turn on 6dB */ + snd_es1968_apu_set_freq(chip, es->apu[0], freq); + snd_es1968_apu_set_freq(chip, es->apu[1], freq); + + /* fix mixer rate at 48khz. and its _must_ be 0x10000. */ + freq = 0x10000; + snd_es1968_apu_set_freq(chip, es->apu[2], freq); + snd_es1968_apu_set_freq(chip, es->apu[3], freq); + + guard(spinlock_irqsave)(&chip->reg_lock); + /* clear WP interrupts */ + outw(1, chip->io_port + 0x04); + /* enable WP ints */ + outw(inw(chip->io_port + ESM_PORT_HOST_IRQ) | ESM_HIRQ_DSIE, chip->io_port + ESM_PORT_HOST_IRQ); +} + +/******************* + * ALSA Interface * + *******************/ + +static int snd_es1968_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct es1968 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct esschan *es = runtime->private_data; + + es->dma_size = snd_pcm_lib_buffer_bytes(substream); + es->frag_size = snd_pcm_lib_period_bytes(substream); + + es->wav_shift = 1; /* maestro handles always 16bit */ + es->fmt = 0; + if (snd_pcm_format_width(runtime->format) == 16) + es->fmt |= ESS_FMT_16BIT; + if (runtime->channels > 1) { + es->fmt |= ESS_FMT_STEREO; + if (es->fmt & ESS_FMT_16BIT) /* 8bit is already word shifted */ + es->wav_shift++; + } + es->bob_freq = snd_es1968_calc_bob_rate(chip, es, runtime); + + switch (es->mode) { + case ESM_MODE_PLAY: + snd_es1968_playback_setup(chip, es, runtime); + break; + case ESM_MODE_CAPTURE: + snd_es1968_capture_setup(chip, es, runtime); + break; + } + + return 0; +} + +static int snd_es1968_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct es1968 *chip = snd_pcm_substream_chip(substream); + struct esschan *es = substream->runtime->private_data; + + guard(spinlock)(&chip->substream_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (es->running) + break; + snd_es1968_bob_inc(chip, es->bob_freq); + es->count = 0; + es->hwptr = 0; + snd_es1968_pcm_start(chip, es); + es->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (! es->running) + break; + snd_es1968_pcm_stop(chip, es); + es->running = 0; + snd_es1968_bob_dec(chip); + break; + } + return 0; +} + +static snd_pcm_uframes_t snd_es1968_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct es1968 *chip = snd_pcm_substream_chip(substream); + struct esschan *es = substream->runtime->private_data; + unsigned int ptr; + + ptr = snd_es1968_get_dma_ptr(chip, es) << es->wav_shift; + + return bytes_to_frames(substream->runtime, ptr % es->dma_size); +} + +static const struct snd_pcm_hardware snd_es1968_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + /*SNDRV_PCM_INFO_PAUSE |*/ + SNDRV_PCM_INFO_RESUME), + .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 = 256, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_es1968_capture = { + .info = (SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + /*SNDRV_PCM_INFO_PAUSE |*/ + SNDRV_PCM_INFO_RESUME), + .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 = 256, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* ************************* + * DMA memory management * + *************************/ + +/* Because the Maestro can only take addresses relative to the PCM base address + register :( */ + +static int calc_available_memory_size(struct es1968 *chip) +{ + int max_size = 0; + struct esm_memory *buf; + + guard(mutex)(&chip->memory_mutex); + list_for_each_entry(buf, &chip->buf_list, list) { + if (buf->empty && buf->buf.bytes > max_size) + max_size = buf->buf.bytes; + } + if (max_size >= 128*1024) + max_size = 127*1024; + return max_size; +} + +/* allocate a new memory chunk with the specified size */ +static struct esm_memory *snd_es1968_new_memory(struct es1968 *chip, int size) +{ + struct esm_memory *buf; + + size = ALIGN(size, ESM_MEM_ALIGN); + guard(mutex)(&chip->memory_mutex); + list_for_each_entry(buf, &chip->buf_list, list) { + if (buf->empty && buf->buf.bytes >= size) + goto __found; + } + return NULL; + +__found: + if (buf->buf.bytes > size) { + struct esm_memory *chunk = kmalloc_obj(*chunk); + if (chunk == NULL) + return NULL; + chunk->buf = buf->buf; + chunk->buf.bytes -= size; + chunk->buf.area += size; + chunk->buf.addr += size; + chunk->empty = 1; + buf->buf.bytes = size; + list_add(&chunk->list, &buf->list); + } + buf->empty = 0; + return buf; +} + +/* free a memory chunk */ +static void snd_es1968_free_memory(struct es1968 *chip, struct esm_memory *buf) +{ + struct esm_memory *chunk; + + guard(mutex)(&chip->memory_mutex); + buf->empty = 1; + if (buf->list.prev != &chip->buf_list) { + chunk = list_entry(buf->list.prev, struct esm_memory, list); + if (chunk->empty) { + chunk->buf.bytes += buf->buf.bytes; + list_del(&buf->list); + kfree(buf); + buf = chunk; + } + } + if (buf->list.next != &chip->buf_list) { + chunk = list_entry(buf->list.next, struct esm_memory, list); + if (chunk->empty) { + buf->buf.bytes += chunk->buf.bytes; + list_del(&chunk->list); + kfree(chunk); + } + } +} + +static void snd_es1968_free_dmabuf(struct es1968 *chip) +{ + struct list_head *p; + + if (! chip->dma.area) + return; + snd_dma_free_pages(&chip->dma); + while ((p = chip->buf_list.next) != &chip->buf_list) { + struct esm_memory *chunk = list_entry(p, struct esm_memory, list); + list_del(p); + kfree(chunk); + } +} + +static int +snd_es1968_init_dmabuf(struct es1968 *chip) +{ + int err; + struct esm_memory *chunk; + + err = snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + chip->total_bufsize, &chip->dma); + if (err < 0 || ! chip->dma.area) { + dev_err(chip->card->dev, + "can't allocate dma pages for size %d\n", + chip->total_bufsize); + return -ENOMEM; + } + if ((chip->dma.addr + chip->dma.bytes - 1) & ~((1 << 28) - 1)) { + snd_dma_free_pages(&chip->dma); + dev_err(chip->card->dev, "DMA buffer beyond 256MB.\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&chip->buf_list); + /* allocate an empty chunk */ + chunk = kmalloc_obj(*chunk); + if (chunk == NULL) { + snd_es1968_free_dmabuf(chip); + return -ENOMEM; + } + memset(chip->dma.area, 0, ESM_MEM_ALIGN); + chunk->buf = chip->dma; + chunk->buf.area += ESM_MEM_ALIGN; + chunk->buf.addr += ESM_MEM_ALIGN; + chunk->buf.bytes -= ESM_MEM_ALIGN; + chunk->empty = 1; + list_add(&chunk->list, &chip->buf_list); + + return 0; +} + +/* setup the dma_areas */ +/* buffer is extracted from the pre-allocated memory chunk */ +static int snd_es1968_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct es1968 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct esschan *chan = runtime->private_data; + int size = params_buffer_bytes(hw_params); + + if (chan->memory) { + if (chan->memory->buf.bytes >= size) { + runtime->dma_bytes = size; + return 0; + } + snd_es1968_free_memory(chip, chan->memory); + } + chan->memory = snd_es1968_new_memory(chip, size); + if (chan->memory == NULL) { + dev_dbg(chip->card->dev, + "cannot allocate dma buffer: size = %d\n", size); + return -ENOMEM; + } + snd_pcm_set_runtime_buffer(substream, &chan->memory->buf); + return 1; /* area was changed */ +} + +/* remove dma areas if allocated */ +static int snd_es1968_hw_free(struct snd_pcm_substream *substream) +{ + struct es1968 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct esschan *chan; + + if (runtime->private_data == NULL) + return 0; + chan = runtime->private_data; + if (chan->memory) { + snd_es1968_free_memory(chip, chan->memory); + chan->memory = NULL; + } + return 0; +} + + +/* + * allocate APU pair + */ +static int snd_es1968_alloc_apu_pair(struct es1968 *chip, int type) +{ + int apu; + + for (apu = 0; apu < NR_APUS; apu += 2) { + if (chip->apu[apu] == ESM_APU_FREE && + chip->apu[apu + 1] == ESM_APU_FREE) { + chip->apu[apu] = chip->apu[apu + 1] = type; + return apu; + } + } + return -EBUSY; +} + +/* + * release APU pair + */ +static void snd_es1968_free_apu_pair(struct es1968 *chip, int apu) +{ + chip->apu[apu] = chip->apu[apu + 1] = ESM_APU_FREE; +} + + +/****************** + * PCM open/close * + ******************/ + +static int snd_es1968_playback_open(struct snd_pcm_substream *substream) +{ + struct es1968 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct esschan *es; + int apu1; + + /* search 2 APUs */ + apu1 = snd_es1968_alloc_apu_pair(chip, ESM_APU_PCM_PLAY); + if (apu1 < 0) + return apu1; + + es = kzalloc_obj(*es); + if (!es) { + snd_es1968_free_apu_pair(chip, apu1); + return -ENOMEM; + } + + es->apu[0] = apu1; + es->apu[1] = apu1 + 1; + es->apu_mode[0] = 0; + es->apu_mode[1] = 0; + es->running = 0; + es->substream = substream; + es->mode = ESM_MODE_PLAY; + + runtime->private_data = es; + runtime->hw = snd_es1968_playback; + runtime->hw.buffer_bytes_max = runtime->hw.period_bytes_max = + calc_available_memory_size(chip); + + guard(spinlock_irq)(&chip->substream_lock); + list_add(&es->list, &chip->substream_list); + + return 0; +} + +static int snd_es1968_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct es1968 *chip = snd_pcm_substream_chip(substream); + struct esschan *es; + int err, apu1, apu2; + + apu1 = snd_es1968_alloc_apu_pair(chip, ESM_APU_PCM_CAPTURE); + if (apu1 < 0) + return apu1; + apu2 = snd_es1968_alloc_apu_pair(chip, ESM_APU_PCM_RATECONV); + if (apu2 < 0) { + snd_es1968_free_apu_pair(chip, apu1); + return apu2; + } + + es = kzalloc_obj(*es); + if (!es) { + snd_es1968_free_apu_pair(chip, apu1); + snd_es1968_free_apu_pair(chip, apu2); + return -ENOMEM; + } + + es->apu[0] = apu1; + es->apu[1] = apu1 + 1; + es->apu[2] = apu2; + es->apu[3] = apu2 + 1; + es->apu_mode[0] = 0; + es->apu_mode[1] = 0; + es->apu_mode[2] = 0; + es->apu_mode[3] = 0; + es->running = 0; + es->substream = substream; + es->mode = ESM_MODE_CAPTURE; + + /* get mixbuffer */ + es->mixbuf = snd_es1968_new_memory(chip, ESM_MIXBUF_SIZE); + if (!es->mixbuf) { + snd_es1968_free_apu_pair(chip, apu1); + snd_es1968_free_apu_pair(chip, apu2); + kfree(es); + return -ENOMEM; + } + memset(es->mixbuf->buf.area, 0, ESM_MIXBUF_SIZE); + + runtime->private_data = es; + runtime->hw = snd_es1968_capture; + runtime->hw.buffer_bytes_max = runtime->hw.period_bytes_max = + calc_available_memory_size(chip) - 1024; /* keep MIXBUF size */ + err = snd_pcm_hw_constraint_pow2(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES); + if (err < 0) + return err; + + guard(spinlock_irq)(&chip->substream_lock); + list_add(&es->list, &chip->substream_list); + + return 0; +} + +static int snd_es1968_playback_close(struct snd_pcm_substream *substream) +{ + struct es1968 *chip = snd_pcm_substream_chip(substream); + struct esschan *es; + + if (substream->runtime->private_data == NULL) + return 0; + es = substream->runtime->private_data; + scoped_guard(spinlock_irq, &chip->substream_lock) { + list_del(&es->list); + } + snd_es1968_free_apu_pair(chip, es->apu[0]); + kfree(es); + + return 0; +} + +static int snd_es1968_capture_close(struct snd_pcm_substream *substream) +{ + struct es1968 *chip = snd_pcm_substream_chip(substream); + struct esschan *es; + + if (substream->runtime->private_data == NULL) + return 0; + es = substream->runtime->private_data; + scoped_guard(spinlock_irq, &chip->substream_lock) { + list_del(&es->list); + } + snd_es1968_free_memory(chip, es->mixbuf); + snd_es1968_free_apu_pair(chip, es->apu[0]); + snd_es1968_free_apu_pair(chip, es->apu[2]); + kfree(es); + + return 0; +} + +static const struct snd_pcm_ops snd_es1968_playback_ops = { + .open = snd_es1968_playback_open, + .close = snd_es1968_playback_close, + .hw_params = snd_es1968_hw_params, + .hw_free = snd_es1968_hw_free, + .prepare = snd_es1968_pcm_prepare, + .trigger = snd_es1968_pcm_trigger, + .pointer = snd_es1968_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_es1968_capture_ops = { + .open = snd_es1968_capture_open, + .close = snd_es1968_capture_close, + .hw_params = snd_es1968_hw_params, + .hw_free = snd_es1968_hw_free, + .prepare = snd_es1968_pcm_prepare, + .trigger = snd_es1968_pcm_trigger, + .pointer = snd_es1968_pcm_pointer, +}; + + +/* + * measure clock + */ +#define CLOCK_MEASURE_BUFSIZE 16768 /* enough large for a single shot */ + +static void es1968_measure_clock(struct es1968 *chip) +{ + int i, apu; + unsigned int pa, offset, t; + struct esm_memory *memory; + ktime_t start_time, stop_time; + ktime_t diff; + + if (chip->clock == 0) + chip->clock = 48000; /* default clock value */ + + /* search 2 APUs (although one apu is enough) */ + apu = snd_es1968_alloc_apu_pair(chip, ESM_APU_PCM_PLAY); + if (apu < 0) { + dev_err(chip->card->dev, "Hmm, cannot find empty APU pair!?\n"); + return; + } + memory = snd_es1968_new_memory(chip, CLOCK_MEASURE_BUFSIZE); + if (!memory) { + dev_warn(chip->card->dev, + "cannot allocate dma buffer - using default clock %d\n", + chip->clock); + snd_es1968_free_apu_pair(chip, apu); + return; + } + + memset(memory->buf.area, 0, CLOCK_MEASURE_BUFSIZE); + + wave_set_register(chip, apu << 3, (memory->buf.addr - 0x10) & 0xfff8); + + pa = (unsigned int)((memory->buf.addr - chip->dma.addr) >> 1); + pa |= 0x00400000; /* System RAM (Bit 22) */ + + /* initialize apu */ + for (i = 0; i < 16; i++) + apu_set_register(chip, apu, i, 0x0000); + + apu_set_register(chip, apu, 0, 0x400f); + apu_set_register(chip, apu, 4, ((pa >> 16) & 0xff) << 8); + apu_set_register(chip, apu, 5, pa & 0xffff); + apu_set_register(chip, apu, 6, (pa + CLOCK_MEASURE_BUFSIZE/2) & 0xffff); + apu_set_register(chip, apu, 7, CLOCK_MEASURE_BUFSIZE/2); + apu_set_register(chip, apu, 8, 0x0000); + apu_set_register(chip, apu, 9, 0xD000); + apu_set_register(chip, apu, 10, 0x8F08); + apu_set_register(chip, apu, 11, 0x0000); + scoped_guard(spinlock_irq, &chip->reg_lock) { + outw(1, chip->io_port + 0x04); /* clear WP interrupts */ + outw(inw(chip->io_port + ESM_PORT_HOST_IRQ) | ESM_HIRQ_DSIE, chip->io_port + ESM_PORT_HOST_IRQ); /* enable WP ints */ + } + + snd_es1968_apu_set_freq(chip, apu, ((unsigned int)48000 << 16) / chip->clock); /* 48000 Hz */ + + chip->in_measurement = 1; + chip->measure_apu = apu; + scoped_guard(spinlock_irq, &chip->reg_lock) { + snd_es1968_bob_inc(chip, ESM_BOB_FREQ); + __apu_set_register(chip, apu, 5, pa & 0xffff); + snd_es1968_trigger_apu(chip, apu, ESM_APU_16BITLINEAR); + start_time = ktime_get(); + } + msleep(50); + scoped_guard(spinlock_irq, &chip->reg_lock) { + offset = __apu_get_register(chip, apu, 5); + stop_time = ktime_get(); + snd_es1968_trigger_apu(chip, apu, 0); /* stop */ + snd_es1968_bob_dec(chip); + chip->in_measurement = 0; + } + + /* check the current position */ + offset -= (pa & 0xffff); + offset &= 0xfffe; + offset += chip->measure_count * (CLOCK_MEASURE_BUFSIZE/2); + + diff = ktime_sub(stop_time, start_time); + t = ktime_to_us(diff); + if (t == 0) { + dev_err(chip->card->dev, "?? calculation error..\n"); + } else { + offset *= 1000; + offset = (offset / t) * 1000 + ((offset % t) * 1000) / t; + if (offset < 47500 || offset > 48500) { + if (offset >= 40000 && offset <= 50000) + chip->clock = (chip->clock * offset) / 48000; + } + dev_info(chip->card->dev, "clocking to %d\n", chip->clock); + } + snd_es1968_free_memory(chip, memory); + snd_es1968_free_apu_pair(chip, apu); +} + + +/* + */ + +static void snd_es1968_pcm_free(struct snd_pcm *pcm) +{ + struct es1968 *esm = pcm->private_data; + snd_es1968_free_dmabuf(esm); + esm->pcm = NULL; +} + +static int +snd_es1968_pcm(struct es1968 *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + /* get DMA buffer */ + err = snd_es1968_init_dmabuf(chip); + if (err < 0) + return err; + + /* set PCMBAR */ + wave_set_register(chip, 0x01FC, chip->dma.addr >> 12); + wave_set_register(chip, 0x01FD, chip->dma.addr >> 12); + wave_set_register(chip, 0x01FE, chip->dma.addr >> 12); + wave_set_register(chip, 0x01FF, chip->dma.addr >> 12); + + err = snd_pcm_new(chip->card, "ESS Maestro", device, + chip->playback_streams, + chip->capture_streams, &pcm); + if (err < 0) + return err; + + pcm->private_data = chip; + pcm->private_free = snd_es1968_pcm_free; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1968_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1968_capture_ops); + + pcm->info_flags = 0; + + strscpy(pcm->name, "ESS Maestro"); + + chip->pcm = pcm; + + return 0; +} +/* + * suppress jitter on some maestros when playing stereo + */ +static void snd_es1968_suppress_jitter(struct es1968 *chip, struct esschan *es) +{ + unsigned int cp1; + unsigned int cp2; + unsigned int diff; + + cp1 = __apu_get_register(chip, 0, 5); + cp2 = __apu_get_register(chip, 1, 5); + diff = (cp1 > cp2 ? cp1 - cp2 : cp2 - cp1); + + if (diff > 1) + __maestro_write(chip, IDR0_DATA_PORT, cp1); +} + +/* + * update pointer + */ +static void snd_es1968_update_pcm(struct es1968 *chip, struct esschan *es) +{ + unsigned int hwptr; + unsigned int diff; + struct snd_pcm_substream *subs = es->substream; + + if (subs == NULL || !es->running) + return; + + hwptr = snd_es1968_get_dma_ptr(chip, es) << es->wav_shift; + hwptr %= es->dma_size; + + diff = (es->dma_size + hwptr - es->hwptr) % es->dma_size; + + es->hwptr = hwptr; + es->count += diff; + + if (es->count > es->frag_size) { + spin_unlock(&chip->substream_lock); + snd_pcm_period_elapsed(subs); + spin_lock(&chip->substream_lock); + es->count %= es->frag_size; + } +} + +/* The hardware volume works by incrementing / decrementing 2 counters + (without wrap around) in response to volume button presses and then + generating an interrupt. The pair of counters is stored in bits 1-3 and 5-7 + of a byte wide register. The meaning of bits 0 and 4 is unknown. */ +static void es1968_update_hw_volume(struct work_struct *work) +{ + struct es1968 *chip = container_of(work, struct es1968, hwvol_work); + int x, val; + + /* Figure out which volume control button was pushed, + based on differences from the default register + values. */ + x = inb(chip->io_port + 0x1c) & 0xee; + /* Reset the volume control registers. */ + outb(0x88, chip->io_port + 0x1c); + outb(0x88, chip->io_port + 0x1d); + outb(0x88, chip->io_port + 0x1e); + outb(0x88, chip->io_port + 0x1f); + + if (chip->in_suspend) + return; + +#ifndef CONFIG_SND_ES1968_INPUT + if (! chip->master_switch || ! chip->master_volume) + return; + + val = snd_ac97_read(chip->ac97, AC97_MASTER); + switch (x) { + case 0x88: + /* mute */ + val ^= 0x8000; + break; + case 0xaa: + /* volume up */ + if ((val & 0x7f) > 0) + val--; + if ((val & 0x7f00) > 0) + val -= 0x0100; + break; + case 0x66: + /* volume down */ + if ((val & 0x7f) < 0x1f) + val++; + if ((val & 0x7f00) < 0x1f00) + val += 0x0100; + break; + } + if (snd_ac97_update(chip->ac97, AC97_MASTER, val)) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_volume->id); +#else + if (!chip->input_dev) + return; + + val = 0; + switch (x) { + case 0x88: + /* The counters have not changed, yet we've received a HV + interrupt. According to tests run by various people this + happens when pressing the mute button. */ + val = KEY_MUTE; + break; + case 0xaa: + /* counters increased by 1 -> volume up */ + val = KEY_VOLUMEUP; + break; + case 0x66: + /* counters decreased by 1 -> volume down */ + val = KEY_VOLUMEDOWN; + break; + } + + if (val) { + input_report_key(chip->input_dev, val, 1); + input_sync(chip->input_dev); + input_report_key(chip->input_dev, val, 0); + input_sync(chip->input_dev); + } +#endif +} + +/* + * interrupt handler + */ +static irqreturn_t snd_es1968_interrupt(int irq, void *dev_id) +{ + struct es1968 *chip = dev_id; + u32 event; + + event = inb(chip->io_port + 0x1A); + if (!event) + return IRQ_NONE; + + outw(inw(chip->io_port + 4) & 1, chip->io_port + 4); + + if (event & ESM_HWVOL_IRQ) + schedule_work(&chip->hwvol_work); + + /* else ack 'em all, i imagine */ + outb(0xFF, chip->io_port + 0x1A); + + if ((event & ESM_MPU401_IRQ) && chip->rmidi) { + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); + } + + if (event & ESM_SOUND_IRQ) { + struct esschan *es; + scoped_guard(spinlock, &chip->substream_lock) { + list_for_each_entry(es, &chip->substream_list, list) { + if (es->running) { + snd_es1968_update_pcm(chip, es); + if (es->fmt & ESS_FMT_STEREO) + snd_es1968_suppress_jitter(chip, es); + } + } + } + if (chip->in_measurement) { + unsigned int curp = __apu_get_register(chip, chip->measure_apu, 5); + if (curp < chip->measure_lastpos) + chip->measure_count++; + chip->measure_lastpos = curp; + } + } + + return IRQ_HANDLED; +} + +/* + * Mixer stuff + */ + +static int +snd_es1968_mixer(struct es1968 *chip) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_es1968_ac97_write, + .read = snd_es1968_ac97_read, + }; + + err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + pbus->no_vra = 1; /* ES1968 doesn't need VRA */ + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + err = snd_ac97_mixer(pbus, &ac97, &chip->ac97); + if (err < 0) + return err; + +#ifndef CONFIG_SND_ES1968_INPUT + /* attach master switch / volumes for h/w volume control */ + chip->master_switch = snd_ctl_find_id_mixer(chip->card, + "Master Playback Switch"); + chip->master_volume = snd_ctl_find_id_mixer(chip->card, + "Master Playback Volume"); +#endif + + return 0; +} + +/* + * reset ac97 codec + */ + +static void snd_es1968_ac97_reset(struct es1968 *chip) +{ + unsigned long ioaddr = chip->io_port; + + unsigned short save_ringbus_a; + unsigned short save_68; + unsigned short w; + unsigned int vend; + + /* save configuration */ + save_ringbus_a = inw(ioaddr + 0x36); + + //outw(inw(ioaddr + 0x38) & 0xfffc, ioaddr + 0x38); /* clear second codec id? */ + /* set command/status address i/o to 1st codec */ + outw(inw(ioaddr + 0x3a) & 0xfffc, ioaddr + 0x3a); + outw(inw(ioaddr + 0x3c) & 0xfffc, ioaddr + 0x3c); + + /* disable ac link */ + outw(0x0000, ioaddr + 0x36); + save_68 = inw(ioaddr + 0x68); + pci_read_config_word(chip->pci, 0x58, &w); /* something magical with gpio and bus arb. */ + pci_read_config_dword(chip->pci, PCI_SUBSYSTEM_VENDOR_ID, &vend); + if (w & 1) + save_68 |= 0x10; + outw(0xfffe, ioaddr + 0x64); /* unmask gpio 0 */ + outw(0x0001, ioaddr + 0x68); /* gpio write */ + outw(0x0000, ioaddr + 0x60); /* write 0 to gpio 0 */ + udelay(20); + outw(0x0001, ioaddr + 0x60); /* write 1 to gpio 1 */ + msleep(20); + + outw(save_68 | 0x1, ioaddr + 0x68); /* now restore .. */ + outw((inw(ioaddr + 0x38) & 0xfffc) | 0x1, ioaddr + 0x38); + outw((inw(ioaddr + 0x3a) & 0xfffc) | 0x1, ioaddr + 0x3a); + outw((inw(ioaddr + 0x3c) & 0xfffc) | 0x1, ioaddr + 0x3c); + + /* now the second codec */ + /* disable ac link */ + outw(0x0000, ioaddr + 0x36); + outw(0xfff7, ioaddr + 0x64); /* unmask gpio 3 */ + save_68 = inw(ioaddr + 0x68); + outw(0x0009, ioaddr + 0x68); /* gpio write 0 & 3 ?? */ + outw(0x0001, ioaddr + 0x60); /* write 1 to gpio */ + udelay(20); + outw(0x0009, ioaddr + 0x60); /* write 9 to gpio */ + msleep(500); + //outw(inw(ioaddr + 0x38) & 0xfffc, ioaddr + 0x38); + outw(inw(ioaddr + 0x3a) & 0xfffc, ioaddr + 0x3a); + outw(inw(ioaddr + 0x3c) & 0xfffc, ioaddr + 0x3c); + +#if 0 /* the loop here needs to be much better if we want it.. */ + dev_info(chip->card->dev, "trying software reset\n"); + /* try and do a software reset */ + outb(0x80 | 0x7c, ioaddr + 0x30); + for (w = 0;; w++) { + if ((inw(ioaddr + 0x30) & 1) == 0) { + if (inb(ioaddr + 0x32) != 0) + break; + + outb(0x80 | 0x7d, ioaddr + 0x30); + if (((inw(ioaddr + 0x30) & 1) == 0) + && (inb(ioaddr + 0x32) != 0)) + break; + outb(0x80 | 0x7f, ioaddr + 0x30); + if (((inw(ioaddr + 0x30) & 1) == 0) + && (inb(ioaddr + 0x32) != 0)) + break; + } + + if (w > 10000) { + outb(inb(ioaddr + 0x37) | 0x08, ioaddr + 0x37); /* do a software reset */ + msleep(500); /* oh my.. */ + outb(inb(ioaddr + 0x37) & ~0x08, + ioaddr + 0x37); + udelay(1); + outw(0x80, ioaddr + 0x30); + for (w = 0; w < 10000; w++) { + if ((inw(ioaddr + 0x30) & 1) == 0) + break; + } + } + } +#endif + if (vend == NEC_VERSA_SUBID1 || vend == NEC_VERSA_SUBID2) { + /* turn on external amp? */ + outw(0xf9ff, ioaddr + 0x64); + outw(inw(ioaddr + 0x68) | 0x600, ioaddr + 0x68); + outw(0x0209, ioaddr + 0x60); + } + + /* restore.. */ + outw(save_ringbus_a, ioaddr + 0x36); + + /* Turn on the 978 docking chip. + First frob the "master output enable" bit, + then set most of the playback volume control registers to max. */ + outb(inb(ioaddr+0xc0)|(1<<5), ioaddr+0xc0); + outb(0xff, ioaddr+0xc3); + outb(0xff, ioaddr+0xc4); + outb(0xff, ioaddr+0xc6); + outb(0xff, ioaddr+0xc8); + outb(0x3f, ioaddr+0xcf); + outb(0x3f, ioaddr+0xd0); +} + +static void snd_es1968_reset(struct es1968 *chip) +{ + /* Reset */ + outw(ESM_RESET_MAESTRO | ESM_RESET_DIRECTSOUND, + chip->io_port + ESM_PORT_HOST_IRQ); + udelay(10); + outw(0x0000, chip->io_port + ESM_PORT_HOST_IRQ); + udelay(10); +} + +/* + * initialize maestro chip + */ +static void snd_es1968_chip_init(struct es1968 *chip) +{ + struct pci_dev *pci = chip->pci; + int i; + unsigned long iobase = chip->io_port; + u16 w; + u32 n; + + /* We used to muck around with pci config space that + * we had no business messing with. We don't know enough + * about the machine to know which DMA mode is appropriate, + * etc. We were guessing wrong on some machines and making + * them unhappy. We now trust in the BIOS to do things right, + * which almost certainly means a new host of problems will + * arise with broken BIOS implementations. screw 'em. + * We're already intolerant of machines that don't assign + * IRQs. + */ + + /* Config Reg A */ + pci_read_config_word(pci, ESM_CONFIG_A, &w); + + w &= ~DMA_CLEAR; /* Clear DMA bits */ + w &= ~(PIC_SNOOP1 | PIC_SNOOP2); /* Clear Pic Snoop Mode Bits */ + w &= ~SAFEGUARD; /* Safeguard off */ + w |= POST_WRITE; /* Posted write */ + w |= PCI_TIMING; /* PCI timing on */ + /* XXX huh? claims to be reserved.. */ + w &= ~SWAP_LR; /* swap left/right + seems to only have effect on SB + Emulation */ + w &= ~SUBTR_DECODE; /* Subtractive decode off */ + + pci_write_config_word(pci, ESM_CONFIG_A, w); + + /* Config Reg B */ + + pci_read_config_word(pci, ESM_CONFIG_B, &w); + + w &= ~(1 << 15); /* Turn off internal clock multiplier */ + /* XXX how do we know which to use? */ + w &= ~(1 << 14); /* External clock */ + + w &= ~SPDIF_CONFB; /* disable S/PDIF output */ + w |= HWV_CONFB; /* HWV on */ + w |= DEBOUNCE; /* Debounce off: easier to push the HW buttons */ + w &= ~GPIO_CONFB; /* GPIO 4:5 */ + w |= CHI_CONFB; /* Disconnect from the CHI. Enabling this made a dell 7500 work. */ + w &= ~IDMA_CONFB; /* IDMA off (undocumented) */ + w &= ~MIDI_FIX; /* MIDI fix off (undoc) */ + w &= ~(1 << 1); /* reserved, always write 0 */ + w &= ~IRQ_TO_ISA; /* IRQ to ISA off (undoc) */ + + pci_write_config_word(pci, ESM_CONFIG_B, w); + + /* DDMA off */ + + pci_read_config_word(pci, ESM_DDMA, &w); + w &= ~(1 << 0); + pci_write_config_word(pci, ESM_DDMA, w); + + /* + * Legacy mode + */ + + pci_read_config_word(pci, ESM_LEGACY_AUDIO_CONTROL, &w); + + w |= ESS_DISABLE_AUDIO; /* Disable Legacy Audio */ + w &= ~ESS_ENABLE_SERIAL_IRQ; /* Disable SIRQ */ + w &= ~(0x1f); /* disable mpu irq/io, game port, fm, SB */ + + pci_write_config_word(pci, ESM_LEGACY_AUDIO_CONTROL, w); + + /* Set up 978 docking control chip. */ + pci_read_config_word(pci, 0x58, &w); + w|=1<<2; /* Enable 978. */ + w|=1<<3; /* Turn on 978 hardware volume control. */ + w&=~(1<<11); /* Turn on 978 mixer volume control. */ + pci_write_config_word(pci, 0x58, w); + + /* Sound Reset */ + + snd_es1968_reset(chip); + + /* + * Ring Bus Setup + */ + + /* setup usual 0x34 stuff.. 0x36 may be chip specific */ + outw(0xC090, iobase + ESM_RING_BUS_DEST); /* direct sound, stereo */ + udelay(20); + outw(0x3000, iobase + ESM_RING_BUS_CONTR_A); /* enable ringbus/serial */ + udelay(20); + + /* + * Reset the CODEC + */ + + snd_es1968_ac97_reset(chip); + + /* Ring Bus Control B */ + + n = inl(iobase + ESM_RING_BUS_CONTR_B); + n &= ~RINGB_EN_SPDIF; /* SPDIF off */ + //w |= RINGB_EN_2CODEC; /* enable 2nd codec */ + outl(n, iobase + ESM_RING_BUS_CONTR_B); + + /* Set hardware volume control registers to midpoints. + We can tell which button was pushed based on how they change. */ + outb(0x88, iobase+0x1c); + outb(0x88, iobase+0x1d); + outb(0x88, iobase+0x1e); + outb(0x88, iobase+0x1f); + + /* it appears some maestros (dell 7500) only work if these are set, + regardless of whether we use the assp or not. */ + + outb(0, iobase + ASSP_CONTROL_B); + outb(3, iobase + ASSP_CONTROL_A); /* M: Reserved bits... */ + outb(0, iobase + ASSP_CONTROL_C); /* M: Disable ASSP, ASSP IRQ's and FM Port */ + + /* + * set up wavecache + */ + for (i = 0; i < 16; i++) { + /* Write 0 into the buffer area 0x1E0->1EF */ + outw(0x01E0 + i, iobase + WC_INDEX); + outw(0x0000, iobase + WC_DATA); + + /* The 1.10 test program seem to write 0 into the buffer area + * 0x1D0-0x1DF too.*/ + outw(0x01D0 + i, iobase + WC_INDEX); + outw(0x0000, iobase + WC_DATA); + } + wave_set_register(chip, IDR7_WAVE_ROMRAM, + (wave_get_register(chip, IDR7_WAVE_ROMRAM) & 0xFF00)); + wave_set_register(chip, IDR7_WAVE_ROMRAM, + wave_get_register(chip, IDR7_WAVE_ROMRAM) | 0x100); + wave_set_register(chip, IDR7_WAVE_ROMRAM, + wave_get_register(chip, IDR7_WAVE_ROMRAM) & ~0x200); + wave_set_register(chip, IDR7_WAVE_ROMRAM, + wave_get_register(chip, IDR7_WAVE_ROMRAM) | ~0x400); + + + maestro_write(chip, IDR2_CRAM_DATA, 0x0000); + /* Now back to the DirectSound stuff */ + /* audio serial configuration.. ? */ + maestro_write(chip, 0x08, 0xB004); + maestro_write(chip, 0x09, 0x001B); + maestro_write(chip, 0x0A, 0x8000); + maestro_write(chip, 0x0B, 0x3F37); + maestro_write(chip, 0x0C, 0x0098); + + /* parallel in, has something to do with recording :) */ + maestro_write(chip, 0x0C, + (maestro_read(chip, 0x0C) & ~0xF000) | 0x8000); + /* parallel out */ + maestro_write(chip, 0x0C, + (maestro_read(chip, 0x0C) & ~0x0F00) | 0x0500); + + maestro_write(chip, 0x0D, 0x7632); + + /* Wave cache control on - test off, sg off, + enable, enable extra chans 1Mb */ + + w = inw(iobase + WC_CONTROL); + + w &= ~0xFA00; /* Seems to be reserved? I don't know */ + w |= 0xA000; /* reserved... I don't know */ + w &= ~0x0200; /* Channels 56,57,58,59 as Extra Play,Rec Channel enable + Seems to crash the Computer if enabled... */ + w |= 0x0100; /* Wave Cache Operation Enabled */ + w |= 0x0080; /* Channels 60/61 as Placback/Record enabled */ + w &= ~0x0060; /* Clear Wavtable Size */ + w |= 0x0020; /* Wavetable Size : 1MB */ + /* Bit 4 is reserved */ + w &= ~0x000C; /* DMA Stuff? I don't understand what the datasheet means */ + /* Bit 1 is reserved */ + w &= ~0x0001; /* Test Mode off */ + + outw(w, iobase + WC_CONTROL); + + /* Now clear the APU control ram */ + for (i = 0; i < NR_APUS; i++) { + for (w = 0; w < NR_APU_REGS; w++) + apu_set_register(chip, i, w, 0); + + } +} + +/* Enable IRQ's */ +static void snd_es1968_start_irq(struct es1968 *chip) +{ + unsigned short w; + w = ESM_HIRQ_DSIE | ESM_HIRQ_HW_VOLUME; + if (chip->rmidi) + w |= ESM_HIRQ_MPU401; + outb(w, chip->io_port + 0x1A); + outw(w, chip->io_port + ESM_PORT_HOST_IRQ); +} + +/* + * PM support + */ +static int es1968_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct es1968 *chip = card->private_data; + + if (! chip->do_pm) + return 0; + + chip->in_suspend = 1; + cancel_work_sync(&chip->hwvol_work); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(chip->ac97); + snd_es1968_bob_stop(chip); + return 0; +} + +static int es1968_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct es1968 *chip = card->private_data; + struct esschan *es; + + if (! chip->do_pm) + return 0; + + snd_es1968_chip_init(chip); + + /* need to restore the base pointers.. */ + if (chip->dma.addr) { + /* set PCMBAR */ + wave_set_register(chip, 0x01FC, chip->dma.addr >> 12); + } + + snd_es1968_start_irq(chip); + + /* restore ac97 state */ + snd_ac97_resume(chip->ac97); + + list_for_each_entry(es, &chip->substream_list, list) { + switch (es->mode) { + case ESM_MODE_PLAY: + snd_es1968_playback_setup(chip, es, es->substream->runtime); + break; + case ESM_MODE_CAPTURE: + snd_es1968_capture_setup(chip, es, es->substream->runtime); + break; + } + } + + /* start timer again */ + if (chip->bobclient) + snd_es1968_bob_start(chip); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + chip->in_suspend = 0; + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(es1968_pm, es1968_suspend, es1968_resume); + +#ifdef SUPPORT_JOYSTICK +#define JOYSTICK_ADDR 0x200 +static int snd_es1968_create_gameport(struct es1968 *chip, int dev) +{ + struct gameport *gp; + struct resource *r; + u16 val; + + if (!joystick[dev]) + return -ENODEV; + + r = devm_request_region(&chip->pci->dev, JOYSTICK_ADDR, 8, + "ES1968 gameport"); + if (!r) + return -EBUSY; + + chip->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(chip->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + pci_read_config_word(chip->pci, ESM_LEGACY_AUDIO_CONTROL, &val); + pci_write_config_word(chip->pci, ESM_LEGACY_AUDIO_CONTROL, val | 0x04); + + gameport_set_name(gp, "ES1968 Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci)); + gameport_set_dev_parent(gp, &chip->pci->dev); + gp->io = JOYSTICK_ADDR; + + gameport_register_port(gp); + + return 0; +} + +static void snd_es1968_free_gameport(struct es1968 *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } +} +#else +static inline int snd_es1968_create_gameport(struct es1968 *chip, int dev) { return -ENOSYS; } +static inline void snd_es1968_free_gameport(struct es1968 *chip) { } +#endif + +#ifdef CONFIG_SND_ES1968_INPUT +static int snd_es1968_input_register(struct es1968 *chip) +{ + struct input_dev *input_dev; + int err; + + input_dev = devm_input_allocate_device(&chip->pci->dev); + if (!input_dev) + return -ENOMEM; + + snprintf(chip->phys, sizeof(chip->phys), "pci-%s/input0", + pci_name(chip->pci)); + + input_dev->name = chip->card->driver; + input_dev->phys = chip->phys; + input_dev->id.bustype = BUS_PCI; + input_dev->id.vendor = chip->pci->vendor; + input_dev->id.product = chip->pci->device; + input_dev->dev.parent = &chip->pci->dev; + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(KEY_MUTE, input_dev->keybit); + __set_bit(KEY_VOLUMEDOWN, input_dev->keybit); + __set_bit(KEY_VOLUMEUP, input_dev->keybit); + + err = input_register_device(input_dev); + if (err) + return err; + + chip->input_dev = input_dev; + return 0; +} +#endif /* CONFIG_SND_ES1968_INPUT */ + +#ifdef CONFIG_SND_ES1968_RADIO +#define GPIO_DATA 0x60 +#define IO_MASK 4 /* mask register offset from GPIO_DATA + bits 1=unmask write to given bit */ +#define IO_DIR 8 /* direction register offset from GPIO_DATA + bits 0/1=read/write direction */ + +/* GPIO to TEA575x maps */ +struct snd_es1968_tea575x_gpio { + u8 data, clk, wren, most; + char *name; +}; + +static const struct snd_es1968_tea575x_gpio snd_es1968_tea575x_gpios[] = { + { .data = 6, .clk = 7, .wren = 8, .most = 9, .name = "SF64-PCE2" }, + { .data = 7, .clk = 8, .wren = 6, .most = 10, .name = "M56VAP" }, +}; + +#define get_tea575x_gpio(chip) \ + (&snd_es1968_tea575x_gpios[(chip)->tea575x_tuner]) + + +static void snd_es1968_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) +{ + struct es1968 *chip = tea->private_data; + struct snd_es1968_tea575x_gpio gpio = *get_tea575x_gpio(chip); + u16 val = 0; + + val |= (pins & TEA575X_DATA) ? (1 << gpio.data) : 0; + val |= (pins & TEA575X_CLK) ? (1 << gpio.clk) : 0; + val |= (pins & TEA575X_WREN) ? (1 << gpio.wren) : 0; + + outw(val, chip->io_port + GPIO_DATA); +} + +static u8 snd_es1968_tea575x_get_pins(struct snd_tea575x *tea) +{ + struct es1968 *chip = tea->private_data; + struct snd_es1968_tea575x_gpio gpio = *get_tea575x_gpio(chip); + u16 val = inw(chip->io_port + GPIO_DATA); + u8 ret = 0; + + if (val & (1 << gpio.data)) + ret |= TEA575X_DATA; + if (val & (1 << gpio.most)) + ret |= TEA575X_MOST; + + return ret; +} + +static void snd_es1968_tea575x_set_direction(struct snd_tea575x *tea, bool output) +{ + struct es1968 *chip = tea->private_data; + unsigned long io = chip->io_port + GPIO_DATA; + u16 odir = inw(io + IO_DIR); + struct snd_es1968_tea575x_gpio gpio = *get_tea575x_gpio(chip); + + if (output) { + outw(~((1 << gpio.data) | (1 << gpio.clk) | (1 << gpio.wren)), + io + IO_MASK); + outw(odir | (1 << gpio.data) | (1 << gpio.clk) | (1 << gpio.wren), + io + IO_DIR); + } else { + outw(~((1 << gpio.clk) | (1 << gpio.wren) | (1 << gpio.data) | (1 << gpio.most)), + io + IO_MASK); + outw((odir & ~((1 << gpio.data) | (1 << gpio.most))) + | (1 << gpio.clk) | (1 << gpio.wren), io + IO_DIR); + } +} + +static const struct snd_tea575x_ops snd_es1968_tea_ops = { + .set_pins = snd_es1968_tea575x_set_pins, + .get_pins = snd_es1968_tea575x_get_pins, + .set_direction = snd_es1968_tea575x_set_direction, +}; +#endif + +static void snd_es1968_free(struct snd_card *card) +{ + struct es1968 *chip = card->private_data; + + cancel_work_sync(&chip->hwvol_work); + + if (chip->io_port) { + outw(1, chip->io_port + 0x04); /* clear WP interrupts */ + outw(0, chip->io_port + ESM_PORT_HOST_IRQ); /* disable IRQ */ + } + +#ifdef CONFIG_SND_ES1968_RADIO + snd_tea575x_exit(&chip->tea); + v4l2_device_unregister(&chip->v4l2_dev); +#endif + + snd_es1968_free_gameport(chip); +} + +struct ess_device_list { + unsigned short type; /* chip type */ + unsigned short vendor; /* subsystem vendor id */ +}; + +static const struct ess_device_list pm_allowlist[] = { + { TYPE_MAESTRO2E, 0x0e11 }, /* Compaq Armada */ + { TYPE_MAESTRO2E, 0x1028 }, + { TYPE_MAESTRO2E, 0x103c }, + { TYPE_MAESTRO2E, 0x1179 }, + { TYPE_MAESTRO2E, 0x14c0 }, /* HP omnibook 4150 */ + { TYPE_MAESTRO2E, 0x1558 }, + { TYPE_MAESTRO2E, 0x125d }, /* a PCI card, e.g. Terratec DMX */ + { TYPE_MAESTRO2, 0x125d }, /* a PCI card, e.g. SF64-PCE2 */ +}; + +static const struct ess_device_list mpu_denylist[] = { + { TYPE_MAESTRO2, 0x125d }, +}; + +static int snd_es1968_create(struct snd_card *card, + struct pci_dev *pci, + int total_bufsize, + int play_streams, + int capt_streams, + int chip_type, + int do_pm, + int radio_nr) +{ + struct es1968 *chip = card->private_data; + int i, err; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + /* check, if we can restrict PCI DMA transfers to 28 bits */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(28))) { + dev_err(card->dev, + "architecture does not support 28bit PCI busmaster DMA\n"); + return -ENXIO; + } + + /* Set Vars */ + chip->type = chip_type; + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->substream_lock); + INIT_LIST_HEAD(&chip->buf_list); + INIT_LIST_HEAD(&chip->substream_list); + mutex_init(&chip->memory_mutex); + INIT_WORK(&chip->hwvol_work, es1968_update_hw_volume); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->total_bufsize = total_bufsize; /* in bytes */ + chip->playback_streams = play_streams; + chip->capture_streams = capt_streams; + + err = pcim_request_all_regions(pci, "ESS Maestro"); + if (err < 0) + return err; + chip->io_port = pci_resource_start(pci, 0); + if (devm_request_irq(&pci->dev, pci->irq, snd_es1968_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_es1968_free; + + /* Clear Maestro_map */ + for (i = 0; i < 32; i++) + chip->maestro_map[i] = 0; + + /* Clear Apu Map */ + for (i = 0; i < NR_APUS; i++) + chip->apu[i] = ESM_APU_FREE; + + /* just to be sure */ + pci_set_master(pci); + + if (do_pm > 1) { + /* disable power-management if not on the allowlist */ + unsigned short vend; + pci_read_config_word(chip->pci, PCI_SUBSYSTEM_VENDOR_ID, &vend); + for (i = 0; i < (int)ARRAY_SIZE(pm_allowlist); i++) { + if (chip->type == pm_allowlist[i].type && + vend == pm_allowlist[i].vendor) { + do_pm = 1; + break; + } + } + if (do_pm > 1) { + /* not matched; disabling pm */ + dev_info(card->dev, "not attempting power management.\n"); + do_pm = 0; + } + } + chip->do_pm = do_pm; + + snd_es1968_chip_init(chip); + +#ifdef CONFIG_SND_ES1968_RADIO + /* don't play with GPIOs on laptops */ + if (chip->pci->subsystem_vendor != 0x125d) + return 0; + err = v4l2_device_register(&pci->dev, &chip->v4l2_dev); + if (err < 0) + return err; + chip->tea.v4l2_dev = &chip->v4l2_dev; + chip->tea.private_data = chip; + chip->tea.radio_nr = radio_nr; + chip->tea.ops = &snd_es1968_tea_ops; + sprintf(chip->tea.bus_info, "PCI:%s", pci_name(pci)); + for (i = 0; i < ARRAY_SIZE(snd_es1968_tea575x_gpios); i++) { + chip->tea575x_tuner = i; + if (!snd_tea575x_init(&chip->tea, THIS_MODULE)) { + dev_info(card->dev, "detected TEA575x radio type %s\n", + get_tea575x_gpio(chip)->name); + strscpy(chip->tea.card, get_tea575x_gpio(chip)->name, + sizeof(chip->tea.card)); + break; + } + } +#endif + return 0; +} + + +/* + */ +static int __snd_es1968_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct es1968 *chip; + unsigned int i; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + if (total_bufsize[dev] < 128) + total_bufsize[dev] = 128; + if (total_bufsize[dev] > 4096) + total_bufsize[dev] = 4096; + err = snd_es1968_create(card, pci, + total_bufsize[dev] * 1024, /* in bytes */ + pcm_substreams_p[dev], + pcm_substreams_c[dev], + pci_id->driver_data, + use_pm[dev], + radio_nr[dev]); + if (err < 0) + return err; + + switch (chip->type) { + case TYPE_MAESTRO2E: + strscpy(card->driver, "ES1978"); + strscpy(card->shortname, "ESS ES1978 (Maestro 2E)"); + break; + case TYPE_MAESTRO2: + strscpy(card->driver, "ES1968"); + strscpy(card->shortname, "ESS ES1968 (Maestro 2)"); + break; + case TYPE_MAESTRO: + strscpy(card->driver, "ESM1"); + strscpy(card->shortname, "ESS Maestro 1"); + break; + } + + err = snd_es1968_pcm(chip, 0); + if (err < 0) + return err; + + err = snd_es1968_mixer(chip); + if (err < 0) + return err; + + if (enable_mpu[dev] == 2) { + /* check the deny list */ + unsigned short vend; + pci_read_config_word(chip->pci, PCI_SUBSYSTEM_VENDOR_ID, &vend); + for (i = 0; i < ARRAY_SIZE(mpu_denylist); i++) { + if (chip->type == mpu_denylist[i].type && + vend == mpu_denylist[i].vendor) { + enable_mpu[dev] = 0; + break; + } + } + } + if (enable_mpu[dev]) { + err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + chip->io_port + ESM_MPU401_PORT, + MPU401_INFO_INTEGRATED | + MPU401_INFO_IRQ_HOOK, + -1, &chip->rmidi); + if (err < 0) + dev_warn(card->dev, "skipping MPU-401 MIDI support..\n"); + } + + snd_es1968_create_gameport(chip, dev); + +#ifdef CONFIG_SND_ES1968_INPUT + err = snd_es1968_input_register(chip); + if (err) + dev_warn(card->dev, + "Input device registration failed with error %i", err); +#endif + + snd_es1968_start_irq(chip); + + chip->clock = clock[dev]; + if (! chip->clock) + es1968_measure_clock(chip); + + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, chip->io_port, chip->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_es1968_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_es1968_probe(pci, pci_id)); +} + +static struct pci_driver es1968_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_es1968_ids, + .probe = snd_es1968_probe, + .driver = { + .pm = &es1968_pm, + }, +}; + +module_pci_driver(es1968_driver); diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c new file mode 100644 index 000000000..9cc96b807 --- /dev/null +++ b/sound/pci/fm801.c @@ -0,0 +1,1405 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The driver for the ForteMedia FM801 based soundcards + * Copyright (c) by Jaroslav Kysela + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SND_FM801_TEA575X_BOOL +#include +#endif + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("ForteMedia FM801"); +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_PNP; /* Enable this card */ +/* + * Enable TEA575x tuner + * 1 = MediaForte 256-PCS + * 2 = MediaForte 256-PCP + * 3 = MediaForte 64-PCR + * 16 = setup tuner only (this is additional bit), i.e. SF64-PCR FM card + * High 16-bits are video (radio) device number + 1 + */ +static int tea575x_tuner[SNDRV_CARDS]; +static int radio_nr[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the FM801 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the FM801 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable FM801 soundcard."); +module_param_array(tea575x_tuner, int, NULL, 0444); +MODULE_PARM_DESC(tea575x_tuner, "TEA575x tuner access method (0 = auto, 1 = SF256-PCS, 2=SF256-PCP, 3=SF64-PCR, 8=disable, +16=tuner-only)."); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); + + +#define TUNER_DISABLED (1<<3) +#define TUNER_ONLY (1<<4) +#define TUNER_TYPE_MASK (~TUNER_ONLY & 0xFFFF) + +/* + * Direct registers + */ + +#define fm801_writew(chip,reg,value) outw((value), chip->port + FM801_##reg) +#define fm801_readw(chip,reg) inw(chip->port + FM801_##reg) + +#define fm801_writel(chip,reg,value) outl((value), chip->port + FM801_##reg) + +#define FM801_PCM_VOL 0x00 /* PCM Output Volume */ +#define FM801_FM_VOL 0x02 /* FM Output Volume */ +#define FM801_I2S_VOL 0x04 /* I2S Volume */ +#define FM801_REC_SRC 0x06 /* Record Source */ +#define FM801_PLY_CTRL 0x08 /* Playback Control */ +#define FM801_PLY_COUNT 0x0a /* Playback Count */ +#define FM801_PLY_BUF1 0x0c /* Playback Bufer I */ +#define FM801_PLY_BUF2 0x10 /* Playback Buffer II */ +#define FM801_CAP_CTRL 0x14 /* Capture Control */ +#define FM801_CAP_COUNT 0x16 /* Capture Count */ +#define FM801_CAP_BUF1 0x18 /* Capture Buffer I */ +#define FM801_CAP_BUF2 0x1c /* Capture Buffer II */ +#define FM801_CODEC_CTRL 0x22 /* Codec Control */ +#define FM801_I2S_MODE 0x24 /* I2S Mode Control */ +#define FM801_VOLUME 0x26 /* Volume Up/Down/Mute Status */ +#define FM801_I2C_CTRL 0x29 /* I2C Control */ +#define FM801_AC97_CMD 0x2a /* AC'97 Command */ +#define FM801_AC97_DATA 0x2c /* AC'97 Data */ +#define FM801_MPU401_DATA 0x30 /* MPU401 Data */ +#define FM801_MPU401_CMD 0x31 /* MPU401 Command */ +#define FM801_GPIO_CTRL 0x52 /* General Purpose I/O Control */ +#define FM801_GEN_CTRL 0x54 /* General Control */ +#define FM801_IRQ_MASK 0x56 /* Interrupt Mask */ +#define FM801_IRQ_STATUS 0x5a /* Interrupt Status */ +#define FM801_OPL3_BANK0 0x68 /* OPL3 Status Read / Bank 0 Write */ +#define FM801_OPL3_DATA0 0x69 /* OPL3 Data 0 Write */ +#define FM801_OPL3_BANK1 0x6a /* OPL3 Bank 1 Write */ +#define FM801_OPL3_DATA1 0x6b /* OPL3 Bank 1 Write */ +#define FM801_POWERDOWN 0x70 /* Blocks Power Down Control */ + +/* codec access */ +#define FM801_AC97_READ (1<<7) /* read=1, write=0 */ +#define FM801_AC97_VALID (1<<8) /* port valid=1 */ +#define FM801_AC97_BUSY (1<<9) /* busy=1 */ +#define FM801_AC97_ADDR_SHIFT 10 /* codec id (2bit) */ + +/* playback and record control register bits */ +#define FM801_BUF1_LAST (1<<1) +#define FM801_BUF2_LAST (1<<2) +#define FM801_START (1<<5) +#define FM801_PAUSE (1<<6) +#define FM801_IMMED_STOP (1<<7) +#define FM801_RATE_SHIFT 8 +#define FM801_RATE_MASK (15 << FM801_RATE_SHIFT) +#define FM801_CHANNELS_4 (1<<12) /* playback only */ +#define FM801_CHANNELS_6 (2<<12) /* playback only */ +#define FM801_CHANNELS_6MS (3<<12) /* playback only */ +#define FM801_CHANNELS_MASK (3<<12) +#define FM801_16BIT (1<<14) +#define FM801_STEREO (1<<15) + +/* IRQ status bits */ +#define FM801_IRQ_PLAYBACK (1<<8) +#define FM801_IRQ_CAPTURE (1<<9) +#define FM801_IRQ_VOLUME (1<<14) +#define FM801_IRQ_MPU (1<<15) + +/* GPIO control register */ +#define FM801_GPIO_GP0 (1<<0) /* read/write */ +#define FM801_GPIO_GP1 (1<<1) +#define FM801_GPIO_GP2 (1<<2) +#define FM801_GPIO_GP3 (1<<3) +#define FM801_GPIO_GP(x) (1<<(0+(x))) +#define FM801_GPIO_GD0 (1<<8) /* directions: 1 = input, 0 = output*/ +#define FM801_GPIO_GD1 (1<<9) +#define FM801_GPIO_GD2 (1<<10) +#define FM801_GPIO_GD3 (1<<11) +#define FM801_GPIO_GD(x) (1<<(8+(x))) +#define FM801_GPIO_GS0 (1<<12) /* function select: */ +#define FM801_GPIO_GS1 (1<<13) /* 1 = GPIO */ +#define FM801_GPIO_GS2 (1<<14) /* 0 = other (S/PDIF, VOL) */ +#define FM801_GPIO_GS3 (1<<15) +#define FM801_GPIO_GS(x) (1<<(12+(x))) + +/** + * struct fm801 - describes FM801 chip + * @dev: device for this chio + * @irq: irq number + * @port: I/O port number + * @multichannel: multichannel support + * @secondary: secondary codec + * @secondary_addr: address of the secondary codec + * @tea575x_tuner: tuner access method & flags + * @ply_ctrl: playback control + * @cap_ctrl: capture control + * @ply_buffer: playback buffer + * @ply_buf: playback buffer index + * @ply_count: playback buffer count + * @ply_size: playback buffer size + * @ply_pos: playback position + * @cap_buffer: capture buffer + * @cap_buf: capture buffer index + * @cap_count: capture buffer count + * @cap_size: capture buffer size + * @cap_pos: capture position + * @ac97_bus: ac97 bus handle + * @ac97: ac97 handle + * @ac97_sec: ac97 secondary handle + * @card: ALSA card + * @pcm: PCM devices + * @rmidi: rmidi device + * @playback_substream: substream for playback + * @capture_substream: substream for capture + * @p_dma_size: playback DMA size + * @c_dma_size: capture DMA size + * @reg_lock: lock + * @proc_entry: /proc entry + * @v4l2_dev: v4l2 device + * @tea: tea575a structure + * @saved_regs: context saved during suspend + */ +struct fm801 { + struct device *dev; + int irq; + + unsigned long port; + unsigned int multichannel: 1, + secondary: 1; + unsigned char secondary_addr; + unsigned int tea575x_tuner; + + unsigned short ply_ctrl; + unsigned short cap_ctrl; + + unsigned long ply_buffer; + unsigned int ply_buf; + unsigned int ply_count; + unsigned int ply_size; + unsigned int ply_pos; + + unsigned long cap_buffer; + unsigned int cap_buf; + unsigned int cap_count; + unsigned int cap_size; + unsigned int cap_pos; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + struct snd_ac97 *ac97_sec; + + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_rawmidi *rmidi; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + unsigned int p_dma_size; + unsigned int c_dma_size; + + spinlock_t reg_lock; + struct snd_info_entry *proc_entry; + +#ifdef CONFIG_SND_FM801_TEA575X_BOOL + struct v4l2_device v4l2_dev; + struct snd_tea575x tea; +#endif + + u16 saved_regs[0x20]; +}; + +/* + * IO accessors + */ + +static inline void fm801_iowrite16(struct fm801 *chip, unsigned short offset, u16 value) +{ + outw(value, chip->port + offset); +} + +static inline u16 fm801_ioread16(struct fm801 *chip, unsigned short offset) +{ + return inw(chip->port + offset); +} + +static const struct pci_device_id snd_fm801_ids[] = { + { + /* FM801 */ + PCI_DEVICE(0x1319, 0x0801), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, { + /* Gallant Odyssey Sound 4 */ + PCI_DEVICE(0x5213, 0x0510), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_fm801_ids); + +/* + * common I/O routines + */ + +static bool fm801_ac97_is_ready(struct fm801 *chip, unsigned int iterations) +{ + unsigned int idx; + + for (idx = 0; idx < iterations; idx++) { + if (!(fm801_readw(chip, AC97_CMD) & FM801_AC97_BUSY)) + return true; + udelay(10); + } + return false; +} + +static bool fm801_ac97_is_valid(struct fm801 *chip, unsigned int iterations) +{ + unsigned int idx; + + for (idx = 0; idx < iterations; idx++) { + if (fm801_readw(chip, AC97_CMD) & FM801_AC97_VALID) + return true; + udelay(10); + } + return false; +} + +static int snd_fm801_update_bits(struct fm801 *chip, unsigned short reg, + unsigned short mask, unsigned short value) +{ + int change; + unsigned short old, new; + + guard(spinlock_irqsave)(&chip->reg_lock); + old = fm801_ioread16(chip, reg); + new = (old & ~mask) | value; + change = old != new; + if (change) + fm801_iowrite16(chip, reg, new); + return change; +} + +static void snd_fm801_codec_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct fm801 *chip = ac97->private_data; + + /* + * Wait until the codec interface is not ready.. + */ + if (!fm801_ac97_is_ready(chip, 100)) { + dev_err(chip->card->dev, "AC'97 interface is busy (1)\n"); + return; + } + + /* write data and address */ + fm801_writew(chip, AC97_DATA, val); + fm801_writew(chip, AC97_CMD, reg | (ac97->addr << FM801_AC97_ADDR_SHIFT)); + /* + * Wait until the write command is not completed.. + */ + if (!fm801_ac97_is_ready(chip, 1000)) + dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n", + ac97->num); +} + +static unsigned short snd_fm801_codec_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct fm801 *chip = ac97->private_data; + + /* + * Wait until the codec interface is not ready.. + */ + if (!fm801_ac97_is_ready(chip, 100)) { + dev_err(chip->card->dev, "AC'97 interface is busy (1)\n"); + return 0; + } + + /* read command */ + fm801_writew(chip, AC97_CMD, + reg | (ac97->addr << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ); + if (!fm801_ac97_is_ready(chip, 100)) { + dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n", + ac97->num); + return 0; + } + + if (!fm801_ac97_is_valid(chip, 1000)) { + dev_err(chip->card->dev, + "AC'97 interface #%d is not valid (2)\n", ac97->num); + return 0; + } + + return fm801_readw(chip, AC97_DATA); +} + +static const unsigned int rates[] = { + 5500, 8000, 9600, 11025, + 16000, 19200, 22050, 32000, + 38400, 44100, 48000 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + 2, 4, 6 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +/* + * Sample rate routines + */ + +static unsigned short snd_fm801_rate_bits(unsigned int rate) +{ + unsigned int idx; + + for (idx = 0; idx < ARRAY_SIZE(rates); idx++) + if (rates[idx] == rate) + return idx; + snd_BUG(); + return ARRAY_SIZE(rates) - 1; +} + +/* + * PCM part + */ + +static int snd_fm801_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + chip->ply_ctrl &= ~(FM801_BUF1_LAST | + FM801_BUF2_LAST | + FM801_PAUSE); + chip->ply_ctrl |= FM801_START | + FM801_IMMED_STOP; + break; + case SNDRV_PCM_TRIGGER_STOP: + chip->ply_ctrl &= ~(FM801_START | FM801_PAUSE); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + chip->ply_ctrl |= FM801_PAUSE; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + chip->ply_ctrl &= ~FM801_PAUSE; + break; + default: + snd_BUG(); + return -EINVAL; + } + fm801_writew(chip, PLY_CTRL, chip->ply_ctrl); + return 0; +} + +static int snd_fm801_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + chip->cap_ctrl &= ~(FM801_BUF1_LAST | + FM801_BUF2_LAST | + FM801_PAUSE); + chip->cap_ctrl |= FM801_START | + FM801_IMMED_STOP; + break; + case SNDRV_PCM_TRIGGER_STOP: + chip->cap_ctrl &= ~(FM801_START | FM801_PAUSE); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + chip->cap_ctrl |= FM801_PAUSE; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + chip->cap_ctrl &= ~FM801_PAUSE; + break; + default: + snd_BUG(); + return -EINVAL; + } + fm801_writew(chip, CAP_CTRL, chip->cap_ctrl); + return 0; +} + +static int snd_fm801_playback_prepare(struct snd_pcm_substream *substream) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + chip->ply_size = snd_pcm_lib_buffer_bytes(substream); + chip->ply_count = snd_pcm_lib_period_bytes(substream); + guard(spinlock_irq)(&chip->reg_lock); + chip->ply_ctrl &= ~(FM801_START | FM801_16BIT | + FM801_STEREO | FM801_RATE_MASK | + FM801_CHANNELS_MASK); + if (snd_pcm_format_width(runtime->format) == 16) + chip->ply_ctrl |= FM801_16BIT; + if (runtime->channels > 1) { + chip->ply_ctrl |= FM801_STEREO; + if (runtime->channels == 4) + chip->ply_ctrl |= FM801_CHANNELS_4; + else if (runtime->channels == 6) + chip->ply_ctrl |= FM801_CHANNELS_6; + } + chip->ply_ctrl |= snd_fm801_rate_bits(runtime->rate) << FM801_RATE_SHIFT; + chip->ply_buf = 0; + fm801_writew(chip, PLY_CTRL, chip->ply_ctrl); + fm801_writew(chip, PLY_COUNT, chip->ply_count - 1); + chip->ply_buffer = runtime->dma_addr; + chip->ply_pos = 0; + fm801_writel(chip, PLY_BUF1, chip->ply_buffer); + fm801_writel(chip, PLY_BUF2, + chip->ply_buffer + (chip->ply_count % chip->ply_size)); + return 0; +} + +static int snd_fm801_capture_prepare(struct snd_pcm_substream *substream) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + chip->cap_size = snd_pcm_lib_buffer_bytes(substream); + chip->cap_count = snd_pcm_lib_period_bytes(substream); + guard(spinlock_irq)(&chip->reg_lock); + chip->cap_ctrl &= ~(FM801_START | FM801_16BIT | + FM801_STEREO | FM801_RATE_MASK); + if (snd_pcm_format_width(runtime->format) == 16) + chip->cap_ctrl |= FM801_16BIT; + if (runtime->channels > 1) + chip->cap_ctrl |= FM801_STEREO; + chip->cap_ctrl |= snd_fm801_rate_bits(runtime->rate) << FM801_RATE_SHIFT; + chip->cap_buf = 0; + fm801_writew(chip, CAP_CTRL, chip->cap_ctrl); + fm801_writew(chip, CAP_COUNT, chip->cap_count - 1); + chip->cap_buffer = runtime->dma_addr; + chip->cap_pos = 0; + fm801_writel(chip, CAP_BUF1, chip->cap_buffer); + fm801_writel(chip, CAP_BUF2, + chip->cap_buffer + (chip->cap_count % chip->cap_size)); + return 0; +} + +static snd_pcm_uframes_t snd_fm801_playback_pointer(struct snd_pcm_substream *substream) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->ply_ctrl & FM801_START)) + return 0; + guard(spinlock)(&chip->reg_lock); + ptr = chip->ply_pos + (chip->ply_count - 1) - fm801_readw(chip, PLY_COUNT); + if (fm801_readw(chip, IRQ_STATUS) & FM801_IRQ_PLAYBACK) { + ptr += chip->ply_count; + ptr %= chip->ply_size; + } + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_fm801_capture_pointer(struct snd_pcm_substream *substream) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->cap_ctrl & FM801_START)) + return 0; + guard(spinlock)(&chip->reg_lock); + ptr = chip->cap_pos + (chip->cap_count - 1) - fm801_readw(chip, CAP_COUNT); + if (fm801_readw(chip, IRQ_STATUS) & FM801_IRQ_CAPTURE) { + ptr += chip->cap_count; + ptr %= chip->cap_size; + } + return bytes_to_frames(substream->runtime, ptr); +} + +static irqreturn_t snd_fm801_interrupt(int irq, void *dev_id) +{ + struct fm801 *chip = dev_id; + unsigned short status; + unsigned int tmp; + + status = fm801_readw(chip, IRQ_STATUS); + status &= FM801_IRQ_PLAYBACK|FM801_IRQ_CAPTURE|FM801_IRQ_MPU|FM801_IRQ_VOLUME; + if (! status) + return IRQ_NONE; + /* ack first */ + fm801_writew(chip, IRQ_STATUS, status); + if (chip->pcm && (status & FM801_IRQ_PLAYBACK) && chip->playback_substream) { + scoped_guard(spinlock, &chip->reg_lock) { + chip->ply_buf++; + chip->ply_pos += chip->ply_count; + chip->ply_pos %= chip->ply_size; + tmp = chip->ply_pos + chip->ply_count; + tmp %= chip->ply_size; + if (chip->ply_buf & 1) + fm801_writel(chip, PLY_BUF1, chip->ply_buffer + tmp); + else + fm801_writel(chip, PLY_BUF2, chip->ply_buffer + tmp); + } + snd_pcm_period_elapsed(chip->playback_substream); + } + if (chip->pcm && (status & FM801_IRQ_CAPTURE) && chip->capture_substream) { + scoped_guard(spinlock, &chip->reg_lock) { + chip->cap_buf++; + chip->cap_pos += chip->cap_count; + chip->cap_pos %= chip->cap_size; + tmp = chip->cap_pos + chip->cap_count; + tmp %= chip->cap_size; + if (chip->cap_buf & 1) + fm801_writel(chip, CAP_BUF1, chip->cap_buffer + tmp); + else + fm801_writel(chip, CAP_BUF2, chip->cap_buffer + tmp); + } + snd_pcm_period_elapsed(chip->capture_substream); + } + if (chip->rmidi && (status & FM801_IRQ_MPU)) + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); + if (status & FM801_IRQ_VOLUME) { + /* TODO */ + } + + return IRQ_HANDLED; +} + +static const struct snd_pcm_hardware snd_fm801_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5500, + .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_fm801_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5500, + .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_fm801_playback_open(struct snd_pcm_substream *substream) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + chip->playback_substream = substream; + runtime->hw = snd_fm801_playback; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_rates); + if (chip->multichannel) { + runtime->hw.channels_max = 6; + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_constraints_channels); + } + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + return 0; +} + +static int snd_fm801_capture_open(struct snd_pcm_substream *substream) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + chip->capture_substream = substream; + runtime->hw = snd_fm801_capture; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_rates); + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + return 0; +} + +static int snd_fm801_playback_close(struct snd_pcm_substream *substream) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + return 0; +} + +static int snd_fm801_capture_close(struct snd_pcm_substream *substream) +{ + struct fm801 *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + return 0; +} + +static const struct snd_pcm_ops snd_fm801_playback_ops = { + .open = snd_fm801_playback_open, + .close = snd_fm801_playback_close, + .prepare = snd_fm801_playback_prepare, + .trigger = snd_fm801_playback_trigger, + .pointer = snd_fm801_playback_pointer, +}; + +static const struct snd_pcm_ops snd_fm801_capture_ops = { + .open = snd_fm801_capture_open, + .close = snd_fm801_capture_close, + .prepare = snd_fm801_capture_prepare, + .trigger = snd_fm801_capture_trigger, + .pointer = snd_fm801_capture_pointer, +}; + +static int snd_fm801_pcm(struct fm801 *chip, int device) +{ + struct pci_dev *pdev = to_pci_dev(chip->dev); + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "FM801", device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_fm801_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_fm801_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + strscpy(pcm->name, "FM801"); + chip->pcm = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &pdev->dev, + chip->multichannel ? 128*1024 : 64*1024, 128*1024); + + return snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_alt_chmaps, + chip->multichannel ? 6 : 2, 0, + NULL); +} + +/* + * TEA5757 radio + */ + +#ifdef CONFIG_SND_FM801_TEA575X_BOOL + +/* GPIO to TEA575x maps */ +struct snd_fm801_tea575x_gpio { + u8 data, clk, wren, most; + char *name; +}; + +static const struct snd_fm801_tea575x_gpio snd_fm801_tea575x_gpios[] = { + { .data = 1, .clk = 3, .wren = 2, .most = 0, .name = "SF256-PCS" }, + { .data = 1, .clk = 0, .wren = 2, .most = 3, .name = "SF256-PCP" }, + { .data = 2, .clk = 0, .wren = 1, .most = 3, .name = "SF64-PCR" }, +}; + +#define get_tea575x_gpio(chip) \ + (&snd_fm801_tea575x_gpios[((chip)->tea575x_tuner & TUNER_TYPE_MASK) - 1]) + +static void snd_fm801_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) +{ + struct fm801 *chip = tea->private_data; + unsigned short reg = fm801_readw(chip, GPIO_CTRL); + struct snd_fm801_tea575x_gpio gpio = *get_tea575x_gpio(chip); + + reg &= ~(FM801_GPIO_GP(gpio.data) | + FM801_GPIO_GP(gpio.clk) | + FM801_GPIO_GP(gpio.wren)); + + reg |= (pins & TEA575X_DATA) ? FM801_GPIO_GP(gpio.data) : 0; + reg |= (pins & TEA575X_CLK) ? FM801_GPIO_GP(gpio.clk) : 0; + /* WRITE_ENABLE is inverted */ + reg |= (pins & TEA575X_WREN) ? 0 : FM801_GPIO_GP(gpio.wren); + + fm801_writew(chip, GPIO_CTRL, reg); +} + +static u8 snd_fm801_tea575x_get_pins(struct snd_tea575x *tea) +{ + struct fm801 *chip = tea->private_data; + unsigned short reg = fm801_readw(chip, GPIO_CTRL); + struct snd_fm801_tea575x_gpio gpio = *get_tea575x_gpio(chip); + u8 ret; + + ret = 0; + if (reg & FM801_GPIO_GP(gpio.data)) + ret |= TEA575X_DATA; + if (reg & FM801_GPIO_GP(gpio.most)) + ret |= TEA575X_MOST; + return ret; +} + +static void snd_fm801_tea575x_set_direction(struct snd_tea575x *tea, bool output) +{ + struct fm801 *chip = tea->private_data; + unsigned short reg = fm801_readw(chip, GPIO_CTRL); + struct snd_fm801_tea575x_gpio gpio = *get_tea575x_gpio(chip); + + /* use GPIO lines and set write enable bit */ + reg |= FM801_GPIO_GS(gpio.data) | + FM801_GPIO_GS(gpio.wren) | + FM801_GPIO_GS(gpio.clk) | + FM801_GPIO_GS(gpio.most); + if (output) { + /* all of lines are in the write direction */ + /* clear data and clock lines */ + reg &= ~(FM801_GPIO_GD(gpio.data) | + FM801_GPIO_GD(gpio.wren) | + FM801_GPIO_GD(gpio.clk) | + FM801_GPIO_GP(gpio.data) | + FM801_GPIO_GP(gpio.clk) | + FM801_GPIO_GP(gpio.wren)); + } else { + /* use GPIO lines, set data direction to input */ + reg |= FM801_GPIO_GD(gpio.data) | + FM801_GPIO_GD(gpio.most) | + FM801_GPIO_GP(gpio.data) | + FM801_GPIO_GP(gpio.most) | + FM801_GPIO_GP(gpio.wren); + /* all of lines are in the write direction, except data */ + /* clear data, write enable and clock lines */ + reg &= ~(FM801_GPIO_GD(gpio.wren) | + FM801_GPIO_GD(gpio.clk) | + FM801_GPIO_GP(gpio.clk)); + } + + fm801_writew(chip, GPIO_CTRL, reg); +} + +static const struct snd_tea575x_ops snd_fm801_tea_ops = { + .set_pins = snd_fm801_tea575x_set_pins, + .get_pins = snd_fm801_tea575x_get_pins, + .set_direction = snd_fm801_tea575x_set_direction, +}; +#endif + +/* + * Mixer routines + */ + +#define FM801_SINGLE(xname, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_fm801_info_single, \ + .get = snd_fm801_get_single, .put = snd_fm801_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_fm801_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_fm801_get_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fm801 *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) & 0xff; + long *value = ucontrol->value.integer.value; + + value[0] = (fm801_ioread16(chip, reg) >> shift) & mask; + if (invert) + value[0] = mask - value[0]; + return 0; +} + +static int snd_fm801_put_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fm801 *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) & 0xff; + unsigned short val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + return snd_fm801_update_bits(chip, reg, mask << shift, val << shift); +} + +#define FM801_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_fm801_info_double, \ + .get = snd_fm801_get_double, .put = snd_fm801_put_double, \ + .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24) } +#define FM801_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_fm801_info_double, \ + .get = snd_fm801_get_double, .put = snd_fm801_put_double, \ + .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24), \ + .tlv = { .p = (xtlv) } } + +static int snd_fm801_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_fm801_get_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fm801 *chip = snd_kcontrol_chip(kcontrol); + 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; + long *value = ucontrol->value.integer.value; + + guard(spinlock_irq)(&chip->reg_lock); + value[0] = (fm801_ioread16(chip, reg) >> shift_left) & mask; + value[1] = (fm801_ioread16(chip, reg) >> shift_right) & mask; + if (invert) { + value[0] = mask - value[0]; + value[1] = mask - value[1]; + } + return 0; +} + +static int snd_fm801_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fm801 *chip = snd_kcontrol_chip(kcontrol); + 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 val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + return snd_fm801_update_bits(chip, reg, + (mask << shift_left) | (mask << shift_right), + (val1 << shift_left ) | (val2 << shift_right)); +} + +static int snd_fm801_info_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[5] = { + "AC97 Primary", "FM", "I2S", "PCM", "AC97 Secondary" + }; + + return snd_ctl_enum_info(uinfo, 1, 5, texts); +} + +static int snd_fm801_get_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fm801 *chip = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = fm801_readw(chip, REC_SRC) & 7; + if (val > 4) + val = 4; + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int snd_fm801_put_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fm801 *chip = snd_kcontrol_chip(kcontrol); + unsigned short val; + + val = ucontrol->value.enumerated.item[0]; + if (val > 4) + return -EINVAL; + return snd_fm801_update_bits(chip, FM801_REC_SRC, 7, val); +} + +static const DECLARE_TLV_DB_SCALE(db_scale_dsp, -3450, 150, 0); + +#define FM801_CONTROLS ARRAY_SIZE(snd_fm801_controls) + +static const struct snd_kcontrol_new snd_fm801_controls[] = { +FM801_DOUBLE_TLV("Wave Playback Volume", FM801_PCM_VOL, 0, 8, 31, 1, + db_scale_dsp), +FM801_SINGLE("Wave Playback Switch", FM801_PCM_VOL, 15, 1, 1), +FM801_DOUBLE_TLV("I2S Playback Volume", FM801_I2S_VOL, 0, 8, 31, 1, + db_scale_dsp), +FM801_SINGLE("I2S Playback Switch", FM801_I2S_VOL, 15, 1, 1), +FM801_DOUBLE_TLV("FM Playback Volume", FM801_FM_VOL, 0, 8, 31, 1, + db_scale_dsp), +FM801_SINGLE("FM Playback Switch", FM801_FM_VOL, 15, 1, 1), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Capture Source", + .info = snd_fm801_info_mux, + .get = snd_fm801_get_mux, + .put = snd_fm801_put_mux, +} +}; + +#define FM801_CONTROLS_MULTI ARRAY_SIZE(snd_fm801_controls_multi) + +static const struct snd_kcontrol_new snd_fm801_controls_multi[] = { +FM801_SINGLE("AC97 2ch->4ch Copy Switch", FM801_CODEC_CTRL, 7, 1, 0), +FM801_SINGLE("AC97 18-bit Switch", FM801_CODEC_CTRL, 10, 1, 0), +FM801_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), FM801_I2S_MODE, 8, 1, 0), +FM801_SINGLE(SNDRV_CTL_NAME_IEC958("Raw Data ",PLAYBACK,SWITCH), FM801_I2S_MODE, 9, 1, 0), +FM801_SINGLE(SNDRV_CTL_NAME_IEC958("Raw Data ",CAPTURE,SWITCH), FM801_I2S_MODE, 10, 1, 0), +FM801_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), FM801_GEN_CTRL, 2, 1, 0), +}; + +static int snd_fm801_mixer(struct fm801 *chip) +{ + struct snd_ac97_template ac97; + unsigned int i; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_fm801_codec_write, + .read = snd_fm801_codec_read, + }; + + err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97); + if (err < 0) + return err; + if (chip->secondary) { + ac97.num = 1; + ac97.addr = chip->secondary_addr; + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97_sec); + if (err < 0) + return err; + } + for (i = 0; i < FM801_CONTROLS; i++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_fm801_controls[i], chip)); + if (err < 0) + return err; + } + if (chip->multichannel) { + for (i = 0; i < FM801_CONTROLS_MULTI; i++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_fm801_controls_multi[i], chip)); + if (err < 0) + return err; + } + } + return 0; +} + +/* + * initialization routines + */ + +static int wait_for_codec(struct fm801 *chip, unsigned int codec_id, + unsigned short reg, unsigned long waits) +{ + unsigned long timeout = jiffies + waits; + + fm801_writew(chip, AC97_CMD, + reg | (codec_id << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ); + udelay(5); + do { + if ((fm801_readw(chip, AC97_CMD) & + (FM801_AC97_VALID | FM801_AC97_BUSY)) == FM801_AC97_VALID) + return 0; + schedule_timeout_uninterruptible(1); + } while (time_after(timeout, jiffies)); + return -EIO; +} + +static int reset_codec(struct fm801 *chip) +{ + /* codec cold reset + AC'97 warm reset */ + fm801_writew(chip, CODEC_CTRL, (1 << 5) | (1 << 6)); + fm801_readw(chip, CODEC_CTRL); /* flush posting data */ + udelay(100); + fm801_writew(chip, CODEC_CTRL, 0); + + return wait_for_codec(chip, 0, AC97_RESET, msecs_to_jiffies(750)); +} + +static void snd_fm801_chip_multichannel_init(struct fm801 *chip) +{ + unsigned short cmdw; + + if (chip->multichannel) { + if (chip->secondary_addr) { + wait_for_codec(chip, chip->secondary_addr, + AC97_VENDOR_ID1, msecs_to_jiffies(50)); + } else { + /* my card has the secondary codec */ + /* at address #3, so the loop is inverted */ + int i; + for (i = 3; i > 0; i--) { + if (!wait_for_codec(chip, i, AC97_VENDOR_ID1, + msecs_to_jiffies(50))) { + cmdw = fm801_readw(chip, AC97_DATA); + if (cmdw != 0xffff && cmdw != 0) { + chip->secondary = 1; + chip->secondary_addr = i; + break; + } + } + } + } + + /* the recovery phase, it seems that probing for non-existing codec might */ + /* cause timeout problems */ + wait_for_codec(chip, 0, AC97_VENDOR_ID1, msecs_to_jiffies(750)); + } +} + +static void snd_fm801_chip_init(struct fm801 *chip) +{ + unsigned short cmdw; + + /* init volume */ + fm801_writew(chip, PCM_VOL, 0x0808); + fm801_writew(chip, FM_VOL, 0x9f1f); + fm801_writew(chip, I2S_VOL, 0x8808); + + /* I2S control - I2S mode */ + fm801_writew(chip, I2S_MODE, 0x0003); + + /* interrupt setup */ + cmdw = fm801_readw(chip, IRQ_MASK); + if (chip->irq < 0) + cmdw |= 0x00c3; /* mask everything, no PCM nor MPU */ + else + cmdw &= ~0x0083; /* unmask MPU, PLAYBACK & CAPTURE */ + fm801_writew(chip, IRQ_MASK, cmdw); + + /* interrupt clear */ + fm801_writew(chip, IRQ_STATUS, + FM801_IRQ_PLAYBACK | FM801_IRQ_CAPTURE | FM801_IRQ_MPU); +} + +static void snd_fm801_free(struct snd_card *card) +{ + struct fm801 *chip = card->private_data; + unsigned short cmdw; + + /* interrupt setup - mask everything */ + cmdw = fm801_readw(chip, IRQ_MASK); + cmdw |= 0x00c3; + fm801_writew(chip, IRQ_MASK, cmdw); + +#ifdef CONFIG_SND_FM801_TEA575X_BOOL + if (!(chip->tea575x_tuner & TUNER_DISABLED)) { + snd_tea575x_exit(&chip->tea); + v4l2_device_unregister(&chip->v4l2_dev); + } +#endif +} + +static int snd_fm801_create(struct snd_card *card, + struct pci_dev *pci, + int tea575x_tuner, + int radio_nr) +{ + struct fm801 *chip = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + spin_lock_init(&chip->reg_lock); + chip->card = card; + chip->dev = &pci->dev; + chip->irq = -1; + chip->tea575x_tuner = tea575x_tuner; + err = pcim_request_all_regions(pci, "FM801"); + if (err < 0) + return err; + chip->port = pci_resource_start(pci, 0); + + if (pci->revision >= 0xb1) /* FM801-AU */ + chip->multichannel = 1; + + if (!(chip->tea575x_tuner & TUNER_ONLY)) { + if (reset_codec(chip) < 0) { + dev_info(chip->card->dev, + "Primary AC'97 codec not found, assume SF64-PCR (tuner-only)\n"); + chip->tea575x_tuner = 3 | TUNER_ONLY; + } else { + snd_fm801_chip_multichannel_init(chip); + } + } + + if ((chip->tea575x_tuner & TUNER_ONLY) == 0) { + if (devm_request_irq(&pci->dev, pci->irq, snd_fm801_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + pci_set_master(pci); + } + + card->private_free = snd_fm801_free; + snd_fm801_chip_init(chip); + +#ifdef CONFIG_SND_FM801_TEA575X_BOOL + err = v4l2_device_register(&pci->dev, &chip->v4l2_dev); + if (err < 0) + return err; + chip->tea.v4l2_dev = &chip->v4l2_dev; + chip->tea.radio_nr = radio_nr; + chip->tea.private_data = chip; + chip->tea.ops = &snd_fm801_tea_ops; + sprintf(chip->tea.bus_info, "PCI:%s", pci_name(pci)); + if ((chip->tea575x_tuner & TUNER_TYPE_MASK) > 0 && + (chip->tea575x_tuner & TUNER_TYPE_MASK) < 4) { + if (snd_tea575x_init(&chip->tea, THIS_MODULE)) { + dev_err(card->dev, "TEA575x radio not found\n"); + return -ENODEV; + } + } else if ((chip->tea575x_tuner & TUNER_TYPE_MASK) == 0) { + unsigned int tuner_only = chip->tea575x_tuner & TUNER_ONLY; + + /* autodetect tuner connection */ + for (tea575x_tuner = 1; tea575x_tuner <= 3; tea575x_tuner++) { + chip->tea575x_tuner = tea575x_tuner; + if (!snd_tea575x_init(&chip->tea, THIS_MODULE)) { + dev_info(card->dev, + "detected TEA575x radio type %s\n", + get_tea575x_gpio(chip)->name); + break; + } + } + if (tea575x_tuner == 4) { + dev_err(card->dev, "TEA575x radio not found\n"); + chip->tea575x_tuner = TUNER_DISABLED; + } + + chip->tea575x_tuner |= tuner_only; + } + if (!(chip->tea575x_tuner & TUNER_DISABLED)) { + strscpy(chip->tea.card, get_tea575x_gpio(chip)->name, + sizeof(chip->tea.card)); + } +#endif + return 0; +} + +static int __snd_card_fm801_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct fm801 *chip; + struct snd_opl3 *opl3; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + err = snd_fm801_create(card, pci, tea575x_tuner[dev], radio_nr[dev]); + if (err < 0) + return err; + + strscpy(card->driver, "FM801"); + strscpy(card->shortname, "ForteMedia FM801-"); + strcat(card->shortname, chip->multichannel ? "AU" : "AS"); + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, chip->port, chip->irq); + + if (chip->tea575x_tuner & TUNER_ONLY) + goto __fm801_tuner_only; + + err = snd_fm801_pcm(chip, 0); + if (err < 0) + return err; + err = snd_fm801_mixer(chip); + if (err < 0) + return err; + err = snd_mpu401_uart_new(card, 0, MPU401_HW_FM801, + chip->port + FM801_MPU401_DATA, + MPU401_INFO_INTEGRATED | + MPU401_INFO_IRQ_HOOK, + -1, &chip->rmidi); + if (err < 0) + return err; + err = snd_opl3_create(card, chip->port + FM801_OPL3_BANK0, + chip->port + FM801_OPL3_BANK1, + OPL3_HW_OPL3_FM801, 1, &opl3); + if (err < 0) + return err; + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + return err; + + __fm801_tuner_only: + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_card_fm801_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_card_fm801_probe(pci, pci_id)); +} + +static const unsigned char saved_regs[] = { + FM801_PCM_VOL, FM801_I2S_VOL, FM801_FM_VOL, FM801_REC_SRC, + FM801_PLY_CTRL, FM801_PLY_COUNT, FM801_PLY_BUF1, FM801_PLY_BUF2, + FM801_CAP_CTRL, FM801_CAP_COUNT, FM801_CAP_BUF1, FM801_CAP_BUF2, + FM801_CODEC_CTRL, FM801_I2S_MODE, FM801_VOLUME, FM801_GEN_CTRL, +}; + +static int snd_fm801_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct fm801 *chip = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + chip->saved_regs[i] = fm801_ioread16(chip, saved_regs[i]); + + if (chip->tea575x_tuner & TUNER_ONLY) { + /* FIXME: tea575x suspend */ + } else { + snd_ac97_suspend(chip->ac97); + snd_ac97_suspend(chip->ac97_sec); + } + + return 0; +} + +static int snd_fm801_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct fm801 *chip = card->private_data; + int i; + + if (chip->tea575x_tuner & TUNER_ONLY) { + snd_fm801_chip_init(chip); + } else { + reset_codec(chip); + snd_fm801_chip_multichannel_init(chip); + snd_fm801_chip_init(chip); + snd_ac97_resume(chip->ac97); + snd_ac97_resume(chip->ac97_sec); + } + + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + fm801_iowrite16(chip, saved_regs[i], chip->saved_regs[i]); + +#ifdef CONFIG_SND_FM801_TEA575X_BOOL + if (!(chip->tea575x_tuner & TUNER_DISABLED)) + snd_tea575x_set_freq(&chip->tea); +#endif + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_fm801_pm, snd_fm801_suspend, snd_fm801_resume); + +static struct pci_driver fm801_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_fm801_ids, + .probe = snd_card_fm801_probe, + .driver = { + .pm = &snd_fm801_pm, + }, +}; + +module_pci_driver(fm801_driver); diff --git a/sound/pci/ice1712/Makefile b/sound/pci/ice1712/Makefile new file mode 100644 index 000000000..f406a0483 --- /dev/null +++ b/sound/pci/ice1712/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-ice17xx-ak4xxx-y := ak4xxx.o +snd-ice1712-y := ice1712.o delta.o hoontech.o ews.o +snd-ice1724-y := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o quartet.o psc724.o wm8766.o wm8776.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o snd-ice17xx-ak4xxx.o +obj-$(CONFIG_SND_ICE1724) += snd-ice1724.o snd-ice17xx-ak4xxx.o diff --git a/sound/pci/ice1712/ak4xxx.c b/sound/pci/ice1712/ak4xxx.c new file mode 100644 index 000000000..bcafa16ce --- /dev/null +++ b/sound/pci/ice1712/ak4xxx.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * AK4524 / AK4528 / AK4529 / AK4355 / AK4381 interface + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ice1712.h" + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("ICEnsemble ICE17xx <-> AK4xxx AD/DA chip interface"); +MODULE_LICENSE("GPL"); + +static void snd_ice1712_akm4xxx_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + + snd_ice1712_save_gpio_status(ice); +} + +static void snd_ice1712_akm4xxx_unlock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + + snd_ice1712_restore_gpio_status(ice); +} + +/* + * write AK4xxx register + */ +static void snd_ice1712_akm4xxx_write(struct snd_akm4xxx *ak, int chip, + unsigned char addr, unsigned char data) +{ + unsigned int tmp; + int idx; + unsigned int addrdata; + struct snd_ak4xxx_private *priv = (void *)ak->private_value[0]; + struct snd_ice1712 *ice = ak->private_data[0]; + + if (snd_BUG_ON(chip < 0 || chip >= 4)) + return; + + tmp = snd_ice1712_gpio_read(ice); + tmp |= priv->add_flags; + tmp &= ~priv->mask_flags; + if (priv->cs_mask == priv->cs_addr) { + if (priv->cif) { + tmp |= priv->cs_mask; /* start without chip select */ + } else { + tmp &= ~priv->cs_mask; /* chip select low */ + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + } else { + /* doesn't handle cf=1 yet */ + tmp &= ~priv->cs_mask; + tmp |= priv->cs_addr; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + + /* build I2C address + data byte */ + addrdata = (priv->caddr << 6) | 0x20 | (addr & 0x1f); + addrdata = (addrdata << 8) | data; + for (idx = 15; idx >= 0; idx--) { + /* drop clock */ + tmp &= ~priv->clk_mask; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + /* set data */ + if (addrdata & (1 << idx)) + tmp |= priv->data_mask; + else + tmp &= ~priv->data_mask; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + /* raise clock */ + tmp |= priv->clk_mask; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + + if (priv->cs_mask == priv->cs_addr) { + if (priv->cif) { + /* assert a cs pulse to trigger */ + tmp &= ~priv->cs_mask; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + tmp |= priv->cs_mask; /* chip select high to trigger */ + } else { + tmp &= ~priv->cs_mask; + tmp |= priv->cs_none; /* deselect address */ + } + snd_ice1712_gpio_write(ice, tmp); + udelay(1); +} + +/* + * initialize the struct snd_akm4xxx record with the template + */ +int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, const struct snd_akm4xxx *temp, + const struct snd_ak4xxx_private *_priv, struct snd_ice1712 *ice) +{ + struct snd_ak4xxx_private *priv; + + if (_priv != NULL) { + priv = kmalloc_obj(*priv); + if (priv == NULL) + return -ENOMEM; + *priv = *_priv; + } else { + priv = NULL; + } + *ak = *temp; + ak->card = ice->card; + ak->private_value[0] = (unsigned long)priv; + ak->private_data[0] = ice; + if (ak->ops.lock == NULL) + ak->ops.lock = snd_ice1712_akm4xxx_lock; + if (ak->ops.unlock == NULL) + ak->ops.unlock = snd_ice1712_akm4xxx_unlock; + if (ak->ops.write == NULL) + ak->ops.write = snd_ice1712_akm4xxx_write; + snd_akm4xxx_init(ak); + return 0; +} + +void snd_ice1712_akm4xxx_free(struct snd_ice1712 *ice) +{ + unsigned int akidx; + if (ice->akm == NULL) + return; + for (akidx = 0; akidx < ice->akm_codecs; akidx++) { + struct snd_akm4xxx *ak = &ice->akm[akidx]; + kfree((void*)ak->private_value[0]); + } + kfree(ice->akm); +} + +/* + * build AK4xxx controls + */ +int snd_ice1712_akm4xxx_build_controls(struct snd_ice1712 *ice) +{ + unsigned int akidx; + int err; + + for (akidx = 0; akidx < ice->akm_codecs; akidx++) { + struct snd_akm4xxx *ak = &ice->akm[akidx]; + err = snd_akm4xxx_build_controls(ak); + if (err < 0) + return err; + } + return 0; +} + +EXPORT_SYMBOL(snd_ice1712_akm4xxx_init); +EXPORT_SYMBOL(snd_ice1712_akm4xxx_free); +EXPORT_SYMBOL(snd_ice1712_akm4xxx_build_controls); diff --git a/sound/pci/ice1712/amp.c b/sound/pci/ice1712/amp.c new file mode 100644 index 000000000..a7b496de6 --- /dev/null +++ b/sound/pci/ice1712/amp.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Advanced Micro Peripherals Ltd AUDIO2000 + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "amp.h" + +static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + unsigned short cval; + cval = (reg << 9) | val; + snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff); +} + +static int snd_vt1724_amp_init(struct snd_ice1712 *ice) +{ + static const unsigned short wm_inits[] = { + WM_ATTEN_L, 0x0000, /* 0 db */ + WM_ATTEN_R, 0x0000, /* 0 db */ + WM_DAC_CTRL, 0x0008, /* 24bit I2S */ + WM_INT_CTRL, 0x0001, /* 24bit I2S */ + }; + + unsigned int i; + + /* only use basic functionality for now */ + + /* VT1616 6ch codec connected to PSDOUT0 using packed mode */ + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + + /* Chaintech AV-710 has another WM8728 codec connected to PSDOUT4 + (shared with the SPDIF output). Mixer control for this codec + is not yet supported. */ + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AV710) { + for (i = 0; i < ARRAY_SIZE(wm_inits); i += 2) + wm_put(ice, wm_inits[i], wm_inits[i+1]); + } + + return 0; +} + +static int snd_vt1724_amp_add_controls(struct snd_ice1712 *ice) +{ + if (ice->ac97) + /* we use pins 39 and 41 of the VT1616 for left and right + read outputs */ + snd_ac97_write_cache(ice->ac97, 0x5a, + snd_ac97_read(ice->ac97, 0x5a) & ~0x8000); + return 0; +} + + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_amp_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_AV710, + .name = "Chaintech AV-710", + .model = "av710", + .chip_init = snd_vt1724_amp_init, + .build_controls = snd_vt1724_amp_add_controls, + }, + { + .subvendor = VT1724_SUBDEVICE_AUDIO2000, + .name = "AMP Ltd AUDIO2000", + .model = "amp2000", + .chip_init = snd_vt1724_amp_init, + .build_controls = snd_vt1724_amp_add_controls, + }, + { } /* terminator */ +}; + diff --git a/sound/pci/ice1712/amp.h b/sound/pci/ice1712/amp.h new file mode 100644 index 000000000..bd6323fe3 --- /dev/null +++ b/sound/pci/ice1712/amp.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_AMP_H +#define __SOUND_AMP_H + +/* + * ALSA driver for VIA VT1724 (Envy24HT) + * + * Lowlevel functions for Advanced Micro Peripherals Ltd AUDIO2000 + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +#define AMP_AUDIO2000_DEVICE_DESC "{AMP Ltd,AUDIO2000},"\ + "{Chaintech,AV-710}," + +#if 0 +#define VT1724_SUBDEVICE_AUDIO2000 0x12142417 /* Advanced Micro Peripherals Ltd AUDIO2000 */ +#else +#define VT1724_SUBDEVICE_AUDIO2000 0x00030003 /* a dummy ID for AMP Audio2000 */ +#endif +#define VT1724_SUBDEVICE_AV710 0x12142417 /* AV710 - the same ID with Audio2000! */ + +/* WM8728 on I2C for AV710 */ +#define WM_DEV 0x36 + +#define WM_ATTEN_L 0x00 +#define WM_ATTEN_R 0x01 +#define WM_DAC_CTRL 0x02 +#define WM_INT_CTRL 0x03 + +extern struct snd_ice1712_card_info snd_vt1724_amp_cards[]; + + +#endif /* __SOUND_AMP_H */ diff --git a/sound/pci/ice1712/aureon.c b/sound/pci/ice1712/aureon.c new file mode 100644 index 000000000..d6abff297 --- /dev/null +++ b/sound/pci/ice1712/aureon.c @@ -0,0 +1,2257 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Terratec Aureon cards + * + * Copyright (c) 2003 Takashi Iwai + * + * NOTES: + * + * - we reuse the struct snd_akm4xxx record for storing the wm8770 codec data. + * both wm and akm codecs are pretty similar, so we can integrate + * both controls in the future, once if wm codecs are reused in + * many boards. + * + * - DAC digital volumes are not implemented in the mixer. + * if they show better response than DAC analog volumes, we can use them + * instead. + * + * Lowlevel functions for AudioTrak Prodigy 7.1 (and possibly 192) cards + * Copyright (c) 2003 Dimitromanolakis Apostolos + * + * version 0.82: Stable / not all features work yet (no communication with AC97 secondary) + * added 64x/128x oversampling switch (should be 64x only for 96khz) + * fixed some recording labels (still need to check the rest) + * recording is working probably thanks to correct wm8770 initialization + * + * version 0.5: Initial release: + * working: analog output, mixer, headphone amplifier switch + * not working: prety much everything else, at least i could verify that + * we have no digital output, no capture, pretty bad clicks and poops + * on mixer switch and other coll stuff. + */ + +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "aureon.h" +#include + +/* AC97 register cache for Aureon */ +struct aureon_spec { + unsigned short stac9744[64]; + unsigned int cs8415_mux; + unsigned short master[2]; + unsigned short vol[8]; + unsigned char pca9554_out; +}; + +/* WM8770 registers */ +#define WM_DAC_ATTEN 0x00 /* DAC1-8 analog attenuation */ +#define WM_DAC_MASTER_ATTEN 0x08 /* DAC master analog attenuation */ +#define WM_DAC_DIG_ATTEN 0x09 /* DAC1-8 digital attenuation */ +#define WM_DAC_DIG_MASTER_ATTEN 0x11 /* DAC master digital attenuation */ +#define WM_PHASE_SWAP 0x12 /* DAC phase */ +#define WM_DAC_CTRL1 0x13 /* DAC control bits */ +#define WM_MUTE 0x14 /* mute controls */ +#define WM_DAC_CTRL2 0x15 /* de-emphasis and zefo-flag */ +#define WM_INT_CTRL 0x16 /* interface control */ +#define WM_MASTER 0x17 /* master clock and mode */ +#define WM_POWERDOWN 0x18 /* power-down controls */ +#define WM_ADC_GAIN 0x19 /* ADC gain L(19)/R(1a) */ +#define WM_ADC_MUX 0x1b /* input MUX */ +#define WM_OUT_MUX1 0x1c /* output MUX */ +#define WM_OUT_MUX2 0x1e /* output MUX */ +#define WM_RESET 0x1f /* software reset */ + +/* CS8415A registers */ +#define CS8415_CTRL1 0x01 +#define CS8415_CTRL2 0x02 +#define CS8415_QSUB 0x14 +#define CS8415_RATIO 0x1E +#define CS8415_C_BUFFER 0x20 +#define CS8415_ID 0x7F + +/* PCA9554 registers */ +#define PCA9554_DEV 0x40 /* I2C device address */ +#define PCA9554_IN 0x00 /* input port */ +#define PCA9554_OUT 0x01 /* output port */ +#define PCA9554_INVERT 0x02 /* input invert */ +#define PCA9554_DIR 0x03 /* port directions */ + +/* + * Aureon Universe additional controls using PCA9554 + */ + +/* + * Send data to pca9554 + */ +static void aureon_pca9554_write(struct snd_ice1712 *ice, unsigned char reg, + unsigned char data) +{ + unsigned int tmp; + int i, j; + unsigned char dev = PCA9554_DEV; /* ID 0100000, write */ + unsigned char val = 0; + + tmp = snd_ice1712_gpio_read(ice); + + snd_ice1712_gpio_set_mask(ice, ~(AUREON_SPI_MOSI|AUREON_SPI_CLK| + AUREON_WM_RW|AUREON_WM_CS| + AUREON_CS8415_CS)); + tmp |= AUREON_WM_RW; + tmp |= AUREON_CS8415_CS | AUREON_WM_CS; /* disable SPI devices */ + + tmp &= ~AUREON_SPI_MOSI; + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + + /* + * send i2c stop condition and start condition + * to obtain sane state + */ + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + tmp |= AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(100); + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(100); + /* + * send device address, command and value, + * skipping ack cycles in between + */ + for (j = 0; j < 3; j++) { + switch (j) { + case 0: + val = dev; + break; + case 1: + val = reg; + break; + case 2: + val = data; + break; + } + for (i = 7; i >= 0; i--) { + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + if (val & (1 << i)) + tmp |= AUREON_SPI_MOSI; + else + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + } + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + } + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + tmp |= AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(100); +} + +static int aureon_universe_inmux_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = + {"Internal Aux", "Wavetable", "Rear Line-In"}; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int aureon_universe_inmux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + ucontrol->value.enumerated.item[0] = spec->pca9554_out; + return 0; +} + +static int aureon_universe_inmux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + unsigned char oval, nval; + int change; + + nval = ucontrol->value.enumerated.item[0]; + if (nval >= 3) + return -EINVAL; + snd_ice1712_save_gpio_status(ice); + oval = spec->pca9554_out; + change = (oval != nval); + if (change) { + aureon_pca9554_write(ice, PCA9554_OUT, nval); + spec->pca9554_out = nval; + } + snd_ice1712_restore_gpio_status(ice); + return change; +} + + +static void aureon_ac97_write(struct snd_ice1712 *ice, unsigned short reg, + unsigned short val) +{ + struct aureon_spec *spec = ice->spec; + unsigned int tmp; + + /* Send address to XILINX chip */ + tmp = (snd_ice1712_gpio_read(ice) & ~0xFF) | (reg & 0x7F); + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp |= AUREON_AC97_ADDR; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp &= ~AUREON_AC97_ADDR; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + + /* Send low-order byte to XILINX chip */ + tmp &= ~AUREON_AC97_DATA_MASK; + tmp |= val & AUREON_AC97_DATA_MASK; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp |= AUREON_AC97_DATA_LOW; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp &= ~AUREON_AC97_DATA_LOW; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + + /* Send high-order byte to XILINX chip */ + tmp &= ~AUREON_AC97_DATA_MASK; + tmp |= (val >> 8) & AUREON_AC97_DATA_MASK; + + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp |= AUREON_AC97_DATA_HIGH; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp &= ~AUREON_AC97_DATA_HIGH; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + + /* Instruct XILINX chip to parse the data to the STAC9744 chip */ + tmp |= AUREON_AC97_COMMIT; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp &= ~AUREON_AC97_COMMIT; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + + /* Store the data in out private buffer */ + spec->stac9744[(reg & 0x7F) >> 1] = val; +} + +static unsigned short aureon_ac97_read(struct snd_ice1712 *ice, unsigned short reg) +{ + struct aureon_spec *spec = ice->spec; + return spec->stac9744[(reg & 0x7F) >> 1]; +} + +/* + * Initialize STAC9744 chip + */ +static int aureon_ac97_init(struct snd_ice1712 *ice) +{ + struct aureon_spec *spec = ice->spec; + int i; + static const unsigned short ac97_defaults[] = { + 0x00, 0x9640, + 0x02, 0x8000, + 0x04, 0x8000, + 0x06, 0x8000, + 0x0C, 0x8008, + 0x0E, 0x8008, + 0x10, 0x8808, + 0x12, 0x8808, + 0x14, 0x8808, + 0x16, 0x8808, + 0x18, 0x8808, + 0x1C, 0x8000, + 0x26, 0x000F, + 0x28, 0x0201, + 0x2C, 0xBB80, + 0x32, 0xBB80, + 0x7C, 0x8384, + 0x7E, 0x7644, + (unsigned short)-1 + }; + unsigned int tmp; + + /* Cold reset */ + tmp = (snd_ice1712_gpio_read(ice) | AUREON_AC97_RESET) & ~AUREON_AC97_DATA_MASK; + snd_ice1712_gpio_write(ice, tmp); + udelay(3); + + tmp &= ~AUREON_AC97_RESET; + snd_ice1712_gpio_write(ice, tmp); + udelay(3); + + tmp |= AUREON_AC97_RESET; + snd_ice1712_gpio_write(ice, tmp); + udelay(3); + + memset(&spec->stac9744, 0, sizeof(spec->stac9744)); + for (i = 0; ac97_defaults[i] != (unsigned short)-1; i += 2) + spec->stac9744[(ac97_defaults[i]) >> 1] = ac97_defaults[i+1]; + + /* Unmute AC'97 master volume permanently - muting is done by WM8770 */ + aureon_ac97_write(ice, AC97_MASTER, 0x0000); + + return 0; +} + +#define AUREON_AC97_STEREO 0x80 + +/* + * AC'97 volume controls + */ +static int aureon_ac97_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = kcontrol->private_value & AUREON_AC97_STEREO ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 31; + return 0; +} + +static int aureon_ac97_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short vol; + + guard(mutex)(&ice->gpio_mutex); + + vol = aureon_ac97_read(ice, kcontrol->private_value & 0x7F); + ucontrol->value.integer.value[0] = 0x1F - (vol & 0x1F); + if (kcontrol->private_value & AUREON_AC97_STEREO) + ucontrol->value.integer.value[1] = 0x1F - ((vol >> 8) & 0x1F); + + return 0; +} + +static int aureon_ac97_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short ovol, nvol; + int change; + + snd_ice1712_save_gpio_status(ice); + + ovol = aureon_ac97_read(ice, kcontrol->private_value & 0x7F); + nvol = (0x1F - ucontrol->value.integer.value[0]) & 0x001F; + if (kcontrol->private_value & AUREON_AC97_STEREO) + nvol |= ((0x1F - ucontrol->value.integer.value[1]) << 8) & 0x1F00; + nvol |= ovol & ~0x1F1F; + + change = (ovol != nvol); + if (change) + aureon_ac97_write(ice, kcontrol->private_value & 0x7F, nvol); + + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* + * AC'97 mute controls + */ +#define aureon_ac97_mute_info snd_ctl_boolean_mono_info + +static int aureon_ac97_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + + ucontrol->value.integer.value[0] = aureon_ac97_read(ice, + kcontrol->private_value & 0x7F) & 0x8000 ? 0 : 1; + + return 0; +} + +static int aureon_ac97_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short ovol, nvol; + int change; + + snd_ice1712_save_gpio_status(ice); + + ovol = aureon_ac97_read(ice, kcontrol->private_value & 0x7F); + nvol = (ucontrol->value.integer.value[0] ? 0x0000 : 0x8000) | (ovol & ~0x8000); + + change = (ovol != nvol); + if (change) + aureon_ac97_write(ice, kcontrol->private_value & 0x7F, nvol); + + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* + * AC'97 mute controls + */ +#define aureon_ac97_micboost_info snd_ctl_boolean_mono_info + +static int aureon_ac97_micboost_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + + ucontrol->value.integer.value[0] = aureon_ac97_read(ice, AC97_MIC) & 0x0020 ? 0 : 1; + + return 0; +} + +static int aureon_ac97_micboost_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short ovol, nvol; + int change; + + snd_ice1712_save_gpio_status(ice); + + ovol = aureon_ac97_read(ice, AC97_MIC); + nvol = (ucontrol->value.integer.value[0] ? 0x0000 : 0x0020) | (ovol & ~0x0020); + + change = (ovol != nvol); + if (change) + aureon_ac97_write(ice, AC97_MIC, nvol); + + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* + * write data in the SPI mode + */ +static void aureon_spi_write(struct snd_ice1712 *ice, unsigned int cs, unsigned int data, int bits) +{ + unsigned int tmp; + int i; + unsigned int mosi, clk; + + tmp = snd_ice1712_gpio_read(ice); + + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71LT || + ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71XT) { + snd_ice1712_gpio_set_mask(ice, ~(PRODIGY_SPI_MOSI|PRODIGY_SPI_CLK|PRODIGY_WM_CS)); + mosi = PRODIGY_SPI_MOSI; + clk = PRODIGY_SPI_CLK; + } else { + snd_ice1712_gpio_set_mask(ice, ~(AUREON_WM_RW|AUREON_SPI_MOSI|AUREON_SPI_CLK| + AUREON_WM_CS|AUREON_CS8415_CS)); + mosi = AUREON_SPI_MOSI; + clk = AUREON_SPI_CLK; + + tmp |= AUREON_WM_RW; + } + + tmp &= ~cs; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + + for (i = bits - 1; i >= 0; i--) { + tmp &= ~clk; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + if (data & (1 << i)) + tmp |= mosi; + else + tmp &= ~mosi; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + tmp |= clk; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + + tmp &= ~clk; + tmp |= cs; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + tmp |= clk; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); +} + +/* + * Read data in SPI mode + */ +static void aureon_spi_read(struct snd_ice1712 *ice, unsigned int cs, + unsigned int data, int bits, unsigned char *buffer, int size) +{ + int i, j; + unsigned int tmp; + + tmp = (snd_ice1712_gpio_read(ice) & ~AUREON_SPI_CLK) | AUREON_CS8415_CS|AUREON_WM_CS; + snd_ice1712_gpio_write(ice, tmp); + tmp &= ~cs; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + + for (i = bits-1; i >= 0; i--) { + if (data & (1 << i)) + tmp |= AUREON_SPI_MOSI; + else + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + + for (j = 0; j < size; j++) { + unsigned char outdata = 0; + for (i = 7; i >= 0; i--) { + tmp = snd_ice1712_gpio_read(ice); + outdata <<= 1; + outdata |= (tmp & AUREON_SPI_MISO) ? 1 : 0; + udelay(1); + + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + buffer[j] = outdata; + } + + tmp |= cs; + snd_ice1712_gpio_write(ice, tmp); +} + +static unsigned char aureon_cs8415_get(struct snd_ice1712 *ice, int reg) +{ + unsigned char val; + aureon_spi_write(ice, AUREON_CS8415_CS, 0x2000 | reg, 16); + aureon_spi_read(ice, AUREON_CS8415_CS, 0x21, 8, &val, 1); + return val; +} + +static void aureon_cs8415_read(struct snd_ice1712 *ice, int reg, + unsigned char *buffer, int size) +{ + aureon_spi_write(ice, AUREON_CS8415_CS, 0x2000 | reg, 16); + aureon_spi_read(ice, AUREON_CS8415_CS, 0x21, 8, buffer, size); +} + +static void aureon_cs8415_put(struct snd_ice1712 *ice, int reg, + unsigned char val) +{ + aureon_spi_write(ice, AUREON_CS8415_CS, 0x200000 | (reg << 8) | val, 24); +} + +/* + * get the current register value of WM codec + */ +static unsigned short wm_get(struct snd_ice1712 *ice, int reg) +{ + reg <<= 1; + return ((unsigned short)ice->akm[0].images[reg] << 8) | + ice->akm[0].images[reg + 1]; +} + +/* + * set the register value of WM codec + */ +static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + aureon_spi_write(ice, + ((ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71LT || + ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71XT) ? + PRODIGY_WM_CS : AUREON_WM_CS), + (reg << 9) | (val & 0x1ff), 16); +} + +/* + * set the register value of WM codec and remember it + */ +static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + wm_put_nocache(ice, reg, val); + reg <<= 1; + ice->akm[0].images[reg] = val >> 8; + ice->akm[0].images[reg + 1] = val; +} + +/* + */ +#define aureon_mono_bool_info snd_ctl_boolean_mono_info + +/* + * AC'97 master playback mute controls (Mute on WM8770 chip) + */ +#define aureon_ac97_mmute_info snd_ctl_boolean_mono_info + +static int aureon_ac97_mmute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + + ucontrol->value.integer.value[0] = (wm_get(ice, WM_OUT_MUX1) >> 1) & 0x01; + + return 0; +} + +static int aureon_ac97_mmute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short ovol, nvol; + int change; + + snd_ice1712_save_gpio_status(ice); + + ovol = wm_get(ice, WM_OUT_MUX1); + nvol = (ovol & ~0x02) | (ucontrol->value.integer.value[0] ? 0x02 : 0x00); + change = (ovol != nvol); + if (change) + wm_put(ice, WM_OUT_MUX1, nvol); + + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_wm_dac, -10000, 100, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_wm_pcm, -6400, 50, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_wm_adc, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_ac97_master, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_ac97_gain, -3450, 150, 0); + +#define WM_VOL_MAX 100 +#define WM_VOL_CNT 101 /* 0dB .. -100dB */ +#define WM_VOL_MUTE 0x8000 + +static void wm_set_vol(struct snd_ice1712 *ice, unsigned int index, unsigned short vol, unsigned short master) +{ + unsigned char nvol; + + if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE)) { + nvol = 0; + } else { + nvol = ((vol % WM_VOL_CNT) * (master % WM_VOL_CNT)) / + WM_VOL_MAX; + nvol += 0x1b; + } + + wm_put(ice, index, nvol); + wm_put_nocache(ice, index, 0x180 | nvol); +} + +/* + * DAC mute control + */ +#define wm_pcm_mute_info snd_ctl_boolean_mono_info + +static int wm_pcm_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.integer.value[0] = (wm_get(ice, WM_MUTE) & 0x10) ? 0 : 1; + return 0; +} + +static int wm_pcm_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short nval, oval; + int change; + + snd_ice1712_save_gpio_status(ice); + oval = wm_get(ice, WM_MUTE); + nval = (oval & ~0x10) | (ucontrol->value.integer.value[0] ? 0 : 0x10); + change = (oval != nval); + if (change) + wm_put(ice, WM_MUTE, nval); + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* + * Master volume attenuation mixer control + */ +static int wm_master_vol_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 = WM_VOL_MAX; + return 0; +} + +static int wm_master_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + int i; + for (i = 0; i < 2; i++) + ucontrol->value.integer.value[i] = + spec->master[i] & ~WM_VOL_MUTE; + return 0; +} + +static int wm_master_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + int ch, change = 0; + + snd_ice1712_save_gpio_status(ice); + for (ch = 0; ch < 2; ch++) { + unsigned int vol = ucontrol->value.integer.value[ch]; + if (vol > WM_VOL_MAX) + vol = WM_VOL_MAX; + vol |= spec->master[ch] & WM_VOL_MUTE; + if (vol != spec->master[ch]) { + int dac; + spec->master[ch] = vol; + for (dac = 0; dac < ice->num_total_dacs; dac += 2) + wm_set_vol(ice, WM_DAC_ATTEN + dac + ch, + spec->vol[dac + ch], + spec->master[ch]); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + return change; +} + +/* + * DAC volume attenuation mixer control + */ +static int wm_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int voices = kcontrol->private_value >> 8; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = voices; + uinfo->value.integer.min = 0; /* mute (-101dB) */ + uinfo->value.integer.max = WM_VOL_MAX; /* 0dB */ + return 0; +} + +static int wm_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + int i, ofs, voices; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xff; + for (i = 0; i < voices; i++) + ucontrol->value.integer.value[i] = + spec->vol[ofs+i] & ~WM_VOL_MUTE; + return 0; +} + +static int wm_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + int i, idx, ofs, voices; + int change = 0; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xff; + snd_ice1712_save_gpio_status(ice); + for (i = 0; i < voices; i++) { + unsigned int vol = ucontrol->value.integer.value[i]; + if (vol > WM_VOL_MAX) + vol = WM_VOL_MAX; + vol |= spec->vol[ofs+i] & WM_VOL_MUTE; + if (vol != spec->vol[ofs+i]) { + spec->vol[ofs+i] = vol; + idx = WM_DAC_ATTEN + ofs + i; + wm_set_vol(ice, idx, spec->vol[ofs + i], + spec->master[i]); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + return change; +} + +/* + * WM8770 mute control + */ +static int wm_mute_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = kcontrol->private_value >> 8; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int wm_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + int voices, ofs, i; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xFF; + + for (i = 0; i < voices; i++) + ucontrol->value.integer.value[i] = + (spec->vol[ofs + i] & WM_VOL_MUTE) ? 0 : 1; + return 0; +} + +static int wm_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + int change = 0, voices, ofs, i; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xFF; + + snd_ice1712_save_gpio_status(ice); + for (i = 0; i < voices; i++) { + int val = (spec->vol[ofs + i] & WM_VOL_MUTE) ? 0 : 1; + if (ucontrol->value.integer.value[i] != val) { + spec->vol[ofs + i] &= ~WM_VOL_MUTE; + spec->vol[ofs + i] |= + ucontrol->value.integer.value[i] ? 0 : WM_VOL_MUTE; + wm_set_vol(ice, ofs + i, spec->vol[ofs + i], + spec->master[i]); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* + * WM8770 master mute control + */ +#define wm_master_mute_info snd_ctl_boolean_stereo_info + +static int wm_master_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + + ucontrol->value.integer.value[0] = + (spec->master[0] & WM_VOL_MUTE) ? 0 : 1; + ucontrol->value.integer.value[1] = + (spec->master[1] & WM_VOL_MUTE) ? 0 : 1; + return 0; +} + +static int wm_master_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + int change = 0, i; + + snd_ice1712_save_gpio_status(ice); + for (i = 0; i < 2; i++) { + int val = (spec->master[i] & WM_VOL_MUTE) ? 0 : 1; + if (ucontrol->value.integer.value[i] != val) { + int dac; + spec->master[i] &= ~WM_VOL_MUTE; + spec->master[i] |= + ucontrol->value.integer.value[i] ? 0 : WM_VOL_MUTE; + for (dac = 0; dac < ice->num_total_dacs; dac += 2) + wm_set_vol(ice, WM_DAC_ATTEN + dac + i, + spec->vol[dac + i], + spec->master[i]); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* digital master volume */ +#define PCM_0dB 0xff +#define PCM_RES 128 /* -64dB */ +#define PCM_MIN (PCM_0dB - PCM_RES) +static int wm_pcm_vol_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; /* mute (-64dB) */ + uinfo->value.integer.max = PCM_RES; /* 0dB */ + return 0; +} + +static int wm_pcm_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val; + + guard(mutex)(&ice->gpio_mutex); + val = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff; + val = val > PCM_MIN ? (val - PCM_MIN) : 0; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int wm_pcm_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short ovol, nvol; + int change = 0; + + nvol = ucontrol->value.integer.value[0]; + if (nvol > PCM_RES) + return -EINVAL; + snd_ice1712_save_gpio_status(ice); + nvol = (nvol ? (nvol + PCM_MIN) : 0) & 0xff; + ovol = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff; + if (ovol != nvol) { + wm_put(ice, WM_DAC_DIG_MASTER_ATTEN, nvol); /* prelatch */ + wm_put_nocache(ice, WM_DAC_DIG_MASTER_ATTEN, nvol | 0x100); /* update */ + change = 1; + } + snd_ice1712_restore_gpio_status(ice); + return change; +} + +/* + * ADC mute control + */ +#define wm_adc_mute_info snd_ctl_boolean_stereo_info + +static int wm_adc_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val; + int i; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + val = wm_get(ice, WM_ADC_GAIN + i); + ucontrol->value.integer.value[i] = ~val>>5 & 0x1; + } + return 0; +} + +static int wm_adc_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short new, old; + int i, change = 0; + + snd_ice1712_save_gpio_status(ice); + for (i = 0; i < 2; i++) { + old = wm_get(ice, WM_ADC_GAIN + i); + new = (~ucontrol->value.integer.value[i]<<5&0x20) | (old&~0x20); + if (new != old) { + wm_put(ice, WM_ADC_GAIN + i, new); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* + * ADC gain mixer control + */ +static int wm_adc_vol_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; /* -12dB */ + uinfo->value.integer.max = 0x1f; /* 19dB */ + return 0; +} + +static int wm_adc_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int i, idx; + unsigned short vol; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + idx = WM_ADC_GAIN + i; + vol = wm_get(ice, idx) & 0x1f; + ucontrol->value.integer.value[i] = vol; + } + return 0; +} + +static int wm_adc_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int i, idx; + unsigned short ovol, nvol; + int change = 0; + + snd_ice1712_save_gpio_status(ice); + for (i = 0; i < 2; i++) { + idx = WM_ADC_GAIN + i; + nvol = ucontrol->value.integer.value[i] & 0x1f; + ovol = wm_get(ice, idx); + if ((ovol & 0x1f) != nvol) { + wm_put(ice, idx, nvol | (ovol & ~0x1f)); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + return change; +} + +/* + * ADC input mux mixer control + */ +static int wm_adc_mux_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "CD", /* AIN1 */ + "Aux", /* AIN2 */ + "Line", /* AIN3 */ + "Mic", /* AIN4 */ + "AC97" /* AIN5 */ + }; + static const char * const universe_texts[] = { + "Aux1", /* AIN1 */ + "CD", /* AIN2 */ + "Phono", /* AIN3 */ + "Line", /* AIN4 */ + "Aux2", /* AIN5 */ + "Mic", /* AIN6 */ + "Aux3", /* AIN7 */ + "AC97" /* AIN8 */ + }; + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON71_UNIVERSE) + return snd_ctl_enum_info(uinfo, 2, 8, universe_texts); + else + return snd_ctl_enum_info(uinfo, 2, 5, texts); +} + +static int wm_adc_mux_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val; + + guard(mutex)(&ice->gpio_mutex); + val = wm_get(ice, WM_ADC_MUX); + ucontrol->value.enumerated.item[0] = val & 7; + ucontrol->value.enumerated.item[1] = (val >> 4) & 7; + return 0; +} + +static int wm_adc_mux_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short oval, nval; + int change; + + snd_ice1712_save_gpio_status(ice); + oval = wm_get(ice, WM_ADC_MUX); + nval = oval & ~0x77; + nval |= ucontrol->value.enumerated.item[0] & 7; + nval |= (ucontrol->value.enumerated.item[1] & 7) << 4; + change = (oval != nval); + if (change) + wm_put(ice, WM_ADC_MUX, nval); + snd_ice1712_restore_gpio_status(ice); + return change; +} + +/* + * CS8415 Input mux + */ +static int aureon_cs8415_mux_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + static const char * const aureon_texts[] = { + "CD", /* RXP0 */ + "Optical" /* RXP1 */ + }; + static const char * const prodigy_texts[] = { + "CD", + "Coax" + }; + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71) + return snd_ctl_enum_info(uinfo, 1, 2, prodigy_texts); + else + return snd_ctl_enum_info(uinfo, 1, 2, aureon_texts); +} + +static int aureon_cs8415_mux_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + + /* snd_ice1712_save_gpio_status(ice); */ + /* val = aureon_cs8415_get(ice, CS8415_CTRL2); */ + ucontrol->value.enumerated.item[0] = spec->cs8415_mux; + /* snd_ice1712_restore_gpio_status(ice); */ + return 0; +} + +static int aureon_cs8415_mux_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + unsigned short oval, nval; + int change; + + snd_ice1712_save_gpio_status(ice); + oval = aureon_cs8415_get(ice, CS8415_CTRL2); + nval = oval & ~0x07; + nval |= ucontrol->value.enumerated.item[0] & 7; + change = (oval != nval); + if (change) + aureon_cs8415_put(ice, CS8415_CTRL2, nval); + snd_ice1712_restore_gpio_status(ice); + spec->cs8415_mux = ucontrol->value.enumerated.item[0]; + return change; +} + +static int aureon_cs8415_rate_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 = 192000; + return 0; +} + +static int aureon_cs8415_rate_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char ratio; + ratio = aureon_cs8415_get(ice, CS8415_RATIO); + ucontrol->value.integer.value[0] = (int)((unsigned int)ratio * 750); + return 0; +} + +/* + * CS8415A Mute + */ +#define aureon_cs8415_mute_info snd_ctl_boolean_mono_info + +static int aureon_cs8415_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + snd_ice1712_save_gpio_status(ice); + ucontrol->value.integer.value[0] = (aureon_cs8415_get(ice, CS8415_CTRL1) & 0x20) ? 0 : 1; + snd_ice1712_restore_gpio_status(ice); + return 0; +} + +static int aureon_cs8415_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char oval, nval; + int change; + snd_ice1712_save_gpio_status(ice); + oval = aureon_cs8415_get(ice, CS8415_CTRL1); + if (ucontrol->value.integer.value[0]) + nval = oval & ~0x20; + else + nval = oval | 0x20; + change = (oval != nval); + if (change) + aureon_cs8415_put(ice, CS8415_CTRL1, nval); + snd_ice1712_restore_gpio_status(ice); + return change; +} + +/* + * CS8415A Q-Sub info + */ +static int aureon_cs8415_qsub_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = 10; + return 0; +} + +static int aureon_cs8415_qsub_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + snd_ice1712_save_gpio_status(ice); + aureon_cs8415_read(ice, CS8415_QSUB, ucontrol->value.bytes.data, 10); + snd_ice1712_restore_gpio_status(ice); + + return 0; +} + +static int aureon_cs8415_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int aureon_cs8415_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0xFF, 24); + return 0; +} + +static int aureon_cs8415_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + snd_ice1712_save_gpio_status(ice); + aureon_cs8415_read(ice, CS8415_C_BUFFER, ucontrol->value.iec958.status, 24); + snd_ice1712_restore_gpio_status(ice); + return 0; +} + +/* + * Headphone Amplifier + */ +static int aureon_set_headphone_amp(struct snd_ice1712 *ice, int enable) +{ + unsigned int tmp, tmp2; + + tmp2 = tmp = snd_ice1712_gpio_read(ice); + if (enable) + if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT && + ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) + tmp |= AUREON_HP_SEL; + else + tmp |= PRODIGY_HP_SEL; + else + if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT && + ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) + tmp &= ~AUREON_HP_SEL; + else + tmp &= ~PRODIGY_HP_SEL; + if (tmp != tmp2) { + snd_ice1712_gpio_write(ice, tmp); + return 1; + } + return 0; +} + +static int aureon_get_headphone_amp(struct snd_ice1712 *ice) +{ + unsigned int tmp = snd_ice1712_gpio_read(ice); + + return (tmp & AUREON_HP_SEL) != 0; +} + +#define aureon_hpamp_info snd_ctl_boolean_mono_info + +static int aureon_hpamp_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = aureon_get_headphone_amp(ice); + return 0; +} + + +static int aureon_hpamp_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + return aureon_set_headphone_amp(ice, ucontrol->value.integer.value[0]); +} + +/* + * Deemphasis + */ + +#define aureon_deemp_info snd_ctl_boolean_mono_info + +static int aureon_deemp_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL2) & 0xf) == 0xf; + return 0; +} + +static int aureon_deemp_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int temp, temp2; + temp2 = temp = wm_get(ice, WM_DAC_CTRL2); + if (ucontrol->value.integer.value[0]) + temp |= 0xf; + else + temp &= ~0xf; + if (temp != temp2) { + wm_put(ice, WM_DAC_CTRL2, temp); + return 1; + } + return 0; +} + +/* + * ADC Oversampling + */ +static int aureon_oversampling_info(struct snd_kcontrol *k, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "128x", "64x" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int aureon_oversampling_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = (wm_get(ice, WM_MASTER) & 0x8) == 0x8; + return 0; +} + +static int aureon_oversampling_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + int temp, temp2; + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + temp2 = temp = wm_get(ice, WM_MASTER); + + if (ucontrol->value.enumerated.item[0]) + temp |= 0x8; + else + temp &= ~0x8; + + if (temp != temp2) { + wm_put(ice, WM_MASTER, temp); + return 1; + } + return 0; +} + +/* + * mixers + */ + +static const struct snd_kcontrol_new aureon_dac_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = wm_master_mute_info, + .get = wm_master_mute_get, + .put = wm_master_mute_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Master Playback Volume", + .info = wm_master_vol_info, + .get = wm_master_vol_get, + .put = wm_master_vol_put, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Front Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (2 << 8) | 0 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Front Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (2 << 8) | 0, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Rear Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (2 << 8) | 2 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Rear Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (2 << 8) | 2, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Center Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (1 << 8) | 4 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Center Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (1 << 8) | 4, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "LFE Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (1 << 8) | 5 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "LFE Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (1 << 8) | 5, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Side Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (2 << 8) | 6 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Side Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (2 << 8) | 6, + .tlv = { .p = db_scale_wm_dac } + } +}; + +static const struct snd_kcontrol_new wm_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = wm_pcm_mute_info, + .get = wm_pcm_mute_get, + .put = wm_pcm_mute_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "PCM Playback Volume", + .info = wm_pcm_vol_info, + .get = wm_pcm_vol_get, + .put = wm_pcm_vol_put, + .tlv = { .p = db_scale_wm_pcm } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Switch", + .info = wm_adc_mute_info, + .get = wm_adc_mute_get, + .put = wm_adc_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Capture Volume", + .info = wm_adc_vol_info, + .get = wm_adc_vol_get, + .put = wm_adc_vol_put, + .tlv = { .p = db_scale_wm_adc } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = wm_adc_mux_info, + .get = wm_adc_mux_get, + .put = wm_adc_mux_put, + .private_value = 5 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "External Amplifier", + .info = aureon_hpamp_info, + .get = aureon_hpamp_get, + .put = aureon_hpamp_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Deemphasis Switch", + .info = aureon_deemp_info, + .get = aureon_deemp_get, + .put = aureon_deemp_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Oversampling", + .info = aureon_oversampling_info, + .get = aureon_oversampling_get, + .put = aureon_oversampling_put + } +}; + +static const struct snd_kcontrol_new ac97_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "AC97 Playback Switch", + .info = aureon_ac97_mmute_info, + .get = aureon_ac97_mmute_get, + .put = aureon_ac97_mmute_put, + .private_value = AC97_MASTER + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "AC97 Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_MASTER|AUREON_AC97_STEREO, + .tlv = { .p = db_scale_ac97_master } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "CD Playback Switch", + .info = aureon_ac97_mute_info, + .get = aureon_ac97_mute_get, + .put = aureon_ac97_mute_put, + .private_value = AC97_CD + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "CD Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_CD|AUREON_AC97_STEREO, + .tlv = { .p = db_scale_ac97_gain } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Aux Playback Switch", + .info = aureon_ac97_mute_info, + .get = aureon_ac97_mute_get, + .put = aureon_ac97_mute_put, + .private_value = AC97_AUX, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Aux Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_AUX|AUREON_AC97_STEREO, + .tlv = { .p = db_scale_ac97_gain } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Playback Switch", + .info = aureon_ac97_mute_info, + .get = aureon_ac97_mute_get, + .put = aureon_ac97_mute_put, + .private_value = AC97_LINE + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Line Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_LINE|AUREON_AC97_STEREO, + .tlv = { .p = db_scale_ac97_gain } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Playback Switch", + .info = aureon_ac97_mute_info, + .get = aureon_ac97_mute_get, + .put = aureon_ac97_mute_put, + .private_value = AC97_MIC + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Mic Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_MIC, + .tlv = { .p = db_scale_ac97_gain } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Boost (+20dB)", + .info = aureon_ac97_micboost_info, + .get = aureon_ac97_micboost_get, + .put = aureon_ac97_micboost_put + } +}; + +static const struct snd_kcontrol_new universe_ac97_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "AC97 Playback Switch", + .info = aureon_ac97_mmute_info, + .get = aureon_ac97_mmute_get, + .put = aureon_ac97_mmute_put, + .private_value = AC97_MASTER + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "AC97 Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_MASTER|AUREON_AC97_STEREO, + .tlv = { .p = db_scale_ac97_master } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "CD Playback Switch", + .info = aureon_ac97_mute_info, + .get = aureon_ac97_mute_get, + .put = aureon_ac97_mute_put, + .private_value = AC97_AUX + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "CD Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_AUX|AUREON_AC97_STEREO, + .tlv = { .p = db_scale_ac97_gain } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Phono Playback Switch", + .info = aureon_ac97_mute_info, + .get = aureon_ac97_mute_get, + .put = aureon_ac97_mute_put, + .private_value = AC97_CD + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Phono Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_CD|AUREON_AC97_STEREO, + .tlv = { .p = db_scale_ac97_gain } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Playback Switch", + .info = aureon_ac97_mute_info, + .get = aureon_ac97_mute_get, + .put = aureon_ac97_mute_put, + .private_value = AC97_LINE + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Line Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_LINE|AUREON_AC97_STEREO, + .tlv = { .p = db_scale_ac97_gain } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Playback Switch", + .info = aureon_ac97_mute_info, + .get = aureon_ac97_mute_get, + .put = aureon_ac97_mute_put, + .private_value = AC97_MIC + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Mic Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_MIC, + .tlv = { .p = db_scale_ac97_gain } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Boost (+20dB)", + .info = aureon_ac97_micboost_info, + .get = aureon_ac97_micboost_get, + .put = aureon_ac97_micboost_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Aux Playback Switch", + .info = aureon_ac97_mute_info, + .get = aureon_ac97_mute_get, + .put = aureon_ac97_mute_put, + .private_value = AC97_VIDEO, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Aux Playback Volume", + .info = aureon_ac97_vol_info, + .get = aureon_ac97_vol_get, + .put = aureon_ac97_vol_put, + .private_value = AC97_VIDEO|AUREON_AC97_STEREO, + .tlv = { .p = db_scale_ac97_gain } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Aux Source", + .info = aureon_universe_inmux_info, + .get = aureon_universe_inmux_get, + .put = aureon_universe_inmux_put + } + +}; + +static const struct snd_kcontrol_new cs8415_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, SWITCH), + .info = aureon_cs8415_mute_info, + .get = aureon_cs8415_mute_get, + .put = aureon_cs8415_mute_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Source", + .info = aureon_cs8415_mux_info, + .get = aureon_cs8415_mux_get, + .put = aureon_cs8415_mux_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("Q-subcode ", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = aureon_cs8415_qsub_info, + .get = aureon_cs8415_qsub_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = aureon_cs8415_spdif_info, + .get = aureon_cs8415_mask_get + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = aureon_cs8415_spdif_info, + .get = aureon_cs8415_spdif_get + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = aureon_cs8415_rate_info, + .get = aureon_cs8415_rate_get + } +}; + +static int aureon_add_controls(struct snd_ice1712 *ice) +{ + unsigned int i, counts; + int err; + + counts = ARRAY_SIZE(aureon_dac_controls); + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY) + counts -= 2; /* no side */ + for (i = 0; i < counts; i++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&aureon_dac_controls[i], ice)); + if (err < 0) + return err; + } + + for (i = 0; i < ARRAY_SIZE(wm_controls); i++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&wm_controls[i], ice)); + if (err < 0) + return err; + } + + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON71_UNIVERSE) { + for (i = 0; i < ARRAY_SIZE(universe_ac97_controls); i++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&universe_ac97_controls[i], ice)); + if (err < 0) + return err; + } + } else if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT && + ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) { + for (i = 0; i < ARRAY_SIZE(ac97_controls); i++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&ac97_controls[i], ice)); + if (err < 0) + return err; + } + } + + if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT && + ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) { + unsigned char id; + snd_ice1712_save_gpio_status(ice); + id = aureon_cs8415_get(ice, CS8415_ID); + snd_ice1712_restore_gpio_status(ice); + if (id != 0x41) + dev_info(ice->card->dev, + "No CS8415 chip. Skipping CS8415 controls.\n"); + else { + for (i = 0; i < ARRAY_SIZE(cs8415_controls); i++) { + struct snd_kcontrol *kctl; + kctl = snd_ctl_new1(&cs8415_controls[i], ice); + if (!kctl) + return -ENOMEM; + if (i > 1) + kctl->id.device = ice->pcm->device; + err = snd_ctl_add(ice->card, kctl); + if (err < 0) + return err; + } + } + } + + return 0; +} + +/* + * reset the chip + */ +static int aureon_reset(struct snd_ice1712 *ice) +{ + static const unsigned short wm_inits_aureon[] = { + /* These come first to reduce init pop noise */ + 0x1b, 0x044, /* ADC Mux (AC'97 source) */ + 0x1c, 0x00B, /* Out Mux1 (VOUT1 = DAC+AUX, VOUT2 = DAC) */ + 0x1d, 0x009, /* Out Mux2 (VOUT2 = DAC, VOUT3 = DAC) */ + + 0x18, 0x000, /* All power-up */ + + 0x16, 0x122, /* I2S, normal polarity, 24bit */ + 0x17, 0x022, /* 256fs, slave mode */ + 0x00, 0, /* DAC1 analog mute */ + 0x01, 0, /* DAC2 analog mute */ + 0x02, 0, /* DAC3 analog mute */ + 0x03, 0, /* DAC4 analog mute */ + 0x04, 0, /* DAC5 analog mute */ + 0x05, 0, /* DAC6 analog mute */ + 0x06, 0, /* DAC7 analog mute */ + 0x07, 0, /* DAC8 analog mute */ + 0x08, 0x100, /* master analog mute */ + 0x09, 0xff, /* DAC1 digital full */ + 0x0a, 0xff, /* DAC2 digital full */ + 0x0b, 0xff, /* DAC3 digital full */ + 0x0c, 0xff, /* DAC4 digital full */ + 0x0d, 0xff, /* DAC5 digital full */ + 0x0e, 0xff, /* DAC6 digital full */ + 0x0f, 0xff, /* DAC7 digital full */ + 0x10, 0xff, /* DAC8 digital full */ + 0x11, 0x1ff, /* master digital full */ + 0x12, 0x000, /* phase normal */ + 0x13, 0x090, /* unmute DAC L/R */ + 0x14, 0x000, /* all unmute */ + 0x15, 0x000, /* no deemphasis, no ZFLG */ + 0x19, 0x000, /* -12dB ADC/L */ + 0x1a, 0x000, /* -12dB ADC/R */ + (unsigned short)-1 + }; + static const unsigned short wm_inits_prodigy[] = { + + /* These come first to reduce init pop noise */ + 0x1b, 0x000, /* ADC Mux */ + 0x1c, 0x009, /* Out Mux1 */ + 0x1d, 0x009, /* Out Mux2 */ + + 0x18, 0x000, /* All power-up */ + + 0x16, 0x022, /* I2S, normal polarity, 24bit, high-pass on */ + 0x17, 0x006, /* 128fs, slave mode */ + + 0x00, 0, /* DAC1 analog mute */ + 0x01, 0, /* DAC2 analog mute */ + 0x02, 0, /* DAC3 analog mute */ + 0x03, 0, /* DAC4 analog mute */ + 0x04, 0, /* DAC5 analog mute */ + 0x05, 0, /* DAC6 analog mute */ + 0x06, 0, /* DAC7 analog mute */ + 0x07, 0, /* DAC8 analog mute */ + 0x08, 0x100, /* master analog mute */ + + 0x09, 0x7f, /* DAC1 digital full */ + 0x0a, 0x7f, /* DAC2 digital full */ + 0x0b, 0x7f, /* DAC3 digital full */ + 0x0c, 0x7f, /* DAC4 digital full */ + 0x0d, 0x7f, /* DAC5 digital full */ + 0x0e, 0x7f, /* DAC6 digital full */ + 0x0f, 0x7f, /* DAC7 digital full */ + 0x10, 0x7f, /* DAC8 digital full */ + 0x11, 0x1FF, /* master digital full */ + + 0x12, 0x000, /* phase normal */ + 0x13, 0x090, /* unmute DAC L/R */ + 0x14, 0x000, /* all unmute */ + 0x15, 0x000, /* no deemphasis, no ZFLG */ + + 0x19, 0x000, /* -12dB ADC/L */ + 0x1a, 0x000, /* -12dB ADC/R */ + (unsigned short)-1 + + }; + static const unsigned short cs_inits[] = { + 0x0441, /* RUN */ + 0x0180, /* no mute, OMCK output on RMCK pin */ + 0x0201, /* S/PDIF source on RXP1 */ + 0x0605, /* slave, 24bit, MSB on second OSCLK, SDOUT for right channel when OLRCK is high */ + (unsigned short)-1 + }; + unsigned int tmp; + const unsigned short *p; + int err; + struct aureon_spec *spec = ice->spec; + + err = aureon_ac97_init(ice); + if (err != 0) + return err; + + snd_ice1712_gpio_set_dir(ice, 0x5fffff); /* fix this for the time being */ + + /* reset the wm codec as the SPI mode */ + snd_ice1712_save_gpio_status(ice); + snd_ice1712_gpio_set_mask(ice, ~(AUREON_WM_RESET|AUREON_WM_CS|AUREON_CS8415_CS|AUREON_HP_SEL)); + + tmp = snd_ice1712_gpio_read(ice); + tmp &= ~AUREON_WM_RESET; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + tmp |= AUREON_WM_CS | AUREON_CS8415_CS; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + tmp |= AUREON_WM_RESET; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + + /* initialize WM8770 codec */ + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71 || + ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71LT || + ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71XT) + p = wm_inits_prodigy; + else + p = wm_inits_aureon; + for (; *p != (unsigned short)-1; p += 2) + wm_put(ice, p[0], p[1]); + + /* initialize CS8415A codec */ + if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT && + ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) { + for (p = cs_inits; *p != (unsigned short)-1; p++) + aureon_spi_write(ice, AUREON_CS8415_CS, *p | 0x200000, 24); + spec->cs8415_mux = 1; + + aureon_set_headphone_amp(ice, 1); + } + + snd_ice1712_restore_gpio_status(ice); + + /* initialize PCA9554 pin directions & set default input */ + aureon_pca9554_write(ice, PCA9554_DIR, 0x00); + aureon_pca9554_write(ice, PCA9554_OUT, 0x00); /* internal AUX */ + return 0; +} + +/* + * suspend/resume + */ +#ifdef CONFIG_PM_SLEEP +static int aureon_resume(struct snd_ice1712 *ice) +{ + struct aureon_spec *spec = ice->spec; + int err, i; + + err = aureon_reset(ice); + if (err != 0) + return err; + + /* workaround for poking volume with alsamixer after resume: + * just set stored volume again */ + for (i = 0; i < ice->num_total_dacs; i++) + wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]); + return 0; +} +#endif + +/* + * initialize the chip + */ +static int aureon_init(struct snd_ice1712 *ice) +{ + struct aureon_spec *spec; + int i, err; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY) { + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + } else { + /* aureon 7.1 and prodigy 7.1 */ + ice->num_total_dacs = 8; + ice->num_total_adcs = 2; + } + + /* to remember the register values of CS8415 */ + ice->akm = kzalloc_obj(struct snd_akm4xxx); + if (!ice->akm) + return -ENOMEM; + ice->akm_codecs = 1; + + err = aureon_reset(ice); + if (err != 0) + return err; + + spec->master[0] = WM_VOL_MUTE; + spec->master[1] = WM_VOL_MUTE; + for (i = 0; i < ice->num_total_dacs; i++) { + spec->vol[i] = WM_VOL_MUTE; + wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]); + } + +#ifdef CONFIG_PM_SLEEP + ice->pm_resume = aureon_resume; + ice->pm_suspend_enabled = 1; +#endif + + return 0; +} + + +/* + * Aureon boards don't provide the EEPROM data except for the vendor IDs. + * hence the driver needs to sets up it properly. + */ + +static const unsigned char aureon51_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x0a, /* clock 512, spdif-in/ADC, 3DACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0x5f, + [ICE_EEP2_GPIO_MASK] = 0x00, + [ICE_EEP2_GPIO_MASK1] = 0x00, + [ICE_EEP2_GPIO_MASK2] = 0x00, + [ICE_EEP2_GPIO_STATE] = 0x00, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; + +static const unsigned char aureon71_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x0b, /* clock 512, spdif-in/ADC, 4DACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0x5f, + [ICE_EEP2_GPIO_MASK] = 0x00, + [ICE_EEP2_GPIO_MASK1] = 0x00, + [ICE_EEP2_GPIO_MASK2] = 0x00, + [ICE_EEP2_GPIO_STATE] = 0x00, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; +#define prodigy71_eeprom aureon71_eeprom + +static const unsigned char aureon71_universe_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x2b, /* clock 512, mpu401, spdif-in/ADC, + * 4DACs + */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0x5f, + [ICE_EEP2_GPIO_MASK] = 0x00, + [ICE_EEP2_GPIO_MASK1] = 0x00, + [ICE_EEP2_GPIO_MASK2] = 0x00, + [ICE_EEP2_GPIO_STATE] = 0x00, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; + +static const unsigned char prodigy71lt_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x4b, /* clock 384, spdif-in/ADC, 4DACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0x5f, + [ICE_EEP2_GPIO_MASK] = 0x00, + [ICE_EEP2_GPIO_MASK1] = 0x00, + [ICE_EEP2_GPIO_MASK2] = 0x00, + [ICE_EEP2_GPIO_STATE] = 0x00, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; +#define prodigy71xt_eeprom prodigy71lt_eeprom + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_aureon_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_AUREON51_SKY, + .name = "Terratec Aureon 5.1-Sky", + .model = "aureon51", + .chip_init = aureon_init, + .build_controls = aureon_add_controls, + .eeprom_size = sizeof(aureon51_eeprom), + .eeprom_data = aureon51_eeprom, + .driver = "Aureon51", + }, + { + .subvendor = VT1724_SUBDEVICE_AUREON71_SPACE, + .name = "Terratec Aureon 7.1-Space", + .model = "aureon71", + .chip_init = aureon_init, + .build_controls = aureon_add_controls, + .eeprom_size = sizeof(aureon71_eeprom), + .eeprom_data = aureon71_eeprom, + .driver = "Aureon71", + }, + { + .subvendor = VT1724_SUBDEVICE_AUREON71_UNIVERSE, + .name = "Terratec Aureon 7.1-Universe", + .model = "universe", + .chip_init = aureon_init, + .build_controls = aureon_add_controls, + .eeprom_size = sizeof(aureon71_universe_eeprom), + .eeprom_data = aureon71_universe_eeprom, + .driver = "Aureon71Univ", /* keep in 15 letters */ + }, + { + .subvendor = VT1724_SUBDEVICE_PRODIGY71, + .name = "Audiotrak Prodigy 7.1", + .model = "prodigy71", + .chip_init = aureon_init, + .build_controls = aureon_add_controls, + .eeprom_size = sizeof(prodigy71_eeprom), + .eeprom_data = prodigy71_eeprom, + .driver = "Prodigy71", /* should be identical with Aureon71 */ + }, + { + .subvendor = VT1724_SUBDEVICE_PRODIGY71LT, + .name = "Audiotrak Prodigy 7.1 LT", + .model = "prodigy71lt", + .chip_init = aureon_init, + .build_controls = aureon_add_controls, + .eeprom_size = sizeof(prodigy71lt_eeprom), + .eeprom_data = prodigy71lt_eeprom, + .driver = "Prodigy71LT", + }, + { + .subvendor = VT1724_SUBDEVICE_PRODIGY71XT, + .name = "Audiotrak Prodigy 7.1 XT", + .model = "prodigy71xt", + .chip_init = aureon_init, + .build_controls = aureon_add_controls, + .eeprom_size = sizeof(prodigy71xt_eeprom), + .eeprom_data = prodigy71xt_eeprom, + .driver = "Prodigy71LT", + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/aureon.h b/sound/pci/ice1712/aureon.h new file mode 100644 index 000000000..de011495e --- /dev/null +++ b/sound/pci/ice1712/aureon.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_AUREON_H +#define __SOUND_AUREON_H + +/* + * ALSA driver for VIA VT1724 (Envy24HT) + * + * Lowlevel functions for Terratec Aureon cards + * + * Copyright (c) 2003 Takashi Iwai + */ + +#define AUREON_DEVICE_DESC "{Terratec,Aureon 5.1 Sky},"\ + "{Terratec,Aureon 7.1 Space},"\ + "{Terratec,Aureon 7.1 Universe}," \ + "{AudioTrak,Prodigy 7.1}," \ + "{AudioTrak,Prodigy 7.1 LT},"\ + "{AudioTrak,Prodigy 7.1 XT}," + +#define VT1724_SUBDEVICE_AUREON51_SKY 0x3b154711 /* Aureon 5.1 Sky */ +#define VT1724_SUBDEVICE_AUREON71_SPACE 0x3b154511 /* Aureon 7.1 Space */ +#define VT1724_SUBDEVICE_AUREON71_UNIVERSE 0x3b155311 /* Aureon 7.1 Universe */ +#define VT1724_SUBDEVICE_PRODIGY71 0x33495345 /* PRODIGY 7.1 */ +#define VT1724_SUBDEVICE_PRODIGY71LT 0x32315441 /* PRODIGY 7.1 LT */ +#define VT1724_SUBDEVICE_PRODIGY71XT 0x36315441 /* PRODIGY 7.1 XT*/ + +extern struct snd_ice1712_card_info snd_vt1724_aureon_cards[]; + +/* GPIO bits */ +#define AUREON_CS8415_CS (1 << 22) +#define AUREON_SPI_MISO (1 << 21) +#define AUREON_WM_RESET (1 << 20) +#define AUREON_SPI_CLK (1 << 19) +#define AUREON_SPI_MOSI (1 << 18) +#define AUREON_WM_RW (1 << 17) +#define AUREON_AC97_RESET (1 << 16) +#define AUREON_DIGITAL_SEL1 (1 << 15) +#define AUREON_HP_SEL (1 << 14) +#define AUREON_WM_CS (1 << 12) +#define AUREON_AC97_COMMIT (1 << 11) +#define AUREON_AC97_ADDR (1 << 10) +#define AUREON_AC97_DATA_LOW (1 << 9) +#define AUREON_AC97_DATA_HIGH (1 << 8) +#define AUREON_AC97_DATA_MASK 0xFF + +#define PRODIGY_WM_CS (1 << 8) +#define PRODIGY_SPI_MOSI (1 << 10) +#define PRODIGY_SPI_CLK (1 << 9) +#define PRODIGY_HP_SEL (1 << 5) + +#endif /* __SOUND_AUREON_H */ diff --git a/sound/pci/ice1712/delta.c b/sound/pci/ice1712/delta.c new file mode 100644 index 000000000..897b749c7 --- /dev/null +++ b/sound/pci/ice1712/delta.c @@ -0,0 +1,913 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for M-Audio Delta 1010, 1010E, 44, 66, 66E, Dio2496, + * Audiophile, Digigram VX442 + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ice1712.h" +#include "delta.h" + +#define SND_CS8403 +#include + + +/* + * CS8427 via SPI mode (for Audiophile), emulated I2C + */ + +/* send 8 bits */ +static void ap_cs8427_write_byte(struct snd_ice1712 *ice, unsigned char data, unsigned char tmp) +{ + int idx; + + for (idx = 7; idx >= 0; idx--) { + tmp &= ~(ICE1712_DELTA_AP_DOUT|ICE1712_DELTA_AP_CCLK); + if (data & (1 << idx)) + tmp |= ICE1712_DELTA_AP_DOUT; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + tmp |= ICE1712_DELTA_AP_CCLK; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + } +} + +/* read 8 bits */ +static unsigned char ap_cs8427_read_byte(struct snd_ice1712 *ice, unsigned char tmp) +{ + unsigned char data = 0; + int idx; + + for (idx = 7; idx >= 0; idx--) { + tmp &= ~ICE1712_DELTA_AP_CCLK; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + if (snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_DELTA_AP_DIN) + data |= 1 << idx; + tmp |= ICE1712_DELTA_AP_CCLK; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + } + return data; +} + +/* assert chip select */ +static unsigned char ap_cs8427_codec_select(struct snd_ice1712 *ice) +{ + unsigned char tmp; + tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA); + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + tmp &= ~ICE1712_DELTA_1010LT_CS; + tmp |= ICE1712_DELTA_1010LT_CCLK | ICE1712_DELTA_1010LT_CS_CS8427; + break; + case ICE1712_SUBDEVICE_AUDIOPHILE: + case ICE1712_SUBDEVICE_DELTA410: + tmp |= ICE1712_DELTA_AP_CCLK | ICE1712_DELTA_AP_CS_CODEC; + tmp &= ~ICE1712_DELTA_AP_CS_DIGITAL; + break; + case ICE1712_SUBDEVICE_DELTA66E: + tmp |= ICE1712_DELTA_66E_CCLK | ICE1712_DELTA_66E_CS_CHIP_A | + ICE1712_DELTA_66E_CS_CHIP_B; + tmp &= ~ICE1712_DELTA_66E_CS_CS8427; + break; + case ICE1712_SUBDEVICE_VX442: + tmp |= ICE1712_VX442_CCLK | ICE1712_VX442_CODEC_CHIP_A | ICE1712_VX442_CODEC_CHIP_B; + tmp &= ~ICE1712_VX442_CS_DIGITAL; + break; + } + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + return tmp; +} + +/* deassert chip select */ +static void ap_cs8427_codec_deassert(struct snd_ice1712 *ice, unsigned char tmp) +{ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + tmp &= ~ICE1712_DELTA_1010LT_CS; + tmp |= ICE1712_DELTA_1010LT_CS_NONE; + break; + case ICE1712_SUBDEVICE_AUDIOPHILE: + case ICE1712_SUBDEVICE_DELTA410: + tmp |= ICE1712_DELTA_AP_CS_DIGITAL; + break; + case ICE1712_SUBDEVICE_DELTA66E: + tmp |= ICE1712_DELTA_66E_CS_CS8427; + break; + case ICE1712_SUBDEVICE_VX442: + tmp |= ICE1712_VX442_CS_DIGITAL; + break; + } + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); +} + +/* sequential write */ +static int ap_cs8427_sendbytes(struct snd_i2c_device *device, unsigned char *bytes, int count) +{ + struct snd_ice1712 *ice = device->bus->private_data; + int res = count; + unsigned char tmp; + + guard(mutex)(&ice->gpio_mutex); + tmp = ap_cs8427_codec_select(ice); + ap_cs8427_write_byte(ice, (device->addr << 1) | 0, tmp); /* address + write mode */ + while (count-- > 0) + ap_cs8427_write_byte(ice, *bytes++, tmp); + ap_cs8427_codec_deassert(ice, tmp); + return res; +} + +/* sequential read */ +static int ap_cs8427_readbytes(struct snd_i2c_device *device, unsigned char *bytes, int count) +{ + struct snd_ice1712 *ice = device->bus->private_data; + int res = count; + unsigned char tmp; + + guard(mutex)(&ice->gpio_mutex); + tmp = ap_cs8427_codec_select(ice); + ap_cs8427_write_byte(ice, (device->addr << 1) | 1, tmp); /* address + read mode */ + while (count-- > 0) + *bytes++ = ap_cs8427_read_byte(ice, tmp); + ap_cs8427_codec_deassert(ice, tmp); + return res; +} + +static int ap_cs8427_probeaddr(struct snd_i2c_bus *bus, unsigned short addr) +{ + if (addr == 0x10) + return 1; + return -ENOENT; +} + +static const struct snd_i2c_ops ap_cs8427_i2c_ops = { + .sendbytes = ap_cs8427_sendbytes, + .readbytes = ap_cs8427_readbytes, + .probeaddr = ap_cs8427_probeaddr, +}; + +/* + */ + +static void snd_ice1712_delta_cs8403_spdif_write(struct snd_ice1712 *ice, unsigned char bits) +{ + unsigned char tmp, mask1, mask2; + int idx; + /* send byte to transmitter */ + mask1 = ICE1712_DELTA_SPDIF_OUT_STAT_CLOCK; + mask2 = ICE1712_DELTA_SPDIF_OUT_STAT_DATA; + guard(mutex)(&ice->gpio_mutex); + tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA); + for (idx = 7; idx >= 0; idx--) { + tmp &= ~(mask1 | mask2); + if (bits & (1 << idx)) + tmp |= mask2; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(100); + tmp |= mask1; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(100); + } + tmp &= ~mask1; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); +} + + +static void delta_spdif_default_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + snd_cs8403_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_bits); +} + +static int delta_spdif_default_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + int change; + + val = snd_cs8403_encode_spdif_bits(&ucontrol->value.iec958); + scoped_guard(spinlock_irq, &ice->reg_lock) { + change = ice->spdif.cs8403_bits != val; + ice->spdif.cs8403_bits = val; + if (!change || ice->playback_pro_substream) + return change; + } + snd_ice1712_delta_cs8403_spdif_write(ice, val); + return change; +} + +static void delta_spdif_stream_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + snd_cs8403_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_stream_bits); +} + +static int delta_spdif_stream_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + int change; + + val = snd_cs8403_encode_spdif_bits(&ucontrol->value.iec958); + scoped_guard(spinlock_irq, &ice->reg_lock) { + change = ice->spdif.cs8403_stream_bits != val; + ice->spdif.cs8403_stream_bits = val; + if (!change || ice->playback_pro_substream) + return change; + } + snd_ice1712_delta_cs8403_spdif_write(ice, val); + return change; +} + + +/* + * AK4524 on Delta 44 and 66 to choose the chip mask + */ +static void delta_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ak4xxx_private *priv = (void *)ak->private_value[0]; + struct snd_ice1712 *ice = ak->private_data[0]; + + snd_ice1712_save_gpio_status(ice); + priv->cs_mask = + priv->cs_addr = chip == 0 ? ICE1712_DELTA_CODEC_CHIP_A : + ICE1712_DELTA_CODEC_CHIP_B; +} + +/* + * AK4524 on Delta1010LT to choose the chip address + */ +static void delta1010lt_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ak4xxx_private *priv = (void *)ak->private_value[0]; + struct snd_ice1712 *ice = ak->private_data[0]; + + snd_ice1712_save_gpio_status(ice); + priv->cs_mask = ICE1712_DELTA_1010LT_CS; + priv->cs_addr = chip << 4; +} + +/* + * AK4524 on Delta66 rev E to choose the chip address + */ +static void delta66e_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ak4xxx_private *priv = (void *)ak->private_value[0]; + struct snd_ice1712 *ice = ak->private_data[0]; + + snd_ice1712_save_gpio_status(ice); + priv->cs_mask = + priv->cs_addr = chip == 0 ? ICE1712_DELTA_66E_CS_CHIP_A : + ICE1712_DELTA_66E_CS_CHIP_B; +} + +/* + * AK4528 on VX442 to choose the chip mask + */ +static void vx442_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ak4xxx_private *priv = (void *)ak->private_value[0]; + struct snd_ice1712 *ice = ak->private_data[0]; + + snd_ice1712_save_gpio_status(ice); + priv->cs_mask = + priv->cs_addr = chip == 0 ? ICE1712_VX442_CODEC_CHIP_A : + ICE1712_VX442_CODEC_CHIP_B; +} + +/* + * change the DFS bit according rate for Delta1010 + */ +static void delta_1010_set_rate_val(struct snd_ice1712 *ice, unsigned int rate) +{ + unsigned char tmp, tmp2; + + if (rate == 0) /* no hint - S/PDIF input is master, simply return */ + return; + + guard(mutex)(&ice->gpio_mutex); + tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA); + tmp2 = tmp & ~ICE1712_DELTA_DFS; + if (rate > 48000) + tmp2 |= ICE1712_DELTA_DFS; + if (tmp != tmp2) + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp2); +} + +/* + * change the rate of AK4524 on Delta 44/66, AP, 1010LT + */ +static void delta_ak4524_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) +{ + unsigned char tmp, tmp2; + struct snd_ice1712 *ice = ak->private_data[0]; + + if (rate == 0) /* no hint - S/PDIF input is master, simply return */ + return; + + /* check before reset ak4524 to avoid unnecessary clicks */ + scoped_guard(mutex, &ice->gpio_mutex) { + tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA); + } + tmp2 = tmp & ~ICE1712_DELTA_DFS; + if (rate > 48000) + tmp2 |= ICE1712_DELTA_DFS; + if (tmp == tmp2) + return; + + /* do it again */ + snd_akm4xxx_reset(ak, 1); + scoped_guard(mutex, &ice->gpio_mutex) { + tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ~ICE1712_DELTA_DFS; + if (rate > 48000) + tmp |= ICE1712_DELTA_DFS; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + } + snd_akm4xxx_reset(ak, 0); +} + +/* + * change the rate of AK4524 on VX442 + */ +static void vx442_ak4524_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) +{ + unsigned char val; + + val = (rate > 48000) ? 0x65 : 0x60; + if (snd_akm4xxx_get(ak, 0, 0x02) != val || + snd_akm4xxx_get(ak, 1, 0x02) != val) { + snd_akm4xxx_reset(ak, 1); + snd_akm4xxx_write(ak, 0, 0x02, val); + snd_akm4xxx_write(ak, 1, 0x02, val); + snd_akm4xxx_reset(ak, 0); + } +} + + +/* + * SPDIF ops for Delta 1010, Dio, 66 + */ + +/* open callback */ +static void delta_open_spdif(struct snd_ice1712 *ice, struct snd_pcm_substream *substream) +{ + ice->spdif.cs8403_stream_bits = ice->spdif.cs8403_bits; +} + +/* set up */ +static void delta_setup_spdif(struct snd_ice1712 *ice, int rate) +{ + unsigned int tmp; + int change; + + scoped_guard(spinlock_irqsave, &ice->reg_lock) { + tmp = ice->spdif.cs8403_stream_bits; + if (tmp & 0x01) /* consumer */ + tmp &= (tmp & 0x01) ? ~0x06 : ~0x18; + switch (rate) { + case 32000: tmp |= (tmp & 0x01) ? 0x04 : 0x00; break; + case 44100: tmp |= (tmp & 0x01) ? 0x00 : 0x10; break; + case 48000: tmp |= (tmp & 0x01) ? 0x02 : 0x08; break; + default: tmp |= (tmp & 0x01) ? 0x00 : 0x18; break; + } + change = ice->spdif.cs8403_stream_bits != tmp; + ice->spdif.cs8403_stream_bits = tmp; + } + if (change) + snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &ice->spdif.stream_ctl->id); + snd_ice1712_delta_cs8403_spdif_write(ice, tmp); +} + +#define snd_ice1712_delta1010lt_wordclock_status_info \ + snd_ctl_boolean_mono_info + +static int snd_ice1712_delta1010lt_wordclock_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + char reg = 0x10; /* CS8427 receiver error register */ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + if (snd_i2c_sendbytes(ice->cs8427, ®, 1) != 1) + dev_err(ice->card->dev, + "unable to send register 0x%x byte to CS8427\n", reg); + snd_i2c_readbytes(ice->cs8427, ®, 1); + ucontrol->value.integer.value[0] = (reg & CS8427_UNLOCK) ? 1 : 0; + return 0; +} + +static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status = +{ + .access = (SNDRV_CTL_ELEM_ACCESS_READ), + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Word Clock Status", + .info = snd_ice1712_delta1010lt_wordclock_status_info, + .get = snd_ice1712_delta1010lt_wordclock_status_get, +}; + +/* + * initialize the chips on M-Audio cards + */ + +static const struct snd_akm4xxx akm_audiophile = { + .type = SND_AK4528, + .num_adcs = 2, + .num_dacs = 2, + .ops = { + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_audiophile_priv = { + .caddr = 2, + .cif = 0, + .data_mask = ICE1712_DELTA_AP_DOUT, + .clk_mask = ICE1712_DELTA_AP_CCLK, + .cs_mask = ICE1712_DELTA_AP_CS_CODEC, + .cs_addr = ICE1712_DELTA_AP_CS_CODEC, + .cs_none = 0, + .add_flags = ICE1712_DELTA_AP_CS_DIGITAL, + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_delta410 = { + .type = SND_AK4529, + .num_adcs = 2, + .num_dacs = 8, + .ops = { + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_delta410_priv = { + .caddr = 0, + .cif = 0, + .data_mask = ICE1712_DELTA_AP_DOUT, + .clk_mask = ICE1712_DELTA_AP_CCLK, + .cs_mask = ICE1712_DELTA_AP_CS_CODEC, + .cs_addr = ICE1712_DELTA_AP_CS_CODEC, + .cs_none = 0, + .add_flags = ICE1712_DELTA_AP_CS_DIGITAL, + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_delta1010lt = { + .type = SND_AK4524, + .num_adcs = 8, + .num_dacs = 8, + .ops = { + .lock = delta1010lt_ak4524_lock, + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_delta1010lt_priv = { + .caddr = 2, + .cif = 0, /* the default level of the CIF pin from AK4524 */ + .data_mask = ICE1712_DELTA_1010LT_DOUT, + .clk_mask = ICE1712_DELTA_1010LT_CCLK, + .cs_mask = 0, + .cs_addr = 0, /* set later */ + .cs_none = ICE1712_DELTA_1010LT_CS_NONE, + .add_flags = 0, + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_delta66e = { + .type = SND_AK4524, + .num_adcs = 4, + .num_dacs = 4, + .ops = { + .lock = delta66e_ak4524_lock, + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_delta66e_priv = { + .caddr = 2, + .cif = 0, /* the default level of the CIF pin from AK4524 */ + .data_mask = ICE1712_DELTA_66E_DOUT, + .clk_mask = ICE1712_DELTA_66E_CCLK, + .cs_mask = 0, + .cs_addr = 0, /* set later */ + .cs_none = 0, + .add_flags = 0, + .mask_flags = 0, +}; + + +static const struct snd_akm4xxx akm_delta44 = { + .type = SND_AK4524, + .num_adcs = 4, + .num_dacs = 4, + .ops = { + .lock = delta_ak4524_lock, + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_delta44_priv = { + .caddr = 2, + .cif = 0, /* the default level of the CIF pin from AK4524 */ + .data_mask = ICE1712_DELTA_CODEC_SERIAL_DATA, + .clk_mask = ICE1712_DELTA_CODEC_SERIAL_CLOCK, + .cs_mask = 0, + .cs_addr = 0, /* set later */ + .cs_none = 0, + .add_flags = 0, + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_vx442 = { + .type = SND_AK4524, + .num_adcs = 4, + .num_dacs = 4, + .ops = { + .lock = vx442_ak4524_lock, + .set_rate_val = vx442_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_vx442_priv = { + .caddr = 2, + .cif = 0, + .data_mask = ICE1712_VX442_DOUT, + .clk_mask = ICE1712_VX442_CCLK, + .cs_mask = 0, + .cs_addr = 0, /* set later */ + .cs_none = 0, + .add_flags = 0, + .mask_flags = 0, +}; + +#ifdef CONFIG_PM_SLEEP +static int snd_ice1712_delta_resume(struct snd_ice1712 *ice) +{ + unsigned char akm_img_bak[AK4XXX_IMAGE_SIZE]; + unsigned char akm_vol_bak[AK4XXX_IMAGE_SIZE]; + + /* init spdif */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_AUDIOPHILE: + case ICE1712_SUBDEVICE_DELTA410: + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + case ICE1712_SUBDEVICE_VX442: + case ICE1712_SUBDEVICE_DELTA66E: + snd_cs8427_init(ice->i2c, ice->cs8427); + break; + case ICE1712_SUBDEVICE_DELTA1010: + case ICE1712_SUBDEVICE_MEDIASTATION: + /* nothing */ + break; + case ICE1712_SUBDEVICE_DELTADIO2496: + case ICE1712_SUBDEVICE_DELTA66: + /* Set spdif defaults */ + snd_ice1712_delta_cs8403_spdif_write(ice, ice->spdif.cs8403_bits); + break; + } + + /* init codec and restore registers */ + if (ice->akm_codecs) { + memcpy(akm_img_bak, ice->akm->images, sizeof(akm_img_bak)); + memcpy(akm_vol_bak, ice->akm->volumes, sizeof(akm_vol_bak)); + snd_akm4xxx_init(ice->akm); + memcpy(ice->akm->images, akm_img_bak, sizeof(akm_img_bak)); + memcpy(ice->akm->volumes, akm_vol_bak, sizeof(akm_vol_bak)); + snd_akm4xxx_reset(ice->akm, 0); + } + + return 0; +} + +static int snd_ice1712_delta_suspend(struct snd_ice1712 *ice) +{ + if (ice->akm_codecs) /* reset & mute codec */ + snd_akm4xxx_reset(ice->akm, 1); + + return 0; +} +#endif + +static int snd_ice1712_delta_init(struct snd_ice1712 *ice) +{ + int err; + struct snd_akm4xxx *ak; + unsigned char tmp; + + if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DELTA1010 && + ice->eeprom.gpiodir == 0x7b) + ice->eeprom.subvendor = ICE1712_SUBDEVICE_DELTA1010E; + + if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DELTA66 && + ice->eeprom.gpiodir == 0xfb) + ice->eeprom.subvendor = ICE1712_SUBDEVICE_DELTA66E; + + /* determine I2C, DACs and ADCs */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_AUDIOPHILE: + ice->num_total_dacs = 2; + ice->num_total_adcs = 2; + break; + case ICE1712_SUBDEVICE_DELTA410: + ice->num_total_dacs = 8; + ice->num_total_adcs = 2; + break; + case ICE1712_SUBDEVICE_DELTA44: + case ICE1712_SUBDEVICE_DELTA66: + ice->num_total_dacs = ice->omni ? 8 : 4; + ice->num_total_adcs = ice->omni ? 8 : 4; + break; + case ICE1712_SUBDEVICE_DELTA1010: + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + case ICE1712_SUBDEVICE_MEDIASTATION: + case ICE1712_SUBDEVICE_EDIROLDA2496: + ice->num_total_dacs = 8; + ice->num_total_adcs = 8; + break; + case ICE1712_SUBDEVICE_DELTADIO2496: + ice->num_total_dacs = 4; /* two AK4324 codecs */ + break; + case ICE1712_SUBDEVICE_VX442: + case ICE1712_SUBDEVICE_DELTA66E: /* omni not supported yet */ + ice->num_total_dacs = 4; + ice->num_total_adcs = 4; + break; + } +#ifdef CONFIG_PM_SLEEP + ice->pm_resume = snd_ice1712_delta_resume; + ice->pm_suspend = snd_ice1712_delta_suspend; + ice->pm_suspend_enabled = 1; +#endif + /* initialize the SPI clock to high */ + tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA); + tmp |= ICE1712_DELTA_AP_CCLK; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + + /* initialize spdif */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_AUDIOPHILE: + case ICE1712_SUBDEVICE_DELTA410: + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + case ICE1712_SUBDEVICE_VX442: + case ICE1712_SUBDEVICE_DELTA66E: + err = snd_i2c_bus_create(ice->card, "ICE1712 GPIO 1", NULL, &ice->i2c); + if (err < 0) { + dev_err(ice->card->dev, "unable to create I2C bus\n"); + return err; + } + ice->i2c->private_data = ice; + ice->i2c->ops = &ap_cs8427_i2c_ops; + err = snd_ice1712_init_cs8427(ice, CS8427_BASE_ADDR); + if (err < 0) + return err; + break; + case ICE1712_SUBDEVICE_DELTA1010: + case ICE1712_SUBDEVICE_MEDIASTATION: + ice->gpio.set_pro_rate = delta_1010_set_rate_val; + break; + case ICE1712_SUBDEVICE_DELTADIO2496: + ice->gpio.set_pro_rate = delta_1010_set_rate_val; + fallthrough; + case ICE1712_SUBDEVICE_DELTA66: + ice->spdif.ops.open = delta_open_spdif; + ice->spdif.ops.setup_rate = delta_setup_spdif; + ice->spdif.ops.default_get = delta_spdif_default_get; + ice->spdif.ops.default_put = delta_spdif_default_put; + ice->spdif.ops.stream_get = delta_spdif_stream_get; + ice->spdif.ops.stream_put = delta_spdif_stream_put; + /* Set spdif defaults */ + snd_ice1712_delta_cs8403_spdif_write(ice, ice->spdif.cs8403_bits); + break; + } + + /* no analog? */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DELTA1010: + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTADIO2496: + case ICE1712_SUBDEVICE_MEDIASTATION: + return 0; + } + + /* second stage of initialization, analog parts and others */ + ak = ice->akm = kmalloc_obj(struct snd_akm4xxx); + if (! ak) + return -ENOMEM; + ice->akm_codecs = 1; + + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_AUDIOPHILE: + err = snd_ice1712_akm4xxx_init(ak, &akm_audiophile, &akm_audiophile_priv, ice); + break; + case ICE1712_SUBDEVICE_DELTA410: + err = snd_ice1712_akm4xxx_init(ak, &akm_delta410, &akm_delta410_priv, ice); + break; + case ICE1712_SUBDEVICE_DELTA1010LT: + case ICE1712_SUBDEVICE_EDIROLDA2496: + err = snd_ice1712_akm4xxx_init(ak, &akm_delta1010lt, &akm_delta1010lt_priv, ice); + break; + case ICE1712_SUBDEVICE_DELTA66: + case ICE1712_SUBDEVICE_DELTA44: + err = snd_ice1712_akm4xxx_init(ak, &akm_delta44, &akm_delta44_priv, ice); + break; + case ICE1712_SUBDEVICE_VX442: + err = snd_ice1712_akm4xxx_init(ak, &akm_vx442, &akm_vx442_priv, ice); + break; + case ICE1712_SUBDEVICE_DELTA66E: + err = snd_ice1712_akm4xxx_init(ak, &akm_delta66e, &akm_delta66e_priv, ice); + break; + default: + snd_BUG(); + return -EINVAL; + } + + return err; +} + + +/* + * additional controls for M-Audio cards + */ + +static const struct snd_kcontrol_new snd_ice1712_delta1010_wordclock_select = +ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Word Clock Sync", 0, ICE1712_DELTA_WORD_CLOCK_SELECT, 1, 0); +static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_select = +ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Word Clock Sync", 0, ICE1712_DELTA_1010LT_WORDCLOCK, 0, 0); +static const struct snd_kcontrol_new snd_ice1712_delta1010_wordclock_status = +ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Word Clock Status", 0, ICE1712_DELTA_WORD_CLOCK_STATUS, 1, SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE); +static const struct snd_kcontrol_new snd_ice1712_deltadio2496_spdif_in_select = +ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "IEC958 Input Optical", 0, ICE1712_DELTA_SPDIF_INPUT_SELECT, 0, 0); +static const struct snd_kcontrol_new snd_ice1712_delta_spdif_in_status = +ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Delta IEC958 Input Status", 0, ICE1712_DELTA_SPDIF_IN_STAT, 1, SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE); + + +static int snd_ice1712_delta_add_controls(struct snd_ice1712 *ice) +{ + int err; + + /* 1010 and dio specific controls */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DELTA1010: + case ICE1712_SUBDEVICE_MEDIASTATION: + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010_wordclock_select, ice)); + if (err < 0) + return err; + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010_wordclock_status, ice)); + if (err < 0) + return err; + break; + case ICE1712_SUBDEVICE_DELTADIO2496: + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_deltadio2496_spdif_in_select, ice)); + if (err < 0) + return err; + break; + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010lt_wordclock_select, ice)); + if (err < 0) + return err; + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010lt_wordclock_status, ice)); + if (err < 0) + return err; + break; + } + + /* normal spdif controls */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DELTA1010: + case ICE1712_SUBDEVICE_DELTADIO2496: + case ICE1712_SUBDEVICE_DELTA66: + case ICE1712_SUBDEVICE_MEDIASTATION: + err = snd_ice1712_spdif_build_controls(ice); + if (err < 0) + return err; + break; + } + + /* spdif status in */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DELTA1010: + case ICE1712_SUBDEVICE_DELTADIO2496: + case ICE1712_SUBDEVICE_DELTA66: + case ICE1712_SUBDEVICE_MEDIASTATION: + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta_spdif_in_status, ice)); + if (err < 0) + return err; + break; + } + + /* ak4524 controls */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DELTA1010LT: + case ICE1712_SUBDEVICE_AUDIOPHILE: + case ICE1712_SUBDEVICE_DELTA410: + case ICE1712_SUBDEVICE_DELTA44: + case ICE1712_SUBDEVICE_DELTA66: + case ICE1712_SUBDEVICE_VX442: + case ICE1712_SUBDEVICE_DELTA66E: + case ICE1712_SUBDEVICE_EDIROLDA2496: + err = snd_ice1712_akm4xxx_build_controls(ice); + if (err < 0) + return err; + break; + } + + return 0; +} + + +/* entry point */ +struct snd_ice1712_card_info snd_ice1712_delta_cards[] = { + { + .subvendor = ICE1712_SUBDEVICE_DELTA1010, + .name = "M Audio Delta 1010", + .model = "delta1010", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_DELTADIO2496, + .name = "M Audio Delta DiO 2496", + .model = "dio2496", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + .no_mpu401 = 1, + }, + { + .subvendor = ICE1712_SUBDEVICE_DELTA66, + .name = "M Audio Delta 66", + .model = "delta66", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + .no_mpu401 = 1, + }, + { + .subvendor = ICE1712_SUBDEVICE_DELTA44, + .name = "M Audio Delta 44", + .model = "delta44", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + .no_mpu401 = 1, + }, + { + .subvendor = ICE1712_SUBDEVICE_AUDIOPHILE, + .name = "M Audio Audiophile 24/96", + .model = "audiophile", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_DELTA410, + .name = "M Audio Delta 410", + .model = "delta410", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_DELTA1010LT, + .name = "M Audio Delta 1010LT", + .model = "delta1010lt", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_VX442, + .name = "Digigram VX442", + .model = "vx442", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + .no_mpu401 = 1, + }, + { + .subvendor = ICE1712_SUBDEVICE_MEDIASTATION, + .name = "Lionstracs Mediastation", + .model = "mediastation", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_EDIROLDA2496, + .name = "Edirol DA2496", + .model = "da2496", + .chip_init = snd_ice1712_delta_init, + .build_controls = snd_ice1712_delta_add_controls, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/delta.h b/sound/pci/ice1712/delta.h new file mode 100644 index 000000000..01fcaf7e8 --- /dev/null +++ b/sound/pci/ice1712/delta.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_DELTA_H +#define __SOUND_DELTA_H + +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for M-Audio Delta 1010, 44, 66, Dio2496, Audiophile + * Digigram VX442 + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +#define DELTA_DEVICE_DESC \ + "{MidiMan M Audio,Delta 1010},"\ + "{MidiMan M Audio,Delta 1010LT},"\ + "{MidiMan M Audio,Delta DiO 2496},"\ + "{MidiMan M Audio,Delta 66},"\ + "{MidiMan M Audio,Delta 44},"\ + "{MidiMan M Audio,Delta 410},"\ + "{MidiMan M Audio,Audiophile 24/96},"\ + "{Digigram,VX442},"\ + "{Lionstracs,Mediastation},"\ + "{Edirol,DA2496}," + +#define ICE1712_SUBDEVICE_DELTA1010 0x121430d6 +#define ICE1712_SUBDEVICE_DELTA1010E 0xff1430d6 +#define ICE1712_SUBDEVICE_DELTADIO2496 0x121431d6 +#define ICE1712_SUBDEVICE_DELTA66 0x121432d6 +#define ICE1712_SUBDEVICE_DELTA66E 0xff1432d6 +#define ICE1712_SUBDEVICE_DELTA44 0x121433d6 +#define ICE1712_SUBDEVICE_AUDIOPHILE 0x121434d6 +#define ICE1712_SUBDEVICE_DELTA410 0x121438d6 +#define ICE1712_SUBDEVICE_DELTA1010LT 0x12143bd6 +#define ICE1712_SUBDEVICE_VX442 0x12143cd6 +#define ICE1712_SUBDEVICE_MEDIASTATION 0x694c0100 +#define ICE1712_SUBDEVICE_EDIROLDA2496 0xce164010 + +/* entry point */ +extern struct snd_ice1712_card_info snd_ice1712_delta_cards[]; + + +/* + * MidiMan M-Audio Delta GPIO definitions + */ + +/* MidiMan M-Audio Delta shared pins */ +#define ICE1712_DELTA_DFS 0x01 /* fast/slow sample rate mode */ + /* (>48kHz must be 1) */ +#define ICE1712_DELTA_SPDIF_IN_STAT 0x02 + /* S/PDIF input status */ + /* 0 = valid signal is present */ + /* all except Delta44 */ + /* look to CS8414 datasheet */ +#define ICE1712_DELTA_SPDIF_OUT_STAT_CLOCK 0x04 + /* S/PDIF output status clock */ + /* (writing on rising edge - 0->1) */ + /* all except Delta44 */ + /* look to CS8404A datasheet */ +#define ICE1712_DELTA_SPDIF_OUT_STAT_DATA 0x08 + /* S/PDIF output status data */ + /* all except Delta44 */ + /* look to CS8404A datasheet */ +/* MidiMan M-Audio DeltaDiO */ +/* 0x01 = DFS */ +/* 0x02 = SPDIF_IN_STAT */ +/* 0x04 = SPDIF_OUT_STAT_CLOCK */ +/* 0x08 = SPDIF_OUT_STAT_DATA */ +#define ICE1712_DELTA_SPDIF_INPUT_SELECT 0x10 + /* coaxial (0), optical (1) */ + /* S/PDIF input select*/ + +/* MidiMan M-Audio Delta1010 */ +/* 0x01 = DFS */ +/* 0x02 = SPDIF_IN_STAT */ +/* 0x04 = SPDIF_OUT_STAT_CLOCK */ +/* 0x08 = SPDIF_OUT_STAT_DATA */ +#define ICE1712_DELTA_WORD_CLOCK_SELECT 0x10 + /* 1 - clock are taken from S/PDIF input */ + /* 0 - clock are taken from Word Clock input */ + /* affected SPMCLKIN pin of Envy24 */ +#define ICE1712_DELTA_WORD_CLOCK_STATUS 0x20 + /* 0 = valid word clock signal is present */ + +/* MidiMan M-Audio Delta66 */ +/* 0x01 = DFS */ +/* 0x02 = SPDIF_IN_STAT */ +/* 0x04 = SPDIF_OUT_STAT_CLOCK */ +/* 0x08 = SPDIF_OUT_STAT_DATA */ +#define ICE1712_DELTA_CODEC_SERIAL_DATA 0x10 + /* AKM4524 serial data */ +#define ICE1712_DELTA_CODEC_SERIAL_CLOCK 0x20 + /* AKM4524 serial clock */ + /* (writing on rising edge - 0->1 */ +#define ICE1712_DELTA_CODEC_CHIP_A 0x40 +#define ICE1712_DELTA_CODEC_CHIP_B 0x80 + /* 1 - select chip A or B */ + +/* MidiMan M-Audio Delta44 */ +/* 0x01 = DFS */ +/* 0x10 = CODEC_SERIAL_DATA */ +/* 0x20 = CODEC_SERIAL_CLOCK */ +/* 0x40 = CODEC_CHIP_A */ +/* 0x80 = CODEC_CHIP_B */ + +/* MidiMan M-Audio Audiophile/Delta410 definitions */ +/* thanks to Kristof Pelckmans for Delta410 info */ +/* 0x01 = DFS */ +#define ICE1712_DELTA_AP_CCLK 0x02 /* SPI clock */ + /* (clocking on rising edge - 0->1) */ +#define ICE1712_DELTA_AP_DIN 0x04 /* data input */ +#define ICE1712_DELTA_AP_DOUT 0x08 /* data output */ +#define ICE1712_DELTA_AP_CS_DIGITAL 0x10 /* CS8427 chip select */ + /* low signal = select */ +#define ICE1712_DELTA_AP_CS_CODEC 0x20 /* AK4528 (audiophile), AK4529 (Delta410) chip select */ + /* low signal = select */ + +/* MidiMan M-Audio Delta1010LT definitions */ +/* thanks to Anders Johansson */ +/* 0x01 = DFS */ +#define ICE1712_DELTA_1010LT_CCLK 0x02 /* SPI clock (AK4524 + CS8427) */ +#define ICE1712_DELTA_1010LT_DIN 0x04 /* data input (CS8427) */ +#define ICE1712_DELTA_1010LT_DOUT 0x08 /* data output (AK4524 + CS8427) */ +#define ICE1712_DELTA_1010LT_CS 0x70 /* mask for CS address */ +#define ICE1712_DELTA_1010LT_CS_CHIP_A 0x00 /* AK4524 #0 */ +#define ICE1712_DELTA_1010LT_CS_CHIP_B 0x10 /* AK4524 #1 */ +#define ICE1712_DELTA_1010LT_CS_CHIP_C 0x20 /* AK4524 #2 */ +#define ICE1712_DELTA_1010LT_CS_CHIP_D 0x30 /* AK4524 #3 */ +#define ICE1712_DELTA_1010LT_CS_CS8427 0x40 /* CS8427 */ +#define ICE1712_DELTA_1010LT_CS_NONE 0x50 /* nothing */ +#define ICE1712_DELTA_1010LT_WORDCLOCK 0x80 /* sample clock source: 0 = Word Clock Input, 1 = S/PDIF Input ??? */ + +/* M-Audio Delta 66 rev. E definitions. + * Newer revisions of Delta 66 have CS8427 over SPI for + * S/PDIF transceiver instead of CS8404/CS8414. */ +/* 0x01 = DFS */ +#define ICE1712_DELTA_66E_CCLK 0x02 /* SPI clock */ +#define ICE1712_DELTA_66E_DIN 0x04 /* data input */ +#define ICE1712_DELTA_66E_DOUT 0x08 /* data output */ +#define ICE1712_DELTA_66E_CS_CS8427 0x10 /* chip select, low = CS8427 */ +#define ICE1712_DELTA_66E_CS_CHIP_A 0x20 /* AK4524 #0 */ +#define ICE1712_DELTA_66E_CS_CHIP_B 0x40 /* AK4524 #1 */ + +/* Digigram VX442 definitions */ +#define ICE1712_VX442_CCLK 0x02 /* SPI clock */ +#define ICE1712_VX442_DIN 0x04 /* data input */ +#define ICE1712_VX442_DOUT 0x08 /* data output */ +#define ICE1712_VX442_CS_DIGITAL 0x10 /* chip select, low = CS8427 */ +#define ICE1712_VX442_CODEC_CHIP_A 0x20 /* select chip A */ +#define ICE1712_VX442_CODEC_CHIP_B 0x40 /* select chip B */ + +#endif /* __SOUND_DELTA_H */ diff --git a/sound/pci/ice1712/envy24ht.h b/sound/pci/ice1712/envy24ht.h new file mode 100644 index 000000000..10e79c82f --- /dev/null +++ b/sound/pci/ice1712/envy24ht.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_VT1724_H +#define __SOUND_VT1724_H + +/* + * ALSA driver for ICEnsemble VT1724 (Envy24) + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +#include +#include +#include +#include +#include + +#include "ice1712.h" + +enum { + ICE_EEP2_SYSCONF = 0, /* 06 */ + ICE_EEP2_ACLINK, /* 07 */ + ICE_EEP2_I2S, /* 08 */ + ICE_EEP2_SPDIF, /* 09 */ + ICE_EEP2_GPIO_DIR, /* 0a */ + ICE_EEP2_GPIO_DIR1, /* 0b */ + ICE_EEP2_GPIO_DIR2, /* 0c */ + ICE_EEP2_GPIO_MASK, /* 0d */ + ICE_EEP2_GPIO_MASK1, /* 0e */ + ICE_EEP2_GPIO_MASK2, /* 0f */ + ICE_EEP2_GPIO_STATE, /* 10 */ + ICE_EEP2_GPIO_STATE1, /* 11 */ + ICE_EEP2_GPIO_STATE2 /* 12 */ +}; + +/* + * Direct registers + */ + +#define ICEREG1724(ice, x) ((ice)->port + VT1724_REG_##x) + +#define VT1724_REG_CONTROL 0x00 /* byte */ +#define VT1724_RESET 0x80 /* reset whole chip */ +#define VT1724_REG_IRQMASK 0x01 /* byte */ +#define VT1724_IRQ_MPU_RX 0x80 +#define VT1724_IRQ_MPU_TX 0x20 +#define VT1724_IRQ_MTPCM 0x10 +#define VT1724_REG_IRQSTAT 0x02 /* byte */ +/* look to VT1724_IRQ_* */ +#define VT1724_REG_SYS_CFG 0x04 /* byte - system configuration PCI60 on Envy24*/ +#define VT1724_CFG_CLOCK 0xc0 +#define VT1724_CFG_CLOCK512 0x00 /* 22.5692Mhz, 44.1kHz*512 */ +#define VT1724_CFG_CLOCK384 0x40 /* 16.9344Mhz, 44.1kHz*384 */ +#define VT1724_CFG_MPU401 0x20 /* MPU401 UARTs */ +#define VT1724_CFG_ADC_MASK 0x0c /* one, two or one and S/PDIF, stereo ADCs */ +#define VT1724_CFG_ADC_NONE 0x0c /* no ADCs */ +#define VT1724_CFG_DAC_MASK 0x03 /* one, two, three, four stereo DACs */ + +#define VT1724_REG_AC97_CFG 0x05 /* byte */ +#define VT1724_CFG_PRO_I2S 0x80 /* multitrack converter: I2S or AC'97 */ +#define VT1724_CFG_AC97_PACKED 0x01 /* split or packed mode - AC'97 */ + +#define VT1724_REG_I2S_FEATURES 0x06 /* byte */ +#define VT1724_CFG_I2S_VOLUME 0x80 /* volume/mute capability */ +#define VT1724_CFG_I2S_96KHZ 0x40 /* supports 96kHz sampling */ +#define VT1724_CFG_I2S_RESMASK 0x30 /* resolution mask, 16,18,20,24-bit */ +#define VT1724_CFG_I2S_192KHZ 0x08 /* supports 192kHz sampling */ +#define VT1724_CFG_I2S_OTHER 0x07 /* other I2S IDs */ + +#define VT1724_REG_SPDIF_CFG 0x07 /* byte */ +#define VT1724_CFG_SPDIF_OUT_EN 0x80 /*Internal S/PDIF output is enabled*/ +#define VT1724_CFG_SPDIF_OUT_INT 0x40 /*Internal S/PDIF output is implemented*/ +#define VT1724_CFG_I2S_CHIPID 0x3c /* I2S chip ID */ +#define VT1724_CFG_SPDIF_IN 0x02 /* S/PDIF input is present */ +#define VT1724_CFG_SPDIF_OUT 0x01 /* External S/PDIF output is present */ + +/*there is no consumer AC97 codec with the VT1724*/ +//#define VT1724_REG_AC97_INDEX 0x08 /* byte */ +//#define VT1724_REG_AC97_CMD 0x09 /* byte */ + +#define VT1724_REG_MPU_TXFIFO 0x0a /*byte ro. number of bytes in TX fifo*/ +#define VT1724_REG_MPU_RXFIFO 0x0b /*byte ro. number of bytes in RX fifo*/ + +#define VT1724_REG_MPU_DATA 0x0c /* byte */ +#define VT1724_REG_MPU_CTRL 0x0d /* byte */ +#define VT1724_MPU_UART 0x01 +#define VT1724_MPU_TX_EMPTY 0x02 +#define VT1724_MPU_TX_FULL 0x04 +#define VT1724_MPU_RX_EMPTY 0x08 +#define VT1724_MPU_RX_FULL 0x10 + +#define VT1724_REG_MPU_FIFO_WM 0x0e /*byte set the high/low watermarks for RX/TX fifos*/ +#define VT1724_MPU_RX_FIFO 0x20 //1=rx fifo watermark 0=tx fifo watermark +#define VT1724_MPU_FIFO_MASK 0x1f + +#define VT1724_REG_I2C_DEV_ADDR 0x10 /* byte */ +#define VT1724_I2C_WRITE 0x01 /* write direction */ +#define VT1724_REG_I2C_BYTE_ADDR 0x11 /* byte */ +#define VT1724_REG_I2C_DATA 0x12 /* byte */ +#define VT1724_REG_I2C_CTRL 0x13 /* byte */ +#define VT1724_I2C_EEPROM 0x80 /* 1 = EEPROM exists */ +#define VT1724_I2C_BUSY 0x01 /* busy bit */ + +#define VT1724_REG_GPIO_DATA 0x14 /* word */ +#define VT1724_REG_GPIO_WRITE_MASK 0x16 /* word */ +#define VT1724_REG_GPIO_DIRECTION 0x18 /* dword? (3 bytes) 0=input 1=output. + bit3 - during reset used for Eeprom power-on strapping + if TESTEN# pin active, bit 2 always input*/ +#define VT1724_REG_POWERDOWN 0x1c +#define VT1724_REG_GPIO_DATA_22 0x1e /* byte direction for GPIO 16:22 */ +#define VT1724_REG_GPIO_WRITE_MASK_22 0x1f /* byte write mask for GPIO 16:22 */ + + +/* + * Professional multi-track direct control registers + */ + +#define ICEMT1724(ice, x) ((ice)->profi_port + VT1724_MT_##x) + +#define VT1724_MT_IRQ 0x00 /* byte - interrupt mask */ +#define VT1724_MULTI_PDMA4 0x80 /* SPDIF Out / PDMA4 */ +#define VT1724_MULTI_PDMA3 0x40 /* PDMA3 */ +#define VT1724_MULTI_PDMA2 0x20 /* PDMA2 */ +#define VT1724_MULTI_PDMA1 0x10 /* PDMA1 */ +#define VT1724_MULTI_FIFO_ERR 0x08 /* DMA FIFO underrun/overrun. */ +#define VT1724_MULTI_RDMA1 0x04 /* RDMA1 (S/PDIF input) */ +#define VT1724_MULTI_RDMA0 0x02 /* RMDA0 */ +#define VT1724_MULTI_PDMA0 0x01 /* MC Interleave/PDMA0 */ + +#define VT1724_MT_RATE 0x01 /* byte - sampling rate select */ +#define VT1724_SPDIF_MASTER 0x10 /* S/PDIF input is master clock */ +#define VT1724_MT_I2S_FORMAT 0x02 /* byte - I2S data format */ +#define VT1724_MT_I2S_MCLK_128X 0x08 +#define VT1724_MT_I2S_FORMAT_MASK 0x03 +#define VT1724_MT_I2S_FORMAT_I2S 0x00 +#define VT1724_MT_DMA_INT_MASK 0x03 /* byte -DMA Interrupt Mask */ +/* lool to VT1724_MULTI_* */ +#define VT1724_MT_AC97_INDEX 0x04 /* byte - AC'97 index */ +#define VT1724_MT_AC97_CMD 0x05 /* byte - AC'97 command & status */ +#define VT1724_AC97_COLD 0x80 /* cold reset */ +#define VT1724_AC97_WARM 0x40 /* warm reset */ +#define VT1724_AC97_WRITE 0x20 /* W: write, R: write in progress */ +#define VT1724_AC97_READ 0x10 /* W: read, R: read in progress */ +#define VT1724_AC97_READY 0x08 /* codec ready status bit */ +#define VT1724_AC97_ID_MASK 0x03 /* codec id mask */ +#define VT1724_MT_AC97_DATA 0x06 /* word - AC'97 data */ +#define VT1724_MT_PLAYBACK_ADDR 0x10 /* dword - playback address */ +#define VT1724_MT_PLAYBACK_SIZE 0x14 /* dword - playback size */ +#define VT1724_MT_DMA_CONTROL 0x18 /* byte - control */ +#define VT1724_PDMA4_START 0x80 /* SPDIF out / PDMA4 start */ +#define VT1724_PDMA3_START 0x40 /* PDMA3 start */ +#define VT1724_PDMA2_START 0x20 /* PDMA2 start */ +#define VT1724_PDMA1_START 0x10 /* PDMA1 start */ +#define VT1724_RDMA1_START 0x04 /* RDMA1 start */ +#define VT1724_RDMA0_START 0x02 /* RMDA0 start */ +#define VT1724_PDMA0_START 0x01 /* MC Interleave / PDMA0 start */ +#define VT1724_MT_BURST 0x19 /* Interleaved playback DMA Active streams / PCI burst size */ +#define VT1724_MT_DMA_FIFO_ERR 0x1a /*Global playback and record DMA FIFO Underrun/Overrun */ +#define VT1724_PDMA4_UNDERRUN 0x80 +#define VT1724_PDMA2_UNDERRUN 0x40 +#define VT1724_PDMA3_UNDERRUN 0x20 +#define VT1724_PDMA1_UNDERRUN 0x10 +#define VT1724_RDMA1_UNDERRUN 0x04 +#define VT1724_RDMA0_UNDERRUN 0x02 +#define VT1724_PDMA0_UNDERRUN 0x01 +#define VT1724_MT_DMA_PAUSE 0x1b /*Global playback and record DMA FIFO pause/resume */ +#define VT1724_PDMA4_PAUSE 0x80 +#define VT1724_PDMA3_PAUSE 0x40 +#define VT1724_PDMA2_PAUSE 0x20 +#define VT1724_PDMA1_PAUSE 0x10 +#define VT1724_RDMA1_PAUSE 0x04 +#define VT1724_RDMA0_PAUSE 0x02 +#define VT1724_PDMA0_PAUSE 0x01 +#define VT1724_MT_PLAYBACK_COUNT 0x1c /* word - playback count */ +#define VT1724_MT_CAPTURE_ADDR 0x20 /* dword - capture address */ +#define VT1724_MT_CAPTURE_SIZE 0x24 /* word - capture size */ +#define VT1724_MT_CAPTURE_COUNT 0x26 /* word - capture count */ + +#define VT1724_MT_ROUTE_PLAYBACK 0x2c /* word */ + +#define VT1724_MT_RDMA1_ADDR 0x30 /* dword - RDMA1 capture address */ +#define VT1724_MT_RDMA1_SIZE 0x34 /* word - RDMA1 capture size */ +#define VT1724_MT_RDMA1_COUNT 0x36 /* word - RDMA1 capture count */ + +#define VT1724_MT_SPDIF_CTRL 0x3c /* word */ +#define VT1724_MT_MONITOR_PEAKINDEX 0x3e /* byte */ +#define VT1724_MT_MONITOR_PEAKDATA 0x3f /* byte */ + +/* concurrent stereo channels */ +#define VT1724_MT_PDMA4_ADDR 0x40 /* dword */ +#define VT1724_MT_PDMA4_SIZE 0x44 /* word */ +#define VT1724_MT_PDMA4_COUNT 0x46 /* word */ +#define VT1724_MT_PDMA3_ADDR 0x50 /* dword */ +#define VT1724_MT_PDMA3_SIZE 0x54 /* word */ +#define VT1724_MT_PDMA3_COUNT 0x56 /* word */ +#define VT1724_MT_PDMA2_ADDR 0x60 /* dword */ +#define VT1724_MT_PDMA2_SIZE 0x64 /* word */ +#define VT1724_MT_PDMA2_COUNT 0x66 /* word */ +#define VT1724_MT_PDMA1_ADDR 0x70 /* dword */ +#define VT1724_MT_PDMA1_SIZE 0x74 /* word */ +#define VT1724_MT_PDMA1_COUNT 0x76 /* word */ + + +unsigned char snd_vt1724_read_i2c(struct snd_ice1712 *ice, unsigned char dev, unsigned char addr); +void snd_vt1724_write_i2c(struct snd_ice1712 *ice, unsigned char dev, unsigned char addr, unsigned char data); + +#endif /* __SOUND_VT1724_H */ diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c new file mode 100644 index 000000000..26f5d5528 --- /dev/null +++ b/sound/pci/ice1712/ews.c @@ -0,0 +1,1070 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for Terratec EWS88MT/D, EWX24/96, DMX 6Fire + * + * Copyright (c) 2000 Jaroslav Kysela + * 2002 Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "ews.h" + +#define SND_CS8404 +#include + +enum { + EWS_I2C_CS8404 = 0, EWS_I2C_PCF1, EWS_I2C_PCF2, + EWS_I2C_88D = 0, + EWS_I2C_6FIRE = 0 +}; + + +/* additional i2c devices for EWS boards */ +struct ews_spec { + struct snd_i2c_device *i2cdevs[3]; +}; + +/* + * access via i2c mode (for EWX 24/96, EWS 88MT&D) + */ + +/* send SDA and SCL */ +static void ewx_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned char tmp = 0; + if (clk) + tmp |= ICE1712_EWX2496_SERIAL_CLOCK; + if (data) + tmp |= ICE1712_EWX2496_SERIAL_DATA; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); +} + +static int ewx_i2c_getclock(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_CLOCK ? 1 : 0; +} + +static int ewx_i2c_getdata(struct snd_i2c_bus *bus, int ack) +{ + struct snd_ice1712 *ice = bus->private_data; + int bit; + /* set RW pin to low */ + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_RW); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, 0); + if (ack) + udelay(5); + bit = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_DATA ? 1 : 0; + /* set RW pin to high */ + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, ICE1712_EWX2496_RW); + /* reset write mask */ + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_SERIAL_CLOCK); + return bit; +} + +static void ewx_i2c_start(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned char mask; + + snd_ice1712_save_gpio_status(ice); + /* set RW high */ + mask = ICE1712_EWX2496_RW; + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWX2496: + mask |= ICE1712_EWX2496_AK4524_CS; /* CS high also */ + break; + case ICE1712_SUBDEVICE_DMX6FIRE: + mask |= ICE1712_6FIRE_AK4524_CS_MASK; /* CS high also */ + break; + } + snd_ice1712_gpio_write_bits(ice, mask, mask); +} + +static void ewx_i2c_stop(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + snd_ice1712_restore_gpio_status(ice); +} + +static void ewx_i2c_direction(struct snd_i2c_bus *bus, int clock, int data) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned char mask = 0; + + if (clock) + mask |= ICE1712_EWX2496_SERIAL_CLOCK; /* write SCL */ + if (data) + mask |= ICE1712_EWX2496_SERIAL_DATA; /* write SDA */ + ice->gpio.direction &= ~(ICE1712_EWX2496_SERIAL_CLOCK|ICE1712_EWX2496_SERIAL_DATA); + ice->gpio.direction |= mask; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, ice->gpio.direction); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~mask); +} + +static struct snd_i2c_bit_ops snd_ice1712_ewx_cs8427_bit_ops = { + .start = ewx_i2c_start, + .stop = ewx_i2c_stop, + .direction = ewx_i2c_direction, + .setlines = ewx_i2c_setlines, + .getclock = ewx_i2c_getclock, + .getdata = ewx_i2c_getdata, +}; + + +/* + * AK4524 access + */ + +/* AK4524 chip select; address 0x48 bit 0-3 */ +static int snd_ice1712_ews88mt_chip_select(struct snd_ice1712 *ice, int chip_mask) +{ + struct ews_spec *spec = ice->spec; + unsigned char data, ndata; + + if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f)) + return -EINVAL; + snd_i2c_lock(ice->i2c); + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1) + goto __error; + ndata = (data & 0xf0) | chip_mask; + if (ndata != data) + if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2], &ndata, 1) + != 1) + goto __error; + snd_i2c_unlock(ice->i2c); + return 0; + + __error: + snd_i2c_unlock(ice->i2c); + dev_err(ice->card->dev, + "AK4524 chip select failed, check cable to the front module\n"); + return -EIO; +} + +/* start callback for EWS88MT, needs to select a certain chip mask */ +static void ews88mt_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + unsigned char tmp; + /* assert AK4524 CS */ + if (snd_ice1712_ews88mt_chip_select(ice, ~(1 << chip) & 0x0f) < 0) + dev_err(ice->card->dev, "fatal error (ews88mt chip select)\n"); + snd_ice1712_save_gpio_status(ice); + tmp = ICE1712_EWS88_SERIAL_DATA | + ICE1712_EWS88_SERIAL_CLOCK | + ICE1712_EWS88_RW; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, + ice->gpio.direction | tmp); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp); +} + +/* stop callback for EWS88MT, needs to deselect chip mask */ +static void ews88mt_ak4524_unlock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + snd_ice1712_restore_gpio_status(ice); + udelay(1); + snd_ice1712_ews88mt_chip_select(ice, 0x0f); +} + +/* start callback for EWX24/96 */ +static void ewx2496_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + unsigned char tmp; + snd_ice1712_save_gpio_status(ice); + tmp = ICE1712_EWX2496_SERIAL_DATA | + ICE1712_EWX2496_SERIAL_CLOCK | + ICE1712_EWX2496_AK4524_CS | + ICE1712_EWX2496_RW; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, + ice->gpio.direction | tmp); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp); +} + +/* start callback for DMX 6fire */ +static void dmx6fire_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ak4xxx_private *priv = (void *)ak->private_value[0]; + struct snd_ice1712 *ice = ak->private_data[0]; + unsigned char tmp; + snd_ice1712_save_gpio_status(ice); + tmp = priv->cs_mask = priv->cs_addr = (1 << chip) & ICE1712_6FIRE_AK4524_CS_MASK; + tmp |= ICE1712_6FIRE_SERIAL_DATA | + ICE1712_6FIRE_SERIAL_CLOCK | + ICE1712_6FIRE_RW; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, + ice->gpio.direction | tmp); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp); +} + +/* + * CS8404 interface on EWS88MT/D + */ + +static void snd_ice1712_ews_cs8404_spdif_write(struct snd_ice1712 *ice, unsigned char bits) +{ + struct ews_spec *spec = ice->spec; + unsigned char bytes[2]; + + snd_i2c_lock(ice->i2c); + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWS88MT: + case ICE1712_SUBDEVICE_EWS88MT_NEW: + case ICE1712_SUBDEVICE_PHASE88: + case ICE1712_SUBDEVICE_TS88: + if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_CS8404], &bits, 1) + != 1) + goto _error; + break; + case ICE1712_SUBDEVICE_EWS88D: + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], bytes, 2) + != 2) + goto _error; + if (bits != bytes[1]) { + bytes[1] = bits; + if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_88D], + bytes, 2) != 2) + goto _error; + } + break; + } + _error: + snd_i2c_unlock(ice->i2c); +} + +/* + */ + +static void ews88_spdif_default_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + snd_cs8404_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_bits); +} + +static int ews88_spdif_default_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + int change; + + val = snd_cs8404_encode_spdif_bits(&ucontrol->value.iec958); + scoped_guard(spinlock_irq, &ice->reg_lock) { + change = ice->spdif.cs8403_bits != val; + ice->spdif.cs8403_bits = val; + if (!change || ice->playback_pro_substream) + return change; + } + snd_ice1712_ews_cs8404_spdif_write(ice, val); + return change; +} + +static void ews88_spdif_stream_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + snd_cs8404_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_stream_bits); +} + +static int ews88_spdif_stream_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + int change; + + val = snd_cs8404_encode_spdif_bits(&ucontrol->value.iec958); + scoped_guard(spinlock_irq, &ice->reg_lock) { + change = ice->spdif.cs8403_stream_bits != val; + ice->spdif.cs8403_stream_bits = val; + if (!change || ice->playback_pro_substream) + return change; + } + snd_ice1712_ews_cs8404_spdif_write(ice, val); + return change; +} + + +/* open callback */ +static void ews88_open_spdif(struct snd_ice1712 *ice, struct snd_pcm_substream *substream) +{ + ice->spdif.cs8403_stream_bits = ice->spdif.cs8403_bits; +} + +/* set up SPDIF for EWS88MT / EWS88D */ +static void ews88_setup_spdif(struct snd_ice1712 *ice, int rate) +{ + unsigned char tmp; + int change; + + scoped_guard(spinlock_irqsave, &ice->reg_lock) { + tmp = ice->spdif.cs8403_stream_bits; + if (tmp & 0x10) /* consumer */ + tmp &= (tmp & 0x01) ? ~0x06 : ~0x60; + switch (rate) { + case 32000: tmp |= (tmp & 0x01) ? 0x02 : 0x00; break; + case 44100: tmp |= (tmp & 0x01) ? 0x06 : 0x40; break; + case 48000: tmp |= (tmp & 0x01) ? 0x04 : 0x20; break; + default: tmp |= (tmp & 0x01) ? 0x06 : 0x40; break; + } + change = ice->spdif.cs8403_stream_bits != tmp; + ice->spdif.cs8403_stream_bits = tmp; + } + if (change) + snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &ice->spdif.stream_ctl->id); + snd_ice1712_ews_cs8404_spdif_write(ice, tmp); +} + + +/* + */ +static const struct snd_akm4xxx akm_ews88mt = { + .num_adcs = 8, + .num_dacs = 8, + .type = SND_AK4524, + .ops = { + .lock = ews88mt_ak4524_lock, + .unlock = ews88mt_ak4524_unlock + } +}; + +static const struct snd_ak4xxx_private akm_ews88mt_priv = { + .caddr = 2, + .cif = 1, /* CIF high */ + .data_mask = ICE1712_EWS88_SERIAL_DATA, + .clk_mask = ICE1712_EWS88_SERIAL_CLOCK, + .cs_mask = 0, + .cs_addr = 0, + .cs_none = 0, /* no chip select on gpio */ + .add_flags = ICE1712_EWS88_RW, /* set rw bit high */ + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_ewx2496 = { + .num_adcs = 2, + .num_dacs = 2, + .type = SND_AK4524, + .ops = { + .lock = ewx2496_ak4524_lock + } +}; + +static const struct snd_ak4xxx_private akm_ewx2496_priv = { + .caddr = 2, + .cif = 1, /* CIF high */ + .data_mask = ICE1712_EWS88_SERIAL_DATA, + .clk_mask = ICE1712_EWS88_SERIAL_CLOCK, + .cs_mask = ICE1712_EWX2496_AK4524_CS, + .cs_addr = ICE1712_EWX2496_AK4524_CS, + .cs_none = 0, + .add_flags = ICE1712_EWS88_RW, /* set rw bit high */ + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_6fire = { + .num_adcs = 6, + .num_dacs = 6, + .type = SND_AK4524, + .ops = { + .lock = dmx6fire_ak4524_lock + } +}; + +static const struct snd_ak4xxx_private akm_6fire_priv = { + .caddr = 2, + .cif = 1, /* CIF high */ + .data_mask = ICE1712_6FIRE_SERIAL_DATA, + .clk_mask = ICE1712_6FIRE_SERIAL_CLOCK, + .cs_mask = 0, + .cs_addr = 0, /* set later */ + .cs_none = 0, + .add_flags = ICE1712_6FIRE_RW, /* set rw bit high */ + .mask_flags = 0, +}; + +/* + * initialize the chip + */ + +/* 6fire specific */ +#define PCF9554_REG_INPUT 0 +#define PCF9554_REG_OUTPUT 1 +#define PCF9554_REG_POLARITY 2 +#define PCF9554_REG_CONFIG 3 + +static int snd_ice1712_6fire_write_pca(struct snd_ice1712 *ice, unsigned char reg, unsigned char data); + +static int snd_ice1712_ews_init(struct snd_ice1712 *ice) +{ + int err; + struct snd_akm4xxx *ak; + struct ews_spec *spec; + + /* set the analog DACs */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWX2496: + ice->num_total_dacs = 2; + ice->num_total_adcs = 2; + break; + case ICE1712_SUBDEVICE_EWS88MT: + case ICE1712_SUBDEVICE_EWS88MT_NEW: + case ICE1712_SUBDEVICE_PHASE88: + case ICE1712_SUBDEVICE_TS88: + ice->num_total_dacs = 8; + ice->num_total_adcs = 8; + break; + case ICE1712_SUBDEVICE_EWS88D: + /* Note: not analog but ADAT I/O */ + ice->num_total_dacs = 8; + ice->num_total_adcs = 8; + break; + case ICE1712_SUBDEVICE_DMX6FIRE: + ice->num_total_dacs = 6; + ice->num_total_adcs = 6; + break; + } + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + /* create i2c */ + err = snd_i2c_bus_create(ice->card, "ICE1712 GPIO 1", NULL, &ice->i2c); + if (err < 0) { + dev_err(ice->card->dev, "unable to create I2C bus\n"); + return err; + } + ice->i2c->private_data = ice; + ice->i2c->hw_ops.bit = &snd_ice1712_ewx_cs8427_bit_ops; + + /* create i2c devices */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DMX6FIRE: + err = snd_i2c_device_create(ice->i2c, "PCF9554", + ICE1712_6FIRE_PCF9554_ADDR, + &spec->i2cdevs[EWS_I2C_6FIRE]); + if (err < 0) { + dev_err(ice->card->dev, + "PCF9554 initialization failed\n"); + return err; + } + snd_ice1712_6fire_write_pca(ice, PCF9554_REG_CONFIG, 0x80); + break; + case ICE1712_SUBDEVICE_EWS88MT: + case ICE1712_SUBDEVICE_EWS88MT_NEW: + case ICE1712_SUBDEVICE_PHASE88: + case ICE1712_SUBDEVICE_TS88: + + err = snd_i2c_device_create(ice->i2c, "CS8404", + ICE1712_EWS88MT_CS8404_ADDR, + &spec->i2cdevs[EWS_I2C_CS8404]); + if (err < 0) + return err; + err = snd_i2c_device_create(ice->i2c, "PCF8574 (1st)", + ICE1712_EWS88MT_INPUT_ADDR, + &spec->i2cdevs[EWS_I2C_PCF1]); + if (err < 0) + return err; + err = snd_i2c_device_create(ice->i2c, "PCF8574 (2nd)", + ICE1712_EWS88MT_OUTPUT_ADDR, + &spec->i2cdevs[EWS_I2C_PCF2]); + if (err < 0) + return err; + /* Check if the front module is connected */ + err = snd_ice1712_ews88mt_chip_select(ice, 0x0f); + if (err < 0) + return err; + break; + case ICE1712_SUBDEVICE_EWS88D: + err = snd_i2c_device_create(ice->i2c, "PCF8575", + ICE1712_EWS88D_PCF_ADDR, + &spec->i2cdevs[EWS_I2C_88D]); + if (err < 0) + return err; + break; + } + + /* set up SPDIF interface */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWX2496: + err = snd_ice1712_init_cs8427(ice, CS8427_BASE_ADDR); + if (err < 0) + return err; + snd_cs8427_reg_write(ice->cs8427, CS8427_REG_RECVERRMASK, CS8427_UNLOCK | CS8427_CONF | CS8427_BIP | CS8427_PAR); + break; + case ICE1712_SUBDEVICE_DMX6FIRE: + err = snd_ice1712_init_cs8427(ice, ICE1712_6FIRE_CS8427_ADDR); + if (err < 0) + return err; + snd_cs8427_reg_write(ice->cs8427, CS8427_REG_RECVERRMASK, CS8427_UNLOCK | CS8427_CONF | CS8427_BIP | CS8427_PAR); + break; + case ICE1712_SUBDEVICE_EWS88MT: + case ICE1712_SUBDEVICE_EWS88MT_NEW: + case ICE1712_SUBDEVICE_PHASE88: + case ICE1712_SUBDEVICE_TS88: + case ICE1712_SUBDEVICE_EWS88D: + /* set up CS8404 */ + ice->spdif.ops.open = ews88_open_spdif; + ice->spdif.ops.setup_rate = ews88_setup_spdif; + ice->spdif.ops.default_get = ews88_spdif_default_get; + ice->spdif.ops.default_put = ews88_spdif_default_put; + ice->spdif.ops.stream_get = ews88_spdif_stream_get; + ice->spdif.ops.stream_put = ews88_spdif_stream_put; + /* Set spdif defaults */ + snd_ice1712_ews_cs8404_spdif_write(ice, ice->spdif.cs8403_bits); + break; + } + + /* no analog? */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWS88D: + return 0; + } + + /* analog section */ + ak = ice->akm = kzalloc_obj(struct snd_akm4xxx); + if (! ak) + return -ENOMEM; + ice->akm_codecs = 1; + + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWS88MT: + case ICE1712_SUBDEVICE_EWS88MT_NEW: + case ICE1712_SUBDEVICE_PHASE88: + case ICE1712_SUBDEVICE_TS88: + err = snd_ice1712_akm4xxx_init(ak, &akm_ews88mt, &akm_ews88mt_priv, ice); + break; + case ICE1712_SUBDEVICE_EWX2496: + err = snd_ice1712_akm4xxx_init(ak, &akm_ewx2496, &akm_ewx2496_priv, ice); + break; + case ICE1712_SUBDEVICE_DMX6FIRE: + err = snd_ice1712_akm4xxx_init(ak, &akm_6fire, &akm_6fire_priv, ice); + break; + default: + err = 0; + } + + return err; +} + +/* + * EWX 24/96 specific controls + */ + +/* i/o sensitivity - this callback is shared among other devices, too */ +static int snd_ice1712_ewx_io_sense_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo){ + + static const char * const texts[2] = { + "+4dBu", "-10dBV", + }; + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_ice1712_ewx_io_sense_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char mask = kcontrol->private_value & 0xff; + + snd_ice1712_save_gpio_status(ice); + ucontrol->value.enumerated.item[0] = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & mask ? 1 : 0; + snd_ice1712_restore_gpio_status(ice); + return 0; +} + +static int snd_ice1712_ewx_io_sense_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char mask = kcontrol->private_value & 0xff; + int val, nval; + + if (kcontrol->private_value & (1 << 31)) + return -EPERM; + nval = ucontrol->value.enumerated.item[0] ? mask : 0; + snd_ice1712_save_gpio_status(ice); + val = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA); + nval |= val & ~mask; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, nval); + snd_ice1712_restore_gpio_status(ice); + return val != nval; +} + +static const struct snd_kcontrol_new snd_ice1712_ewx2496_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Sensitivity Switch", + .info = snd_ice1712_ewx_io_sense_info, + .get = snd_ice1712_ewx_io_sense_get, + .put = snd_ice1712_ewx_io_sense_put, + .private_value = ICE1712_EWX2496_AIN_SEL, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output Sensitivity Switch", + .info = snd_ice1712_ewx_io_sense_info, + .get = snd_ice1712_ewx_io_sense_get, + .put = snd_ice1712_ewx_io_sense_put, + .private_value = ICE1712_EWX2496_AOUT_SEL, + }, +}; + + +/* + * EWS88MT specific controls + */ +/* analog output sensitivity;; address 0x48 bit 6 */ +static int snd_ice1712_ews88mt_output_sense_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + unsigned char data; + + snd_i2c_lock(ice->i2c); + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + snd_i2c_unlock(ice->i2c); + ucontrol->value.enumerated.item[0] = data & ICE1712_EWS88MT_OUTPUT_SENSE ? 1 : 0; /* high = -10dBV, low = +4dBu */ + return 0; +} + +/* analog output sensitivity;; address 0x48 bit 6 */ +static int snd_ice1712_ews88mt_output_sense_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + unsigned char data, ndata; + + snd_i2c_lock(ice->i2c); + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + ndata = (data & ~ICE1712_EWS88MT_OUTPUT_SENSE) | (ucontrol->value.enumerated.item[0] ? ICE1712_EWS88MT_OUTPUT_SENSE : 0); + if (ndata != data && snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2], + &ndata, 1) != 1) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + snd_i2c_unlock(ice->i2c); + return ndata != data; +} + +/* analog input sensitivity; address 0x46 */ +static int snd_ice1712_ews88mt_input_sense_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + int channel = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned char data; + + if (snd_BUG_ON(channel < 0 || channel > 7)) + return 0; + snd_i2c_lock(ice->i2c); + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF1], &data, 1) != 1) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + /* reversed; high = +4dBu, low = -10dBV */ + ucontrol->value.enumerated.item[0] = data & (1 << channel) ? 0 : 1; + snd_i2c_unlock(ice->i2c); + return 0; +} + +/* analog output sensitivity; address 0x46 */ +static int snd_ice1712_ews88mt_input_sense_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + int channel = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned char data, ndata; + + if (snd_BUG_ON(channel < 0 || channel > 7)) + return 0; + snd_i2c_lock(ice->i2c); + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF1], &data, 1) != 1) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + ndata = (data & ~(1 << channel)) | (ucontrol->value.enumerated.item[0] ? 0 : (1 << channel)); + if (ndata != data && snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF1], + &ndata, 1) != 1) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + snd_i2c_unlock(ice->i2c); + return ndata != data; +} + +static const struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Sensitivity Switch", + .info = snd_ice1712_ewx_io_sense_info, + .get = snd_ice1712_ews88mt_input_sense_get, + .put = snd_ice1712_ews88mt_input_sense_put, + .count = 8, +}; + +static const struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output Sensitivity Switch", + .info = snd_ice1712_ewx_io_sense_info, + .get = snd_ice1712_ews88mt_output_sense_get, + .put = snd_ice1712_ews88mt_output_sense_put, +}; + + +/* + * EWS88D specific controls + */ + +#define snd_ice1712_ews88d_control_info snd_ctl_boolean_mono_info + +static int snd_ice1712_ews88d_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + unsigned char data[2]; + + snd_i2c_lock(ice->i2c); + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], data, 2) != 2) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + snd_i2c_unlock(ice->i2c); + data[0] = (data[shift >> 3] >> (shift & 7)) & 0x01; + if (invert) + data[0] ^= 0x01; + ucontrol->value.integer.value[0] = data[0]; + return 0; +} + +static int snd_ice1712_ews88d_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + unsigned char data[2], ndata[2]; + int change; + + snd_i2c_lock(ice->i2c); + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], data, 2) != 2) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + ndata[shift >> 3] = data[shift >> 3] & ~(1 << (shift & 7)); + if (invert) { + if (! ucontrol->value.integer.value[0]) + ndata[shift >> 3] |= (1 << (shift & 7)); + } else { + if (ucontrol->value.integer.value[0]) + ndata[shift >> 3] |= (1 << (shift & 7)); + } + change = (data[shift >> 3] != ndata[shift >> 3]); + if (change && + snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_88D], data, 2) != 2) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + snd_i2c_unlock(ice->i2c); + return change; +} + +#define EWS88D_CONTROL(xiface, xname, xshift, xinvert, xaccess) \ +{ .iface = xiface,\ + .name = xname,\ + .access = xaccess,\ + .info = snd_ice1712_ews88d_control_info,\ + .get = snd_ice1712_ews88d_control_get,\ + .put = snd_ice1712_ews88d_control_put,\ + .private_value = xshift | (xinvert << 8),\ +} + +static const struct snd_kcontrol_new snd_ice1712_ews88d_controls[] = { + EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "IEC958 Input Optical", 0, 1, 0), /* inverted */ + EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "ADAT Output Optical", 1, 0, 0), + EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "ADAT External Master Clock", 2, 0, 0), + EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "Enable ADAT", 3, 0, 0), + EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "ADAT Through", 4, 1, 0), +}; + + +/* + * DMX 6Fire specific controls + */ + +static int snd_ice1712_6fire_read_pca(struct snd_ice1712 *ice, unsigned char reg) +{ + unsigned char byte; + struct ews_spec *spec = ice->spec; + + snd_i2c_lock(ice->i2c); + byte = reg; + if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_6FIRE], &byte, 1) != 1) { + snd_i2c_unlock(ice->i2c); + dev_err(ice->card->dev, "cannot send pca\n"); + return -EIO; + } + + byte = 0; + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_6FIRE], &byte, 1) != 1) { + snd_i2c_unlock(ice->i2c); + dev_err(ice->card->dev, "cannot read pca\n"); + return -EIO; + } + snd_i2c_unlock(ice->i2c); + return byte; +} + +static int snd_ice1712_6fire_write_pca(struct snd_ice1712 *ice, unsigned char reg, unsigned char data) +{ + unsigned char bytes[2]; + struct ews_spec *spec = ice->spec; + + snd_i2c_lock(ice->i2c); + bytes[0] = reg; + bytes[1] = data; + if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_6FIRE], bytes, 2) != 2) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + snd_i2c_unlock(ice->i2c); + return 0; +} + +#define snd_ice1712_6fire_control_info snd_ctl_boolean_mono_info + +static int snd_ice1712_6fire_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + int data; + + data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT); + if (data < 0) + return data; + data = (data >> shift) & 1; + if (invert) + data ^= 1; + ucontrol->value.integer.value[0] = data; + return 0; +} + +static int snd_ice1712_6fire_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + int data, ndata; + + data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT); + if (data < 0) + return data; + ndata = data & ~(1 << shift); + if (ucontrol->value.integer.value[0]) + ndata |= (1 << shift); + if (invert) + ndata ^= (1 << shift); + if (data != ndata) { + snd_ice1712_6fire_write_pca(ice, PCF9554_REG_OUTPUT, (unsigned char)ndata); + return 1; + } + return 0; +} + +static int snd_ice1712_6fire_select_input_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "Internal", "Front Input", "Rear Input", "Wave Table" + }; + return snd_ctl_enum_info(uinfo, 1, 4, texts); +} + +static int snd_ice1712_6fire_select_input_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int data; + + data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT); + if (data < 0) + return data; + ucontrol->value.integer.value[0] = data & 3; + return 0; +} + +static int snd_ice1712_6fire_select_input_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int data, ndata; + + data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT); + if (data < 0) + return data; + ndata = data & ~3; + ndata |= (ucontrol->value.integer.value[0] & 3); + if (data != ndata) { + snd_ice1712_6fire_write_pca(ice, PCF9554_REG_OUTPUT, (unsigned char)ndata); + return 1; + } + return 0; +} + + +#define DMX6FIRE_CONTROL(xname, xshift, xinvert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\ + .name = xname,\ + .info = snd_ice1712_6fire_control_info,\ + .get = snd_ice1712_6fire_control_get,\ + .put = snd_ice1712_6fire_control_put,\ + .private_value = xshift | (xinvert << 8),\ +} + +static const struct snd_kcontrol_new snd_ice1712_6fire_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Input Select", + .info = snd_ice1712_6fire_select_input_info, + .get = snd_ice1712_6fire_select_input_get, + .put = snd_ice1712_6fire_select_input_put, + }, + DMX6FIRE_CONTROL("Front Digital Input Switch", 2, 1), + // DMX6FIRE_CONTROL("Master Clock Select", 3, 0), + DMX6FIRE_CONTROL("Optical Digital Input Switch", 4, 0), + DMX6FIRE_CONTROL("Phono Analog Input Switch", 5, 0), + DMX6FIRE_CONTROL("Breakbox LED", 6, 0), +}; + + +static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice) +{ + unsigned int idx; + int err; + + /* all terratec cards have spdif, but cs8427 module builds it's own controls */ + if (ice->cs8427 == NULL) { + err = snd_ice1712_spdif_build_controls(ice); + if (err < 0) + return err; + } + + /* ak4524 controls */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWX2496: + case ICE1712_SUBDEVICE_EWS88MT: + case ICE1712_SUBDEVICE_EWS88MT_NEW: + case ICE1712_SUBDEVICE_PHASE88: + case ICE1712_SUBDEVICE_TS88: + case ICE1712_SUBDEVICE_DMX6FIRE: + err = snd_ice1712_akm4xxx_build_controls(ice); + if (err < 0) + return err; + break; + } + + /* card specific controls */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWX2496: + for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_ewx2496_controls); idx++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ewx2496_controls[idx], ice)); + if (err < 0) + return err; + } + break; + case ICE1712_SUBDEVICE_EWS88MT: + case ICE1712_SUBDEVICE_EWS88MT_NEW: + case ICE1712_SUBDEVICE_PHASE88: + case ICE1712_SUBDEVICE_TS88: + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88mt_input_sense, ice)); + if (err < 0) + return err; + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88mt_output_sense, ice)); + if (err < 0) + return err; + break; + case ICE1712_SUBDEVICE_EWS88D: + for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_ews88d_controls); idx++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88d_controls[idx], ice)); + if (err < 0) + return err; + } + break; + case ICE1712_SUBDEVICE_DMX6FIRE: + for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_6fire_controls); idx++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_6fire_controls[idx], ice)); + if (err < 0) + return err; + } + break; + } + return 0; +} + + +/* entry point */ +struct snd_ice1712_card_info snd_ice1712_ews_cards[] = { + { + .subvendor = ICE1712_SUBDEVICE_EWX2496, + .name = "TerraTec EWX24/96", + .model = "ewx2496", + .chip_init = snd_ice1712_ews_init, + .build_controls = snd_ice1712_ews_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_EWS88MT, + .name = "TerraTec EWS88MT", + .model = "ews88mt", + .chip_init = snd_ice1712_ews_init, + .build_controls = snd_ice1712_ews_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_EWS88MT_NEW, + .name = "TerraTec EWS88MT", + .model = "ews88mt_new", + .chip_init = snd_ice1712_ews_init, + .build_controls = snd_ice1712_ews_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_PHASE88, + .name = "TerraTec Phase88", + .model = "phase88", + .chip_init = snd_ice1712_ews_init, + .build_controls = snd_ice1712_ews_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_TS88, + .name = "terrasoniq TS88", + .model = "phase88", + .chip_init = snd_ice1712_ews_init, + .build_controls = snd_ice1712_ews_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_EWS88D, + .name = "TerraTec EWS88D", + .model = "ews88d", + .chip_init = snd_ice1712_ews_init, + .build_controls = snd_ice1712_ews_add_controls, + }, + { + .subvendor = ICE1712_SUBDEVICE_DMX6FIRE, + .name = "TerraTec DMX6Fire", + .model = "dmx6fire", + .chip_init = snd_ice1712_ews_init, + .build_controls = snd_ice1712_ews_add_controls, + .mpu401_1_name = "MIDI-Front DMX6fire", + .mpu401_2_name = "Wavetable DMX6fire", + .mpu401_2_info_flags = MPU401_INFO_OUTPUT, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/ews.h b/sound/pci/ice1712/ews.h new file mode 100644 index 000000000..aec8dc69a --- /dev/null +++ b/sound/pci/ice1712/ews.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_EWS_H +#define __SOUND_EWS_H + +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for Terratec EWS88MT/D, EWX24/96, DMX 6Fire + * + * Copyright (c) 2000 Jaroslav Kysela + * 2002 Takashi Iwai + */ + +#define EWS_DEVICE_DESC \ + "{TerraTec,EWX 24/96},"\ + "{TerraTec,EWS 88MT},"\ + "{TerraTec,EWS 88D},"\ + "{TerraTec,DMX 6Fire},"\ + "{TerraTec,Phase 88}," \ + "{terrasoniq,TS 88}," + +#define ICE1712_SUBDEVICE_EWX2496 0x3b153011 +#define ICE1712_SUBDEVICE_EWS88MT 0x3b151511 +#define ICE1712_SUBDEVICE_EWS88MT_NEW 0x3b152511 +#define ICE1712_SUBDEVICE_EWS88D 0x3b152b11 +#define ICE1712_SUBDEVICE_DMX6FIRE 0x3b153811 +#define ICE1712_SUBDEVICE_PHASE88 0x3b155111 +#define ICE1712_SUBDEVICE_TS88 0x3b157c11 + +/* entry point */ +extern struct snd_ice1712_card_info snd_ice1712_ews_cards[]; + + +/* TerraTec EWX 24/96 configuration definitions */ + +#define ICE1712_EWX2496_AK4524_CS 0x01 /* AK4524 chip select; low = active */ +#define ICE1712_EWX2496_AIN_SEL 0x02 /* input sensitivity switch; high = louder */ +#define ICE1712_EWX2496_AOUT_SEL 0x04 /* output sensitivity switch; high = louder */ +#define ICE1712_EWX2496_RW 0x08 /* read/write switch for i2c; high = write */ +#define ICE1712_EWX2496_SERIAL_DATA 0x10 /* i2c & ak4524 data */ +#define ICE1712_EWX2496_SERIAL_CLOCK 0x20 /* i2c & ak4524 clock */ +#define ICE1712_EWX2496_TX2 0x40 /* MIDI2 (not used) */ +#define ICE1712_EWX2496_RX2 0x80 /* MIDI2 (not used) */ + +/* TerraTec EWS 88MT/D configuration definitions */ +/* RW, SDA snd SCLK are identical with EWX24/96 */ +#define ICE1712_EWS88_CS8414_RATE 0x07 /* CS8414 sample rate: gpio 0-2 */ +#define ICE1712_EWS88_RW 0x08 /* read/write switch for i2c; high = write */ +#define ICE1712_EWS88_SERIAL_DATA 0x10 /* i2c & ak4524 data */ +#define ICE1712_EWS88_SERIAL_CLOCK 0x20 /* i2c & ak4524 clock */ +#define ICE1712_EWS88_TX2 0x40 /* MIDI2 (only on 88D) */ +#define ICE1712_EWS88_RX2 0x80 /* MIDI2 (only on 88D) */ + +/* i2c address */ +#define ICE1712_EWS88MT_CS8404_ADDR (0x40>>1) +#define ICE1712_EWS88MT_INPUT_ADDR (0x46>>1) +#define ICE1712_EWS88MT_OUTPUT_ADDR (0x48>>1) +#define ICE1712_EWS88MT_OUTPUT_SENSE 0x40 /* mask */ +#define ICE1712_EWS88D_PCF_ADDR (0x40>>1) + +/* TerraTec DMX 6Fire configuration definitions */ +#define ICE1712_6FIRE_AK4524_CS_MASK 0x07 /* AK4524 chip select #1-#3 */ +#define ICE1712_6FIRE_RW 0x08 /* read/write switch for i2c; high = write */ +#define ICE1712_6FIRE_SERIAL_DATA 0x10 /* i2c & ak4524 data */ +#define ICE1712_6FIRE_SERIAL_CLOCK 0x20 /* i2c & ak4524 clock */ +#define ICE1712_6FIRE_TX2 0x40 /* MIDI2 */ +#define ICE1712_6FIRE_RX2 0x80 /* MIDI2 */ + +#define ICE1712_6FIRE_PCF9554_ADDR (0x40>>1) +#define ICE1712_6FIRE_CS8427_ADDR (0x22) + +#endif /* __SOUND_EWS_H */ diff --git a/sound/pci/ice1712/hoontech.c b/sound/pci/ice1712/hoontech.c new file mode 100644 index 000000000..5294c9dd7 --- /dev/null +++ b/sound/pci/ice1712/hoontech.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for Hoontech STDSP24 + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" +#include "hoontech.h" + +/* Hoontech-specific setting */ +struct hoontech_spec { + unsigned char boxbits[4]; + unsigned int config; + unsigned short boxconfig[4]; +}; + +static void snd_ice1712_stdsp24_gpio_write(struct snd_ice1712 *ice, unsigned char byte) +{ + byte |= ICE1712_STDSP24_CLOCK_BIT; + udelay(100); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte); + byte &= ~ICE1712_STDSP24_CLOCK_BIT; + udelay(100); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte); + byte |= ICE1712_STDSP24_CLOCK_BIT; + udelay(100); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte); +} + +static void snd_ice1712_stdsp24_darear(struct snd_ice1712 *ice, int activate) +{ + struct hoontech_spec *spec = ice->spec; + + guard(mutex)(&ice->gpio_mutex); + ICE1712_STDSP24_0_DAREAR(spec->boxbits, activate); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]); +} + +static void snd_ice1712_stdsp24_mute(struct snd_ice1712 *ice, int activate) +{ + struct hoontech_spec *spec = ice->spec; + + guard(mutex)(&ice->gpio_mutex); + ICE1712_STDSP24_3_MUTE(spec->boxbits, activate); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]); +} + +static void snd_ice1712_stdsp24_insel(struct snd_ice1712 *ice, int activate) +{ + struct hoontech_spec *spec = ice->spec; + + guard(mutex)(&ice->gpio_mutex); + ICE1712_STDSP24_3_INSEL(spec->boxbits, activate); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]); +} + +static void snd_ice1712_stdsp24_box_channel(struct snd_ice1712 *ice, int box, int chn, int activate) +{ + struct hoontech_spec *spec = ice->spec; + + guard(mutex)(&ice->gpio_mutex); + + /* select box */ + ICE1712_STDSP24_0_BOX(spec->boxbits, box); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]); + + /* prepare for write */ + if (chn == 3) + ICE1712_STDSP24_2_CHN4(spec->boxbits, 0); + ICE1712_STDSP24_2_MIDI1(spec->boxbits, activate); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]); + + ICE1712_STDSP24_1_CHN1(spec->boxbits, 1); + ICE1712_STDSP24_1_CHN2(spec->boxbits, 1); + ICE1712_STDSP24_1_CHN3(spec->boxbits, 1); + ICE1712_STDSP24_2_CHN4(spec->boxbits, 1); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[1]); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]); + udelay(100); + if (chn == 3) { + ICE1712_STDSP24_2_CHN4(spec->boxbits, 0); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]); + } else { + switch (chn) { + case 0: ICE1712_STDSP24_1_CHN1(spec->boxbits, 0); break; + case 1: ICE1712_STDSP24_1_CHN2(spec->boxbits, 0); break; + case 2: ICE1712_STDSP24_1_CHN3(spec->boxbits, 0); break; + } + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[1]); + } + udelay(100); + ICE1712_STDSP24_1_CHN1(spec->boxbits, 1); + ICE1712_STDSP24_1_CHN2(spec->boxbits, 1); + ICE1712_STDSP24_1_CHN3(spec->boxbits, 1); + ICE1712_STDSP24_2_CHN4(spec->boxbits, 1); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[1]); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]); + udelay(100); + + ICE1712_STDSP24_2_MIDI1(spec->boxbits, 0); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]); +} + +static void snd_ice1712_stdsp24_box_midi(struct snd_ice1712 *ice, int box, int master) +{ + struct hoontech_spec *spec = ice->spec; + + guard(mutex)(&ice->gpio_mutex); + + /* select box */ + ICE1712_STDSP24_0_BOX(spec->boxbits, box); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]); + + ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 1); + ICE1712_STDSP24_2_MIDI1(spec->boxbits, master); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]); + + udelay(100); + + ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 0); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]); + + mdelay(10); + + ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 1); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]); +} + +static void snd_ice1712_stdsp24_midi2(struct snd_ice1712 *ice, int activate) +{ + struct hoontech_spec *spec = ice->spec; + + guard(mutex)(&ice->gpio_mutex); + ICE1712_STDSP24_3_MIDI2(spec->boxbits, activate); + snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]); +} + +static int hoontech_init(struct snd_ice1712 *ice, bool staudio) +{ + struct hoontech_spec *spec; + int box, chn; + + ice->num_total_dacs = 8; + ice->num_total_adcs = 8; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + ICE1712_STDSP24_SET_ADDR(spec->boxbits, 0); + ICE1712_STDSP24_CLOCK(spec->boxbits, 0, 1); + ICE1712_STDSP24_0_BOX(spec->boxbits, 0); + ICE1712_STDSP24_0_DAREAR(spec->boxbits, 0); + + ICE1712_STDSP24_SET_ADDR(spec->boxbits, 1); + ICE1712_STDSP24_CLOCK(spec->boxbits, 1, 1); + ICE1712_STDSP24_1_CHN1(spec->boxbits, 1); + ICE1712_STDSP24_1_CHN2(spec->boxbits, 1); + ICE1712_STDSP24_1_CHN3(spec->boxbits, 1); + + ICE1712_STDSP24_SET_ADDR(spec->boxbits, 2); + ICE1712_STDSP24_CLOCK(spec->boxbits, 2, 1); + ICE1712_STDSP24_2_CHN4(spec->boxbits, 1); + ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 1); + ICE1712_STDSP24_2_MIDI1(spec->boxbits, 0); + + ICE1712_STDSP24_SET_ADDR(spec->boxbits, 3); + ICE1712_STDSP24_CLOCK(spec->boxbits, 3, 1); + ICE1712_STDSP24_3_MIDI2(spec->boxbits, 0); + ICE1712_STDSP24_3_MUTE(spec->boxbits, 1); + ICE1712_STDSP24_3_INSEL(spec->boxbits, 0); + + /* let's go - activate only functions in first box */ + if (staudio) + spec->config = ICE1712_STDSP24_MUTE; + else + spec->config = 0; + /* ICE1712_STDSP24_MUTE | + ICE1712_STDSP24_INSEL | + ICE1712_STDSP24_DAREAR; */ + /* These boxconfigs have caused problems in the past. + * The code is not optimal, but should now enable a working config to + * be achieved. + * ** MIDI IN can only be configured on one box ** + * ICE1712_STDSP24_BOX_MIDI1 needs to be set for that box. + * Tests on a ADAC2000 box suggest the box config flags do not + * work as would be expected, and the inputs are crossed. + * Setting ICE1712_STDSP24_BOX_MIDI1 and ICE1712_STDSP24_BOX_MIDI2 + * on the same box connects MIDI-In to both 401 uarts; both outputs + * are then active on all boxes. + * The default config here sets up everything on the first box. + * Alan Horstmann 5.2.2008 + */ + spec->boxconfig[0] = ICE1712_STDSP24_BOX_CHN1 | + ICE1712_STDSP24_BOX_CHN2 | + ICE1712_STDSP24_BOX_CHN3 | + ICE1712_STDSP24_BOX_CHN4 | + ICE1712_STDSP24_BOX_MIDI1 | + ICE1712_STDSP24_BOX_MIDI2; + if (staudio) { + spec->boxconfig[1] = + spec->boxconfig[2] = + spec->boxconfig[3] = spec->boxconfig[0]; + } else { + spec->boxconfig[1] = + spec->boxconfig[2] = + spec->boxconfig[3] = 0; + } + + snd_ice1712_stdsp24_darear(ice, + (spec->config & ICE1712_STDSP24_DAREAR) ? 1 : 0); + snd_ice1712_stdsp24_mute(ice, + (spec->config & ICE1712_STDSP24_MUTE) ? 1 : 0); + snd_ice1712_stdsp24_insel(ice, + (spec->config & ICE1712_STDSP24_INSEL) ? 1 : 0); + for (box = 0; box < 4; box++) { + if (spec->boxconfig[box] & ICE1712_STDSP24_BOX_MIDI2) + snd_ice1712_stdsp24_midi2(ice, 1); + for (chn = 0; chn < 4; chn++) + snd_ice1712_stdsp24_box_channel(ice, box, chn, + (spec->boxconfig[box] & (1 << chn)) ? 1 : 0); + if (spec->boxconfig[box] & ICE1712_STDSP24_BOX_MIDI1) + snd_ice1712_stdsp24_box_midi(ice, box, 1); + } + + return 0; +} + +static int snd_ice1712_hoontech_init(struct snd_ice1712 *ice) +{ + return hoontech_init(ice, false); +} + +static int snd_ice1712_staudio_init(struct snd_ice1712 *ice) +{ + return hoontech_init(ice, true); +} + +/* + * AK4524 access + */ + +/* start callback for STDSP24 with modified hardware */ +static void stdsp24_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + unsigned char tmp; + snd_ice1712_save_gpio_status(ice); + tmp = ICE1712_STDSP24_SERIAL_DATA | + ICE1712_STDSP24_SERIAL_CLOCK | + ICE1712_STDSP24_AK4524_CS; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, + ice->gpio.direction | tmp); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp); +} + +static int snd_ice1712_value_init(struct snd_ice1712 *ice) +{ + /* Hoontech STDSP24 with modified hardware */ + static const struct snd_akm4xxx akm_stdsp24_mv = { + .num_adcs = 2, + .num_dacs = 2, + .type = SND_AK4524, + .ops = { + .lock = stdsp24_ak4524_lock + } + }; + + static const struct snd_ak4xxx_private akm_stdsp24_mv_priv = { + .caddr = 2, + .cif = 1, /* CIF high */ + .data_mask = ICE1712_STDSP24_SERIAL_DATA, + .clk_mask = ICE1712_STDSP24_SERIAL_CLOCK, + .cs_mask = ICE1712_STDSP24_AK4524_CS, + .cs_addr = ICE1712_STDSP24_AK4524_CS, + .cs_none = 0, + .add_flags = 0, + }; + + int err; + struct snd_akm4xxx *ak; + + /* set the analog DACs */ + ice->num_total_dacs = 2; + + /* set the analog ADCs */ + ice->num_total_adcs = 2; + + /* analog section */ + ak = ice->akm = kmalloc_obj(struct snd_akm4xxx); + if (! ak) + return -ENOMEM; + ice->akm_codecs = 1; + + err = snd_ice1712_akm4xxx_init(ak, &akm_stdsp24_mv, &akm_stdsp24_mv_priv, ice); + if (err < 0) + return err; + + /* ak4524 controls */ + return snd_ice1712_akm4xxx_build_controls(ice); +} + +static int snd_ice1712_ez8_init(struct snd_ice1712 *ice) +{ + ice->gpio.write_mask = ice->eeprom.gpiomask; + ice->gpio.direction = ice->eeprom.gpiodir; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ice->eeprom.gpiomask); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, ice->eeprom.gpiodir); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, ice->eeprom.gpiostate); + return 0; +} + + +/* entry point */ +struct snd_ice1712_card_info snd_ice1712_hoontech_cards[] = { + { + .subvendor = ICE1712_SUBDEVICE_STDSP24, + .name = "Hoontech SoundTrack Audio DSP24", + .model = "dsp24", + .chip_init = snd_ice1712_hoontech_init, + .mpu401_1_name = "MIDI-1 Hoontech/STA DSP24", + .mpu401_2_name = "MIDI-2 Hoontech/STA DSP24", + }, + { + .subvendor = ICE1712_SUBDEVICE_STDSP24_VALUE, /* a dummy id */ + .name = "Hoontech SoundTrack Audio DSP24 Value", + .model = "dsp24_value", + .chip_init = snd_ice1712_value_init, + }, + { + .subvendor = ICE1712_SUBDEVICE_STDSP24_MEDIA7_1, + .name = "Hoontech STA DSP24 Media 7.1", + .model = "dsp24_71", + .chip_init = snd_ice1712_hoontech_init, + }, + { + .subvendor = ICE1712_SUBDEVICE_EVENT_EZ8, /* a dummy id */ + .name = "Event Electronics EZ8", + .model = "ez8", + .chip_init = snd_ice1712_ez8_init, + }, + { + /* STAudio ADCIII has the same SSID as Hoontech StA DSP24, + * thus identified only via the explicit model option + */ + .subvendor = ICE1712_SUBDEVICE_STAUDIO_ADCIII, /* a dummy id */ + .name = "STAudio ADCIII", + .model = "staudio", + .chip_init = snd_ice1712_staudio_init, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/hoontech.h b/sound/pci/ice1712/hoontech.h new file mode 100644 index 000000000..89404ceec --- /dev/null +++ b/sound/pci/ice1712/hoontech.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_HOONTECH_H +#define __SOUND_HOONTECH_H + +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for Hoontech STDSP24 + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +#define HOONTECH_DEVICE_DESC \ + "{Hoontech,SoundTrack DSP 24}," \ + "{Hoontech,SoundTrack DSP 24 Value}," \ + "{Hoontech,SoundTrack DSP 24 Media 7.1}," \ + "{Event Electronics,EZ8}," + +#define ICE1712_SUBDEVICE_STDSP24 0x12141217 /* Hoontech SoundTrack Audio DSP 24 */ +#define ICE1712_SUBDEVICE_STDSP24_VALUE 0x00010010 /* A dummy id for Hoontech SoundTrack Audio DSP 24 Value */ +#define ICE1712_SUBDEVICE_STDSP24_MEDIA7_1 0x16141217 /* Hoontech ST Audio DSP24 Media 7.1 */ +#define ICE1712_SUBDEVICE_EVENT_EZ8 0x00010001 /* A dummy id for EZ8 */ +#define ICE1712_SUBDEVICE_STAUDIO_ADCIII 0x00010002 /* A dummy id for STAudio ADCIII */ + +extern struct snd_ice1712_card_info snd_ice1712_hoontech_cards[]; + + +/* Hoontech SoundTrack Audio DSP 24 GPIO definitions */ + +#define ICE1712_STDSP24_0_BOX(r, x) r[0] = ((r[0] & ~3) | ((x)&3)) +#define ICE1712_STDSP24_0_DAREAR(r, x) r[0] = ((r[0] & ~4) | (((x)&1)<<2)) +#define ICE1712_STDSP24_1_CHN1(r, x) r[1] = ((r[1] & ~1) | ((x)&1)) +#define ICE1712_STDSP24_1_CHN2(r, x) r[1] = ((r[1] & ~2) | (((x)&1)<<1)) +#define ICE1712_STDSP24_1_CHN3(r, x) r[1] = ((r[1] & ~4) | (((x)&1)<<2)) +#define ICE1712_STDSP24_2_CHN4(r, x) r[2] = ((r[2] & ~1) | ((x)&1)) +#define ICE1712_STDSP24_2_MIDIIN(r, x) r[2] = ((r[2] & ~2) | (((x)&1)<<1)) +#define ICE1712_STDSP24_2_MIDI1(r, x) r[2] = ((r[2] & ~4) | (((x)&1)<<2)) +#define ICE1712_STDSP24_3_MIDI2(r, x) r[3] = ((r[3] & ~1) | ((x)&1)) +#define ICE1712_STDSP24_3_MUTE(r, x) r[3] = ((r[3] & ~2) | (((x)&1)<<1)) +#define ICE1712_STDSP24_3_INSEL(r, x) r[3] = ((r[3] & ~4) | (((x)&1)<<2)) +#define ICE1712_STDSP24_SET_ADDR(r, a) r[a&3] = ((r[a&3] & ~0x18) | (((a)&3)<<3)) +#define ICE1712_STDSP24_CLOCK(r, a, c) r[a&3] = ((r[a&3] & ~0x20) | (((c)&1)<<5)) +#define ICE1712_STDSP24_CLOCK_BIT (1<<5) + +/* Hoontech SoundTrack Audio DSP 24 box configuration definitions */ + +#define ICE1712_STDSP24_DAREAR (1<<0) +#define ICE1712_STDSP24_MUTE (1<<1) +#define ICE1712_STDSP24_INSEL (1<<2) + +#define ICE1712_STDSP24_BOX_CHN1 (1<<0) /* input channel 1 */ +#define ICE1712_STDSP24_BOX_CHN2 (1<<1) /* input channel 2 */ +#define ICE1712_STDSP24_BOX_CHN3 (1<<2) /* input channel 3 */ +#define ICE1712_STDSP24_BOX_CHN4 (1<<3) /* input channel 4 */ +#define ICE1712_STDSP24_BOX_MIDI1 (1<<8) +#define ICE1712_STDSP24_BOX_MIDI2 (1<<9) + +/* Hoontech SoundTrack Audio DSP 24 Value definitions for modified hardware */ + +#define ICE1712_STDSP24_AK4524_CS 0x03 /* AK4524 chip select; low = active */ +#define ICE1712_STDSP24_SERIAL_DATA 0x0c /* ak4524 data */ +#define ICE1712_STDSP24_SERIAL_CLOCK 0x30 /* ak4524 clock */ + +#endif /* __SOUND_HOONTECH_H */ diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c new file mode 100644 index 000000000..d6bfd8fb2 --- /dev/null +++ b/sound/pci/ice1712/ice1712.c @@ -0,0 +1,2733 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +/* + NOTES: + - spdif nonaudio consumer mode does not work (at least with my + Sony STR-DB830) +*/ + +/* + * Changes: + * + * 2002.09.09 Takashi Iwai + * split the code to several files. each low-level routine + * is stored in the local file and called from registration + * function from card_info struct. + * + * 2002.11.26 James Stafford + * Added support for VT1724 (Envy24HT) + * I have left out support for 176.4 and 192 KHz for the moment. + * I also haven't done anything with the internal S/PDIF transmitter or the MPU-401 + * + * 2003.02.20 Taksahi Iwai + * Split vt1724 part to an independent driver. + * The GPIO is accessed through the callback functions now. + * + * 2004.03.31 Doug McLain + * Added support for Event Electronics EZ8 card to hoontech.c. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" + +/* lowlevel routines */ +#include "delta.h" +#include "ews.h" +#include "hoontech.h" + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("ICEnsemble ICE1712 (Envy24)"); +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_PNP;/* Enable this card */ +static char *model[SNDRV_CARDS]; +static bool omni[SNDRV_CARDS]; /* Delta44 & 66 Omni I/O support */ +static int cs8427_timeout[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 500}; /* CS8427 S/PDIF transceiver reset timeout value in msec */ +static int dxr_enable[SNDRV_CARDS]; /* DXR enable for DMX6FIRE */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ICE1712 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ICE1712 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ICE1712 soundcard."); +module_param_array(omni, bool, NULL, 0444); +MODULE_PARM_DESC(omni, "Enable Midiman M-Audio Delta Omni I/O support."); +module_param_array(cs8427_timeout, int, NULL, 0444); +MODULE_PARM_DESC(cs8427_timeout, "Define reset timeout for cs8427 chip in msec resolution."); +module_param_array(model, charp, NULL, 0444); +MODULE_PARM_DESC(model, "Use the given board model."); +module_param_array(dxr_enable, int, NULL, 0444); +MODULE_PARM_DESC(dxr_enable, "Enable DXR support for Terratec DMX6FIRE."); + + +static const struct pci_device_id snd_ice1712_ids[] = { + { PCI_VDEVICE(ICE, PCI_DEVICE_ID_ICE_1712) }, /* ICE1712 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_ice1712_ids); + +static int snd_ice1712_build_pro_mixer(struct snd_ice1712 *ice); +static int snd_ice1712_build_controls(struct snd_ice1712 *ice); + +static int PRO_RATE_LOCKED; +static int PRO_RATE_RESET = 1; +static unsigned int PRO_RATE_DEFAULT = 44100; + +/* + * Basic I/O + */ + +/* check whether the clock mode is spdif-in */ +static inline int is_spdif_master(struct snd_ice1712 *ice) +{ + return (inb(ICEMT(ice, RATE)) & ICE1712_SPDIF_MASTER) ? 1 : 0; +} + +static inline int is_pro_rate_locked(struct snd_ice1712 *ice) +{ + return is_spdif_master(ice) || PRO_RATE_LOCKED; +} + +static inline void snd_ice1712_ds_write(struct snd_ice1712 *ice, u8 channel, u8 addr, u32 data) +{ + outb((channel << 4) | addr, ICEDS(ice, INDEX)); + outl(data, ICEDS(ice, DATA)); +} + +static inline u32 snd_ice1712_ds_read(struct snd_ice1712 *ice, u8 channel, u8 addr) +{ + outb((channel << 4) | addr, ICEDS(ice, INDEX)); + return inl(ICEDS(ice, DATA)); +} + +static void snd_ice1712_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct snd_ice1712 *ice = ac97->private_data; + int tm; + unsigned char old_cmd = 0; + + for (tm = 0; tm < 0x10000; tm++) { + old_cmd = inb(ICEREG(ice, AC97_CMD)); + if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ)) + continue; + if (!(old_cmd & ICE1712_AC97_READY)) + continue; + break; + } + outb(reg, ICEREG(ice, AC97_INDEX)); + outw(val, ICEREG(ice, AC97_DATA)); + old_cmd &= ~(ICE1712_AC97_PBK_VSR | ICE1712_AC97_CAP_VSR); + outb(old_cmd | ICE1712_AC97_WRITE, ICEREG(ice, AC97_CMD)); + for (tm = 0; tm < 0x10000; tm++) + if ((inb(ICEREG(ice, AC97_CMD)) & ICE1712_AC97_WRITE) == 0) + break; +} + +static unsigned short snd_ice1712_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct snd_ice1712 *ice = ac97->private_data; + int tm; + unsigned char old_cmd = 0; + + for (tm = 0; tm < 0x10000; tm++) { + old_cmd = inb(ICEREG(ice, AC97_CMD)); + if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ)) + continue; + if (!(old_cmd & ICE1712_AC97_READY)) + continue; + break; + } + outb(reg, ICEREG(ice, AC97_INDEX)); + outb(old_cmd | ICE1712_AC97_READ, ICEREG(ice, AC97_CMD)); + for (tm = 0; tm < 0x10000; tm++) + if ((inb(ICEREG(ice, AC97_CMD)) & ICE1712_AC97_READ) == 0) + break; + if (tm >= 0x10000) /* timeout */ + return ~0; + return inw(ICEREG(ice, AC97_DATA)); +} + +/* + * pro ac97 section + */ + +static void snd_ice1712_pro_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct snd_ice1712 *ice = ac97->private_data; + int tm; + unsigned char old_cmd = 0; + + for (tm = 0; tm < 0x10000; tm++) { + old_cmd = inb(ICEMT(ice, AC97_CMD)); + if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ)) + continue; + if (!(old_cmd & ICE1712_AC97_READY)) + continue; + break; + } + outb(reg, ICEMT(ice, AC97_INDEX)); + outw(val, ICEMT(ice, AC97_DATA)); + old_cmd &= ~(ICE1712_AC97_PBK_VSR | ICE1712_AC97_CAP_VSR); + outb(old_cmd | ICE1712_AC97_WRITE, ICEMT(ice, AC97_CMD)); + for (tm = 0; tm < 0x10000; tm++) + if ((inb(ICEMT(ice, AC97_CMD)) & ICE1712_AC97_WRITE) == 0) + break; +} + + +static unsigned short snd_ice1712_pro_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct snd_ice1712 *ice = ac97->private_data; + int tm; + unsigned char old_cmd = 0; + + for (tm = 0; tm < 0x10000; tm++) { + old_cmd = inb(ICEMT(ice, AC97_CMD)); + if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ)) + continue; + if (!(old_cmd & ICE1712_AC97_READY)) + continue; + break; + } + outb(reg, ICEMT(ice, AC97_INDEX)); + outb(old_cmd | ICE1712_AC97_READ, ICEMT(ice, AC97_CMD)); + for (tm = 0; tm < 0x10000; tm++) + if ((inb(ICEMT(ice, AC97_CMD)) & ICE1712_AC97_READ) == 0) + break; + if (tm >= 0x10000) /* timeout */ + return ~0; + return inw(ICEMT(ice, AC97_DATA)); +} + +/* + * consumer ac97 digital mix + */ +#define snd_ice1712_digmix_route_ac97_info snd_ctl_boolean_mono_info + +static int snd_ice1712_digmix_route_ac97_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = inb(ICEMT(ice, MONITOR_ROUTECTRL)) & ICE1712_ROUTE_AC97 ? 1 : 0; + return 0; +} + +static int snd_ice1712_digmix_route_ac97_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char val, nval; + + guard(spinlock_irq)(&ice->reg_lock); + val = inb(ICEMT(ice, MONITOR_ROUTECTRL)); + nval = val & ~ICE1712_ROUTE_AC97; + if (ucontrol->value.integer.value[0]) + nval |= ICE1712_ROUTE_AC97; + outb(nval, ICEMT(ice, MONITOR_ROUTECTRL)); + return val != nval; +} + +static const struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Mixer To AC97", + .info = snd_ice1712_digmix_route_ac97_info, + .get = snd_ice1712_digmix_route_ac97_get, + .put = snd_ice1712_digmix_route_ac97_put, +}; + + +/* + * gpio operations + */ +static void snd_ice1712_set_gpio_dir(struct snd_ice1712 *ice, unsigned int data) +{ + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, data); + inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */ +} + +static unsigned int snd_ice1712_get_gpio_dir(struct snd_ice1712 *ice) +{ + return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DIRECTION); +} + +static unsigned int snd_ice1712_get_gpio_mask(struct snd_ice1712 *ice) +{ + return snd_ice1712_read(ice, ICE1712_IREG_GPIO_WRITE_MASK); +} + +static void snd_ice1712_set_gpio_mask(struct snd_ice1712 *ice, unsigned int data) +{ + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, data); + inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */ +} + +static unsigned int snd_ice1712_get_gpio_data(struct snd_ice1712 *ice) +{ + return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA); +} + +static void snd_ice1712_set_gpio_data(struct snd_ice1712 *ice, unsigned int val) +{ + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, val); + inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */ +} + +/* + * + * CS8427 interface + * + */ + +/* + * change the input clock selection + * spdif_clock = 1 - IEC958 input, 0 - Envy24 + */ +static int snd_ice1712_cs8427_set_input_clock(struct snd_ice1712 *ice, int spdif_clock) +{ + unsigned char reg[2] = { 0x80 | 4, 0 }; /* CS8427 auto increment | register number 4 + data */ + unsigned char val, nval; + int res = 0; + + snd_i2c_lock(ice->i2c); + if (snd_i2c_sendbytes(ice->cs8427, reg, 1) != 1) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + if (snd_i2c_readbytes(ice->cs8427, &val, 1) != 1) { + snd_i2c_unlock(ice->i2c); + return -EIO; + } + nval = val & 0xf0; + if (spdif_clock) + nval |= 0x01; + else + nval |= 0x04; + if (val != nval) { + reg[1] = nval; + if (snd_i2c_sendbytes(ice->cs8427, reg, 2) != 2) { + res = -EIO; + } else { + res++; + } + } + snd_i2c_unlock(ice->i2c); + return res; +} + +/* + * spdif callbacks + */ +static void open_cs8427(struct snd_ice1712 *ice, struct snd_pcm_substream *substream) +{ + snd_cs8427_iec958_active(ice->cs8427, 1); +} + +static void close_cs8427(struct snd_ice1712 *ice, struct snd_pcm_substream *substream) +{ + snd_cs8427_iec958_active(ice->cs8427, 0); +} + +static void setup_cs8427(struct snd_ice1712 *ice, int rate) +{ + snd_cs8427_iec958_pcm(ice->cs8427, rate); +} + +/* + * create and initialize callbacks for cs8427 interface + */ +int snd_ice1712_init_cs8427(struct snd_ice1712 *ice, int addr) +{ + int err; + + err = snd_cs8427_create(ice->i2c, addr, + (ice->cs8427_timeout * HZ) / 1000, &ice->cs8427); + if (err < 0) { + dev_err(ice->card->dev, "CS8427 initialization failed\n"); + return err; + } + ice->spdif.ops.open = open_cs8427; + ice->spdif.ops.close = close_cs8427; + ice->spdif.ops.setup_rate = setup_cs8427; + return 0; +} + +static void snd_ice1712_set_input_clock_source(struct snd_ice1712 *ice, int spdif_is_master) +{ + /* change CS8427 clock source too */ + if (ice->cs8427) + snd_ice1712_cs8427_set_input_clock(ice, spdif_is_master); + /* notify ak4524 chip as well */ + if (spdif_is_master) { + unsigned int i; + for (i = 0; i < ice->akm_codecs; i++) { + if (ice->akm[i].ops.set_rate_val) + ice->akm[i].ops.set_rate_val(&ice->akm[i], 0); + } + } +} + +/* + * Interrupt handler + */ + +static irqreturn_t snd_ice1712_interrupt(int irq, void *dev_id) +{ + struct snd_ice1712 *ice = dev_id; + unsigned char status; + int handled = 0; + + while (1) { + status = inb(ICEREG(ice, IRQSTAT)); + if (status == 0) + break; + handled = 1; + if (status & ICE1712_IRQ_MPU1) { + if (ice->rmidi[0]) + snd_mpu401_uart_interrupt(irq, ice->rmidi[0]->private_data); + outb(ICE1712_IRQ_MPU1, ICEREG(ice, IRQSTAT)); + status &= ~ICE1712_IRQ_MPU1; + } + if (status & ICE1712_IRQ_TIMER) + outb(ICE1712_IRQ_TIMER, ICEREG(ice, IRQSTAT)); + if (status & ICE1712_IRQ_MPU2) { + if (ice->rmidi[1]) + snd_mpu401_uart_interrupt(irq, ice->rmidi[1]->private_data); + outb(ICE1712_IRQ_MPU2, ICEREG(ice, IRQSTAT)); + status &= ~ICE1712_IRQ_MPU2; + } + if (status & ICE1712_IRQ_PROPCM) { + unsigned char mtstat = inb(ICEMT(ice, IRQ)); + if (mtstat & ICE1712_MULTI_PBKSTATUS) { + if (ice->playback_pro_substream) + snd_pcm_period_elapsed(ice->playback_pro_substream); + outb(ICE1712_MULTI_PBKSTATUS, ICEMT(ice, IRQ)); + } + if (mtstat & ICE1712_MULTI_CAPSTATUS) { + if (ice->capture_pro_substream) + snd_pcm_period_elapsed(ice->capture_pro_substream); + outb(ICE1712_MULTI_CAPSTATUS, ICEMT(ice, IRQ)); + } + } + if (status & ICE1712_IRQ_FM) + outb(ICE1712_IRQ_FM, ICEREG(ice, IRQSTAT)); + if (status & ICE1712_IRQ_PBKDS) { + u32 idx; + u16 pbkstatus; + struct snd_pcm_substream *substream; + pbkstatus = inw(ICEDS(ice, INTSTAT)); + /* dev_dbg(ice->card->dev, "pbkstatus = 0x%x\n", pbkstatus); */ + for (idx = 0; idx < 6; idx++) { + if ((pbkstatus & (3 << (idx * 2))) == 0) + continue; + substream = ice->playback_con_substream_ds[idx]; + if (substream != NULL) + snd_pcm_period_elapsed(substream); + outw(3 << (idx * 2), ICEDS(ice, INTSTAT)); + } + outb(ICE1712_IRQ_PBKDS, ICEREG(ice, IRQSTAT)); + } + if (status & ICE1712_IRQ_CONCAP) { + if (ice->capture_con_substream) + snd_pcm_period_elapsed(ice->capture_con_substream); + outb(ICE1712_IRQ_CONCAP, ICEREG(ice, IRQSTAT)); + } + if (status & ICE1712_IRQ_CONPBK) { + if (ice->playback_con_substream) + snd_pcm_period_elapsed(ice->playback_con_substream); + outb(ICE1712_IRQ_CONPBK, ICEREG(ice, IRQSTAT)); + } + } + return IRQ_RETVAL(handled); +} + + +/* + * PCM part - consumer I/O + */ + +static int snd_ice1712_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + int result = 0; + u32 tmp; + + guard(spinlock)(&ice->reg_lock); + tmp = snd_ice1712_read(ice, ICE1712_IREG_PBK_CTRL); + if (cmd == SNDRV_PCM_TRIGGER_START) { + tmp |= 1; + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + tmp &= ~1; + } else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) { + tmp |= 2; + } else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) { + tmp &= ~2; + } else { + result = -EINVAL; + } + snd_ice1712_write(ice, ICE1712_IREG_PBK_CTRL, tmp); + return result; +} + +static int snd_ice1712_playback_ds_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + int result = 0; + u32 tmp; + + guard(spinlock)(&ice->reg_lock); + tmp = snd_ice1712_ds_read(ice, substream->number * 2, ICE1712_DSC_CONTROL); + if (cmd == SNDRV_PCM_TRIGGER_START) { + tmp |= 1; + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + tmp &= ~1; + } else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) { + tmp |= 2; + } else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) { + tmp &= ~2; + } else { + result = -EINVAL; + } + snd_ice1712_ds_write(ice, substream->number * 2, ICE1712_DSC_CONTROL, tmp); + return result; +} + +static int snd_ice1712_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + int result = 0; + u8 tmp; + + guard(spinlock)(&ice->reg_lock); + tmp = snd_ice1712_read(ice, ICE1712_IREG_CAP_CTRL); + if (cmd == SNDRV_PCM_TRIGGER_START) { + tmp |= 1; + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + tmp &= ~1; + } else { + result = -EINVAL; + } + snd_ice1712_write(ice, ICE1712_IREG_CAP_CTRL, tmp); + return result; +} + +static int snd_ice1712_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u32 period_size, buf_size, rate, tmp; + + period_size = (snd_pcm_lib_period_bytes(substream) >> 2) - 1; + buf_size = snd_pcm_lib_buffer_bytes(substream) - 1; + tmp = 0x0000; + if (snd_pcm_format_width(runtime->format) == 16) + tmp |= 0x10; + if (runtime->channels == 2) + tmp |= 0x08; + rate = (runtime->rate * 8192) / 375; + if (rate > 0x000fffff) + rate = 0x000fffff; + guard(spinlock_irq)(&ice->reg_lock); + outb(0, ice->ddma_port + 15); + outb(ICE1712_DMA_MODE_WRITE | ICE1712_DMA_AUTOINIT, ice->ddma_port + 0x0b); + outl(runtime->dma_addr, ice->ddma_port + 0); + outw(buf_size, ice->ddma_port + 4); + snd_ice1712_write(ice, ICE1712_IREG_PBK_RATE_LO, rate & 0xff); + snd_ice1712_write(ice, ICE1712_IREG_PBK_RATE_MID, (rate >> 8) & 0xff); + snd_ice1712_write(ice, ICE1712_IREG_PBK_RATE_HI, (rate >> 16) & 0xff); + snd_ice1712_write(ice, ICE1712_IREG_PBK_CTRL, tmp); + snd_ice1712_write(ice, ICE1712_IREG_PBK_COUNT_LO, period_size & 0xff); + snd_ice1712_write(ice, ICE1712_IREG_PBK_COUNT_HI, period_size >> 8); + snd_ice1712_write(ice, ICE1712_IREG_PBK_LEFT, 0); + snd_ice1712_write(ice, ICE1712_IREG_PBK_RIGHT, 0); + return 0; +} + +static int snd_ice1712_playback_ds_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u32 period_size, rate, tmp, chn; + + period_size = snd_pcm_lib_period_bytes(substream) - 1; + tmp = 0x0064; + if (snd_pcm_format_width(runtime->format) == 16) + tmp &= ~0x04; + if (runtime->channels == 2) + tmp |= 0x08; + rate = (runtime->rate * 8192) / 375; + if (rate > 0x000fffff) + rate = 0x000fffff; + ice->playback_con_active_buf[substream->number] = 0; + ice->playback_con_virt_addr[substream->number] = runtime->dma_addr; + chn = substream->number * 2; + guard(spinlock_irq)(&ice->reg_lock); + snd_ice1712_ds_write(ice, chn, ICE1712_DSC_ADDR0, runtime->dma_addr); + snd_ice1712_ds_write(ice, chn, ICE1712_DSC_COUNT0, period_size); + snd_ice1712_ds_write(ice, chn, ICE1712_DSC_ADDR1, runtime->dma_addr + (runtime->periods > 1 ? period_size + 1 : 0)); + snd_ice1712_ds_write(ice, chn, ICE1712_DSC_COUNT1, period_size); + snd_ice1712_ds_write(ice, chn, ICE1712_DSC_RATE, rate); + snd_ice1712_ds_write(ice, chn, ICE1712_DSC_VOLUME, 0); + snd_ice1712_ds_write(ice, chn, ICE1712_DSC_CONTROL, tmp); + if (runtime->channels == 2) { + snd_ice1712_ds_write(ice, chn + 1, ICE1712_DSC_RATE, rate); + snd_ice1712_ds_write(ice, chn + 1, ICE1712_DSC_VOLUME, 0); + } + return 0; +} + +static int snd_ice1712_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u32 period_size, buf_size; + u8 tmp; + + period_size = (snd_pcm_lib_period_bytes(substream) >> 2) - 1; + buf_size = snd_pcm_lib_buffer_bytes(substream) - 1; + tmp = 0x06; + if (snd_pcm_format_width(runtime->format) == 16) + tmp &= ~0x04; + if (runtime->channels == 2) + tmp &= ~0x02; + scoped_guard(spinlock_irq, &ice->reg_lock) { + outl(ice->capture_con_virt_addr = runtime->dma_addr, ICEREG(ice, CONCAP_ADDR)); + outw(buf_size, ICEREG(ice, CONCAP_COUNT)); + snd_ice1712_write(ice, ICE1712_IREG_CAP_COUNT_HI, period_size >> 8); + snd_ice1712_write(ice, ICE1712_IREG_CAP_COUNT_LO, period_size & 0xff); + snd_ice1712_write(ice, ICE1712_IREG_CAP_CTRL, tmp); + } + snd_ac97_set_rate(ice->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate); + return 0; +} + +static snd_pcm_uframes_t snd_ice1712_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + size_t ptr; + + if (!(snd_ice1712_read(ice, ICE1712_IREG_PBK_CTRL) & 1)) + return 0; + ptr = runtime->buffer_size - inw(ice->ddma_port + 4); + ptr = bytes_to_frames(substream->runtime, ptr); + if (ptr == runtime->buffer_size) + ptr = 0; + return ptr; +} + +static snd_pcm_uframes_t snd_ice1712_playback_ds_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + u8 addr; + size_t ptr; + + if (!(snd_ice1712_ds_read(ice, substream->number * 2, ICE1712_DSC_CONTROL) & 1)) + return 0; + if (ice->playback_con_active_buf[substream->number]) + addr = ICE1712_DSC_ADDR1; + else + addr = ICE1712_DSC_ADDR0; + ptr = snd_ice1712_ds_read(ice, substream->number * 2, addr) - + ice->playback_con_virt_addr[substream->number]; + ptr = bytes_to_frames(substream->runtime, ptr); + if (ptr == substream->runtime->buffer_size) + ptr = 0; + return ptr; +} + +static snd_pcm_uframes_t snd_ice1712_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(snd_ice1712_read(ice, ICE1712_IREG_CAP_CTRL) & 1)) + return 0; + ptr = inl(ICEREG(ice, CONCAP_ADDR)) - ice->capture_con_virt_addr; + ptr = bytes_to_frames(substream->runtime, ptr); + if (ptr == substream->runtime->buffer_size) + ptr = 0; + return ptr; +} + +static const struct snd_pcm_hardware snd_ice1712_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .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 = (64*1024), + .period_bytes_min = 64, + .period_bytes_max = (64*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_ice1712_playback_ds = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .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 = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_ice1712_capture = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + 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 = (64*1024), + .period_bytes_min = 64, + .period_bytes_max = (64*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_ice1712_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + ice->playback_con_substream = substream; + runtime->hw = snd_ice1712_playback; + return 0; +} + +static int snd_ice1712_playback_ds_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + u32 tmp; + + ice->playback_con_substream_ds[substream->number] = substream; + runtime->hw = snd_ice1712_playback_ds; + guard(spinlock_irq)(&ice->reg_lock); + tmp = inw(ICEDS(ice, INTMASK)) & ~(1 << (substream->number * 2)); + outw(tmp, ICEDS(ice, INTMASK)); + return 0; +} + +static int snd_ice1712_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + ice->capture_con_substream = substream; + runtime->hw = snd_ice1712_capture; + runtime->hw.rates = ice->ac97->rates[AC97_RATES_ADC]; + if (!(runtime->hw.rates & SNDRV_PCM_RATE_8000)) + runtime->hw.rate_min = 48000; + return 0; +} + +static int snd_ice1712_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + ice->playback_con_substream = NULL; + return 0; +} + +static int snd_ice1712_playback_ds_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + u32 tmp; + + guard(spinlock_irq)(&ice->reg_lock); + tmp = inw(ICEDS(ice, INTMASK)) | (3 << (substream->number * 2)); + outw(tmp, ICEDS(ice, INTMASK)); + ice->playback_con_substream_ds[substream->number] = NULL; + return 0; +} + +static int snd_ice1712_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + ice->capture_con_substream = NULL; + return 0; +} + +static const struct snd_pcm_ops snd_ice1712_playback_ops = { + .open = snd_ice1712_playback_open, + .close = snd_ice1712_playback_close, + .prepare = snd_ice1712_playback_prepare, + .trigger = snd_ice1712_playback_trigger, + .pointer = snd_ice1712_playback_pointer, +}; + +static const struct snd_pcm_ops snd_ice1712_playback_ds_ops = { + .open = snd_ice1712_playback_ds_open, + .close = snd_ice1712_playback_ds_close, + .prepare = snd_ice1712_playback_ds_prepare, + .trigger = snd_ice1712_playback_ds_trigger, + .pointer = snd_ice1712_playback_ds_pointer, +}; + +static const struct snd_pcm_ops snd_ice1712_capture_ops = { + .open = snd_ice1712_capture_open, + .close = snd_ice1712_capture_close, + .prepare = snd_ice1712_capture_prepare, + .trigger = snd_ice1712_capture_trigger, + .pointer = snd_ice1712_capture_pointer, +}; + +static int snd_ice1712_pcm(struct snd_ice1712 *ice, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ice->card, "ICE1712 consumer", device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ice1712_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ice1712_capture_ops); + + pcm->private_data = ice; + pcm->info_flags = 0; + strscpy(pcm->name, "ICE1712 consumer"); + ice->pcm = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &ice->pci->dev, 64*1024, 64*1024); + + dev_warn(ice->card->dev, + "Consumer PCM code does not work well at the moment --jk\n"); + + return 0; +} + +static int snd_ice1712_pcm_ds(struct snd_ice1712 *ice, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ice->card, "ICE1712 consumer (DS)", device, 6, 0, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ice1712_playback_ds_ops); + + pcm->private_data = ice; + pcm->info_flags = 0; + strscpy(pcm->name, "ICE1712 consumer (DS)"); + ice->pcm_ds = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &ice->pci->dev, 64*1024, 128*1024); + + return 0; +} + +/* + * PCM code - professional part (multitrack) + */ + +static const unsigned int rates[] = { 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000 }; + +static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static int snd_ice1712_pro_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + { + unsigned int what; + unsigned int old; + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + what = ICE1712_PLAYBACK_PAUSE; + snd_pcm_trigger_done(substream, substream); + guard(spinlock)(&ice->reg_lock); + old = inl(ICEMT(ice, PLAYBACK_CONTROL)); + if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) + old |= what; + else + old &= ~what; + outl(old, ICEMT(ice, PLAYBACK_CONTROL)); + break; + } + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + { + unsigned int what = 0; + unsigned int old; + struct snd_pcm_substream *s; + + snd_pcm_group_for_each_entry(s, substream) { + if (s == ice->playback_pro_substream) { + what |= ICE1712_PLAYBACK_START; + snd_pcm_trigger_done(s, substream); + } else if (s == ice->capture_pro_substream) { + what |= ICE1712_CAPTURE_START_SHADOW; + snd_pcm_trigger_done(s, substream); + } + } + guard(spinlock)(&ice->reg_lock); + old = inl(ICEMT(ice, PLAYBACK_CONTROL)); + if (cmd == SNDRV_PCM_TRIGGER_START) + old |= what; + else + old &= ~what; + outl(old, ICEMT(ice, PLAYBACK_CONTROL)); + break; + } + default: + return -EINVAL; + } + return 0; +} + +/* + */ +static void snd_ice1712_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, int force) +{ + unsigned char val, old; + unsigned int i; + + switch (rate) { + case 8000: val = 6; break; + case 9600: val = 3; break; + case 11025: val = 10; break; + case 12000: val = 2; break; + case 16000: val = 5; break; + case 22050: val = 9; break; + case 24000: val = 1; break; + case 32000: val = 4; break; + case 44100: val = 8; break; + case 48000: val = 0; break; + case 64000: val = 15; break; + case 88200: val = 11; break; + case 96000: val = 7; break; + default: + snd_BUG(); + val = 0; + rate = 48000; + break; + } + + scoped_guard(spinlock_irqsave, &ice->reg_lock) { + if (inb(ICEMT(ice, PLAYBACK_CONTROL)) & (ICE1712_CAPTURE_START_SHADOW| + ICE1712_PLAYBACK_PAUSE| + ICE1712_PLAYBACK_START)) + return; + if (!force && is_pro_rate_locked(ice)) + return; + + old = inb(ICEMT(ice, RATE)); + if (!force && old == val) + return; + + ice->cur_rate = rate; + outb(val, ICEMT(ice, RATE)); + } + + if (ice->gpio.set_pro_rate) + ice->gpio.set_pro_rate(ice, rate); + for (i = 0; i < ice->akm_codecs; i++) { + if (ice->akm[i].ops.set_rate_val) + ice->akm[i].ops.set_rate_val(&ice->akm[i], rate); + } + if (ice->spdif.ops.setup_rate) + ice->spdif.ops.setup_rate(ice, rate); +} + +static int snd_ice1712_playback_pro_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + ice->playback_pro_size = snd_pcm_lib_buffer_bytes(substream); + guard(spinlock_irq)(&ice->reg_lock); + outl(substream->runtime->dma_addr, ICEMT(ice, PLAYBACK_ADDR)); + outw((ice->playback_pro_size >> 2) - 1, ICEMT(ice, PLAYBACK_SIZE)); + outw((snd_pcm_lib_period_bytes(substream) >> 2) - 1, ICEMT(ice, PLAYBACK_COUNT)); + + return 0; +} + +static int snd_ice1712_playback_pro_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + snd_ice1712_set_pro_rate(ice, params_rate(hw_params), 0); + return 0; +} + +static int snd_ice1712_capture_pro_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + ice->capture_pro_size = snd_pcm_lib_buffer_bytes(substream); + guard(spinlock_irq)(&ice->reg_lock); + outl(substream->runtime->dma_addr, ICEMT(ice, CAPTURE_ADDR)); + outw((ice->capture_pro_size >> 2) - 1, ICEMT(ice, CAPTURE_SIZE)); + outw((snd_pcm_lib_period_bytes(substream) >> 2) - 1, ICEMT(ice, CAPTURE_COUNT)); + return 0; +} + +static int snd_ice1712_capture_pro_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + snd_ice1712_set_pro_rate(ice, params_rate(hw_params), 0); + return 0; +} + +static snd_pcm_uframes_t snd_ice1712_playback_pro_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(inl(ICEMT(ice, PLAYBACK_CONTROL)) & ICE1712_PLAYBACK_START)) + return 0; + ptr = ice->playback_pro_size - (inw(ICEMT(ice, PLAYBACK_SIZE)) << 2); + ptr = bytes_to_frames(substream->runtime, ptr); + if (ptr == substream->runtime->buffer_size) + ptr = 0; + return ptr; +} + +static snd_pcm_uframes_t snd_ice1712_capture_pro_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(inl(ICEMT(ice, PLAYBACK_CONTROL)) & ICE1712_CAPTURE_START_SHADOW)) + return 0; + ptr = ice->capture_pro_size - (inw(ICEMT(ice, CAPTURE_SIZE)) << 2); + ptr = bytes_to_frames(substream->runtime, ptr); + if (ptr == substream->runtime->buffer_size) + ptr = 0; + return ptr; +} + +static const struct snd_pcm_hardware snd_ice1712_playback_pro = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_96000, + .rate_min = 4000, + .rate_max = 96000, + .channels_min = 10, + .channels_max = 10, + .buffer_bytes_max = (256*1024), + .period_bytes_min = 10 * 4 * 2, + .period_bytes_max = 131040, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_ice1712_capture_pro = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_96000, + .rate_min = 4000, + .rate_max = 96000, + .channels_min = 12, + .channels_max = 12, + .buffer_bytes_max = (256*1024), + .period_bytes_min = 12 * 4 * 2, + .period_bytes_max = 131040, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_ice1712_playback_pro_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + ice->playback_pro_substream = substream; + runtime->hw = snd_ice1712_playback_pro; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); + if (is_pro_rate_locked(ice)) { + runtime->hw.rate_min = PRO_RATE_DEFAULT; + runtime->hw.rate_max = PRO_RATE_DEFAULT; + } + + if (ice->spdif.ops.open) + ice->spdif.ops.open(ice, substream); + + return 0; +} + +static int snd_ice1712_capture_pro_open(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + ice->capture_pro_substream = substream; + runtime->hw = snd_ice1712_capture_pro; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); + if (is_pro_rate_locked(ice)) { + runtime->hw.rate_min = PRO_RATE_DEFAULT; + runtime->hw.rate_max = PRO_RATE_DEFAULT; + } + + return 0; +} + +static int snd_ice1712_playback_pro_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + if (PRO_RATE_RESET) + snd_ice1712_set_pro_rate(ice, PRO_RATE_DEFAULT, 0); + ice->playback_pro_substream = NULL; + if (ice->spdif.ops.close) + ice->spdif.ops.close(ice, substream); + + return 0; +} + +static int snd_ice1712_capture_pro_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + if (PRO_RATE_RESET) + snd_ice1712_set_pro_rate(ice, PRO_RATE_DEFAULT, 0); + ice->capture_pro_substream = NULL; + return 0; +} + +static const struct snd_pcm_ops snd_ice1712_playback_pro_ops = { + .open = snd_ice1712_playback_pro_open, + .close = snd_ice1712_playback_pro_close, + .hw_params = snd_ice1712_playback_pro_hw_params, + .prepare = snd_ice1712_playback_pro_prepare, + .trigger = snd_ice1712_pro_trigger, + .pointer = snd_ice1712_playback_pro_pointer, +}; + +static const struct snd_pcm_ops snd_ice1712_capture_pro_ops = { + .open = snd_ice1712_capture_pro_open, + .close = snd_ice1712_capture_pro_close, + .hw_params = snd_ice1712_capture_pro_hw_params, + .prepare = snd_ice1712_capture_pro_prepare, + .trigger = snd_ice1712_pro_trigger, + .pointer = snd_ice1712_capture_pro_pointer, +}; + +static int snd_ice1712_pcm_profi(struct snd_ice1712 *ice, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ice->card, "ICE1712 multi", device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ice1712_playback_pro_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ice1712_capture_pro_ops); + + pcm->private_data = ice; + pcm->info_flags = 0; + strscpy(pcm->name, "ICE1712 multi"); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &ice->pci->dev, 256*1024, 256*1024); + + ice->pcm_pro = pcm; + + if (ice->cs8427) { + /* assign channels to iec958 */ + err = snd_cs8427_iec958_build(ice->cs8427, + pcm->streams[0].substream, + pcm->streams[1].substream); + if (err < 0) + return err; + } + + return snd_ice1712_build_pro_mixer(ice); +} + +/* + * Mixer section + */ + +static void snd_ice1712_update_volume(struct snd_ice1712 *ice, int index) +{ + unsigned int vol = ice->pro_volumes[index]; + unsigned short val = 0; + + val |= (vol & 0x8000) == 0 ? (96 - (vol & 0x7f)) : 0x7f; + val |= ((vol & 0x80000000) == 0 ? (96 - ((vol >> 16) & 0x7f)) : 0x7f) << 8; + outb(index, ICEMT(ice, MONITOR_INDEX)); + outw(val, ICEMT(ice, MONITOR_VOLUME)); +} + +#define snd_ice1712_pro_mixer_switch_info snd_ctl_boolean_stereo_info + +static int snd_ice1712_pro_mixer_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + + kcontrol->private_value; + + guard(spinlock_irq)(&ice->reg_lock); + ucontrol->value.integer.value[0] = + !((ice->pro_volumes[priv_idx] >> 15) & 1); + ucontrol->value.integer.value[1] = + !((ice->pro_volumes[priv_idx] >> 31) & 1); + return 0; +} + +static int snd_ice1712_pro_mixer_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + + kcontrol->private_value; + unsigned int nval, change; + + nval = (ucontrol->value.integer.value[0] ? 0 : 0x00008000) | + (ucontrol->value.integer.value[1] ? 0 : 0x80000000); + guard(spinlock_irq)(&ice->reg_lock); + nval |= ice->pro_volumes[priv_idx] & ~0x80008000; + change = nval != ice->pro_volumes[priv_idx]; + ice->pro_volumes[priv_idx] = nval; + snd_ice1712_update_volume(ice, priv_idx); + return change; +} + +static int snd_ice1712_pro_mixer_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 = 96; + return 0; +} + +static int snd_ice1712_pro_mixer_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + + kcontrol->private_value; + + guard(spinlock_irq)(&ice->reg_lock); + ucontrol->value.integer.value[0] = + (ice->pro_volumes[priv_idx] >> 0) & 127; + ucontrol->value.integer.value[1] = + (ice->pro_volumes[priv_idx] >> 16) & 127; + return 0; +} + +static int snd_ice1712_pro_mixer_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + + kcontrol->private_value; + unsigned int nval, change; + + nval = (ucontrol->value.integer.value[0] & 127) | + ((ucontrol->value.integer.value[1] & 127) << 16); + guard(spinlock_irq)(&ice->reg_lock); + nval |= ice->pro_volumes[priv_idx] & ~0x007f007f; + change = nval != ice->pro_volumes[priv_idx]; + ice->pro_volumes[priv_idx] = nval; + snd_ice1712_update_volume(ice, priv_idx); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_playback, -14400, 150, 0); + +static const struct snd_kcontrol_new snd_ice1712_multi_playback_ctrls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Multi Playback Switch", + .info = snd_ice1712_pro_mixer_switch_info, + .get = snd_ice1712_pro_mixer_switch_get, + .put = snd_ice1712_pro_mixer_switch_put, + .private_value = 0, + .count = 10, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Multi Playback Volume", + .info = snd_ice1712_pro_mixer_volume_info, + .get = snd_ice1712_pro_mixer_volume_get, + .put = snd_ice1712_pro_mixer_volume_put, + .private_value = 0, + .count = 10, + .tlv = { .p = db_scale_playback } + }, +}; + +static const struct snd_kcontrol_new snd_ice1712_multi_capture_analog_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "H/W Multi Capture Switch", + .info = snd_ice1712_pro_mixer_switch_info, + .get = snd_ice1712_pro_mixer_switch_get, + .put = snd_ice1712_pro_mixer_switch_put, + .private_value = 10, +}; + +static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, SWITCH), + .info = snd_ice1712_pro_mixer_switch_info, + .get = snd_ice1712_pro_mixer_switch_get, + .put = snd_ice1712_pro_mixer_switch_put, + .private_value = 18, + .count = 2, +}; + +static const struct snd_kcontrol_new snd_ice1712_multi_capture_analog_volume = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "H/W Multi Capture Volume", + .info = snd_ice1712_pro_mixer_volume_info, + .get = snd_ice1712_pro_mixer_volume_get, + .put = snd_ice1712_pro_mixer_volume_put, + .private_value = 10, + .tlv = { .p = db_scale_playback } +}; + +static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, VOLUME), + .info = snd_ice1712_pro_mixer_volume_info, + .get = snd_ice1712_pro_mixer_volume_get, + .put = snd_ice1712_pro_mixer_volume_put, + .private_value = 18, + .count = 2, +}; + +static int snd_ice1712_build_pro_mixer(struct snd_ice1712 *ice) +{ + struct snd_card *card = ice->card; + unsigned int idx; + int err; + + /* multi-channel mixer */ + for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_multi_playback_ctrls); idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_ice1712_multi_playback_ctrls[idx], ice)); + if (err < 0) + return err; + } + + if (ice->num_total_adcs > 0) { + struct snd_kcontrol_new tmp = snd_ice1712_multi_capture_analog_switch; + tmp.count = ice->num_total_adcs; + err = snd_ctl_add(card, snd_ctl_new1(&tmp, ice)); + if (err < 0) + return err; + } + + err = snd_ctl_add(card, snd_ctl_new1(&snd_ice1712_multi_capture_spdif_switch, ice)); + if (err < 0) + return err; + + if (ice->num_total_adcs > 0) { + struct snd_kcontrol_new tmp = snd_ice1712_multi_capture_analog_volume; + tmp.count = ice->num_total_adcs; + err = snd_ctl_add(card, snd_ctl_new1(&tmp, ice)); + if (err < 0) + return err; + } + + err = snd_ctl_add(card, snd_ctl_new1(&snd_ice1712_multi_capture_spdif_volume, ice)); + if (err < 0) + return err; + + /* initialize volumes */ + for (idx = 0; idx < 10; idx++) { + ice->pro_volumes[idx] = 0x80008000; /* mute */ + snd_ice1712_update_volume(ice, idx); + } + for (idx = 10; idx < 10 + ice->num_total_adcs; idx++) { + ice->pro_volumes[idx] = 0x80008000; /* mute */ + snd_ice1712_update_volume(ice, idx); + } + for (idx = 18; idx < 20; idx++) { + ice->pro_volumes[idx] = 0x80008000; /* mute */ + snd_ice1712_update_volume(ice, idx); + } + return 0; +} + +static void snd_ice1712_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct snd_ice1712 *ice = ac97->private_data; + ice->ac97 = NULL; +} + +static int snd_ice1712_ac97_mixer(struct snd_ice1712 *ice) +{ + int err, bus_num = 0; + struct snd_ac97_template ac97; + struct snd_ac97_bus *pbus; + static const struct snd_ac97_bus_ops con_ops = { + .write = snd_ice1712_ac97_write, + .read = snd_ice1712_ac97_read, + }; + static const struct snd_ac97_bus_ops pro_ops = { + .write = snd_ice1712_pro_ac97_write, + .read = snd_ice1712_pro_ac97_read, + }; + + if (ice_has_con_ac97(ice)) { + err = snd_ac97_bus(ice->card, bus_num++, &con_ops, NULL, &pbus); + if (err < 0) + return err; + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = ice; + ac97.private_free = snd_ice1712_mixer_free_ac97; + err = snd_ac97_mixer(pbus, &ac97, &ice->ac97); + if (err < 0) + dev_warn(ice->card->dev, + "cannot initialize ac97 for consumer, skipped\n"); + else { + return snd_ctl_add(ice->card, + snd_ctl_new1(&snd_ice1712_mixer_digmix_route_ac97, + ice)); + } + } + + if (!(ice->eeprom.data[ICE_EEP1_ACLINK] & ICE1712_CFG_PRO_I2S)) { + err = snd_ac97_bus(ice->card, bus_num, &pro_ops, NULL, &pbus); + if (err < 0) + return err; + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = ice; + ac97.private_free = snd_ice1712_mixer_free_ac97; + err = snd_ac97_mixer(pbus, &ac97, &ice->ac97); + if (err < 0) + dev_warn(ice->card->dev, + "cannot initialize pro ac97, skipped\n"); + else + return 0; + } + /* I2S mixer only */ + strcat(ice->card->mixername, "ICE1712 - multitrack"); + return 0; +} + +/* + * + */ + +static inline unsigned int eeprom_double(struct snd_ice1712 *ice, int idx) +{ + return (unsigned int)ice->eeprom.data[idx] | ((unsigned int)ice->eeprom.data[idx + 1] << 8); +} + +static void snd_ice1712_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ice1712 *ice = entry->private_data; + unsigned int idx; + + snd_iprintf(buffer, "%s\n\n", ice->card->longname); + snd_iprintf(buffer, "EEPROM:\n"); + + snd_iprintf(buffer, " Subvendor : 0x%x\n", ice->eeprom.subvendor); + snd_iprintf(buffer, " Size : %i bytes\n", ice->eeprom.size); + snd_iprintf(buffer, " Version : %i\n", ice->eeprom.version); + snd_iprintf(buffer, " Codec : 0x%x\n", ice->eeprom.data[ICE_EEP1_CODEC]); + snd_iprintf(buffer, " ACLink : 0x%x\n", ice->eeprom.data[ICE_EEP1_ACLINK]); + snd_iprintf(buffer, " I2S ID : 0x%x\n", ice->eeprom.data[ICE_EEP1_I2SID]); + snd_iprintf(buffer, " S/PDIF : 0x%x\n", ice->eeprom.data[ICE_EEP1_SPDIF]); + snd_iprintf(buffer, " GPIO mask : 0x%x\n", ice->eeprom.gpiomask); + snd_iprintf(buffer, " GPIO state : 0x%x\n", ice->eeprom.gpiostate); + snd_iprintf(buffer, " GPIO direction : 0x%x\n", ice->eeprom.gpiodir); + snd_iprintf(buffer, " AC'97 main : 0x%x\n", eeprom_double(ice, ICE_EEP1_AC97_MAIN_LO)); + snd_iprintf(buffer, " AC'97 pcm : 0x%x\n", eeprom_double(ice, ICE_EEP1_AC97_PCM_LO)); + snd_iprintf(buffer, " AC'97 record : 0x%x\n", eeprom_double(ice, ICE_EEP1_AC97_REC_LO)); + snd_iprintf(buffer, " AC'97 record src : 0x%x\n", ice->eeprom.data[ICE_EEP1_AC97_RECSRC]); + for (idx = 0; idx < 4; idx++) + snd_iprintf(buffer, " DAC ID #%i : 0x%x\n", idx, ice->eeprom.data[ICE_EEP1_DAC_ID + idx]); + for (idx = 0; idx < 4; idx++) + snd_iprintf(buffer, " ADC ID #%i : 0x%x\n", idx, ice->eeprom.data[ICE_EEP1_ADC_ID + idx]); + for (idx = 0x1c; idx < ice->eeprom.size; idx++) + snd_iprintf(buffer, " Extra #%02i : 0x%x\n", idx, ice->eeprom.data[idx]); + + snd_iprintf(buffer, "\nRegisters:\n"); + snd_iprintf(buffer, " PSDOUT03 : 0x%04x\n", (unsigned)inw(ICEMT(ice, ROUTE_PSDOUT03))); + snd_iprintf(buffer, " CAPTURE : 0x%08x\n", inl(ICEMT(ice, ROUTE_CAPTURE))); + snd_iprintf(buffer, " SPDOUT : 0x%04x\n", (unsigned)inw(ICEMT(ice, ROUTE_SPDOUT))); + snd_iprintf(buffer, " RATE : 0x%02x\n", (unsigned)inb(ICEMT(ice, RATE))); + snd_iprintf(buffer, " GPIO_DATA : 0x%02x\n", (unsigned)snd_ice1712_get_gpio_data(ice)); + snd_iprintf(buffer, " GPIO_WRITE_MASK : 0x%02x\n", (unsigned)snd_ice1712_read(ice, ICE1712_IREG_GPIO_WRITE_MASK)); + snd_iprintf(buffer, " GPIO_DIRECTION : 0x%02x\n", (unsigned)snd_ice1712_read(ice, ICE1712_IREG_GPIO_DIRECTION)); +} + +static void snd_ice1712_proc_init(struct snd_ice1712 *ice) +{ + snd_card_ro_proc_new(ice->card, "ice1712", ice, snd_ice1712_proc_read); +} + +/* + * + */ + +static int snd_ice1712_eeprom_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = sizeof(struct snd_ice1712_eeprom); + return 0; +} + +static int snd_ice1712_eeprom_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + memcpy(ucontrol->value.bytes.data, &ice->eeprom, sizeof(ice->eeprom)); + return 0; +} + +static const struct snd_kcontrol_new snd_ice1712_eeprom = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "ICE1712 EEPROM", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ice1712_eeprom_info, + .get = snd_ice1712_eeprom_get +}; + +/* + */ +static int snd_ice1712_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_ice1712_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + if (ice->spdif.ops.default_get) + ice->spdif.ops.default_get(ice, ucontrol); + return 0; +} + +static int snd_ice1712_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + if (ice->spdif.ops.default_put) + return ice->spdif.ops.default_put(ice, ucontrol); + return 0; +} + +static const struct snd_kcontrol_new snd_ice1712_spdif_default = +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = snd_ice1712_spdif_info, + .get = snd_ice1712_spdif_default_get, + .put = snd_ice1712_spdif_default_put +}; + +static int snd_ice1712_spdif_maskc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + if (ice->spdif.ops.default_get) { + ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS; + ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL | + IEC958_AES1_CON_CATEGORY; + ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS; + } else { + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + ucontrol->value.iec958.status[4] = 0xff; + } + return 0; +} + +static int snd_ice1712_spdif_maskp_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + if (ice->spdif.ops.default_get) { + ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_PRO_FS | + IEC958_AES0_PRO_EMPHASIS; + ucontrol->value.iec958.status[1] = IEC958_AES1_PRO_MODE; + } else { + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + ucontrol->value.iec958.status[4] = 0xff; + } + return 0; +} + +static const struct snd_kcontrol_new snd_ice1712_spdif_maskc = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .info = snd_ice1712_spdif_info, + .get = snd_ice1712_spdif_maskc_get, +}; + +static const struct snd_kcontrol_new snd_ice1712_spdif_maskp = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK), + .info = snd_ice1712_spdif_info, + .get = snd_ice1712_spdif_maskp_get, +}; + +static int snd_ice1712_spdif_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + if (ice->spdif.ops.stream_get) + ice->spdif.ops.stream_get(ice, ucontrol); + return 0; +} + +static int snd_ice1712_spdif_stream_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + if (ice->spdif.ops.stream_put) + return ice->spdif.ops.stream_put(ice, ucontrol); + return 0; +} + +static const struct snd_kcontrol_new snd_ice1712_spdif_stream = +{ + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE), + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), + .info = snd_ice1712_spdif_info, + .get = snd_ice1712_spdif_stream_get, + .put = snd_ice1712_spdif_stream_put +}; + +int snd_ice1712_gpio_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char mask = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value & (1<<24)) ? 1 : 0; + + snd_ice1712_save_gpio_status(ice); + ucontrol->value.integer.value[0] = + (snd_ice1712_gpio_read(ice) & mask ? 1 : 0) ^ invert; + snd_ice1712_restore_gpio_status(ice); + return 0; +} + +int snd_ice1712_gpio_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char mask = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value & (1<<24)) ? mask : 0; + unsigned int val, nval; + + if (kcontrol->private_value & (1 << 31)) + return -EPERM; + nval = (ucontrol->value.integer.value[0] ? mask : 0) ^ invert; + snd_ice1712_save_gpio_status(ice); + val = snd_ice1712_gpio_read(ice); + nval |= val & ~mask; + if (val != nval) + snd_ice1712_gpio_write(ice, nval); + snd_ice1712_restore_gpio_status(ice); + return val != nval; +} + +/* + * rate + */ +static int snd_ice1712_pro_internal_clock_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "8000", /* 0: 6 */ + "9600", /* 1: 3 */ + "11025", /* 2: 10 */ + "12000", /* 3: 2 */ + "16000", /* 4: 5 */ + "22050", /* 5: 9 */ + "24000", /* 6: 1 */ + "32000", /* 7: 4 */ + "44100", /* 8: 8 */ + "48000", /* 9: 0 */ + "64000", /* 10: 15 */ + "88200", /* 11: 11 */ + "96000", /* 12: 7 */ + "IEC958 Input", /* 13: -- */ + }; + return snd_ctl_enum_info(uinfo, 1, 14, texts); +} + +static int snd_ice1712_pro_internal_clock_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + static const unsigned char xlate[16] = { + 9, 6, 3, 1, 7, 4, 0, 12, 8, 5, 2, 11, 255, 255, 255, 10 + }; + unsigned char val; + + guard(spinlock_irq)(&ice->reg_lock); + if (is_spdif_master(ice)) { + ucontrol->value.enumerated.item[0] = 13; + } else { + val = xlate[inb(ICEMT(ice, RATE)) & 15]; + if (val == 255) { + snd_BUG(); + val = 0; + } + ucontrol->value.enumerated.item[0] = val; + } + return 0; +} + +static int snd_ice1712_pro_internal_clock_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + static const unsigned int xrate[13] = { + 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000 + }; + unsigned char oval; + int change = 0; + + spin_lock_irq(&ice->reg_lock); + oval = inb(ICEMT(ice, RATE)); + if (ucontrol->value.enumerated.item[0] == 13) { + outb(oval | ICE1712_SPDIF_MASTER, ICEMT(ice, RATE)); + } else { + PRO_RATE_DEFAULT = xrate[ucontrol->value.integer.value[0] % 13]; + spin_unlock_irq(&ice->reg_lock); + snd_ice1712_set_pro_rate(ice, PRO_RATE_DEFAULT, 1); + spin_lock_irq(&ice->reg_lock); + } + change = inb(ICEMT(ice, RATE)) != oval; + spin_unlock_irq(&ice->reg_lock); + + if ((oval & ICE1712_SPDIF_MASTER) != + (inb(ICEMT(ice, RATE)) & ICE1712_SPDIF_MASTER)) + snd_ice1712_set_input_clock_source(ice, is_spdif_master(ice)); + + return change; +} + +static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Multi Track Internal Clock", + .info = snd_ice1712_pro_internal_clock_info, + .get = snd_ice1712_pro_internal_clock_get, + .put = snd_ice1712_pro_internal_clock_put +}; + +static int snd_ice1712_pro_internal_clock_default_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "8000", /* 0: 6 */ + "9600", /* 1: 3 */ + "11025", /* 2: 10 */ + "12000", /* 3: 2 */ + "16000", /* 4: 5 */ + "22050", /* 5: 9 */ + "24000", /* 6: 1 */ + "32000", /* 7: 4 */ + "44100", /* 8: 8 */ + "48000", /* 9: 0 */ + "64000", /* 10: 15 */ + "88200", /* 11: 11 */ + "96000", /* 12: 7 */ + /* "IEC958 Input", 13: -- */ + }; + return snd_ctl_enum_info(uinfo, 1, 13, texts); +} + +static int snd_ice1712_pro_internal_clock_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val; + static const unsigned int xrate[13] = { + 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000 + }; + + for (val = 0; val < 13; val++) { + if (xrate[val] == PRO_RATE_DEFAULT) + break; + } + + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int snd_ice1712_pro_internal_clock_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + static const unsigned int xrate[13] = { + 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000 + }; + unsigned char oval; + int change = 0; + + oval = PRO_RATE_DEFAULT; + PRO_RATE_DEFAULT = xrate[ucontrol->value.integer.value[0] % 13]; + change = PRO_RATE_DEFAULT != oval; + + return change; +} + +static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Multi Track Internal Clock Default", + .info = snd_ice1712_pro_internal_clock_default_info, + .get = snd_ice1712_pro_internal_clock_default_get, + .put = snd_ice1712_pro_internal_clock_default_put +}; + +#define snd_ice1712_pro_rate_locking_info snd_ctl_boolean_mono_info + +static int snd_ice1712_pro_rate_locking_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = PRO_RATE_LOCKED; + return 0; +} + +static int snd_ice1712_pro_rate_locking_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int change = 0, nval; + + nval = ucontrol->value.integer.value[0] ? 1 : 0; + guard(spinlock_irq)(&ice->reg_lock); + change = PRO_RATE_LOCKED != nval; + PRO_RATE_LOCKED = nval; + return change; +} + +static const struct snd_kcontrol_new snd_ice1712_pro_rate_locking = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Multi Track Rate Locking", + .info = snd_ice1712_pro_rate_locking_info, + .get = snd_ice1712_pro_rate_locking_get, + .put = snd_ice1712_pro_rate_locking_put +}; + +#define snd_ice1712_pro_rate_reset_info snd_ctl_boolean_mono_info + +static int snd_ice1712_pro_rate_reset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = PRO_RATE_RESET; + return 0; +} + +static int snd_ice1712_pro_rate_reset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int change = 0, nval; + + nval = ucontrol->value.integer.value[0] ? 1 : 0; + guard(spinlock_irq)(&ice->reg_lock); + change = PRO_RATE_RESET != nval; + PRO_RATE_RESET = nval; + return change; +} + +static const struct snd_kcontrol_new snd_ice1712_pro_rate_reset = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Multi Track Rate Reset", + .info = snd_ice1712_pro_rate_reset_info, + .get = snd_ice1712_pro_rate_reset_get, + .put = snd_ice1712_pro_rate_reset_put +}; + +/* + * routing + */ +static int snd_ice1712_pro_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "PCM Out", /* 0 */ + "H/W In 0", "H/W In 1", "H/W In 2", "H/W In 3", /* 1-4 */ + "H/W In 4", "H/W In 5", "H/W In 6", "H/W In 7", /* 5-8 */ + "IEC958 In L", "IEC958 In R", /* 9-10 */ + "Digital Mixer", /* 11 - optional */ + }; + int num_items = snd_ctl_get_ioffidx(kcontrol, &uinfo->id) < 2 ? 12 : 11; + return snd_ctl_enum_info(uinfo, 1, num_items, texts); +} + +static int snd_ice1712_pro_route_analog_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned int val, cval; + + scoped_guard(spinlock_irq, &ice->reg_lock) { + val = inw(ICEMT(ice, ROUTE_PSDOUT03)); + cval = inl(ICEMT(ice, ROUTE_CAPTURE)); + } + + val >>= ((idx % 2) * 8) + ((idx / 2) * 2); + val &= 3; + cval >>= ((idx / 2) * 8) + ((idx % 2) * 4); + if (val == 1 && idx < 2) + ucontrol->value.enumerated.item[0] = 11; + else if (val == 2) + ucontrol->value.enumerated.item[0] = (cval & 7) + 1; + else if (val == 3) + ucontrol->value.enumerated.item[0] = ((cval >> 3) & 1) + 9; + else + ucontrol->value.enumerated.item[0] = 0; + return 0; +} + +static int snd_ice1712_pro_route_analog_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int change, shift; + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned int val, old_val, nval; + + /* update PSDOUT */ + if (ucontrol->value.enumerated.item[0] >= 11) + nval = idx < 2 ? 1 : 0; /* dig mixer (or pcm) */ + else if (ucontrol->value.enumerated.item[0] >= 9) + nval = 3; /* spdif in */ + else if (ucontrol->value.enumerated.item[0] >= 1) + nval = 2; /* analog in */ + else + nval = 0; /* pcm */ + shift = ((idx % 2) * 8) + ((idx / 2) * 2); + scoped_guard(spinlock_irq, &ice->reg_lock) { + val = old_val = inw(ICEMT(ice, ROUTE_PSDOUT03)); + val &= ~(0x03 << shift); + val |= nval << shift; + change = val != old_val; + if (change) + outw(val, ICEMT(ice, ROUTE_PSDOUT03)); + } + if (nval < 2) /* dig mixer of pcm */ + return change; + + /* update CAPTURE */ + scoped_guard(spinlock_irq, &ice->reg_lock) { + val = old_val = inl(ICEMT(ice, ROUTE_CAPTURE)); + shift = ((idx / 2) * 8) + ((idx % 2) * 4); + if (nval == 2) { /* analog in */ + nval = ucontrol->value.enumerated.item[0] - 1; + val &= ~(0x07 << shift); + val |= nval << shift; + } else { /* spdif in */ + nval = (ucontrol->value.enumerated.item[0] - 9) << 3; + val &= ~(0x08 << shift); + val |= nval << shift; + } + if (val != old_val) { + change = 1; + outl(val, ICEMT(ice, ROUTE_CAPTURE)); + } + } + return change; +} + +static int snd_ice1712_pro_route_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned int val, cval; + val = inw(ICEMT(ice, ROUTE_SPDOUT)); + cval = (val >> (idx * 4 + 8)) & 0x0f; + val = (val >> (idx * 2)) & 0x03; + if (val == 1) + ucontrol->value.enumerated.item[0] = 11; + else if (val == 2) + ucontrol->value.enumerated.item[0] = (cval & 7) + 1; + else if (val == 3) + ucontrol->value.enumerated.item[0] = ((cval >> 3) & 1) + 9; + else + ucontrol->value.enumerated.item[0] = 0; + return 0; +} + +static int snd_ice1712_pro_route_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int change, shift; + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned int val, old_val, nval; + + /* update SPDOUT */ + guard(spinlock_irq)(&ice->reg_lock); + val = old_val = inw(ICEMT(ice, ROUTE_SPDOUT)); + if (ucontrol->value.enumerated.item[0] >= 11) + nval = 1; + else if (ucontrol->value.enumerated.item[0] >= 9) + nval = 3; + else if (ucontrol->value.enumerated.item[0] >= 1) + nval = 2; + else + nval = 0; + shift = idx * 2; + val &= ~(0x03 << shift); + val |= nval << shift; + shift = idx * 4 + 8; + if (nval == 2) { + nval = ucontrol->value.enumerated.item[0] - 1; + val &= ~(0x07 << shift); + val |= nval << shift; + } else if (nval == 3) { + nval = (ucontrol->value.enumerated.item[0] - 9) << 3; + val &= ~(0x08 << shift); + val |= nval << shift; + } + change = val != old_val; + if (change) + outw(val, ICEMT(ice, ROUTE_SPDOUT)); + return change; +} + +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_analog_route = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "H/W Playback Route", + .info = snd_ice1712_pro_route_info, + .get = snd_ice1712_pro_route_analog_get, + .put = snd_ice1712_pro_route_analog_put, +}; + +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route", + .info = snd_ice1712_pro_route_info, + .get = snd_ice1712_pro_route_spdif_get, + .put = snd_ice1712_pro_route_spdif_put, + .count = 2, +}; + + +static int snd_ice1712_pro_volume_rate_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 snd_ice1712_pro_volume_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = inb(ICEMT(ice, MONITOR_RATE)); + return 0; +} + +static int snd_ice1712_pro_volume_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int change; + + guard(spinlock_irq)(&ice->reg_lock); + change = inb(ICEMT(ice, MONITOR_RATE)) != ucontrol->value.integer.value[0]; + outb(ucontrol->value.integer.value[0], ICEMT(ice, MONITOR_RATE)); + return change; +} + +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Multi Track Volume Rate", + .info = snd_ice1712_pro_volume_rate_info, + .get = snd_ice1712_pro_volume_rate_get, + .put = snd_ice1712_pro_volume_rate_put +}; + +static int snd_ice1712_pro_peak_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 22; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + return 0; +} + +static int snd_ice1712_pro_peak_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx; + + guard(spinlock_irq)(&ice->reg_lock); + for (idx = 0; idx < 22; idx++) { + outb(idx, ICEMT(ice, MONITOR_PEAKINDEX)); + ucontrol->value.integer.value[idx] = inb(ICEMT(ice, MONITOR_PEAKDATA)); + } + return 0; +} + +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Multi Track Peak", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ice1712_pro_peak_info, + .get = snd_ice1712_pro_peak_get +}; + +/* + * + */ + +/* + * list of available boards + */ +static const struct snd_ice1712_card_info *card_tables[] = { + snd_ice1712_hoontech_cards, + snd_ice1712_delta_cards, + snd_ice1712_ews_cards, + NULL, +}; + +static unsigned char snd_ice1712_read_i2c(struct snd_ice1712 *ice, + unsigned char dev, + unsigned char addr) +{ + long t = 0x10000; + + outb(addr, ICEREG(ice, I2C_BYTE_ADDR)); + outb(dev & ~ICE1712_I2C_WRITE, ICEREG(ice, I2C_DEV_ADDR)); + while (t-- > 0 && (inb(ICEREG(ice, I2C_CTRL)) & ICE1712_I2C_BUSY)) ; + return inb(ICEREG(ice, I2C_DATA)); +} + +static int snd_ice1712_read_eeprom(struct snd_ice1712 *ice, + const char *modelname) +{ + int dev = ICE_I2C_EEPROM_ADDR; /* I2C EEPROM device address */ + unsigned int i, size; + const struct snd_ice1712_card_info * const *tbl, *c; + + if (!modelname || !*modelname) { + ice->eeprom.subvendor = 0; + if ((inb(ICEREG(ice, I2C_CTRL)) & ICE1712_I2C_EEPROM) != 0) + ice->eeprom.subvendor = (snd_ice1712_read_i2c(ice, dev, 0x00) << 0) | + (snd_ice1712_read_i2c(ice, dev, 0x01) << 8) | + (snd_ice1712_read_i2c(ice, dev, 0x02) << 16) | + (snd_ice1712_read_i2c(ice, dev, 0x03) << 24); + if (ice->eeprom.subvendor == 0 || + ice->eeprom.subvendor == (unsigned int)-1) { + /* invalid subvendor from EEPROM, try the PCI subststem ID instead */ + u16 vendor, device; + pci_read_config_word(ice->pci, PCI_SUBSYSTEM_VENDOR_ID, &vendor); + pci_read_config_word(ice->pci, PCI_SUBSYSTEM_ID, &device); + ice->eeprom.subvendor = ((unsigned int)swab16(vendor) << 16) | swab16(device); + if (ice->eeprom.subvendor == 0 || ice->eeprom.subvendor == (unsigned int)-1) { + dev_err(ice->card->dev, + "No valid ID is found\n"); + return -ENXIO; + } + } + } + for (tbl = card_tables; *tbl; tbl++) { + for (c = *tbl; c->subvendor; c++) { + if (modelname && c->model && !strcmp(modelname, c->model)) { + dev_info(ice->card->dev, + "Using board model %s\n", c->name); + ice->eeprom.subvendor = c->subvendor; + } else if (c->subvendor != ice->eeprom.subvendor) + continue; + if (!c->eeprom_size || !c->eeprom_data) + goto found; + /* if the EEPROM is given by the driver, use it */ + dev_dbg(ice->card->dev, "using the defined eeprom..\n"); + ice->eeprom.version = 1; + ice->eeprom.size = c->eeprom_size + 6; + memcpy(ice->eeprom.data, c->eeprom_data, c->eeprom_size); + goto read_skipped; + } + } + dev_warn(ice->card->dev, "No matching model found for ID 0x%x\n", + ice->eeprom.subvendor); + + found: + ice->eeprom.size = snd_ice1712_read_i2c(ice, dev, 0x04); + if (ice->eeprom.size < 6) + ice->eeprom.size = 32; /* FIXME: any cards without the correct size? */ + else if (ice->eeprom.size > 32) { + dev_err(ice->card->dev, + "invalid EEPROM (size = %i)\n", ice->eeprom.size); + return -EIO; + } + ice->eeprom.version = snd_ice1712_read_i2c(ice, dev, 0x05); + if (ice->eeprom.version != 1) { + dev_err(ice->card->dev, "invalid EEPROM version %i\n", + ice->eeprom.version); + /* return -EIO; */ + } + size = ice->eeprom.size - 6; + for (i = 0; i < size; i++) + ice->eeprom.data[i] = snd_ice1712_read_i2c(ice, dev, i + 6); + + read_skipped: + ice->eeprom.gpiomask = ice->eeprom.data[ICE_EEP1_GPIO_MASK]; + ice->eeprom.gpiostate = ice->eeprom.data[ICE_EEP1_GPIO_STATE]; + ice->eeprom.gpiodir = ice->eeprom.data[ICE_EEP1_GPIO_DIR]; + + return 0; +} + + + +static int snd_ice1712_chip_init(struct snd_ice1712 *ice) +{ + outb(ICE1712_RESET | ICE1712_NATIVE, ICEREG(ice, CONTROL)); + udelay(200); + outb(ICE1712_NATIVE, ICEREG(ice, CONTROL)); + udelay(200); + if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE && + !ice->dxr_enable) + /* Set eeprom value to limit active ADCs and DACs to 6; + * Also disable AC97 as no hardware in standard 6fire card/box + * Note: DXR extensions are not currently supported + */ + ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a; + pci_write_config_byte(ice->pci, 0x60, ice->eeprom.data[ICE_EEP1_CODEC]); + pci_write_config_byte(ice->pci, 0x61, ice->eeprom.data[ICE_EEP1_ACLINK]); + pci_write_config_byte(ice->pci, 0x62, ice->eeprom.data[ICE_EEP1_I2SID]); + pci_write_config_byte(ice->pci, 0x63, ice->eeprom.data[ICE_EEP1_SPDIF]); + if (ice->eeprom.subvendor != ICE1712_SUBDEVICE_STDSP24 && + ice->eeprom.subvendor != ICE1712_SUBDEVICE_STAUDIO_ADCIII) { + ice->gpio.write_mask = ice->eeprom.gpiomask; + ice->gpio.direction = ice->eeprom.gpiodir; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, + ice->eeprom.gpiomask); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, + ice->eeprom.gpiodir); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, + ice->eeprom.gpiostate); + } else { + ice->gpio.write_mask = 0xc0; + ice->gpio.direction = 0xff; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, 0xc0); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, 0xff); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, + ICE1712_STDSP24_CLOCK_BIT); + } + snd_ice1712_write(ice, ICE1712_IREG_PRO_POWERDOWN, 0); + if (!(ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97)) { + outb(ICE1712_AC97_WARM, ICEREG(ice, AC97_CMD)); + udelay(100); + outb(0, ICEREG(ice, AC97_CMD)); + udelay(200); + snd_ice1712_write(ice, ICE1712_IREG_CONSUMER_POWERDOWN, 0); + } + snd_ice1712_set_pro_rate(ice, 48000, 1); + /* unmask used interrupts */ + outb(((ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_2xMPU401) == 0 ? + ICE1712_IRQ_MPU2 : 0) | + ((ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97) ? + ICE1712_IRQ_PBKDS | ICE1712_IRQ_CONCAP | ICE1712_IRQ_CONPBK : 0), + ICEREG(ice, IRQMASK)); + outb(0x00, ICEMT(ice, IRQ)); + + return 0; +} + +int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice) +{ + int err; + struct snd_kcontrol *kctl; + + if (snd_BUG_ON(!ice->pcm_pro)) + return -EIO; + kctl = snd_ctl_new1(&snd_ice1712_spdif_default, ice); + if (!kctl) + return -ENOMEM; + kctl->id.device = ice->pcm_pro->device; + err = snd_ctl_add(ice->card, kctl); + if (err < 0) + return err; + kctl = snd_ctl_new1(&snd_ice1712_spdif_maskc, ice); + if (!kctl) + return -ENOMEM; + kctl->id.device = ice->pcm_pro->device; + err = snd_ctl_add(ice->card, kctl); + if (err < 0) + return err; + kctl = snd_ctl_new1(&snd_ice1712_spdif_maskp, ice); + if (!kctl) + return -ENOMEM; + kctl->id.device = ice->pcm_pro->device; + err = snd_ctl_add(ice->card, kctl); + if (err < 0) + return err; + kctl = snd_ctl_new1(&snd_ice1712_spdif_stream, ice); + if (!kctl) + return -ENOMEM; + kctl->id.device = ice->pcm_pro->device; + err = snd_ctl_add(ice->card, kctl); + if (err < 0) + return err; + ice->spdif.stream_ctl = kctl; + return 0; +} + + +static int snd_ice1712_build_controls(struct snd_ice1712 *ice) +{ + int err; + + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_eeprom, ice)); + if (err < 0) + return err; + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_internal_clock, ice)); + if (err < 0) + return err; + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_internal_clock_default, ice)); + if (err < 0) + return err; + + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_rate_locking, ice)); + if (err < 0) + return err; + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_rate_reset, ice)); + if (err < 0) + return err; + + if (ice->num_total_dacs > 0) { + struct snd_kcontrol_new tmp = snd_ice1712_mixer_pro_analog_route; + tmp.count = ice->num_total_dacs; + err = snd_ctl_add(ice->card, snd_ctl_new1(&tmp, ice)); + if (err < 0) + return err; + } + + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_mixer_pro_spdif_route, ice)); + if (err < 0) + return err; + + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_mixer_pro_volume_rate, ice)); + if (err < 0) + return err; + return snd_ctl_add(ice->card, + snd_ctl_new1(&snd_ice1712_mixer_pro_peak, ice)); +} + +static void snd_ice1712_free(struct snd_card *card) +{ + struct snd_ice1712 *ice = card->private_data; + + if (ice->card_info && ice->card_info->chip_exit) + ice->card_info->chip_exit(ice); + + /* mask all interrupts */ + outb(ICE1712_MULTI_CAPTURE | ICE1712_MULTI_PLAYBACK, ICEMT(ice, IRQ)); + outb(0xff, ICEREG(ice, IRQMASK)); + + snd_ice1712_akm4xxx_free(ice); +} + +static int snd_ice1712_create(struct snd_card *card, + struct pci_dev *pci, + const char *modelname, + int omni, + int cs8427_timeout, + int dxr_enable) +{ + struct snd_ice1712 *ice = card->private_data; + int err; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + /* check, if we can restrict PCI DMA transfers to 28 bits */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(28))) { + dev_err(card->dev, + "architecture does not support 28bit PCI busmaster DMA\n"); + return -ENXIO; + } + + ice->omni = omni ? 1 : 0; + if (cs8427_timeout < 1) + cs8427_timeout = 1; + else if (cs8427_timeout > 1000) + cs8427_timeout = 1000; + ice->cs8427_timeout = cs8427_timeout; + ice->dxr_enable = dxr_enable; + spin_lock_init(&ice->reg_lock); + mutex_init(&ice->gpio_mutex); + mutex_init(&ice->i2c_mutex); + mutex_init(&ice->open_mutex); + ice->gpio.set_mask = snd_ice1712_set_gpio_mask; + ice->gpio.get_mask = snd_ice1712_get_gpio_mask; + ice->gpio.set_dir = snd_ice1712_set_gpio_dir; + ice->gpio.get_dir = snd_ice1712_get_gpio_dir; + ice->gpio.set_data = snd_ice1712_set_gpio_data; + ice->gpio.get_data = snd_ice1712_get_gpio_data; + + ice->spdif.cs8403_bits = + ice->spdif.cs8403_stream_bits = (0x01 | /* consumer format */ + 0x10 | /* no emphasis */ + 0x20); /* PCM encoder/decoder */ + ice->card = card; + ice->pci = pci; + ice->irq = -1; + pci_set_master(pci); + /* disable legacy emulation */ + pci_write_config_word(ice->pci, 0x40, 0x807f); + pci_write_config_word(ice->pci, 0x42, 0x0006); + snd_ice1712_proc_init(ice); + + err = pcim_request_all_regions(pci, "ICE1712"); + if (err < 0) + return err; + ice->port = pci_resource_start(pci, 0); + ice->ddma_port = pci_resource_start(pci, 1); + ice->dmapath_port = pci_resource_start(pci, 2); + ice->profi_port = pci_resource_start(pci, 3); + + if (devm_request_irq(&pci->dev, pci->irq, snd_ice1712_interrupt, + IRQF_SHARED, KBUILD_MODNAME, ice)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EIO; + } + + ice->irq = pci->irq; + card->sync_irq = ice->irq; + card->private_free = snd_ice1712_free; + + if (snd_ice1712_read_eeprom(ice, modelname) < 0) + return -EIO; + if (snd_ice1712_chip_init(ice) < 0) + return -EIO; + + return 0; +} + + +/* + * + * Registration + * + */ + +static struct snd_ice1712_card_info no_matched; + +static int snd_ice1712_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card __free(snd_card_unref) = NULL; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info * const *tbl, *c; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*ice), &card); + if (err < 0) + return err; + ice = card->private_data; + + strscpy(card->driver, "ICE1712"); + strscpy(card->shortname, "ICEnsemble ICE1712"); + + err = snd_ice1712_create(card, pci, model[dev], omni[dev], + cs8427_timeout[dev], dxr_enable[dev]); + if (err < 0) + return err; + + for (tbl = card_tables; *tbl; tbl++) { + for (c = *tbl; c->subvendor; c++) { + if (c->subvendor == ice->eeprom.subvendor) { + strscpy(card->shortname, c->name); + if (c->driver) /* specific driver? */ + strscpy(card->driver, c->driver); + if (c->chip_init) { + err = c->chip_init(ice); + if (err < 0) + return err; + } + ice->card_info = c; + goto __found; + } + } + } + c = &no_matched; + __found: + + err = snd_ice1712_pcm_profi(ice, pcm_dev++); + if (err < 0) + return err; + + if (ice_has_con_ac97(ice)) { + err = snd_ice1712_pcm(ice, pcm_dev++); + if (err < 0) + return err; + } + + err = snd_ice1712_ac97_mixer(ice); + if (err < 0) + return err; + + err = snd_ice1712_build_controls(ice); + if (err < 0) + return err; + + if (c->build_controls) { + err = c->build_controls(ice); + if (err < 0) + return err; + } + + if (ice_has_con_ac97(ice)) { + err = snd_ice1712_pcm_ds(ice, pcm_dev++); + if (err < 0) + return err; + } + + if (!c->no_mpu401) { + err = snd_mpu401_uart_new(card, 0, MPU401_HW_ICE1712, + ICEREG(ice, MPU1_CTRL), + c->mpu401_1_info_flags | + MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK, + -1, &ice->rmidi[0]); + if (err < 0) + return err; + if (c->mpu401_1_name) + /* Preferred name available in card_info */ + snprintf(ice->rmidi[0]->name, + sizeof(ice->rmidi[0]->name), + "%s %d", c->mpu401_1_name, card->number); + + if (ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_2xMPU401) { + /* 2nd port used */ + err = snd_mpu401_uart_new(card, 1, MPU401_HW_ICE1712, + ICEREG(ice, MPU2_CTRL), + c->mpu401_2_info_flags | + MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK, + -1, &ice->rmidi[1]); + + if (err < 0) + return err; + if (c->mpu401_2_name) + /* Preferred name available in card_info */ + snprintf(ice->rmidi[1]->name, + sizeof(ice->rmidi[1]->name), + "%s %d", c->mpu401_2_name, + card->number); + } + } + + snd_ice1712_set_input_clock_source(ice, 0); + + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, ice->port, ice->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ + dev++; + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int snd_ice1712_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ice1712 *ice = card->private_data; + + if (!ice->pm_suspend_enabled) + return 0; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + snd_ac97_suspend(ice->ac97); + + scoped_guard(spinlock_irq, &ice->reg_lock) { + ice->pm_saved_is_spdif_master = is_spdif_master(ice); + ice->pm_saved_spdif_ctrl = inw(ICEMT(ice, ROUTE_SPDOUT)); + ice->pm_saved_route = inw(ICEMT(ice, ROUTE_PSDOUT03)); + } + + if (ice->pm_suspend) + ice->pm_suspend(ice); + return 0; +} + +static int snd_ice1712_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ice1712 *ice = card->private_data; + int rate; + + if (!ice->pm_suspend_enabled) + return 0; + + if (ice->cur_rate) + rate = ice->cur_rate; + else + rate = PRO_RATE_DEFAULT; + + if (snd_ice1712_chip_init(ice) < 0) { + snd_card_disconnect(card); + return -EIO; + } + + ice->cur_rate = rate; + + if (ice->pm_resume) + ice->pm_resume(ice); + + if (ice->pm_saved_is_spdif_master) { + /* switching to external clock via SPDIF */ + scoped_guard(spinlock_irq, &ice->reg_lock) { + outb(inb(ICEMT(ice, RATE)) | ICE1712_SPDIF_MASTER, + ICEMT(ice, RATE)); + } + snd_ice1712_set_input_clock_source(ice, 1); + } else { + /* internal on-card clock */ + snd_ice1712_set_pro_rate(ice, rate, 1); + snd_ice1712_set_input_clock_source(ice, 0); + } + + outw(ice->pm_saved_spdif_ctrl, ICEMT(ice, ROUTE_SPDOUT)); + outw(ice->pm_saved_route, ICEMT(ice, ROUTE_PSDOUT03)); + + snd_ac97_resume(ice->ac97); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_ice1712_pm, snd_ice1712_suspend, snd_ice1712_resume); +#define SND_VT1712_PM_OPS &snd_ice1712_pm +#else +#define SND_VT1712_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct pci_driver ice1712_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_ice1712_ids, + .probe = snd_ice1712_probe, + .driver = { + .pm = SND_VT1712_PM_OPS, + }, +}; + +module_pci_driver(ice1712_driver); diff --git a/sound/pci/ice1712/ice1712.h b/sound/pci/ice1712/ice1712.h new file mode 100644 index 000000000..cd02710d8 --- /dev/null +++ b/sound/pci/ice1712/ice1712.h @@ -0,0 +1,525 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_ICE1712_H +#define __SOUND_ICE1712_H + +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Copyright (c) 2000 Jaroslav Kysela + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * Direct registers + */ + +#define ICEREG(ice, x) ((ice)->port + ICE1712_REG_##x) + +#define ICE1712_REG_CONTROL 0x00 /* byte */ +#define ICE1712_RESET 0x80 /* soft reset whole chip */ +#define ICE1712_SERR_ASSERT_DS_DMA 0x40 /* disabled SERR# assertion for the DS DMA Ch-C irq otherwise enabled */ +#define ICE1712_DOS_VOL 0x10 /* DOS WT/FM volume control */ +#define ICE1712_SERR_LEVEL 0x08 /* SERR# level otherwise edge */ +#define ICE1712_SERR_ASSERT_SB 0x02 /* disabled SERR# assertion for SB irq otherwise enabled */ +#define ICE1712_NATIVE 0x01 /* native mode otherwise SB */ +#define ICE1712_REG_IRQMASK 0x01 /* byte */ +#define ICE1712_IRQ_MPU1 0x80 /* MIDI irq mask */ +#define ICE1712_IRQ_TIMER 0x40 /* Timer mask */ +#define ICE1712_IRQ_MPU2 0x20 /* Secondary MIDI irq mask */ +#define ICE1712_IRQ_PROPCM 0x10 /* professional multi-track */ +#define ICE1712_IRQ_FM 0x08 /* FM/MIDI - legacy */ +#define ICE1712_IRQ_PBKDS 0x04 /* playback DS channels */ +#define ICE1712_IRQ_CONCAP 0x02 /* consumer capture */ +#define ICE1712_IRQ_CONPBK 0x01 /* consumer playback */ +#define ICE1712_REG_IRQSTAT 0x02 /* byte */ +/* look to ICE1712_IRQ_* */ +#define ICE1712_REG_INDEX 0x03 /* byte - indirect CCIxx regs */ +#define ICE1712_REG_DATA 0x04 /* byte - indirect CCIxx regs */ +#define ICE1712_REG_NMI_STAT1 0x05 /* byte */ +#define ICE1712_REG_NMI_DATA 0x06 /* byte */ +#define ICE1712_REG_NMI_INDEX 0x07 /* byte */ +#define ICE1712_REG_AC97_INDEX 0x08 /* byte */ +#define ICE1712_REG_AC97_CMD 0x09 /* byte */ +#define ICE1712_AC97_COLD 0x80 /* cold reset */ +#define ICE1712_AC97_WARM 0x40 /* warm reset */ +#define ICE1712_AC97_WRITE 0x20 /* W: write, R: write in progress */ +#define ICE1712_AC97_READ 0x10 /* W: read, R: read in progress */ +#define ICE1712_AC97_READY 0x08 /* codec ready status bit */ +#define ICE1712_AC97_PBK_VSR 0x02 /* playback VSR */ +#define ICE1712_AC97_CAP_VSR 0x01 /* capture VSR */ +#define ICE1712_REG_AC97_DATA 0x0a /* word (little endian) */ +#define ICE1712_REG_MPU1_CTRL 0x0c /* byte */ +#define ICE1712_REG_MPU1_DATA 0x0d /* byte */ +#define ICE1712_REG_I2C_DEV_ADDR 0x10 /* byte */ +#define ICE1712_I2C_WRITE 0x01 /* write direction */ +#define ICE1712_REG_I2C_BYTE_ADDR 0x11 /* byte */ +#define ICE1712_REG_I2C_DATA 0x12 /* byte */ +#define ICE1712_REG_I2C_CTRL 0x13 /* byte */ +#define ICE1712_I2C_EEPROM 0x80 /* EEPROM exists */ +#define ICE1712_I2C_BUSY 0x01 /* busy bit */ +#define ICE1712_REG_CONCAP_ADDR 0x14 /* dword - consumer capture */ +#define ICE1712_REG_CONCAP_COUNT 0x18 /* word - current/base count */ +#define ICE1712_REG_SERR_SHADOW 0x1b /* byte */ +#define ICE1712_REG_MPU2_CTRL 0x1c /* byte */ +#define ICE1712_REG_MPU2_DATA 0x1d /* byte */ +#define ICE1712_REG_TIMER 0x1e /* word */ + +/* + * Indirect registers + */ + +#define ICE1712_IREG_PBK_COUNT_LO 0x00 +#define ICE1712_IREG_PBK_COUNT_HI 0x01 +#define ICE1712_IREG_PBK_CTRL 0x02 +#define ICE1712_IREG_PBK_LEFT 0x03 /* left volume */ +#define ICE1712_IREG_PBK_RIGHT 0x04 /* right volume */ +#define ICE1712_IREG_PBK_SOFT 0x05 /* soft volume */ +#define ICE1712_IREG_PBK_RATE_LO 0x06 +#define ICE1712_IREG_PBK_RATE_MID 0x07 +#define ICE1712_IREG_PBK_RATE_HI 0x08 +#define ICE1712_IREG_CAP_COUNT_LO 0x10 +#define ICE1712_IREG_CAP_COUNT_HI 0x11 +#define ICE1712_IREG_CAP_CTRL 0x12 +#define ICE1712_IREG_GPIO_DATA 0x20 +#define ICE1712_IREG_GPIO_WRITE_MASK 0x21 +#define ICE1712_IREG_GPIO_DIRECTION 0x22 +#define ICE1712_IREG_CONSUMER_POWERDOWN 0x30 +#define ICE1712_IREG_PRO_POWERDOWN 0x31 + +/* + * Consumer section direct DMA registers + */ + +#define ICEDS(ice, x) ((ice)->dmapath_port + ICE1712_DS_##x) + +#define ICE1712_DS_INTMASK 0x00 /* word - interrupt mask */ +#define ICE1712_DS_INTSTAT 0x02 /* word - interrupt status */ +#define ICE1712_DS_DATA 0x04 /* dword - channel data */ +#define ICE1712_DS_INDEX 0x08 /* dword - channel index */ + +/* + * Consumer section channel registers + */ + +#define ICE1712_DSC_ADDR0 0x00 /* dword - base address 0 */ +#define ICE1712_DSC_COUNT0 0x01 /* word - count 0 */ +#define ICE1712_DSC_ADDR1 0x02 /* dword - base address 1 */ +#define ICE1712_DSC_COUNT1 0x03 /* word - count 1 */ +#define ICE1712_DSC_CONTROL 0x04 /* byte - control & status */ +#define ICE1712_BUFFER1 0x80 /* buffer1 is active */ +#define ICE1712_BUFFER1_AUTO 0x40 /* buffer1 auto init */ +#define ICE1712_BUFFER0_AUTO 0x20 /* buffer0 auto init */ +#define ICE1712_FLUSH 0x10 /* flush FIFO */ +#define ICE1712_STEREO 0x08 /* stereo */ +#define ICE1712_16BIT 0x04 /* 16-bit data */ +#define ICE1712_PAUSE 0x02 /* pause */ +#define ICE1712_START 0x01 /* start */ +#define ICE1712_DSC_RATE 0x05 /* dword - rate */ +#define ICE1712_DSC_VOLUME 0x06 /* word - volume control */ + +/* + * Professional multi-track direct control registers + */ + +#define ICEMT(ice, x) ((ice)->profi_port + ICE1712_MT_##x) + +#define ICE1712_MT_IRQ 0x00 /* byte - interrupt mask */ +#define ICE1712_MULTI_CAPTURE 0x80 /* capture IRQ */ +#define ICE1712_MULTI_PLAYBACK 0x40 /* playback IRQ */ +#define ICE1712_MULTI_CAPSTATUS 0x02 /* capture IRQ status */ +#define ICE1712_MULTI_PBKSTATUS 0x01 /* playback IRQ status */ +#define ICE1712_MT_RATE 0x01 /* byte - sampling rate select */ +#define ICE1712_SPDIF_MASTER 0x10 /* S/PDIF input is master clock */ +#define ICE1712_MT_I2S_FORMAT 0x02 /* byte - I2S data format */ +#define ICE1712_MT_AC97_INDEX 0x04 /* byte - AC'97 index */ +#define ICE1712_MT_AC97_CMD 0x05 /* byte - AC'97 command & status */ +/* look to ICE1712_AC97_* */ +#define ICE1712_MT_AC97_DATA 0x06 /* word - AC'97 data */ +#define ICE1712_MT_PLAYBACK_ADDR 0x10 /* dword - playback address */ +#define ICE1712_MT_PLAYBACK_SIZE 0x14 /* word - playback size */ +#define ICE1712_MT_PLAYBACK_COUNT 0x16 /* word - playback count */ +#define ICE1712_MT_PLAYBACK_CONTROL 0x18 /* byte - control */ +#define ICE1712_CAPTURE_START_SHADOW 0x04 /* capture start */ +#define ICE1712_PLAYBACK_PAUSE 0x02 /* playback pause */ +#define ICE1712_PLAYBACK_START 0x01 /* playback start */ +#define ICE1712_MT_CAPTURE_ADDR 0x20 /* dword - capture address */ +#define ICE1712_MT_CAPTURE_SIZE 0x24 /* word - capture size */ +#define ICE1712_MT_CAPTURE_COUNT 0x26 /* word - capture count */ +#define ICE1712_MT_CAPTURE_CONTROL 0x28 /* byte - control */ +#define ICE1712_CAPTURE_START 0x01 /* capture start */ +#define ICE1712_MT_ROUTE_PSDOUT03 0x30 /* word */ +#define ICE1712_MT_ROUTE_SPDOUT 0x32 /* word */ +#define ICE1712_MT_ROUTE_CAPTURE 0x34 /* dword */ +#define ICE1712_MT_MONITOR_VOLUME 0x38 /* word */ +#define ICE1712_MT_MONITOR_INDEX 0x3a /* byte */ +#define ICE1712_MT_MONITOR_RATE 0x3b /* byte */ +#define ICE1712_MT_MONITOR_ROUTECTRL 0x3c /* byte */ +#define ICE1712_ROUTE_AC97 0x01 /* route digital mixer output to AC'97 */ +#define ICE1712_MT_MONITOR_PEAKINDEX 0x3e /* byte */ +#define ICE1712_MT_MONITOR_PEAKDATA 0x3f /* byte */ + +/* + * Codec configuration bits + */ + +/* PCI[60] System Configuration */ +#define ICE1712_CFG_CLOCK 0xc0 +#define ICE1712_CFG_CLOCK512 0x00 /* 22.5692Mhz, 44.1kHz*512 */ +#define ICE1712_CFG_CLOCK384 0x40 /* 16.9344Mhz, 44.1kHz*384 */ +#define ICE1712_CFG_EXT 0x80 /* external clock */ +#define ICE1712_CFG_2xMPU401 0x20 /* two MPU401 UARTs */ +#define ICE1712_CFG_NO_CON_AC97 0x10 /* consumer AC'97 codec is not present */ +#define ICE1712_CFG_ADC_MASK 0x0c /* one, two, three, four stereo ADCs */ +#define ICE1712_CFG_DAC_MASK 0x03 /* one, two, three, four stereo DACs */ +/* PCI[61] AC-Link Configuration */ +#define ICE1712_CFG_PRO_I2S 0x80 /* multitrack converter: I2S or AC'97 */ +#define ICE1712_CFG_AC97_PACKED 0x01 /* split or packed mode - AC'97 */ +/* PCI[62] I2S Features */ +#define ICE1712_CFG_I2S_VOLUME 0x80 /* volume/mute capability */ +#define ICE1712_CFG_I2S_96KHZ 0x40 /* supports 96kHz sampling */ +#define ICE1712_CFG_I2S_RESMASK 0x30 /* resolution mask, 16,18,20,24-bit */ +#define ICE1712_CFG_I2S_OTHER 0x0f /* other I2S IDs */ +/* PCI[63] S/PDIF Configuration */ +#define ICE1712_CFG_I2S_CHIPID 0xfc /* I2S chip ID */ +#define ICE1712_CFG_SPDIF_IN 0x02 /* S/PDIF input is present */ +#define ICE1712_CFG_SPDIF_OUT 0x01 /* S/PDIF output is present */ + +/* + * DMA mode values + * identical with DMA_XXX on i386 architecture. + */ +#define ICE1712_DMA_MODE_WRITE 0x48 +#define ICE1712_DMA_AUTOINIT 0x10 + + +/* + * I2C EEPROM Address + */ +#define ICE_I2C_EEPROM_ADDR 0xA0 + +struct snd_ice1712; + +struct snd_ice1712_eeprom { + unsigned int subvendor; /* PCI[2c-2f] */ + unsigned char size; /* size of EEPROM image in bytes */ + unsigned char version; /* must be 1 (or 2 for vt1724) */ + unsigned char data[32]; + unsigned int gpiomask; + unsigned int gpiostate; + unsigned int gpiodir; +}; + +enum { + ICE_EEP1_CODEC = 0, /* 06 */ + ICE_EEP1_ACLINK, /* 07 */ + ICE_EEP1_I2SID, /* 08 */ + ICE_EEP1_SPDIF, /* 09 */ + ICE_EEP1_GPIO_MASK, /* 0a */ + ICE_EEP1_GPIO_STATE, /* 0b */ + ICE_EEP1_GPIO_DIR, /* 0c */ + ICE_EEP1_AC97_MAIN_LO, /* 0d */ + ICE_EEP1_AC97_MAIN_HI, /* 0e */ + ICE_EEP1_AC97_PCM_LO, /* 0f */ + ICE_EEP1_AC97_PCM_HI, /* 10 */ + ICE_EEP1_AC97_REC_LO, /* 11 */ + ICE_EEP1_AC97_REC_HI, /* 12 */ + ICE_EEP1_AC97_RECSRC, /* 13 */ + ICE_EEP1_DAC_ID, /* 14 */ + ICE_EEP1_DAC_ID1, + ICE_EEP1_DAC_ID2, + ICE_EEP1_DAC_ID3, + ICE_EEP1_ADC_ID, /* 18 */ + ICE_EEP1_ADC_ID1, + ICE_EEP1_ADC_ID2, + ICE_EEP1_ADC_ID3 +}; + +#define ice_has_con_ac97(ice) (!((ice)->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97)) + + +struct snd_ak4xxx_private { + unsigned int cif:1; /* CIF mode */ + unsigned char caddr; /* C0 and C1 bits */ + unsigned int data_mask; /* DATA gpio bit */ + unsigned int clk_mask; /* CLK gpio bit */ + unsigned int cs_mask; /* bit mask for select/deselect address */ + unsigned int cs_addr; /* bits to select address */ + unsigned int cs_none; /* bits to deselect address */ + unsigned int add_flags; /* additional bits at init */ + unsigned int mask_flags; /* total mask bits */ + struct snd_akm4xxx_ops { + void (*set_rate_val)(struct snd_akm4xxx *ak, unsigned int rate); + } ops; +}; + +struct snd_ice1712_spdif { + unsigned char cs8403_bits; + unsigned char cs8403_stream_bits; + struct snd_kcontrol *stream_ctl; + + struct snd_ice1712_spdif_ops { + void (*open)(struct snd_ice1712 *, struct snd_pcm_substream *); + void (*setup_rate)(struct snd_ice1712 *, int rate); + void (*close)(struct snd_ice1712 *, struct snd_pcm_substream *); + void (*default_get)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol); + int (*default_put)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol); + void (*stream_get)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol); + int (*stream_put)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol); + } ops; +}; + +struct snd_ice1712_card_info; + +struct snd_ice1712 { + unsigned long conp_dma_size; + unsigned long conc_dma_size; + unsigned long prop_dma_size; + unsigned long proc_dma_size; + int irq; + + unsigned long port; + unsigned long ddma_port; + unsigned long dmapath_port; + unsigned long profi_port; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm *pcm_ds; + struct snd_pcm *pcm_pro; + struct snd_pcm_substream *playback_con_substream; + struct snd_pcm_substream *playback_con_substream_ds[6]; + struct snd_pcm_substream *capture_con_substream; + struct snd_pcm_substream *playback_pro_substream; + struct snd_pcm_substream *capture_pro_substream; + unsigned int playback_pro_size; + unsigned int capture_pro_size; + unsigned int playback_con_virt_addr[6]; + unsigned int playback_con_active_buf[6]; + unsigned int capture_con_virt_addr; + unsigned int ac97_ext_id; + struct snd_ac97 *ac97; + struct snd_rawmidi *rmidi[2]; + + spinlock_t reg_lock; + struct snd_info_entry *proc_entry; + + struct snd_ice1712_eeprom eeprom; + const struct snd_ice1712_card_info *card_info; + + unsigned int pro_volumes[20]; + unsigned int omni:1; /* Delta Omni I/O */ + unsigned int dxr_enable:1; /* Terratec DXR enable for DMX6FIRE */ + unsigned int vt1724:1; + unsigned int vt1720:1; + unsigned int has_spdif:1; /* VT1720/4 - has SPDIF I/O */ + unsigned int force_pdma4:1; /* VT1720/4 - PDMA4 as non-spdif */ + unsigned int force_rdma1:1; /* VT1720/4 - RDMA1 as non-spdif */ + unsigned int midi_output:1; /* VT1720/4: MIDI output triggered */ + unsigned int midi_input:1; /* VT1720/4: MIDI input triggered */ + unsigned int own_routing:1; /* VT1720/4: use own routing ctls */ + unsigned int num_total_dacs; /* total DACs */ + unsigned int num_total_adcs; /* total ADCs */ + unsigned int cur_rate; /* current rate */ + + struct mutex open_mutex; + struct snd_pcm_substream *pcm_reserved[4]; + const struct snd_pcm_hw_constraint_list *hw_rates; /* card-specific rate constraints */ + + unsigned int akm_codecs; + struct snd_akm4xxx *akm; + struct snd_ice1712_spdif spdif; + + struct mutex i2c_mutex; /* I2C mutex for ICE1724 registers */ + struct snd_i2c_bus *i2c; /* I2C bus */ + struct snd_i2c_device *cs8427; /* CS8427 I2C device */ + unsigned int cs8427_timeout; /* CS8427 reset timeout in HZ/100 */ + + struct ice1712_gpio { + unsigned int direction; /* current direction bits */ + unsigned int write_mask; /* current mask bits */ + unsigned int saved[2]; /* for ewx_i2c */ + /* operators */ + void (*set_mask)(struct snd_ice1712 *ice, unsigned int data); + unsigned int (*get_mask)(struct snd_ice1712 *ice); + void (*set_dir)(struct snd_ice1712 *ice, unsigned int data); + unsigned int (*get_dir)(struct snd_ice1712 *ice); + void (*set_data)(struct snd_ice1712 *ice, unsigned int data); + unsigned int (*get_data)(struct snd_ice1712 *ice); + /* misc operators - move to another place? */ + void (*set_pro_rate)(struct snd_ice1712 *ice, unsigned int rate); + void (*i2s_mclk_changed)(struct snd_ice1712 *ice); + } gpio; + struct mutex gpio_mutex; + + /* other board-specific data */ + void *spec; + + /* VT172x specific */ + int pro_rate_default; + int (*is_spdif_master)(struct snd_ice1712 *ice); + unsigned int (*get_rate)(struct snd_ice1712 *ice); + void (*set_rate)(struct snd_ice1712 *ice, unsigned int rate); + unsigned char (*set_mclk)(struct snd_ice1712 *ice, unsigned int rate); + int (*set_spdif_clock)(struct snd_ice1712 *ice, int type); + int (*get_spdif_master_type)(struct snd_ice1712 *ice); + const char * const *ext_clock_names; + int ext_clock_count; + void (*pro_open)(struct snd_ice1712 *, struct snd_pcm_substream *); +#ifdef CONFIG_PM_SLEEP + int (*pm_suspend)(struct snd_ice1712 *); + int (*pm_resume)(struct snd_ice1712 *); + unsigned int pm_suspend_enabled:1; + unsigned int pm_saved_is_spdif_master:1; + unsigned int pm_saved_spdif_ctrl; + unsigned char pm_saved_spdif_cfg; + unsigned int pm_saved_route; +#endif +}; + + +/* + * gpio access functions + */ +static inline void snd_ice1712_gpio_set_dir(struct snd_ice1712 *ice, unsigned int bits) +{ + ice->gpio.set_dir(ice, bits); +} + +static inline unsigned int snd_ice1712_gpio_get_dir(struct snd_ice1712 *ice) +{ + return ice->gpio.get_dir(ice); +} + +static inline void snd_ice1712_gpio_set_mask(struct snd_ice1712 *ice, unsigned int bits) +{ + ice->gpio.set_mask(ice, bits); +} + +static inline void snd_ice1712_gpio_write(struct snd_ice1712 *ice, unsigned int val) +{ + ice->gpio.set_data(ice, val); +} + +static inline unsigned int snd_ice1712_gpio_read(struct snd_ice1712 *ice) +{ + return ice->gpio.get_data(ice); +} + +/* + * save and restore gpio status + * The access to gpio will be protected by mutex, so don't forget to + * restore! + */ +static inline void snd_ice1712_save_gpio_status(struct snd_ice1712 *ice) +{ + mutex_lock(&ice->gpio_mutex); + ice->gpio.saved[0] = ice->gpio.direction; + ice->gpio.saved[1] = ice->gpio.write_mask; +} + +static inline void snd_ice1712_restore_gpio_status(struct snd_ice1712 *ice) +{ + ice->gpio.set_dir(ice, ice->gpio.saved[0]); + ice->gpio.set_mask(ice, ice->gpio.saved[1]); + ice->gpio.direction = ice->gpio.saved[0]; + ice->gpio.write_mask = ice->gpio.saved[1]; + mutex_unlock(&ice->gpio_mutex); +} + +/* for bit controls */ +#define ICE1712_GPIO(xiface, xname, xindex, mask, invert, xaccess) \ +{ .iface = xiface, .name = xname, .access = xaccess, .info = snd_ctl_boolean_mono_info, \ + .get = snd_ice1712_gpio_get, .put = snd_ice1712_gpio_put, \ + .private_value = mask | (invert << 24) } + +int snd_ice1712_gpio_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +int snd_ice1712_gpio_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); + +/* + * set gpio direction, write mask and data + */ +static inline void snd_ice1712_gpio_write_bits(struct snd_ice1712 *ice, + unsigned int mask, unsigned int bits) +{ + unsigned val; + + ice->gpio.direction |= mask; + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); + val = snd_ice1712_gpio_read(ice); + val &= ~mask; + val |= mask & bits; + snd_ice1712_gpio_write(ice, val); +} + +static inline int snd_ice1712_gpio_read_bits(struct snd_ice1712 *ice, + unsigned int mask) +{ + ice->gpio.direction &= ~mask; + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); + return snd_ice1712_gpio_read(ice) & mask; +} + +/* route access functions */ +int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift); +int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val, + int shift); + +int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice); + +int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, + const struct snd_akm4xxx *template, + const struct snd_ak4xxx_private *priv, + struct snd_ice1712 *ice); +void snd_ice1712_akm4xxx_free(struct snd_ice1712 *ice); +int snd_ice1712_akm4xxx_build_controls(struct snd_ice1712 *ice); + +int snd_ice1712_init_cs8427(struct snd_ice1712 *ice, int addr); + +static inline void snd_ice1712_write(struct snd_ice1712 *ice, u8 addr, u8 data) +{ + outb(addr, ICEREG(ice, INDEX)); + outb(data, ICEREG(ice, DATA)); +} + +static inline u8 snd_ice1712_read(struct snd_ice1712 *ice, u8 addr) +{ + outb(addr, ICEREG(ice, INDEX)); + return inb(ICEREG(ice, DATA)); +} + + +/* + * entry pointer + */ + +struct snd_ice1712_card_info { + unsigned int subvendor; + const char *name; + const char *model; + const char *driver; + int (*chip_init)(struct snd_ice1712 *); + void (*chip_exit)(struct snd_ice1712 *); + int (*build_controls)(struct snd_ice1712 *); + unsigned int no_mpu401:1; + unsigned int mpu401_1_info_flags; + unsigned int mpu401_2_info_flags; + const char *mpu401_1_name; + const char *mpu401_2_name; + const unsigned int eeprom_size; + const unsigned char *eeprom_data; +}; + + +#endif /* __SOUND_ICE1712_H */ diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c new file mode 100644 index 000000000..859bb8739 --- /dev/null +++ b/sound/pci/ice1712/ice1724.c @@ -0,0 +1,2758 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for VT1724 ICEnsemble ICE1724 / VIA VT1724 (Envy24HT) + * VIA VT1720 (Envy24PT) + * + * Copyright (c) 2000 Jaroslav Kysela + * 2002 James Stafford + * 2003 Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" +#include "envy24ht.h" + +/* lowlevel routines */ +#include "amp.h" +#include "revo.h" +#include "aureon.h" +#include "vt1720_mobo.h" +#include "pontis.h" +#include "prodigy192.h" +#include "prodigy_hifi.h" +#include "juli.h" +#include "maya44.h" +#include "phase.h" +#include "wtm.h" +#include "se.h" +#include "quartet.h" +#include "psc724.h" + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("VIA ICEnsemble ICE1724/1720 (Envy24HT/PT)"); +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_PNP; /* Enable this card */ +static char *model[SNDRV_CARDS]; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ICE1724 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ICE1724 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ICE1724 soundcard."); +module_param_array(model, charp, NULL, 0444); +MODULE_PARM_DESC(model, "Use the given board model."); + + +/* Both VT1720 and VT1724 have the same PCI IDs */ +static const struct pci_device_id snd_vt1724_ids[] = { + { PCI_VDEVICE(ICE, PCI_DEVICE_ID_VT1724) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_vt1724_ids); + + +static int PRO_RATE_LOCKED; +static int PRO_RATE_RESET = 1; +static unsigned int PRO_RATE_DEFAULT = 44100; + +static const char * const ext_clock_names[1] = { "IEC958 In" }; + +/* + * Basic I/O + */ + +/* + * default rates, default clock routines + */ + +/* check whether the clock mode is spdif-in */ +static inline int stdclock_is_spdif_master(struct snd_ice1712 *ice) +{ + return (inb(ICEMT1724(ice, RATE)) & VT1724_SPDIF_MASTER) ? 1 : 0; +} + +/* + * locking rate makes sense only for internal clock mode + */ +static inline int is_pro_rate_locked(struct snd_ice1712 *ice) +{ + return (!ice->is_spdif_master(ice)) && PRO_RATE_LOCKED; +} + +/* + * ac97 section + */ + +static unsigned char snd_vt1724_ac97_ready(struct snd_ice1712 *ice) +{ + unsigned char old_cmd; + int tm; + for (tm = 0; tm < 0x10000; tm++) { + old_cmd = inb(ICEMT1724(ice, AC97_CMD)); + if (old_cmd & (VT1724_AC97_WRITE | VT1724_AC97_READ)) + continue; + if (!(old_cmd & VT1724_AC97_READY)) + continue; + return old_cmd; + } + dev_dbg(ice->card->dev, "snd_vt1724_ac97_ready: timeout\n"); + return old_cmd; +} + +static int snd_vt1724_ac97_wait_bit(struct snd_ice1712 *ice, unsigned char bit) +{ + int tm; + for (tm = 0; tm < 0x10000; tm++) + if ((inb(ICEMT1724(ice, AC97_CMD)) & bit) == 0) + return 0; + dev_dbg(ice->card->dev, "snd_vt1724_ac97_wait_bit: timeout\n"); + return -EIO; +} + +static void snd_vt1724_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct snd_ice1712 *ice = ac97->private_data; + unsigned char old_cmd; + + old_cmd = snd_vt1724_ac97_ready(ice); + old_cmd &= ~VT1724_AC97_ID_MASK; + old_cmd |= ac97->num; + outb(reg, ICEMT1724(ice, AC97_INDEX)); + outw(val, ICEMT1724(ice, AC97_DATA)); + outb(old_cmd | VT1724_AC97_WRITE, ICEMT1724(ice, AC97_CMD)); + snd_vt1724_ac97_wait_bit(ice, VT1724_AC97_WRITE); +} + +static unsigned short snd_vt1724_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct snd_ice1712 *ice = ac97->private_data; + unsigned char old_cmd; + + old_cmd = snd_vt1724_ac97_ready(ice); + old_cmd &= ~VT1724_AC97_ID_MASK; + old_cmd |= ac97->num; + outb(reg, ICEMT1724(ice, AC97_INDEX)); + outb(old_cmd | VT1724_AC97_READ, ICEMT1724(ice, AC97_CMD)); + if (snd_vt1724_ac97_wait_bit(ice, VT1724_AC97_READ) < 0) + return ~0; + return inw(ICEMT1724(ice, AC97_DATA)); +} + + +/* + * GPIO operations + */ + +/* set gpio direction 0 = read, 1 = write */ +static void snd_vt1724_set_gpio_dir(struct snd_ice1712 *ice, unsigned int data) +{ + outl(data, ICEREG1724(ice, GPIO_DIRECTION)); + inw(ICEREG1724(ice, GPIO_DIRECTION)); /* dummy read for pci-posting */ +} + +/* get gpio direction 0 = read, 1 = write */ +static unsigned int snd_vt1724_get_gpio_dir(struct snd_ice1712 *ice) +{ + return inl(ICEREG1724(ice, GPIO_DIRECTION)); +} + +/* set the gpio mask (0 = writable) */ +static void snd_vt1724_set_gpio_mask(struct snd_ice1712 *ice, unsigned int data) +{ + outw(data, ICEREG1724(ice, GPIO_WRITE_MASK)); + if (!ice->vt1720) /* VT1720 supports only 16 GPIO bits */ + outb((data >> 16) & 0xff, ICEREG1724(ice, GPIO_WRITE_MASK_22)); + inw(ICEREG1724(ice, GPIO_WRITE_MASK)); /* dummy read for pci-posting */ +} + +static unsigned int snd_vt1724_get_gpio_mask(struct snd_ice1712 *ice) +{ + unsigned int mask; + if (!ice->vt1720) + mask = (unsigned int)inb(ICEREG1724(ice, GPIO_WRITE_MASK_22)); + else + mask = 0; + mask = (mask << 16) | inw(ICEREG1724(ice, GPIO_WRITE_MASK)); + return mask; +} + +static void snd_vt1724_set_gpio_data(struct snd_ice1712 *ice, unsigned int data) +{ + outw(data, ICEREG1724(ice, GPIO_DATA)); + if (!ice->vt1720) + outb(data >> 16, ICEREG1724(ice, GPIO_DATA_22)); + inw(ICEREG1724(ice, GPIO_DATA)); /* dummy read for pci-posting */ +} + +static unsigned int snd_vt1724_get_gpio_data(struct snd_ice1712 *ice) +{ + unsigned int data; + if (!ice->vt1720) + data = (unsigned int)inb(ICEREG1724(ice, GPIO_DATA_22)); + else + data = 0; + data = (data << 16) | inw(ICEREG1724(ice, GPIO_DATA)); + return data; +} + +/* + * MIDI + */ + +static void vt1724_midi_clear_rx(struct snd_ice1712 *ice) +{ + unsigned int count; + + for (count = inb(ICEREG1724(ice, MPU_RXFIFO)); count > 0; --count) + inb(ICEREG1724(ice, MPU_DATA)); +} + +static inline struct snd_rawmidi_substream * +get_rawmidi_substream(struct snd_ice1712 *ice, unsigned int stream) +{ + return list_first_entry(&ice->rmidi[0]->streams[stream].substreams, + struct snd_rawmidi_substream, list); +} + +static void enable_midi_irq(struct snd_ice1712 *ice, u8 flag, int enable); + +static void vt1724_midi_write(struct snd_ice1712 *ice) +{ + struct snd_rawmidi_substream *s; + int count, i; + u8 buffer[32]; + + s = get_rawmidi_substream(ice, SNDRV_RAWMIDI_STREAM_OUTPUT); + count = 31 - inb(ICEREG1724(ice, MPU_TXFIFO)); + if (count > 0) { + count = snd_rawmidi_transmit(s, buffer, count); + for (i = 0; i < count; ++i) + outb(buffer[i], ICEREG1724(ice, MPU_DATA)); + } + /* mask irq when all bytes have been transmitted. + * enabled again in output_trigger when the new data comes in. + */ + enable_midi_irq(ice, VT1724_IRQ_MPU_TX, + !snd_rawmidi_transmit_empty(s)); +} + +static void vt1724_midi_read(struct snd_ice1712 *ice) +{ + struct snd_rawmidi_substream *s; + int count, i; + u8 buffer[32]; + + s = get_rawmidi_substream(ice, SNDRV_RAWMIDI_STREAM_INPUT); + count = inb(ICEREG1724(ice, MPU_RXFIFO)); + if (count > 0) { + count = min(count, 32); + for (i = 0; i < count; ++i) + buffer[i] = inb(ICEREG1724(ice, MPU_DATA)); + snd_rawmidi_receive(s, buffer, count); + } +} + +/* call with ice->reg_lock */ +static void enable_midi_irq(struct snd_ice1712 *ice, u8 flag, int enable) +{ + u8 mask = inb(ICEREG1724(ice, IRQMASK)); + if (enable) + mask &= ~flag; + else + mask |= flag; + outb(mask, ICEREG1724(ice, IRQMASK)); +} + +static void vt1724_enable_midi_irq(struct snd_rawmidi_substream *substream, + u8 flag, int enable) +{ + struct snd_ice1712 *ice = substream->rmidi->private_data; + + guard(spinlock_irq)(&ice->reg_lock); + enable_midi_irq(ice, flag, enable); +} + +static int vt1724_midi_output_open(struct snd_rawmidi_substream *s) +{ + return 0; +} + +static int vt1724_midi_output_close(struct snd_rawmidi_substream *s) +{ + return 0; +} + +static void vt1724_midi_output_trigger(struct snd_rawmidi_substream *s, int up) +{ + struct snd_ice1712 *ice = s->rmidi->private_data; + + guard(spinlock_irqsave)(&ice->reg_lock); + if (up) { + ice->midi_output = 1; + vt1724_midi_write(ice); + } else { + ice->midi_output = 0; + enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0); + } +} + +static void vt1724_midi_output_drain(struct snd_rawmidi_substream *s) +{ + struct snd_ice1712 *ice = s->rmidi->private_data; + unsigned long timeout; + + vt1724_enable_midi_irq(s, VT1724_IRQ_MPU_TX, 0); + /* 32 bytes should be transmitted in less than about 12 ms */ + timeout = jiffies + msecs_to_jiffies(15); + do { + if (inb(ICEREG1724(ice, MPU_CTRL)) & VT1724_MPU_TX_EMPTY) + break; + schedule_timeout_uninterruptible(1); + } while (time_after(timeout, jiffies)); +} + +static const struct snd_rawmidi_ops vt1724_midi_output_ops = { + .open = vt1724_midi_output_open, + .close = vt1724_midi_output_close, + .trigger = vt1724_midi_output_trigger, + .drain = vt1724_midi_output_drain, +}; + +static int vt1724_midi_input_open(struct snd_rawmidi_substream *s) +{ + vt1724_midi_clear_rx(s->rmidi->private_data); + vt1724_enable_midi_irq(s, VT1724_IRQ_MPU_RX, 1); + return 0; +} + +static int vt1724_midi_input_close(struct snd_rawmidi_substream *s) +{ + vt1724_enable_midi_irq(s, VT1724_IRQ_MPU_RX, 0); + return 0; +} + +static void vt1724_midi_input_trigger(struct snd_rawmidi_substream *s, int up) +{ + struct snd_ice1712 *ice = s->rmidi->private_data; + + guard(spinlock_irqsave)(&ice->reg_lock); + if (up) { + ice->midi_input = 1; + vt1724_midi_read(ice); + } else { + ice->midi_input = 0; + } +} + +static const struct snd_rawmidi_ops vt1724_midi_input_ops = { + .open = vt1724_midi_input_open, + .close = vt1724_midi_input_close, + .trigger = vt1724_midi_input_trigger, +}; + + +/* + * Interrupt handler + */ + +static irqreturn_t snd_vt1724_interrupt(int irq, void *dev_id) +{ + struct snd_ice1712 *ice = dev_id; + unsigned char status; + unsigned char status_mask = + VT1724_IRQ_MPU_RX | VT1724_IRQ_MPU_TX | VT1724_IRQ_MTPCM; + int handled = 0; + int timeout = 0; + + while (1) { + status = inb(ICEREG1724(ice, IRQSTAT)); + status &= status_mask; + if (status == 0) + break; + scoped_guard(spinlock, &ice->reg_lock) { + if (++timeout > 10) { + status = inb(ICEREG1724(ice, IRQSTAT)); + dev_err(ice->card->dev, + "Too long irq loop, status = 0x%x\n", status); + if (status & VT1724_IRQ_MPU_TX) { + dev_err(ice->card->dev, "Disabling MPU_TX\n"); + enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0); + } + goto out; + } + handled = 1; + if (status & VT1724_IRQ_MPU_TX) { + if (ice->midi_output) + vt1724_midi_write(ice); + else + enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0); + /* Due to mysterical reasons, MPU_TX is always + * generated (and can't be cleared) when a PCM + * playback is going. So let's ignore at the + * next loop. + */ + status_mask &= ~VT1724_IRQ_MPU_TX; + } + if (status & VT1724_IRQ_MPU_RX) { + if (ice->midi_input) + vt1724_midi_read(ice); + else + vt1724_midi_clear_rx(ice); + } + /* ack MPU irq */ + outb(status, ICEREG1724(ice, IRQSTAT)); + } + if (status & VT1724_IRQ_MTPCM) { + /* + * Multi-track PCM + * PCM assignment are: + * Playback DMA0 (M/C) = playback_pro_substream + * Playback DMA1 = playback_con_substream_ds[0] + * Playback DMA2 = playback_con_substream_ds[1] + * Playback DMA3 = playback_con_substream_ds[2] + * Playback DMA4 (SPDIF) = playback_con_substream + * Record DMA0 = capture_pro_substream + * Record DMA1 = capture_con_substream + */ + unsigned char mtstat = inb(ICEMT1724(ice, IRQ)); + if (mtstat & VT1724_MULTI_PDMA0) { + if (ice->playback_pro_substream) + snd_pcm_period_elapsed(ice->playback_pro_substream); + } + if (mtstat & VT1724_MULTI_RDMA0) { + if (ice->capture_pro_substream) + snd_pcm_period_elapsed(ice->capture_pro_substream); + } + if (mtstat & VT1724_MULTI_PDMA1) { + if (ice->playback_con_substream_ds[0]) + snd_pcm_period_elapsed(ice->playback_con_substream_ds[0]); + } + if (mtstat & VT1724_MULTI_PDMA2) { + if (ice->playback_con_substream_ds[1]) + snd_pcm_period_elapsed(ice->playback_con_substream_ds[1]); + } + if (mtstat & VT1724_MULTI_PDMA3) { + if (ice->playback_con_substream_ds[2]) + snd_pcm_period_elapsed(ice->playback_con_substream_ds[2]); + } + if (mtstat & VT1724_MULTI_PDMA4) { + if (ice->playback_con_substream) + snd_pcm_period_elapsed(ice->playback_con_substream); + } + if (mtstat & VT1724_MULTI_RDMA1) { + if (ice->capture_con_substream) + snd_pcm_period_elapsed(ice->capture_con_substream); + } + /* ack anyway to avoid freeze */ + outb(mtstat, ICEMT1724(ice, IRQ)); + /* ought to really handle this properly */ + if (mtstat & VT1724_MULTI_FIFO_ERR) { + unsigned char fstat = inb(ICEMT1724(ice, DMA_FIFO_ERR)); + outb(fstat, ICEMT1724(ice, DMA_FIFO_ERR)); + outb(VT1724_MULTI_FIFO_ERR | inb(ICEMT1724(ice, DMA_INT_MASK)), ICEMT1724(ice, DMA_INT_MASK)); + /* If I don't do this, I get machine lockup due to continual interrupts */ + } + + } + } + out: + return IRQ_RETVAL(handled); +} + +/* + * PCM code - professional part (multitrack) + */ + +static const unsigned int rates[] = { + 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000, + 176400, 192000, +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_rates_96 = { + .count = ARRAY_SIZE(rates) - 2, /* up to 96000 */ + .list = rates, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_rates_48 = { + .count = ARRAY_SIZE(rates) - 5, /* up to 48000 */ + .list = rates, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_rates_192 = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +struct vt1724_pcm_reg { + unsigned int addr; /* ADDR register offset */ + unsigned int size; /* SIZE register offset */ + unsigned int count; /* COUNT register offset */ + unsigned int start; /* start & pause bit */ +}; + +static int snd_vt1724_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + unsigned char what; + unsigned char old; + struct snd_pcm_substream *s; + + what = 0; + snd_pcm_group_for_each_entry(s, substream) { + if (snd_pcm_substream_chip(s) == ice) { + const struct vt1724_pcm_reg *reg; + reg = s->runtime->private_data; + what |= reg->start; + snd_pcm_trigger_done(s, substream); + } + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + scoped_guard(spinlock, &ice->reg_lock) { + old = inb(ICEMT1724(ice, DMA_PAUSE)); + if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) + old |= what; + else + old &= ~what; + outb(old, ICEMT1724(ice, DMA_PAUSE)); + } + break; + + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + scoped_guard(spinlock, &ice->reg_lock) { + old = inb(ICEMT1724(ice, DMA_CONTROL)); + if (cmd == SNDRV_PCM_TRIGGER_START) + old |= what; + else + old &= ~what; + outb(old, ICEMT1724(ice, DMA_CONTROL)); + } + break; + + case SNDRV_PCM_TRIGGER_RESUME: + /* apps will have to restart stream */ + break; + + default: + return -EINVAL; + } + return 0; +} + +/* + */ + +#define DMA_STARTS (VT1724_RDMA0_START|VT1724_PDMA0_START|VT1724_RDMA1_START|\ + VT1724_PDMA1_START|VT1724_PDMA2_START|VT1724_PDMA3_START|VT1724_PDMA4_START) +#define DMA_PAUSES (VT1724_RDMA0_PAUSE|VT1724_PDMA0_PAUSE|VT1724_RDMA1_PAUSE|\ + VT1724_PDMA1_PAUSE|VT1724_PDMA2_PAUSE|VT1724_PDMA3_PAUSE|VT1724_PDMA4_PAUSE) + +static const unsigned int stdclock_rate_list[16] = { + 48000, 24000, 12000, 9600, 32000, 16000, 8000, 96000, 44100, + 22050, 11025, 88200, 176400, 0, 192000, 64000 +}; + +static unsigned int stdclock_get_rate(struct snd_ice1712 *ice) +{ + return stdclock_rate_list[inb(ICEMT1724(ice, RATE)) & 15]; +} + +static void stdclock_set_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + int i; + for (i = 0; i < ARRAY_SIZE(stdclock_rate_list); i++) { + if (stdclock_rate_list[i] == rate) { + outb(i, ICEMT1724(ice, RATE)); + return; + } + } +} + +static unsigned char stdclock_set_mclk(struct snd_ice1712 *ice, + unsigned int rate) +{ + unsigned char val, old; + /* check MT02 */ + if (ice->eeprom.data[ICE_EEP2_ACLINK] & VT1724_CFG_PRO_I2S) { + val = old = inb(ICEMT1724(ice, I2S_FORMAT)); + if (rate > 96000) + val |= VT1724_MT_I2S_MCLK_128X; /* 128x MCLK */ + else + val &= ~VT1724_MT_I2S_MCLK_128X; /* 256x MCLK */ + if (val != old) { + outb(val, ICEMT1724(ice, I2S_FORMAT)); + /* master clock changed */ + return 1; + } + } + /* no change in master clock */ + return 0; +} + +static int snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, + int force) +{ + unsigned char mclk_change; + unsigned int i, old_rate; + bool call_set_rate = false; + + if (rate > ice->hw_rates->list[ice->hw_rates->count - 1]) + return -EINVAL; + + scoped_guard(spinlock_irqsave, &ice->reg_lock) { + if ((inb(ICEMT1724(ice, DMA_CONTROL)) & DMA_STARTS) || + (inb(ICEMT1724(ice, DMA_PAUSE)) & DMA_PAUSES)) { + /* running? we cannot change the rate now... */ + return ((rate == ice->cur_rate) && !force) ? 0 : -EBUSY; + } + if (!force && is_pro_rate_locked(ice)) { + /* comparing required and current rate - makes sense for + * internal clock only */ + return (rate == ice->cur_rate) ? 0 : -EBUSY; + } + + if (force || !ice->is_spdif_master(ice)) { + /* force means the rate was switched by ucontrol, otherwise + * setting clock rate for internal clock mode */ + old_rate = ice->get_rate(ice); + if (force || (old_rate != rate)) + call_set_rate = true; + else if (rate == ice->cur_rate) { + return 0; + } + } + + ice->cur_rate = rate; + } + + if (call_set_rate) + ice->set_rate(ice, rate); + + /* setting master clock */ + mclk_change = ice->set_mclk(ice, rate); + + if (mclk_change && ice->gpio.i2s_mclk_changed) + ice->gpio.i2s_mclk_changed(ice); + if (ice->gpio.set_pro_rate) + ice->gpio.set_pro_rate(ice, rate); + + /* set up codecs */ + for (i = 0; i < ice->akm_codecs; i++) { + if (ice->akm[i].ops.set_rate_val) + ice->akm[i].ops.set_rate_val(&ice->akm[i], rate); + } + if (ice->spdif.ops.setup_rate) + ice->spdif.ops.setup_rate(ice, rate); + + return 0; +} + +static int __snd_vt1724_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + int i, chs; + + chs = params_channels(hw_params); + /* mark surround channels */ + if (substream == ice->playback_pro_substream) { + /* PDMA0 can be multi-channel up to 8 */ + chs = chs / 2 - 1; + for (i = 0; i < chs; i++) { + if (ice->pcm_reserved[i] && + ice->pcm_reserved[i] != substream) + return -EBUSY; + ice->pcm_reserved[i] = substream; + } + for (; i < 3; i++) { + if (ice->pcm_reserved[i] == substream) + ice->pcm_reserved[i] = NULL; + } + } else { + for (i = 0; i < 3; i++) { + /* check individual playback stream */ + if (ice->playback_con_substream_ds[i] == substream) { + if (ice->pcm_reserved[i] && + ice->pcm_reserved[i] != substream) + return -EBUSY; + ice->pcm_reserved[i] = substream; + break; + } + } + } + + return 0; +} + +static int snd_vt1724_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + int err; + + scoped_guard(mutex, &ice->open_mutex) { + err = __snd_vt1724_pcm_hw_params(substream, hw_params); + if (err < 0) + return err; + } + + return snd_vt1724_set_pro_rate(ice, params_rate(hw_params), 0); +} + +static int snd_vt1724_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + bool released = false; + int i; + + scoped_guard(mutex, &ice->open_mutex) { + /* unmark surround channels */ + for (i = 0; i < 3; i++) { + if (ice->pcm_reserved[i] != substream) + continue; + ice->pcm_reserved[i] = NULL; + released = true; + } + } + + if (released && ice->pcm_ds) + wake_up(&ice->pcm_ds->open_wait); + + return 0; +} + +static int snd_vt1724_playback_pro_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + unsigned char val; + unsigned int size; + + guard(spinlock_irq)(&ice->reg_lock); + val = (8 - substream->runtime->channels) >> 1; + outb(val, ICEMT1724(ice, BURST)); + + outl(substream->runtime->dma_addr, ICEMT1724(ice, PLAYBACK_ADDR)); + + size = (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1; + /* outl(size, ICEMT1724(ice, PLAYBACK_SIZE)); */ + outw(size, ICEMT1724(ice, PLAYBACK_SIZE)); + outb(size >> 16, ICEMT1724(ice, PLAYBACK_SIZE) + 2); + size = (snd_pcm_lib_period_bytes(substream) >> 2) - 1; + /* outl(size, ICEMT1724(ice, PLAYBACK_COUNT)); */ + outw(size, ICEMT1724(ice, PLAYBACK_COUNT)); + outb(size >> 16, ICEMT1724(ice, PLAYBACK_COUNT) + 2); + + /* + dev_dbg(ice->card->dev, "pro prepare: ch = %d, addr = 0x%x, " + "buffer = 0x%x, period = 0x%x\n", + substream->runtime->channels, + (unsigned int)substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream)); + */ + return 0; +} + +static snd_pcm_uframes_t snd_vt1724_playback_pro_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(inl(ICEMT1724(ice, DMA_CONTROL)) & VT1724_PDMA0_START)) + return 0; +#if 0 /* read PLAYBACK_ADDR */ + ptr = inl(ICEMT1724(ice, PLAYBACK_ADDR)); + if (ptr < substream->runtime->dma_addr) { + dev_dbg(ice->card->dev, "invalid negative ptr\n"); + return 0; + } + ptr -= substream->runtime->dma_addr; + ptr = bytes_to_frames(substream->runtime, ptr); + if (ptr >= substream->runtime->buffer_size) { + dev_dbg(ice->card->dev, "invalid ptr %d (size=%d)\n", + (int)ptr, (int)substream->runtime->period_size); + return 0; + } +#else /* read PLAYBACK_SIZE */ + ptr = inl(ICEMT1724(ice, PLAYBACK_SIZE)) & 0xffffff; + ptr = (ptr + 1) << 2; + ptr = bytes_to_frames(substream->runtime, ptr); + if (!ptr) + ; + else if (ptr <= substream->runtime->buffer_size) + ptr = substream->runtime->buffer_size - ptr; + else { + dev_dbg(ice->card->dev, "invalid ptr %d (size=%d)\n", + (int)ptr, (int)substream->runtime->buffer_size); + ptr = 0; + } +#endif + return ptr; +} + +static int snd_vt1724_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + const struct vt1724_pcm_reg *reg = substream->runtime->private_data; + + guard(spinlock_irq)(&ice->reg_lock); + outl(substream->runtime->dma_addr, ice->profi_port + reg->addr); + outw((snd_pcm_lib_buffer_bytes(substream) >> 2) - 1, + ice->profi_port + reg->size); + outw((snd_pcm_lib_period_bytes(substream) >> 2) - 1, + ice->profi_port + reg->count); + return 0; +} + +static snd_pcm_uframes_t snd_vt1724_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + const struct vt1724_pcm_reg *reg = substream->runtime->private_data; + size_t ptr; + + if (!(inl(ICEMT1724(ice, DMA_CONTROL)) & reg->start)) + return 0; +#if 0 /* use ADDR register */ + ptr = inl(ice->profi_port + reg->addr); + ptr -= substream->runtime->dma_addr; + return bytes_to_frames(substream->runtime, ptr); +#else /* use SIZE register */ + ptr = inw(ice->profi_port + reg->size); + ptr = (ptr + 1) << 2; + ptr = bytes_to_frames(substream->runtime, ptr); + if (!ptr) + ; + else if (ptr <= substream->runtime->buffer_size) + ptr = substream->runtime->buffer_size - ptr; + else { + dev_dbg(ice->card->dev, "invalid ptr %d (size=%d)\n", + (int)ptr, (int)substream->runtime->buffer_size); + ptr = 0; + } + return ptr; +#endif +} + +static const struct vt1724_pcm_reg vt1724_pdma0_reg = { + .addr = VT1724_MT_PLAYBACK_ADDR, + .size = VT1724_MT_PLAYBACK_SIZE, + .count = VT1724_MT_PLAYBACK_COUNT, + .start = VT1724_PDMA0_START, +}; + +static const struct vt1724_pcm_reg vt1724_pdma4_reg = { + .addr = VT1724_MT_PDMA4_ADDR, + .size = VT1724_MT_PDMA4_SIZE, + .count = VT1724_MT_PDMA4_COUNT, + .start = VT1724_PDMA4_START, +}; + +static const struct vt1724_pcm_reg vt1724_rdma0_reg = { + .addr = VT1724_MT_CAPTURE_ADDR, + .size = VT1724_MT_CAPTURE_SIZE, + .count = VT1724_MT_CAPTURE_COUNT, + .start = VT1724_RDMA0_START, +}; + +static const struct vt1724_pcm_reg vt1724_rdma1_reg = { + .addr = VT1724_MT_RDMA1_ADDR, + .size = VT1724_MT_RDMA1_SIZE, + .count = VT1724_MT_RDMA1_COUNT, + .start = VT1724_RDMA1_START, +}; + +#define vt1724_playback_pro_reg vt1724_pdma0_reg +#define vt1724_playback_spdif_reg vt1724_pdma4_reg +#define vt1724_capture_pro_reg vt1724_rdma0_reg +#define vt1724_capture_spdif_reg vt1724_rdma1_reg + +static const struct snd_pcm_hardware snd_vt1724_playback_pro = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = (1UL << 21), /* 19bits dword */ + .period_bytes_min = 8 * 4 * 2, /* FIXME: constraints needed */ + .period_bytes_max = (1UL << 21), + .periods_min = 2, + .periods_max = 1024, +}; + +static const struct snd_pcm_hardware snd_vt1724_spdif = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = (SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_88200| + SNDRV_PCM_RATE_96000|SNDRV_PCM_RATE_176400| + SNDRV_PCM_RATE_192000), + .rate_min = 32000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (1UL << 18), /* 16bits dword */ + .period_bytes_min = 2 * 4 * 2, + .period_bytes_max = (1UL << 18), + .periods_min = 2, + .periods_max = 1024, +}; + +static const struct snd_pcm_hardware snd_vt1724_2ch_stereo = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (1UL << 18), /* 16bits dword */ + .period_bytes_min = 2 * 4 * 2, + .period_bytes_max = (1UL << 18), + .periods_min = 2, + .periods_max = 1024, +}; + +/* + * set rate constraints + */ +static void set_std_hw_rates(struct snd_ice1712 *ice) +{ + if (ice->eeprom.data[ICE_EEP2_ACLINK] & VT1724_CFG_PRO_I2S) { + /* I2S */ + /* VT1720 doesn't support more than 96kHz */ + if ((ice->eeprom.data[ICE_EEP2_I2S] & 0x08) && !ice->vt1720) + ice->hw_rates = &hw_constraints_rates_192; + else + ice->hw_rates = &hw_constraints_rates_96; + } else { + /* ACLINK */ + ice->hw_rates = &hw_constraints_rates_48; + } +} + +static int set_rate_constraints(struct snd_ice1712 *ice, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.rate_min = ice->hw_rates->list[0]; + runtime->hw.rate_max = ice->hw_rates->list[ice->hw_rates->count - 1]; + runtime->hw.rates = SNDRV_PCM_RATE_KNOT; + return snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + ice->hw_rates); +} + +/* if the card has the internal rate locked (is_pro_rate_locked), limit runtime + hw rates to the current internal rate only. +*/ +static void constrain_rate_if_locked(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int rate; + if (is_pro_rate_locked(ice)) { + rate = ice->get_rate(ice); + if (rate >= runtime->hw.rate_min + && rate <= runtime->hw.rate_max) { + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } + } +} + + +/* multi-channel playback needs alignment 8x32bit regardless of the channels + * actually used + */ +#define VT1724_BUFFER_ALIGN 0x20 + +static int snd_vt1724_playback_pro_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + int chs, num_indeps; + + runtime->private_data = (void *)&vt1724_playback_pro_reg; + ice->playback_pro_substream = substream; + runtime->hw = snd_vt1724_playback_pro; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + set_rate_constraints(ice, substream); + scoped_guard(mutex, &ice->open_mutex) { + /* calculate the currently available channels */ + num_indeps = ice->num_total_dacs / 2 - 1; + for (chs = 0; chs < num_indeps; chs++) { + if (ice->pcm_reserved[chs]) + break; + } + chs = (chs + 1) * 2; + runtime->hw.channels_max = chs; + if (chs > 2) /* channels must be even */ + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2); + } + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + VT1724_BUFFER_ALIGN); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + VT1724_BUFFER_ALIGN); + constrain_rate_if_locked(substream); + if (ice->pro_open) + ice->pro_open(ice, substream); + return 0; +} + +static int snd_vt1724_capture_pro_open(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->private_data = (void *)&vt1724_capture_pro_reg; + ice->capture_pro_substream = substream; + runtime->hw = snd_vt1724_2ch_stereo; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + set_rate_constraints(ice, substream); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + VT1724_BUFFER_ALIGN); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + VT1724_BUFFER_ALIGN); + constrain_rate_if_locked(substream); + if (ice->pro_open) + ice->pro_open(ice, substream); + return 0; +} + +static int snd_vt1724_playback_pro_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + if (PRO_RATE_RESET) + snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0); + ice->playback_pro_substream = NULL; + + return 0; +} + +static int snd_vt1724_capture_pro_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + if (PRO_RATE_RESET) + snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0); + ice->capture_pro_substream = NULL; + return 0; +} + +static const struct snd_pcm_ops snd_vt1724_playback_pro_ops = { + .open = snd_vt1724_playback_pro_open, + .close = snd_vt1724_playback_pro_close, + .hw_params = snd_vt1724_pcm_hw_params, + .hw_free = snd_vt1724_pcm_hw_free, + .prepare = snd_vt1724_playback_pro_prepare, + .trigger = snd_vt1724_pcm_trigger, + .pointer = snd_vt1724_playback_pro_pointer, +}; + +static const struct snd_pcm_ops snd_vt1724_capture_pro_ops = { + .open = snd_vt1724_capture_pro_open, + .close = snd_vt1724_capture_pro_close, + .hw_params = snd_vt1724_pcm_hw_params, + .hw_free = snd_vt1724_pcm_hw_free, + .prepare = snd_vt1724_pcm_prepare, + .trigger = snd_vt1724_pcm_trigger, + .pointer = snd_vt1724_pcm_pointer, +}; + +static int snd_vt1724_pcm_profi(struct snd_ice1712 *ice, int device) +{ + struct snd_pcm *pcm; + int capt, err; + + if ((ice->eeprom.data[ICE_EEP2_SYSCONF] & VT1724_CFG_ADC_MASK) == + VT1724_CFG_ADC_NONE) + capt = 0; + else + capt = 1; + err = snd_pcm_new(ice->card, "ICE1724", device, 1, capt, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_vt1724_playback_pro_ops); + if (capt) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_vt1724_capture_pro_ops); + + pcm->private_data = ice; + pcm->info_flags = 0; + strscpy(pcm->name, "ICE1724"); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &ice->pci->dev, 256*1024, 256*1024); + + ice->pcm_pro = pcm; + + return 0; +} + + +/* + * SPDIF PCM + */ + +/* update spdif control bits; call with reg_lock */ +static void update_spdif_bits(struct snd_ice1712 *ice, unsigned int val) +{ + unsigned char cbit, disabled; + + cbit = inb(ICEREG1724(ice, SPDIF_CFG)); + disabled = cbit & ~VT1724_CFG_SPDIF_OUT_EN; + if (cbit != disabled) + outb(disabled, ICEREG1724(ice, SPDIF_CFG)); + outw(val, ICEMT1724(ice, SPDIF_CTRL)); + if (cbit != disabled) + outb(cbit, ICEREG1724(ice, SPDIF_CFG)); + outw(val, ICEMT1724(ice, SPDIF_CTRL)); +} + +/* update SPDIF control bits according to the given rate */ +static void update_spdif_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + unsigned int val, nval; + + guard(spinlock_irqsave)(&ice->reg_lock); + nval = val = inw(ICEMT1724(ice, SPDIF_CTRL)); + nval &= ~(7 << 12); + switch (rate) { + case 44100: break; + case 48000: nval |= 2 << 12; break; + case 32000: nval |= 3 << 12; break; + case 88200: nval |= 4 << 12; break; + case 96000: nval |= 5 << 12; break; + case 192000: nval |= 6 << 12; break; + case 176400: nval |= 7 << 12; break; + } + if (val != nval) + update_spdif_bits(ice, nval); +} + +static int snd_vt1724_playback_spdif_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + if (!ice->force_pdma4) + update_spdif_rate(ice, substream->runtime->rate); + return snd_vt1724_pcm_prepare(substream); +} + +static int snd_vt1724_playback_spdif_open(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->private_data = (void *)&vt1724_playback_spdif_reg; + ice->playback_con_substream = substream; + if (ice->force_pdma4) { + runtime->hw = snd_vt1724_2ch_stereo; + set_rate_constraints(ice, substream); + } else + runtime->hw = snd_vt1724_spdif; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + VT1724_BUFFER_ALIGN); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + VT1724_BUFFER_ALIGN); + constrain_rate_if_locked(substream); + if (ice->spdif.ops.open) + ice->spdif.ops.open(ice, substream); + return 0; +} + +static int snd_vt1724_playback_spdif_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + if (PRO_RATE_RESET) + snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0); + ice->playback_con_substream = NULL; + if (ice->spdif.ops.close) + ice->spdif.ops.close(ice, substream); + + return 0; +} + +static int snd_vt1724_capture_spdif_open(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->private_data = (void *)&vt1724_capture_spdif_reg; + ice->capture_con_substream = substream; + if (ice->force_rdma1) { + runtime->hw = snd_vt1724_2ch_stereo; + set_rate_constraints(ice, substream); + } else + runtime->hw = snd_vt1724_spdif; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + VT1724_BUFFER_ALIGN); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + VT1724_BUFFER_ALIGN); + constrain_rate_if_locked(substream); + if (ice->spdif.ops.open) + ice->spdif.ops.open(ice, substream); + return 0; +} + +static int snd_vt1724_capture_spdif_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + if (PRO_RATE_RESET) + snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0); + ice->capture_con_substream = NULL; + if (ice->spdif.ops.close) + ice->spdif.ops.close(ice, substream); + + return 0; +} + +static const struct snd_pcm_ops snd_vt1724_playback_spdif_ops = { + .open = snd_vt1724_playback_spdif_open, + .close = snd_vt1724_playback_spdif_close, + .hw_params = snd_vt1724_pcm_hw_params, + .hw_free = snd_vt1724_pcm_hw_free, + .prepare = snd_vt1724_playback_spdif_prepare, + .trigger = snd_vt1724_pcm_trigger, + .pointer = snd_vt1724_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_vt1724_capture_spdif_ops = { + .open = snd_vt1724_capture_spdif_open, + .close = snd_vt1724_capture_spdif_close, + .hw_params = snd_vt1724_pcm_hw_params, + .hw_free = snd_vt1724_pcm_hw_free, + .prepare = snd_vt1724_pcm_prepare, + .trigger = snd_vt1724_pcm_trigger, + .pointer = snd_vt1724_pcm_pointer, +}; + + +static int snd_vt1724_pcm_spdif(struct snd_ice1712 *ice, int device) +{ + char *name; + struct snd_pcm *pcm; + int play, capt; + int err; + + if (ice->force_pdma4 || + (ice->eeprom.data[ICE_EEP2_SPDIF] & VT1724_CFG_SPDIF_OUT_INT)) { + play = 1; + ice->has_spdif = 1; + } else + play = 0; + if (ice->force_rdma1 || + (ice->eeprom.data[ICE_EEP2_SPDIF] & VT1724_CFG_SPDIF_IN)) { + capt = 1; + ice->has_spdif = 1; + } else + capt = 0; + if (!play && !capt) + return 0; /* no spdif device */ + + if (ice->force_pdma4 || ice->force_rdma1) + name = "ICE1724 Secondary"; + else + name = "ICE1724 IEC958"; + err = snd_pcm_new(ice->card, name, device, play, capt, &pcm); + if (err < 0) + return err; + + if (play) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_vt1724_playback_spdif_ops); + if (capt) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_vt1724_capture_spdif_ops); + + pcm->private_data = ice; + pcm->info_flags = 0; + strscpy(pcm->name, name); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &ice->pci->dev, 256*1024, 256*1024); + + ice->pcm = pcm; + + return 0; +} + + +/* + * independent surround PCMs + */ + +static const struct vt1724_pcm_reg vt1724_playback_dma_regs[3] = { + { + .addr = VT1724_MT_PDMA1_ADDR, + .size = VT1724_MT_PDMA1_SIZE, + .count = VT1724_MT_PDMA1_COUNT, + .start = VT1724_PDMA1_START, + }, + { + .addr = VT1724_MT_PDMA2_ADDR, + .size = VT1724_MT_PDMA2_SIZE, + .count = VT1724_MT_PDMA2_COUNT, + .start = VT1724_PDMA2_START, + }, + { + .addr = VT1724_MT_PDMA3_ADDR, + .size = VT1724_MT_PDMA3_SIZE, + .count = VT1724_MT_PDMA3_COUNT, + .start = VT1724_PDMA3_START, + }, +}; + +static int snd_vt1724_playback_indep_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + unsigned char val; + + scoped_guard(spinlock_irq, &ice->reg_lock) { + val = 3 - substream->number; + if (inb(ICEMT1724(ice, BURST)) < val) + outb(val, ICEMT1724(ice, BURST)); + } + return snd_vt1724_pcm_prepare(substream); +} + +static int snd_vt1724_playback_indep_open(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + scoped_guard(mutex, &ice->open_mutex) { + /* already used by PDMA0? */ + if (ice->pcm_reserved[substream->number]) + return -EAGAIN; + } + runtime->private_data = (void *)&vt1724_playback_dma_regs[substream->number]; + ice->playback_con_substream_ds[substream->number] = substream; + runtime->hw = snd_vt1724_2ch_stereo; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + set_rate_constraints(ice, substream); + return 0; +} + +static int snd_vt1724_playback_indep_close(struct snd_pcm_substream *substream) +{ + struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); + + if (PRO_RATE_RESET) + snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0); + ice->playback_con_substream_ds[substream->number] = NULL; + ice->pcm_reserved[substream->number] = NULL; + + return 0; +} + +static const struct snd_pcm_ops snd_vt1724_playback_indep_ops = { + .open = snd_vt1724_playback_indep_open, + .close = snd_vt1724_playback_indep_close, + .hw_params = snd_vt1724_pcm_hw_params, + .hw_free = snd_vt1724_pcm_hw_free, + .prepare = snd_vt1724_playback_indep_prepare, + .trigger = snd_vt1724_pcm_trigger, + .pointer = snd_vt1724_pcm_pointer, +}; + + +static int snd_vt1724_pcm_indep(struct snd_ice1712 *ice, int device) +{ + struct snd_pcm *pcm; + int play; + int err; + + play = ice->num_total_dacs / 2 - 1; + if (play <= 0) + return 0; + + err = snd_pcm_new(ice->card, "ICE1724 Surrounds", device, play, 0, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_vt1724_playback_indep_ops); + + pcm->private_data = ice; + pcm->info_flags = 0; + strscpy(pcm->name, "ICE1724 Surround PCM"); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &ice->pci->dev, 256*1024, 256*1024); + + ice->pcm_ds = pcm; + + return 0; +} + + +/* + * Mixer section + */ + +static int snd_vt1724_ac97_mixer(struct snd_ice1712 *ice) +{ + int err; + + if (!(ice->eeprom.data[ICE_EEP2_ACLINK] & VT1724_CFG_PRO_I2S)) { + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + static const struct snd_ac97_bus_ops ops = { + .write = snd_vt1724_ac97_write, + .read = snd_vt1724_ac97_read, + }; + + /* cold reset */ + outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD)); + mdelay(5); /* FIXME */ + outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD)); + + err = snd_ac97_bus(ice->card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = ice; + err = snd_ac97_mixer(pbus, &ac97, &ice->ac97); + if (err < 0) + dev_warn(ice->card->dev, + "cannot initialize pro ac97, skipped\n"); + else + return 0; + } + /* I2S mixer only */ + strcat(ice->card->mixername, "ICE1724 - multitrack"); + return 0; +} + +/* + * + */ + +static inline unsigned int eeprom_triple(struct snd_ice1712 *ice, int idx) +{ + return (unsigned int)ice->eeprom.data[idx] | \ + ((unsigned int)ice->eeprom.data[idx + 1] << 8) | \ + ((unsigned int)ice->eeprom.data[idx + 2] << 16); +} + +static void snd_vt1724_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ice1712 *ice = entry->private_data; + unsigned int idx; + + snd_iprintf(buffer, "%s\n\n", ice->card->longname); + snd_iprintf(buffer, "EEPROM:\n"); + + snd_iprintf(buffer, " Subvendor : 0x%x\n", ice->eeprom.subvendor); + snd_iprintf(buffer, " Size : %i bytes\n", ice->eeprom.size); + snd_iprintf(buffer, " Version : %i\n", ice->eeprom.version); + snd_iprintf(buffer, " System Config : 0x%x\n", + ice->eeprom.data[ICE_EEP2_SYSCONF]); + snd_iprintf(buffer, " ACLink : 0x%x\n", + ice->eeprom.data[ICE_EEP2_ACLINK]); + snd_iprintf(buffer, " I2S : 0x%x\n", + ice->eeprom.data[ICE_EEP2_I2S]); + snd_iprintf(buffer, " S/PDIF : 0x%x\n", + ice->eeprom.data[ICE_EEP2_SPDIF]); + snd_iprintf(buffer, " GPIO direction : 0x%x\n", + ice->eeprom.gpiodir); + snd_iprintf(buffer, " GPIO mask : 0x%x\n", + ice->eeprom.gpiomask); + snd_iprintf(buffer, " GPIO state : 0x%x\n", + ice->eeprom.gpiostate); + for (idx = 0x12; idx < ice->eeprom.size; idx++) + snd_iprintf(buffer, " Extra #%02i : 0x%x\n", + idx, ice->eeprom.data[idx]); + + snd_iprintf(buffer, "\nRegisters:\n"); + + snd_iprintf(buffer, " PSDOUT03 : 0x%08x\n", + (unsigned)inl(ICEMT1724(ice, ROUTE_PLAYBACK))); + for (idx = 0x0; idx < 0x20 ; idx++) + snd_iprintf(buffer, " CCS%02x : 0x%02x\n", + idx, inb(ice->port+idx)); + for (idx = 0x0; idx < 0x30 ; idx++) + snd_iprintf(buffer, " MT%02x : 0x%02x\n", + idx, inb(ice->profi_port+idx)); +} + +static void snd_vt1724_proc_init(struct snd_ice1712 *ice) +{ + snd_card_ro_proc_new(ice->card, "ice1724", ice, snd_vt1724_proc_read); +} + +/* + * + */ + +static int snd_vt1724_eeprom_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = sizeof(struct snd_ice1712_eeprom); + return 0; +} + +static int snd_vt1724_eeprom_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + memcpy(ucontrol->value.bytes.data, &ice->eeprom, sizeof(ice->eeprom)); + return 0; +} + +static const struct snd_kcontrol_new snd_vt1724_eeprom = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "ICE1724 EEPROM", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_vt1724_eeprom_info, + .get = snd_vt1724_eeprom_get +}; + +/* + */ +static int snd_vt1724_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static unsigned int encode_spdif_bits(struct snd_aes_iec958 *diga) +{ + unsigned int val, rbits; + + val = diga->status[0] & 0x03; /* professional, non-audio */ + if (val & 0x01) { + /* professional */ + if ((diga->status[0] & IEC958_AES0_PRO_EMPHASIS) == + IEC958_AES0_PRO_EMPHASIS_5015) + val |= 1U << 3; + rbits = (diga->status[4] >> 3) & 0x0f; + if (rbits) { + switch (rbits) { + case 2: val |= 5 << 12; break; /* 96k */ + case 3: val |= 6 << 12; break; /* 192k */ + case 10: val |= 4 << 12; break; /* 88.2k */ + case 11: val |= 7 << 12; break; /* 176.4k */ + } + } else { + switch (diga->status[0] & IEC958_AES0_PRO_FS) { + case IEC958_AES0_PRO_FS_44100: + break; + case IEC958_AES0_PRO_FS_32000: + val |= 3U << 12; + break; + default: + val |= 2U << 12; + break; + } + } + } else { + /* consumer */ + val |= diga->status[1] & 0x04; /* copyright */ + if ((diga->status[0] & IEC958_AES0_CON_EMPHASIS) == + IEC958_AES0_CON_EMPHASIS_5015) + val |= 1U << 3; + val |= (unsigned int)(diga->status[1] & 0x3f) << 4; /* category */ + val |= (unsigned int)(diga->status[3] & IEC958_AES3_CON_FS) << 12; /* fs */ + } + return val; +} + +static void decode_spdif_bits(struct snd_aes_iec958 *diga, unsigned int val) +{ + memset(diga->status, 0, sizeof(diga->status)); + diga->status[0] = val & 0x03; /* professional, non-audio */ + if (val & 0x01) { + /* professional */ + if (val & (1U << 3)) + diga->status[0] |= IEC958_AES0_PRO_EMPHASIS_5015; + switch ((val >> 12) & 0x7) { + case 0: + break; + case 2: + diga->status[0] |= IEC958_AES0_PRO_FS_32000; + break; + default: + diga->status[0] |= IEC958_AES0_PRO_FS_48000; + break; + } + } else { + /* consumer */ + diga->status[0] |= val & (1U << 2); /* copyright */ + if (val & (1U << 3)) + diga->status[0] |= IEC958_AES0_CON_EMPHASIS_5015; + diga->status[1] |= (val >> 4) & 0x3f; /* category */ + diga->status[3] |= (val >> 12) & 0x07; /* fs */ + } +} + +static int snd_vt1724_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val; + val = inw(ICEMT1724(ice, SPDIF_CTRL)); + decode_spdif_bits(&ucontrol->value.iec958, val); + return 0; +} + +static int snd_vt1724_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val, old; + + val = encode_spdif_bits(&ucontrol->value.iec958); + guard(spinlock_irq)(&ice->reg_lock); + old = inw(ICEMT1724(ice, SPDIF_CTRL)); + if (val != old) + update_spdif_bits(ice, val); + return val != old; +} + +static const struct snd_kcontrol_new snd_vt1724_spdif_default = +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = snd_vt1724_spdif_info, + .get = snd_vt1724_spdif_default_get, + .put = snd_vt1724_spdif_default_put +}; + +static int snd_vt1724_spdif_maskc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS; + ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL | + IEC958_AES1_CON_CATEGORY; + ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS; + return 0; +} + +static int snd_vt1724_spdif_maskp_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_PRO_FS | + IEC958_AES0_PRO_EMPHASIS; + return 0; +} + +static const struct snd_kcontrol_new snd_vt1724_spdif_maskc = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .info = snd_vt1724_spdif_info, + .get = snd_vt1724_spdif_maskc_get, +}; + +static const struct snd_kcontrol_new snd_vt1724_spdif_maskp = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK), + .info = snd_vt1724_spdif_info, + .get = snd_vt1724_spdif_maskp_get, +}; + +#define snd_vt1724_spdif_sw_info snd_ctl_boolean_mono_info + +static int snd_vt1724_spdif_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = inb(ICEREG1724(ice, SPDIF_CFG)) & + VT1724_CFG_SPDIF_OUT_EN ? 1 : 0; + return 0; +} + +static int snd_vt1724_spdif_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char old, val; + + guard(spinlock_irq)(&ice->reg_lock); + old = val = inb(ICEREG1724(ice, SPDIF_CFG)); + val &= ~VT1724_CFG_SPDIF_OUT_EN; + if (ucontrol->value.integer.value[0]) + val |= VT1724_CFG_SPDIF_OUT_EN; + if (old != val) + outb(val, ICEREG1724(ice, SPDIF_CFG)); + return old != val; +} + +static const struct snd_kcontrol_new snd_vt1724_spdif_switch = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* FIXME: the following conflict with IEC958 Playback Route */ + /* .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), */ + .name = SNDRV_CTL_NAME_IEC958("Output ", NONE, SWITCH), + .info = snd_vt1724_spdif_sw_info, + .get = snd_vt1724_spdif_sw_get, + .put = snd_vt1724_spdif_sw_put +}; + + +#if 0 /* NOT USED YET */ +/* + * GPIO access from extern + */ + +#define snd_vt1724_gpio_info snd_ctl_boolean_mono_info + +int snd_vt1724_gpio_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value & (1<<24)) ? 1 : 0; + + snd_ice1712_save_gpio_status(ice); + ucontrol->value.integer.value[0] = + (snd_ice1712_gpio_read(ice) & (1 << shift) ? 1 : 0) ^ invert; + snd_ice1712_restore_gpio_status(ice); + return 0; +} + +int snd_ice1712_gpio_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value & (1<<24)) ? mask : 0; + unsigned int val, nval; + + if (kcontrol->private_value & (1 << 31)) + return -EPERM; + nval = (ucontrol->value.integer.value[0] ? (1 << shift) : 0) ^ invert; + snd_ice1712_save_gpio_status(ice); + val = snd_ice1712_gpio_read(ice); + nval |= val & ~(1 << shift); + if (val != nval) + snd_ice1712_gpio_write(ice, nval); + snd_ice1712_restore_gpio_status(ice); + return val != nval; +} +#endif /* NOT USED YET */ + +/* + * rate + */ +static int snd_vt1724_pro_internal_clock_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int hw_rates_count = ice->hw_rates->count; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + + /* internal clocks */ + uinfo->value.enumerated.items = hw_rates_count; + /* external clocks */ + if (ice->force_rdma1 || + (ice->eeprom.data[ICE_EEP2_SPDIF] & VT1724_CFG_SPDIF_IN)) + uinfo->value.enumerated.items += ice->ext_clock_count; + /* upper limit - keep at top */ + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1; + if (uinfo->value.enumerated.item >= hw_rates_count) + /* ext_clock items */ + strscpy(uinfo->value.enumerated.name, + ice->ext_clock_names[ + uinfo->value.enumerated.item - hw_rates_count]); + else + /* int clock items */ + sprintf(uinfo->value.enumerated.name, "%d", + ice->hw_rates->list[uinfo->value.enumerated.item]); + return 0; +} + +static int snd_vt1724_pro_internal_clock_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int i, rate; + + guard(spinlock_irq)(&ice->reg_lock); + if (ice->is_spdif_master(ice)) { + ucontrol->value.enumerated.item[0] = ice->hw_rates->count + + ice->get_spdif_master_type(ice); + } else { + rate = ice->get_rate(ice); + ucontrol->value.enumerated.item[0] = 0; + for (i = 0; i < ice->hw_rates->count; i++) { + if (ice->hw_rates->list[i] == rate) { + ucontrol->value.enumerated.item[0] = i; + break; + } + } + } + return 0; +} + +static int stdclock_get_spdif_master_type(struct snd_ice1712 *ice) +{ + /* standard external clock - only single type - SPDIF IN */ + return 0; +} + +/* setting clock to external - SPDIF */ +static int stdclock_set_spdif_clock(struct snd_ice1712 *ice, int type) +{ + unsigned char oval; + unsigned char i2s_oval; + oval = inb(ICEMT1724(ice, RATE)); + outb(oval | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE)); + /* setting 256fs */ + i2s_oval = inb(ICEMT1724(ice, I2S_FORMAT)); + outb(i2s_oval & ~VT1724_MT_I2S_MCLK_128X, ICEMT1724(ice, I2S_FORMAT)); + return 0; +} + + +static int snd_vt1724_pro_internal_clock_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int old_rate, new_rate; + unsigned int item = ucontrol->value.enumerated.item[0]; + unsigned int first_ext_clock = ice->hw_rates->count; + bool set_pro_rate = false; + + if (item > first_ext_clock + ice->ext_clock_count - 1) + return -EINVAL; + + /* if rate = 0 => external clock */ + scoped_guard(spinlock_irq, &ice->reg_lock) { + if (ice->is_spdif_master(ice)) + old_rate = 0; + else + old_rate = ice->get_rate(ice); + if (item >= first_ext_clock) { + /* switching to external clock */ + ice->set_spdif_clock(ice, item - first_ext_clock); + new_rate = 0; + } else { + /* internal on-card clock */ + new_rate = ice->hw_rates->list[item]; + ice->pro_rate_default = new_rate; + set_pro_rate = true; + } + } + + if (set_pro_rate) + snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 1); + + /* the first switch to the ext. clock mode? */ + if (old_rate != new_rate && !new_rate) { + /* notify akm chips as well */ + unsigned int i; + if (ice->gpio.set_pro_rate) + ice->gpio.set_pro_rate(ice, 0); + for (i = 0; i < ice->akm_codecs; i++) { + if (ice->akm[i].ops.set_rate_val) + ice->akm[i].ops.set_rate_val(&ice->akm[i], 0); + } + } + return old_rate != new_rate; +} + +static const struct snd_kcontrol_new snd_vt1724_pro_internal_clock = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Multi Track Internal Clock", + .info = snd_vt1724_pro_internal_clock_info, + .get = snd_vt1724_pro_internal_clock_get, + .put = snd_vt1724_pro_internal_clock_put +}; + +#define snd_vt1724_pro_rate_locking_info snd_ctl_boolean_mono_info + +static int snd_vt1724_pro_rate_locking_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = PRO_RATE_LOCKED; + return 0; +} + +static int snd_vt1724_pro_rate_locking_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int change = 0, nval; + + nval = ucontrol->value.integer.value[0] ? 1 : 0; + guard(spinlock_irq)(&ice->reg_lock); + change = PRO_RATE_LOCKED != nval; + PRO_RATE_LOCKED = nval; + return change; +} + +static const struct snd_kcontrol_new snd_vt1724_pro_rate_locking = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Multi Track Rate Locking", + .info = snd_vt1724_pro_rate_locking_info, + .get = snd_vt1724_pro_rate_locking_get, + .put = snd_vt1724_pro_rate_locking_put +}; + +#define snd_vt1724_pro_rate_reset_info snd_ctl_boolean_mono_info + +static int snd_vt1724_pro_rate_reset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = PRO_RATE_RESET ? 1 : 0; + return 0; +} + +static int snd_vt1724_pro_rate_reset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int change = 0, nval; + + nval = ucontrol->value.integer.value[0] ? 1 : 0; + guard(spinlock_irq)(&ice->reg_lock); + change = PRO_RATE_RESET != nval; + PRO_RATE_RESET = nval; + return change; +} + +static const struct snd_kcontrol_new snd_vt1724_pro_rate_reset = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Multi Track Rate Reset", + .info = snd_vt1724_pro_rate_reset_info, + .get = snd_vt1724_pro_rate_reset_get, + .put = snd_vt1724_pro_rate_reset_put +}; + + +/* + * routing + */ +static int snd_vt1724_pro_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "PCM Out", /* 0 */ + "H/W In 0", "H/W In 1", /* 1-2 */ + "IEC958 In L", "IEC958 In R", /* 3-4 */ + }; + + return snd_ctl_enum_info(uinfo, 1, 5, texts); +} + +static inline int analog_route_shift(int idx) +{ + return (idx % 2) * 12 + ((idx / 2) * 3) + 8; +} + +static inline int digital_route_shift(int idx) +{ + return idx * 3; +} + +int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift) +{ + unsigned long val; + unsigned char eitem; + static const unsigned char xlate[8] = { + 0, 255, 1, 2, 255, 255, 3, 4, + }; + + val = inl(ICEMT1724(ice, ROUTE_PLAYBACK)); + val >>= shift; + val &= 7; /* we now have 3 bits per output */ + eitem = xlate[val]; + if (eitem == 255) { + snd_BUG(); + return 0; + } + return eitem; +} + +int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val, + int shift) +{ + unsigned int old_val, nval; + int change; + static const unsigned char xroute[8] = { + 0, /* PCM */ + 2, /* PSDIN0 Left */ + 3, /* PSDIN0 Right */ + 6, /* SPDIN Left */ + 7, /* SPDIN Right */ + }; + + nval = xroute[val % 5]; + val = old_val = inl(ICEMT1724(ice, ROUTE_PLAYBACK)); + val &= ~(0x07 << shift); + val |= nval << shift; + change = val != old_val; + if (change) + outl(val, ICEMT1724(ice, ROUTE_PLAYBACK)); + return change; +} + +static int snd_vt1724_pro_route_analog_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + ucontrol->value.enumerated.item[0] = + snd_ice1724_get_route_val(ice, analog_route_shift(idx)); + return 0; +} + +static int snd_vt1724_pro_route_analog_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + return snd_ice1724_put_route_val(ice, + ucontrol->value.enumerated.item[0], + analog_route_shift(idx)); +} + +static int snd_vt1724_pro_route_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + ucontrol->value.enumerated.item[0] = + snd_ice1724_get_route_val(ice, digital_route_shift(idx)); + return 0; +} + +static int snd_vt1724_pro_route_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + return snd_ice1724_put_route_val(ice, + ucontrol->value.enumerated.item[0], + digital_route_shift(idx)); +} + +static const struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "H/W Playback Route", + .info = snd_vt1724_pro_route_info, + .get = snd_vt1724_pro_route_analog_get, + .put = snd_vt1724_pro_route_analog_put, +}; + +static const struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route", + .info = snd_vt1724_pro_route_info, + .get = snd_vt1724_pro_route_spdif_get, + .put = snd_vt1724_pro_route_spdif_put, + .count = 2, +}; + + +static int snd_vt1724_pro_peak_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 22; /* FIXME: for compatibility with ice1712... */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + return 0; +} + +static int snd_vt1724_pro_peak_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx; + + guard(spinlock_irq)(&ice->reg_lock); + for (idx = 0; idx < 22; idx++) { + outb(idx, ICEMT1724(ice, MONITOR_PEAKINDEX)); + ucontrol->value.integer.value[idx] = + inb(ICEMT1724(ice, MONITOR_PEAKDATA)); + } + return 0; +} + +static const struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Multi Track Peak", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_vt1724_pro_peak_info, + .get = snd_vt1724_pro_peak_get +}; + +/* + ooAoo cards with no controls +*/ +static const unsigned char ooaoo_sq210_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x4c, /* 49MHz crystal, no mpu401, no ADC, + 1xDACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0x78, /* no volume, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc1, /* out-en, out-int, out-ext */ + [ICE_EEP2_GPIO_DIR] = 0x00, /* no GPIOs are used */ + [ICE_EEP2_GPIO_DIR1] = 0x00, + [ICE_EEP2_GPIO_DIR2] = 0x00, + [ICE_EEP2_GPIO_MASK] = 0xff, + [ICE_EEP2_GPIO_MASK1] = 0xff, + [ICE_EEP2_GPIO_MASK2] = 0xff, + + [ICE_EEP2_GPIO_STATE] = 0x00, /* inputs */ + [ICE_EEP2_GPIO_STATE1] = 0x00, /* all 1, but GPIO_CPLD_RW + and GPIO15 always zero */ + [ICE_EEP2_GPIO_STATE2] = 0x00, /* inputs */ +}; + + +static const struct snd_ice1712_card_info snd_vt1724_ooaoo_cards[] = { + { + .name = "ooAoo SQ210a", + .model = "sq210a", + .eeprom_size = sizeof(ooaoo_sq210_eeprom), + .eeprom_data = ooaoo_sq210_eeprom, + }, + { } /* terminator */ +}; + +static const struct snd_ice1712_card_info *card_tables[] = { + snd_vt1724_revo_cards, + snd_vt1724_amp_cards, + snd_vt1724_aureon_cards, + snd_vt1720_mobo_cards, + snd_vt1720_pontis_cards, + snd_vt1724_prodigy_hifi_cards, + snd_vt1724_prodigy192_cards, + snd_vt1724_juli_cards, + snd_vt1724_maya44_cards, + snd_vt1724_phase_cards, + snd_vt1724_wtm_cards, + snd_vt1724_se_cards, + snd_vt1724_qtet_cards, + snd_vt1724_ooaoo_cards, + snd_vt1724_psc724_cards, + NULL, +}; + + +/* + */ + +static void wait_i2c_busy(struct snd_ice1712 *ice) +{ + int t = 0x10000; + while ((inb(ICEREG1724(ice, I2C_CTRL)) & VT1724_I2C_BUSY) && t--) + ; + if (t == -1) + dev_err(ice->card->dev, "i2c busy timeout\n"); +} + +unsigned char snd_vt1724_read_i2c(struct snd_ice1712 *ice, + unsigned char dev, unsigned char addr) +{ + unsigned char val; + + guard(mutex)(&ice->i2c_mutex); + wait_i2c_busy(ice); + outb(addr, ICEREG1724(ice, I2C_BYTE_ADDR)); + outb(dev & ~VT1724_I2C_WRITE, ICEREG1724(ice, I2C_DEV_ADDR)); + wait_i2c_busy(ice); + val = inb(ICEREG1724(ice, I2C_DATA)); + /* + dev_dbg(ice->card->dev, "i2c_read: [0x%x,0x%x] = 0x%x\n", dev, addr, val); + */ + return val; +} + +void snd_vt1724_write_i2c(struct snd_ice1712 *ice, + unsigned char dev, unsigned char addr, unsigned char data) +{ + guard(mutex)(&ice->i2c_mutex); + wait_i2c_busy(ice); + /* + dev_dbg(ice->card->dev, "i2c_write: [0x%x,0x%x] = 0x%x\n", dev, addr, data); + */ + outb(addr, ICEREG1724(ice, I2C_BYTE_ADDR)); + outb(data, ICEREG1724(ice, I2C_DATA)); + outb(dev | VT1724_I2C_WRITE, ICEREG1724(ice, I2C_DEV_ADDR)); + wait_i2c_busy(ice); +} + +static int snd_vt1724_read_eeprom(struct snd_ice1712 *ice, + const char *modelname) +{ + const int dev = 0xa0; /* EEPROM device address */ + unsigned int i, size; + const struct snd_ice1712_card_info * const *tbl, *c; + + if (!modelname || !*modelname) { + ice->eeprom.subvendor = 0; + if ((inb(ICEREG1724(ice, I2C_CTRL)) & VT1724_I2C_EEPROM) != 0) + ice->eeprom.subvendor = + (snd_vt1724_read_i2c(ice, dev, 0x00) << 0) | + (snd_vt1724_read_i2c(ice, dev, 0x01) << 8) | + (snd_vt1724_read_i2c(ice, dev, 0x02) << 16) | + (snd_vt1724_read_i2c(ice, dev, 0x03) << 24); + if (ice->eeprom.subvendor == 0 || + ice->eeprom.subvendor == (unsigned int)-1) { + /* invalid subvendor from EEPROM, try the PCI + * subststem ID instead + */ + u16 vendor, device; + pci_read_config_word(ice->pci, PCI_SUBSYSTEM_VENDOR_ID, + &vendor); + pci_read_config_word(ice->pci, PCI_SUBSYSTEM_ID, &device); + ice->eeprom.subvendor = + ((unsigned int)swab16(vendor) << 16) | swab16(device); + if (ice->eeprom.subvendor == 0 || + ice->eeprom.subvendor == (unsigned int)-1) { + dev_err(ice->card->dev, + "No valid ID is found\n"); + return -ENXIO; + } + } + } + for (tbl = card_tables; *tbl; tbl++) { + for (c = *tbl; c->name; c++) { + if (modelname && c->model && + !strcmp(modelname, c->model)) { + dev_info(ice->card->dev, + "Using board model %s\n", + c->name); + ice->eeprom.subvendor = c->subvendor; + } else if (c->subvendor != ice->eeprom.subvendor) + continue; + ice->card_info = c; + if (!c->eeprom_size || !c->eeprom_data) + goto found; + /* if the EEPROM is given by the driver, use it */ + dev_dbg(ice->card->dev, "using the defined eeprom..\n"); + ice->eeprom.version = 2; + ice->eeprom.size = c->eeprom_size + 6; + memcpy(ice->eeprom.data, c->eeprom_data, c->eeprom_size); + goto read_skipped; + } + } + dev_warn(ice->card->dev, "No matching model found for ID 0x%x\n", + ice->eeprom.subvendor); +#ifdef CONFIG_PM_SLEEP + /* assume AC97-only card which can suspend without additional code */ + ice->pm_suspend_enabled = 1; +#endif + + found: + ice->eeprom.size = snd_vt1724_read_i2c(ice, dev, 0x04); + if (ice->eeprom.size < 6) + ice->eeprom.size = 32; + else if (ice->eeprom.size > 32) { + dev_err(ice->card->dev, "Invalid EEPROM (size = %i)\n", + ice->eeprom.size); + return -EIO; + } + ice->eeprom.version = snd_vt1724_read_i2c(ice, dev, 0x05); + if (ice->eeprom.version != 1 && ice->eeprom.version != 2) + dev_warn(ice->card->dev, "Invalid EEPROM version %i\n", + ice->eeprom.version); + size = ice->eeprom.size - 6; + for (i = 0; i < size; i++) + ice->eeprom.data[i] = snd_vt1724_read_i2c(ice, dev, i + 6); + + read_skipped: + ice->eeprom.gpiomask = eeprom_triple(ice, ICE_EEP2_GPIO_MASK); + ice->eeprom.gpiostate = eeprom_triple(ice, ICE_EEP2_GPIO_STATE); + ice->eeprom.gpiodir = eeprom_triple(ice, ICE_EEP2_GPIO_DIR); + + return 0; +} + + + +static void snd_vt1724_chip_reset(struct snd_ice1712 *ice) +{ + outb(VT1724_RESET , ICEREG1724(ice, CONTROL)); + inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */ + msleep(10); + outb(0, ICEREG1724(ice, CONTROL)); + inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */ + msleep(10); +} + +static int snd_vt1724_chip_init(struct snd_ice1712 *ice) +{ + outb(ice->eeprom.data[ICE_EEP2_SYSCONF], ICEREG1724(ice, SYS_CFG)); + outb(ice->eeprom.data[ICE_EEP2_ACLINK], ICEREG1724(ice, AC97_CFG)); + outb(ice->eeprom.data[ICE_EEP2_I2S], ICEREG1724(ice, I2S_FEATURES)); + outb(ice->eeprom.data[ICE_EEP2_SPDIF], ICEREG1724(ice, SPDIF_CFG)); + + ice->gpio.write_mask = ice->eeprom.gpiomask; + ice->gpio.direction = ice->eeprom.gpiodir; + snd_vt1724_set_gpio_mask(ice, ice->eeprom.gpiomask); + snd_vt1724_set_gpio_dir(ice, ice->eeprom.gpiodir); + snd_vt1724_set_gpio_data(ice, ice->eeprom.gpiostate); + + outb(0, ICEREG1724(ice, POWERDOWN)); + + /* MPU_RX and TX irq masks are cleared later dynamically */ + outb(VT1724_IRQ_MPU_RX | VT1724_IRQ_MPU_TX , ICEREG1724(ice, IRQMASK)); + + /* don't handle FIFO overrun/underruns (just yet), + * since they cause machine lockups + */ + outb(VT1724_MULTI_FIFO_ERR, ICEMT1724(ice, DMA_INT_MASK)); + + return 0; +} + +static int snd_vt1724_spdif_build_controls(struct snd_ice1712 *ice) +{ + int err; + struct snd_kcontrol *kctl; + + if (snd_BUG_ON(!ice->pcm)) + return -EIO; + + if (!ice->own_routing) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&snd_vt1724_mixer_pro_spdif_route, ice)); + if (err < 0) + return err; + } + + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_spdif_switch, ice)); + if (err < 0) + return err; + + kctl = snd_ctl_new1(&snd_vt1724_spdif_default, ice); + if (!kctl) + return -ENOMEM; + kctl->id.device = ice->pcm->device; + err = snd_ctl_add(ice->card, kctl); + if (err < 0) + return err; + kctl = snd_ctl_new1(&snd_vt1724_spdif_maskc, ice); + if (!kctl) + return -ENOMEM; + kctl->id.device = ice->pcm->device; + err = snd_ctl_add(ice->card, kctl); + if (err < 0) + return err; + kctl = snd_ctl_new1(&snd_vt1724_spdif_maskp, ice); + if (!kctl) + return -ENOMEM; + kctl->id.device = ice->pcm->device; + err = snd_ctl_add(ice->card, kctl); + if (err < 0) + return err; +#if 0 /* use default only */ + kctl = snd_ctl_new1(&snd_vt1724_spdif_stream, ice); + kctl->id.device = ice->pcm->device; + err = snd_ctl_add(ice->card, kctl); + if (err < 0) + return err; + ice->spdif.stream_ctl = kctl; +#endif + return 0; +} + + +static int snd_vt1724_build_controls(struct snd_ice1712 *ice) +{ + int err; + + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_eeprom, ice)); + if (err < 0) + return err; + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_pro_internal_clock, ice)); + if (err < 0) + return err; + + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_pro_rate_locking, ice)); + if (err < 0) + return err; + err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_pro_rate_reset, ice)); + if (err < 0) + return err; + + if (!ice->own_routing && ice->num_total_dacs > 0) { + struct snd_kcontrol_new tmp = snd_vt1724_mixer_pro_analog_route; + tmp.count = ice->num_total_dacs; + if (ice->vt1720 && tmp.count > 2) + tmp.count = 2; + err = snd_ctl_add(ice->card, snd_ctl_new1(&tmp, ice)); + if (err < 0) + return err; + } + + return snd_ctl_add(ice->card, + snd_ctl_new1(&snd_vt1724_mixer_pro_peak, ice)); +} + +static void snd_vt1724_free(struct snd_card *card) +{ + struct snd_ice1712 *ice = card->private_data; + + /* mask all interrupts */ + outb(0xff, ICEMT1724(ice, DMA_INT_MASK)); + outb(0xff, ICEREG1724(ice, IRQMASK)); + + snd_ice1712_akm4xxx_free(ice); +} + +static int snd_vt1724_create(struct snd_card *card, + struct pci_dev *pci, + const char *modelname) +{ + struct snd_ice1712 *ice = card->private_data; + int err; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + + ice->vt1724 = 1; + spin_lock_init(&ice->reg_lock); + mutex_init(&ice->gpio_mutex); + mutex_init(&ice->open_mutex); + mutex_init(&ice->i2c_mutex); + ice->gpio.set_mask = snd_vt1724_set_gpio_mask; + ice->gpio.get_mask = snd_vt1724_get_gpio_mask; + ice->gpio.set_dir = snd_vt1724_set_gpio_dir; + ice->gpio.get_dir = snd_vt1724_get_gpio_dir; + ice->gpio.set_data = snd_vt1724_set_gpio_data; + ice->gpio.get_data = snd_vt1724_get_gpio_data; + ice->card = card; + ice->pci = pci; + ice->irq = -1; + pci_set_master(pci); + snd_vt1724_proc_init(ice); + + err = pcim_request_all_regions(pci, "ICE1724"); + if (err < 0) + return err; + ice->port = pci_resource_start(pci, 0); + ice->profi_port = pci_resource_start(pci, 1); + + if (devm_request_irq(&pci->dev, pci->irq, snd_vt1724_interrupt, + IRQF_SHARED, KBUILD_MODNAME, ice)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EIO; + } + + ice->irq = pci->irq; + card->sync_irq = ice->irq; + card->private_free = snd_vt1724_free; + + snd_vt1724_chip_reset(ice); + if (snd_vt1724_read_eeprom(ice, modelname) < 0) + return -EIO; + if (snd_vt1724_chip_init(ice) < 0) + return -EIO; + + return 0; +} + + +/* + * + * Registration + * + */ + +static int __snd_vt1724_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info *c; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*ice), &card); + if (err < 0) + return err; + ice = card->private_data; + + strscpy(card->driver, "ICE1724"); + strscpy(card->shortname, "ICEnsemble ICE1724"); + + err = snd_vt1724_create(card, pci, model[dev]); + if (err < 0) + return err; + + /* field init before calling chip_init */ + ice->ext_clock_count = 0; + + c = ice->card_info; + if (c) { + strscpy(card->shortname, c->name); + if (c->driver) /* specific driver? */ + strscpy(card->driver, c->driver); + if (c->chip_init) { + err = c->chip_init(ice); + if (err < 0) + return err; + } + } + + /* + * VT1724 has separate DMAs for the analog and the SPDIF streams while + * ICE1712 has only one for both (mixed up). + * + * Confusingly the analog PCM is named "professional" here because it + * was called so in ice1712 driver, and vt1724 driver is derived from + * ice1712 driver. + */ + ice->pro_rate_default = PRO_RATE_DEFAULT; + if (!ice->is_spdif_master) + ice->is_spdif_master = stdclock_is_spdif_master; + if (!ice->get_rate) + ice->get_rate = stdclock_get_rate; + if (!ice->set_rate) + ice->set_rate = stdclock_set_rate; + if (!ice->set_mclk) + ice->set_mclk = stdclock_set_mclk; + if (!ice->set_spdif_clock) + ice->set_spdif_clock = stdclock_set_spdif_clock; + if (!ice->get_spdif_master_type) + ice->get_spdif_master_type = stdclock_get_spdif_master_type; + if (!ice->ext_clock_names) + ice->ext_clock_names = ext_clock_names; + if (!ice->ext_clock_count) + ice->ext_clock_count = ARRAY_SIZE(ext_clock_names); + + if (!ice->hw_rates) + set_std_hw_rates(ice); + + err = snd_vt1724_pcm_profi(ice, pcm_dev++); + if (err < 0) + return err; + + err = snd_vt1724_pcm_spdif(ice, pcm_dev++); + if (err < 0) + return err; + + err = snd_vt1724_pcm_indep(ice, pcm_dev++); + if (err < 0) + return err; + + err = snd_vt1724_ac97_mixer(ice); + if (err < 0) + return err; + + err = snd_vt1724_build_controls(ice); + if (err < 0) + return err; + + if (ice->pcm && ice->has_spdif) { /* has SPDIF I/O */ + err = snd_vt1724_spdif_build_controls(ice); + if (err < 0) + return err; + } + + if (c && c->build_controls) { + err = c->build_controls(ice); + if (err < 0) + return err; + } + + if (!c || !c->no_mpu401) { + if (ice->eeprom.data[ICE_EEP2_SYSCONF] & VT1724_CFG_MPU401) { + struct snd_rawmidi *rmidi; + + err = snd_rawmidi_new(card, "MIDI", 0, 1, 1, &rmidi); + if (err < 0) + return err; + ice->rmidi[0] = rmidi; + rmidi->private_data = ice; + strscpy(rmidi->name, "ICE1724 MIDI"); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &vt1724_midi_output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &vt1724_midi_input_ops); + + /* set watermarks */ + outb(VT1724_MPU_RX_FIFO | 0x1, + ICEREG1724(ice, MPU_FIFO_WM)); + outb(0x1, ICEREG1724(ice, MPU_FIFO_WM)); + /* set UART mode */ + outb(VT1724_MPU_UART, ICEREG1724(ice, MPU_CTRL)); + } + } + + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, ice->port, ice->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_vt1724_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_vt1724_probe(pci, pci_id)); +} + +#ifdef CONFIG_PM_SLEEP +static int snd_vt1724_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ice1712 *ice = card->private_data; + + if (!ice->pm_suspend_enabled) + return 0; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + snd_ac97_suspend(ice->ac97); + + scoped_guard(spinlock_irq, &ice->reg_lock) { + ice->pm_saved_is_spdif_master = ice->is_spdif_master(ice); + ice->pm_saved_spdif_ctrl = inw(ICEMT1724(ice, SPDIF_CTRL)); + ice->pm_saved_spdif_cfg = inb(ICEREG1724(ice, SPDIF_CFG)); + ice->pm_saved_route = inl(ICEMT1724(ice, ROUTE_PLAYBACK)); + } + + if (ice->pm_suspend) + ice->pm_suspend(ice); + return 0; +} + +static int snd_vt1724_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ice1712 *ice = card->private_data; + + if (!ice->pm_suspend_enabled) + return 0; + + snd_vt1724_chip_reset(ice); + + if (snd_vt1724_chip_init(ice) < 0) { + snd_card_disconnect(card); + return -EIO; + } + + if (ice->pm_resume) + ice->pm_resume(ice); + + if (ice->pm_saved_is_spdif_master) { + /* switching to external clock via SPDIF */ + ice->set_spdif_clock(ice, 0); + } else { + /* internal on-card clock */ + int rate; + if (ice->cur_rate) + rate = ice->cur_rate; + else + rate = ice->pro_rate_default; + snd_vt1724_set_pro_rate(ice, rate, 1); + } + + update_spdif_bits(ice, ice->pm_saved_spdif_ctrl); + + outb(ice->pm_saved_spdif_cfg, ICEREG1724(ice, SPDIF_CFG)); + outl(ice->pm_saved_route, ICEMT1724(ice, ROUTE_PLAYBACK)); + + snd_ac97_resume(ice->ac97); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_vt1724_pm, snd_vt1724_suspend, snd_vt1724_resume); +#define SND_VT1724_PM_OPS &snd_vt1724_pm +#else +#define SND_VT1724_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct pci_driver vt1724_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_vt1724_ids, + .probe = snd_vt1724_probe, + .driver = { + .pm = SND_VT1724_PM_OPS, + }, +}; + +module_pci_driver(vt1724_driver); diff --git a/sound/pci/ice1712/juli.c b/sound/pci/ice1712/juli.c new file mode 100644 index 000000000..ab1c8ac07 --- /dev/null +++ b/sound/pci/ice1712/juli.c @@ -0,0 +1,662 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for ESI Juli@ cards + * + * Copyright (c) 2004 Jaroslav Kysela + * 2008 Pavel Hofman + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "juli.h" + +struct juli_spec { + struct ak4114 *ak4114; + unsigned int analog:1; +}; + +/* + * chip addresses on I2C bus + */ +#define AK4114_ADDR 0x20 /* S/PDIF receiver */ +#define AK4358_ADDR 0x22 /* DAC */ + +/* + * Juli does not use the standard ICE1724 clock scheme. Juli's ice1724 chip is + * supplied by external clock provided by Xilinx array and MK73-1 PLL frequency + * multiplier. Actual frequency is set by ice1724 GPIOs hooked to the Xilinx. + * + * The clock circuitry is supplied by the two ice1724 crystals. This + * arrangement allows to generate independent clock signal for AK4114's input + * rate detection circuit. As a result, Juli, unlike most other + * ice1724+ak4114-based cards, detects spdif input rate correctly. + * This fact is applied in the driver, allowing to modify PCM stream rate + * parameter according to the actual input rate. + * + * Juli uses the remaining three stereo-channels of its DAC to optionally + * monitor analog input, digital input, and digital output. The corresponding + * I2S signals are routed by Xilinx, controlled by GPIOs. + * + * The master mute is implemented using output muting transistors (GPIO) in + * combination with smuting the DAC. + * + * The card itself has no HW master volume control, implemented using the + * vmaster control. + * + * TODO: + * researching and fixing the input monitors + */ + +/* + * GPIO pins + */ +#define GPIO_FREQ_MASK (3<<0) +#define GPIO_FREQ_32KHZ (0<<0) +#define GPIO_FREQ_44KHZ (1<<0) +#define GPIO_FREQ_48KHZ (2<<0) +#define GPIO_MULTI_MASK (3<<2) +#define GPIO_MULTI_4X (0<<2) +#define GPIO_MULTI_2X (1<<2) +#define GPIO_MULTI_1X (2<<2) /* also external */ +#define GPIO_MULTI_HALF (3<<2) +#define GPIO_INTERNAL_CLOCK (1<<4) /* 0 = external, 1 = internal */ +#define GPIO_CLOCK_MASK (1<<4) +#define GPIO_ANALOG_PRESENT (1<<5) /* RO only: 0 = present */ +#define GPIO_RXMCLK_SEL (1<<7) /* must be 0 */ +#define GPIO_AK5385A_CKS0 (1<<8) +#define GPIO_AK5385A_DFS1 (1<<9) +#define GPIO_AK5385A_DFS0 (1<<10) +#define GPIO_DIGOUT_MONITOR (1<<11) /* 1 = active */ +#define GPIO_DIGIN_MONITOR (1<<12) /* 1 = active */ +#define GPIO_ANAIN_MONITOR (1<<13) /* 1 = active */ +#define GPIO_AK5385A_CKS1 (1<<14) /* must be 0 */ +#define GPIO_MUTE_CONTROL (1<<15) /* output mute, 1 = muted */ + +#define GPIO_RATE_MASK (GPIO_FREQ_MASK | GPIO_MULTI_MASK | \ + GPIO_CLOCK_MASK) +#define GPIO_AK5385A_MASK (GPIO_AK5385A_CKS0 | GPIO_AK5385A_DFS0 | \ + GPIO_AK5385A_DFS1 | GPIO_AK5385A_CKS1) + +#define JULI_PCM_RATE (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define GPIO_RATE_16000 (GPIO_FREQ_32KHZ | GPIO_MULTI_HALF | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_22050 (GPIO_FREQ_44KHZ | GPIO_MULTI_HALF | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_24000 (GPIO_FREQ_48KHZ | GPIO_MULTI_HALF | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_32000 (GPIO_FREQ_32KHZ | GPIO_MULTI_1X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_44100 (GPIO_FREQ_44KHZ | GPIO_MULTI_1X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_48000 (GPIO_FREQ_48KHZ | GPIO_MULTI_1X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_64000 (GPIO_FREQ_32KHZ | GPIO_MULTI_2X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_88200 (GPIO_FREQ_44KHZ | GPIO_MULTI_2X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_96000 (GPIO_FREQ_48KHZ | GPIO_MULTI_2X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_176400 (GPIO_FREQ_44KHZ | GPIO_MULTI_4X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_192000 (GPIO_FREQ_48KHZ | GPIO_MULTI_4X | \ + GPIO_INTERNAL_CLOCK) + +/* + * Initial setup of the conversion array GPIO <-> rate + */ +static const unsigned int juli_rates[] = { + 16000, 22050, 24000, 32000, + 44100, 48000, 64000, 88200, + 96000, 176400, 192000, +}; + +static const unsigned int gpio_vals[] = { + GPIO_RATE_16000, GPIO_RATE_22050, GPIO_RATE_24000, GPIO_RATE_32000, + GPIO_RATE_44100, GPIO_RATE_48000, GPIO_RATE_64000, GPIO_RATE_88200, + GPIO_RATE_96000, GPIO_RATE_176400, GPIO_RATE_192000, +}; + +static const struct snd_pcm_hw_constraint_list juli_rates_info = { + .count = ARRAY_SIZE(juli_rates), + .list = juli_rates, + .mask = 0, +}; + +static int get_gpio_val(int rate) +{ + int i; + for (i = 0; i < ARRAY_SIZE(juli_rates); i++) + if (juli_rates[i] == rate) + return gpio_vals[i]; + return 0; +} + +static void juli_ak4114_write(void *private_data, unsigned char reg, + unsigned char val) +{ + snd_vt1724_write_i2c((struct snd_ice1712 *)private_data, AK4114_ADDR, + reg, val); +} + +static unsigned char juli_ak4114_read(void *private_data, unsigned char reg) +{ + return snd_vt1724_read_i2c((struct snd_ice1712 *)private_data, + AK4114_ADDR, reg); +} + +/* + * If SPDIF capture and slaved to SPDIF-IN, setting runtime rate + * to the external rate + */ +static void juli_spdif_in_open(struct snd_ice1712 *ice, + struct snd_pcm_substream *substream) +{ + struct juli_spec *spec = ice->spec; + struct snd_pcm_runtime *runtime = substream->runtime; + int rate; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + !ice->is_spdif_master(ice)) + return; + rate = snd_ak4114_external_rate(spec->ak4114); + if (rate >= runtime->hw.rate_min && rate <= runtime->hw.rate_max) { + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } +} + +/* + * AK4358 section + */ + +static void juli_akm_lock(struct snd_akm4xxx *ak, int chip) +{ +} + +static void juli_akm_unlock(struct snd_akm4xxx *ak, int chip) +{ +} + +static void juli_akm_write(struct snd_akm4xxx *ak, int chip, + unsigned char addr, unsigned char data) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + + if (snd_BUG_ON(chip)) + return; + snd_vt1724_write_i2c(ice, AK4358_ADDR, addr, data); +} + +/* + * change the rate of envy24HT, AK4358, AK5385 + */ +static void juli_akm_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) +{ + unsigned char old, tmp, ak4358_dfs; + unsigned int ak5385_pins, old_gpio, new_gpio; + struct snd_ice1712 *ice = ak->private_data[0]; + struct juli_spec *spec = ice->spec; + + if (rate == 0) /* no hint - S/PDIF input is master or the new spdif + input rate undetected, simply return */ + return; + + /* adjust DFS on codecs */ + if (rate > 96000) { + ak4358_dfs = 2; + ak5385_pins = GPIO_AK5385A_DFS1 | GPIO_AK5385A_CKS0; + } else if (rate > 48000) { + ak4358_dfs = 1; + ak5385_pins = GPIO_AK5385A_DFS0; + } else { + ak4358_dfs = 0; + ak5385_pins = 0; + } + /* AK5385 first, since it requires cold reset affecting both codecs */ + old_gpio = ice->gpio.get_data(ice); + new_gpio = (old_gpio & ~GPIO_AK5385A_MASK) | ak5385_pins; + /* dev_dbg(ice->card->dev, "JULI - ak5385 set_rate_val: new gpio 0x%x\n", + new_gpio); */ + ice->gpio.set_data(ice, new_gpio); + + /* cold reset */ + old = inb(ICEMT1724(ice, AC97_CMD)); + outb(old | VT1724_AC97_COLD, ICEMT1724(ice, AC97_CMD)); + udelay(1); + outb(old & ~VT1724_AC97_COLD, ICEMT1724(ice, AC97_CMD)); + + /* AK4358 */ + /* set new value, reset DFS */ + tmp = snd_akm4xxx_get(ak, 0, 2); + snd_akm4xxx_reset(ak, 1); + tmp = snd_akm4xxx_get(ak, 0, 2); + tmp &= ~(0x03 << 4); + tmp |= ak4358_dfs << 4; + snd_akm4xxx_set(ak, 0, 2, tmp); + snd_akm4xxx_reset(ak, 0); + + /* reinit ak4114 */ + snd_ak4114_reinit(spec->ak4114); +} + +#define AK_DAC(xname, xch) { .name = xname, .num_channels = xch } +#define PCM_VOLUME "PCM Playback Volume" +#define MONITOR_AN_IN_VOLUME "Monitor Analog In Volume" +#define MONITOR_DIG_IN_VOLUME "Monitor Digital In Volume" +#define MONITOR_DIG_OUT_VOLUME "Monitor Digital Out Volume" + +static const struct snd_akm4xxx_dac_channel juli_dac[] = { + AK_DAC(PCM_VOLUME, 2), + AK_DAC(MONITOR_AN_IN_VOLUME, 2), + AK_DAC(MONITOR_DIG_OUT_VOLUME, 2), + AK_DAC(MONITOR_DIG_IN_VOLUME, 2), +}; + + +static const struct snd_akm4xxx akm_juli_dac = { + .type = SND_AK4358, + .num_dacs = 8, /* DAC1 - analog out + DAC2 - analog in monitor + DAC3 - digital out monitor + DAC4 - digital in monitor + */ + .ops = { + .lock = juli_akm_lock, + .unlock = juli_akm_unlock, + .write = juli_akm_write, + .set_rate_val = juli_akm_set_rate_val + }, + .dac_info = juli_dac, +}; + +#define juli_mute_info snd_ctl_boolean_mono_info + +static int juli_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val; + val = ice->gpio.get_data(ice) & (unsigned int) kcontrol->private_value; + if (kcontrol->private_value == GPIO_MUTE_CONTROL) + /* val 0 = signal on */ + ucontrol->value.integer.value[0] = (val) ? 0 : 1; + else + /* val 1 = signal on */ + ucontrol->value.integer.value[0] = (val) ? 1 : 0; + return 0; +} + +static int juli_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int old_gpio, new_gpio; + old_gpio = ice->gpio.get_data(ice); + if (ucontrol->value.integer.value[0]) { + /* unmute */ + if (kcontrol->private_value == GPIO_MUTE_CONTROL) { + /* 0 = signal on */ + new_gpio = old_gpio & ~GPIO_MUTE_CONTROL; + /* un-smuting DAC */ + snd_akm4xxx_write(ice->akm, 0, 0x01, 0x01); + } else + /* 1 = signal on */ + new_gpio = old_gpio | + (unsigned int) kcontrol->private_value; + } else { + /* mute */ + if (kcontrol->private_value == GPIO_MUTE_CONTROL) { + /* 1 = signal off */ + new_gpio = old_gpio | GPIO_MUTE_CONTROL; + /* smuting DAC */ + snd_akm4xxx_write(ice->akm, 0, 0x01, 0x03); + } else + /* 0 = signal off */ + new_gpio = old_gpio & + ~((unsigned int) kcontrol->private_value); + } + /* dev_dbg(ice->card->dev, + "JULI - mute/unmute: control_value: 0x%x, old_gpio: 0x%x, " + "new_gpio 0x%x\n", + (unsigned int)ucontrol->value.integer.value[0], old_gpio, + new_gpio); */ + if (old_gpio != new_gpio) { + ice->gpio.set_data(ice, new_gpio); + return 1; + } + /* no change */ + return 0; +} + +static const struct snd_kcontrol_new juli_mute_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = juli_mute_info, + .get = juli_mute_get, + .put = juli_mute_put, + .private_value = GPIO_MUTE_CONTROL, + }, + /* Although the following functionality respects the succint NDA'd + * documentation from the card manufacturer, and the same way of + * operation is coded in OSS Juli driver, only Digital Out monitor + * seems to work. Surprisingly, Analog input monitor outputs Digital + * output data. The two are independent, as enabling both doubles + * volume of the monitor sound. + * + * Checking traces on the board suggests the functionality described + * by the manufacturer is correct - I2S from ADC and AK4114 + * go to ICE as well as to Xilinx, I2S inputs of DAC2,3,4 (the monitor + * inputs) are fed from Xilinx. + * + * I even checked traces on board and coded a support in driver for + * an alternative possibility - the unused I2S ICE output channels + * switched to HW-IN/SPDIF-IN and providing the monitoring signal to + * the DAC - to no avail. The I2S outputs seem to be unconnected. + * + * The windows driver supports the monitoring correctly. + */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Analog In Switch", + .info = juli_mute_info, + .get = juli_mute_get, + .put = juli_mute_put, + .private_value = GPIO_ANAIN_MONITOR, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Digital Out Switch", + .info = juli_mute_info, + .get = juli_mute_get, + .put = juli_mute_put, + .private_value = GPIO_DIGOUT_MONITOR, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Digital In Switch", + .info = juli_mute_info, + .get = juli_mute_get, + .put = juli_mute_put, + .private_value = GPIO_DIGIN_MONITOR, + }, +}; + +static const char * const follower_vols[] = { + PCM_VOLUME, + MONITOR_AN_IN_VOLUME, + MONITOR_DIG_IN_VOLUME, + MONITOR_DIG_OUT_VOLUME, + NULL +}; + +static +DECLARE_TLV_DB_SCALE(juli_master_db_scale, -6350, 50, 1); + +static int juli_add_controls(struct snd_ice1712 *ice) +{ + struct juli_spec *spec = ice->spec; + int err; + unsigned int i; + struct snd_kcontrol *vmaster; + + err = snd_ice1712_akm4xxx_build_controls(ice); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(juli_mute_controls); i++) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&juli_mute_controls[i], ice)); + if (err < 0) + return err; + } + /* Create virtual master control */ + vmaster = snd_ctl_make_virtual_master("Master Playback Volume", + juli_master_db_scale); + if (!vmaster) + return -ENOMEM; + err = snd_ctl_add(ice->card, vmaster); + if (err < 0) + return err; + err = snd_ctl_add_followers(ice->card, vmaster, follower_vols); + if (err < 0) + return err; + + /* only capture SPDIF over AK4114 */ + return snd_ak4114_build(spec->ak4114, NULL, + ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream); +} + +/* + * suspend/resume + * */ + +#ifdef CONFIG_PM_SLEEP +static int juli_resume(struct snd_ice1712 *ice) +{ + struct snd_akm4xxx *ak = ice->akm; + struct juli_spec *spec = ice->spec; + /* akm4358 un-reset, un-mute */ + snd_akm4xxx_reset(ak, 0); + /* reinit ak4114 */ + snd_ak4114_resume(spec->ak4114); + return 0; +} + +static int juli_suspend(struct snd_ice1712 *ice) +{ + struct snd_akm4xxx *ak = ice->akm; + struct juli_spec *spec = ice->spec; + /* akm4358 reset and soft-mute */ + snd_akm4xxx_reset(ak, 1); + snd_ak4114_suspend(spec->ak4114); + return 0; +} +#endif + +/* + * initialize the chip + */ + +static inline int juli_is_spdif_master(struct snd_ice1712 *ice) +{ + return (ice->gpio.get_data(ice) & GPIO_INTERNAL_CLOCK) ? 0 : 1; +} + +static unsigned int juli_get_rate(struct snd_ice1712 *ice) +{ + int i; + unsigned char result; + + result = ice->gpio.get_data(ice) & GPIO_RATE_MASK; + for (i = 0; i < ARRAY_SIZE(gpio_vals); i++) + if (gpio_vals[i] == result) + return juli_rates[i]; + return 0; +} + +/* setting new rate */ +static void juli_set_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + unsigned int old, new; + unsigned char val; + + old = ice->gpio.get_data(ice); + new = (old & ~GPIO_RATE_MASK) | get_gpio_val(rate); + /* dev_dbg(ice->card->dev, "JULI - set_rate: old %x, new %x\n", + old & GPIO_RATE_MASK, + new & GPIO_RATE_MASK); */ + + ice->gpio.set_data(ice, new); + /* switching to external clock - supplied by external circuits */ + val = inb(ICEMT1724(ice, RATE)); + outb(val | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE)); +} + +static inline unsigned char juli_set_mclk(struct snd_ice1712 *ice, + unsigned int rate) +{ + /* no change in master clock */ + return 0; +} + +/* setting clock to external - SPDIF */ +static int juli_set_spdif_clock(struct snd_ice1712 *ice, int type) +{ + unsigned int old; + old = ice->gpio.get_data(ice); + /* external clock (= 0), multiply 1x, 48kHz */ + ice->gpio.set_data(ice, (old & ~GPIO_RATE_MASK) | GPIO_MULTI_1X | + GPIO_FREQ_48KHZ); + return 0; +} + +/* Called when ak4114 detects change in the input SPDIF stream */ +static void juli_ak4114_change(struct ak4114 *ak4114, unsigned char c0, + unsigned char c1) +{ + struct snd_ice1712 *ice = ak4114->change_callback_private; + int rate; + if (ice->is_spdif_master(ice) && c1) { + /* only for SPDIF master mode, rate was changed */ + rate = snd_ak4114_external_rate(ak4114); + /* dev_dbg(ice->card->dev, "ak4114 - input rate changed to %d\n", + rate); */ + juli_akm_set_rate_val(ice->akm, rate); + } +} + +static int juli_init(struct snd_ice1712 *ice) +{ + static const unsigned char ak4114_init_vals[] = { + /* AK4117_REG_PWRDN */ AK4114_RST | AK4114_PWN | + AK4114_OCKS0 | AK4114_OCKS1, + /* AK4114_REQ_FORMAT */ AK4114_DIF_I24I2S, + /* AK4114_REG_IO0 */ AK4114_TX1E, + /* AK4114_REG_IO1 */ AK4114_EFH_1024 | AK4114_DIT | + AK4114_IPS(1), + /* AK4114_REG_INT0_MASK */ 0, + /* AK4114_REG_INT1_MASK */ 0 + }; + static const unsigned char ak4114_init_txcsb[] = { + 0x41, 0x02, 0x2c, 0x00, 0x00 + }; + int err; + struct juli_spec *spec; + struct snd_akm4xxx *ak; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + err = snd_ak4114_create(ice->card, + juli_ak4114_read, + juli_ak4114_write, + ak4114_init_vals, ak4114_init_txcsb, + ice, &spec->ak4114); + if (err < 0) + return err; + /* callback for codecs rate setting */ + spec->ak4114->change_callback = juli_ak4114_change; + spec->ak4114->change_callback_private = ice; + /* AK4114 in Juli can detect external rate correctly */ + spec->ak4114->check_flags = 0; + +#if 0 +/* + * it seems that the analog doughter board detection does not work reliably, so + * force the analog flag; it should be very rare (if ever) to come at Juli@ + * used without the analog daughter board + */ + spec->analog = (ice->gpio.get_data(ice) & GPIO_ANALOG_PRESENT) ? 0 : 1; +#else + spec->analog = 1; +#endif + + if (spec->analog) { + dev_info(ice->card->dev, "juli@: analog I/O detected\n"); + ice->num_total_dacs = 2; + ice->num_total_adcs = 2; + + ice->akm = kzalloc_obj(struct snd_akm4xxx); + ak = ice->akm; + if (!ak) + return -ENOMEM; + ice->akm_codecs = 1; + err = snd_ice1712_akm4xxx_init(ak, &akm_juli_dac, NULL, ice); + if (err < 0) + return err; + } + + /* juli is clocked by Xilinx array */ + ice->hw_rates = &juli_rates_info; + ice->is_spdif_master = juli_is_spdif_master; + ice->get_rate = juli_get_rate; + ice->set_rate = juli_set_rate; + ice->set_mclk = juli_set_mclk; + ice->set_spdif_clock = juli_set_spdif_clock; + + ice->spdif.ops.open = juli_spdif_in_open; + +#ifdef CONFIG_PM_SLEEP + ice->pm_resume = juli_resume; + ice->pm_suspend = juli_suspend; + ice->pm_suspend_enabled = 1; +#endif + + return 0; +} + + +/* + * Juli@ boards don't provide the EEPROM data except for the vendor IDs. + * hence the driver needs to sets up it properly. + */ + +static const unsigned char juli_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x2b, /* clock 512, mpu401, 1xADC, 1xDACs, + SPDIF in */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xf8, /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + [ICE_EEP2_GPIO_DIR] = 0x9f, /* 5, 6:inputs; 7, 4-0 outputs*/ + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0x7f, + [ICE_EEP2_GPIO_MASK] = 0x60, /* 5, 6: locked; 7, 4-0 writable */ + [ICE_EEP2_GPIO_MASK1] = 0x00, /* 0-7 writable */ + [ICE_EEP2_GPIO_MASK2] = 0x7f, + [ICE_EEP2_GPIO_STATE] = GPIO_FREQ_48KHZ | GPIO_MULTI_1X | + GPIO_INTERNAL_CLOCK, /* internal clock, multiple 1x, 48kHz*/ + [ICE_EEP2_GPIO_STATE1] = 0x00, /* unmuted */ + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_juli_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_JULI, + .name = "ESI Juli@", + .model = "juli", + .chip_init = juli_init, + .build_controls = juli_add_controls, + .eeprom_size = sizeof(juli_eeprom), + .eeprom_data = juli_eeprom, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/juli.h b/sound/pci/ice1712/juli.h new file mode 100644 index 000000000..9c22d4e73 --- /dev/null +++ b/sound/pci/ice1712/juli.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_JULI_H +#define __SOUND_JULI_H + +#define JULI_DEVICE_DESC "{ESI,Juli@}," + +#define VT1724_SUBDEVICE_JULI 0x31305345 /* Juli@ */ + +extern struct snd_ice1712_card_info snd_vt1724_juli_cards[]; + +#endif /* __SOUND_JULI_H */ diff --git a/sound/pci/ice1712/maya44.c b/sound/pci/ice1712/maya44.c new file mode 100644 index 000000000..fd1427d64 --- /dev/null +++ b/sound/pci/ice1712/maya44.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for ESI Maya44 cards + * + * Copyright (c) 2009 Takashi Iwai + * Based on the patches by Rainer Zimmermann + */ + +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "maya44.h" + +/* WM8776 register indexes */ +#define WM8776_REG_HEADPHONE_L 0x00 +#define WM8776_REG_HEADPHONE_R 0x01 +#define WM8776_REG_HEADPHONE_MASTER 0x02 +#define WM8776_REG_DAC_ATTEN_L 0x03 +#define WM8776_REG_DAC_ATTEN_R 0x04 +#define WM8776_REG_DAC_ATTEN_MASTER 0x05 +#define WM8776_REG_DAC_PHASE 0x06 +#define WM8776_REG_DAC_CONTROL 0x07 +#define WM8776_REG_DAC_MUTE 0x08 +#define WM8776_REG_DAC_DEEMPH 0x09 +#define WM8776_REG_DAC_IF_CONTROL 0x0a +#define WM8776_REG_ADC_IF_CONTROL 0x0b +#define WM8776_REG_MASTER_MODE_CONTROL 0x0c +#define WM8776_REG_POWERDOWN 0x0d +#define WM8776_REG_ADC_ATTEN_L 0x0e +#define WM8776_REG_ADC_ATTEN_R 0x0f +#define WM8776_REG_ADC_ALC1 0x10 +#define WM8776_REG_ADC_ALC2 0x11 +#define WM8776_REG_ADC_ALC3 0x12 +#define WM8776_REG_ADC_NOISE_GATE 0x13 +#define WM8776_REG_ADC_LIMITER 0x14 +#define WM8776_REG_ADC_MUX 0x15 +#define WM8776_REG_OUTPUT_MUX 0x16 +#define WM8776_REG_RESET 0x17 + +#define WM8776_NUM_REGS 0x18 + +/* clock ratio identifiers for snd_wm8776_set_rate() */ +#define WM8776_CLOCK_RATIO_128FS 0 +#define WM8776_CLOCK_RATIO_192FS 1 +#define WM8776_CLOCK_RATIO_256FS 2 +#define WM8776_CLOCK_RATIO_384FS 3 +#define WM8776_CLOCK_RATIO_512FS 4 +#define WM8776_CLOCK_RATIO_768FS 5 + +enum { WM_VOL_HP, WM_VOL_DAC, WM_VOL_ADC, WM_NUM_VOLS }; +enum { WM_SW_DAC, WM_SW_BYPASS, WM_NUM_SWITCHES }; + +struct snd_wm8776 { + unsigned char addr; + unsigned short regs[WM8776_NUM_REGS]; + unsigned char volumes[WM_NUM_VOLS][2]; + unsigned int switch_bits; +}; + +struct snd_maya44 { + struct snd_ice1712 *ice; + struct snd_wm8776 wm[2]; + struct mutex mutex; +}; + + +/* write the given register and save the data to the cache */ +static void wm8776_write(struct snd_ice1712 *ice, struct snd_wm8776 *wm, + unsigned char reg, unsigned short val) +{ + /* + * WM8776 registers are up to 9 bits wide, bit 8 is placed in the LSB + * of the address field + */ + snd_vt1724_write_i2c(ice, wm->addr, + (reg << 1) | ((val >> 8) & 1), + val & 0xff); + wm->regs[reg] = val; +} + +/* + * update the given register with and/or mask and save the data to the cache + */ +static int wm8776_write_bits(struct snd_ice1712 *ice, struct snd_wm8776 *wm, + unsigned char reg, + unsigned short mask, unsigned short val) +{ + val |= wm->regs[reg] & ~mask; + if (val != wm->regs[reg]) { + wm8776_write(ice, wm, reg, val); + return 1; + } + return 0; +} + + +/* + * WM8776 volume controls + */ + +struct maya_vol_info { + unsigned int maxval; /* volume range: 0..maxval */ + unsigned char regs[2]; /* left and right registers */ + unsigned short mask; /* value mask */ + unsigned short offset; /* zero-value offset */ + unsigned short mute; /* mute bit */ + unsigned short update; /* update bits */ + unsigned char mux_bits[2]; /* extra bits for ADC mute */ +}; + +static const struct maya_vol_info vol_info[WM_NUM_VOLS] = { + [WM_VOL_HP] = { + .maxval = 80, + .regs = { WM8776_REG_HEADPHONE_L, WM8776_REG_HEADPHONE_R }, + .mask = 0x7f, + .offset = 0x30, + .mute = 0x00, + .update = 0x180, /* update and zero-cross enable */ + }, + [WM_VOL_DAC] = { + .maxval = 255, + .regs = { WM8776_REG_DAC_ATTEN_L, WM8776_REG_DAC_ATTEN_R }, + .mask = 0xff, + .offset = 0x01, + .mute = 0x00, + .update = 0x100, /* zero-cross enable */ + }, + [WM_VOL_ADC] = { + .maxval = 91, + .regs = { WM8776_REG_ADC_ATTEN_L, WM8776_REG_ADC_ATTEN_R }, + .mask = 0xff, + .offset = 0xa5, + .mute = 0xa5, + .update = 0x100, /* update */ + .mux_bits = { 0x80, 0x40 }, /* ADCMUX bits */ + }, +}; + +/* + * dB tables + */ +/* headphone output: mute, -73..+6db (1db step) */ +static const DECLARE_TLV_DB_SCALE(db_scale_hp, -7400, 100, 1); +/* DAC output: mute, -127..0db (0.5db step) */ +static const DECLARE_TLV_DB_SCALE(db_scale_dac, -12750, 50, 1); +/* ADC gain: mute, -21..+24db (0.5db step) */ +static const DECLARE_TLV_DB_SCALE(db_scale_adc, -2100, 50, 1); + +static int maya_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int idx = kcontrol->private_value; + const struct maya_vol_info *vol = &vol_info[idx]; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = vol->maxval; + return 0; +} + +static int maya_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + struct snd_wm8776 *wm = + &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)]; + unsigned int idx = kcontrol->private_value; + + guard(mutex)(&chip->mutex); + ucontrol->value.integer.value[0] = wm->volumes[idx][0]; + ucontrol->value.integer.value[1] = wm->volumes[idx][1]; + return 0; +} + +static int maya_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + struct snd_wm8776 *wm = + &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)]; + unsigned int idx = kcontrol->private_value; + const struct maya_vol_info *vol = &vol_info[idx]; + unsigned int val, data; + int ch, changed = 0; + + guard(mutex)(&chip->mutex); + for (ch = 0; ch < 2; ch++) { + val = ucontrol->value.integer.value[ch]; + if (val > vol->maxval) + val = vol->maxval; + if (val == wm->volumes[idx][ch]) + continue; + if (!val) + data = vol->mute; + else + data = (val - 1) + vol->offset; + data |= vol->update; + changed |= wm8776_write_bits(chip->ice, wm, vol->regs[ch], + vol->mask | vol->update, data); + if (vol->mux_bits[ch]) + wm8776_write_bits(chip->ice, wm, WM8776_REG_ADC_MUX, + vol->mux_bits[ch], + val ? 0 : vol->mux_bits[ch]); + wm->volumes[idx][ch] = val; + } + return changed; +} + +/* + * WM8776 switch controls + */ + +#define COMPOSE_SW_VAL(idx, reg, mask) ((idx) | ((reg) << 8) | ((mask) << 16)) +#define GET_SW_VAL_IDX(val) ((val) & 0xff) +#define GET_SW_VAL_REG(val) (((val) >> 8) & 0xff) +#define GET_SW_VAL_MASK(val) (((val) >> 16) & 0xff) + +#define maya_sw_info snd_ctl_boolean_mono_info + +static int maya_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + struct snd_wm8776 *wm = + &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)]; + unsigned int idx = GET_SW_VAL_IDX(kcontrol->private_value); + + ucontrol->value.integer.value[0] = (wm->switch_bits >> idx) & 1; + return 0; +} + +static int maya_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + struct snd_wm8776 *wm = + &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)]; + unsigned int idx = GET_SW_VAL_IDX(kcontrol->private_value); + unsigned int mask, val; + int changed; + + guard(mutex)(&chip->mutex); + mask = 1 << idx; + wm->switch_bits &= ~mask; + val = ucontrol->value.integer.value[0]; + if (val) + wm->switch_bits |= mask; + mask = GET_SW_VAL_MASK(kcontrol->private_value); + changed = wm8776_write_bits(chip->ice, wm, + GET_SW_VAL_REG(kcontrol->private_value), + mask, val ? mask : 0); + return changed; +} + +/* + * GPIO pins (known ones for maya44) + */ +#define GPIO_PHANTOM_OFF 2 +#define GPIO_MIC_RELAY 4 +#define GPIO_SPDIF_IN_INV 5 +#define GPIO_MUST_BE_0 7 + +/* + * GPIO switch controls + */ + +#define COMPOSE_GPIO_VAL(shift, inv) ((shift) | ((inv) << 8)) +#define GET_GPIO_VAL_SHIFT(val) ((val) & 0xff) +#define GET_GPIO_VAL_INV(val) (((val) >> 8) & 1) + +static int maya_set_gpio_bits(struct snd_ice1712 *ice, unsigned int mask, + unsigned int bits) +{ + unsigned int data; + data = snd_ice1712_gpio_read(ice); + if ((data & mask) == bits) + return 0; + snd_ice1712_gpio_write(ice, (data & ~mask) | bits); + return 1; +} + +#define maya_gpio_sw_info snd_ctl_boolean_mono_info + +static int maya_gpio_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + unsigned int shift = GET_GPIO_VAL_SHIFT(kcontrol->private_value); + unsigned int val; + + val = (snd_ice1712_gpio_read(chip->ice) >> shift) & 1; + if (GET_GPIO_VAL_INV(kcontrol->private_value)) + val = !val; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int maya_gpio_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + unsigned int shift = GET_GPIO_VAL_SHIFT(kcontrol->private_value); + unsigned int val, mask; + int changed; + + guard(mutex)(&chip->mutex); + mask = 1 << shift; + val = ucontrol->value.integer.value[0]; + if (GET_GPIO_VAL_INV(kcontrol->private_value)) + val = !val; + val = val ? mask : 0; + changed = maya_set_gpio_bits(chip->ice, mask, val); + return changed; +} + +/* + * capture source selection + */ + +/* known working input slots (0-4) */ +#define MAYA_LINE_IN 1 /* in-2 */ +#define MAYA_MIC_IN 3 /* in-4 */ + +static void wm8776_select_input(struct snd_maya44 *chip, int idx, int line) +{ + wm8776_write_bits(chip->ice, &chip->wm[idx], WM8776_REG_ADC_MUX, + 0x1f, 1 << line); +} + +static int maya_rec_src_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "Line", "Mic" }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int maya_rec_src_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + int sel; + + if (snd_ice1712_gpio_read(chip->ice) & (1 << GPIO_MIC_RELAY)) + sel = 1; + else + sel = 0; + ucontrol->value.enumerated.item[0] = sel; + return 0; +} + +static int maya_rec_src_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + int sel = ucontrol->value.enumerated.item[0]; + int changed; + + guard(mutex)(&chip->mutex); + changed = maya_set_gpio_bits(chip->ice, 1 << GPIO_MIC_RELAY, + sel ? (1 << GPIO_MIC_RELAY) : 0); + wm8776_select_input(chip, 0, sel ? MAYA_MIC_IN : MAYA_LINE_IN); + return changed; +} + +/* + * Maya44 routing switch settings have different meanings than the standard + * ice1724 switches as defined in snd_vt1724_pro_route_info (ice1724.c). + */ +static int maya_pb_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "PCM Out", /* 0 */ + "Input 1", "Input 2", "Input 3", "Input 4" + }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int maya_pb_route_shift(int idx) +{ + static const unsigned char shift[10] = + { 8, 20, 0, 3, 11, 23, 14, 26, 17, 29 }; + return shift[idx % 10]; +} + +static int maya_pb_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + ucontrol->value.enumerated.item[0] = + snd_ice1724_get_route_val(chip->ice, maya_pb_route_shift(idx)); + return 0; +} + +static int maya_pb_route_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + return snd_ice1724_put_route_val(chip->ice, + ucontrol->value.enumerated.item[0], + maya_pb_route_shift(idx)); +} + + +/* + * controls to be added + */ + +static const struct snd_kcontrol_new maya_controls[] = { + { + .name = "Crossmix Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = maya_vol_info, + .get = maya_vol_get, + .put = maya_vol_put, + .tlv = { .p = db_scale_hp }, + .private_value = WM_VOL_HP, + .count = 2, + }, + { + .name = "PCM Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = maya_vol_info, + .get = maya_vol_get, + .put = maya_vol_put, + .tlv = { .p = db_scale_dac }, + .private_value = WM_VOL_DAC, + .count = 2, + }, + { + .name = "Line Capture Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = maya_vol_info, + .get = maya_vol_get, + .put = maya_vol_put, + .tlv = { .p = db_scale_adc }, + .private_value = WM_VOL_ADC, + .count = 2, + }, + { + .name = "PCM Playback Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_sw_info, + .get = maya_sw_get, + .put = maya_sw_put, + .private_value = COMPOSE_SW_VAL(WM_SW_DAC, + WM8776_REG_OUTPUT_MUX, 0x01), + .count = 2, + }, + { + .name = "Bypass Playback Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_sw_info, + .get = maya_sw_get, + .put = maya_sw_put, + .private_value = COMPOSE_SW_VAL(WM_SW_BYPASS, + WM8776_REG_OUTPUT_MUX, 0x04), + .count = 2, + }, + { + .name = "Capture Source", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_rec_src_info, + .get = maya_rec_src_get, + .put = maya_rec_src_put, + }, + { + .name = "Mic Phantom Power Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_gpio_sw_info, + .get = maya_gpio_sw_get, + .put = maya_gpio_sw_put, + .private_value = COMPOSE_GPIO_VAL(GPIO_PHANTOM_OFF, 1), + }, + { + .name = "SPDIF Capture Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_gpio_sw_info, + .get = maya_gpio_sw_get, + .put = maya_gpio_sw_put, + .private_value = COMPOSE_GPIO_VAL(GPIO_SPDIF_IN_INV, 1), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "H/W Playback Route", + .info = maya_pb_route_info, + .get = maya_pb_route_get, + .put = maya_pb_route_put, + .count = 4, /* FIXME: do controls 5-9 have any meaning? */ + }, +}; + +static int maya44_add_controls(struct snd_ice1712 *ice) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(maya_controls); i++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&maya_controls[i], + ice->spec)); + if (err < 0) + return err; + } + return 0; +} + + +/* + * initialize a wm8776 chip + */ +static void wm8776_init(struct snd_ice1712 *ice, + struct snd_wm8776 *wm, unsigned int addr) +{ + static const unsigned short inits_wm8776[] = { + 0x02, 0x100, /* R2: headphone L+R muted + update */ + 0x05, 0x100, /* R5: DAC output L+R muted + update */ + 0x06, 0x000, /* R6: DAC output phase normal */ + 0x07, 0x091, /* R7: DAC enable zero cross detection, + normal output */ + 0x08, 0x000, /* R8: DAC soft mute off */ + 0x09, 0x000, /* R9: no deemph, DAC zero detect disabled */ + 0x0a, 0x022, /* R10: DAC I2C mode, std polarities, 24bit */ + 0x0b, 0x022, /* R11: ADC I2C mode, std polarities, 24bit, + highpass filter enabled */ + 0x0c, 0x042, /* R12: ADC+DAC slave, ADC+DAC 44,1kHz */ + 0x0d, 0x000, /* R13: all power up */ + 0x0e, 0x100, /* R14: ADC left muted, + enable zero cross detection */ + 0x0f, 0x100, /* R15: ADC right muted, + enable zero cross detection */ + /* R16: ALC...*/ + 0x11, 0x000, /* R17: disable ALC */ + /* R18: ALC...*/ + /* R19: noise gate...*/ + 0x15, 0x000, /* R21: ADC input mux init, mute all inputs */ + 0x16, 0x001, /* R22: output mux, select DAC */ + 0xff, 0xff + }; + + const unsigned short *ptr; + unsigned char reg; + unsigned short data; + + wm->addr = addr; + /* enable DAC output; mute bypass, aux & all inputs */ + wm->switch_bits = (1 << WM_SW_DAC); + + ptr = inits_wm8776; + while (*ptr != 0xff) { + reg = *ptr++; + data = *ptr++; + wm8776_write(ice, wm, reg, data); + } +} + + +/* + * change the rate on the WM8776 codecs. + * this assumes that the VT17xx's rate is changed by the calling function. + * NOTE: even though the WM8776's are running in slave mode and rate + * selection is automatic, we need to call snd_wm8776_set_rate() here + * to make sure some flags are set correctly. + */ +static void set_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + struct snd_maya44 *chip = ice->spec; + unsigned int ratio, adc_ratio, val; + int i; + + switch (rate) { + case 192000: + ratio = WM8776_CLOCK_RATIO_128FS; + break; + case 176400: + ratio = WM8776_CLOCK_RATIO_128FS; + break; + case 96000: + ratio = WM8776_CLOCK_RATIO_256FS; + break; + case 88200: + ratio = WM8776_CLOCK_RATIO_384FS; + break; + case 48000: + ratio = WM8776_CLOCK_RATIO_512FS; + break; + case 44100: + ratio = WM8776_CLOCK_RATIO_512FS; + break; + case 32000: + ratio = WM8776_CLOCK_RATIO_768FS; + break; + case 0: + /* no hint - S/PDIF input is master, simply return */ + return; + default: + snd_BUG(); + return; + } + + /* + * this currently sets the same rate for ADC and DAC, but limits + * ADC rate to 256X (96kHz). For 256X mode (96kHz), this sets ADC + * oversampling to 64x, as recommended by WM8776 datasheet. + * Setting the rate is not really necessary in slave mode. + */ + adc_ratio = ratio; + if (adc_ratio < WM8776_CLOCK_RATIO_256FS) + adc_ratio = WM8776_CLOCK_RATIO_256FS; + + val = adc_ratio; + if (adc_ratio == WM8776_CLOCK_RATIO_256FS) + val |= 8; + val |= ratio << 4; + + guard(mutex)(&chip->mutex); + for (i = 0; i < 2; i++) + wm8776_write_bits(ice, &chip->wm[i], + WM8776_REG_MASTER_MODE_CONTROL, + 0x180, val); +} + +/* + * supported sample rates (to override the default one) + */ + +static const unsigned int rates[] = { + 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000 +}; + +/* playback rates: 32..192 kHz */ +static const struct snd_pcm_hw_constraint_list dac_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0 +}; + + +/* + * chip addresses on I2C bus + */ +static const unsigned char wm8776_addr[2] = { + 0x34, 0x36, /* codec 0 & 1 */ +}; + +/* + * initialize the chip + */ +static int maya44_init(struct snd_ice1712 *ice) +{ + int i; + struct snd_maya44 *chip; + + chip = kzalloc_obj(*chip); + if (!chip) + return -ENOMEM; + mutex_init(&chip->mutex); + chip->ice = ice; + ice->spec = chip; + + /* initialise codecs */ + ice->num_total_dacs = 4; + ice->num_total_adcs = 4; + ice->akm_codecs = 0; + + for (i = 0; i < 2; i++) { + wm8776_init(ice, &chip->wm[i], wm8776_addr[i]); + wm8776_select_input(chip, i, MAYA_LINE_IN); + } + + /* set card specific rates */ + ice->hw_rates = &dac_rates; + + /* register change rate notifier */ + ice->gpio.set_pro_rate = set_rate; + + /* RDMA1 (2nd input channel) is used for ADC by default */ + ice->force_rdma1 = 1; + + /* have an own routing control */ + ice->own_routing = 1; + + return 0; +} + + +/* + * Maya44 boards don't provide the EEPROM data except for the vendor IDs. + * hence the driver needs to sets up it properly. + */ + +static const unsigned char maya44_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x45, + /* clock xin1=49.152MHz, mpu401, 2 stereo ADCs+DACs */ + [ICE_EEP2_ACLINK] = 0x80, + /* I2S */ + [ICE_EEP2_I2S] = 0xf8, + /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, + /* enable spdif out, spdif out supp, spdif-in, ext spdif out */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0xff, + [ICE_EEP2_GPIO_MASK] = 0/*0x9f*/, + [ICE_EEP2_GPIO_MASK1] = 0/*0xff*/, + [ICE_EEP2_GPIO_MASK2] = 0/*0x7f*/, + [ICE_EEP2_GPIO_STATE] = (1 << GPIO_PHANTOM_OFF) | + (1 << GPIO_SPDIF_IN_INV), + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_maya44_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_MAYA44, + .name = "ESI Maya44", + .model = "maya44", + .chip_init = maya44_init, + .build_controls = maya44_add_controls, + .eeprom_size = sizeof(maya44_eeprom), + .eeprom_data = maya44_eeprom, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/maya44.h b/sound/pci/ice1712/maya44.h new file mode 100644 index 000000000..f5a97d987 --- /dev/null +++ b/sound/pci/ice1712/maya44.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_MAYA44_H +#define __SOUND_MAYA44_H + +#define MAYA44_DEVICE_DESC "{ESI,Maya44}," + +#define VT1724_SUBDEVICE_MAYA44 0x34315441 /* Maya44 */ + +extern struct snd_ice1712_card_info snd_vt1724_maya44_cards[]; + +#endif /* __SOUND_MAYA44_H */ diff --git a/sound/pci/ice1712/phase.c b/sound/pci/ice1712/phase.c new file mode 100644 index 000000000..37756d062 --- /dev/null +++ b/sound/pci/ice1712/phase.c @@ -0,0 +1,948 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble ICE1724 (Envy24) + * + * Lowlevel functions for Terratec PHASE 22 + * + * Copyright (c) 2005 Misha Zhilin + */ + +/* PHASE 22 overview: + * Audio controller: VIA Envy24HT-S (slightly trimmed down Envy24HT, 4in/4out) + * Analog chip: AK4524 (partially via Philip's 74HCT125) + * Digital receiver: CS8414-CS (supported in this release) + * PHASE 22 revision 2.0 and Terrasoniq/Musonik TS22PCI have CS8416 + * (support status unknown, please test and report) + * + * Envy connects to AK4524 + * - CS directly from GPIO 10 + * - CCLK via 74HCT125's gate #4 from GPIO 4 + * - CDTI via 74HCT125's gate #2 from GPIO 5 + * CDTI may be completely blocked by 74HCT125's gate #1 + * controlled by GPIO 3 + */ + +/* PHASE 28 overview: + * Audio controller: VIA Envy24HT (full untrimmed version, 4in/8out) + * Analog chip: WM8770 (8 channel 192k DAC, 2 channel 96k ADC) + * Digital receiver: CS8414-CS (supported in this release) + */ + +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "phase.h" +#include + +/* AC97 register cache for Phase28 */ +struct phase28_spec { + unsigned short master[2]; + unsigned short vol[8]; +}; + +/* WM8770 registers */ +#define WM_DAC_ATTEN 0x00 /* DAC1-8 analog attenuation */ +#define WM_DAC_MASTER_ATTEN 0x08 /* DAC master analog attenuation */ +#define WM_DAC_DIG_ATTEN 0x09 /* DAC1-8 digital attenuation */ +#define WM_DAC_DIG_MASTER_ATTEN 0x11 /* DAC master digital attenuation */ +#define WM_PHASE_SWAP 0x12 /* DAC phase */ +#define WM_DAC_CTRL1 0x13 /* DAC control bits */ +#define WM_MUTE 0x14 /* mute controls */ +#define WM_DAC_CTRL2 0x15 /* de-emphasis and zefo-flag */ +#define WM_INT_CTRL 0x16 /* interface control */ +#define WM_MASTER 0x17 /* master clock and mode */ +#define WM_POWERDOWN 0x18 /* power-down controls */ +#define WM_ADC_GAIN 0x19 /* ADC gain L(19)/R(1a) */ +#define WM_ADC_MUX 0x1b /* input MUX */ +#define WM_OUT_MUX1 0x1c /* output MUX */ +#define WM_OUT_MUX2 0x1e /* output MUX */ +#define WM_RESET 0x1f /* software reset */ + + +/* + * Logarithmic volume values for WM8770 + * Computed as 20 * Log10(255 / x) + */ +static const unsigned char wm_vol[256] = { + 127, 48, 42, 39, 36, 34, 33, 31, 30, 29, 28, 27, 27, 26, 25, 25, 24, + 24, 23, 23, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 18, + 17, 17, 17, 17, 16, 16, 16, 16, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, + 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +#define WM_VOL_MAX (sizeof(wm_vol) - 1) +#define WM_VOL_MUTE 0x8000 + +static const struct snd_akm4xxx akm_phase22 = { + .type = SND_AK4524, + .num_dacs = 2, + .num_adcs = 2, +}; + +static const struct snd_ak4xxx_private akm_phase22_priv = { + .caddr = 2, + .cif = 1, + .data_mask = 1 << 4, + .clk_mask = 1 << 5, + .cs_mask = 1 << 10, + .cs_addr = 1 << 10, + .cs_none = 0, + .add_flags = 1 << 3, + .mask_flags = 0, +}; + +static int phase22_init(struct snd_ice1712 *ice) +{ + struct snd_akm4xxx *ak; + int err; + + /* Configure DAC/ADC description for generic part of ice1724 */ + switch (ice->eeprom.subvendor) { + case VT1724_SUBDEVICE_PHASE22: + case VT1724_SUBDEVICE_TS22: + ice->num_total_dacs = 2; + ice->num_total_adcs = 2; + ice->vt1720 = 1; /* Envy24HT-S have 16 bit wide GPIO */ + break; + default: + snd_BUG(); + return -EINVAL; + } + + /* Initialize analog chips */ + ice->akm = kzalloc_obj(struct snd_akm4xxx); + ak = ice->akm; + if (!ak) + return -ENOMEM; + ice->akm_codecs = 1; + switch (ice->eeprom.subvendor) { + case VT1724_SUBDEVICE_PHASE22: + case VT1724_SUBDEVICE_TS22: + err = snd_ice1712_akm4xxx_init(ak, &akm_phase22, + &akm_phase22_priv, ice); + if (err < 0) + return err; + break; + } + + return 0; +} + +static int phase22_add_controls(struct snd_ice1712 *ice) +{ + int err = 0; + + switch (ice->eeprom.subvendor) { + case VT1724_SUBDEVICE_PHASE22: + case VT1724_SUBDEVICE_TS22: + err = snd_ice1712_akm4xxx_build_controls(ice); + if (err < 0) + return err; + } + return 0; +} + +static const unsigned char phase22_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x28, /* clock 512, mpu 401, + spdif-in/1xADC, 1xDACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xf0, /* vol, 96k, 24bit */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0xff, + [ICE_EEP2_GPIO_MASK] = 0x00, + [ICE_EEP2_GPIO_MASK1] = 0x00, + [ICE_EEP2_GPIO_MASK2] = 0x00, + [ICE_EEP2_GPIO_STATE] = 0x00, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; + +static const unsigned char phase28_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x2b, /* clock 512, mpu401, + spdif-in/1xADC, 4xDACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0x5f, + [ICE_EEP2_GPIO_MASK] = 0x00, + [ICE_EEP2_GPIO_MASK1] = 0x00, + [ICE_EEP2_GPIO_MASK2] = 0x00, + [ICE_EEP2_GPIO_STATE] = 0x00, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; + +/* + * write data in the SPI mode + */ +static void phase28_spi_write(struct snd_ice1712 *ice, unsigned int cs, + unsigned int data, int bits) +{ + unsigned int tmp; + int i; + + tmp = snd_ice1712_gpio_read(ice); + + snd_ice1712_gpio_set_mask(ice, ~(PHASE28_WM_RW|PHASE28_SPI_MOSI| + PHASE28_SPI_CLK|PHASE28_WM_CS)); + tmp |= PHASE28_WM_RW; + tmp &= ~cs; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + + for (i = bits - 1; i >= 0; i--) { + tmp &= ~PHASE28_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + if (data & (1 << i)) + tmp |= PHASE28_SPI_MOSI; + else + tmp &= ~PHASE28_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + tmp |= PHASE28_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + + tmp &= ~PHASE28_SPI_CLK; + tmp |= cs; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + tmp |= PHASE28_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); +} + +/* + * get the current register value of WM codec + */ +static unsigned short wm_get(struct snd_ice1712 *ice, int reg) +{ + reg <<= 1; + return ((unsigned short)ice->akm[0].images[reg] << 8) | + ice->akm[0].images[reg + 1]; +} + +/* + * set the register value of WM codec + */ +static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + phase28_spi_write(ice, PHASE28_WM_CS, (reg << 9) | (val & 0x1ff), 16); +} + +/* + * set the register value of WM codec and remember it + */ +static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + wm_put_nocache(ice, reg, val); + reg <<= 1; + ice->akm[0].images[reg] = val >> 8; + ice->akm[0].images[reg + 1] = val; +} + +static void wm_set_vol(struct snd_ice1712 *ice, unsigned int index, + unsigned short vol, unsigned short master) +{ + unsigned char nvol; + + if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE)) + nvol = 0; + else + nvol = 127 - wm_vol[(((vol & ~WM_VOL_MUTE) * + (master & ~WM_VOL_MUTE)) / 127) & WM_VOL_MAX]; + + wm_put(ice, index, nvol); + wm_put_nocache(ice, index, 0x180 | nvol); +} + +/* + * DAC mute control + */ +#define wm_pcm_mute_info snd_ctl_boolean_mono_info + +static int wm_pcm_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.integer.value[0] = (wm_get(ice, WM_MUTE) & 0x10) ? + 0 : 1; + return 0; +} + +static int wm_pcm_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short nval, oval; + int change; + + snd_ice1712_save_gpio_status(ice); + oval = wm_get(ice, WM_MUTE); + nval = (oval & ~0x10) | (ucontrol->value.integer.value[0] ? 0 : 0x10); + change = (nval != oval); + if (change) + wm_put(ice, WM_MUTE, nval); + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* + * Master volume attenuation mixer control + */ +static int wm_master_vol_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 = WM_VOL_MAX; + return 0; +} + +static int wm_master_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct phase28_spec *spec = ice->spec; + int i; + for (i = 0; i < 2; i++) + ucontrol->value.integer.value[i] = spec->master[i] & + ~WM_VOL_MUTE; + return 0; +} + +static int wm_master_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct phase28_spec *spec = ice->spec; + int ch, change = 0; + + snd_ice1712_save_gpio_status(ice); + for (ch = 0; ch < 2; ch++) { + unsigned int vol = ucontrol->value.integer.value[ch]; + if (vol > WM_VOL_MAX) + continue; + vol |= spec->master[ch] & WM_VOL_MUTE; + if (vol != spec->master[ch]) { + int dac; + spec->master[ch] = vol; + for (dac = 0; dac < ice->num_total_dacs; dac += 2) + wm_set_vol(ice, WM_DAC_ATTEN + dac + ch, + spec->vol[dac + ch], + spec->master[ch]); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + return change; +} + +static int phase28_init(struct snd_ice1712 *ice) +{ + static const unsigned short wm_inits_phase28[] = { + /* These come first to reduce init pop noise */ + 0x1b, 0x044, /* ADC Mux (AC'97 source) */ + 0x1c, 0x00B, /* Out Mux1 (VOUT1 = DAC+AUX, VOUT2 = DAC) */ + 0x1d, 0x009, /* Out Mux2 (VOUT2 = DAC, VOUT3 = DAC) */ + + 0x18, 0x000, /* All power-up */ + + 0x16, 0x122, /* I2S, normal polarity, 24bit */ + 0x17, 0x022, /* 256fs, slave mode */ + 0x00, 0, /* DAC1 analog mute */ + 0x01, 0, /* DAC2 analog mute */ + 0x02, 0, /* DAC3 analog mute */ + 0x03, 0, /* DAC4 analog mute */ + 0x04, 0, /* DAC5 analog mute */ + 0x05, 0, /* DAC6 analog mute */ + 0x06, 0, /* DAC7 analog mute */ + 0x07, 0, /* DAC8 analog mute */ + 0x08, 0x100, /* master analog mute */ + 0x09, 0xff, /* DAC1 digital full */ + 0x0a, 0xff, /* DAC2 digital full */ + 0x0b, 0xff, /* DAC3 digital full */ + 0x0c, 0xff, /* DAC4 digital full */ + 0x0d, 0xff, /* DAC5 digital full */ + 0x0e, 0xff, /* DAC6 digital full */ + 0x0f, 0xff, /* DAC7 digital full */ + 0x10, 0xff, /* DAC8 digital full */ + 0x11, 0x1ff, /* master digital full */ + 0x12, 0x000, /* phase normal */ + 0x13, 0x090, /* unmute DAC L/R */ + 0x14, 0x000, /* all unmute */ + 0x15, 0x000, /* no deemphasis, no ZFLG */ + 0x19, 0x000, /* -12dB ADC/L */ + 0x1a, 0x000, /* -12dB ADC/R */ + (unsigned short)-1 + }; + + unsigned int tmp; + struct snd_akm4xxx *ak; + struct phase28_spec *spec; + const unsigned short *p; + int i; + + ice->num_total_dacs = 8; + ice->num_total_adcs = 2; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + /* Initialize analog chips */ + ice->akm = kzalloc_obj(struct snd_akm4xxx); + ak = ice->akm; + if (!ak) + return -ENOMEM; + ice->akm_codecs = 1; + + snd_ice1712_gpio_set_dir(ice, 0x5fffff); /* fix this for time being */ + + /* reset the wm codec as the SPI mode */ + snd_ice1712_save_gpio_status(ice); + snd_ice1712_gpio_set_mask(ice, ~(PHASE28_WM_RESET|PHASE28_WM_CS| + PHASE28_HP_SEL)); + + tmp = snd_ice1712_gpio_read(ice); + tmp &= ~PHASE28_WM_RESET; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + tmp |= PHASE28_WM_CS; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + tmp |= PHASE28_WM_RESET; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + + p = wm_inits_phase28; + for (; *p != (unsigned short)-1; p += 2) + wm_put(ice, p[0], p[1]); + + snd_ice1712_restore_gpio_status(ice); + + spec->master[0] = WM_VOL_MUTE; + spec->master[1] = WM_VOL_MUTE; + for (i = 0; i < ice->num_total_dacs; i++) { + spec->vol[i] = WM_VOL_MUTE; + wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]); + } + + return 0; +} + +/* + * DAC volume attenuation mixer control + */ +static int wm_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int voices = kcontrol->private_value >> 8; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = voices; + uinfo->value.integer.min = 0; /* mute (-101dB) */ + uinfo->value.integer.max = 0x7F; /* 0dB */ + return 0; +} + +static int wm_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct phase28_spec *spec = ice->spec; + int i, ofs, voices; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xff; + for (i = 0; i < voices; i++) + ucontrol->value.integer.value[i] = + spec->vol[ofs+i] & ~WM_VOL_MUTE; + return 0; +} + +static int wm_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct phase28_spec *spec = ice->spec; + int i, idx, ofs, voices; + int change = 0; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xff; + snd_ice1712_save_gpio_status(ice); + for (i = 0; i < voices; i++) { + unsigned int vol; + vol = ucontrol->value.integer.value[i]; + if (vol > 0x7f) + continue; + vol |= spec->vol[ofs+i] & WM_VOL_MUTE; + if (vol != spec->vol[ofs+i]) { + spec->vol[ofs+i] = vol; + idx = WM_DAC_ATTEN + ofs + i; + wm_set_vol(ice, idx, spec->vol[ofs+i], + spec->master[i]); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + return change; +} + +/* + * WM8770 mute control + */ +static int wm_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = kcontrol->private_value >> 8; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int wm_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct phase28_spec *spec = ice->spec; + int voices, ofs, i; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xFF; + + for (i = 0; i < voices; i++) + ucontrol->value.integer.value[i] = + (spec->vol[ofs+i] & WM_VOL_MUTE) ? 0 : 1; + return 0; +} + +static int wm_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct phase28_spec *spec = ice->spec; + int change = 0, voices, ofs, i; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xFF; + + snd_ice1712_save_gpio_status(ice); + for (i = 0; i < voices; i++) { + int val = (spec->vol[ofs + i] & WM_VOL_MUTE) ? 0 : 1; + if (ucontrol->value.integer.value[i] != val) { + spec->vol[ofs + i] &= ~WM_VOL_MUTE; + spec->vol[ofs + i] |= + ucontrol->value.integer.value[i] ? 0 : + WM_VOL_MUTE; + wm_set_vol(ice, ofs + i, spec->vol[ofs + i], + spec->master[i]); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* + * WM8770 master mute control + */ +#define wm_master_mute_info snd_ctl_boolean_stereo_info + +static int wm_master_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct phase28_spec *spec = ice->spec; + + ucontrol->value.integer.value[0] = + (spec->master[0] & WM_VOL_MUTE) ? 0 : 1; + ucontrol->value.integer.value[1] = + (spec->master[1] & WM_VOL_MUTE) ? 0 : 1; + return 0; +} + +static int wm_master_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct phase28_spec *spec = ice->spec; + int change = 0, i; + + snd_ice1712_save_gpio_status(ice); + for (i = 0; i < 2; i++) { + int val = (spec->master[i] & WM_VOL_MUTE) ? 0 : 1; + if (ucontrol->value.integer.value[i] != val) { + int dac; + spec->master[i] &= ~WM_VOL_MUTE; + spec->master[i] |= + ucontrol->value.integer.value[i] ? 0 : + WM_VOL_MUTE; + for (dac = 0; dac < ice->num_total_dacs; dac += 2) + wm_set_vol(ice, WM_DAC_ATTEN + dac + i, + spec->vol[dac + i], + spec->master[i]); + change = 1; + } + } + snd_ice1712_restore_gpio_status(ice); + + return change; +} + +/* digital master volume */ +#define PCM_0dB 0xff +#define PCM_RES 128 /* -64dB */ +#define PCM_MIN (PCM_0dB - PCM_RES) +static int wm_pcm_vol_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; /* mute (-64dB) */ + uinfo->value.integer.max = PCM_RES; /* 0dB */ + return 0; +} + +static int wm_pcm_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val; + + guard(mutex)(&ice->gpio_mutex); + val = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff; + val = val > PCM_MIN ? (val - PCM_MIN) : 0; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int wm_pcm_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short ovol, nvol; + int change = 0; + + nvol = ucontrol->value.integer.value[0]; + if (nvol > PCM_RES) + return -EINVAL; + snd_ice1712_save_gpio_status(ice); + nvol = (nvol ? (nvol + PCM_MIN) : 0) & 0xff; + ovol = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff; + if (ovol != nvol) { + wm_put(ice, WM_DAC_DIG_MASTER_ATTEN, nvol); /* prelatch */ + /* update */ + wm_put_nocache(ice, WM_DAC_DIG_MASTER_ATTEN, nvol | 0x100); + change = 1; + } + snd_ice1712_restore_gpio_status(ice); + return change; +} + +/* + * Deemphasis + */ +#define phase28_deemp_info snd_ctl_boolean_mono_info + +static int phase28_deemp_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL2) & 0xf) == + 0xf; + return 0; +} + +static int phase28_deemp_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int temp, temp2; + temp = wm_get(ice, WM_DAC_CTRL2); + temp2 = temp; + if (ucontrol->value.integer.value[0]) + temp |= 0xf; + else + temp &= ~0xf; + if (temp != temp2) { + wm_put(ice, WM_DAC_CTRL2, temp); + return 1; + } + return 0; +} + +/* + * ADC Oversampling + */ +static int phase28_oversampling_info(struct snd_kcontrol *k, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "128x", "64x" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int phase28_oversampling_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = (wm_get(ice, WM_MASTER) & 0x8) == + 0x8; + return 0; +} + +static int phase28_oversampling_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int temp, temp2; + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + temp = wm_get(ice, WM_MASTER); + temp2 = temp; + + if (ucontrol->value.enumerated.item[0]) + temp |= 0x8; + else + temp &= ~0x8; + + if (temp != temp2) { + wm_put(ice, WM_MASTER, temp); + return 1; + } + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_wm_dac, -12700, 100, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_wm_pcm, -6400, 50, 1); + +static const struct snd_kcontrol_new phase28_dac_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = wm_master_mute_info, + .get = wm_master_mute_get, + .put = wm_master_mute_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Master Playback Volume", + .info = wm_master_vol_info, + .get = wm_master_vol_get, + .put = wm_master_vol_put, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Front Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (2 << 8) | 0 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Front Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (2 << 8) | 0, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Rear Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (2 << 8) | 2 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Rear Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (2 << 8) | 2, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Center Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (1 << 8) | 4 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Center Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (1 << 8) | 4, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "LFE Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (1 << 8) | 5 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "LFE Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (1 << 8) | 5, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Side Playback Switch", + .info = wm_mute_info, + .get = wm_mute_get, + .put = wm_mute_put, + .private_value = (2 << 8) | 6 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Side Playback Volume", + .info = wm_vol_info, + .get = wm_vol_get, + .put = wm_vol_put, + .private_value = (2 << 8) | 6, + .tlv = { .p = db_scale_wm_dac } + } +}; + +static const struct snd_kcontrol_new wm_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = wm_pcm_mute_info, + .get = wm_pcm_mute_get, + .put = wm_pcm_mute_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "PCM Playback Volume", + .info = wm_pcm_vol_info, + .get = wm_pcm_vol_get, + .put = wm_pcm_vol_put, + .tlv = { .p = db_scale_wm_pcm } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Deemphasis Switch", + .info = phase28_deemp_info, + .get = phase28_deemp_get, + .put = phase28_deemp_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Oversampling", + .info = phase28_oversampling_info, + .get = phase28_oversampling_get, + .put = phase28_oversampling_put + } +}; + +static int phase28_add_controls(struct snd_ice1712 *ice) +{ + unsigned int i, counts; + int err; + + counts = ARRAY_SIZE(phase28_dac_controls); + for (i = 0; i < counts; i++) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&phase28_dac_controls[i], + ice)); + if (err < 0) + return err; + } + + for (i = 0; i < ARRAY_SIZE(wm_controls); i++) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&wm_controls[i], ice)); + if (err < 0) + return err; + } + + return 0; +} + +struct snd_ice1712_card_info snd_vt1724_phase_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_PHASE22, + .name = "Terratec PHASE 22", + .model = "phase22", + .chip_init = phase22_init, + .build_controls = phase22_add_controls, + .eeprom_size = sizeof(phase22_eeprom), + .eeprom_data = phase22_eeprom, + }, + { + .subvendor = VT1724_SUBDEVICE_PHASE28, + .name = "Terratec PHASE 28", + .model = "phase28", + .chip_init = phase28_init, + .build_controls = phase28_add_controls, + .eeprom_size = sizeof(phase28_eeprom), + .eeprom_data = phase28_eeprom, + }, + { + .subvendor = VT1724_SUBDEVICE_TS22, + .name = "Terrasoniq TS22 PCI", + .model = "TS22", + .chip_init = phase22_init, + .build_controls = phase22_add_controls, + .eeprom_size = sizeof(phase22_eeprom), + .eeprom_data = phase22_eeprom, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/phase.h b/sound/pci/ice1712/phase.h new file mode 100644 index 000000000..c019018e2 --- /dev/null +++ b/sound/pci/ice1712/phase.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_PHASE_H +#define __SOUND_PHASE_H + +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for Terratec PHASE 22 + * + * Copyright (c) 2005 Misha Zhilin + */ + +#define PHASE_DEVICE_DESC "{Terratec,Phase 22},"\ + "{Terratec,Phase 28},"\ + "{Terrasoniq,TS22}," + +#define VT1724_SUBDEVICE_PHASE22 0x3b155011 +#define VT1724_SUBDEVICE_PHASE28 0x3b154911 +#define VT1724_SUBDEVICE_TS22 0x3b157b11 + +/* entry point */ +extern struct snd_ice1712_card_info snd_vt1724_phase_cards[]; + +/* PHASE28 GPIO bits */ +#define PHASE28_SPI_MISO (1 << 21) +#define PHASE28_WM_RESET (1 << 20) +#define PHASE28_SPI_CLK (1 << 19) +#define PHASE28_SPI_MOSI (1 << 18) +#define PHASE28_WM_RW (1 << 17) +#define PHASE28_AC97_RESET (1 << 16) +#define PHASE28_DIGITAL_SEL1 (1 << 15) +#define PHASE28_HP_SEL (1 << 14) +#define PHASE28_WM_CS (1 << 12) +#define PHASE28_AC97_COMMIT (1 << 11) +#define PHASE28_AC97_ADDR (1 << 10) +#define PHASE28_AC97_DATA_LOW (1 << 9) +#define PHASE28_AC97_DATA_HIGH (1 << 8) +#define PHASE28_AC97_DATA_MASK 0xFF +#endif /* __SOUND_PHASE */ diff --git a/sound/pci/ice1712/pontis.c b/sound/pci/ice1712/pontis.c new file mode 100644 index 000000000..b2bd64ece --- /dev/null +++ b/sound/pci/ice1712/pontis.c @@ -0,0 +1,795 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Pontis MS300 + * + * Copyright (c) 2004 Takashi Iwai + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "pontis.h" + +/* I2C addresses */ +#define WM_DEV 0x34 +#define CS_DEV 0x20 + +/* WM8776 registers */ +#define WM_HP_ATTEN_L 0x00 /* headphone left attenuation */ +#define WM_HP_ATTEN_R 0x01 /* headphone left attenuation */ +#define WM_HP_MASTER 0x02 /* headphone master (both channels) */ + /* override LLR */ +#define WM_DAC_ATTEN_L 0x03 /* digital left attenuation */ +#define WM_DAC_ATTEN_R 0x04 +#define WM_DAC_MASTER 0x05 +#define WM_PHASE_SWAP 0x06 /* DAC phase swap */ +#define WM_DAC_CTRL1 0x07 +#define WM_DAC_MUTE 0x08 +#define WM_DAC_CTRL2 0x09 +#define WM_DAC_INT 0x0a +#define WM_ADC_INT 0x0b +#define WM_MASTER_CTRL 0x0c +#define WM_POWERDOWN 0x0d +#define WM_ADC_ATTEN_L 0x0e +#define WM_ADC_ATTEN_R 0x0f +#define WM_ALC_CTRL1 0x10 +#define WM_ALC_CTRL2 0x11 +#define WM_ALC_CTRL3 0x12 +#define WM_NOISE_GATE 0x13 +#define WM_LIMITER 0x14 +#define WM_ADC_MUX 0x15 +#define WM_OUT_MUX 0x16 +#define WM_RESET 0x17 + +/* + * GPIO + */ +#define PONTIS_CS_CS (1<<4) /* CS */ +#define PONTIS_CS_CLK (1<<5) /* CLK */ +#define PONTIS_CS_RDATA (1<<6) /* CS8416 -> VT1720 */ +#define PONTIS_CS_WDATA (1<<7) /* VT1720 -> CS8416 */ + + +/* + * get the current register value of WM codec + */ +static unsigned short wm_get(struct snd_ice1712 *ice, int reg) +{ + reg <<= 1; + return ((unsigned short)ice->akm[0].images[reg] << 8) | + ice->akm[0].images[reg + 1]; +} + +/* + * set the register value of WM codec and remember it + */ +static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + unsigned short cval; + cval = (reg << 9) | val; + snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff); +} + +static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + wm_put_nocache(ice, reg, val); + reg <<= 1; + ice->akm[0].images[reg] = val >> 8; + ice->akm[0].images[reg + 1] = val; +} + +/* + * DAC volume attenuation mixer control (-64dB to 0dB) + */ + +#define DAC_0dB 0xff +#define DAC_RES 128 +#define DAC_MIN (DAC_0dB - DAC_RES) + +static int wm_dac_vol_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; /* mute */ + uinfo->value.integer.max = DAC_RES; /* 0dB, 0.5dB step */ + return 0; +} + +static int wm_dac_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val; + int i; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + val = wm_get(ice, WM_DAC_ATTEN_L + i) & 0xff; + val = val > DAC_MIN ? (val - DAC_MIN) : 0; + ucontrol->value.integer.value[i] = val; + } + return 0; +} + +static int wm_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short oval, nval; + int i, idx, change = 0; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + nval = ucontrol->value.integer.value[i]; + nval = (nval ? (nval + DAC_MIN) : 0) & 0xff; + idx = WM_DAC_ATTEN_L + i; + oval = wm_get(ice, idx) & 0xff; + if (oval != nval) { + wm_put(ice, idx, nval); + wm_put_nocache(ice, idx, nval | 0x100); + change = 1; + } + } + return change; +} + +/* + * ADC gain mixer control (-64dB to 0dB) + */ + +#define ADC_0dB 0xcf +#define ADC_RES 128 +#define ADC_MIN (ADC_0dB - ADC_RES) + +static int wm_adc_vol_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; /* mute (-64dB) */ + uinfo->value.integer.max = ADC_RES; /* 0dB, 0.5dB step */ + return 0; +} + +static int wm_adc_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val; + int i; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + val = wm_get(ice, WM_ADC_ATTEN_L + i) & 0xff; + val = val > ADC_MIN ? (val - ADC_MIN) : 0; + ucontrol->value.integer.value[i] = val; + } + return 0; +} + +static int wm_adc_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short ovol, nvol; + int i, idx, change = 0; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + nvol = ucontrol->value.integer.value[i]; + nvol = nvol ? (nvol + ADC_MIN) : 0; + idx = WM_ADC_ATTEN_L + i; + ovol = wm_get(ice, idx) & 0xff; + if (ovol != nvol) { + wm_put(ice, idx, nvol); + change = 1; + } + } + return change; +} + +/* + * ADC input mux mixer control + */ +#define wm_adc_mux_info snd_ctl_boolean_mono_info + +static int wm_adc_mux_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int bit = kcontrol->private_value; + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.integer.value[0] = (wm_get(ice, WM_ADC_MUX) & (1 << bit)) ? 1 : 0; + return 0; +} + +static int wm_adc_mux_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int bit = kcontrol->private_value; + unsigned short oval, nval; + int change; + + guard(mutex)(&ice->gpio_mutex); + nval = oval = wm_get(ice, WM_ADC_MUX); + if (ucontrol->value.integer.value[0]) + nval |= (1 << bit); + else + nval &= ~(1 << bit); + change = nval != oval; + if (change) { + wm_put(ice, WM_ADC_MUX, nval); + } + return change; +} + +/* + * Analog bypass (In -> Out) + */ +#define wm_bypass_info snd_ctl_boolean_mono_info + +static int wm_bypass_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.integer.value[0] = (wm_get(ice, WM_OUT_MUX) & 0x04) ? 1 : 0; + return 0; +} + +static int wm_bypass_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val, oval; + int change = 0; + + guard(mutex)(&ice->gpio_mutex); + val = oval = wm_get(ice, WM_OUT_MUX); + if (ucontrol->value.integer.value[0]) + val |= 0x04; + else + val &= ~0x04; + if (val != oval) { + wm_put(ice, WM_OUT_MUX, val); + change = 1; + } + return change; +} + +/* + * Left/Right swap + */ +#define wm_chswap_info snd_ctl_boolean_mono_info + +static int wm_chswap_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL1) & 0xf0) != 0x90; + return 0; +} + +static int wm_chswap_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val, oval; + int change = 0; + + guard(mutex)(&ice->gpio_mutex); + oval = wm_get(ice, WM_DAC_CTRL1); + val = oval & 0x0f; + if (ucontrol->value.integer.value[0]) + val |= 0x60; + else + val |= 0x90; + if (val != oval) { + wm_put(ice, WM_DAC_CTRL1, val); + wm_put_nocache(ice, WM_DAC_CTRL1, val); + change = 1; + } + return change; +} + +/* + * write data in the SPI mode + */ +static void set_gpio_bit(struct snd_ice1712 *ice, unsigned int bit, int val) +{ + unsigned int tmp = snd_ice1712_gpio_read(ice); + if (val) + tmp |= bit; + else + tmp &= ~bit; + snd_ice1712_gpio_write(ice, tmp); +} + +static void spi_send_byte(struct snd_ice1712 *ice, unsigned char data) +{ + int i; + for (i = 0; i < 8; i++) { + set_gpio_bit(ice, PONTIS_CS_CLK, 0); + udelay(1); + set_gpio_bit(ice, PONTIS_CS_WDATA, data & 0x80); + udelay(1); + set_gpio_bit(ice, PONTIS_CS_CLK, 1); + udelay(1); + data <<= 1; + } +} + +static unsigned int spi_read_byte(struct snd_ice1712 *ice) +{ + int i; + unsigned int val = 0; + + for (i = 0; i < 8; i++) { + val <<= 1; + set_gpio_bit(ice, PONTIS_CS_CLK, 0); + udelay(1); + if (snd_ice1712_gpio_read(ice) & PONTIS_CS_RDATA) + val |= 1; + udelay(1); + set_gpio_bit(ice, PONTIS_CS_CLK, 1); + udelay(1); + } + return val; +} + + +static void spi_write(struct snd_ice1712 *ice, unsigned int dev, unsigned int reg, unsigned int data) +{ + snd_ice1712_gpio_set_dir(ice, PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK); + snd_ice1712_gpio_set_mask(ice, ~(PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK)); + set_gpio_bit(ice, PONTIS_CS_CS, 0); + spi_send_byte(ice, dev & ~1); /* WRITE */ + spi_send_byte(ice, reg); /* MAP */ + spi_send_byte(ice, data); /* DATA */ + /* trigger */ + set_gpio_bit(ice, PONTIS_CS_CS, 1); + udelay(1); + /* restore */ + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); +} + +static unsigned int spi_read(struct snd_ice1712 *ice, unsigned int dev, unsigned int reg) +{ + unsigned int val; + snd_ice1712_gpio_set_dir(ice, PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK); + snd_ice1712_gpio_set_mask(ice, ~(PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK)); + set_gpio_bit(ice, PONTIS_CS_CS, 0); + spi_send_byte(ice, dev & ~1); /* WRITE */ + spi_send_byte(ice, reg); /* MAP */ + /* trigger */ + set_gpio_bit(ice, PONTIS_CS_CS, 1); + udelay(1); + set_gpio_bit(ice, PONTIS_CS_CS, 0); + spi_send_byte(ice, dev | 1); /* READ */ + val = spi_read_byte(ice); + /* trigger */ + set_gpio_bit(ice, PONTIS_CS_CS, 1); + udelay(1); + /* restore */ + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); + return val; +} + + +/* + * SPDIF input source + */ +static int cs_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "Coax", /* RXP0 */ + "Optical", /* RXP1 */ + "CD", /* RXP2 */ + }; + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int cs_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.enumerated.item[0] = ice->gpio.saved[0]; + return 0; +} + +static int cs_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char val; + int change = 0; + + guard(mutex)(&ice->gpio_mutex); + if (ucontrol->value.enumerated.item[0] != ice->gpio.saved[0]) { + ice->gpio.saved[0] = ucontrol->value.enumerated.item[0] & 3; + val = 0x80 | (ice->gpio.saved[0] << 3); + spi_write(ice, CS_DEV, 0x04, val); + change = 1; + } + return change; +} + + +/* + * GPIO controls + */ +static int pontis_gpio_mask_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 = 0xffff; /* 16bit */ + return 0; +} + +static int pontis_gpio_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + /* 4-7 reserved */ + ucontrol->value.integer.value[0] = (~ice->gpio.write_mask & 0xffff) | 0x00f0; + return 0; +} + +static int pontis_gpio_mask_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val; + int changed; + + guard(mutex)(&ice->gpio_mutex); + /* 4-7 reserved */ + val = (~ucontrol->value.integer.value[0] & 0xffff) | 0x00f0; + changed = val != ice->gpio.write_mask; + ice->gpio.write_mask = val; + return changed; +} + +static int pontis_gpio_dir_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + /* 4-7 reserved */ + ucontrol->value.integer.value[0] = ice->gpio.direction & 0xff0f; + return 0; +} + +static int pontis_gpio_dir_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val; + int changed; + + guard(mutex)(&ice->gpio_mutex); + /* 4-7 reserved */ + val = ucontrol->value.integer.value[0] & 0xff0f; + changed = (val != ice->gpio.direction); + ice->gpio.direction = val; + return changed; +} + +static int pontis_gpio_data_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask); + ucontrol->value.integer.value[0] = snd_ice1712_gpio_read(ice) & 0xffff; + return 0; +} + +static int pontis_gpio_data_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val, nval; + int changed = 0; + + guard(mutex)(&ice->gpio_mutex); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask); + val = snd_ice1712_gpio_read(ice) & 0xffff; + nval = ucontrol->value.integer.value[0] & 0xffff; + if (val != nval) { + snd_ice1712_gpio_write(ice, nval); + changed = 1; + } + return changed; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_volume, -6400, 50, 1); + +/* + * mixers + */ + +static const struct snd_kcontrol_new pontis_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "PCM Playback Volume", + .info = wm_dac_vol_info, + .get = wm_dac_vol_get, + .put = wm_dac_vol_put, + .tlv = { .p = db_scale_volume }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Capture Volume", + .info = wm_adc_vol_info, + .get = wm_adc_vol_get, + .put = wm_adc_vol_put, + .tlv = { .p = db_scale_volume }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "CD Capture Switch", + .info = wm_adc_mux_info, + .get = wm_adc_mux_get, + .put = wm_adc_mux_put, + .private_value = 0, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Capture Switch", + .info = wm_adc_mux_info, + .get = wm_adc_mux_get, + .put = wm_adc_mux_put, + .private_value = 1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Bypass Switch", + .info = wm_bypass_info, + .get = wm_bypass_get, + .put = wm_bypass_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Swap Output Channels", + .info = wm_chswap_info, + .get = wm_chswap_get, + .put = wm_chswap_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Input Source", + .info = cs_source_info, + .get = cs_source_get, + .put = cs_source_put, + }, + /* FIXME: which interface? */ + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "GPIO Mask", + .info = pontis_gpio_mask_info, + .get = pontis_gpio_mask_get, + .put = pontis_gpio_mask_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "GPIO Direction", + .info = pontis_gpio_mask_info, + .get = pontis_gpio_dir_get, + .put = pontis_gpio_dir_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "GPIO Data", + .info = pontis_gpio_mask_info, + .get = pontis_gpio_data_get, + .put = pontis_gpio_data_put, + }, +}; + + +/* + * WM codec registers + */ +static void wm_proc_regs_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_ice1712 *ice = entry->private_data; + char line[64]; + unsigned int reg, val; + + guard(mutex)(&ice->gpio_mutex); + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x", ®, &val) != 2) + continue; + if (reg <= 0x17 && val <= 0xffff) + wm_put(ice, reg, val); + } +} + +static void wm_proc_regs_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_ice1712 *ice = entry->private_data; + int reg, val; + + guard(mutex)(&ice->gpio_mutex); + for (reg = 0; reg <= 0x17; reg++) { + val = wm_get(ice, reg); + snd_iprintf(buffer, "%02x = %04x\n", reg, val); + } +} + +static void wm_proc_init(struct snd_ice1712 *ice) +{ + snd_card_rw_proc_new(ice->card, "wm_codec", ice, wm_proc_regs_read, + wm_proc_regs_write); +} + +static void cs_proc_regs_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_ice1712 *ice = entry->private_data; + int reg, val; + + guard(mutex)(&ice->gpio_mutex); + for (reg = 0; reg <= 0x26; reg++) { + val = spi_read(ice, CS_DEV, reg); + snd_iprintf(buffer, "%02x = %02x\n", reg, val); + } + val = spi_read(ice, CS_DEV, 0x7f); + snd_iprintf(buffer, "%02x = %02x\n", 0x7f, val); +} + +static void cs_proc_init(struct snd_ice1712 *ice) +{ + snd_card_ro_proc_new(ice->card, "cs_codec", ice, cs_proc_regs_read); +} + + +static int pontis_add_controls(struct snd_ice1712 *ice) +{ + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(pontis_controls); i++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&pontis_controls[i], ice)); + if (err < 0) + return err; + } + + wm_proc_init(ice); + cs_proc_init(ice); + + return 0; +} + + +/* + * initialize the chip + */ +static int pontis_init(struct snd_ice1712 *ice) +{ + static const unsigned short wm_inits[] = { + /* These come first to reduce init pop noise */ + WM_ADC_MUX, 0x00c0, /* ADC mute */ + WM_DAC_MUTE, 0x0001, /* DAC softmute */ + WM_DAC_CTRL1, 0x0000, /* DAC mute */ + + WM_POWERDOWN, 0x0008, /* All power-up except HP */ + WM_RESET, 0x0000, /* reset */ + }; + static const unsigned short wm_inits2[] = { + WM_MASTER_CTRL, 0x0022, /* 256fs, slave mode */ + WM_DAC_INT, 0x0022, /* I2S, normal polarity, 24bit */ + WM_ADC_INT, 0x0022, /* I2S, normal polarity, 24bit */ + WM_DAC_CTRL1, 0x0090, /* DAC L/R */ + WM_OUT_MUX, 0x0001, /* OUT DAC */ + WM_HP_ATTEN_L, 0x0179, /* HP 0dB */ + WM_HP_ATTEN_R, 0x0179, /* HP 0dB */ + WM_DAC_ATTEN_L, 0x0000, /* DAC 0dB */ + WM_DAC_ATTEN_L, 0x0100, /* DAC 0dB */ + WM_DAC_ATTEN_R, 0x0000, /* DAC 0dB */ + WM_DAC_ATTEN_R, 0x0100, /* DAC 0dB */ + /* WM_DAC_MASTER, 0x0100, */ /* DAC master muted */ + WM_PHASE_SWAP, 0x0000, /* phase normal */ + WM_DAC_CTRL2, 0x0000, /* no deemphasis, no ZFLG */ + WM_ADC_ATTEN_L, 0x0000, /* ADC muted */ + WM_ADC_ATTEN_R, 0x0000, /* ADC muted */ +#if 0 + WM_ALC_CTRL1, 0x007b, /* */ + WM_ALC_CTRL2, 0x0000, /* */ + WM_ALC_CTRL3, 0x0000, /* */ + WM_NOISE_GATE, 0x0000, /* */ +#endif + WM_DAC_MUTE, 0x0000, /* DAC unmute */ + WM_ADC_MUX, 0x0003, /* ADC unmute, both CD/Line On */ + }; + static const unsigned char cs_inits[] = { + 0x04, 0x80, /* RUN, RXP0 */ + 0x05, 0x05, /* slave, 24bit */ + 0x01, 0x00, + 0x02, 0x00, + 0x03, 0x00, + }; + unsigned int i; + + ice->vt1720 = 1; + ice->num_total_dacs = 2; + ice->num_total_adcs = 2; + + /* to remember the register values */ + ice->akm = kzalloc_obj(struct snd_akm4xxx); + if (! ice->akm) + return -ENOMEM; + ice->akm_codecs = 1; + + /* HACK - use this as the SPDIF source. + * don't call snd_ice1712_gpio_get/put(), otherwise it's overwritten + */ + ice->gpio.saved[0] = 0; + + /* initialize WM8776 codec */ + for (i = 0; i < ARRAY_SIZE(wm_inits); i += 2) + wm_put(ice, wm_inits[i], wm_inits[i+1]); + schedule_timeout_uninterruptible(1); + for (i = 0; i < ARRAY_SIZE(wm_inits2); i += 2) + wm_put(ice, wm_inits2[i], wm_inits2[i+1]); + + /* initialize CS8416 codec */ + /* assert PRST#; MT05 bit 7 */ + outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD)); + mdelay(5); + /* deassert PRST# */ + outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD)); + + for (i = 0; i < ARRAY_SIZE(cs_inits); i += 2) + spi_write(ice, CS_DEV, cs_inits[i], cs_inits[i+1]); + + return 0; +} + + +/* + * Pontis boards don't provide the EEPROM data at all. + * hence the driver needs to sets up it properly. + */ + +static const unsigned char pontis_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x08, /* clock 256, mpu401, spdif-in/ADC, 1DAC */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xf8, /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + [ICE_EEP2_GPIO_DIR] = 0x07, + [ICE_EEP2_GPIO_DIR1] = 0x00, + [ICE_EEP2_GPIO_DIR2] = 0x00, /* ignored */ + [ICE_EEP2_GPIO_MASK] = 0x0f, /* 4-7 reserved for CS8416 */ + [ICE_EEP2_GPIO_MASK1] = 0xff, + [ICE_EEP2_GPIO_MASK2] = 0x00, /* ignored */ + [ICE_EEP2_GPIO_STATE] = 0x06, /* 0-low, 1-high, 2-high */ + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, /* ignored */ +}; + +/* entry point */ +struct snd_ice1712_card_info snd_vt1720_pontis_cards[] = { + { + .subvendor = VT1720_SUBDEVICE_PONTIS_MS300, + .name = "Pontis MS300", + .model = "ms300", + .chip_init = pontis_init, + .build_controls = pontis_add_controls, + .eeprom_size = sizeof(pontis_eeprom), + .eeprom_data = pontis_eeprom, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/pontis.h b/sound/pci/ice1712/pontis.h new file mode 100644 index 000000000..bba01aeac --- /dev/null +++ b/sound/pci/ice1712/pontis.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_PONTIS_H +#define __SOUND_PONTIS_H + +/* + * ALSA driver for VIA VT1724 (Envy24HT) + * + * Lowlevel functions for Pontis MS300 boards + * + * Copyright (c) 2004 Takashi Iwai + */ + +#define PONTIS_DEVICE_DESC "{Pontis,MS300}," + +#define VT1720_SUBDEVICE_PONTIS_MS300 0x00020002 /* a dummy id for MS300 */ + +extern struct snd_ice1712_card_info snd_vt1720_pontis_cards[]; + +#endif /* __SOUND_PONTIS_H */ diff --git a/sound/pci/ice1712/prodigy192.c b/sound/pci/ice1712/prodigy192.c new file mode 100644 index 000000000..6c4783b9a --- /dev/null +++ b/sound/pci/ice1712/prodigy192.c @@ -0,0 +1,783 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for AudioTrak Prodigy 192 cards + * Supported IEC958 input from optional MI/ODI/O add-on card. + * + * Specifics (SW, HW): + * ------------------- + * * 49.5MHz crystal + * * SPDIF-OUT on the card: + * - coax (through isolation transformer)/toslink supplied by + * 74HC04 gates - 3 in parallel + * - output switched between on-board CD drive dig-out connector + * and ice1724 SPDTX pin, using 74HC02 NOR gates, controlled + * by GPIO20 (0 = CD dig-out, 1 = SPDTX) + * * SPDTX goes straight to MI/ODI/O card's SPDIF-OUT coax + * + * * MI/ODI/O card: AK4114 based, used for iec958 input only + * - toslink input -> RX0 + * - coax input -> RX1 + * - 4wire protocol: + * AK4114 ICE1724 + * ------------------------------ + * CDTO (pin 32) -- GPIO11 pin 86 + * CDTI (pin 33) -- GPIO10 pin 77 + * CCLK (pin 34) -- GPIO9 pin 76 + * CSN (pin 35) -- GPIO8 pin 75 + * - output data Mode 7 (24bit, I2S, slave) + * - both MCKO1 and MCKO2 of ak4114 are fed to FPGA, which + * outputs master clock to SPMCLKIN of ice1724. + * Experimentally I found out that only a combination of + * OCKS0=1, OCKS1=1 (128fs, 64fs output) and ice1724 - + * VT1724_MT_I2S_MCLK_128X=0 (256fs input) yields correct + * sampling rate. That means that the FPGA doubles the + * MCK01 rate. + * + * Copyright (c) 2003 Takashi Iwai + * Copyright (c) 2003 Dimitromanolakis Apostolos + * Copyright (c) 2004 Kouichi ONO + */ + +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "prodigy192.h" +#include "stac946x.h" +#include + +struct prodigy192_spec { + struct ak4114 *ak4114; + /* rate change needs atomic mute/unmute of all dacs*/ + struct mutex mute_mutex; +}; + +static inline void stac9460_put(struct snd_ice1712 *ice, int reg, unsigned char val) +{ + snd_vt1724_write_i2c(ice, PRODIGY192_STAC9460_ADDR, reg, val); +} + +static inline unsigned char stac9460_get(struct snd_ice1712 *ice, int reg) +{ + return snd_vt1724_read_i2c(ice, PRODIGY192_STAC9460_ADDR, reg); +} + +/* + * DAC mute control + */ + +/* + * idx = STAC9460 volume register number, mute: 0 = mute, 1 = unmute + */ +static int stac9460_dac_mute(struct snd_ice1712 *ice, int idx, + unsigned char mute) +{ + unsigned char new, old; + int change; + old = stac9460_get(ice, idx); + new = (~mute << 7 & 0x80) | (old & ~0x80); + change = (new != old); + if (change) + /* dev_dbg(ice->card->dev, "Volume register 0x%02x: 0x%02x\n", idx, new);*/ + stac9460_put(ice, idx, new); + return change; +} + +#define stac9460_dac_mute_info snd_ctl_boolean_mono_info + +static int stac9460_dac_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char val; + int idx; + + if (kcontrol->private_value) + idx = STAC946X_MASTER_VOLUME; + else + idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME; + val = stac9460_get(ice, idx); + ucontrol->value.integer.value[0] = (~val >> 7) & 0x1; + return 0; +} + +static int stac9460_dac_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy192_spec *spec = ice->spec; + int idx; + + if (kcontrol->private_value) + idx = STAC946X_MASTER_VOLUME; + else + idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME; + /* due to possible conflicts with stac9460_set_rate_val, mutexing */ + guard(mutex)(&spec->mute_mutex); + /* + dev_dbg(ice->card->dev, "Mute put: reg 0x%02x, ctrl value: 0x%02x\n", idx, + ucontrol->value.integer.value[0]); + */ + return stac9460_dac_mute(ice, idx, ucontrol->value.integer.value[0]); +} + +/* + * DAC volume attenuation mixer control + */ +static int stac9460_dac_vol_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; /* mute */ + uinfo->value.integer.max = 0x7f; /* 0dB */ + return 0; +} + +static int stac9460_dac_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx; + unsigned char vol; + + if (kcontrol->private_value) + idx = STAC946X_MASTER_VOLUME; + else + idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME; + vol = stac9460_get(ice, idx) & 0x7f; + ucontrol->value.integer.value[0] = 0x7f - vol; + + return 0; +} + +static int stac9460_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx; + unsigned char tmp, ovol, nvol; + int change; + + if (kcontrol->private_value) + idx = STAC946X_MASTER_VOLUME; + else + idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME; + nvol = ucontrol->value.integer.value[0]; + tmp = stac9460_get(ice, idx); + ovol = 0x7f - (tmp & 0x7f); + change = (ovol != nvol); + if (change) + stac9460_put(ice, idx, (0x7f - nvol) | (tmp & 0x80)); + + return change; +} + +/* + * ADC mute control + */ +#define stac9460_adc_mute_info snd_ctl_boolean_stereo_info + +static int stac9460_adc_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char val; + int i; + + for (i = 0; i < 2; ++i) { + val = stac9460_get(ice, STAC946X_MIC_L_VOLUME + i); + ucontrol->value.integer.value[i] = ~val>>7 & 0x1; + } + + return 0; +} + +static int stac9460_adc_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char new, old; + int i, reg; + int change; + + for (i = 0; i < 2; ++i) { + reg = STAC946X_MIC_L_VOLUME + i; + old = stac9460_get(ice, reg); + new = (~ucontrol->value.integer.value[i]<<7&0x80) | (old&~0x80); + change = (new != old); + if (change) + stac9460_put(ice, reg, new); + } + + return change; +} + +/* + * ADC gain mixer control + */ +static int stac9460_adc_vol_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; /* 0dB */ + uinfo->value.integer.max = 0x0f; /* 22.5dB */ + return 0; +} + +static int stac9460_adc_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int i, reg; + unsigned char vol; + + for (i = 0; i < 2; ++i) { + reg = STAC946X_MIC_L_VOLUME + i; + vol = stac9460_get(ice, reg) & 0x0f; + ucontrol->value.integer.value[i] = 0x0f - vol; + } + + return 0; +} + +static int stac9460_adc_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int i, reg; + unsigned char ovol, nvol; + int change; + + for (i = 0; i < 2; ++i) { + reg = STAC946X_MIC_L_VOLUME + i; + nvol = ucontrol->value.integer.value[i] & 0x0f; + ovol = 0x0f - stac9460_get(ice, reg); + change = ((ovol & 0x0f) != nvol); + if (change) + stac9460_put(ice, reg, (0x0f - nvol) | (ovol & ~0x0f)); + } + + return change; +} + +static int stac9460_mic_sw_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "Line In", "Mic" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + + +static int stac9460_mic_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char val; + + val = stac9460_get(ice, STAC946X_GENERAL_PURPOSE); + ucontrol->value.enumerated.item[0] = (val >> 7) & 0x1; + return 0; +} + +static int stac9460_mic_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char new, old; + int change; + old = stac9460_get(ice, STAC946X_GENERAL_PURPOSE); + new = (ucontrol->value.enumerated.item[0] << 7 & 0x80) | (old & ~0x80); + change = (new != old); + if (change) + stac9460_put(ice, STAC946X_GENERAL_PURPOSE, new); + return change; +} +/* + * Handler for setting correct codec rate - called when rate change is detected + */ +static void stac9460_set_rate_val(struct snd_ice1712 *ice, unsigned int rate) +{ + unsigned char old, new; + int idx; + unsigned char changed[7]; + struct prodigy192_spec *spec = ice->spec; + + if (rate == 0) /* no hint - S/PDIF input is master, simply return */ + return; + else if (rate <= 48000) + new = 0x08; /* 256x, base rate mode */ + else if (rate <= 96000) + new = 0x11; /* 256x, mid rate mode */ + else + new = 0x12; /* 128x, high rate mode */ + old = stac9460_get(ice, STAC946X_MASTER_CLOCKING); + if (old == new) + return; + /* change detected, setting master clock, muting first */ + /* due to possible conflicts with mute controls - mutexing */ + guard(mutex)(&spec->mute_mutex); + /* we have to remember current mute status for each DAC */ + for (idx = 0; idx < 7 ; ++idx) + changed[idx] = stac9460_dac_mute(ice, + STAC946X_MASTER_VOLUME + idx, 0); + /*dev_dbg(ice->card->dev, "Rate change: %d, new MC: 0x%02x\n", rate, new);*/ + stac9460_put(ice, STAC946X_MASTER_CLOCKING, new); + udelay(10); + /* unmuting - only originally unmuted dacs - + * i.e. those changed when muting */ + for (idx = 0; idx < 7 ; ++idx) { + if (changed[idx]) + stac9460_dac_mute(ice, STAC946X_MASTER_VOLUME + idx, 1); + } +} + + +static const DECLARE_TLV_DB_SCALE(db_scale_dac, -19125, 75, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_adc, 0, 150, 0); + +/* + * mixers + */ + +static const struct snd_kcontrol_new stac_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = stac9460_dac_mute_info, + .get = stac9460_dac_mute_get, + .put = stac9460_dac_mute_put, + .private_value = 1, + .tlv = { .p = db_scale_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Master Playback Volume", + .info = stac9460_dac_vol_info, + .get = stac9460_dac_vol_get, + .put = stac9460_dac_vol_put, + .private_value = 1, + .tlv = { .p = db_scale_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Switch", + .count = 6, + .info = stac9460_dac_mute_info, + .get = stac9460_dac_mute_get, + .put = stac9460_dac_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "DAC Volume", + .count = 6, + .info = stac9460_dac_vol_info, + .get = stac9460_dac_vol_get, + .put = stac9460_dac_vol_put, + .tlv = { .p = db_scale_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Capture Switch", + .count = 1, + .info = stac9460_adc_mute_info, + .get = stac9460_adc_mute_get, + .put = stac9460_adc_mute_put, + + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "ADC Capture Volume", + .count = 1, + .info = stac9460_adc_vol_info, + .get = stac9460_adc_vol_get, + .put = stac9460_adc_vol_put, + .tlv = { .p = db_scale_adc } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Capture Input", + .info = stac9460_mic_sw_info, + .get = stac9460_mic_sw_get, + .put = stac9460_mic_sw_put, + + }, +}; + +/* AK4114 - ICE1724 connections on Prodigy192 + MI/ODI/O */ +/* CDTO (pin 32) -- GPIO11 pin 86 + * CDTI (pin 33) -- GPIO10 pin 77 + * CCLK (pin 34) -- GPIO9 pin 76 + * CSN (pin 35) -- GPIO8 pin 75 + */ +#define AK4114_ADDR 0x00 /* C1-C0: Chip Address + * (According to datasheet fixed to “00”) + */ + +/* + * 4wire ak4114 protocol - writing data + */ +static void write_data(struct snd_ice1712 *ice, unsigned int gpio, + unsigned int data, int idx) +{ + for (; idx >= 0; idx--) { + /* drop clock */ + gpio &= ~VT1724_PRODIGY192_CCLK; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + /* set data */ + if (data & (1 << idx)) + gpio |= VT1724_PRODIGY192_CDOUT; + else + gpio &= ~VT1724_PRODIGY192_CDOUT; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + /* raise clock */ + gpio |= VT1724_PRODIGY192_CCLK; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + } +} + +/* + * 4wire ak4114 protocol - reading data + */ +static unsigned char read_data(struct snd_ice1712 *ice, unsigned int gpio, + int idx) +{ + unsigned char data = 0; + + for (; idx >= 0; idx--) { + /* drop clock */ + gpio &= ~VT1724_PRODIGY192_CCLK; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + /* read data */ + if (snd_ice1712_gpio_read(ice) & VT1724_PRODIGY192_CDIN) + data |= (1 << idx); + udelay(1); + /* raise clock */ + gpio |= VT1724_PRODIGY192_CCLK; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + } + return data; +} +/* + * 4wire ak4114 protocol - starting sequence + */ +static unsigned int prodigy192_4wire_start(struct snd_ice1712 *ice) +{ + unsigned int tmp; + + snd_ice1712_save_gpio_status(ice); + tmp = snd_ice1712_gpio_read(ice); + + tmp |= VT1724_PRODIGY192_CCLK; /* high at init */ + tmp &= ~VT1724_PRODIGY192_CS; /* drop chip select */ + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + return tmp; +} + +/* + * 4wire ak4114 protocol - final sequence + */ +static void prodigy192_4wire_finish(struct snd_ice1712 *ice, unsigned int tmp) +{ + tmp |= VT1724_PRODIGY192_CS; /* raise chip select */ + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + snd_ice1712_restore_gpio_status(ice); +} + +/* + * Write data to addr register of ak4114 + */ +static void prodigy192_ak4114_write(void *private_data, unsigned char addr, + unsigned char data) +{ + struct snd_ice1712 *ice = private_data; + unsigned int tmp, addrdata; + tmp = prodigy192_4wire_start(ice); + addrdata = (AK4114_ADDR << 6) | 0x20 | (addr & 0x1f); + addrdata = (addrdata << 8) | data; + write_data(ice, tmp, addrdata, 15); + prodigy192_4wire_finish(ice, tmp); +} + +/* + * Read data from addr register of ak4114 + */ +static unsigned char prodigy192_ak4114_read(void *private_data, + unsigned char addr) +{ + struct snd_ice1712 *ice = private_data; + unsigned int tmp; + unsigned char data; + + tmp = prodigy192_4wire_start(ice); + write_data(ice, tmp, (AK4114_ADDR << 6) | (addr & 0x1f), 7); + data = read_data(ice, tmp, 7); + prodigy192_4wire_finish(ice, tmp); + return data; +} + + +static int ak4114_input_sw_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "Toslink", "Coax" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + + +static int ak4114_input_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char val; + + val = prodigy192_ak4114_read(ice, AK4114_REG_IO1); + /* AK4114_IPS0 bit = 0 -> RX0 = Toslink + * AK4114_IPS0 bit = 1 -> RX1 = Coax + */ + ucontrol->value.enumerated.item[0] = (val & AK4114_IPS0) ? 1 : 0; + return 0; +} + +static int ak4114_input_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char new, old, itemvalue; + int change; + + old = prodigy192_ak4114_read(ice, AK4114_REG_IO1); + /* AK4114_IPS0 could be any bit */ + itemvalue = (ucontrol->value.enumerated.item[0]) ? 0xff : 0x00; + + new = (itemvalue & AK4114_IPS0) | (old & ~AK4114_IPS0); + change = (new != old); + if (change) + prodigy192_ak4114_write(ice, AK4114_REG_IO1, new); + return change; +} + + +static const struct snd_kcontrol_new ak4114_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "MIODIO IEC958 Capture Input", + .info = ak4114_input_sw_info, + .get = ak4114_input_sw_get, + .put = ak4114_input_sw_put, + + } +}; + + +static int prodigy192_ak4114_init(struct snd_ice1712 *ice) +{ + static const unsigned char ak4114_init_vals[] = { + AK4114_RST | AK4114_PWN | AK4114_OCKS0 | AK4114_OCKS1, + /* ice1724 expects I2S and provides clock, + * DEM0 disables the deemphasis filter + */ + AK4114_DIF_I24I2S | AK4114_DEM0 , + AK4114_TX1E, + AK4114_EFH_1024 | AK4114_DIT, /* default input RX0 */ + 0, + 0 + }; + static const unsigned char ak4114_init_txcsb[] = { + 0x41, 0x02, 0x2c, 0x00, 0x00 + }; + struct prodigy192_spec *spec = ice->spec; + int err; + + err = snd_ak4114_create(ice->card, + prodigy192_ak4114_read, + prodigy192_ak4114_write, + ak4114_init_vals, ak4114_init_txcsb, + ice, &spec->ak4114); + if (err < 0) + return err; + /* AK4114 in Prodigy192 cannot detect external rate correctly. + * No reason to stop capture stream due to incorrect checks */ + spec->ak4114->check_flags = AK4114_CHECK_NO_RATE; + return 0; +} + +static void stac9460_proc_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ice1712 *ice = entry->private_data; + int reg, val; + /* registers 0x0 - 0x14 */ + for (reg = 0; reg <= 0x15; reg++) { + val = stac9460_get(ice, reg); + snd_iprintf(buffer, "0x%02x = 0x%02x\n", reg, val); + } +} + + +static void stac9460_proc_init(struct snd_ice1712 *ice) +{ + snd_card_ro_proc_new(ice->card, "stac9460_codec", ice, + stac9460_proc_regs_read); +} + + +static int prodigy192_add_controls(struct snd_ice1712 *ice) +{ + struct prodigy192_spec *spec = ice->spec; + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(stac_controls); i++) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&stac_controls[i], ice)); + if (err < 0) + return err; + } + if (spec->ak4114) { + /* ak4114 is connected */ + for (i = 0; i < ARRAY_SIZE(ak4114_controls); i++) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&ak4114_controls[i], + ice)); + if (err < 0) + return err; + } + err = snd_ak4114_build(spec->ak4114, + NULL, /* ak4114 in MIO/DI/O handles no IEC958 output */ + ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream); + if (err < 0) + return err; + } + stac9460_proc_init(ice); + return 0; +} + +/* + * check for presence of MI/ODI/O add-on card with digital inputs + */ +static int prodigy192_miodio_exists(struct snd_ice1712 *ice) +{ + + unsigned char orig_value; + const unsigned char test_data = 0xd1; /* random value */ + unsigned char addr = AK4114_REG_INT0_MASK; /* random SAFE address */ + int exists = 0; + + orig_value = prodigy192_ak4114_read(ice, addr); + prodigy192_ak4114_write(ice, addr, test_data); + if (prodigy192_ak4114_read(ice, addr) == test_data) { + /* ak4114 seems to communicate, apparently exists */ + /* writing back original value */ + prodigy192_ak4114_write(ice, addr, orig_value); + exists = 1; + } + return exists; +} + +/* + * initialize the chip + */ +static int prodigy192_init(struct snd_ice1712 *ice) +{ + static const unsigned short stac_inits_prodigy[] = { + STAC946X_RESET, 0, + STAC946X_MASTER_CLOCKING, 0x11, +/* STAC946X_MASTER_VOLUME, 0, + STAC946X_LF_VOLUME, 0, + STAC946X_RF_VOLUME, 0, + STAC946X_LR_VOLUME, 0, + STAC946X_RR_VOLUME, 0, + STAC946X_CENTER_VOLUME, 0, + STAC946X_LFE_VOLUME, 0,*/ + (unsigned short)-1 + }; + const unsigned short *p; + int err = 0; + struct prodigy192_spec *spec; + + /* prodigy 192 */ + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + ice->vt1720 = 0; /* ice1724, e.g. 23 GPIOs */ + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + mutex_init(&spec->mute_mutex); + + /* initialize codec */ + p = stac_inits_prodigy; + for (; *p != (unsigned short)-1; p += 2) + stac9460_put(ice, p[0], p[1]); + ice->gpio.set_pro_rate = stac9460_set_rate_val; + + /* MI/ODI/O add on card with AK4114 */ + if (prodigy192_miodio_exists(ice)) { + err = prodigy192_ak4114_init(ice); + /* from this moment if err = 0 then + * spec->ak4114 should not be null + */ + dev_dbg(ice->card->dev, + "AK4114 initialized with status %d\n", err); + } else + dev_dbg(ice->card->dev, "AK4114 not found\n"); + + return err; +} + + +/* + * Aureon boards don't provide the EEPROM data except for the vendor IDs. + * hence the driver needs to sets up it properly. + */ + +static const unsigned char prodigy71_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x6a, /* 49MHz crystal, mpu401, + * spdif-in+ 1 stereo ADC, + * 3 stereo DACs + */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xf8, /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = ~(VT1724_PRODIGY192_CDIN >> 8) , + [ICE_EEP2_GPIO_DIR2] = 0xbf, + [ICE_EEP2_GPIO_MASK] = 0x00, + [ICE_EEP2_GPIO_MASK1] = 0x00, + [ICE_EEP2_GPIO_MASK2] = 0x00, + [ICE_EEP2_GPIO_STATE] = 0x00, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x10, /* GPIO20: 0 = CD drive dig. input + * passthrough, + * 1 = SPDIF-OUT from ice1724 + */ +}; + + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_prodigy192_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_PRODIGY192VE, + .name = "Audiotrak Prodigy 192", + .model = "prodigy192", + .chip_init = prodigy192_init, + .build_controls = prodigy192_add_controls, + .eeprom_size = sizeof(prodigy71_eeprom), + .eeprom_data = prodigy71_eeprom, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/prodigy192.h b/sound/pci/ice1712/prodigy192.h new file mode 100644 index 000000000..7bfd769ba --- /dev/null +++ b/sound/pci/ice1712/prodigy192.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_PRODIGY192_H +#define __SOUND_PRODIGY192_H + +#define PRODIGY192_DEVICE_DESC "{AudioTrak,Prodigy 192}," +#define PRODIGY192_STAC9460_ADDR 0x54 + +#define VT1724_SUBDEVICE_PRODIGY192VE 0x34495345 /* PRODIGY 192 VE */ +/* + * AudioTrak Prodigy192 GPIO definitions for MI/ODI/O card with + * AK4114 (SPDIF-IN) + */ +#define VT1724_PRODIGY192_CS (1 << 8) /* GPIO8, pin 75 */ +#define VT1724_PRODIGY192_CCLK (1 << 9) /* GPIO9, pin 76 */ +#define VT1724_PRODIGY192_CDOUT (1 << 10) /* GPIO10, pin 77 */ +#define VT1724_PRODIGY192_CDIN (1 << 11) /* GPIO11, pin 86 */ + +extern struct snd_ice1712_card_info snd_vt1724_prodigy192_cards[]; + +#endif /* __SOUND_PRODIGY192_H */ diff --git a/sound/pci/ice1712/prodigy_hifi.c b/sound/pci/ice1712/prodigy_hifi.c new file mode 100644 index 000000000..bb5169a69 --- /dev/null +++ b/sound/pci/ice1712/prodigy_hifi.c @@ -0,0 +1,1249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Audiotrak Prodigy 7.1 Hifi + * based on pontis.c + * + * Copyright (c) 2007 Julian Scheel + * Copyright (c) 2007 allank + * Copyright (c) 2004 Takashi Iwai + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "prodigy_hifi.h" + +struct prodigy_hifi_spec { + unsigned short master[2]; + unsigned short vol[8]; +}; + +/* I2C addresses */ +#define WM_DEV 0x34 + +/* WM8776 registers */ +#define WM_HP_ATTEN_L 0x00 /* headphone left attenuation */ +#define WM_HP_ATTEN_R 0x01 /* headphone left attenuation */ +#define WM_HP_MASTER 0x02 /* headphone master (both channels), + override LLR */ +#define WM_DAC_ATTEN_L 0x03 /* digital left attenuation */ +#define WM_DAC_ATTEN_R 0x04 +#define WM_DAC_MASTER 0x05 +#define WM_PHASE_SWAP 0x06 /* DAC phase swap */ +#define WM_DAC_CTRL1 0x07 +#define WM_DAC_MUTE 0x08 +#define WM_DAC_CTRL2 0x09 +#define WM_DAC_INT 0x0a +#define WM_ADC_INT 0x0b +#define WM_MASTER_CTRL 0x0c +#define WM_POWERDOWN 0x0d +#define WM_ADC_ATTEN_L 0x0e +#define WM_ADC_ATTEN_R 0x0f +#define WM_ALC_CTRL1 0x10 +#define WM_ALC_CTRL2 0x11 +#define WM_ALC_CTRL3 0x12 +#define WM_NOISE_GATE 0x13 +#define WM_LIMITER 0x14 +#define WM_ADC_MUX 0x15 +#define WM_OUT_MUX 0x16 +#define WM_RESET 0x17 + +/* Analog Recording Source :- Mic, LineIn, CD/Video, */ + +/* implement capture source select control for WM8776 */ + +#define WM_AIN1 "AIN1" +#define WM_AIN2 "AIN2" +#define WM_AIN3 "AIN3" +#define WM_AIN4 "AIN4" +#define WM_AIN5 "AIN5" + +/* GPIO pins of envy24ht connected to wm8766 */ +#define WM8766_SPI_CLK (1<<17) /* CLK, Pin97 on ICE1724 */ +#define WM8766_SPI_MD (1<<16) /* DATA VT1724 -> WM8766, Pin96 */ +#define WM8766_SPI_ML (1<<18) /* Latch, Pin98 */ + +/* WM8766 registers */ +#define WM8766_DAC_CTRL 0x02 /* DAC Control */ +#define WM8766_INT_CTRL 0x03 /* Interface Control */ +#define WM8766_DAC_CTRL2 0x09 +#define WM8766_DAC_CTRL3 0x0a +#define WM8766_RESET 0x1f +#define WM8766_LDA1 0x00 +#define WM8766_LDA2 0x04 +#define WM8766_LDA3 0x06 +#define WM8766_RDA1 0x01 +#define WM8766_RDA2 0x05 +#define WM8766_RDA3 0x07 +#define WM8766_MUTE1 0x0C +#define WM8766_MUTE2 0x0F + + +/* + * Prodigy HD2 + */ +#define AK4396_ADDR 0x00 +#define AK4396_CSN (1 << 8) /* CSN->GPIO8, pin 75 */ +#define AK4396_CCLK (1 << 9) /* CCLK->GPIO9, pin 76 */ +#define AK4396_CDTI (1 << 10) /* CDTI->GPIO10, pin 77 */ + +/* ak4396 registers */ +#define AK4396_CTRL1 0x00 +#define AK4396_CTRL2 0x01 +#define AK4396_CTRL3 0x02 +#define AK4396_LCH_ATT 0x03 +#define AK4396_RCH_ATT 0x04 + + +/* + * get the current register value of WM codec + */ +static unsigned short wm_get(struct snd_ice1712 *ice, int reg) +{ + reg <<= 1; + return ((unsigned short)ice->akm[0].images[reg] << 8) | + ice->akm[0].images[reg + 1]; +} + +/* + * set the register value of WM codec and remember it + */ +static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + unsigned short cval; + cval = (reg << 9) | val; + snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff); +} + +static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + wm_put_nocache(ice, reg, val); + reg <<= 1; + ice->akm[0].images[reg] = val >> 8; + ice->akm[0].images[reg + 1] = val; +} + +/* + * write data in the SPI mode + */ + +static void set_gpio_bit(struct snd_ice1712 *ice, unsigned int bit, int val) +{ + unsigned int tmp = snd_ice1712_gpio_read(ice); + if (val) + tmp |= bit; + else + tmp &= ~bit; + snd_ice1712_gpio_write(ice, tmp); +} + +/* + * SPI implementation for WM8766 codec - only writing supported, no readback + */ + +static void wm8766_spi_send_word(struct snd_ice1712 *ice, unsigned int data) +{ + int i; + for (i = 0; i < 16; i++) { + set_gpio_bit(ice, WM8766_SPI_CLK, 0); + udelay(1); + set_gpio_bit(ice, WM8766_SPI_MD, data & 0x8000); + udelay(1); + set_gpio_bit(ice, WM8766_SPI_CLK, 1); + udelay(1); + data <<= 1; + } +} + +static void wm8766_spi_write(struct snd_ice1712 *ice, unsigned int reg, + unsigned int data) +{ + unsigned int block; + + snd_ice1712_gpio_set_dir(ice, WM8766_SPI_MD| + WM8766_SPI_CLK|WM8766_SPI_ML); + snd_ice1712_gpio_set_mask(ice, ~(WM8766_SPI_MD| + WM8766_SPI_CLK|WM8766_SPI_ML)); + /* latch must be low when writing */ + set_gpio_bit(ice, WM8766_SPI_ML, 0); + block = (reg << 9) | (data & 0x1ff); + wm8766_spi_send_word(ice, block); /* REGISTER ADDRESS */ + /* release latch */ + set_gpio_bit(ice, WM8766_SPI_ML, 1); + udelay(1); + /* restore */ + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); +} + + +/* + * serial interface for ak4396 - only writing supported, no readback + */ + +static void ak4396_send_word(struct snd_ice1712 *ice, unsigned int data) +{ + int i; + for (i = 0; i < 16; i++) { + set_gpio_bit(ice, AK4396_CCLK, 0); + udelay(1); + set_gpio_bit(ice, AK4396_CDTI, data & 0x8000); + udelay(1); + set_gpio_bit(ice, AK4396_CCLK, 1); + udelay(1); + data <<= 1; + } +} + +static void ak4396_write(struct snd_ice1712 *ice, unsigned int reg, + unsigned int data) +{ + unsigned int block; + + snd_ice1712_gpio_set_dir(ice, AK4396_CSN|AK4396_CCLK|AK4396_CDTI); + snd_ice1712_gpio_set_mask(ice, ~(AK4396_CSN|AK4396_CCLK|AK4396_CDTI)); + /* latch must be low when writing */ + set_gpio_bit(ice, AK4396_CSN, 0); + block = ((AK4396_ADDR & 0x03) << 14) | (1 << 13) | + ((reg & 0x1f) << 8) | (data & 0xff); + ak4396_send_word(ice, block); /* REGISTER ADDRESS */ + /* release latch */ + set_gpio_bit(ice, AK4396_CSN, 1); + udelay(1); + /* restore */ + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); +} + + +/* + * ak4396 mixers + */ + + + +/* + * DAC volume attenuation mixer control (-64dB to 0dB) + */ + +static int ak4396_dac_vol_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; /* mute */ + uinfo->value.integer.max = 0xFF; /* linear */ + return 0; +} + +static int ak4396_dac_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int i; + + for (i = 0; i < 2; i++) + ucontrol->value.integer.value[i] = spec->vol[i]; + + return 0; +} + +static int ak4396_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int i; + int change = 0; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + if (ucontrol->value.integer.value[i] != spec->vol[i]) { + spec->vol[i] = ucontrol->value.integer.value[i]; + ak4396_write(ice, AK4396_LCH_ATT + i, + spec->vol[i] & 0xff); + change = 1; + } + } + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_wm_dac, -12700, 100, 1); +static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0); + +static const struct snd_kcontrol_new prodigy_hd2_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Front Playback Volume", + .info = ak4396_dac_vol_info, + .get = ak4396_dac_vol_get, + .put = ak4396_dac_vol_put, + .tlv = { .p = ak4396_db_scale }, + }, +}; + + +/* --------------- */ + +#define WM_VOL_MAX 255 +#define WM_VOL_MUTE 0x8000 + + +#define DAC_0dB 0xff +#define DAC_RES 128 +#define DAC_MIN (DAC_0dB - DAC_RES) + + +static void wm_set_vol(struct snd_ice1712 *ice, unsigned int index, + unsigned short vol, unsigned short master) +{ + unsigned char nvol; + + if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE)) + nvol = 0; + else { + nvol = (((vol & ~WM_VOL_MUTE) * (master & ~WM_VOL_MUTE)) / 128) + & WM_VOL_MAX; + nvol = (nvol ? (nvol + DAC_MIN) : 0) & 0xff; + } + + wm_put(ice, index, nvol); + wm_put_nocache(ice, index, 0x100 | nvol); +} + +static void wm8766_set_vol(struct snd_ice1712 *ice, unsigned int index, + unsigned short vol, unsigned short master) +{ + unsigned char nvol; + + if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE)) + nvol = 0; + else { + nvol = (((vol & ~WM_VOL_MUTE) * (master & ~WM_VOL_MUTE)) / 128) + & WM_VOL_MAX; + nvol = (nvol ? (nvol + DAC_MIN) : 0) & 0xff; + } + + wm8766_spi_write(ice, index, (0x0100 | nvol)); +} + + +/* + * DAC volume attenuation mixer control (-64dB to 0dB) + */ + +static int wm_dac_vol_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; /* mute */ + uinfo->value.integer.max = DAC_RES; /* 0dB, 0.5dB step */ + return 0; +} + +static int wm_dac_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int i; + + for (i = 0; i < 2; i++) + ucontrol->value.integer.value[i] = + spec->vol[2 + i] & ~WM_VOL_MUTE; + return 0; +} + +static int wm_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int i, idx, change = 0; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + if (ucontrol->value.integer.value[i] != spec->vol[2 + i]) { + idx = WM_DAC_ATTEN_L + i; + spec->vol[2 + i] &= WM_VOL_MUTE; + spec->vol[2 + i] |= ucontrol->value.integer.value[i]; + wm_set_vol(ice, idx, spec->vol[2 + i], spec->master[i]); + change = 1; + } + } + return change; +} + + +/* + * WM8766 DAC volume attenuation mixer control + */ +static int wm8766_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int voices = kcontrol->private_value >> 8; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = voices; + uinfo->value.integer.min = 0; /* mute */ + uinfo->value.integer.max = DAC_RES; /* 0dB */ + return 0; +} + +static int wm8766_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int i, ofs, voices; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xff; + for (i = 0; i < voices; i++) + ucontrol->value.integer.value[i] = spec->vol[ofs + i]; + return 0; +} + +static int wm8766_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int i, idx, ofs, voices; + int change = 0; + + voices = kcontrol->private_value >> 8; + ofs = kcontrol->private_value & 0xff; + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < voices; i++) { + if (ucontrol->value.integer.value[i] != spec->vol[ofs + i]) { + idx = WM8766_LDA1 + ofs + i; + spec->vol[ofs + i] &= WM_VOL_MUTE; + spec->vol[ofs + i] |= ucontrol->value.integer.value[i]; + wm8766_set_vol(ice, idx, + spec->vol[ofs + i], spec->master[i]); + change = 1; + } + } + return change; +} + +/* + * Master volume attenuation mixer control / applied to WM8776+WM8766 + */ +static int wm_master_vol_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 = DAC_RES; + return 0; +} + +static int wm_master_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int i; + for (i = 0; i < 2; i++) + ucontrol->value.integer.value[i] = spec->master[i]; + return 0; +} + +static int wm_master_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int ch, change = 0; + + guard(mutex)(&ice->gpio_mutex); + for (ch = 0; ch < 2; ch++) { + if (ucontrol->value.integer.value[ch] != spec->master[ch]) { + spec->master[ch] = ucontrol->value.integer.value[ch]; + + /* Apply to front DAC */ + wm_set_vol(ice, WM_DAC_ATTEN_L + ch, + spec->vol[2 + ch], spec->master[ch]); + + wm8766_set_vol(ice, WM8766_LDA1 + ch, + spec->vol[0 + ch], spec->master[ch]); + + wm8766_set_vol(ice, WM8766_LDA2 + ch, + spec->vol[4 + ch], spec->master[ch]); + + wm8766_set_vol(ice, WM8766_LDA3 + ch, + spec->vol[6 + ch], spec->master[ch]); + change = 1; + } + } + return change; +} + + +/* KONSTI */ + +static int wm_adc_mux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[32] = { + "NULL", WM_AIN1, WM_AIN2, WM_AIN1 "+" WM_AIN2, + WM_AIN3, WM_AIN1 "+" WM_AIN3, WM_AIN2 "+" WM_AIN3, + WM_AIN1 "+" WM_AIN2 "+" WM_AIN3, + WM_AIN4, WM_AIN1 "+" WM_AIN4, WM_AIN2 "+" WM_AIN4, + WM_AIN1 "+" WM_AIN2 "+" WM_AIN4, + WM_AIN3 "+" WM_AIN4, WM_AIN1 "+" WM_AIN3 "+" WM_AIN4, + WM_AIN2 "+" WM_AIN3 "+" WM_AIN4, + WM_AIN1 "+" WM_AIN2 "+" WM_AIN3 "+" WM_AIN4, + WM_AIN5, WM_AIN1 "+" WM_AIN5, WM_AIN2 "+" WM_AIN5, + WM_AIN1 "+" WM_AIN2 "+" WM_AIN5, + WM_AIN3 "+" WM_AIN5, WM_AIN1 "+" WM_AIN3 "+" WM_AIN5, + WM_AIN2 "+" WM_AIN3 "+" WM_AIN5, + WM_AIN1 "+" WM_AIN2 "+" WM_AIN3 "+" WM_AIN5, + WM_AIN4 "+" WM_AIN5, WM_AIN1 "+" WM_AIN4 "+" WM_AIN5, + WM_AIN2 "+" WM_AIN4 "+" WM_AIN5, + WM_AIN1 "+" WM_AIN2 "+" WM_AIN4 "+" WM_AIN5, + WM_AIN3 "+" WM_AIN4 "+" WM_AIN5, + WM_AIN1 "+" WM_AIN3 "+" WM_AIN4 "+" WM_AIN5, + WM_AIN2 "+" WM_AIN3 "+" WM_AIN4 "+" WM_AIN5, + WM_AIN1 "+" WM_AIN2 "+" WM_AIN3 "+" WM_AIN4 "+" WM_AIN5 + }; + + return snd_ctl_enum_info(uinfo, 1, 32, texts); +} + +static int wm_adc_mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.enumerated.item[0] = wm_get(ice, WM_ADC_MUX) & 0x1f; + return 0; +} + +static int wm_adc_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short oval, nval; + int change = 0; + + guard(mutex)(&ice->gpio_mutex); + oval = wm_get(ice, WM_ADC_MUX); + nval = (oval & 0xe0) | ucontrol->value.enumerated.item[0]; + if (nval != oval) { + wm_put(ice, WM_ADC_MUX, nval); + change = 1; + } + return change; +} + +/* KONSTI */ + +/* + * ADC gain mixer control (-64dB to 0dB) + */ + +#define ADC_0dB 0xcf +#define ADC_RES 128 +#define ADC_MIN (ADC_0dB - ADC_RES) + +static int wm_adc_vol_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; /* mute (-64dB) */ + uinfo->value.integer.max = ADC_RES; /* 0dB, 0.5dB step */ + return 0; +} + +static int wm_adc_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val; + int i; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + val = wm_get(ice, WM_ADC_ATTEN_L + i) & 0xff; + val = val > ADC_MIN ? (val - ADC_MIN) : 0; + ucontrol->value.integer.value[i] = val; + } + return 0; +} + +static int wm_adc_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short ovol, nvol; + int i, idx, change = 0; + + guard(mutex)(&ice->gpio_mutex); + for (i = 0; i < 2; i++) { + nvol = ucontrol->value.integer.value[i]; + nvol = nvol ? (nvol + ADC_MIN) : 0; + idx = WM_ADC_ATTEN_L + i; + ovol = wm_get(ice, idx) & 0xff; + if (ovol != nvol) { + wm_put(ice, idx, nvol); + change = 1; + } + } + return change; +} + +/* + * ADC input mux mixer control + */ +#define wm_adc_mux_info snd_ctl_boolean_mono_info + +static int wm_adc_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int bit = kcontrol->private_value; + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.integer.value[0] = + (wm_get(ice, WM_ADC_MUX) & (1 << bit)) ? 1 : 0; + return 0; +} + +static int wm_adc_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int bit = kcontrol->private_value; + unsigned short oval, nval; + int change; + + guard(mutex)(&ice->gpio_mutex); + nval = oval = wm_get(ice, WM_ADC_MUX); + if (ucontrol->value.integer.value[0]) + nval |= (1 << bit); + else + nval &= ~(1 << bit); + change = nval != oval; + if (change) { + wm_put(ice, WM_ADC_MUX, nval); + } + return 0; +} + +/* + * Analog bypass (In -> Out) + */ +#define wm_bypass_info snd_ctl_boolean_mono_info + +static int wm_bypass_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.integer.value[0] = + (wm_get(ice, WM_OUT_MUX) & 0x04) ? 1 : 0; + return 0; +} + +static int wm_bypass_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val, oval; + int change = 0; + + guard(mutex)(&ice->gpio_mutex); + val = oval = wm_get(ice, WM_OUT_MUX); + if (ucontrol->value.integer.value[0]) + val |= 0x04; + else + val &= ~0x04; + if (val != oval) { + wm_put(ice, WM_OUT_MUX, val); + change = 1; + } + return change; +} + +/* + * Left/Right swap + */ +#define wm_chswap_info snd_ctl_boolean_mono_info + +static int wm_chswap_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&ice->gpio_mutex); + ucontrol->value.integer.value[0] = + (wm_get(ice, WM_DAC_CTRL1) & 0xf0) != 0x90; + return 0; +} + +static int wm_chswap_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val, oval; + int change = 0; + + guard(mutex)(&ice->gpio_mutex); + oval = wm_get(ice, WM_DAC_CTRL1); + val = oval & 0x0f; + if (ucontrol->value.integer.value[0]) + val |= 0x60; + else + val |= 0x90; + if (val != oval) { + wm_put(ice, WM_DAC_CTRL1, val); + wm_put_nocache(ice, WM_DAC_CTRL1, val); + change = 1; + } + return change; +} + + +/* + * mixers + */ + +static const struct snd_kcontrol_new prodigy_hifi_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Master Playback Volume", + .info = wm_master_vol_info, + .get = wm_master_vol_get, + .put = wm_master_vol_put, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Front Playback Volume", + .info = wm_dac_vol_info, + .get = wm_dac_vol_get, + .put = wm_dac_vol_put, + .tlv = { .p = db_scale_wm_dac }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Rear Playback Volume", + .info = wm8766_vol_info, + .get = wm8766_vol_get, + .put = wm8766_vol_put, + .private_value = (2 << 8) | 0, + .tlv = { .p = db_scale_wm_dac }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Center Playback Volume", + .info = wm8766_vol_info, + .get = wm8766_vol_get, + .put = wm8766_vol_put, + .private_value = (1 << 8) | 4, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "LFE Playback Volume", + .info = wm8766_vol_info, + .get = wm8766_vol_get, + .put = wm8766_vol_put, + .private_value = (1 << 8) | 5, + .tlv = { .p = db_scale_wm_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Side Playback Volume", + .info = wm8766_vol_info, + .get = wm8766_vol_get, + .put = wm8766_vol_put, + .private_value = (2 << 8) | 6, + .tlv = { .p = db_scale_wm_dac }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Capture Volume", + .info = wm_adc_vol_info, + .get = wm_adc_vol_get, + .put = wm_adc_vol_put, + .tlv = { .p = db_scale_wm_dac }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "CD Capture Switch", + .info = wm_adc_mux_info, + .get = wm_adc_mux_get, + .put = wm_adc_mux_put, + .private_value = 0, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Capture Switch", + .info = wm_adc_mux_info, + .get = wm_adc_mux_get, + .put = wm_adc_mux_put, + .private_value = 1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Bypass Switch", + .info = wm_bypass_info, + .get = wm_bypass_get, + .put = wm_bypass_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Swap Output Channels", + .info = wm_chswap_info, + .get = wm_chswap_get, + .put = wm_chswap_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Capture Source", + .info = wm_adc_mux_enum_info, + .get = wm_adc_mux_enum_get, + .put = wm_adc_mux_enum_put, + }, +}; + +/* + * WM codec registers + */ +static void wm_proc_regs_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ice1712 *ice = entry->private_data; + char line[64]; + unsigned int reg, val; + + guard(mutex)(&ice->gpio_mutex); + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x", ®, &val) != 2) + continue; + if (reg <= 0x17 && val <= 0xffff) + wm_put(ice, reg, val); + } +} + +static void wm_proc_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ice1712 *ice = entry->private_data; + int reg, val; + + guard(mutex)(&ice->gpio_mutex); + for (reg = 0; reg <= 0x17; reg++) { + val = wm_get(ice, reg); + snd_iprintf(buffer, "%02x = %04x\n", reg, val); + } +} + +static void wm_proc_init(struct snd_ice1712 *ice) +{ + snd_card_rw_proc_new(ice->card, "wm_codec", ice, wm_proc_regs_read, + wm_proc_regs_write); +} + +static int prodigy_hifi_add_controls(struct snd_ice1712 *ice) +{ + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(prodigy_hifi_controls); i++) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&prodigy_hifi_controls[i], ice)); + if (err < 0) + return err; + } + + wm_proc_init(ice); + + return 0; +} + +static int prodigy_hd2_add_controls(struct snd_ice1712 *ice) +{ + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(prodigy_hd2_controls); i++) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&prodigy_hd2_controls[i], ice)); + if (err < 0) + return err; + } + + wm_proc_init(ice); + + return 0; +} + +static void wm8766_init(struct snd_ice1712 *ice) +{ + static const unsigned short wm8766_inits[] = { + WM8766_RESET, 0x0000, + WM8766_DAC_CTRL, 0x0120, + WM8766_INT_CTRL, 0x0022, /* I2S Normal Mode, 24 bit */ + WM8766_DAC_CTRL2, 0x0001, + WM8766_DAC_CTRL3, 0x0080, + WM8766_LDA1, 0x0100, + WM8766_LDA2, 0x0100, + WM8766_LDA3, 0x0100, + WM8766_RDA1, 0x0100, + WM8766_RDA2, 0x0100, + WM8766_RDA3, 0x0100, + WM8766_MUTE1, 0x0000, + WM8766_MUTE2, 0x0000, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(wm8766_inits); i += 2) + wm8766_spi_write(ice, wm8766_inits[i], wm8766_inits[i + 1]); +} + +static void wm8776_init(struct snd_ice1712 *ice) +{ + static const unsigned short wm8776_inits[] = { + /* These come first to reduce init pop noise */ + WM_ADC_MUX, 0x0003, /* ADC mute */ + /* 0x00c0 replaced by 0x0003 */ + + WM_DAC_MUTE, 0x0001, /* DAC softmute */ + WM_DAC_CTRL1, 0x0000, /* DAC mute */ + + WM_POWERDOWN, 0x0008, /* All power-up except HP */ + WM_RESET, 0x0000, /* reset */ + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(wm8776_inits); i += 2) + wm_put(ice, wm8776_inits[i], wm8776_inits[i + 1]); +} + +#ifdef CONFIG_PM_SLEEP +static int prodigy_hifi_resume(struct snd_ice1712 *ice) +{ + static const unsigned short wm8776_reinit_registers[] = { + WM_MASTER_CTRL, + WM_DAC_INT, + WM_ADC_INT, + WM_OUT_MUX, + WM_HP_ATTEN_L, + WM_HP_ATTEN_R, + WM_PHASE_SWAP, + WM_DAC_CTRL2, + WM_ADC_ATTEN_L, + WM_ADC_ATTEN_R, + WM_ALC_CTRL1, + WM_ALC_CTRL2, + WM_ALC_CTRL3, + WM_NOISE_GATE, + WM_ADC_MUX, + /* no DAC attenuation here */ + }; + struct prodigy_hifi_spec *spec = ice->spec; + int i, ch; + + guard(mutex)(&ice->gpio_mutex); + + /* reinitialize WM8776 and re-apply old register values */ + wm8776_init(ice); + schedule_timeout_uninterruptible(1); + for (i = 0; i < ARRAY_SIZE(wm8776_reinit_registers); i++) + wm_put(ice, wm8776_reinit_registers[i], + wm_get(ice, wm8776_reinit_registers[i])); + + /* reinitialize WM8766 and re-apply volumes for all DACs */ + wm8766_init(ice); + for (ch = 0; ch < 2; ch++) { + wm_set_vol(ice, WM_DAC_ATTEN_L + ch, + spec->vol[2 + ch], spec->master[ch]); + + wm8766_set_vol(ice, WM8766_LDA1 + ch, + spec->vol[0 + ch], spec->master[ch]); + + wm8766_set_vol(ice, WM8766_LDA2 + ch, + spec->vol[4 + ch], spec->master[ch]); + + wm8766_set_vol(ice, WM8766_LDA3 + ch, + spec->vol[6 + ch], spec->master[ch]); + } + + /* unmute WM8776 DAC */ + wm_put(ice, WM_DAC_MUTE, 0x00); + wm_put(ice, WM_DAC_CTRL1, 0x90); + + return 0; +} +#endif + +/* + * initialize the chip + */ +static int prodigy_hifi_init(struct snd_ice1712 *ice) +{ + static const unsigned short wm8776_defaults[] = { + WM_MASTER_CTRL, 0x0022, /* 256fs, slave mode */ + WM_DAC_INT, 0x0022, /* I2S, normal polarity, 24bit */ + WM_ADC_INT, 0x0022, /* I2S, normal polarity, 24bit */ + WM_DAC_CTRL1, 0x0090, /* DAC L/R */ + WM_OUT_MUX, 0x0001, /* OUT DAC */ + WM_HP_ATTEN_L, 0x0179, /* HP 0dB */ + WM_HP_ATTEN_R, 0x0179, /* HP 0dB */ + WM_DAC_ATTEN_L, 0x0000, /* DAC 0dB */ + WM_DAC_ATTEN_L, 0x0100, /* DAC 0dB */ + WM_DAC_ATTEN_R, 0x0000, /* DAC 0dB */ + WM_DAC_ATTEN_R, 0x0100, /* DAC 0dB */ + WM_PHASE_SWAP, 0x0000, /* phase normal */ +#if 0 + WM_DAC_MASTER, 0x0100, /* DAC master muted */ +#endif + WM_DAC_CTRL2, 0x0000, /* no deemphasis, no ZFLG */ + WM_ADC_ATTEN_L, 0x0000, /* ADC muted */ + WM_ADC_ATTEN_R, 0x0000, /* ADC muted */ +#if 1 + WM_ALC_CTRL1, 0x007b, /* */ + WM_ALC_CTRL2, 0x0000, /* */ + WM_ALC_CTRL3, 0x0000, /* */ + WM_NOISE_GATE, 0x0000, /* */ +#endif + WM_DAC_MUTE, 0x0000, /* DAC unmute */ + WM_ADC_MUX, 0x0003, /* ADC unmute, both CD/Line On */ + }; + struct prodigy_hifi_spec *spec; + unsigned int i; + + ice->vt1720 = 0; + ice->vt1724 = 1; + + ice->num_total_dacs = 8; + ice->num_total_adcs = 1; + + /* HACK - use this as the SPDIF source. + * don't call snd_ice1712_gpio_get/put(), otherwise it's overwritten + */ + ice->gpio.saved[0] = 0; + /* to remember the register values */ + + ice->akm = kzalloc_obj(struct snd_akm4xxx); + if (! ice->akm) + return -ENOMEM; + ice->akm_codecs = 1; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + /* initialize WM8776 codec */ + wm8776_init(ice); + schedule_timeout_uninterruptible(1); + for (i = 0; i < ARRAY_SIZE(wm8776_defaults); i += 2) + wm_put(ice, wm8776_defaults[i], wm8776_defaults[i + 1]); + + wm8766_init(ice); + +#ifdef CONFIG_PM_SLEEP + ice->pm_resume = &prodigy_hifi_resume; + ice->pm_suspend_enabled = 1; +#endif + + return 0; +} + + +/* + * initialize the chip + */ +static void ak4396_init(struct snd_ice1712 *ice) +{ + static const unsigned short ak4396_inits[] = { + AK4396_CTRL1, 0x87, /* I2S Normal Mode, 24 bit */ + AK4396_CTRL2, 0x02, + AK4396_CTRL3, 0x00, + AK4396_LCH_ATT, 0x00, + AK4396_RCH_ATT, 0x00, + }; + + unsigned int i; + + /* initialize ak4396 codec */ + /* reset codec */ + ak4396_write(ice, AK4396_CTRL1, 0x86); + msleep(100); + ak4396_write(ice, AK4396_CTRL1, 0x87); + + for (i = 0; i < ARRAY_SIZE(ak4396_inits); i += 2) + ak4396_write(ice, ak4396_inits[i], ak4396_inits[i+1]); +} + +#ifdef CONFIG_PM_SLEEP +static int prodigy_hd2_resume(struct snd_ice1712 *ice) +{ + /* initialize ak4396 codec and restore previous mixer volumes */ + struct prodigy_hifi_spec *spec = ice->spec; + int i; + + guard(mutex)(&ice->gpio_mutex); + ak4396_init(ice); + for (i = 0; i < 2; i++) + ak4396_write(ice, AK4396_LCH_ATT + i, spec->vol[i] & 0xff); + return 0; +} +#endif + +static int prodigy_hd2_init(struct snd_ice1712 *ice) +{ + struct prodigy_hifi_spec *spec; + + ice->vt1720 = 0; + ice->vt1724 = 1; + + ice->num_total_dacs = 1; + ice->num_total_adcs = 1; + + /* HACK - use this as the SPDIF source. + * don't call snd_ice1712_gpio_get/put(), otherwise it's overwritten + */ + ice->gpio.saved[0] = 0; + /* to remember the register values */ + + ice->akm = kzalloc_obj(struct snd_akm4xxx); + if (! ice->akm) + return -ENOMEM; + ice->akm_codecs = 1; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + +#ifdef CONFIG_PM_SLEEP + ice->pm_resume = &prodigy_hd2_resume; + ice->pm_suspend_enabled = 1; +#endif + + ak4396_init(ice); + + return 0; +} + + +static const unsigned char prodigy71hifi_eeprom[] = { + 0x4b, /* SYSCONF: clock 512, spdif-in/ADC, 4DACs */ + 0x80, /* ACLINK: I2S */ + 0xfc, /* I2S: vol, 96k, 24bit, 192k */ + 0xc3, /* SPDIF: out-en, out-int, spdif-in */ + 0xff, /* GPIO_DIR */ + 0xff, /* GPIO_DIR1 */ + 0x5f, /* GPIO_DIR2 */ + 0x00, /* GPIO_MASK */ + 0x00, /* GPIO_MASK1 */ + 0x00, /* GPIO_MASK2 */ + 0x00, /* GPIO_STATE */ + 0x00, /* GPIO_STATE1 */ + 0x00, /* GPIO_STATE2 */ +}; + +static const unsigned char prodigyhd2_eeprom[] = { + 0x4b, /* SYSCONF: clock 512, spdif-in/ADC, 4DACs */ + 0x80, /* ACLINK: I2S */ + 0xfc, /* I2S: vol, 96k, 24bit, 192k */ + 0xc3, /* SPDIF: out-en, out-int, spdif-in */ + 0xff, /* GPIO_DIR */ + 0xff, /* GPIO_DIR1 */ + 0x5f, /* GPIO_DIR2 */ + 0x00, /* GPIO_MASK */ + 0x00, /* GPIO_MASK1 */ + 0x00, /* GPIO_MASK2 */ + 0x00, /* GPIO_STATE */ + 0x00, /* GPIO_STATE1 */ + 0x00, /* GPIO_STATE2 */ +}; + +static const unsigned char fortissimo4_eeprom[] = { + 0x43, /* SYSCONF: clock 512, ADC, 4DACs */ + 0x80, /* ACLINK: I2S */ + 0xfc, /* I2S: vol, 96k, 24bit, 192k */ + 0xc1, /* SPDIF: out-en, out-int */ + 0xff, /* GPIO_DIR */ + 0xff, /* GPIO_DIR1 */ + 0x5f, /* GPIO_DIR2 */ + 0x00, /* GPIO_MASK */ + 0x00, /* GPIO_MASK1 */ + 0x00, /* GPIO_MASK2 */ + 0x00, /* GPIO_STATE */ + 0x00, /* GPIO_STATE1 */ + 0x00, /* GPIO_STATE2 */ +}; + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_prodigy_hifi_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_PRODIGY_HIFI, + .name = "Audiotrak Prodigy 7.1 HiFi", + .model = "prodigy71hifi", + .chip_init = prodigy_hifi_init, + .build_controls = prodigy_hifi_add_controls, + .eeprom_size = sizeof(prodigy71hifi_eeprom), + .eeprom_data = prodigy71hifi_eeprom, + .driver = "Prodigy71HIFI", + }, + { + .subvendor = VT1724_SUBDEVICE_PRODIGY_HD2, + .name = "Audiotrak Prodigy HD2", + .model = "prodigyhd2", + .chip_init = prodigy_hd2_init, + .build_controls = prodigy_hd2_add_controls, + .eeprom_size = sizeof(prodigyhd2_eeprom), + .eeprom_data = prodigyhd2_eeprom, + .driver = "Prodigy71HD2", + }, + { + .subvendor = VT1724_SUBDEVICE_FORTISSIMO4, + .name = "Hercules Fortissimo IV", + .model = "fortissimo4", + .chip_init = prodigy_hifi_init, + .build_controls = prodigy_hifi_add_controls, + .eeprom_size = sizeof(fortissimo4_eeprom), + .eeprom_data = fortissimo4_eeprom, + .driver = "Fortissimo4", + }, + { } /* terminator */ +}; + diff --git a/sound/pci/ice1712/prodigy_hifi.h b/sound/pci/ice1712/prodigy_hifi.h new file mode 100644 index 000000000..f0e88396d --- /dev/null +++ b/sound/pci/ice1712/prodigy_hifi.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_PRODIGY_HIFI_H +#define __SOUND_PRODIGY_HIFI_H + +/* + * ALSA driver for VIA VT1724 (Envy24HT) + * + * Lowlevel functions for Audiotrak Prodigy Hifi + * + * Copyright (c) 2004 Takashi Iwai + */ + +#define PRODIGY_HIFI_DEVICE_DESC "{Audiotrak,Prodigy 7.1 HIFI},"\ + "{Audiotrak Prodigy HD2},"\ + "{Hercules Fortissimo IV}," + +#define VT1724_SUBDEVICE_PRODIGY_HIFI 0x38315441 /* PRODIGY 7.1 HIFI */ +#define VT1724_SUBDEVICE_PRODIGY_HD2 0x37315441 /* PRODIGY HD2 */ +#define VT1724_SUBDEVICE_FORTISSIMO4 0x81160100 /* Fortissimo IV */ + + +extern struct snd_ice1712_card_info snd_vt1724_prodigy_hifi_cards[]; + +#endif /* __SOUND_PRODIGY_HIFI_H */ diff --git a/sound/pci/ice1712/psc724.c b/sound/pci/ice1712/psc724.c new file mode 100644 index 000000000..7842bf98a --- /dev/null +++ b/sound/pci/ice1712/psc724.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Philips PSC724 Ultimate Edge + * + * Copyright (c) 2012 Ondrej Zary + */ + +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "psc724.h" +#include "wm8766.h" +#include "wm8776.h" + +struct psc724_spec { + struct snd_wm8766 wm8766; + struct snd_wm8776 wm8776; + bool mute_all, jack_detect; + struct snd_ice1712 *ice; + struct delayed_work hp_work; + bool hp_connected; +}; + +/****************************************************************************/ +/* PHILIPS PSC724 ULTIMATE EDGE */ +/****************************************************************************/ +/* + * VT1722 (Envy24GT) - 6 outputs, 4 inputs (only 2 used), 24-bit/96kHz + * + * system configuration ICE_EEP2_SYSCONF=0x42 + * XIN1 49.152MHz + * no MPU401 + * one stereo ADC, no S/PDIF receiver + * three stereo DACs (FRONT, REAR, CENTER+LFE) + * + * AC-Link configuration ICE_EEP2_ACLINK=0x80 + * use I2S, not AC97 + * + * I2S converters feature ICE_EEP2_I2S=0x30 + * I2S codec has no volume/mute control feature (bug!) + * I2S codec does not support 96KHz or 192KHz (bug!) + * I2S codec 24bits + * + * S/PDIF configuration ICE_EEP2_SPDIF=0xc1 + * Enable integrated S/PDIF transmitter + * internal S/PDIF out implemented + * No S/PDIF input + * External S/PDIF out implemented + * + * + * ** connected chips ** + * + * WM8776 + * 2-channel DAC used for main output and stereo ADC (with 10-channel MUX) + * AIN1: LINE IN, AIN2: CD/VIDEO, AIN3: AUX, AIN4: Front MIC, AIN5: Rear MIC + * Controlled by I2C using VT1722 I2C interface: + * MODE (pin16) -- GND + * CE (pin17) -- GND I2C mode (address=0x34) + * DI (pin18) -- SDA (VT1722 pin70) + * CL (pin19) -- SCLK (VT1722 pin71) + * + * WM8766 + * 6-channel DAC used for rear & center/LFE outputs (only 4 channels used) + * Controlled by SPI using VT1722 GPIO pins: + * MODE (pin 1) -- GPIO19 (VT1722 pin99) + * ML/I2S (pin11) -- GPIO18 (VT1722 pin98) + * MC/IWL (pin12) -- GPIO17 (VT1722 pin97) + * MD/DM (pin13) -- GPIO16 (VT1722 pin96) + * MUTE (pin14) -- GPIO20 (VT1722 pin101) + * + * GPIO14 is used as input for headphone jack detection (1 = connected) + * GPIO22 is used as MUTE ALL output, grounding all 6 channels + * + * ** output pins and device names ** + * + * 5.1ch name -- output connector color -- device (-D option) + * + * FRONT 2ch -- green -- plughw:0,0 + * CENTER(Lch) SUBWOOFER(Rch) -- orange -- plughw:0,2,0 + * REAR 2ch -- black -- plughw:0,2,1 + */ + +/* codec access low-level functions */ + +#define GPIO_HP_JACK (1 << 14) +#define GPIO_MUTE_SUR (1 << 20) +#define GPIO_MUTE_ALL (1 << 22) + +#define JACK_INTERVAL 1000 + +#define PSC724_SPI_DELAY 1 + +#define PSC724_SPI_DATA (1 << 16) +#define PSC724_SPI_CLK (1 << 17) +#define PSC724_SPI_LOAD (1 << 18) +#define PSC724_SPI_MASK (PSC724_SPI_DATA | PSC724_SPI_CLK | PSC724_SPI_LOAD) + +static void psc724_wm8766_write(struct snd_wm8766 *wm, u16 addr, u16 data) +{ + struct psc724_spec *spec = container_of(wm, struct psc724_spec, wm8766); + struct snd_ice1712 *ice = spec->ice; + u32 st, bits; + int i; + + snd_ice1712_save_gpio_status(ice); + + st = ((addr & 0x7f) << 9) | (data & 0x1ff); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction | PSC724_SPI_MASK); + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask & ~PSC724_SPI_MASK); + bits = snd_ice1712_gpio_read(ice) & ~PSC724_SPI_MASK; + snd_ice1712_gpio_write(ice, bits); + + for (i = 0; i < 16; i++) { + udelay(PSC724_SPI_DELAY); + bits &= ~PSC724_SPI_CLK; + /* MSB first */ + st <<= 1; + if (st & 0x10000) + bits |= PSC724_SPI_DATA; + else + bits &= ~PSC724_SPI_DATA; + snd_ice1712_gpio_write(ice, bits); + /* CLOCK high */ + udelay(PSC724_SPI_DELAY); + bits |= PSC724_SPI_CLK; + snd_ice1712_gpio_write(ice, bits); + } + /* LOAD high */ + udelay(PSC724_SPI_DELAY); + bits |= PSC724_SPI_LOAD; + snd_ice1712_gpio_write(ice, bits); + /* LOAD low, DATA and CLOCK high */ + udelay(PSC724_SPI_DELAY); + bits |= (PSC724_SPI_DATA | PSC724_SPI_CLK); + snd_ice1712_gpio_write(ice, bits); + + snd_ice1712_restore_gpio_status(ice); +} + +static void psc724_wm8776_write(struct snd_wm8776 *wm, u8 addr, u8 data) +{ + struct psc724_spec *spec = container_of(wm, struct psc724_spec, wm8776); + + snd_vt1724_write_i2c(spec->ice, 0x34, addr, data); +} + +/* mute all */ + +static void psc724_set_master_switch(struct snd_ice1712 *ice, bool on) +{ + unsigned int bits = snd_ice1712_gpio_read(ice); + struct psc724_spec *spec = ice->spec; + + spec->mute_all = !on; + if (on) + bits &= ~(GPIO_MUTE_ALL | GPIO_MUTE_SUR); + else + bits |= GPIO_MUTE_ALL | GPIO_MUTE_SUR; + snd_ice1712_gpio_write(ice, bits); +} + +static bool psc724_get_master_switch(struct snd_ice1712 *ice) +{ + struct psc724_spec *spec = ice->spec; + + return !spec->mute_all; +} + +/* jack detection */ + +static void psc724_set_jack_state(struct snd_ice1712 *ice, bool hp_connected) +{ + struct psc724_spec *spec = ice->spec; + struct snd_kcontrol *kctl; + u16 power = spec->wm8776.regs[WM8776_REG_PWRDOWN] & ~WM8776_PWR_HPPD; + + psc724_set_master_switch(ice, !hp_connected); + if (!hp_connected) + power |= WM8776_PWR_HPPD; + snd_wm8776_set_power(&spec->wm8776, power); + spec->hp_connected = hp_connected; + /* notify about master speaker mute change */ + kctl = snd_ctl_find_id_mixer(ice->card, + "Master Speakers Playback Switch"); + if (kctl) + snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + /* and headphone mute change */ + kctl = snd_ctl_find_id_mixer(ice->card, + spec->wm8776.ctl[WM8776_CTL_HP_SW].name); + if (kctl) + snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); +} + +static void psc724_update_hp_jack_state(struct work_struct *work) +{ + struct psc724_spec *spec = container_of(work, struct psc724_spec, + hp_work.work); + struct snd_ice1712 *ice = spec->ice; + bool hp_connected = snd_ice1712_gpio_read(ice) & GPIO_HP_JACK; + + schedule_delayed_work(&spec->hp_work, msecs_to_jiffies(JACK_INTERVAL)); + if (hp_connected == spec->hp_connected) + return; + psc724_set_jack_state(ice, hp_connected); +} + +static void psc724_set_jack_detection(struct snd_ice1712 *ice, bool on) +{ + struct psc724_spec *spec = ice->spec; + + if (spec->jack_detect == on) + return; + + spec->jack_detect = on; + if (on) { + bool hp_connected = snd_ice1712_gpio_read(ice) & GPIO_HP_JACK; + psc724_set_jack_state(ice, hp_connected); + schedule_delayed_work(&spec->hp_work, + msecs_to_jiffies(JACK_INTERVAL)); + } else + cancel_delayed_work_sync(&spec->hp_work); +} + +static bool psc724_get_jack_detection(struct snd_ice1712 *ice) +{ + struct psc724_spec *spec = ice->spec; + + return spec->jack_detect; +} + +/* mixer controls */ + +struct psc724_control { + const char *name; + void (*set)(struct snd_ice1712 *ice, bool on); + bool (*get)(struct snd_ice1712 *ice); +}; + +static const struct psc724_control psc724_cont[] = { + { + .name = "Master Speakers Playback Switch", + .set = psc724_set_master_switch, + .get = psc724_get_master_switch, + }, + { + .name = "Headphone Jack Detection Playback Switch", + .set = psc724_set_jack_detection, + .get = psc724_get_jack_detection, + }, +}; + +static int psc724_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + + ucontrol->value.integer.value[0] = psc724_cont[n].get(ice); + + return 0; +} + +static int psc724_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + + psc724_cont[n].set(ice, ucontrol->value.integer.value[0]); + + return 0; +} + +static const char *front_volume = "Front Playback Volume"; +static const char *front_switch = "Front Playback Switch"; +static const char *front_zc = "Front Zero Cross Detect Playback Switch"; +static const char *front_izd = "Front Infinite Zero Detect Playback Switch"; +static const char *front_phase = "Front Phase Invert Playback Switch"; +static const char *front_deemph = "Front Deemphasis Playback Switch"; +static const char *ain1_switch = "Line Capture Switch"; +static const char *ain2_switch = "CD Capture Switch"; +static const char *ain3_switch = "AUX Capture Switch"; +static const char *ain4_switch = "Front Mic Capture Switch"; +static const char *ain5_switch = "Rear Mic Capture Switch"; +static const char *rear_volume = "Surround Playback Volume"; +static const char *clfe_volume = "CLFE Playback Volume"; +static const char *rear_switch = "Surround Playback Switch"; +static const char *clfe_switch = "CLFE Playback Switch"; +static const char *rear_phase = "Surround Phase Invert Playback Switch"; +static const char *clfe_phase = "CLFE Phase Invert Playback Switch"; +static const char *rear_deemph = "Surround Deemphasis Playback Switch"; +static const char *clfe_deemph = "CLFE Deemphasis Playback Switch"; +static const char *rear_clfe_izd = "Rear Infinite Zero Detect Playback Switch"; +static const char *rear_clfe_zc = "Rear Zero Cross Detect Playback Switch"; + +static int psc724_add_controls(struct snd_ice1712 *ice) +{ + struct snd_kcontrol_new cont; + struct snd_kcontrol *ctl; + int err, i; + struct psc724_spec *spec = ice->spec; + + spec->wm8776.ctl[WM8776_CTL_DAC_VOL].name = front_volume; + spec->wm8776.ctl[WM8776_CTL_DAC_SW].name = front_switch; + spec->wm8776.ctl[WM8776_CTL_DAC_ZC_SW].name = front_zc; + spec->wm8776.ctl[WM8776_CTL_AUX_SW].name = NULL; + spec->wm8776.ctl[WM8776_CTL_DAC_IZD_SW].name = front_izd; + spec->wm8776.ctl[WM8776_CTL_PHASE_SW].name = front_phase; + spec->wm8776.ctl[WM8776_CTL_DEEMPH_SW].name = front_deemph; + spec->wm8776.ctl[WM8776_CTL_INPUT1_SW].name = ain1_switch; + spec->wm8776.ctl[WM8776_CTL_INPUT2_SW].name = ain2_switch; + spec->wm8776.ctl[WM8776_CTL_INPUT3_SW].name = ain3_switch; + spec->wm8776.ctl[WM8776_CTL_INPUT4_SW].name = ain4_switch; + spec->wm8776.ctl[WM8776_CTL_INPUT5_SW].name = ain5_switch; + snd_wm8776_build_controls(&spec->wm8776); + spec->wm8766.ctl[WM8766_CTL_CH1_VOL].name = rear_volume; + spec->wm8766.ctl[WM8766_CTL_CH2_VOL].name = clfe_volume; + spec->wm8766.ctl[WM8766_CTL_CH3_VOL].name = NULL; + spec->wm8766.ctl[WM8766_CTL_CH1_SW].name = rear_switch; + spec->wm8766.ctl[WM8766_CTL_CH2_SW].name = clfe_switch; + spec->wm8766.ctl[WM8766_CTL_CH3_SW].name = NULL; + spec->wm8766.ctl[WM8766_CTL_PHASE1_SW].name = rear_phase; + spec->wm8766.ctl[WM8766_CTL_PHASE2_SW].name = clfe_phase; + spec->wm8766.ctl[WM8766_CTL_PHASE3_SW].name = NULL; + spec->wm8766.ctl[WM8766_CTL_DEEMPH1_SW].name = rear_deemph; + spec->wm8766.ctl[WM8766_CTL_DEEMPH2_SW].name = clfe_deemph; + spec->wm8766.ctl[WM8766_CTL_DEEMPH3_SW].name = NULL; + spec->wm8766.ctl[WM8766_CTL_IZD_SW].name = rear_clfe_izd; + spec->wm8766.ctl[WM8766_CTL_ZC_SW].name = rear_clfe_zc; + snd_wm8766_build_controls(&spec->wm8766); + + memset(&cont, 0, sizeof(cont)); + cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + for (i = 0; i < ARRAY_SIZE(psc724_cont); i++) { + cont.private_value = i; + cont.name = psc724_cont[i].name; + cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + cont.info = snd_ctl_boolean_mono_info; + cont.get = psc724_ctl_get; + cont.put = psc724_ctl_put; + ctl = snd_ctl_new1(&cont, ice); + if (!ctl) + return -ENOMEM; + err = snd_ctl_add(ice->card, ctl); + if (err < 0) + return err; + } + + return 0; +} + +static void psc724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + struct psc724_spec *spec = ice->spec; + /* restore codec volume settings after rate change (PMCLK stop) */ + snd_wm8776_volume_restore(&spec->wm8776); + snd_wm8766_volume_restore(&spec->wm8766); +} + +/* power management */ + +#ifdef CONFIG_PM_SLEEP +static int psc724_resume(struct snd_ice1712 *ice) +{ + struct psc724_spec *spec = ice->spec; + + snd_wm8776_resume(&spec->wm8776); + snd_wm8766_resume(&spec->wm8766); + + return 0; +} +#endif + +/* init */ + +static int psc724_init(struct snd_ice1712 *ice) +{ + struct psc724_spec *spec; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + spec->ice = ice; + + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + spec->wm8776.ops.write = psc724_wm8776_write; + spec->wm8776.card = ice->card; + snd_wm8776_init(&spec->wm8776); + spec->wm8766.ops.write = psc724_wm8766_write; + spec->wm8766.card = ice->card; +#ifdef CONFIG_PM_SLEEP + ice->pm_resume = psc724_resume; + ice->pm_suspend_enabled = 1; +#endif + snd_wm8766_init(&spec->wm8766); + snd_wm8766_set_if(&spec->wm8766, + WM8766_IF_FMT_I2S | WM8766_IF_IWL_24BIT); + ice->gpio.set_pro_rate = psc724_set_pro_rate; + INIT_DELAYED_WORK(&spec->hp_work, psc724_update_hp_jack_state); + psc724_set_jack_detection(ice, true); + return 0; +} + +static void psc724_exit(struct snd_ice1712 *ice) +{ + struct psc724_spec *spec = ice->spec; + + cancel_delayed_work_sync(&spec->hp_work); +} + +/* PSC724 has buggy EEPROM (no 96&192kHz, all FFh GPIOs), so override it here */ +static const unsigned char psc724_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x42, /* 49.152MHz, 1 ADC, 3 DACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xf0, /* I2S volume, 96kHz, 24bit */ + [ICE_EEP2_SPDIF] = 0xc1, /* spdif out-en, out-int, no input */ + /* GPIO outputs */ + [ICE_EEP2_GPIO_DIR2] = 0x5f, /* MUTE_ALL,WM8766 MUTE/MODE/ML/MC/MD */ + /* GPIO write enable */ + [ICE_EEP2_GPIO_MASK] = 0xff, /* read-only */ + [ICE_EEP2_GPIO_MASK1] = 0xff, /* read-only */ + [ICE_EEP2_GPIO_MASK2] = 0xa0, /* MUTE_ALL,WM8766 MUTE/MODE/ML/MC/MD */ + /* GPIO initial state */ + [ICE_EEP2_GPIO_STATE2] = 0x20, /* unmuted, all WM8766 pins low */ +}; + +struct snd_ice1712_card_info snd_vt1724_psc724_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_PSC724, + .name = "Philips PSC724 Ultimate Edge", + .model = "psc724", + .chip_init = psc724_init, + .chip_exit = psc724_exit, + .build_controls = psc724_add_controls, + .eeprom_size = sizeof(psc724_eeprom), + .eeprom_data = psc724_eeprom, + }, + {} /*terminator*/ +}; diff --git a/sound/pci/ice1712/psc724.h b/sound/pci/ice1712/psc724.h new file mode 100644 index 000000000..e6ce335ae --- /dev/null +++ b/sound/pci/ice1712/psc724.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_PSC724_H +#define __SOUND_PSC724_H + +/* ID */ +#define PSC724_DEVICE_DESC \ + "{Philips,PSC724 Ultimate Edge}," + +#define VT1724_SUBDEVICE_PSC724 0xab170619 + +/* entry struct */ +extern struct snd_ice1712_card_info snd_vt1724_psc724_cards[]; + +#endif /* __SOUND_PSC724_H */ diff --git a/sound/pci/ice1712/quartet.c b/sound/pci/ice1712/quartet.c new file mode 100644 index 000000000..6a44ae969 --- /dev/null +++ b/sound/pci/ice1712/quartet.c @@ -0,0 +1,1069 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Infrasonic Quartet + * + * Copyright (c) 2009 Pavel Hofman + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include +#include "quartet.h" + +struct qtet_spec { + struct ak4113 *ak4113; + unsigned int scr; /* system control register */ + unsigned int mcr; /* monitoring control register */ + unsigned int cpld; /* cpld register */ +}; + +struct qtet_kcontrol_private { + unsigned int bit; + void (*set_register)(struct snd_ice1712 *ice, unsigned int val); + unsigned int (*get_register)(struct snd_ice1712 *ice); + const char * const texts[2]; +}; + +enum { + IN12_SEL = 0, + IN34_SEL, + AIN34_SEL, + COAX_OUT, + IN12_MON12, + IN12_MON34, + IN34_MON12, + IN34_MON34, + OUT12_MON34, + OUT34_MON12, +}; + +static const char * const ext_clock_names[3] = {"IEC958 In", "Word Clock 1xFS", + "Word Clock 256xFS"}; + +/* chip address on I2C bus */ +#define AK4113_ADDR 0x26 /* S/PDIF receiver */ + +/* chip address on SPI bus */ +#define AK4620_ADDR 0x02 /* ADC/DAC */ + + +/* + * GPIO pins + */ + +/* GPIO0 - O - DATA0, def. 0 */ +#define GPIO_D0 (1<<0) +/* GPIO1 - I/O - DATA1, Jack Detect Input0 (0:present, 1:missing), def. 1 */ +#define GPIO_D1_JACKDTC0 (1<<1) +/* GPIO2 - I/O - DATA2, Jack Detect Input1 (0:present, 1:missing), def. 1 */ +#define GPIO_D2_JACKDTC1 (1<<2) +/* GPIO3 - I/O - DATA3, def. 1 */ +#define GPIO_D3 (1<<3) +/* GPIO4 - I/O - DATA4, SPI CDTO, def. 1 */ +#define GPIO_D4_SPI_CDTO (1<<4) +/* GPIO5 - I/O - DATA5, SPI CCLK, def. 1 */ +#define GPIO_D5_SPI_CCLK (1<<5) +/* GPIO6 - I/O - DATA6, Cable Detect Input (0:detected, 1:not detected */ +#define GPIO_D6_CD (1<<6) +/* GPIO7 - I/O - DATA7, Device Detect Input (0:detected, 1:not detected */ +#define GPIO_D7_DD (1<<7) +/* GPIO8 - O - CPLD Chip Select, def. 1 */ +#define GPIO_CPLD_CSN (1<<8) +/* GPIO9 - O - CPLD register read/write (0:write, 1:read), def. 0 */ +#define GPIO_CPLD_RW (1<<9) +/* GPIO10 - O - SPI Chip Select for CODEC#0, def. 1 */ +#define GPIO_SPI_CSN0 (1<<10) +/* GPIO11 - O - SPI Chip Select for CODEC#1, def. 1 */ +#define GPIO_SPI_CSN1 (1<<11) +/* GPIO12 - O - Ex. Register Output Enable (0:enable, 1:disable), def. 1, + * init 0 */ +#define GPIO_EX_GPIOE (1<<12) +/* GPIO13 - O - Ex. Register0 Chip Select for System Control Register, + * def. 1 */ +#define GPIO_SCR (1<<13) +/* GPIO14 - O - Ex. Register1 Chip Select for Monitor Control Register, + * def. 1 */ +#define GPIO_MCR (1<<14) + +#define GPIO_SPI_ALL (GPIO_D4_SPI_CDTO | GPIO_D5_SPI_CCLK |\ + GPIO_SPI_CSN0 | GPIO_SPI_CSN1) + +#define GPIO_DATA_MASK (GPIO_D0 | GPIO_D1_JACKDTC0 | \ + GPIO_D2_JACKDTC1 | GPIO_D3 | \ + GPIO_D4_SPI_CDTO | GPIO_D5_SPI_CCLK | \ + GPIO_D6_CD | GPIO_D7_DD) + +/* System Control Register GPIO_SCR data bits */ +/* Mic/Line select relay (0:line, 1:mic) */ +#define SCR_RELAY GPIO_D0 +/* Phantom power drive control (0:5V, 1:48V) */ +#define SCR_PHP_V GPIO_D1_JACKDTC0 +/* H/W mute control (0:Normal, 1:Mute) */ +#define SCR_MUTE GPIO_D2_JACKDTC1 +/* Phantom power control (0:Phantom on, 1:off) */ +#define SCR_PHP GPIO_D3 +/* Analog input 1/2 Source Select */ +#define SCR_AIN12_SEL0 GPIO_D4_SPI_CDTO +#define SCR_AIN12_SEL1 GPIO_D5_SPI_CCLK +/* Analog input 3/4 Source Select (0:line, 1:hi-z) */ +#define SCR_AIN34_SEL GPIO_D6_CD +/* Codec Power Down (0:power down, 1:normal) */ +#define SCR_CODEC_PDN GPIO_D7_DD + +#define SCR_AIN12_LINE (0) +#define SCR_AIN12_MIC (SCR_AIN12_SEL0) +#define SCR_AIN12_LOWCUT (SCR_AIN12_SEL1 | SCR_AIN12_SEL0) + +/* Monitor Control Register GPIO_MCR data bits */ +/* Input 1/2 to Monitor 1/2 (0:off, 1:on) */ +#define MCR_IN12_MON12 GPIO_D0 +/* Input 1/2 to Monitor 3/4 (0:off, 1:on) */ +#define MCR_IN12_MON34 GPIO_D1_JACKDTC0 +/* Input 3/4 to Monitor 1/2 (0:off, 1:on) */ +#define MCR_IN34_MON12 GPIO_D2_JACKDTC1 +/* Input 3/4 to Monitor 3/4 (0:off, 1:on) */ +#define MCR_IN34_MON34 GPIO_D3 +/* Output to Monitor 1/2 (0:off, 1:on) */ +#define MCR_OUT34_MON12 GPIO_D4_SPI_CDTO +/* Output to Monitor 3/4 (0:off, 1:on) */ +#define MCR_OUT12_MON34 GPIO_D5_SPI_CCLK + +/* CPLD Register DATA bits */ +/* Clock Rate Select */ +#define CPLD_CKS0 GPIO_D0 +#define CPLD_CKS1 GPIO_D1_JACKDTC0 +#define CPLD_CKS2 GPIO_D2_JACKDTC1 +/* Sync Source Select (0:Internal, 1:External) */ +#define CPLD_SYNC_SEL GPIO_D3 +/* Word Clock FS Select (0:FS, 1:256FS) */ +#define CPLD_WORD_SEL GPIO_D4_SPI_CDTO +/* Coaxial Output Source (IS-Link) (0:SPDIF, 1:I2S) */ +#define CPLD_COAX_OUT GPIO_D5_SPI_CCLK +/* Input 1/2 Source Select (0:Analog12, 1:An34) */ +#define CPLD_IN12_SEL GPIO_D6_CD +/* Input 3/4 Source Select (0:Analog34, 1:Digital In) */ +#define CPLD_IN34_SEL GPIO_D7_DD + +/* internal clock (CPLD_SYNC_SEL = 0) options */ +#define CPLD_CKS_44100HZ (0) +#define CPLD_CKS_48000HZ (CPLD_CKS0) +#define CPLD_CKS_88200HZ (CPLD_CKS1) +#define CPLD_CKS_96000HZ (CPLD_CKS1 | CPLD_CKS0) +#define CPLD_CKS_176400HZ (CPLD_CKS2) +#define CPLD_CKS_192000HZ (CPLD_CKS2 | CPLD_CKS0) + +#define CPLD_CKS_MASK (CPLD_CKS0 | CPLD_CKS1 | CPLD_CKS2) + +/* external clock (CPLD_SYNC_SEL = 1) options */ +/* external clock - SPDIF */ +#define CPLD_EXT_SPDIF (0 | CPLD_SYNC_SEL) +/* external clock - WordClock 1xfs */ +#define CPLD_EXT_WORDCLOCK_1FS (CPLD_CKS1 | CPLD_SYNC_SEL) +/* external clock - WordClock 256xfs */ +#define CPLD_EXT_WORDCLOCK_256FS (CPLD_CKS1 | CPLD_WORD_SEL |\ + CPLD_SYNC_SEL) + +#define EXT_SPDIF_TYPE 0 +#define EXT_WORDCLOCK_1FS_TYPE 1 +#define EXT_WORDCLOCK_256FS_TYPE 2 + +#define AK4620_DFS0 (1<<0) +#define AK4620_DFS1 (1<<1) +#define AK4620_CKS0 (1<<2) +#define AK4620_CKS1 (1<<3) +/* Clock and Format Control register */ +#define AK4620_DFS_REG 0x02 + +/* Deem and Volume Control register */ +#define AK4620_DEEMVOL_REG 0x03 +#define AK4620_SMUTE (1<<7) + +/* + * Conversion from int value to its binary form. Used for debugging. + * The output buffer must be allocated prior to calling the function. + */ +static char *get_binary(char *buffer, int value) +{ + int i, j, pos; + pos = 0; + for (i = 0; i < 4; ++i) { + for (j = 0; j < 8; ++j) { + if (value & (1 << (31-(i*8 + j)))) + buffer[pos] = '1'; + else + buffer[pos] = '0'; + pos++; + } + if (i < 3) { + buffer[pos] = ' '; + pos++; + } + } + buffer[pos] = '\0'; + return buffer; +} + +/* + * Initial setup of the conversion array GPIO <-> rate + */ +static const unsigned int qtet_rates[] = { + 44100, 48000, 88200, + 96000, 176400, 192000, +}; + +static const unsigned int cks_vals[] = { + CPLD_CKS_44100HZ, CPLD_CKS_48000HZ, CPLD_CKS_88200HZ, + CPLD_CKS_96000HZ, CPLD_CKS_176400HZ, CPLD_CKS_192000HZ, +}; + +static const struct snd_pcm_hw_constraint_list qtet_rates_info = { + .count = ARRAY_SIZE(qtet_rates), + .list = qtet_rates, + .mask = 0, +}; + +static void qtet_ak4113_write(void *private_data, unsigned char reg, + unsigned char val) +{ + snd_vt1724_write_i2c((struct snd_ice1712 *)private_data, AK4113_ADDR, + reg, val); +} + +static unsigned char qtet_ak4113_read(void *private_data, unsigned char reg) +{ + return snd_vt1724_read_i2c((struct snd_ice1712 *)private_data, + AK4113_ADDR, reg); +} + + +/* + * AK4620 section + */ + +/* + * Write data to addr register of ak4620 + */ +static void qtet_akm_write(struct snd_akm4xxx *ak, int chip, + unsigned char addr, unsigned char data) +{ + unsigned int tmp, orig_dir; + int idx; + unsigned int addrdata; + struct snd_ice1712 *ice = ak->private_data[0]; + + if (snd_BUG_ON(chip < 0 || chip >= 4)) + return; + /*dev_dbg(ice->card->dev, "Writing to AK4620: chip=%d, addr=0x%x, + data=0x%x\n", chip, addr, data);*/ + orig_dir = ice->gpio.get_dir(ice); + ice->gpio.set_dir(ice, orig_dir | GPIO_SPI_ALL); + /* set mask - only SPI bits */ + ice->gpio.set_mask(ice, ~GPIO_SPI_ALL); + + tmp = ice->gpio.get_data(ice); + /* high all */ + tmp |= GPIO_SPI_ALL; + ice->gpio.set_data(ice, tmp); + udelay(100); + /* drop chip select */ + if (chip) + /* CODEC 1 */ + tmp &= ~GPIO_SPI_CSN1; + else + tmp &= ~GPIO_SPI_CSN0; + ice->gpio.set_data(ice, tmp); + udelay(100); + + /* build I2C address + data byte */ + addrdata = (AK4620_ADDR << 6) | 0x20 | (addr & 0x1f); + addrdata = (addrdata << 8) | data; + for (idx = 15; idx >= 0; idx--) { + /* drop clock */ + tmp &= ~GPIO_D5_SPI_CCLK; + ice->gpio.set_data(ice, tmp); + udelay(100); + /* set data */ + if (addrdata & (1 << idx)) + tmp |= GPIO_D4_SPI_CDTO; + else + tmp &= ~GPIO_D4_SPI_CDTO; + ice->gpio.set_data(ice, tmp); + udelay(100); + /* raise clock */ + tmp |= GPIO_D5_SPI_CCLK; + ice->gpio.set_data(ice, tmp); + udelay(100); + } + /* all back to 1 */ + tmp |= GPIO_SPI_ALL; + ice->gpio.set_data(ice, tmp); + udelay(100); + + /* return all gpios to non-writable */ + ice->gpio.set_mask(ice, 0xffffff); + /* restore GPIOs direction */ + ice->gpio.set_dir(ice, orig_dir); +} + +static void qtet_akm_set_regs(struct snd_akm4xxx *ak, unsigned char addr, + unsigned char mask, unsigned char value) +{ + unsigned char tmp; + int chip; + for (chip = 0; chip < ak->num_chips; chip++) { + tmp = snd_akm4xxx_get(ak, chip, addr); + /* clear the bits */ + tmp &= ~mask; + /* set the new bits */ + tmp |= value; + snd_akm4xxx_write(ak, chip, addr, tmp); + } +} + +/* + * change the rate of AK4620 + */ +static void qtet_akm_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) +{ + unsigned char ak4620_dfs; + + if (rate == 0) /* no hint - S/PDIF input is master or the new spdif + input rate undetected, simply return */ + return; + + /* adjust DFS on codecs - see datasheet */ + if (rate > 108000) + ak4620_dfs = AK4620_DFS1 | AK4620_CKS1; + else if (rate > 54000) + ak4620_dfs = AK4620_DFS0 | AK4620_CKS0; + else + ak4620_dfs = 0; + + /* set new value */ + qtet_akm_set_regs(ak, AK4620_DFS_REG, AK4620_DFS0 | AK4620_DFS1 | + AK4620_CKS0 | AK4620_CKS1, ak4620_dfs); +} + +#define AK_CONTROL(xname, xch) { .name = xname, .num_channels = xch } + +#define PCM_12_PLAYBACK_VOLUME "PCM 1/2 Playback Volume" +#define PCM_34_PLAYBACK_VOLUME "PCM 3/4 Playback Volume" +#define PCM_12_CAPTURE_VOLUME "PCM 1/2 Capture Volume" +#define PCM_34_CAPTURE_VOLUME "PCM 3/4 Capture Volume" + +static const struct snd_akm4xxx_dac_channel qtet_dac[] = { + AK_CONTROL(PCM_12_PLAYBACK_VOLUME, 2), + AK_CONTROL(PCM_34_PLAYBACK_VOLUME, 2), +}; + +static const struct snd_akm4xxx_adc_channel qtet_adc[] = { + AK_CONTROL(PCM_12_CAPTURE_VOLUME, 2), + AK_CONTROL(PCM_34_CAPTURE_VOLUME, 2), +}; + +static const struct snd_akm4xxx akm_qtet_dac = { + .type = SND_AK4620, + .num_dacs = 4, /* DAC1 - Output 12 + */ + .num_adcs = 4, /* ADC1 - Input 12 + */ + .ops = { + .write = qtet_akm_write, + .set_rate_val = qtet_akm_set_rate_val, + }, + .dac_info = qtet_dac, + .adc_info = qtet_adc, +}; + +/* Communication routines with the CPLD */ + + +/* Writes data to external register reg, both reg and data are + * GPIO representations */ +static void reg_write(struct snd_ice1712 *ice, unsigned int reg, + unsigned int data) +{ + unsigned int tmp; + + guard(mutex)(&ice->gpio_mutex); + /* set direction of used GPIOs*/ + /* all outputs */ + tmp = 0x00ffff; + ice->gpio.set_dir(ice, tmp); + /* mask - writable bits */ + ice->gpio.set_mask(ice, ~(tmp)); + /* write the data */ + tmp = ice->gpio.get_data(ice); + tmp &= ~GPIO_DATA_MASK; + tmp |= data; + ice->gpio.set_data(ice, tmp); + udelay(100); + /* drop output enable */ + tmp &= ~GPIO_EX_GPIOE; + ice->gpio.set_data(ice, tmp); + udelay(100); + /* drop the register gpio */ + tmp &= ~reg; + ice->gpio.set_data(ice, tmp); + udelay(100); + /* raise the register GPIO */ + tmp |= reg; + ice->gpio.set_data(ice, tmp); + udelay(100); + + /* raise all data gpios */ + tmp |= GPIO_DATA_MASK; + ice->gpio.set_data(ice, tmp); + /* mask - immutable bits */ + ice->gpio.set_mask(ice, 0xffffff); + /* outputs only 8-15 */ + ice->gpio.set_dir(ice, 0x00ff00); +} + +static unsigned int get_scr(struct snd_ice1712 *ice) +{ + struct qtet_spec *spec = ice->spec; + return spec->scr; +} + +static unsigned int get_mcr(struct snd_ice1712 *ice) +{ + struct qtet_spec *spec = ice->spec; + return spec->mcr; +} + +static unsigned int get_cpld(struct snd_ice1712 *ice) +{ + struct qtet_spec *spec = ice->spec; + return spec->cpld; +} + +static void set_scr(struct snd_ice1712 *ice, unsigned int val) +{ + struct qtet_spec *spec = ice->spec; + reg_write(ice, GPIO_SCR, val); + spec->scr = val; +} + +static void set_mcr(struct snd_ice1712 *ice, unsigned int val) +{ + struct qtet_spec *spec = ice->spec; + reg_write(ice, GPIO_MCR, val); + spec->mcr = val; +} + +static void set_cpld(struct snd_ice1712 *ice, unsigned int val) +{ + struct qtet_spec *spec = ice->spec; + reg_write(ice, GPIO_CPLD_CSN, val); + spec->cpld = val; +} + +static void proc_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ice1712 *ice = entry->private_data; + char bin_buffer[36]; + + snd_iprintf(buffer, "SCR: %s\n", get_binary(bin_buffer, + get_scr(ice))); + snd_iprintf(buffer, "MCR: %s\n", get_binary(bin_buffer, + get_mcr(ice))); + snd_iprintf(buffer, "CPLD: %s\n", get_binary(bin_buffer, + get_cpld(ice))); +} + +static void proc_init(struct snd_ice1712 *ice) +{ + snd_card_ro_proc_new(ice->card, "quartet", ice, proc_regs_read); +} + +static int qtet_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val; + val = get_scr(ice) & SCR_MUTE; + ucontrol->value.integer.value[0] = (val) ? 0 : 1; + return 0; +} + +static int qtet_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int old, new, smute; + old = get_scr(ice) & SCR_MUTE; + if (ucontrol->value.integer.value[0]) { + /* unmute */ + new = 0; + /* un-smuting DAC */ + smute = 0; + } else { + /* mute */ + new = SCR_MUTE; + /* smuting DAC */ + smute = AK4620_SMUTE; + } + if (old != new) { + struct snd_akm4xxx *ak = ice->akm; + set_scr(ice, (get_scr(ice) & ~SCR_MUTE) | new); + /* set smute */ + qtet_akm_set_regs(ak, AK4620_DEEMVOL_REG, AK4620_SMUTE, smute); + return 1; + } + /* no change */ + return 0; +} + +static int qtet_ain12_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = + {"Line In 1/2", "Mic", "Mic + Low-cut"}; + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int qtet_ain12_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val, result; + val = get_scr(ice) & (SCR_AIN12_SEL1 | SCR_AIN12_SEL0); + switch (val) { + case SCR_AIN12_LINE: + result = 0; + break; + case SCR_AIN12_MIC: + result = 1; + break; + case SCR_AIN12_LOWCUT: + result = 2; + break; + default: + /* BUG - no other combinations allowed */ + snd_BUG(); + result = 0; + } + ucontrol->value.integer.value[0] = result; + return 0; +} + +static int qtet_ain12_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int old, new, tmp, masked_old; + old = get_scr(ice); + masked_old = old & (SCR_AIN12_SEL1 | SCR_AIN12_SEL0); + tmp = ucontrol->value.integer.value[0]; + if (tmp == 2) + tmp = 3; /* binary 10 is not supported */ + tmp <<= 4; /* shifting to SCR_AIN12_SEL0 */ + if (tmp != masked_old) { + /* change requested */ + switch (tmp) { + case SCR_AIN12_LINE: + new = old & ~(SCR_AIN12_SEL1 | SCR_AIN12_SEL0); + set_scr(ice, new); + /* turn off relay */ + new &= ~SCR_RELAY; + set_scr(ice, new); + break; + case SCR_AIN12_MIC: + /* turn on relay */ + new = old | SCR_RELAY; + set_scr(ice, new); + new = (new & ~SCR_AIN12_SEL1) | SCR_AIN12_SEL0; + set_scr(ice, new); + break; + case SCR_AIN12_LOWCUT: + /* turn on relay */ + new = old | SCR_RELAY; + set_scr(ice, new); + new |= SCR_AIN12_SEL1 | SCR_AIN12_SEL0; + set_scr(ice, new); + break; + default: + snd_BUG(); + } + return 1; + } + /* no change */ + return 0; +} + +static int qtet_php_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val; + /* if phantom voltage =48V, phantom on */ + val = get_scr(ice) & SCR_PHP_V; + ucontrol->value.integer.value[0] = val ? 1 : 0; + return 0; +} + +static int qtet_php_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int old, new; + old = new = get_scr(ice); + if (ucontrol->value.integer.value[0] /* phantom on requested */ + && (~old & SCR_PHP_V)) /* 0 = voltage 5V */ { + /* is off, turn on */ + /* turn voltage on first, = 1 */ + new = old | SCR_PHP_V; + set_scr(ice, new); + /* turn phantom on, = 0 */ + new &= ~SCR_PHP; + set_scr(ice, new); + } else if (!ucontrol->value.integer.value[0] && (old & SCR_PHP_V)) { + /* phantom off requested and 1 = voltage 48V */ + /* is on, turn off */ + /* turn voltage off first, = 0 */ + new = old & ~SCR_PHP_V; + set_scr(ice, new); + /* turn phantom off, = 1 */ + new |= SCR_PHP; + set_scr(ice, new); + } + if (old != new) + return 1; + /* no change */ + return 0; +} + +#define PRIV_SW(xid, xbit, xreg) [xid] = {.bit = xbit,\ + .set_register = set_##xreg,\ + .get_register = get_##xreg, } + + +#define PRIV_ENUM2(xid, xbit, xreg, xtext1, xtext2) [xid] = {.bit = xbit,\ + .set_register = set_##xreg,\ + .get_register = get_##xreg,\ + .texts = {xtext1, xtext2} } + +static const struct qtet_kcontrol_private qtet_privates[] = { + PRIV_ENUM2(IN12_SEL, CPLD_IN12_SEL, cpld, "An In 1/2", "An In 3/4"), + PRIV_ENUM2(IN34_SEL, CPLD_IN34_SEL, cpld, "An In 3/4", "IEC958 In"), + PRIV_ENUM2(AIN34_SEL, SCR_AIN34_SEL, scr, "Line In 3/4", "Hi-Z"), + PRIV_ENUM2(COAX_OUT, CPLD_COAX_OUT, cpld, "IEC958", "I2S"), + PRIV_SW(IN12_MON12, MCR_IN12_MON12, mcr), + PRIV_SW(IN12_MON34, MCR_IN12_MON34, mcr), + PRIV_SW(IN34_MON12, MCR_IN34_MON12, mcr), + PRIV_SW(IN34_MON34, MCR_IN34_MON34, mcr), + PRIV_SW(OUT12_MON34, MCR_OUT12_MON34, mcr), + PRIV_SW(OUT34_MON12, MCR_OUT34_MON12, mcr), +}; + +static int qtet_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct qtet_kcontrol_private private = + qtet_privates[kcontrol->private_value]; + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(private.texts), + private.texts); +} + +static int qtet_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct qtet_kcontrol_private private = + qtet_privates[kcontrol->private_value]; + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = + (private.get_register(ice) & private.bit) ? 1 : 0; + return 0; +} + +static int qtet_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct qtet_kcontrol_private private = + qtet_privates[kcontrol->private_value]; + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int old, new; + old = private.get_register(ice); + if (ucontrol->value.integer.value[0]) + new = old | private.bit; + else + new = old & ~private.bit; + if (old != new) { + private.set_register(ice, new); + return 1; + } + /* no change */ + return 0; +} + +#define qtet_sw_info snd_ctl_boolean_mono_info + +#define QTET_CONTROL(xname, xtype, xpriv) \ + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER,\ + .name = xname,\ + .info = qtet_##xtype##_info,\ + .get = qtet_sw_get,\ + .put = qtet_sw_put,\ + .private_value = xpriv } + +static const struct snd_kcontrol_new qtet_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = qtet_sw_info, + .get = qtet_mute_get, + .put = qtet_mute_put, + .private_value = 0 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Phantom Power", + .info = qtet_sw_info, + .get = qtet_php_get, + .put = qtet_php_put, + .private_value = 0 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog In 1/2 Capture Switch", + .info = qtet_ain12_enum_info, + .get = qtet_ain12_sw_get, + .put = qtet_ain12_sw_put, + .private_value = 0 + }, + QTET_CONTROL("Analog In 3/4 Capture Switch", enum, AIN34_SEL), + QTET_CONTROL("PCM In 1/2 Capture Switch", enum, IN12_SEL), + QTET_CONTROL("PCM In 3/4 Capture Switch", enum, IN34_SEL), + QTET_CONTROL("Coax Output Source", enum, COAX_OUT), + QTET_CONTROL("Analog In 1/2 to Monitor 1/2", sw, IN12_MON12), + QTET_CONTROL("Analog In 1/2 to Monitor 3/4", sw, IN12_MON34), + QTET_CONTROL("Analog In 3/4 to Monitor 1/2", sw, IN34_MON12), + QTET_CONTROL("Analog In 3/4 to Monitor 3/4", sw, IN34_MON34), + QTET_CONTROL("Output 1/2 to Monitor 3/4", sw, OUT12_MON34), + QTET_CONTROL("Output 3/4 to Monitor 1/2", sw, OUT34_MON12), +}; + +static const char * const follower_vols[] = { + PCM_12_PLAYBACK_VOLUME, + PCM_34_PLAYBACK_VOLUME, + NULL +}; + +static +DECLARE_TLV_DB_SCALE(qtet_master_db_scale, -6350, 50, 1); + +static int qtet_add_controls(struct snd_ice1712 *ice) +{ + struct qtet_spec *spec = ice->spec; + int err, i; + struct snd_kcontrol *vmaster; + err = snd_ice1712_akm4xxx_build_controls(ice); + if (err < 0) + return err; + for (i = 0; i < ARRAY_SIZE(qtet_controls); i++) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&qtet_controls[i], ice)); + if (err < 0) + return err; + } + + /* Create virtual master control */ + vmaster = snd_ctl_make_virtual_master("Master Playback Volume", + qtet_master_db_scale); + if (!vmaster) + return -ENOMEM; + err = snd_ctl_add(ice->card, vmaster); + if (err < 0) + return err; + err = snd_ctl_add_followers(ice->card, vmaster, follower_vols); + if (err < 0) + return err; + /* only capture SPDIF over AK4113 */ + return snd_ak4113_build(spec->ak4113, + ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream); +} + +static inline int qtet_is_spdif_master(struct snd_ice1712 *ice) +{ + /* CPLD_SYNC_SEL: 0 = internal, 1 = external (i.e. spdif master) */ + return (get_cpld(ice) & CPLD_SYNC_SEL) ? 1 : 0; +} + +static unsigned int qtet_get_rate(struct snd_ice1712 *ice) +{ + int i; + unsigned char result; + + result = get_cpld(ice) & CPLD_CKS_MASK; + for (i = 0; i < ARRAY_SIZE(cks_vals); i++) + if (cks_vals[i] == result) + return qtet_rates[i]; + return 0; +} + +static int get_cks_val(int rate) +{ + int i; + for (i = 0; i < ARRAY_SIZE(qtet_rates); i++) + if (qtet_rates[i] == rate) + return cks_vals[i]; + return 0; +} + +/* setting new rate */ +static void qtet_set_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + unsigned int new; + unsigned char val; + /* switching ice1724 to external clock - supplied by ext. circuits */ + val = inb(ICEMT1724(ice, RATE)); + outb(val | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE)); + + new = (get_cpld(ice) & ~CPLD_CKS_MASK) | get_cks_val(rate); + /* switch to internal clock, drop CPLD_SYNC_SEL */ + new &= ~CPLD_SYNC_SEL; + /* dev_dbg(ice->card->dev, "QT - set_rate: old %x, new %x\n", + get_cpld(ice), new); */ + set_cpld(ice, new); +} + +static inline unsigned char qtet_set_mclk(struct snd_ice1712 *ice, + unsigned int rate) +{ + /* no change in master clock */ + return 0; +} + +/* setting clock to external - SPDIF */ +static int qtet_set_spdif_clock(struct snd_ice1712 *ice, int type) +{ + unsigned int old, new; + + old = new = get_cpld(ice); + new &= ~(CPLD_CKS_MASK | CPLD_WORD_SEL); + switch (type) { + case EXT_SPDIF_TYPE: + new |= CPLD_EXT_SPDIF; + break; + case EXT_WORDCLOCK_1FS_TYPE: + new |= CPLD_EXT_WORDCLOCK_1FS; + break; + case EXT_WORDCLOCK_256FS_TYPE: + new |= CPLD_EXT_WORDCLOCK_256FS; + break; + default: + snd_BUG(); + } + if (old != new) { + set_cpld(ice, new); + /* changed */ + return 1; + } + return 0; +} + +static int qtet_get_spdif_master_type(struct snd_ice1712 *ice) +{ + unsigned int val; + int result; + val = get_cpld(ice); + /* checking only rate/clock-related bits */ + val &= (CPLD_CKS_MASK | CPLD_WORD_SEL | CPLD_SYNC_SEL); + if (!(val & CPLD_SYNC_SEL)) { + /* switched to internal clock, is not any external type */ + result = -1; + } else { + switch (val) { + case (CPLD_EXT_SPDIF): + result = EXT_SPDIF_TYPE; + break; + case (CPLD_EXT_WORDCLOCK_1FS): + result = EXT_WORDCLOCK_1FS_TYPE; + break; + case (CPLD_EXT_WORDCLOCK_256FS): + result = EXT_WORDCLOCK_256FS_TYPE; + break; + default: + /* undefined combination of external clock setup */ + snd_BUG(); + result = 0; + } + } + return result; +} + +/* Called when ak4113 detects change in the input SPDIF stream */ +static void qtet_ak4113_change(struct ak4113 *ak4113, unsigned char c0, + unsigned char c1) +{ + struct snd_ice1712 *ice = ak4113->change_callback_private; + int rate; + if ((qtet_get_spdif_master_type(ice) == EXT_SPDIF_TYPE) && + c1) { + /* only for SPDIF master mode, rate was changed */ + rate = snd_ak4113_external_rate(ak4113); + /* dev_dbg(ice->card->dev, "ak4113 - input rate changed to %d\n", + rate); */ + qtet_akm_set_rate_val(ice->akm, rate); + } +} + +/* + * If clock slaved to SPDIF-IN, setting runtime rate + * to the detected external rate + */ +static void qtet_spdif_in_open(struct snd_ice1712 *ice, + struct snd_pcm_substream *substream) +{ + struct qtet_spec *spec = ice->spec; + struct snd_pcm_runtime *runtime = substream->runtime; + int rate; + + if (qtet_get_spdif_master_type(ice) != EXT_SPDIF_TYPE) + /* not external SPDIF, no rate limitation */ + return; + /* only external SPDIF can detect incoming sample rate */ + rate = snd_ak4113_external_rate(spec->ak4113); + if (rate >= runtime->hw.rate_min && rate <= runtime->hw.rate_max) { + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } +} + +/* + * initialize the chip + */ +static int qtet_init(struct snd_ice1712 *ice) +{ + static const unsigned char ak4113_init_vals[] = { + /* AK4113_REG_PWRDN */ AK4113_RST | AK4113_PWN | + AK4113_OCKS0 | AK4113_OCKS1, + /* AK4113_REQ_FORMAT */ AK4113_DIF_I24I2S | AK4113_VTX | + AK4113_DEM_OFF | AK4113_DEAU, + /* AK4113_REG_IO0 */ AK4113_OPS2 | AK4113_TXE | + AK4113_XTL_24_576M, + /* AK4113_REG_IO1 */ AK4113_EFH_1024LRCLK | AK4113_IPS(0), + /* AK4113_REG_INT0_MASK */ 0, + /* AK4113_REG_INT1_MASK */ 0, + /* AK4113_REG_DATDTS */ 0, + }; + int err; + struct qtet_spec *spec; + struct snd_akm4xxx *ak; + unsigned char val; + + /* switching ice1724 to external clock - supplied by ext. circuits */ + val = inb(ICEMT1724(ice, RATE)); + outb(val | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE)); + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + /* qtet is clocked by Xilinx array */ + ice->hw_rates = &qtet_rates_info; + ice->is_spdif_master = qtet_is_spdif_master; + ice->get_rate = qtet_get_rate; + ice->set_rate = qtet_set_rate; + ice->set_mclk = qtet_set_mclk; + ice->set_spdif_clock = qtet_set_spdif_clock; + ice->get_spdif_master_type = qtet_get_spdif_master_type; + ice->ext_clock_names = ext_clock_names; + ice->ext_clock_count = ARRAY_SIZE(ext_clock_names); + /* since Qtet can detect correct SPDIF-in rate, all streams can be + * limited to this specific rate */ + ice->spdif.ops.open = ice->pro_open = qtet_spdif_in_open; + ice->spec = spec; + + /* Mute Off */ + /* SCR Initialize*/ + /* keep codec power down first */ + set_scr(ice, SCR_PHP); + udelay(1); + /* codec power up */ + set_scr(ice, SCR_PHP | SCR_CODEC_PDN); + + /* MCR Initialize */ + set_mcr(ice, 0); + + /* CPLD Initialize */ + set_cpld(ice, 0); + + + ice->num_total_dacs = 2; + ice->num_total_adcs = 2; + + ice->akm = kzalloc_objs(struct snd_akm4xxx, 2); + ak = ice->akm; + if (!ak) + return -ENOMEM; + /* only one codec with two chips */ + ice->akm_codecs = 1; + err = snd_ice1712_akm4xxx_init(ak, &akm_qtet_dac, NULL, ice); + if (err < 0) + return err; + err = snd_ak4113_create(ice->card, + qtet_ak4113_read, + qtet_ak4113_write, + ak4113_init_vals, + ice, &spec->ak4113); + if (err < 0) + return err; + /* callback for codecs rate setting */ + spec->ak4113->change_callback = qtet_ak4113_change; + spec->ak4113->change_callback_private = ice; + /* AK41143 in Quartet can detect external rate correctly + * (i.e. check_flags = 0) */ + spec->ak4113->check_flags = 0; + + proc_init(ice); + + qtet_set_rate(ice, 44100); + return 0; +} + +static const unsigned char qtet_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x28, /* clock 256(24MHz), mpu401, 1xADC, + 1xDACs, SPDIF in */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0x78, /* 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, in, out-ext */ + [ICE_EEP2_GPIO_DIR] = 0x00, /* 0-7 inputs, switched to output + only during output operations */ + [ICE_EEP2_GPIO_DIR1] = 0xff, /* 8-15 outputs */ + [ICE_EEP2_GPIO_DIR2] = 0x00, + [ICE_EEP2_GPIO_MASK] = 0xff, /* changed only for OUT operations */ + [ICE_EEP2_GPIO_MASK1] = 0x00, + [ICE_EEP2_GPIO_MASK2] = 0xff, + + [ICE_EEP2_GPIO_STATE] = 0x00, /* inputs */ + [ICE_EEP2_GPIO_STATE1] = 0x7d, /* all 1, but GPIO_CPLD_RW + and GPIO15 always zero */ + [ICE_EEP2_GPIO_STATE2] = 0x00, /* inputs */ +}; + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_qtet_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_QTET, + .name = "Infrasonic Quartet", + .model = "quartet", + .chip_init = qtet_init, + .build_controls = qtet_add_controls, + .eeprom_size = sizeof(qtet_eeprom), + .eeprom_data = qtet_eeprom, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/quartet.h b/sound/pci/ice1712/quartet.h new file mode 100644 index 000000000..a1c2fe271 --- /dev/null +++ b/sound/pci/ice1712/quartet.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_QTET_H +#define __SOUND_QTET_H + +#define QTET_DEVICE_DESC "{Infrasonic,Quartet}," + +#define VT1724_SUBDEVICE_QTET 0x30305349 /* Infrasonic Quartet */ + +extern struct snd_ice1712_card_info snd_vt1724_qtet_cards[]; + +#endif /* __SOUND_QTET_H */ diff --git a/sound/pci/ice1712/revo.c b/sound/pci/ice1712/revo.c new file mode 100644 index 000000000..d9723c297 --- /dev/null +++ b/sound/pci/ice1712/revo.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for M-Audio Audiophile 192, Revolution 7.1 and 5.1 + * + * Copyright (c) 2003 Takashi Iwai + */ + +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "revo.h" + +/* a non-standard I2C device for revo51 */ +struct revo51_spec { + struct snd_i2c_device *dev; + struct snd_pt2258 *pt2258; + struct ak4114 *ak4114; +}; + +static void revo_i2s_mclk_changed(struct snd_ice1712 *ice) +{ + /* assert PRST# to converters; MT05 bit 7 */ + outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD)); + mdelay(5); + /* deassert PRST# */ + outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD)); +} + +/* + * change the rate of Envy24HT, AK4355 and AK4381 + */ +static void revo_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) +{ + unsigned char old, tmp, dfs; + int reg, shift; + + if (rate == 0) /* no hint - S/PDIF input is master, simply return */ + return; + + /* adjust DFS on codecs */ + if (rate > 96000) + dfs = 2; + else if (rate > 48000) + dfs = 1; + else + dfs = 0; + + if (ak->type == SND_AK4355 || ak->type == SND_AK4358) { + reg = 2; + shift = 4; + } else { + reg = 1; + shift = 3; + } + tmp = snd_akm4xxx_get(ak, 0, reg); + old = (tmp >> shift) & 0x03; + if (old == dfs) + return; + + /* reset DFS */ + snd_akm4xxx_reset(ak, 1); + tmp = snd_akm4xxx_get(ak, 0, reg); + tmp &= ~(0x03 << shift); + tmp |= dfs << shift; + /* snd_akm4xxx_write(ak, 0, reg, tmp); */ + snd_akm4xxx_set(ak, 0, reg, tmp); /* value is written in reset(0) */ + snd_akm4xxx_reset(ak, 0); +} + +/* + * I2C access to the PT2258 volume controller on GPIO 6/7 (Revolution 5.1) + */ + +static void revo_i2c_start(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + snd_ice1712_save_gpio_status(ice); +} + +static void revo_i2c_stop(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + snd_ice1712_restore_gpio_status(ice); +} + +static void revo_i2c_direction(struct snd_i2c_bus *bus, int clock, int data) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned int mask, val; + + val = 0; + if (clock) + val |= VT1724_REVO_I2C_CLOCK; /* write SCL */ + if (data) + val |= VT1724_REVO_I2C_DATA; /* write SDA */ + mask = VT1724_REVO_I2C_CLOCK | VT1724_REVO_I2C_DATA; + ice->gpio.direction &= ~mask; + ice->gpio.direction |= val; + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); + snd_ice1712_gpio_set_mask(ice, ~mask); +} + +static void revo_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned int val = 0; + + if (clk) + val |= VT1724_REVO_I2C_CLOCK; + if (data) + val |= VT1724_REVO_I2C_DATA; + snd_ice1712_gpio_write_bits(ice, + VT1724_REVO_I2C_DATA | + VT1724_REVO_I2C_CLOCK, val); + udelay(5); +} + +static int revo_i2c_getdata(struct snd_i2c_bus *bus, int ack) +{ + struct snd_ice1712 *ice = bus->private_data; + int bit; + + if (ack) + udelay(5); + bit = snd_ice1712_gpio_read_bits(ice, VT1724_REVO_I2C_DATA) ? 1 : 0; + return bit; +} + +static struct snd_i2c_bit_ops revo51_bit_ops = { + .start = revo_i2c_start, + .stop = revo_i2c_stop, + .direction = revo_i2c_direction, + .setlines = revo_i2c_setlines, + .getdata = revo_i2c_getdata, +}; + +static int revo51_i2c_init(struct snd_ice1712 *ice, + struct snd_pt2258 *pt) +{ + struct revo51_spec *spec; + int err; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + /* create the I2C bus */ + err = snd_i2c_bus_create(ice->card, "ICE1724 GPIO6", NULL, &ice->i2c); + if (err < 0) + return err; + + ice->i2c->private_data = ice; + ice->i2c->hw_ops.bit = &revo51_bit_ops; + + /* create the I2C device */ + err = snd_i2c_device_create(ice->i2c, "PT2258", 0x40, &spec->dev); + if (err < 0) + return err; + + pt->card = ice->card; + pt->i2c_bus = ice->i2c; + pt->i2c_dev = spec->dev; + spec->pt2258 = pt; + + snd_pt2258_reset(pt); + + return 0; +} + +/* + * initialize the chips on M-Audio Revolution cards + */ + +#define AK_DAC(xname,xch) { .name = xname, .num_channels = xch } + +static const struct snd_akm4xxx_dac_channel revo71_front[] = { + { + .name = "PCM Playback Volume", + .num_channels = 2, + /* front channels DAC supports muting */ + .switch_name = "PCM Playback Switch", + }, +}; + +static const struct snd_akm4xxx_dac_channel revo71_surround[] = { + AK_DAC("PCM Center Playback Volume", 1), + AK_DAC("PCM LFE Playback Volume", 1), + AK_DAC("PCM Side Playback Volume", 2), + AK_DAC("PCM Rear Playback Volume", 2), +}; + +static const struct snd_akm4xxx_dac_channel revo51_dac[] = { + AK_DAC("PCM Playback Volume", 2), + AK_DAC("PCM Center Playback Volume", 1), + AK_DAC("PCM LFE Playback Volume", 1), + AK_DAC("PCM Rear Playback Volume", 2), + AK_DAC("PCM Headphone Volume", 2), +}; + +static const char *revo51_adc_input_names[] = { + "Mic", + "Line", + "CD", + NULL +}; + +static const struct snd_akm4xxx_adc_channel revo51_adc[] = { + { + .name = "PCM Capture Volume", + .switch_name = "PCM Capture Switch", + .num_channels = 2, + .input_names = revo51_adc_input_names + }, +}; + +static const struct snd_akm4xxx akm_revo_front = { + .type = SND_AK4381, + .num_dacs = 2, + .ops = { + .set_rate_val = revo_set_rate_val + }, + .dac_info = revo71_front, +}; + +static const struct snd_ak4xxx_private akm_revo_front_priv = { + .caddr = 1, + .cif = 0, + .data_mask = VT1724_REVO_CDOUT, + .clk_mask = VT1724_REVO_CCLK, + .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2, + .cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS2, + .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2, + .add_flags = VT1724_REVO_CCLK, /* high at init */ + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_revo_surround = { + .type = SND_AK4355, + .idx_offset = 1, + .num_dacs = 6, + .ops = { + .set_rate_val = revo_set_rate_val + }, + .dac_info = revo71_surround, +}; + +static const struct snd_ak4xxx_private akm_revo_surround_priv = { + .caddr = 3, + .cif = 0, + .data_mask = VT1724_REVO_CDOUT, + .clk_mask = VT1724_REVO_CCLK, + .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2, + .cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS1, + .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2, + .add_flags = VT1724_REVO_CCLK, /* high at init */ + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_revo51 = { + .type = SND_AK4358, + .num_dacs = 8, + .ops = { + .set_rate_val = revo_set_rate_val + }, + .dac_info = revo51_dac, +}; + +static const struct snd_ak4xxx_private akm_revo51_priv = { + .caddr = 2, + .cif = 0, + .data_mask = VT1724_REVO_CDOUT, + .clk_mask = VT1724_REVO_CCLK, + .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1, + .cs_addr = VT1724_REVO_CS1, + .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1, + .add_flags = VT1724_REVO_CCLK, /* high at init */ + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_revo51_adc = { + .type = SND_AK5365, + .num_adcs = 2, + .adc_info = revo51_adc, +}; + +static const struct snd_ak4xxx_private akm_revo51_adc_priv = { + .caddr = 2, + .cif = 0, + .data_mask = VT1724_REVO_CDOUT, + .clk_mask = VT1724_REVO_CCLK, + .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1, + .cs_addr = VT1724_REVO_CS0, + .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1, + .add_flags = VT1724_REVO_CCLK, /* high at init */ + .mask_flags = 0, +}; + +static struct snd_pt2258 ptc_revo51_volume; + +/* AK4358 for AP192 DAC, AK5385A for ADC */ +static void ap192_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + int dfs; + + revo_set_rate_val(ak, rate); + + /* reset CKS */ + snd_ice1712_gpio_write_bits(ice, 1 << 8, rate > 96000 ? 1 << 8 : 0); + /* reset DFS pins of AK5385A for ADC, too */ + if (rate > 96000) + dfs = 2; + else if (rate > 48000) + dfs = 1; + else + dfs = 0; + snd_ice1712_gpio_write_bits(ice, 3 << 9, dfs << 9); + /* reset ADC */ + snd_ice1712_gpio_write_bits(ice, 1 << 11, 0); + snd_ice1712_gpio_write_bits(ice, 1 << 11, 1 << 11); +} + +static const struct snd_akm4xxx_dac_channel ap192_dac[] = { + AK_DAC("PCM Playback Volume", 2) +}; + +static const struct snd_akm4xxx akm_ap192 = { + .type = SND_AK4358, + .num_dacs = 2, + .ops = { + .set_rate_val = ap192_set_rate_val + }, + .dac_info = ap192_dac, +}; + +static const struct snd_ak4xxx_private akm_ap192_priv = { + .caddr = 2, + .cif = 0, + .data_mask = VT1724_REVO_CDOUT, + .clk_mask = VT1724_REVO_CCLK, + .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS3, + .cs_addr = VT1724_REVO_CS3, + .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS3, + .add_flags = VT1724_REVO_CCLK, /* high at init */ + .mask_flags = 0, +}; + +/* AK4114 support on Audiophile 192 */ +/* CDTO (pin 32) -- GPIO2 pin 52 + * CDTI (pin 33) -- GPIO3 pin 53 (shared with AK4358) + * CCLK (pin 34) -- GPIO1 pin 51 (shared with AK4358) + * CSN (pin 35) -- GPIO7 pin 59 + */ +#define AK4114_ADDR 0x00 + +static void write_data(struct snd_ice1712 *ice, unsigned int gpio, + unsigned int data, int idx) +{ + for (; idx >= 0; idx--) { + /* drop clock */ + gpio &= ~VT1724_REVO_CCLK; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + /* set data */ + if (data & (1 << idx)) + gpio |= VT1724_REVO_CDOUT; + else + gpio &= ~VT1724_REVO_CDOUT; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + /* raise clock */ + gpio |= VT1724_REVO_CCLK; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + } +} + +static unsigned char read_data(struct snd_ice1712 *ice, unsigned int gpio, + int idx) +{ + unsigned char data = 0; + + for (; idx >= 0; idx--) { + /* drop clock */ + gpio &= ~VT1724_REVO_CCLK; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + /* read data */ + if (snd_ice1712_gpio_read(ice) & VT1724_REVO_CDIN) + data |= (1 << idx); + udelay(1); + /* raise clock */ + gpio |= VT1724_REVO_CCLK; + snd_ice1712_gpio_write(ice, gpio); + udelay(1); + } + return data; +} + +static unsigned int ap192_4wire_start(struct snd_ice1712 *ice) +{ + unsigned int tmp; + + snd_ice1712_save_gpio_status(ice); + tmp = snd_ice1712_gpio_read(ice); + tmp |= VT1724_REVO_CCLK; /* high at init */ + tmp |= VT1724_REVO_CS0; + tmp &= ~VT1724_REVO_CS3; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + return tmp; +} + +static void ap192_4wire_finish(struct snd_ice1712 *ice, unsigned int tmp) +{ + tmp |= VT1724_REVO_CS3; + tmp |= VT1724_REVO_CS0; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + snd_ice1712_restore_gpio_status(ice); +} + +static void ap192_ak4114_write(void *private_data, unsigned char addr, + unsigned char data) +{ + struct snd_ice1712 *ice = private_data; + unsigned int tmp, addrdata; + + tmp = ap192_4wire_start(ice); + addrdata = (AK4114_ADDR << 6) | 0x20 | (addr & 0x1f); + addrdata = (addrdata << 8) | data; + write_data(ice, tmp, addrdata, 15); + ap192_4wire_finish(ice, tmp); +} + +static unsigned char ap192_ak4114_read(void *private_data, unsigned char addr) +{ + struct snd_ice1712 *ice = private_data; + unsigned int tmp; + unsigned char data; + + tmp = ap192_4wire_start(ice); + write_data(ice, tmp, (AK4114_ADDR << 6) | (addr & 0x1f), 7); + data = read_data(ice, tmp, 7); + ap192_4wire_finish(ice, tmp); + return data; +} + +static int ap192_ak4114_init(struct snd_ice1712 *ice) +{ + static const unsigned char ak4114_init_vals[] = { + AK4114_RST | AK4114_PWN | AK4114_OCKS0, + AK4114_DIF_I24I2S, + AK4114_TX1E, + AK4114_EFH_1024 | AK4114_DIT | AK4114_IPS(0), + 0, + 0 + }; + static const unsigned char ak4114_init_txcsb[] = { + 0x41, 0x02, 0x2c, 0x00, 0x00 + }; + int err; + + struct revo51_spec *spec; + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + err = snd_ak4114_create(ice->card, + ap192_ak4114_read, + ap192_ak4114_write, + ak4114_init_vals, ak4114_init_txcsb, + ice, &spec->ak4114); + if (err < 0) + return err; + /* AK4114 in Revo cannot detect external rate correctly. + * No reason to stop capture stream due to incorrect checks */ + spec->ak4114->check_flags = AK4114_CHECK_NO_RATE; + + return 0; +} + +static int revo_init(struct snd_ice1712 *ice) +{ + struct snd_akm4xxx *ak; + int err; + + /* determine I2C, DACs and ADCs */ + switch (ice->eeprom.subvendor) { + case VT1724_SUBDEVICE_REVOLUTION71: + ice->num_total_dacs = 8; + ice->num_total_adcs = 2; + ice->gpio.i2s_mclk_changed = revo_i2s_mclk_changed; + break; + case VT1724_SUBDEVICE_REVOLUTION51: + ice->num_total_dacs = 8; + ice->num_total_adcs = 2; + break; + case VT1724_SUBDEVICE_AUDIOPHILE192: + ice->num_total_dacs = 2; + ice->num_total_adcs = 2; + break; + default: + snd_BUG(); + return -EINVAL; + } + + /* second stage of initialization, analog parts and others */ + ak = ice->akm = kzalloc_objs(struct snd_akm4xxx, 2); + if (! ak) + return -ENOMEM; + switch (ice->eeprom.subvendor) { + case VT1724_SUBDEVICE_REVOLUTION71: + ice->akm_codecs = 2; + err = snd_ice1712_akm4xxx_init(ak, &akm_revo_front, + &akm_revo_front_priv, ice); + if (err < 0) + return err; + err = snd_ice1712_akm4xxx_init(ak+1, &akm_revo_surround, + &akm_revo_surround_priv, ice); + if (err < 0) + return err; + /* unmute all codecs */ + snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE, + VT1724_REVO_MUTE); + break; + case VT1724_SUBDEVICE_REVOLUTION51: + ice->akm_codecs = 2; + err = snd_ice1712_akm4xxx_init(ak, &akm_revo51, + &akm_revo51_priv, ice); + if (err < 0) + return err; + err = snd_ice1712_akm4xxx_init(ak+1, &akm_revo51_adc, + &akm_revo51_adc_priv, ice); + if (err < 0) + return err; + err = revo51_i2c_init(ice, &ptc_revo51_volume); + if (err < 0) + return err; + /* unmute all codecs */ + snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE, + VT1724_REVO_MUTE); + break; + case VT1724_SUBDEVICE_AUDIOPHILE192: + ice->akm_codecs = 1; + err = snd_ice1712_akm4xxx_init(ak, &akm_ap192, &akm_ap192_priv, + ice); + if (err < 0) + return err; + err = ap192_ak4114_init(ice); + if (err < 0) + return err; + + /* unmute all codecs */ + snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE, + VT1724_REVO_MUTE); + break; + } + + return 0; +} + + +static int revo_add_controls(struct snd_ice1712 *ice) +{ + struct revo51_spec *spec = ice->spec; + int err; + + switch (ice->eeprom.subvendor) { + case VT1724_SUBDEVICE_REVOLUTION71: + err = snd_ice1712_akm4xxx_build_controls(ice); + if (err < 0) + return err; + break; + case VT1724_SUBDEVICE_REVOLUTION51: + err = snd_ice1712_akm4xxx_build_controls(ice); + if (err < 0) + return err; + spec = ice->spec; + err = snd_pt2258_build_controls(spec->pt2258); + if (err < 0) + return err; + break; + case VT1724_SUBDEVICE_AUDIOPHILE192: + err = snd_ice1712_akm4xxx_build_controls(ice); + if (err < 0) + return err; + /* only capture SPDIF over AK4114 */ + err = snd_ak4114_build(spec->ak4114, NULL, + ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream); + if (err < 0) + return err; + break; + } + return 0; +} + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_revo_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_REVOLUTION71, + .name = "M Audio Revolution-7.1", + .model = "revo71", + .chip_init = revo_init, + .build_controls = revo_add_controls, + }, + { + .subvendor = VT1724_SUBDEVICE_REVOLUTION51, + .name = "M Audio Revolution-5.1", + .model = "revo51", + .chip_init = revo_init, + .build_controls = revo_add_controls, + }, + { + .subvendor = VT1724_SUBDEVICE_AUDIOPHILE192, + .name = "M Audio Audiophile192", + .model = "ap192", + .chip_init = revo_init, + .build_controls = revo_add_controls, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/revo.h b/sound/pci/ice1712/revo.h new file mode 100644 index 000000000..573ae2506 --- /dev/null +++ b/sound/pci/ice1712/revo.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_REVO_H +#define __SOUND_REVO_H + +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for M-Audio Revolution 7.1 + * + * Copyright (c) 2003 Takashi Iwai + */ + +#define REVO_DEVICE_DESC \ + "{MidiMan M Audio,Revolution 7.1},"\ + "{MidiMan M Audio,Revolution 5.1},"\ + "{MidiMan M Audio,Audiophile 192}," + +#define VT1724_SUBDEVICE_REVOLUTION71 0x12143036 +#define VT1724_SUBDEVICE_REVOLUTION51 0x12143136 +#define VT1724_SUBDEVICE_AUDIOPHILE192 0x12143236 + +/* entry point */ +extern struct snd_ice1712_card_info snd_vt1724_revo_cards[]; + + +/* + * MidiMan M-Audio Revolution GPIO definitions + */ + +#define VT1724_REVO_CCLK 0x02 +#define VT1724_REVO_CDIN 0x04 /* not used */ +#define VT1724_REVO_CDOUT 0x08 +#define VT1724_REVO_CS0 0x10 /* AK5365 chipselect for (revo51) */ +#define VT1724_REVO_CS1 0x20 /* front AKM4381 chipselect */ +#define VT1724_REVO_CS2 0x40 /* surround AKM4355 CS (revo71) */ +#define VT1724_REVO_I2C_DATA 0x40 /* I2C: PT 2258 SDA (on revo51) */ +#define VT1724_REVO_I2C_CLOCK 0x80 /* I2C: PT 2258 SCL (on revo51) */ +#define VT1724_REVO_CS3 0x80 /* AK4114 for AP192 */ +#define VT1724_REVO_MUTE (1<<22) /* 0 = all mute, 1 = normal operation */ + +#endif /* __SOUND_REVO_H */ diff --git a/sound/pci/ice1712/se.c b/sound/pci/ice1712/se.c new file mode 100644 index 000000000..d78328288 --- /dev/null +++ b/sound/pci/ice1712/se.c @@ -0,0 +1,752 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for ONKYO WAVIO SE-90PCI and SE-200PCI + * + * Copyright (c) 2007 Shin-ya Okada sh_okada(at)d4.dion.ne.jp + * (at) -> @ + */ + +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "se.h" + +struct se_spec { + struct { + unsigned char ch1, ch2; + } vol[8]; +}; + +/****************************************************************************/ +/* ONKYO WAVIO SE-200PCI */ +/****************************************************************************/ +/* + * system configuration ICE_EEP2_SYSCONF=0x4b + * XIN1 49.152MHz + * not have UART + * one stereo ADC and a S/PDIF receiver connected + * four stereo DACs connected + * + * AC-Link configuration ICE_EEP2_ACLINK=0x80 + * use I2C, not use AC97 + * + * I2S converters feature ICE_EEP2_I2S=0x78 + * I2S codec has no volume/mute control feature + * I2S codec supports 96KHz and 192KHz + * I2S codec 24bits + * + * S/PDIF configuration ICE_EEP2_SPDIF=0xc3 + * Enable integrated S/PDIF transmitter + * internal S/PDIF out implemented + * S/PDIF is stereo + * External S/PDIF out implemented + * + * + * ** connected chips ** + * + * WM8740 + * A 2ch-DAC of main outputs. + * It setuped as I2S mode by wire, so no way to setup from software. + * The sample-rate are automatically changed. + * ML/I2S (28pin) --------+ + * MC/DM1 (27pin) -- 5V | + * MD/DM0 (26pin) -- GND | + * MUTEB (25pin) -- NC | + * MODE (24pin) -- GND | + * CSBIW (23pin) --------+ + * | + * RSTB (22pin) --R(1K)-+ + * Probably it reduce the noise from the control line. + * + * WM8766 + * A 6ch-DAC for surrounds. + * It's control wire was connected to GPIOxx (3-wire serial interface) + * ML/I2S (11pin) -- GPIO18 + * MC/IWL (12pin) -- GPIO17 + * MD/DM (13pin) -- GPIO16 + * MUTE (14pin) -- GPIO01 + * + * WM8776 + * A 2ch-ADC(with 10ch-selector) plus 2ch-DAC. + * It's control wire was connected to SDA/SCLK (2-wire serial interface) + * MODE (16pin) -- R(1K) -- GND + * CE (17pin) -- R(1K) -- GND 2-wire mode (address=0x34) + * DI (18pin) -- SDA + * CL (19pin) -- SCLK + * + * + * ** output pins and device names ** + * + * 7.1ch name -- output connector color -- device (-D option) + * + * FRONT 2ch -- green -- plughw:0,0 + * CENTER(Lch) SUBWOOFER(Rch) -- black -- plughw:0,2,0 + * SURROUND 2ch -- orange -- plughw:0,2,1 + * SURROUND BACK 2ch -- white -- plughw:0,2,2 + * + */ + + +/****************************************************************************/ +/* WM8740 interface */ +/****************************************************************************/ + +static void se200pci_WM8740_init(struct snd_ice1712 *ice) +{ + /* nothing to do */ +} + + +static void se200pci_WM8740_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + /* nothing to do */ +} + + +/****************************************************************************/ +/* WM8766 interface */ +/****************************************************************************/ + +static void se200pci_WM8766_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int st; + unsigned int bits; + int i; + const unsigned int DATA = 0x010000; + const unsigned int CLOCK = 0x020000; + const unsigned int LOAD = 0x040000; + const unsigned int ALL_MASK = (DATA | CLOCK | LOAD); + + snd_ice1712_save_gpio_status(ice); + + st = ((addr & 0x7f) << 9) | (data & 0x1ff); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction | ALL_MASK); + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask & ~ALL_MASK); + bits = snd_ice1712_gpio_read(ice) & ~ALL_MASK; + + snd_ice1712_gpio_write(ice, bits); + for (i = 0; i < 16; i++) { + udelay(1); + bits &= ~CLOCK; + st = (st << 1); + if (st & 0x10000) + bits |= DATA; + else + bits &= ~DATA; + + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= CLOCK; + snd_ice1712_gpio_write(ice, bits); + } + + udelay(1); + bits |= LOAD; + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= (DATA | CLOCK); + snd_ice1712_gpio_write(ice, bits); + + snd_ice1712_restore_gpio_status(ice); +} + +static void se200pci_WM8766_set_volume(struct snd_ice1712 *ice, int ch, + unsigned int vol1, unsigned int vol2) +{ + switch (ch) { + case 0: + se200pci_WM8766_write(ice, 0x000, vol1); + se200pci_WM8766_write(ice, 0x001, vol2 | 0x100); + break; + case 1: + se200pci_WM8766_write(ice, 0x004, vol1); + se200pci_WM8766_write(ice, 0x005, vol2 | 0x100); + break; + case 2: + se200pci_WM8766_write(ice, 0x006, vol1); + se200pci_WM8766_write(ice, 0x007, vol2 | 0x100); + break; + } +} + +static void se200pci_WM8766_init(struct snd_ice1712 *ice) +{ + se200pci_WM8766_write(ice, 0x1f, 0x000); /* RESET ALL */ + udelay(10); + + se200pci_WM8766_set_volume(ice, 0, 0, 0); /* volume L=0 R=0 */ + se200pci_WM8766_set_volume(ice, 1, 0, 0); /* volume L=0 R=0 */ + se200pci_WM8766_set_volume(ice, 2, 0, 0); /* volume L=0 R=0 */ + + se200pci_WM8766_write(ice, 0x03, 0x022); /* serial mode I2S-24bits */ + se200pci_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ + se200pci_WM8766_write(ice, 0x12, 0x000); /* MDP=0 */ + se200pci_WM8766_write(ice, 0x15, 0x000); /* MDP=0 */ + se200pci_WM8766_write(ice, 0x09, 0x000); /* demp=off mute=off */ + + se200pci_WM8766_write(ice, 0x02, 0x124); /* ch-assign L=L R=R RESET */ + se200pci_WM8766_write(ice, 0x02, 0x120); /* ch-assign L=L R=R */ +} + +static void se200pci_WM8766_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + if (rate > 96000) + se200pci_WM8766_write(ice, 0x0a, 0x000); /* MCLK=128fs */ + else + se200pci_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ +} + + +/****************************************************************************/ +/* WM8776 interface */ +/****************************************************************************/ + +static void se200pci_WM8776_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int val; + + val = (addr << 9) | data; + snd_vt1724_write_i2c(ice, 0x34, val >> 8, val & 0xff); +} + + +static void se200pci_WM8776_set_output_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + se200pci_WM8776_write(ice, 0x03, vol1); + se200pci_WM8776_write(ice, 0x04, vol2 | 0x100); +} + +static void se200pci_WM8776_set_input_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + se200pci_WM8776_write(ice, 0x0e, vol1); + se200pci_WM8776_write(ice, 0x0f, vol2 | 0x100); +} + +static const char * const se200pci_sel[] = { + "LINE-IN", "CD-IN", "MIC-IN", "ALL-MIX", NULL +}; + +static void se200pci_WM8776_set_input_selector(struct snd_ice1712 *ice, + unsigned int sel) +{ + static const unsigned char vals[] = { + /* LINE, CD, MIC, ALL, GND */ + 0x10, 0x04, 0x08, 0x1c, 0x03 + }; + if (sel > 4) + sel = 4; + se200pci_WM8776_write(ice, 0x15, vals[sel]); +} + +static void se200pci_WM8776_set_afl(struct snd_ice1712 *ice, unsigned int afl) +{ + /* AFL -- After Fader Listening */ + if (afl) + se200pci_WM8776_write(ice, 0x16, 0x005); + else + se200pci_WM8776_write(ice, 0x16, 0x001); +} + +static const char * const se200pci_agc[] = { + "Off", "LimiterMode", "ALCMode", NULL +}; + +static void se200pci_WM8776_set_agc(struct snd_ice1712 *ice, unsigned int agc) +{ + /* AGC -- Auto Gain Control of the input */ + switch (agc) { + case 0: + se200pci_WM8776_write(ice, 0x11, 0x000); /* Off */ + break; + case 1: + se200pci_WM8776_write(ice, 0x10, 0x07b); + se200pci_WM8776_write(ice, 0x11, 0x100); /* LimiterMode */ + break; + case 2: + se200pci_WM8776_write(ice, 0x10, 0x1fb); + se200pci_WM8776_write(ice, 0x11, 0x100); /* ALCMode */ + break; + } +} + +static void se200pci_WM8776_init(struct snd_ice1712 *ice) +{ + int i; + static const unsigned short default_values[] = { + 0x100, 0x100, 0x100, + 0x100, 0x100, 0x100, + 0x000, 0x090, 0x000, 0x000, + 0x022, 0x022, 0x022, + 0x008, 0x0cf, 0x0cf, 0x07b, 0x000, + 0x032, 0x000, 0x0a6, 0x001, 0x001 + }; + + se200pci_WM8776_write(ice, 0x17, 0x000); /* reset all */ + /* ADC and DAC interface is I2S 24bits mode */ + /* The sample-rate are automatically changed */ + udelay(10); + /* BUT my board can not do reset all, so I load all by manually. */ + for (i = 0; i < ARRAY_SIZE(default_values); i++) + se200pci_WM8776_write(ice, i, default_values[i]); + + se200pci_WM8776_set_input_selector(ice, 0); + se200pci_WM8776_set_afl(ice, 0); + se200pci_WM8776_set_agc(ice, 0); + se200pci_WM8776_set_input_volume(ice, 0, 0); + se200pci_WM8776_set_output_volume(ice, 0, 0); + + /* head phone mute and power down */ + se200pci_WM8776_write(ice, 0x00, 0); + se200pci_WM8776_write(ice, 0x01, 0); + se200pci_WM8776_write(ice, 0x02, 0x100); + se200pci_WM8776_write(ice, 0x0d, 0x080); +} + +static void se200pci_WM8776_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + /* nothing to do */ +} + + +/****************************************************************************/ +/* runtime interface */ +/****************************************************************************/ + +static void se200pci_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + se200pci_WM8740_set_pro_rate(ice, rate); + se200pci_WM8766_set_pro_rate(ice, rate); + se200pci_WM8776_set_pro_rate(ice, rate); +} + +struct se200pci_control { + const char *name; + enum { + WM8766, + WM8776in, + WM8776out, + WM8776sel, + WM8776agc, + WM8776afl + } target; + enum { VOLUME1, VOLUME2, BOOLEAN, ENUM } type; + int ch; + const char * const *member; + const char *comment; +}; + +static const struct se200pci_control se200pci_cont[] = { + { + .name = "Front Playback Volume", + .target = WM8776out, + .type = VOLUME1, + .comment = "Front(green)" + }, + { + .name = "Side Playback Volume", + .target = WM8766, + .type = VOLUME1, + .ch = 1, + .comment = "Surround(orange)" + }, + { + .name = "Surround Playback Volume", + .target = WM8766, + .type = VOLUME1, + .ch = 2, + .comment = "SurroundBack(white)" + }, + { + .name = "CLFE Playback Volume", + .target = WM8766, + .type = VOLUME1, + .ch = 0, + .comment = "Center(Lch)&SubWoofer(Rch)(black)" + }, + { + .name = "Capture Volume", + .target = WM8776in, + .type = VOLUME2 + }, + { + .name = "Capture Select", + .target = WM8776sel, + .type = ENUM, + .member = se200pci_sel + }, + { + .name = "AGC Capture Mode", + .target = WM8776agc, + .type = ENUM, + .member = se200pci_agc + }, + { + .name = "AFL Bypass Playback Switch", + .target = WM8776afl, + .type = BOOLEAN + } +}; + +static int se200pci_get_enum_count(int n) +{ + const char * const *member; + int c; + + member = se200pci_cont[n].member; + if (!member) + return 0; + for (c = 0; member[c]; c++) + ; + return c; +} + +static int se200pci_cont_volume_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; /* mute */ + uinfo->value.integer.max = 0xff; /* 0dB */ + return 0; +} + +#define se200pci_cont_boolean_info snd_ctl_boolean_mono_info + +static int se200pci_cont_enum_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + int n, c; + + n = kc->private_value; + c = se200pci_get_enum_count(n); + if (!c) + return -EINVAL; + return snd_ctl_enum_info(uinfo, 1, c, se200pci_cont[n].member); +} + +static int se200pci_cont_volume_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct se_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.integer.value[0] = spec->vol[n].ch1; + uc->value.integer.value[1] = spec->vol[n].ch2; + return 0; +} + +static int se200pci_cont_boolean_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct se_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.integer.value[0] = spec->vol[n].ch1; + return 0; +} + +static int se200pci_cont_enum_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct se_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.enumerated.item[0] = spec->vol[n].ch1; + return 0; +} + +static void se200pci_cont_update(struct snd_ice1712 *ice, int n) +{ + struct se_spec *spec = ice->spec; + switch (se200pci_cont[n].target) { + case WM8766: + se200pci_WM8766_set_volume(ice, + se200pci_cont[n].ch, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776in: + se200pci_WM8776_set_input_volume(ice, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776out: + se200pci_WM8776_set_output_volume(ice, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776sel: + se200pci_WM8776_set_input_selector(ice, + spec->vol[n].ch1); + break; + + case WM8776agc: + se200pci_WM8776_set_agc(ice, spec->vol[n].ch1); + break; + + case WM8776afl: + se200pci_WM8776_set_afl(ice, spec->vol[n].ch1); + break; + + default: + break; + } +} + +static int se200pci_cont_volume_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct se_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1, vol2; + int changed; + + changed = 0; + vol1 = uc->value.integer.value[0] & 0xff; + vol2 = uc->value.integer.value[1] & 0xff; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + changed = 1; + } + if (spec->vol[n].ch2 != vol2) { + spec->vol[n].ch2 = vol2; + changed = 1; + } + if (changed) + se200pci_cont_update(ice, n); + + return changed; +} + +static int se200pci_cont_boolean_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct se_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1; + + vol1 = !!uc->value.integer.value[0]; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + se200pci_cont_update(ice, n); + return 1; + } + return 0; +} + +static int se200pci_cont_enum_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct se_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1; + + vol1 = uc->value.enumerated.item[0]; + if (vol1 >= se200pci_get_enum_count(n)) + return -EINVAL; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + se200pci_cont_update(ice, n); + return 1; + } + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_gain1, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_gain2, -10350, 50, 1); + +static int se200pci_add_controls(struct snd_ice1712 *ice) +{ + int i; + struct snd_kcontrol_new cont; + int err; + + memset(&cont, 0, sizeof(cont)); + cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + for (i = 0; i < ARRAY_SIZE(se200pci_cont); i++) { + cont.private_value = i; + cont.name = se200pci_cont[i].name; + cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + cont.tlv.p = NULL; + switch (se200pci_cont[i].type) { + case VOLUME1: + case VOLUME2: + cont.info = se200pci_cont_volume_info; + cont.get = se200pci_cont_volume_get; + cont.put = se200pci_cont_volume_put; + cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + if (se200pci_cont[i].type == VOLUME1) + cont.tlv.p = db_scale_gain1; + else + cont.tlv.p = db_scale_gain2; + break; + case BOOLEAN: + cont.info = se200pci_cont_boolean_info; + cont.get = se200pci_cont_boolean_get; + cont.put = se200pci_cont_boolean_put; + break; + case ENUM: + cont.info = se200pci_cont_enum_info; + cont.get = se200pci_cont_enum_get; + cont.put = se200pci_cont_enum_put; + break; + default: + snd_BUG(); + return -EINVAL; + } + err = snd_ctl_add(ice->card, snd_ctl_new1(&cont, ice)); + if (err < 0) + return err; + } + + return 0; +} + + +/****************************************************************************/ +/* ONKYO WAVIO SE-90PCI */ +/****************************************************************************/ +/* + * system configuration ICE_EEP2_SYSCONF=0x4b + * AC-Link configuration ICE_EEP2_ACLINK=0x80 + * I2S converters feature ICE_EEP2_I2S=0x78 + * S/PDIF configuration ICE_EEP2_SPDIF=0xc3 + * + * ** connected chip ** + * + * WM8716 + * A 2ch-DAC of main outputs. + * It setuped as I2S mode by wire, so no way to setup from software. + * ML/I2S (28pin) -- +5V + * MC/DM1 (27pin) -- GND + * MC/DM0 (26pin) -- GND + * MUTEB (25pin) -- open (internal pull-up) + * MODE (24pin) -- GND + * CSBIWO (23pin) -- +5V + * + */ + + /* Nothing to do for this chip. */ + + +/****************************************************************************/ +/* probe/initialize/setup */ +/****************************************************************************/ + +static int se_init(struct snd_ice1712 *ice) +{ + struct se_spec *spec; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_SE90PCI) { + ice->num_total_dacs = 2; + ice->num_total_adcs = 0; + ice->vt1720 = 1; + return 0; + + } else if (ice->eeprom.subvendor == VT1724_SUBDEVICE_SE200PCI) { + ice->num_total_dacs = 8; + ice->num_total_adcs = 2; + se200pci_WM8740_init(ice); + se200pci_WM8766_init(ice); + se200pci_WM8776_init(ice); + ice->gpio.set_pro_rate = se200pci_set_pro_rate; + return 0; + } + + return -ENOENT; +} + +static int se_add_controls(struct snd_ice1712 *ice) +{ + int err; + + err = 0; + /* nothing to do for VT1724_SUBDEVICE_SE90PCI */ + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_SE200PCI) + err = se200pci_add_controls(ice); + + return err; +} + + +/****************************************************************************/ +/* entry point */ +/****************************************************************************/ + +static const unsigned char se200pci_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x4b, /* 49.152Hz, spdif-in/ADC, 4DACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0x78, /* 96k-ok, 24bit, 192k-ok */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + + [ICE_EEP2_GPIO_DIR] = 0x02, /* WM8766 mute 1=output */ + [ICE_EEP2_GPIO_DIR1] = 0x00, /* not used */ + [ICE_EEP2_GPIO_DIR2] = 0x07, /* WM8766 ML/MC/MD 1=output */ + + [ICE_EEP2_GPIO_MASK] = 0x00, /* 0=writable */ + [ICE_EEP2_GPIO_MASK1] = 0x00, /* 0=writable */ + [ICE_EEP2_GPIO_MASK2] = 0x00, /* 0=writable */ + + [ICE_EEP2_GPIO_STATE] = 0x00, /* WM8766 mute=0 */ + [ICE_EEP2_GPIO_STATE1] = 0x00, /* not used */ + [ICE_EEP2_GPIO_STATE2] = 0x07, /* WM8766 ML/MC/MD */ +}; + +static const unsigned char se90pci_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x4b, /* 49.152Hz, spdif-in/ADC, 4DACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0x78, /* 96k-ok, 24bit, 192k-ok */ + [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */ + + /* ALL GPIO bits are in input mode */ +}; + +struct snd_ice1712_card_info snd_vt1724_se_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_SE200PCI, + .name = "ONKYO SE200PCI", + .model = "se200pci", + .chip_init = se_init, + .build_controls = se_add_controls, + .eeprom_size = sizeof(se200pci_eeprom), + .eeprom_data = se200pci_eeprom, + }, + { + .subvendor = VT1724_SUBDEVICE_SE90PCI, + .name = "ONKYO SE90PCI", + .model = "se90pci", + .chip_init = se_init, + .build_controls = se_add_controls, + .eeprom_size = sizeof(se90pci_eeprom), + .eeprom_data = se90pci_eeprom, + }, + {} /*terminator*/ +}; diff --git a/sound/pci/ice1712/se.h b/sound/pci/ice1712/se.h new file mode 100644 index 000000000..61348ecef --- /dev/null +++ b/sound/pci/ice1712/se.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_SE_H +#define __SOUND_SE_H + +/* ID */ +#define SE_DEVICE_DESC \ + "{ONKYO INC,SE-90PCI},"\ + "{ONKYO INC,SE-200PCI}," + +#define VT1724_SUBDEVICE_SE90PCI 0xb161000 +#define VT1724_SUBDEVICE_SE200PCI 0xb160100 + +/* entry struct */ +extern struct snd_ice1712_card_info snd_vt1724_se_cards[]; + +#endif /* __SOUND_SE_H */ diff --git a/sound/pci/ice1712/stac946x.h b/sound/pci/ice1712/stac946x.h new file mode 100644 index 000000000..58f9f17a2 --- /dev/null +++ b/sound/pci/ice1712/stac946x.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_STAC946X_H +#define __SOUND_STAC946X_H + +#define STAC946X_RESET 0x00 +#define STAC946X_STATUS 0x01 +#define STAC946X_MASTER_VOLUME 0x02 +#define STAC946X_LF_VOLUME 0x03 +#define STAC946X_RF_VOLUME 0x04 +#define STAC946X_LR_VOLUME 0x05 +#define STAC946X_RR_VOLUME 0x06 +#define STAC946X_CENTER_VOLUME 0x07 +#define STAC946X_LFE_VOLUME 0x08 +#define STAC946X_MIC_L_VOLUME 0x09 +#define STAC946X_MIC_R_VOLUME 0x0a +#define STAC946X_DEEMPHASIS 0x0c +#define STAC946X_GENERAL_PURPOSE 0x0d +#define STAC946X_AUDIO_PORT_CONTROL 0x0e +#define STAC946X_MASTER_CLOCKING 0x0f +#define STAC946X_POWERDOWN_CTRL1 0x10 +#define STAC946X_POWERDOWN_CTRL2 0x11 +#define STAC946X_REVISION_CODE 0x12 +#define STAC946X_ADDRESS_CONTROL 0x13 +#define STAC946X_ADDRESS 0x14 + +#endif /* __SOUND_STAC946X_H */ diff --git a/sound/pci/ice1712/vt1720_mobo.c b/sound/pci/ice1712/vt1720_mobo.c new file mode 100644 index 000000000..47470b071 --- /dev/null +++ b/sound/pci/ice1712/vt1720_mobo.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for VT1720/VT1724 (Envy24PT/Envy24HT) + * + * Lowlevel functions for VT1720-based motherboards + * + * Copyright (c) 2004 Takashi Iwai + */ + +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "vt1720_mobo.h" + + +static int k8x800_init(struct snd_ice1712 *ice) +{ + ice->vt1720 = 1; + + /* VT1616 codec */ + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + + /* WM8728 codec */ + /* FIXME: TODO */ + + return 0; +} + +static int k8x800_add_controls(struct snd_ice1712 *ice) +{ + /* FIXME: needs some quirks for VT1616? */ + return 0; +} + +/* EEPROM image */ + +static const unsigned char k8x800_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x01, /* clock 256, 1ADC, 2DACs */ + [ICE_EEP2_ACLINK] = 0x02, /* ACLINK, packed */ + [ICE_EEP2_I2S] = 0x00, /* - */ + [ICE_EEP2_SPDIF] = 0x00, /* - */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0x00, /* - */ + [ICE_EEP2_GPIO_MASK] = 0xff, + [ICE_EEP2_GPIO_MASK1] = 0xff, + [ICE_EEP2_GPIO_MASK2] = 0x00, /* - */ + [ICE_EEP2_GPIO_STATE] = 0x00, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, /* - */ +}; + +static const unsigned char sn25p_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x01, /* clock 256, 1ADC, 2DACs */ + [ICE_EEP2_ACLINK] = 0x02, /* ACLINK, packed */ + [ICE_EEP2_I2S] = 0x00, /* - */ + [ICE_EEP2_SPDIF] = 0x41, /* - */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0x00, /* - */ + [ICE_EEP2_GPIO_MASK] = 0xff, + [ICE_EEP2_GPIO_MASK1] = 0xff, + [ICE_EEP2_GPIO_MASK2] = 0x00, /* - */ + [ICE_EEP2_GPIO_STATE] = 0x00, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, /* - */ +}; + + +/* entry point */ +struct snd_ice1712_card_info snd_vt1720_mobo_cards[] = { + { + .subvendor = VT1720_SUBDEVICE_K8X800, + .name = "Albatron K8X800 Pro II", + .model = "k8x800", + .chip_init = k8x800_init, + .build_controls = k8x800_add_controls, + .eeprom_size = sizeof(k8x800_eeprom), + .eeprom_data = k8x800_eeprom, + }, + { + .subvendor = VT1720_SUBDEVICE_ZNF3_150, + .name = "Chaintech ZNF3-150", + /* identical with k8x800 */ + .chip_init = k8x800_init, + .build_controls = k8x800_add_controls, + .eeprom_size = sizeof(k8x800_eeprom), + .eeprom_data = k8x800_eeprom, + }, + { + .subvendor = VT1720_SUBDEVICE_ZNF3_250, + .name = "Chaintech ZNF3-250", + /* identical with k8x800 */ + .chip_init = k8x800_init, + .build_controls = k8x800_add_controls, + .eeprom_size = sizeof(k8x800_eeprom), + .eeprom_data = k8x800_eeprom, + }, + { + .subvendor = VT1720_SUBDEVICE_9CJS, + .name = "Chaintech 9CJS", + /* identical with k8x800 */ + .chip_init = k8x800_init, + .build_controls = k8x800_add_controls, + .eeprom_size = sizeof(k8x800_eeprom), + .eeprom_data = k8x800_eeprom, + }, + { + .subvendor = VT1720_SUBDEVICE_SN25P, + .name = "Shuttle SN25P", + .model = "sn25p", + .chip_init = k8x800_init, + .build_controls = k8x800_add_controls, + .eeprom_size = sizeof(k8x800_eeprom), + .eeprom_data = sn25p_eeprom, + }, + { } /* terminator */ +}; + diff --git a/sound/pci/ice1712/vt1720_mobo.h b/sound/pci/ice1712/vt1720_mobo.h new file mode 100644 index 000000000..90c77d827 --- /dev/null +++ b/sound/pci/ice1712/vt1720_mobo.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_VT1720_MOBO_H +#define __SOUND_VT1720_MOBO_H + +/* + * ALSA driver for VT1720/VT1724 (Envy24PT/Envy24HT) + * + * Lowlevel functions for VT1720-based motherboards + * + * Copyright (c) 2004 Takashi Iwai + */ + +#define VT1720_MOBO_DEVICE_DESC "{Albatron,K8X800 Pro II},"\ + "{Chaintech,ZNF3-150},"\ + "{Chaintech,ZNF3-250},"\ + "{Chaintech,9CJS},"\ + "{Shuttle,SN25P}," + +#define VT1720_SUBDEVICE_K8X800 0xf217052c +#define VT1720_SUBDEVICE_ZNF3_150 0x0f2741f6 +#define VT1720_SUBDEVICE_ZNF3_250 0x0f2745f6 +#define VT1720_SUBDEVICE_9CJS 0x0f272327 +#define VT1720_SUBDEVICE_SN25P 0x97123650 + +extern struct snd_ice1712_card_info snd_vt1720_mobo_cards[]; + +#endif /* __SOUND_VT1720_MOBO_H */ diff --git a/sound/pci/ice1712/wm8766.c b/sound/pci/ice1712/wm8766.c new file mode 100644 index 000000000..fe3e243b3 --- /dev/null +++ b/sound/pci/ice1712/wm8766.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT17xx + * + * Lowlevel functions for WM8766 codec + * + * Copyright (c) 2012 Ondrej Zary + */ + +#include +#include +#include +#include +#include "wm8766.h" + +/* low-level access */ + +static void snd_wm8766_write(struct snd_wm8766 *wm, u16 addr, u16 data) +{ + if (addr < WM8766_REG_COUNT) + wm->regs[addr] = data; + wm->ops.write(wm, addr, data); +} + +/* mixer controls */ + +static const DECLARE_TLV_DB_SCALE(wm8766_tlv, -12750, 50, 1); + +static const struct snd_wm8766_ctl snd_wm8766_default_ctl[WM8766_CTL_COUNT] = { + [WM8766_CTL_CH1_VOL] = { + .name = "Channel 1 Playback Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8766_tlv, + .reg1 = WM8766_REG_DACL1, + .reg2 = WM8766_REG_DACR1, + .mask1 = WM8766_VOL_MASK, + .mask2 = WM8766_VOL_MASK, + .max = 0xff, + .flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE, + }, + [WM8766_CTL_CH2_VOL] = { + .name = "Channel 2 Playback Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8766_tlv, + .reg1 = WM8766_REG_DACL2, + .reg2 = WM8766_REG_DACR2, + .mask1 = WM8766_VOL_MASK, + .mask2 = WM8766_VOL_MASK, + .max = 0xff, + .flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE, + }, + [WM8766_CTL_CH3_VOL] = { + .name = "Channel 3 Playback Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8766_tlv, + .reg1 = WM8766_REG_DACL3, + .reg2 = WM8766_REG_DACR3, + .mask1 = WM8766_VOL_MASK, + .mask2 = WM8766_VOL_MASK, + .max = 0xff, + .flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE, + }, + [WM8766_CTL_CH1_SW] = { + .name = "Channel 1 Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_DACCTRL2, + .mask1 = WM8766_DAC2_MUTE1, + .flags = WM8766_FLAG_INVERT, + }, + [WM8766_CTL_CH2_SW] = { + .name = "Channel 2 Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_DACCTRL2, + .mask1 = WM8766_DAC2_MUTE2, + .flags = WM8766_FLAG_INVERT, + }, + [WM8766_CTL_CH3_SW] = { + .name = "Channel 3 Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_DACCTRL2, + .mask1 = WM8766_DAC2_MUTE3, + .flags = WM8766_FLAG_INVERT, + }, + [WM8766_CTL_PHASE1_SW] = { + .name = "Channel 1 Phase Invert Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_IFCTRL, + .mask1 = WM8766_PHASE_INVERT1, + }, + [WM8766_CTL_PHASE2_SW] = { + .name = "Channel 2 Phase Invert Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_IFCTRL, + .mask1 = WM8766_PHASE_INVERT2, + }, + [WM8766_CTL_PHASE3_SW] = { + .name = "Channel 3 Phase Invert Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_IFCTRL, + .mask1 = WM8766_PHASE_INVERT3, + }, + [WM8766_CTL_DEEMPH1_SW] = { + .name = "Channel 1 Deemphasis Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_DACCTRL2, + .mask1 = WM8766_DAC2_DEEMP1, + }, + [WM8766_CTL_DEEMPH2_SW] = { + .name = "Channel 2 Deemphasis Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_DACCTRL2, + .mask1 = WM8766_DAC2_DEEMP2, + }, + [WM8766_CTL_DEEMPH3_SW] = { + .name = "Channel 3 Deemphasis Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_DACCTRL2, + .mask1 = WM8766_DAC2_DEEMP3, + }, + [WM8766_CTL_IZD_SW] = { + .name = "Infinite Zero Detect Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_DACCTRL1, + .mask1 = WM8766_DAC_IZD, + }, + [WM8766_CTL_ZC_SW] = { + .name = "Zero Cross Detect Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8766_REG_DACCTRL2, + .mask1 = WM8766_DAC2_ZCD, + .flags = WM8766_FLAG_INVERT, + }, +}; + +/* exported functions */ + +void snd_wm8766_init(struct snd_wm8766 *wm) +{ + int i; + static const u16 default_values[] = { + 0x000, 0x100, + 0x120, 0x000, + 0x000, 0x100, 0x000, 0x100, 0x000, + 0x000, 0x080, + }; + + memcpy(wm->ctl, snd_wm8766_default_ctl, sizeof(wm->ctl)); + + snd_wm8766_write(wm, WM8766_REG_RESET, 0x00); /* reset */ + udelay(10); + /* load defaults */ + for (i = 0; i < ARRAY_SIZE(default_values); i++) + snd_wm8766_write(wm, i, default_values[i]); +} + +void snd_wm8766_resume(struct snd_wm8766 *wm) +{ + int i; + + for (i = 0; i < WM8766_REG_COUNT; i++) + snd_wm8766_write(wm, i, wm->regs[i]); +} + +void snd_wm8766_set_if(struct snd_wm8766 *wm, u16 dac) +{ + u16 val = wm->regs[WM8766_REG_IFCTRL] & ~WM8766_IF_MASK; + + dac &= WM8766_IF_MASK; + snd_wm8766_write(wm, WM8766_REG_IFCTRL, val | dac); +} + +void snd_wm8766_volume_restore(struct snd_wm8766 *wm) +{ + u16 val = wm->regs[WM8766_REG_DACR1]; + /* restore volume after MCLK stopped */ + snd_wm8766_write(wm, WM8766_REG_DACR1, val | WM8766_VOL_UPDATE); +} + +/* mixer callbacks */ + +static int snd_wm8766_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = (wm->ctl[n].flags & WM8766_FLAG_STEREO) ? 2 : 1; + uinfo->value.integer.min = wm->ctl[n].min; + uinfo->value.integer.max = wm->ctl[n].max; + + return 0; +} + +static int snd_wm8766_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + + return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max, + wm->ctl[n].enum_names); +} + +static int snd_wm8766_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + u16 val1, val2; + + if (wm->ctl[n].get) + wm->ctl[n].get(wm, &val1, &val2); + else { + val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1; + val1 >>= __ffs(wm->ctl[n].mask1); + if (wm->ctl[n].flags & WM8766_FLAG_STEREO) { + val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2; + val2 >>= __ffs(wm->ctl[n].mask2); + if (wm->ctl[n].flags & WM8766_FLAG_VOL_UPDATE) + val2 &= ~WM8766_VOL_UPDATE; + } + } + if (wm->ctl[n].flags & WM8766_FLAG_INVERT) { + val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min); + if (wm->ctl[n].flags & WM8766_FLAG_STEREO) + val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min); + } + ucontrol->value.integer.value[0] = val1; + if (wm->ctl[n].flags & WM8766_FLAG_STEREO) + ucontrol->value.integer.value[1] = val2; + + return 0; +} + +static int snd_wm8766_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + u16 val, regval1, regval2; + + /* this also works for enum because value is a union */ + regval1 = ucontrol->value.integer.value[0]; + regval2 = ucontrol->value.integer.value[1]; + if (wm->ctl[n].flags & WM8766_FLAG_INVERT) { + regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min); + regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min); + } + if (wm->ctl[n].set) + wm->ctl[n].set(wm, regval1, regval2); + else { + val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1; + val |= regval1 << __ffs(wm->ctl[n].mask1); + /* both stereo controls in one register */ + if (wm->ctl[n].flags & WM8766_FLAG_STEREO && + wm->ctl[n].reg1 == wm->ctl[n].reg2) { + val &= ~wm->ctl[n].mask2; + val |= regval2 << __ffs(wm->ctl[n].mask2); + } + snd_wm8766_write(wm, wm->ctl[n].reg1, val); + /* stereo controls in different registers */ + if (wm->ctl[n].flags & WM8766_FLAG_STEREO && + wm->ctl[n].reg1 != wm->ctl[n].reg2) { + val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2; + val |= regval2 << __ffs(wm->ctl[n].mask2); + if (wm->ctl[n].flags & WM8766_FLAG_VOL_UPDATE) + val |= WM8766_VOL_UPDATE; + snd_wm8766_write(wm, wm->ctl[n].reg2, val); + } + } + + return 0; +} + +static int snd_wm8766_add_control(struct snd_wm8766 *wm, int num) +{ + struct snd_kcontrol_new cont; + struct snd_kcontrol *ctl; + + memset(&cont, 0, sizeof(cont)); + cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cont.private_value = num; + cont.name = wm->ctl[num].name; + cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + if (wm->ctl[num].flags & WM8766_FLAG_LIM || + wm->ctl[num].flags & WM8766_FLAG_ALC) + cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + cont.tlv.p = NULL; + cont.get = snd_wm8766_ctl_get; + cont.put = snd_wm8766_ctl_put; + + switch (wm->ctl[num].type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER: + cont.info = snd_wm8766_volume_info; + cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + cont.tlv.p = wm->ctl[num].tlv; + break; + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + wm->ctl[num].max = 1; + if (wm->ctl[num].flags & WM8766_FLAG_STEREO) + cont.info = snd_ctl_boolean_stereo_info; + else + cont.info = snd_ctl_boolean_mono_info; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + cont.info = snd_wm8766_enum_info; + break; + default: + return -EINVAL; + } + ctl = snd_ctl_new1(&cont, wm); + if (!ctl) + return -ENOMEM; + wm->ctl[num].kctl = ctl; + + return snd_ctl_add(wm->card, ctl); +} + +int snd_wm8766_build_controls(struct snd_wm8766 *wm) +{ + int err, i; + + for (i = 0; i < WM8766_CTL_COUNT; i++) + if (wm->ctl[i].name) { + err = snd_wm8766_add_control(wm, i); + if (err < 0) + return err; + } + + return 0; +} diff --git a/sound/pci/ice1712/wm8766.h b/sound/pci/ice1712/wm8766.h new file mode 100644 index 000000000..44ab7c72a --- /dev/null +++ b/sound/pci/ice1712/wm8766.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_WM8766_H +#define __SOUND_WM8766_H + +/* + * ALSA driver for ICEnsemble VT17xx + * + * Lowlevel functions for WM8766 codec + * + * Copyright (c) 2012 Ondrej Zary + */ + +#define WM8766_REG_DACL1 0x00 +#define WM8766_REG_DACR1 0x01 +#define WM8766_VOL_MASK 0x1ff /* incl. update bit */ +#define WM8766_VOL_UPDATE (1 << 8) /* update volume */ +#define WM8766_REG_DACCTRL1 0x02 +#define WM8766_DAC_MUTEALL (1 << 0) +#define WM8766_DAC_DEEMPALL (1 << 1) +#define WM8766_DAC_PDWN (1 << 2) +#define WM8766_DAC_ATC (1 << 3) +#define WM8766_DAC_IZD (1 << 4) +#define WM8766_DAC_PL_MASK 0x1e0 +#define WM8766_DAC_PL_LL (1 << 5) /* L chan: L signal */ +#define WM8766_DAC_PL_LR (2 << 5) /* L chan: R signal */ +#define WM8766_DAC_PL_LB (3 << 5) /* L chan: both */ +#define WM8766_DAC_PL_RL (1 << 7) /* R chan: L signal */ +#define WM8766_DAC_PL_RR (2 << 7) /* R chan: R signal */ +#define WM8766_DAC_PL_RB (3 << 7) /* R chan: both */ +#define WM8766_REG_IFCTRL 0x03 +#define WM8766_IF_FMT_RIGHTJ (0 << 0) +#define WM8766_IF_FMT_LEFTJ (1 << 0) +#define WM8766_IF_FMT_I2S (2 << 0) +#define WM8766_IF_FMT_DSP (3 << 0) +#define WM8766_IF_DSP_LATE (1 << 2) /* in DSP mode */ +#define WM8766_IF_LRC_INVERTED (1 << 2) /* in other modes */ +#define WM8766_IF_BCLK_INVERTED (1 << 3) +#define WM8766_IF_IWL_16BIT (0 << 4) +#define WM8766_IF_IWL_20BIT (1 << 4) +#define WM8766_IF_IWL_24BIT (2 << 4) +#define WM8766_IF_IWL_32BIT (3 << 4) +#define WM8766_IF_MASK 0x3f +#define WM8766_PHASE_INVERT1 (1 << 6) +#define WM8766_PHASE_INVERT2 (1 << 7) +#define WM8766_PHASE_INVERT3 (1 << 8) +#define WM8766_REG_DACL2 0x04 +#define WM8766_REG_DACR2 0x05 +#define WM8766_REG_DACL3 0x06 +#define WM8766_REG_DACR3 0x07 +#define WM8766_REG_MASTDA 0x08 +#define WM8766_REG_DACCTRL2 0x09 +#define WM8766_DAC2_ZCD (1 << 0) +#define WM8766_DAC2_ZFLAG_ALL (0 << 1) +#define WM8766_DAC2_ZFLAG_1 (1 << 1) +#define WM8766_DAC2_ZFLAG_2 (2 << 1) +#define WM8766_DAC2_ZFLAG_3 (3 << 1) +#define WM8766_DAC2_MUTE1 (1 << 3) +#define WM8766_DAC2_MUTE2 (1 << 4) +#define WM8766_DAC2_MUTE3 (1 << 5) +#define WM8766_DAC2_DEEMP1 (1 << 6) +#define WM8766_DAC2_DEEMP2 (1 << 7) +#define WM8766_DAC2_DEEMP3 (1 << 8) +#define WM8766_REG_DACCTRL3 0x0a +#define WM8766_DAC3_DACPD1 (1 << 1) +#define WM8766_DAC3_DACPD2 (1 << 2) +#define WM8766_DAC3_DACPD3 (1 << 3) +#define WM8766_DAC3_PWRDNALL (1 << 4) +#define WM8766_DAC3_POWER_MASK 0x1e +#define WM8766_DAC3_MASTER (1 << 5) +#define WM8766_DAC3_DAC128FS (0 << 6) +#define WM8766_DAC3_DAC192FS (1 << 6) +#define WM8766_DAC3_DAC256FS (2 << 6) +#define WM8766_DAC3_DAC384FS (3 << 6) +#define WM8766_DAC3_DAC512FS (4 << 6) +#define WM8766_DAC3_DAC768FS (5 << 6) +#define WM8766_DAC3_MSTR_MASK 0x1e0 +#define WM8766_REG_MUTE1 0x0c +#define WM8766_MUTE1_MPD (1 << 6) +#define WM8766_REG_MUTE2 0x0f +#define WM8766_MUTE2_MPD (1 << 5) +#define WM8766_REG_RESET 0x1f + +#define WM8766_REG_COUNT 0x10 /* don't cache the RESET register */ + +struct snd_wm8766; + +struct snd_wm8766_ops { + void (*write)(struct snd_wm8766 *wm, u16 addr, u16 data); +}; + +enum snd_wm8766_ctl_id { + WM8766_CTL_CH1_VOL, + WM8766_CTL_CH2_VOL, + WM8766_CTL_CH3_VOL, + WM8766_CTL_CH1_SW, + WM8766_CTL_CH2_SW, + WM8766_CTL_CH3_SW, + WM8766_CTL_PHASE1_SW, + WM8766_CTL_PHASE2_SW, + WM8766_CTL_PHASE3_SW, + WM8766_CTL_DEEMPH1_SW, + WM8766_CTL_DEEMPH2_SW, + WM8766_CTL_DEEMPH3_SW, + WM8766_CTL_IZD_SW, + WM8766_CTL_ZC_SW, + + WM8766_CTL_COUNT, +}; + +#define WM8766_ENUM_MAX 16 + +#define WM8766_FLAG_STEREO (1 << 0) +#define WM8766_FLAG_VOL_UPDATE (1 << 1) +#define WM8766_FLAG_INVERT (1 << 2) +#define WM8766_FLAG_LIM (1 << 3) +#define WM8766_FLAG_ALC (1 << 4) + +struct snd_wm8766_ctl { + struct snd_kcontrol *kctl; + const char *name; + snd_ctl_elem_type_t type; + const char *const enum_names[WM8766_ENUM_MAX]; + const unsigned int *tlv; + u16 reg1, reg2, mask1, mask2, min, max, flags; + void (*set)(struct snd_wm8766 *wm, u16 ch1, u16 ch2); + void (*get)(struct snd_wm8766 *wm, u16 *ch1, u16 *ch2); +}; + +enum snd_wm8766_agc_mode { WM8766_AGC_OFF, WM8766_AGC_LIM, WM8766_AGC_ALC }; + +struct snd_wm8766 { + struct snd_card *card; + struct snd_wm8766_ctl ctl[WM8766_CTL_COUNT]; + enum snd_wm8766_agc_mode agc_mode; + struct snd_wm8766_ops ops; + u16 regs[WM8766_REG_COUNT]; /* 9-bit registers */ +}; + + + +void snd_wm8766_init(struct snd_wm8766 *wm); +void snd_wm8766_resume(struct snd_wm8766 *wm); +void snd_wm8766_set_if(struct snd_wm8766 *wm, u16 dac); +void snd_wm8766_volume_restore(struct snd_wm8766 *wm); +int snd_wm8766_build_controls(struct snd_wm8766 *wm); + +#endif /* __SOUND_WM8766_H */ diff --git a/sound/pci/ice1712/wm8776.c b/sound/pci/ice1712/wm8776.c new file mode 100644 index 000000000..493425697 --- /dev/null +++ b/sound/pci/ice1712/wm8776.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT17xx + * + * Lowlevel functions for WM8776 codec + * + * Copyright (c) 2012 Ondrej Zary + */ + +#include +#include +#include +#include +#include "wm8776.h" + +/* low-level access */ + +static void snd_wm8776_write(struct snd_wm8776 *wm, u16 addr, u16 data) +{ + u8 bus_addr = addr << 1 | data >> 8; /* addr + 9th data bit */ + u8 bus_data = data & 0xff; /* remaining 8 data bits */ + + if (addr < WM8776_REG_RESET) + wm->regs[addr] = data; + wm->ops.write(wm, bus_addr, bus_data); +} + +/* register-level functions */ + +static void snd_wm8776_activate_ctl(struct snd_wm8776 *wm, + const char *ctl_name, + bool active) +{ + struct snd_card *card = wm->card; + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + + kctl = snd_ctl_find_id_mixer(card, ctl_name); + if (!kctl) + return; + index_offset = snd_ctl_get_ioff(kctl, &kctl->id); + vd = &kctl->vd[index_offset]; + if (active) + vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + else + vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id); +} + +static void snd_wm8776_update_agc_ctl(struct snd_wm8776 *wm) +{ + int i, flags_on = 0, flags_off = 0; + + switch (wm->agc_mode) { + case WM8776_AGC_OFF: + flags_off = WM8776_FLAG_LIM | WM8776_FLAG_ALC; + break; + case WM8776_AGC_LIM: + flags_off = WM8776_FLAG_ALC; + flags_on = WM8776_FLAG_LIM; + break; + case WM8776_AGC_ALC_R: + case WM8776_AGC_ALC_L: + case WM8776_AGC_ALC_STEREO: + flags_off = WM8776_FLAG_LIM; + flags_on = WM8776_FLAG_ALC; + break; + } + + for (i = 0; i < WM8776_CTL_COUNT; i++) + if (wm->ctl[i].flags & flags_off) + snd_wm8776_activate_ctl(wm, wm->ctl[i].name, false); + else if (wm->ctl[i].flags & flags_on) + snd_wm8776_activate_ctl(wm, wm->ctl[i].name, true); +} + +static void snd_wm8776_set_agc(struct snd_wm8776 *wm, u16 agc, u16 nothing) +{ + u16 alc1 = wm->regs[WM8776_REG_ALCCTRL1] & ~WM8776_ALC1_LCT_MASK; + u16 alc2 = wm->regs[WM8776_REG_ALCCTRL2] & ~WM8776_ALC2_LCEN; + + switch (agc) { + case 0: /* Off */ + wm->agc_mode = WM8776_AGC_OFF; + break; + case 1: /* Limiter */ + alc2 |= WM8776_ALC2_LCEN; + wm->agc_mode = WM8776_AGC_LIM; + break; + case 2: /* ALC Right */ + alc1 |= WM8776_ALC1_LCSEL_ALCR; + alc2 |= WM8776_ALC2_LCEN; + wm->agc_mode = WM8776_AGC_ALC_R; + break; + case 3: /* ALC Left */ + alc1 |= WM8776_ALC1_LCSEL_ALCL; + alc2 |= WM8776_ALC2_LCEN; + wm->agc_mode = WM8776_AGC_ALC_L; + break; + case 4: /* ALC Stereo */ + alc1 |= WM8776_ALC1_LCSEL_ALCSTEREO; + alc2 |= WM8776_ALC2_LCEN; + wm->agc_mode = WM8776_AGC_ALC_STEREO; + break; + } + snd_wm8776_write(wm, WM8776_REG_ALCCTRL1, alc1); + snd_wm8776_write(wm, WM8776_REG_ALCCTRL2, alc2); + snd_wm8776_update_agc_ctl(wm); +} + +static void snd_wm8776_get_agc(struct snd_wm8776 *wm, u16 *mode, u16 *nothing) +{ + *mode = wm->agc_mode; +} + +/* mixer controls */ + +static const DECLARE_TLV_DB_SCALE(wm8776_hp_tlv, -7400, 100, 1); +static const DECLARE_TLV_DB_SCALE(wm8776_dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(wm8776_adc_tlv, -10350, 50, 1); +static const DECLARE_TLV_DB_SCALE(wm8776_lct_tlv, -1600, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_tlv, 0, 400, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_ngth_tlv, -7800, 600, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_tlv, -2100, 400, 0); + +static const struct snd_wm8776_ctl snd_wm8776_default_ctl[WM8776_CTL_COUNT] = { + [WM8776_CTL_DAC_VOL] = { + .name = "Master Playback Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_dac_tlv, + .reg1 = WM8776_REG_DACLVOL, + .reg2 = WM8776_REG_DACRVOL, + .mask1 = WM8776_DACVOL_MASK, + .mask2 = WM8776_DACVOL_MASK, + .max = 0xff, + .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE, + }, + [WM8776_CTL_DAC_SW] = { + .name = "Master Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_DACCTRL1, + .reg2 = WM8776_REG_DACCTRL1, + .mask1 = WM8776_DAC_PL_LL, + .mask2 = WM8776_DAC_PL_RR, + .flags = WM8776_FLAG_STEREO, + }, + [WM8776_CTL_DAC_ZC_SW] = { + .name = "Master Zero Cross Detect Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_DACCTRL1, + .mask1 = WM8776_DAC_DZCEN, + }, + [WM8776_CTL_HP_VOL] = { + .name = "Headphone Playback Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_hp_tlv, + .reg1 = WM8776_REG_HPLVOL, + .reg2 = WM8776_REG_HPRVOL, + .mask1 = WM8776_HPVOL_MASK, + .mask2 = WM8776_HPVOL_MASK, + .min = 0x2f, + .max = 0x7f, + .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE, + }, + [WM8776_CTL_HP_SW] = { + .name = "Headphone Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_PWRDOWN, + .mask1 = WM8776_PWR_HPPD, + .flags = WM8776_FLAG_INVERT, + }, + [WM8776_CTL_HP_ZC_SW] = { + .name = "Headphone Zero Cross Detect Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_HPLVOL, + .reg2 = WM8776_REG_HPRVOL, + .mask1 = WM8776_VOL_HPZCEN, + .mask2 = WM8776_VOL_HPZCEN, + .flags = WM8776_FLAG_STEREO, + }, + [WM8776_CTL_AUX_SW] = { + .name = "AUX Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_OUTMUX, + .mask1 = WM8776_OUTMUX_AUX, + }, + [WM8776_CTL_BYPASS_SW] = { + .name = "Bypass Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_OUTMUX, + .mask1 = WM8776_OUTMUX_BYPASS, + }, + [WM8776_CTL_DAC_IZD_SW] = { + .name = "Infinite Zero Detect Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_DACCTRL1, + .mask1 = WM8776_DAC_IZD, + }, + [WM8776_CTL_PHASE_SW] = { + .name = "Phase Invert Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_PHASESWAP, + .reg2 = WM8776_REG_PHASESWAP, + .mask1 = WM8776_PHASE_INVERTL, + .mask2 = WM8776_PHASE_INVERTR, + .flags = WM8776_FLAG_STEREO, + }, + [WM8776_CTL_DEEMPH_SW] = { + .name = "Deemphasis Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_DACCTRL2, + .mask1 = WM8776_DAC2_DEEMPH, + }, + [WM8776_CTL_ADC_VOL] = { + .name = "Input Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_adc_tlv, + .reg1 = WM8776_REG_ADCLVOL, + .reg2 = WM8776_REG_ADCRVOL, + .mask1 = WM8776_ADC_GAIN_MASK, + .mask2 = WM8776_ADC_GAIN_MASK, + .max = 0xff, + .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE, + }, + [WM8776_CTL_ADC_SW] = { + .name = "Input Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .reg2 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUTEL, + .mask2 = WM8776_ADC_MUTER, + .flags = WM8776_FLAG_STEREO | WM8776_FLAG_INVERT, + }, + [WM8776_CTL_INPUT1_SW] = { + .name = "AIN1 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN1, + }, + [WM8776_CTL_INPUT2_SW] = { + .name = "AIN2 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN2, + }, + [WM8776_CTL_INPUT3_SW] = { + .name = "AIN3 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN3, + }, + [WM8776_CTL_INPUT4_SW] = { + .name = "AIN4 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN4, + }, + [WM8776_CTL_INPUT5_SW] = { + .name = "AIN5 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN5, + }, + [WM8776_CTL_AGC_SEL] = { + .name = "AGC Select Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "Off", "Limiter", "ALC Right", "ALC Left", + "ALC Stereo" }, + .max = 5, /* .enum_names item count */ + .set = snd_wm8776_set_agc, + .get = snd_wm8776_get_agc, + }, + [WM8776_CTL_LIM_THR] = { + .name = "Limiter Threshold Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_lct_tlv, + .reg1 = WM8776_REG_ALCCTRL1, + .mask1 = WM8776_ALC1_LCT_MASK, + .max = 15, + .flags = WM8776_FLAG_LIM, + }, + [WM8776_CTL_LIM_ATK] = { + .name = "Limiter Attack Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "0.25 ms", "0.5 ms", "1 ms", "2 ms", "4 ms", + "8 ms", "16 ms", "32 ms", "64 ms", "128 ms", "256 ms" }, + .max = 11, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL3, + .mask1 = WM8776_ALC3_ATK_MASK, + .flags = WM8776_FLAG_LIM, + }, + [WM8776_CTL_LIM_DCY] = { + .name = "Limiter Decay Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms", + "19.2 ms", "38.4 ms", "76.8 ms", "154 ms", "307 ms", + "614 ms", "1.23 s" }, + .max = 11, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL3, + .mask1 = WM8776_ALC3_DCY_MASK, + .flags = WM8776_FLAG_LIM, + }, + [WM8776_CTL_LIM_TRANWIN] = { + .name = "Limiter Transient Window Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "0 us", "62.5 us", "125 us", "250 us", "500 us", + "1 ms", "2 ms", "4 ms" }, + .max = 8, /* .enum_names item count */ + .reg1 = WM8776_REG_LIMITER, + .mask1 = WM8776_LIM_TRANWIN_MASK, + .flags = WM8776_FLAG_LIM, + }, + [WM8776_CTL_LIM_MAXATTN] = { + .name = "Limiter Maximum Attenuation Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_maxatten_lim_tlv, + .reg1 = WM8776_REG_LIMITER, + .mask1 = WM8776_LIM_MAXATTEN_MASK, + .min = 3, + .max = 12, + .flags = WM8776_FLAG_LIM | WM8776_FLAG_INVERT, + }, + [WM8776_CTL_ALC_TGT] = { + .name = "ALC Target Level Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_lct_tlv, + .reg1 = WM8776_REG_ALCCTRL1, + .mask1 = WM8776_ALC1_LCT_MASK, + .max = 15, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_ALC_ATK] = { + .name = "ALC Attack Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms", + "134 ms", "269 ms", "538 ms", "1.08 s", "2.15 s", + "4.3 s", "8.6 s" }, + .max = 11, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL3, + .mask1 = WM8776_ALC3_ATK_MASK, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_ALC_DCY] = { + .name = "ALC Decay Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "33.5 ms", "67.0 ms", "134 ms", "268 ms", + "536 ms", "1.07 s", "2.14 s", "4.29 s", "8.58 s", + "17.2 s", "34.3 s" }, + .max = 11, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL3, + .mask1 = WM8776_ALC3_DCY_MASK, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_ALC_MAXGAIN] = { + .name = "ALC Maximum Gain Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_maxgain_tlv, + .reg1 = WM8776_REG_ALCCTRL1, + .mask1 = WM8776_ALC1_MAXGAIN_MASK, + .min = 1, + .max = 7, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_ALC_MAXATTN] = { + .name = "ALC Maximum Attenuation Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_maxatten_alc_tlv, + .reg1 = WM8776_REG_LIMITER, + .mask1 = WM8776_LIM_MAXATTEN_MASK, + .min = 10, + .max = 15, + .flags = WM8776_FLAG_ALC | WM8776_FLAG_INVERT, + }, + [WM8776_CTL_ALC_HLD] = { + .name = "ALC Hold Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "0 ms", "2.67 ms", "5.33 ms", "10.6 ms", + "21.3 ms", "42.7 ms", "85.3 ms", "171 ms", "341 ms", + "683 ms", "1.37 s", "2.73 s", "5.46 s", "10.9 s", + "21.8 s", "43.7 s" }, + .max = 16, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL2, + .mask1 = WM8776_ALC2_HOLD_MASK, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_NGT_SW] = { + .name = "Noise Gate Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_NOISEGATE, + .mask1 = WM8776_NGAT_ENABLE, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_NGT_THR] = { + .name = "Noise Gate Threshold Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_ngth_tlv, + .reg1 = WM8776_REG_NOISEGATE, + .mask1 = WM8776_NGAT_THR_MASK, + .max = 7, + .flags = WM8776_FLAG_ALC, + }, +}; + +/* exported functions */ + +void snd_wm8776_init(struct snd_wm8776 *wm) +{ + int i; + static const u16 default_values[] = { + 0x000, 0x100, 0x000, + 0x000, 0x100, 0x000, + 0x000, 0x090, 0x000, 0x000, + 0x022, 0x022, 0x022, + 0x008, 0x0cf, 0x0cf, 0x07b, 0x000, + 0x032, 0x000, 0x0a6, 0x001, 0x001 + }; + + memcpy(wm->ctl, snd_wm8776_default_ctl, sizeof(wm->ctl)); + + snd_wm8776_write(wm, WM8776_REG_RESET, 0x00); /* reset */ + udelay(10); + /* load defaults */ + for (i = 0; i < ARRAY_SIZE(default_values); i++) + snd_wm8776_write(wm, i, default_values[i]); +} + +void snd_wm8776_resume(struct snd_wm8776 *wm) +{ + int i; + + for (i = 0; i < WM8776_REG_COUNT; i++) + snd_wm8776_write(wm, i, wm->regs[i]); +} + +void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power) +{ + snd_wm8776_write(wm, WM8776_REG_PWRDOWN, power); +} + +void snd_wm8776_volume_restore(struct snd_wm8776 *wm) +{ + u16 val = wm->regs[WM8776_REG_DACRVOL]; + /* restore volume after MCLK stopped */ + snd_wm8776_write(wm, WM8776_REG_DACRVOL, val | WM8776_VOL_UPDATE); +} + +/* mixer callbacks */ + +static int snd_wm8776_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = (wm->ctl[n].flags & WM8776_FLAG_STEREO) ? 2 : 1; + uinfo->value.integer.min = wm->ctl[n].min; + uinfo->value.integer.max = wm->ctl[n].max; + + return 0; +} + +static int snd_wm8776_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + + return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max, + wm->ctl[n].enum_names); +} + +static int snd_wm8776_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + u16 val1, val2; + + if (wm->ctl[n].get) + wm->ctl[n].get(wm, &val1, &val2); + else { + val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1; + val1 >>= __ffs(wm->ctl[n].mask1); + if (wm->ctl[n].flags & WM8776_FLAG_STEREO) { + val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2; + val2 >>= __ffs(wm->ctl[n].mask2); + if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE) + val2 &= ~WM8776_VOL_UPDATE; + } + } + if (wm->ctl[n].flags & WM8776_FLAG_INVERT) { + val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min); + if (wm->ctl[n].flags & WM8776_FLAG_STEREO) + val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min); + } + ucontrol->value.integer.value[0] = val1; + if (wm->ctl[n].flags & WM8776_FLAG_STEREO) + ucontrol->value.integer.value[1] = val2; + + return 0; +} + +static int snd_wm8776_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + u16 val, regval1, regval2; + + /* this also works for enum because value is a union */ + regval1 = ucontrol->value.integer.value[0]; + regval2 = ucontrol->value.integer.value[1]; + if (wm->ctl[n].flags & WM8776_FLAG_INVERT) { + regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min); + regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min); + } + if (wm->ctl[n].set) + wm->ctl[n].set(wm, regval1, regval2); + else { + val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1; + val |= regval1 << __ffs(wm->ctl[n].mask1); + /* both stereo controls in one register */ + if (wm->ctl[n].flags & WM8776_FLAG_STEREO && + wm->ctl[n].reg1 == wm->ctl[n].reg2) { + val &= ~wm->ctl[n].mask2; + val |= regval2 << __ffs(wm->ctl[n].mask2); + } + snd_wm8776_write(wm, wm->ctl[n].reg1, val); + /* stereo controls in different registers */ + if (wm->ctl[n].flags & WM8776_FLAG_STEREO && + wm->ctl[n].reg1 != wm->ctl[n].reg2) { + val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2; + val |= regval2 << __ffs(wm->ctl[n].mask2); + if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE) + val |= WM8776_VOL_UPDATE; + snd_wm8776_write(wm, wm->ctl[n].reg2, val); + } + } + + return 0; +} + +static int snd_wm8776_add_control(struct snd_wm8776 *wm, int num) +{ + struct snd_kcontrol_new cont; + struct snd_kcontrol *ctl; + + memset(&cont, 0, sizeof(cont)); + cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cont.private_value = num; + cont.name = wm->ctl[num].name; + cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + if (wm->ctl[num].flags & WM8776_FLAG_LIM || + wm->ctl[num].flags & WM8776_FLAG_ALC) + cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + cont.tlv.p = NULL; + cont.get = snd_wm8776_ctl_get; + cont.put = snd_wm8776_ctl_put; + + switch (wm->ctl[num].type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER: + cont.info = snd_wm8776_volume_info; + cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + cont.tlv.p = wm->ctl[num].tlv; + break; + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + wm->ctl[num].max = 1; + if (wm->ctl[num].flags & WM8776_FLAG_STEREO) + cont.info = snd_ctl_boolean_stereo_info; + else + cont.info = snd_ctl_boolean_mono_info; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + cont.info = snd_wm8776_enum_info; + break; + default: + return -EINVAL; + } + ctl = snd_ctl_new1(&cont, wm); + if (!ctl) + return -ENOMEM; + + return snd_ctl_add(wm->card, ctl); +} + +int snd_wm8776_build_controls(struct snd_wm8776 *wm) +{ + int err, i; + + for (i = 0; i < WM8776_CTL_COUNT; i++) + if (wm->ctl[i].name) { + err = snd_wm8776_add_control(wm, i); + if (err < 0) + return err; + } + + return 0; +} diff --git a/sound/pci/ice1712/wm8776.h b/sound/pci/ice1712/wm8776.h new file mode 100644 index 000000000..0d49d0c74 --- /dev/null +++ b/sound/pci/ice1712/wm8776.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_WM8776_H +#define __SOUND_WM8776_H + +/* + * ALSA driver for ICEnsemble VT17xx + * + * Lowlevel functions for WM8776 codec + * + * Copyright (c) 2012 Ondrej Zary + */ + +#define WM8776_REG_HPLVOL 0x00 +#define WM8776_REG_HPRVOL 0x01 +#define WM8776_REG_HPMASTER 0x02 +#define WM8776_HPVOL_MASK 0x17f /* incl. update bit */ +#define WM8776_VOL_HPZCEN (1 << 7) /* zero cross detect */ +#define WM8776_VOL_UPDATE (1 << 8) /* update volume */ +#define WM8776_REG_DACLVOL 0x03 +#define WM8776_REG_DACRVOL 0x04 +#define WM8776_REG_DACMASTER 0x05 +#define WM8776_DACVOL_MASK 0x1ff /* incl. update bit */ +#define WM8776_REG_PHASESWAP 0x06 +#define WM8776_PHASE_INVERTL (1 << 0) +#define WM8776_PHASE_INVERTR (1 << 1) +#define WM8776_REG_DACCTRL1 0x07 +#define WM8776_DAC_DZCEN (1 << 0) +#define WM8776_DAC_ATC (1 << 1) +#define WM8776_DAC_IZD (1 << 2) +#define WM8776_DAC_TOD (1 << 3) +#define WM8776_DAC_PL_MASK 0xf0 +#define WM8776_DAC_PL_LL (1 << 4) /* L chan: L signal */ +#define WM8776_DAC_PL_LR (2 << 4) /* L chan: R signal */ +#define WM8776_DAC_PL_LB (3 << 4) /* L chan: both */ +#define WM8776_DAC_PL_RL (1 << 6) /* R chan: L signal */ +#define WM8776_DAC_PL_RR (2 << 6) /* R chan: R signal */ +#define WM8776_DAC_PL_RB (3 << 6) /* R chan: both */ +#define WM8776_REG_DACMUTE 0x08 +#define WM8776_DACMUTE (1 << 0) +#define WM8776_REG_DACCTRL2 0x09 +#define WM8776_DAC2_DEEMPH (1 << 0) +#define WM8776_DAC2_ZFLAG_DISABLE (0 << 1) +#define WM8776_DAC2_ZFLAG_OWN (1 << 1) +#define WM8776_DAC2_ZFLAG_BOTH (2 << 1) +#define WM8776_DAC2_ZFLAG_EITHER (3 << 1) +#define WM8776_REG_DACIFCTRL 0x0a +#define WM8776_FMT_RIGHTJ (0 << 0) +#define WM8776_FMT_LEFTJ (1 << 0) +#define WM8776_FMT_I2S (2 << 0) +#define WM8776_FMT_DSP (3 << 0) +#define WM8776_FMT_DSP_LATE (1 << 2) /* in DSP mode */ +#define WM8776_FMT_LRC_INVERTED (1 << 2) /* in other modes */ +#define WM8776_FMT_BCLK_INVERTED (1 << 3) +#define WM8776_FMT_16BIT (0 << 4) +#define WM8776_FMT_20BIT (1 << 4) +#define WM8776_FMT_24BIT (2 << 4) +#define WM8776_FMT_32BIT (3 << 4) +#define WM8776_REG_ADCIFCTRL 0x0b +#define WM8776_FMT_ADCMCLK_INVERTED (1 << 6) +#define WM8776_FMT_ADCHPD (1 << 8) +#define WM8776_REG_MSTRCTRL 0x0c +#define WM8776_IF_ADC256FS (2 << 0) +#define WM8776_IF_ADC384FS (3 << 0) +#define WM8776_IF_ADC512FS (4 << 0) +#define WM8776_IF_ADC768FS (5 << 0) +#define WM8776_IF_OVERSAMP64 (1 << 3) +#define WM8776_IF_DAC128FS (0 << 4) +#define WM8776_IF_DAC192FS (1 << 4) +#define WM8776_IF_DAC256FS (2 << 4) +#define WM8776_IF_DAC384FS (3 << 4) +#define WM8776_IF_DAC512FS (4 << 4) +#define WM8776_IF_DAC768FS (5 << 4) +#define WM8776_IF_DAC_MASTER (1 << 7) +#define WM8776_IF_ADC_MASTER (1 << 8) +#define WM8776_REG_PWRDOWN 0x0d +#define WM8776_PWR_PDWN (1 << 0) +#define WM8776_PWR_ADCPD (1 << 1) +#define WM8776_PWR_DACPD (1 << 2) +#define WM8776_PWR_HPPD (1 << 3) +#define WM8776_PWR_AINPD (1 << 6) +#define WM8776_REG_ADCLVOL 0x0e +#define WM8776_REG_ADCRVOL 0x0f +#define WM8776_ADC_GAIN_MASK 0xff +#define WM8776_ADC_ZCEN (1 << 8) +#define WM8776_REG_ALCCTRL1 0x10 +#define WM8776_ALC1_LCT_MASK 0x0f /* 0=-16dB, 1=-15dB..15=-1dB */ +#define WM8776_ALC1_MAXGAIN_MASK 0x70 /* 0,1=0dB, 2=+4dB...7=+24dB */ +#define WM8776_ALC1_LCSEL_MASK 0x180 +#define WM8776_ALC1_LCSEL_LIMITER (0 << 7) +#define WM8776_ALC1_LCSEL_ALCR (1 << 7) +#define WM8776_ALC1_LCSEL_ALCL (2 << 7) +#define WM8776_ALC1_LCSEL_ALCSTEREO (3 << 7) +#define WM8776_REG_ALCCTRL2 0x11 +#define WM8776_ALC2_HOLD_MASK 0x0f /*0=0ms, 1=2.67ms, 2=5.33ms.. */ +#define WM8776_ALC2_ZCEN (1 << 7) +#define WM8776_ALC2_LCEN (1 << 8) +#define WM8776_REG_ALCCTRL3 0x12 +#define WM8776_ALC3_ATK_MASK 0x0f +#define WM8776_ALC3_DCY_MASK 0xf0 +#define WM8776_ALC3_FDECAY (1 << 8) +#define WM8776_REG_NOISEGATE 0x13 +#define WM8776_NGAT_ENABLE (1 << 0) +#define WM8776_NGAT_THR_MASK 0x1c /*0=-78dB, 1=-72dB...7=-36dB */ +#define WM8776_REG_LIMITER 0x14 +#define WM8776_LIM_MAXATTEN_MASK 0x0f +#define WM8776_LIM_TRANWIN_MASK 0x70 /*0=0us, 1=62.5us, 2=125us.. */ +#define WM8776_REG_ADCMUX 0x15 +#define WM8776_ADC_MUX_AIN1 (1 << 0) +#define WM8776_ADC_MUX_AIN2 (1 << 1) +#define WM8776_ADC_MUX_AIN3 (1 << 2) +#define WM8776_ADC_MUX_AIN4 (1 << 3) +#define WM8776_ADC_MUX_AIN5 (1 << 4) +#define WM8776_ADC_MUTER (1 << 6) +#define WM8776_ADC_MUTEL (1 << 7) +#define WM8776_ADC_LRBOTH (1 << 8) +#define WM8776_REG_OUTMUX 0x16 +#define WM8776_OUTMUX_DAC (1 << 0) +#define WM8776_OUTMUX_AUX (1 << 1) +#define WM8776_OUTMUX_BYPASS (1 << 2) +#define WM8776_REG_RESET 0x17 + +#define WM8776_REG_COUNT 0x17 /* don't cache the RESET register */ + +struct snd_wm8776; + +struct snd_wm8776_ops { + void (*write)(struct snd_wm8776 *wm, u8 addr, u8 data); +}; + +enum snd_wm8776_ctl_id { + WM8776_CTL_DAC_VOL, + WM8776_CTL_DAC_SW, + WM8776_CTL_DAC_ZC_SW, + WM8776_CTL_HP_VOL, + WM8776_CTL_HP_SW, + WM8776_CTL_HP_ZC_SW, + WM8776_CTL_AUX_SW, + WM8776_CTL_BYPASS_SW, + WM8776_CTL_DAC_IZD_SW, + WM8776_CTL_PHASE_SW, + WM8776_CTL_DEEMPH_SW, + WM8776_CTL_ADC_VOL, + WM8776_CTL_ADC_SW, + WM8776_CTL_INPUT1_SW, + WM8776_CTL_INPUT2_SW, + WM8776_CTL_INPUT3_SW, + WM8776_CTL_INPUT4_SW, + WM8776_CTL_INPUT5_SW, + WM8776_CTL_AGC_SEL, + WM8776_CTL_LIM_THR, + WM8776_CTL_LIM_ATK, + WM8776_CTL_LIM_DCY, + WM8776_CTL_LIM_TRANWIN, + WM8776_CTL_LIM_MAXATTN, + WM8776_CTL_ALC_TGT, + WM8776_CTL_ALC_ATK, + WM8776_CTL_ALC_DCY, + WM8776_CTL_ALC_MAXGAIN, + WM8776_CTL_ALC_MAXATTN, + WM8776_CTL_ALC_HLD, + WM8776_CTL_NGT_SW, + WM8776_CTL_NGT_THR, + + WM8776_CTL_COUNT, +}; + +#define WM8776_ENUM_MAX 16 + +#define WM8776_FLAG_STEREO (1 << 0) +#define WM8776_FLAG_VOL_UPDATE (1 << 1) +#define WM8776_FLAG_INVERT (1 << 2) +#define WM8776_FLAG_LIM (1 << 3) +#define WM8776_FLAG_ALC (1 << 4) + +struct snd_wm8776_ctl { + const char *name; + snd_ctl_elem_type_t type; + const char *const enum_names[WM8776_ENUM_MAX]; + const unsigned int *tlv; + u16 reg1, reg2, mask1, mask2, min, max, flags; + void (*set)(struct snd_wm8776 *wm, u16 ch1, u16 ch2); + void (*get)(struct snd_wm8776 *wm, u16 *ch1, u16 *ch2); +}; + +enum snd_wm8776_agc_mode { + WM8776_AGC_OFF, + WM8776_AGC_LIM, + WM8776_AGC_ALC_R, + WM8776_AGC_ALC_L, + WM8776_AGC_ALC_STEREO +}; + +struct snd_wm8776 { + struct snd_card *card; + struct snd_wm8776_ctl ctl[WM8776_CTL_COUNT]; + enum snd_wm8776_agc_mode agc_mode; + struct snd_wm8776_ops ops; + u16 regs[WM8776_REG_COUNT]; /* 9-bit registers */ +}; + + + +void snd_wm8776_init(struct snd_wm8776 *wm); +void snd_wm8776_resume(struct snd_wm8776 *wm); +void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power); +void snd_wm8776_volume_restore(struct snd_wm8776 *wm); +int snd_wm8776_build_controls(struct snd_wm8776 *wm); + +#endif /* __SOUND_WM8776_H */ diff --git a/sound/pci/ice1712/wtm.c b/sound/pci/ice1712/wtm.c new file mode 100644 index 000000000..c38f1bd19 --- /dev/null +++ b/sound/pci/ice1712/wtm.c @@ -0,0 +1,630 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Ego Sys Waveterminal 192M + * + * Copyright (c) 2006 Guedez Clement + * Some functions are taken from the Prodigy192 driver + * source + */ + + + +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "wtm.h" +#include "stac946x.h" + +struct wtm_spec { + /* rate change needs atomic mute/unmute of all dacs*/ + struct mutex mute_mutex; +}; + + +/* + * 2*ADC 6*DAC no1 ringbuffer r/w on i2c bus + */ +static inline void stac9460_put(struct snd_ice1712 *ice, int reg, + unsigned char val) +{ + snd_vt1724_write_i2c(ice, STAC9460_I2C_ADDR, reg, val); +} + +static inline unsigned char stac9460_get(struct snd_ice1712 *ice, int reg) +{ + return snd_vt1724_read_i2c(ice, STAC9460_I2C_ADDR, reg); +} + +/* + * 2*ADC 2*DAC no2 ringbuffer r/w on i2c bus + */ +static inline void stac9460_2_put(struct snd_ice1712 *ice, int reg, + unsigned char val) +{ + snd_vt1724_write_i2c(ice, STAC9460_2_I2C_ADDR, reg, val); +} + +static inline unsigned char stac9460_2_get(struct snd_ice1712 *ice, int reg) +{ + return snd_vt1724_read_i2c(ice, STAC9460_2_I2C_ADDR, reg); +} + + +/* + * DAC mute control + */ +static void stac9460_dac_mute_all(struct snd_ice1712 *ice, unsigned char mute, + unsigned short int *change_mask) +{ + unsigned char new, old; + int id, idx, change; + + /*stac9460 1*/ + for (id = 0; id < 7; id++) { + if (*change_mask & (0x01 << id)) { + if (id == 0) + idx = STAC946X_MASTER_VOLUME; + else + idx = STAC946X_LF_VOLUME - 1 + id; + old = stac9460_get(ice, idx); + new = (~mute << 7 & 0x80) | (old & ~0x80); + change = (new != old); + if (change) { + stac9460_put(ice, idx, new); + *change_mask = *change_mask | (0x01 << id); + } else { + *change_mask = *change_mask & ~(0x01 << id); + } + } + } + + /*stac9460 2*/ + for (id = 0; id < 3; id++) { + if (*change_mask & (0x01 << (id + 7))) { + if (id == 0) + idx = STAC946X_MASTER_VOLUME; + else + idx = STAC946X_LF_VOLUME - 1 + id; + old = stac9460_2_get(ice, idx); + new = (~mute << 7 & 0x80) | (old & ~0x80); + change = (new != old); + if (change) { + stac9460_2_put(ice, idx, new); + *change_mask = *change_mask | (0x01 << id); + } else { + *change_mask = *change_mask & ~(0x01 << id); + } + } + } +} + + + +#define stac9460_dac_mute_info snd_ctl_boolean_mono_info + +static int stac9460_dac_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct wtm_spec *spec = ice->spec; + unsigned char val; + int idx, id; + + guard(mutex)(&spec->mute_mutex); + + if (kcontrol->private_value) { + idx = STAC946X_MASTER_VOLUME; + id = 0; + } else { + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + idx = id + STAC946X_LF_VOLUME; + } + if (id < 6) + val = stac9460_get(ice, idx); + else + val = stac9460_2_get(ice, idx - 6); + ucontrol->value.integer.value[0] = (~val >> 7) & 0x1; + + return 0; +} + +static int stac9460_dac_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char new, old; + int id, idx; + int change; + + if (kcontrol->private_value) { + idx = STAC946X_MASTER_VOLUME; + old = stac9460_get(ice, idx); + new = (~ucontrol->value.integer.value[0] << 7 & 0x80) | + (old & ~0x80); + change = (new != old); + if (change) { + stac9460_put(ice, idx, new); + stac9460_2_put(ice, idx, new); + } + } else { + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + idx = id + STAC946X_LF_VOLUME; + if (id < 6) + old = stac9460_get(ice, idx); + else + old = stac9460_2_get(ice, idx - 6); + new = (~ucontrol->value.integer.value[0] << 7 & 0x80) | + (old & ~0x80); + change = (new != old); + if (change) { + if (id < 6) + stac9460_put(ice, idx, new); + else + stac9460_2_put(ice, idx - 6, new); + } + } + return change; +} + +/* + * DAC volume attenuation mixer control + */ +static int stac9460_dac_vol_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; /* mute */ + uinfo->value.integer.max = 0x7f; /* 0dB */ + return 0; +} + +static int stac9460_dac_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx, id; + unsigned char vol; + + if (kcontrol->private_value) { + idx = STAC946X_MASTER_VOLUME; + id = 0; + } else { + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + idx = id + STAC946X_LF_VOLUME; + } + if (id < 6) + vol = stac9460_get(ice, idx) & 0x7f; + else + vol = stac9460_2_get(ice, idx - 6) & 0x7f; + ucontrol->value.integer.value[0] = 0x7f - vol; + return 0; +} + +static int stac9460_dac_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx, id; + unsigned char tmp, ovol, nvol; + int change; + + if (kcontrol->private_value) { + idx = STAC946X_MASTER_VOLUME; + nvol = ucontrol->value.integer.value[0] & 0x7f; + tmp = stac9460_get(ice, idx); + ovol = 0x7f - (tmp & 0x7f); + change = (ovol != nvol); + if (change) { + stac9460_put(ice, idx, (0x7f - nvol) | (tmp & 0x80)); + stac9460_2_put(ice, idx, (0x7f - nvol) | (tmp & 0x80)); + } + } else { + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + idx = id + STAC946X_LF_VOLUME; + nvol = ucontrol->value.integer.value[0] & 0x7f; + if (id < 6) + tmp = stac9460_get(ice, idx); + else + tmp = stac9460_2_get(ice, idx - 6); + ovol = 0x7f - (tmp & 0x7f); + change = (ovol != nvol); + if (change) { + if (id < 6) + stac9460_put(ice, idx, (0x7f - nvol) | + (tmp & 0x80)); + else + stac9460_2_put(ice, idx-6, (0x7f - nvol) | + (tmp & 0x80)); + } + } + return change; +} + +/* + * ADC mute control + */ +#define stac9460_adc_mute_info snd_ctl_boolean_stereo_info + +static int stac9460_adc_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char val; + int i, id; + + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + if (id == 0) { + for (i = 0; i < 2; ++i) { + val = stac9460_get(ice, STAC946X_MIC_L_VOLUME + i); + ucontrol->value.integer.value[i] = ~val>>7 & 0x1; + } + } else { + for (i = 0; i < 2; ++i) { + val = stac9460_2_get(ice, STAC946X_MIC_L_VOLUME + i); + ucontrol->value.integer.value[i] = ~val>>7 & 0x1; + } + } + return 0; +} + +static int stac9460_adc_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char new, old; + int i, reg, id; + int change; + + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + if (id == 0) { + for (i = 0; i < 2; ++i) { + reg = STAC946X_MIC_L_VOLUME + i; + old = stac9460_get(ice, reg); + new = (~ucontrol->value.integer.value[i]<<7&0x80) | + (old&~0x80); + change = (new != old); + if (change) + stac9460_put(ice, reg, new); + } + } else { + for (i = 0; i < 2; ++i) { + reg = STAC946X_MIC_L_VOLUME + i; + old = stac9460_2_get(ice, reg); + new = (~ucontrol->value.integer.value[i]<<7&0x80) | + (old&~0x80); + change = (new != old); + if (change) + stac9460_2_put(ice, reg, new); + } + } + return change; +} + +/* + *ADC gain mixer control + */ +static int stac9460_adc_vol_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; /* 0dB */ + uinfo->value.integer.max = 0x0f; /* 22.5dB */ + return 0; +} + +static int stac9460_adc_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int i, reg, id; + unsigned char vol; + + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + if (id == 0) { + for (i = 0; i < 2; ++i) { + reg = STAC946X_MIC_L_VOLUME + i; + vol = stac9460_get(ice, reg) & 0x0f; + ucontrol->value.integer.value[i] = 0x0f - vol; + } + } else { + for (i = 0; i < 2; ++i) { + reg = STAC946X_MIC_L_VOLUME + i; + vol = stac9460_2_get(ice, reg) & 0x0f; + ucontrol->value.integer.value[i] = 0x0f - vol; + } + } + return 0; +} + +static int stac9460_adc_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int i, reg, id; + unsigned char ovol, nvol; + int change; + + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + if (id == 0) { + for (i = 0; i < 2; ++i) { + reg = STAC946X_MIC_L_VOLUME + i; + nvol = ucontrol->value.integer.value[i] & 0x0f; + ovol = 0x0f - stac9460_get(ice, reg); + change = ((ovol & 0x0f) != nvol); + if (change) + stac9460_put(ice, reg, (0x0f - nvol) | + (ovol & ~0x0f)); + } + } else { + for (i = 0; i < 2; ++i) { + reg = STAC946X_MIC_L_VOLUME + i; + nvol = ucontrol->value.integer.value[i] & 0x0f; + ovol = 0x0f - stac9460_2_get(ice, reg); + change = ((ovol & 0x0f) != nvol); + if (change) + stac9460_2_put(ice, reg, (0x0f - nvol) | + (ovol & ~0x0f)); + } + } + return change; +} + +/* + * MIC / LINE switch fonction + */ +static int stac9460_mic_sw_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "Line In", "Mic" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + + +static int stac9460_mic_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char val; + int id; + + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + if (id == 0) + val = stac9460_get(ice, STAC946X_GENERAL_PURPOSE); + else + val = stac9460_2_get(ice, STAC946X_GENERAL_PURPOSE); + ucontrol->value.enumerated.item[0] = (val >> 7) & 0x1; + return 0; +} + +static int stac9460_mic_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char new, old; + int change, id; + + id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + if (id == 0) + old = stac9460_get(ice, STAC946X_GENERAL_PURPOSE); + else + old = stac9460_2_get(ice, STAC946X_GENERAL_PURPOSE); + new = (ucontrol->value.enumerated.item[0] << 7 & 0x80) | (old & ~0x80); + change = (new != old); + if (change) { + if (id == 0) + stac9460_put(ice, STAC946X_GENERAL_PURPOSE, new); + else + stac9460_2_put(ice, STAC946X_GENERAL_PURPOSE, new); + } + return change; +} + + +/* + * Handler for setting correct codec rate - called when rate change is detected + */ +static void stac9460_set_rate_val(struct snd_ice1712 *ice, unsigned int rate) +{ + unsigned char old, new; + unsigned short int changed; + struct wtm_spec *spec = ice->spec; + + if (rate == 0) /* no hint - S/PDIF input is master, simply return */ + return; + else if (rate <= 48000) + new = 0x08; /* 256x, base rate mode */ + else if (rate <= 96000) + new = 0x11; /* 256x, mid rate mode */ + else + new = 0x12; /* 128x, high rate mode */ + + old = stac9460_get(ice, STAC946X_MASTER_CLOCKING); + if (old == new) + return; + /* change detected, setting master clock, muting first */ + /* due to possible conflicts with mute controls - mutexing */ + guard(mutex)(&spec->mute_mutex); + /* we have to remember current mute status for each DAC */ + changed = 0xFFFF; + stac9460_dac_mute_all(ice, 0, &changed); + /*printk(KERN_DEBUG "Rate change: %d, new MC: 0x%02x\n", rate, new);*/ + stac9460_put(ice, STAC946X_MASTER_CLOCKING, new); + stac9460_2_put(ice, STAC946X_MASTER_CLOCKING, new); + udelay(10); + /* unmuting - only originally unmuted dacs - + * i.e. those changed when muting */ + stac9460_dac_mute_all(ice, 1, &changed); +} + + +/*Limits value in dB for fader*/ +static const DECLARE_TLV_DB_SCALE(db_scale_dac, -19125, 75, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_adc, 0, 150, 0); + +/* + * Control tabs + */ +static const struct snd_kcontrol_new stac9640_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Master Playback Switch", + .info = stac9460_dac_mute_info, + .get = stac9460_dac_mute_get, + .put = stac9460_dac_mute_put, + .private_value = 1, + .tlv = { .p = db_scale_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .info = stac9460_dac_vol_info, + .get = stac9460_dac_vol_get, + .put = stac9460_dac_vol_put, + .private_value = 1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "MIC/Line Input Enum", + .count = 2, + .info = stac9460_mic_sw_info, + .get = stac9460_mic_sw_get, + .put = stac9460_mic_sw_put, + + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Switch", + .count = 8, + .info = stac9460_dac_mute_info, + .get = stac9460_dac_mute_get, + .put = stac9460_dac_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + + .name = "DAC Volume", + .count = 8, + .info = stac9460_dac_vol_info, + .get = stac9460_dac_vol_get, + .put = stac9460_dac_vol_put, + .tlv = { .p = db_scale_dac } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Switch", + .count = 2, + .info = stac9460_adc_mute_info, + .get = stac9460_adc_mute_get, + .put = stac9460_adc_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + + .name = "ADC Volume", + .count = 2, + .info = stac9460_adc_vol_info, + .get = stac9460_adc_vol_get, + .put = stac9460_adc_vol_put, + .tlv = { .p = db_scale_adc } + } +}; + + + +/*INIT*/ +static int wtm_add_controls(struct snd_ice1712 *ice) +{ + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(stac9640_controls); i++) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&stac9640_controls[i], ice)); + if (err < 0) + return err; + } + return 0; +} + +static int wtm_init(struct snd_ice1712 *ice) +{ + static const unsigned short stac_inits_wtm[] = { + STAC946X_RESET, 0, + STAC946X_MASTER_CLOCKING, 0x11, + (unsigned short)-1 + }; + const unsigned short *p; + struct wtm_spec *spec; + + /*WTM 192M*/ + ice->num_total_dacs = 8; + ice->num_total_adcs = 4; + ice->force_rdma1 = 1; + + /*init mutex for dac mute conflict*/ + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + ice->spec = spec; + mutex_init(&spec->mute_mutex); + + + /*initialize codec*/ + p = stac_inits_wtm; + for (; *p != (unsigned short)-1; p += 2) { + stac9460_put(ice, p[0], p[1]); + stac9460_2_put(ice, p[0], p[1]); + } + ice->gpio.set_pro_rate = stac9460_set_rate_val; + return 0; +} + + +static const unsigned char wtm_eeprom[] = { + [ICE_EEP2_SYSCONF] = 0x67, /*SYSCONF: clock 192KHz, mpu401, + 4ADC, 8DAC */ + [ICE_EEP2_ACLINK] = 0x80, /* ACLINK : I2S */ + [ICE_EEP2_I2S] = 0xf8, /* I2S: vol; 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc1, /*SPDIF: out-en, spidf ext out*/ + [ICE_EEP2_GPIO_DIR] = 0x9f, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0x7f, + [ICE_EEP2_GPIO_MASK] = 0x9f, + [ICE_EEP2_GPIO_MASK1] = 0xff, + [ICE_EEP2_GPIO_MASK2] = 0x7f, + [ICE_EEP2_GPIO_STATE] = 0x16, + [ICE_EEP2_GPIO_STATE1] = 0x80, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; + + +/*entry point*/ +struct snd_ice1712_card_info snd_vt1724_wtm_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_WTM, + .name = "ESI Waveterminal 192M", + .model = "WT192M", + .chip_init = wtm_init, + .build_controls = wtm_add_controls, + .eeprom_size = sizeof(wtm_eeprom), + .eeprom_data = wtm_eeprom, + }, + {} /*terminator*/ +}; diff --git a/sound/pci/ice1712/wtm.h b/sound/pci/ice1712/wtm.h new file mode 100644 index 000000000..1cfcbde15 --- /dev/null +++ b/sound/pci/ice1712/wtm.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOUND_WTM_H +#define __SOUND_WTM_H + +/* ID */ +#define WTM_DEVICE_DESC "{EGO SYS INC,WaveTerminal 192M}," +#define VT1724_SUBDEVICE_WTM 0x36495345 /* WT192M ver1.0 */ + +/* + *chip addresses on I2C bus + */ + +#define AK4114_ADDR 0x20 /*S/PDIF receiver*/ +#define STAC9460_I2C_ADDR 0x54 /* ADC*2 | DAC*6 */ +#define STAC9460_2_I2C_ADDR 0x56 /* ADC|DAC *2 */ + + +extern struct snd_ice1712_card_info snd_vt1724_wtm_cards[]; + +#endif /* __SOUND_WTM_H */ + diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c new file mode 100644 index 000000000..e2ea9016a --- /dev/null +++ b/sound/pci/intel8x0.c @@ -0,0 +1,3265 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for Intel ICH (i8x0) chipsets + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This code also contains alpha support for SiS 735 chipsets provided + * by Mike Pieper . We have no datasheet + * for SiS735, so the code is not fully functional. + * + + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; SiS 7012; Ali 5455"); +MODULE_LICENSE("GPL"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static int ac97_clock; +static char *ac97_quirk; +static bool buggy_semaphore; +static int buggy_irq = -1; /* auto-check */ +static bool xbox; +static int spdif_aclink = -1; +static int inside_vm = -1; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for Intel i8x0 soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for Intel i8x0 soundcard."); +module_param(ac97_clock, int, 0444); +MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (0 = allowlist + auto-detect, 1 = force autodetect)."); +module_param(ac97_quirk, charp, 0444); +MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware."); +module_param(buggy_semaphore, bool, 0444); +MODULE_PARM_DESC(buggy_semaphore, "Enable workaround for hardwares with problematic codec semaphores."); +module_param(buggy_irq, bint, 0444); +MODULE_PARM_DESC(buggy_irq, "Enable workaround for buggy interrupts on some motherboards."); +module_param(xbox, bool, 0444); +MODULE_PARM_DESC(xbox, "Set to 1 for Xbox, if you have problems with the AC'97 codec detection."); +module_param(spdif_aclink, int, 0444); +MODULE_PARM_DESC(spdif_aclink, "S/PDIF over AC-link."); +module_param(inside_vm, bint, 0444); +MODULE_PARM_DESC(inside_vm, "KVM/Parallels optimization."); + +/* just for backward compatibility */ +static bool enable; +module_param(enable, bool, 0444); +static int joystick; +module_param(joystick, int, 0444); + +/* + * Direct registers + */ +enum { DEVICE_INTEL, DEVICE_INTEL_ICH4, DEVICE_SIS, DEVICE_ALI, DEVICE_NFORCE }; + +#define ICHREG(x) ICH_REG_##x + +#define DEFINE_REGSET(name,base) \ +enum { \ + ICH_REG_##name##_BDBAR = base + 0x0, /* dword - buffer descriptor list base address */ \ + ICH_REG_##name##_CIV = base + 0x04, /* byte - current index value */ \ + ICH_REG_##name##_LVI = base + 0x05, /* byte - last valid index */ \ + ICH_REG_##name##_SR = base + 0x06, /* byte - status register */ \ + ICH_REG_##name##_PICB = base + 0x08, /* word - position in current buffer */ \ + ICH_REG_##name##_PIV = base + 0x0a, /* byte - prefetched index value */ \ + ICH_REG_##name##_CR = base + 0x0b, /* byte - control register */ \ +} + +/* busmaster blocks */ +DEFINE_REGSET(OFF, 0); /* offset */ +DEFINE_REGSET(PI, 0x00); /* PCM in */ +DEFINE_REGSET(PO, 0x10); /* PCM out */ +DEFINE_REGSET(MC, 0x20); /* Mic in */ + +/* ICH4 busmaster blocks */ +DEFINE_REGSET(MC2, 0x40); /* Mic in 2 */ +DEFINE_REGSET(PI2, 0x50); /* PCM in 2 */ +DEFINE_REGSET(SP, 0x60); /* SPDIF out */ + +/* values for each busmaster block */ + +/* LVI */ +#define ICH_REG_LVI_MASK 0x1f + +/* SR */ +#define ICH_FIFOE 0x10 /* FIFO error */ +#define ICH_BCIS 0x08 /* buffer completion interrupt status */ +#define ICH_LVBCI 0x04 /* last valid buffer completion interrupt */ +#define ICH_CELV 0x02 /* current equals last valid */ +#define ICH_DCH 0x01 /* DMA controller halted */ + +/* PIV */ +#define ICH_REG_PIV_MASK 0x1f /* mask */ + +/* CR */ +#define ICH_IOCE 0x10 /* interrupt on completion enable */ +#define ICH_FEIE 0x08 /* fifo error interrupt enable */ +#define ICH_LVBIE 0x04 /* last valid buffer interrupt enable */ +#define ICH_RESETREGS 0x02 /* reset busmaster registers */ +#define ICH_STARTBM 0x01 /* start busmaster operation */ + + +/* global block */ +#define ICH_REG_GLOB_CNT 0x2c /* dword - global control */ +#define ICH_PCM_SPDIF_MASK 0xc0000000 /* s/pdif pcm slot mask (ICH4) */ +#define ICH_PCM_SPDIF_NONE 0x00000000 /* reserved - undefined */ +#define ICH_PCM_SPDIF_78 0x40000000 /* s/pdif pcm on slots 7&8 */ +#define ICH_PCM_SPDIF_69 0x80000000 /* s/pdif pcm on slots 6&9 */ +#define ICH_PCM_SPDIF_1011 0xc0000000 /* s/pdif pcm on slots 10&11 */ +#define ICH_PCM_20BIT 0x00400000 /* 20-bit samples (ICH4) */ +#define ICH_PCM_246_MASK 0x00300000 /* chan mask (not all chips) */ +#define ICH_PCM_8 0x00300000 /* 8 channels (not all chips) */ +#define ICH_PCM_6 0x00200000 /* 6 channels (not all chips) */ +#define ICH_PCM_4 0x00100000 /* 4 channels (not all chips) */ +#define ICH_PCM_2 0x00000000 /* 2 channels (stereo) */ +#define ICH_SIS_PCM_246_MASK 0x000000c0 /* 6 channels (SIS7012) */ +#define ICH_SIS_PCM_6 0x00000080 /* 6 channels (SIS7012) */ +#define ICH_SIS_PCM_4 0x00000040 /* 4 channels (SIS7012) */ +#define ICH_SIS_PCM_2 0x00000000 /* 2 channels (SIS7012) */ +#define ICH_TRIE 0x00000040 /* tertiary resume interrupt enable */ +#define ICH_SRIE 0x00000020 /* secondary resume interrupt enable */ +#define ICH_PRIE 0x00000010 /* primary resume interrupt enable */ +#define ICH_ACLINK 0x00000008 /* AClink shut off */ +#define ICH_AC97WARM 0x00000004 /* AC'97 warm reset */ +#define ICH_AC97COLD 0x00000002 /* AC'97 cold reset */ +#define ICH_GIE 0x00000001 /* GPI interrupt enable */ +#define ICH_REG_GLOB_STA 0x30 /* dword - global status */ +#define ICH_TRI 0x20000000 /* ICH4: tertiary (AC_SDIN2) resume interrupt */ +#define ICH_TCR 0x10000000 /* ICH4: tertiary (AC_SDIN2) codec ready */ +#define ICH_BCS 0x08000000 /* ICH4: bit clock stopped */ +#define ICH_SPINT 0x04000000 /* ICH4: S/PDIF interrupt */ +#define ICH_P2INT 0x02000000 /* ICH4: PCM2-In interrupt */ +#define ICH_M2INT 0x01000000 /* ICH4: Mic2-In interrupt */ +#define ICH_SAMPLE_CAP 0x00c00000 /* ICH4: sample capability bits (RO) */ +#define ICH_SAMPLE_16_20 0x00400000 /* ICH4: 16- and 20-bit samples */ +#define ICH_MULTICHAN_CAP 0x00300000 /* ICH4: multi-channel capability bits (RO) */ +#define ICH_SIS_TRI 0x00080000 /* SIS: tertiary resume irq */ +#define ICH_SIS_TCR 0x00040000 /* SIS: tertiary codec ready */ +#define ICH_MD3 0x00020000 /* modem power down semaphore */ +#define ICH_AD3 0x00010000 /* audio power down semaphore */ +#define ICH_RCS 0x00008000 /* read completion status */ +#define ICH_BIT3 0x00004000 /* bit 3 slot 12 */ +#define ICH_BIT2 0x00002000 /* bit 2 slot 12 */ +#define ICH_BIT1 0x00001000 /* bit 1 slot 12 */ +#define ICH_SRI 0x00000800 /* secondary (AC_SDIN1) resume interrupt */ +#define ICH_PRI 0x00000400 /* primary (AC_SDIN0) resume interrupt */ +#define ICH_SCR 0x00000200 /* secondary (AC_SDIN1) codec ready */ +#define ICH_PCR 0x00000100 /* primary (AC_SDIN0) codec ready */ +#define ICH_MCINT 0x00000080 /* MIC capture interrupt */ +#define ICH_POINT 0x00000040 /* playback interrupt */ +#define ICH_PIINT 0x00000020 /* capture interrupt */ +#define ICH_NVSPINT 0x00000010 /* nforce spdif interrupt */ +#define ICH_MOINT 0x00000004 /* modem playback interrupt */ +#define ICH_MIINT 0x00000002 /* modem capture interrupt */ +#define ICH_GSCI 0x00000001 /* GPI status change interrupt */ +#define ICH_REG_ACC_SEMA 0x34 /* byte - codec write semaphore */ +#define ICH_CAS 0x01 /* codec access semaphore */ +#define ICH_REG_SDM 0x80 +#define ICH_DI2L_MASK 0x000000c0 /* PCM In 2, Mic In 2 data in line */ +#define ICH_DI2L_SHIFT 6 +#define ICH_DI1L_MASK 0x00000030 /* PCM In 1, Mic In 1 data in line */ +#define ICH_DI1L_SHIFT 4 +#define ICH_SE 0x00000008 /* steer enable */ +#define ICH_LDI_MASK 0x00000003 /* last codec read data input */ + +#define ICH_MAX_FRAGS 32 /* max hw frags */ + + +/* + * registers for Ali5455 + */ + +/* ALi 5455 busmaster blocks */ +DEFINE_REGSET(AL_PI, 0x40); /* ALi PCM in */ +DEFINE_REGSET(AL_PO, 0x50); /* Ali PCM out */ +DEFINE_REGSET(AL_MC, 0x60); /* Ali Mic in */ +DEFINE_REGSET(AL_CDC_SPO, 0x70); /* Ali Codec SPDIF out */ +DEFINE_REGSET(AL_CENTER, 0x80); /* Ali center out */ +DEFINE_REGSET(AL_LFE, 0x90); /* Ali center out */ +DEFINE_REGSET(AL_CLR_SPI, 0xa0); /* Ali Controller SPDIF in */ +DEFINE_REGSET(AL_CLR_SPO, 0xb0); /* Ali Controller SPDIF out */ +DEFINE_REGSET(AL_I2S, 0xc0); /* Ali I2S in */ +DEFINE_REGSET(AL_PI2, 0xd0); /* Ali PCM2 in */ +DEFINE_REGSET(AL_MC2, 0xe0); /* Ali Mic2 in */ + +enum { + ICH_REG_ALI_SCR = 0x00, /* System Control Register */ + ICH_REG_ALI_SSR = 0x04, /* System Status Register */ + ICH_REG_ALI_DMACR = 0x08, /* DMA Control Register */ + ICH_REG_ALI_FIFOCR1 = 0x0c, /* FIFO Control Register 1 */ + ICH_REG_ALI_INTERFACECR = 0x10, /* Interface Control Register */ + ICH_REG_ALI_INTERRUPTCR = 0x14, /* Interrupt control Register */ + ICH_REG_ALI_INTERRUPTSR = 0x18, /* Interrupt Status Register */ + ICH_REG_ALI_FIFOCR2 = 0x1c, /* FIFO Control Register 2 */ + ICH_REG_ALI_CPR = 0x20, /* Command Port Register */ + ICH_REG_ALI_CPR_ADDR = 0x22, /* ac97 addr write */ + ICH_REG_ALI_SPR = 0x24, /* Status Port Register */ + ICH_REG_ALI_SPR_ADDR = 0x26, /* ac97 addr read */ + ICH_REG_ALI_FIFOCR3 = 0x2c, /* FIFO Control Register 3 */ + ICH_REG_ALI_TTSR = 0x30, /* Transmit Tag Slot Register */ + ICH_REG_ALI_RTSR = 0x34, /* Receive Tag Slot Register */ + ICH_REG_ALI_CSPSR = 0x38, /* Command/Status Port Status Register */ + ICH_REG_ALI_CAS = 0x3c, /* Codec Write Semaphore Register */ + ICH_REG_ALI_HWVOL = 0xf0, /* hardware volume control/status */ + ICH_REG_ALI_I2SCR = 0xf4, /* I2S control/status */ + ICH_REG_ALI_SPDIFCSR = 0xf8, /* spdif channel status register */ + ICH_REG_ALI_SPDIFICS = 0xfc, /* spdif interface control/status */ +}; + +#define ALI_CAS_SEM_BUSY 0x80000000 +#define ALI_CPR_ADDR_SECONDARY 0x100 +#define ALI_CPR_ADDR_READ 0x80 +#define ALI_CSPSR_CODEC_READY 0x08 +#define ALI_CSPSR_READ_OK 0x02 +#define ALI_CSPSR_WRITE_OK 0x01 + +/* interrupts for the whole chip by interrupt status register finish */ + +#define ALI_INT_MICIN2 (1<<26) +#define ALI_INT_PCMIN2 (1<<25) +#define ALI_INT_I2SIN (1<<24) +#define ALI_INT_SPDIFOUT (1<<23) /* controller spdif out INTERRUPT */ +#define ALI_INT_SPDIFIN (1<<22) +#define ALI_INT_LFEOUT (1<<21) +#define ALI_INT_CENTEROUT (1<<20) +#define ALI_INT_CODECSPDIFOUT (1<<19) +#define ALI_INT_MICIN (1<<18) +#define ALI_INT_PCMOUT (1<<17) +#define ALI_INT_PCMIN (1<<16) +#define ALI_INT_CPRAIS (1<<7) /* command port available */ +#define ALI_INT_SPRAIS (1<<5) /* status port available */ +#define ALI_INT_GPIO (1<<1) +#define ALI_INT_MASK (ALI_INT_SPDIFOUT|ALI_INT_CODECSPDIFOUT|\ + ALI_INT_MICIN|ALI_INT_PCMOUT|ALI_INT_PCMIN) + +#define ICH_ALI_SC_RESET (1<<31) /* master reset */ +#define ICH_ALI_SC_AC97_DBL (1<<30) +#define ICH_ALI_SC_CODEC_SPDF (3<<20) /* 1=7/8, 2=6/9, 3=10/11 */ +#define ICH_ALI_SC_IN_BITS (3<<18) +#define ICH_ALI_SC_OUT_BITS (3<<16) +#define ICH_ALI_SC_6CH_CFG (3<<14) +#define ICH_ALI_SC_PCM_4 (1<<8) +#define ICH_ALI_SC_PCM_6 (2<<8) +#define ICH_ALI_SC_PCM_246_MASK (3<<8) + +#define ICH_ALI_SS_SEC_ID (3<<5) +#define ICH_ALI_SS_PRI_ID (3<<3) + +#define ICH_ALI_IF_AC97SP (1<<21) +#define ICH_ALI_IF_MC (1<<20) +#define ICH_ALI_IF_PI (1<<19) +#define ICH_ALI_IF_MC2 (1<<18) +#define ICH_ALI_IF_PI2 (1<<17) +#define ICH_ALI_IF_LINE_SRC (1<<15) /* 0/1 = slot 3/6 */ +#define ICH_ALI_IF_MIC_SRC (1<<14) /* 0/1 = slot 3/6 */ +#define ICH_ALI_IF_SPDF_SRC (3<<12) /* 00 = PCM, 01 = AC97-in, 10 = spdif-in, 11 = i2s */ +#define ICH_ALI_IF_AC97_OUT (3<<8) /* 00 = PCM, 10 = spdif-in, 11 = i2s */ +#define ICH_ALI_IF_PO_SPDF (1<<3) +#define ICH_ALI_IF_PO (1<<1) + +/* + * + */ + +enum { + ICHD_PCMIN, + ICHD_PCMOUT, + ICHD_MIC, + ICHD_MIC2, + ICHD_PCM2IN, + ICHD_SPBAR, + ICHD_LAST = ICHD_SPBAR +}; +enum { + NVD_PCMIN, + NVD_PCMOUT, + NVD_MIC, + NVD_SPBAR, + NVD_LAST = NVD_SPBAR +}; +enum { + ALID_PCMIN, + ALID_PCMOUT, + ALID_MIC, + ALID_AC97SPDIFOUT, + ALID_SPDIFIN, + ALID_SPDIFOUT, + ALID_LAST = ALID_SPDIFOUT +}; + +#define get_ichdev(substream) (substream->runtime->private_data) + +struct ichdev { + unsigned int ichd; /* ich device number */ + unsigned long reg_offset; /* offset to bmaddr */ + __le32 *bdbar; /* CPU address (32bit) */ + unsigned int bdbar_addr; /* PCI bus address (32bit) */ + struct snd_pcm_substream *substream; + unsigned int physbuf; /* physical address (32bit) */ + unsigned int size; + unsigned int fragsize; + unsigned int fragsize1; + unsigned int position; + unsigned int pos_shift; + unsigned int last_pos; + int frags; + int lvi; + int lvi_frag; + int civ; + int ack; + int ack_reload; + unsigned int ack_bit; + unsigned int roff_sr; + unsigned int roff_picb; + unsigned int int_sta_mask; /* interrupt status mask */ + unsigned int ali_slot; /* ALI DMA slot */ + struct ac97_pcm *pcm; + int pcm_open_flag; + unsigned int prepared:1; + unsigned int suspended: 1; +}; + +struct intel8x0 { + unsigned int device_type; + + int irq; + + void __iomem *addr; + void __iomem *bmaddr; + + struct pci_dev *pci; + struct snd_card *card; + + int pcm_devs; + struct snd_pcm *pcm[6]; + struct ichdev ichd[6]; + + unsigned multi4: 1, + multi6: 1, + multi8 :1, + dra: 1, + smp20bit: 1; + unsigned in_ac97_init: 1, + in_sdin_init: 1; + unsigned in_measurement: 1; /* during ac97 clock measurement */ + unsigned fix_nocache: 1; /* workaround for 440MX */ + unsigned buggy_irq: 1; /* workaround for buggy mobos */ + unsigned xbox: 1; /* workaround for Xbox AC'97 detection */ + unsigned buggy_semaphore: 1; /* workaround for buggy codec semaphore */ + unsigned inside_vm: 1; /* enable VM optimization */ + + int spdif_idx; /* SPDIF BAR index; *_SPBAR or -1 if use PCMOUT */ + unsigned int sdm_saved; /* SDM reg value */ + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97[3]; + unsigned int ac97_sdin[3]; + unsigned int max_codecs, ncodecs; + const unsigned int *codec_bit; + unsigned int codec_isr_bits; + unsigned int codec_ready_bits; + + spinlock_t reg_lock; + + u32 bdbars_count; + struct snd_dma_buffer *bdbars; + u32 int_sta_reg; /* interrupt status register */ + u32 int_sta_mask; /* interrupt status mask */ +}; + +static const struct pci_device_id snd_intel8x0_ids[] = { + { + /* 82801AA */ + PCI_VDEVICE(INTEL, 0x2415), + .driver_data = DEVICE_INTEL, + }, { + /* 82901AB */ + PCI_VDEVICE(INTEL, 0x2425), + .driver_data = DEVICE_INTEL, + }, { + /* 82801BA */ + PCI_VDEVICE(INTEL, 0x2445), + .driver_data = DEVICE_INTEL, + }, { + /* ICH3 */ + PCI_VDEVICE(INTEL, 0x2485), + .driver_data = DEVICE_INTEL, + }, { + /* ICH4 */ + PCI_VDEVICE(INTEL, 0x24c5), + .driver_data = DEVICE_INTEL_ICH4, + }, { + /* ICH5 */ + PCI_VDEVICE(INTEL, 0x24d5), + .driver_data = DEVICE_INTEL_ICH4, + }, { + /* ESB */ + PCI_VDEVICE(INTEL, 0x25a6), + .driver_data = DEVICE_INTEL_ICH4, + }, { + /* ICH6 */ + PCI_VDEVICE(INTEL, 0x266e), + .driver_data = DEVICE_INTEL_ICH4, + }, { + /* ICH7 */ + PCI_VDEVICE(INTEL, 0x27de), + .driver_data = DEVICE_INTEL_ICH4, + }, { + /* ESB2 */ + PCI_VDEVICE(INTEL, 0x2698), + .driver_data = DEVICE_INTEL_ICH4, + }, { + /* 440MX */ + PCI_VDEVICE(INTEL, 0x7195), + .driver_data = DEVICE_INTEL, + }, { + /* SI7012 */ + PCI_VDEVICE(SI, 0x7012), + .driver_data = DEVICE_SIS, + }, { + /* NFORCE */ + PCI_VDEVICE(NVIDIA, 0x01b1), + .driver_data = DEVICE_NFORCE, + }, { + /* MCP04 */ + PCI_VDEVICE(NVIDIA, 0x003a), + .driver_data = DEVICE_NFORCE, + }, { + /* NFORCE2 */ + PCI_VDEVICE(NVIDIA, 0x006a), + .driver_data = DEVICE_NFORCE, + }, { + /* CK804 */ + PCI_VDEVICE(NVIDIA, 0x0059), + .driver_data = DEVICE_NFORCE, + }, { + /* CK8 */ + PCI_VDEVICE(NVIDIA, 0x008a), + .driver_data = DEVICE_NFORCE, + }, { + /* NFORCE3 */ + PCI_VDEVICE(NVIDIA, 0x00da), + .driver_data = DEVICE_NFORCE, + }, { + /* CK8S */ + PCI_VDEVICE(NVIDIA, 0x00ea), + .driver_data = DEVICE_NFORCE, + }, { + /* MCP51 */ + PCI_VDEVICE(NVIDIA, 0x026b), + .driver_data = DEVICE_NFORCE, + }, { + /* AMD8111 */ + PCI_VDEVICE(AMD, 0x746d), + .driver_data = DEVICE_INTEL, + }, { + /* AMD768 */ + PCI_VDEVICE(AMD, 0x7445), + .driver_data = DEVICE_INTEL, + }, { + /* Ali5455 */ + PCI_VDEVICE(AL, 0x5455), + .driver_data = DEVICE_ALI, + }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_intel8x0_ids); + +/* + * Lowlevel I/O - busmaster + */ + +static inline u8 igetbyte(struct intel8x0 *chip, u32 offset) +{ + return ioread8(chip->bmaddr + offset); +} + +static inline u16 igetword(struct intel8x0 *chip, u32 offset) +{ + return ioread16(chip->bmaddr + offset); +} + +static inline u32 igetdword(struct intel8x0 *chip, u32 offset) +{ + return ioread32(chip->bmaddr + offset); +} + +static inline void iputbyte(struct intel8x0 *chip, u32 offset, u8 val) +{ + iowrite8(val, chip->bmaddr + offset); +} + +static inline void iputword(struct intel8x0 *chip, u32 offset, u16 val) +{ + iowrite16(val, chip->bmaddr + offset); +} + +static inline void iputdword(struct intel8x0 *chip, u32 offset, u32 val) +{ + iowrite32(val, chip->bmaddr + offset); +} + +/* + * Lowlevel I/O - AC'97 registers + */ + +static inline u16 iagetword(struct intel8x0 *chip, u32 offset) +{ + return ioread16(chip->addr + offset); +} + +static inline void iaputword(struct intel8x0 *chip, u32 offset, u16 val) +{ + iowrite16(val, chip->addr + offset); +} + +/* + * Basic I/O + */ + +/* + * access to AC97 codec via normal i/o (for ICH and SIS7012) + */ + +static int snd_intel8x0_codec_semaphore(struct intel8x0 *chip, unsigned int codec) +{ + int time; + + if (codec > 2) + return -EIO; + if (chip->in_sdin_init) { + /* we don't know the ready bit assignment at the moment */ + /* so we check any */ + codec = chip->codec_isr_bits; + } else { + codec = chip->codec_bit[chip->ac97_sdin[codec]]; + } + + /* codec ready ? */ + if ((igetdword(chip, ICHREG(GLOB_STA)) & codec) == 0) + return -EIO; + + if (chip->buggy_semaphore) + return 0; /* just ignore ... */ + + /* Anyone holding a semaphore for 1 msec should be shot... */ + time = 100; + do { + if (!(igetbyte(chip, ICHREG(ACC_SEMA)) & ICH_CAS)) + return 0; + udelay(10); + } while (time--); + + /* access to some forbidden (non existent) ac97 registers will not + * reset the semaphore. So even if you don't get the semaphore, still + * continue the access. We don't need the semaphore anyway. */ + dev_err(chip->card->dev, + "codec_semaphore: semaphore is not ready [0x%x][0x%x]\n", + igetbyte(chip, ICHREG(ACC_SEMA)), igetdword(chip, ICHREG(GLOB_STA))); + iagetword(chip, 0); /* clear semaphore flag */ + /* I don't care about the semaphore */ + return -EBUSY; +} + +static void snd_intel8x0_codec_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct intel8x0 *chip = ac97->private_data; + + if (snd_intel8x0_codec_semaphore(chip, ac97->num) < 0) { + if (! chip->in_ac97_init) + dev_err(chip->card->dev, + "codec_write %d: semaphore is not ready for register 0x%x\n", + ac97->num, reg); + } + iaputword(chip, reg + ac97->num * 0x80, val); +} + +static unsigned short snd_intel8x0_codec_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct intel8x0 *chip = ac97->private_data; + unsigned short res; + unsigned int tmp; + + if (snd_intel8x0_codec_semaphore(chip, ac97->num) < 0) { + if (! chip->in_ac97_init) + dev_err(chip->card->dev, + "codec_read %d: semaphore is not ready for register 0x%x\n", + ac97->num, reg); + res = 0xffff; + } else { + res = iagetword(chip, reg + ac97->num * 0x80); + tmp = igetdword(chip, ICHREG(GLOB_STA)); + if (tmp & ICH_RCS) { + /* reset RCS and preserve other R/WC bits */ + iputdword(chip, ICHREG(GLOB_STA), tmp & + ~(chip->codec_ready_bits | ICH_GSCI)); + if (! chip->in_ac97_init) + dev_err(chip->card->dev, + "codec_read %d: read timeout for register 0x%x\n", + ac97->num, reg); + res = 0xffff; + } + } + return res; +} + +static void snd_intel8x0_codec_read_test(struct intel8x0 *chip, + unsigned int codec) +{ + unsigned int tmp; + + if (snd_intel8x0_codec_semaphore(chip, codec) >= 0) { + iagetword(chip, codec * 0x80); + tmp = igetdword(chip, ICHREG(GLOB_STA)); + if (tmp & ICH_RCS) { + /* reset RCS and preserve other R/WC bits */ + iputdword(chip, ICHREG(GLOB_STA), tmp & + ~(chip->codec_ready_bits | ICH_GSCI)); + } + } +} + +/* + * access to AC97 for Ali5455 + */ +static int snd_intel8x0_ali_codec_ready(struct intel8x0 *chip, int mask) +{ + int count = 0; + for (count = 0; count < 0x7f; count++) { + int val = igetbyte(chip, ICHREG(ALI_CSPSR)); + if (val & mask) + return 0; + } + if (! chip->in_ac97_init) + dev_warn(chip->card->dev, "AC97 codec ready timeout.\n"); + return -EBUSY; +} + +static int snd_intel8x0_ali_codec_semaphore(struct intel8x0 *chip) +{ + int time = 100; + if (chip->buggy_semaphore) + return 0; /* just ignore ... */ + while (--time && (igetdword(chip, ICHREG(ALI_CAS)) & ALI_CAS_SEM_BUSY)) + udelay(1); + if (! time && ! chip->in_ac97_init) + dev_warn(chip->card->dev, "ali_codec_semaphore timeout\n"); + return snd_intel8x0_ali_codec_ready(chip, ALI_CSPSR_CODEC_READY); +} + +static unsigned short snd_intel8x0_ali_codec_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct intel8x0 *chip = ac97->private_data; + unsigned short data = 0xffff; + + if (snd_intel8x0_ali_codec_semaphore(chip)) + goto __err; + reg |= ALI_CPR_ADDR_READ; + if (ac97->num) + reg |= ALI_CPR_ADDR_SECONDARY; + iputword(chip, ICHREG(ALI_CPR_ADDR), reg); + if (snd_intel8x0_ali_codec_ready(chip, ALI_CSPSR_READ_OK)) + goto __err; + data = igetword(chip, ICHREG(ALI_SPR)); + __err: + return data; +} + +static void snd_intel8x0_ali_codec_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct intel8x0 *chip = ac97->private_data; + + if (snd_intel8x0_ali_codec_semaphore(chip)) + return; + iputword(chip, ICHREG(ALI_CPR), val); + if (ac97->num) + reg |= ALI_CPR_ADDR_SECONDARY; + iputword(chip, ICHREG(ALI_CPR_ADDR), reg); + snd_intel8x0_ali_codec_ready(chip, ALI_CSPSR_WRITE_OK); +} + + +/* + * DMA I/O + */ +static void snd_intel8x0_setup_periods(struct intel8x0 *chip, struct ichdev *ichdev) +{ + int idx; + __le32 *bdbar = ichdev->bdbar; + unsigned long port = ichdev->reg_offset; + + iputdword(chip, port + ICH_REG_OFF_BDBAR, ichdev->bdbar_addr); + if (ichdev->size == ichdev->fragsize) { + ichdev->ack_reload = ichdev->ack = 2; + ichdev->fragsize1 = ichdev->fragsize >> 1; + for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 4) { + bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf); + bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */ + ichdev->fragsize1 >> ichdev->pos_shift); + bdbar[idx + 2] = cpu_to_le32(ichdev->physbuf + (ichdev->size >> 1)); + bdbar[idx + 3] = cpu_to_le32(0x80000000 | /* interrupt on completion */ + ichdev->fragsize1 >> ichdev->pos_shift); + } + ichdev->frags = 2; + } else { + ichdev->ack_reload = ichdev->ack = 1; + ichdev->fragsize1 = ichdev->fragsize; + for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 2) { + bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf + + (((idx >> 1) * ichdev->fragsize) % + ichdev->size)); + bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */ + ichdev->fragsize >> ichdev->pos_shift); +#if 0 + dev_dbg(chip->card->dev, "bdbar[%i] = 0x%x [0x%x]\n", + idx + 0, bdbar[idx + 0], bdbar[idx + 1]); +#endif + } + ichdev->frags = ichdev->size / ichdev->fragsize; + } + iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi = ICH_REG_LVI_MASK); + ichdev->civ = 0; + iputbyte(chip, port + ICH_REG_OFF_CIV, 0); + ichdev->lvi_frag = ICH_REG_LVI_MASK % ichdev->frags; + ichdev->position = 0; +#if 0 + dev_dbg(chip->card->dev, + "lvi_frag = %i, frags = %i, period_size = 0x%x, period_size1 = 0x%x\n", + ichdev->lvi_frag, ichdev->frags, ichdev->fragsize, + ichdev->fragsize1); +#endif + /* clear interrupts */ + iputbyte(chip, port + ichdev->roff_sr, ICH_FIFOE | ICH_BCIS | ICH_LVBCI); +} + +/* + * Interrupt handler + */ + +static inline void snd_intel8x0_update(struct intel8x0 *chip, struct ichdev *ichdev) +{ + unsigned long port = ichdev->reg_offset; + int status, civ, i, step; + int ack = 0; + + if (!(ichdev->prepared || chip->in_measurement) || ichdev->suspended) + return; + + scoped_guard(spinlock_irqsave, &chip->reg_lock) { + status = igetbyte(chip, port + ichdev->roff_sr); + civ = igetbyte(chip, port + ICH_REG_OFF_CIV); + if (!(status & ICH_BCIS)) { + step = 0; + } else if (civ == ichdev->civ) { + step = 1; + ichdev->civ++; + ichdev->civ &= ICH_REG_LVI_MASK; + } else { + step = civ - ichdev->civ; + if (step < 0) + step += ICH_REG_LVI_MASK + 1; + ichdev->civ = civ; + } + + ichdev->position += step * ichdev->fragsize1; + if (! chip->in_measurement) + ichdev->position %= ichdev->size; + ichdev->lvi += step; + ichdev->lvi &= ICH_REG_LVI_MASK; + iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi); + for (i = 0; i < step; i++) { + ichdev->lvi_frag++; + ichdev->lvi_frag %= ichdev->frags; + ichdev->bdbar[ichdev->lvi * 2] = cpu_to_le32(ichdev->physbuf + ichdev->lvi_frag * ichdev->fragsize1); +#if 0 + dev_dbg(chip->card->dev, + "new: bdbar[%i] = 0x%x [0x%x], prefetch = %i, all = 0x%x, 0x%x\n", + ichdev->lvi * 2, ichdev->bdbar[ichdev->lvi * 2], + ichdev->bdbar[ichdev->lvi * 2 + 1], inb(ICH_REG_OFF_PIV + port), + inl(port + 4), inb(port + ICH_REG_OFF_CR)); +#endif + if (--ichdev->ack == 0) { + ichdev->ack = ichdev->ack_reload; + ack = 1; + } + } + } + if (ack && ichdev->substream) { + snd_pcm_period_elapsed(ichdev->substream); + } + iputbyte(chip, port + ichdev->roff_sr, + status & (ICH_FIFOE | ICH_BCIS | ICH_LVBCI)); +} + +static irqreturn_t snd_intel8x0_interrupt(int irq, void *dev_id) +{ + struct intel8x0 *chip = dev_id; + struct ichdev *ichdev; + unsigned int status; + unsigned int i; + + status = igetdword(chip, chip->int_sta_reg); + if (status == 0xffffffff) /* we are not yet resumed */ + return IRQ_NONE; + + if ((status & chip->int_sta_mask) == 0) { + if (status) { + /* ack */ + iputdword(chip, chip->int_sta_reg, status); + if (! chip->buggy_irq) + status = 0; + } + return IRQ_RETVAL(status); + } + + for (i = 0; i < chip->bdbars_count; i++) { + ichdev = &chip->ichd[i]; + if (status & ichdev->int_sta_mask) + snd_intel8x0_update(chip, ichdev); + } + + /* ack them */ + iputdword(chip, chip->int_sta_reg, status & chip->int_sta_mask); + + return IRQ_HANDLED; +} + +/* + * PCM part + */ + +static int snd_intel8x0_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + struct ichdev *ichdev = get_ichdev(substream); + unsigned char val = 0; + unsigned long port = ichdev->reg_offset; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + ichdev->suspended = 0; + fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = ICH_IOCE | ICH_STARTBM; + ichdev->last_pos = ichdev->position; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + ichdev->suspended = 1; + fallthrough; + case SNDRV_PCM_TRIGGER_STOP: + val = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = ICH_IOCE; + break; + default: + return -EINVAL; + } + iputbyte(chip, port + ICH_REG_OFF_CR, val); + if (cmd == SNDRV_PCM_TRIGGER_STOP) { + /* wait until DMA stopped */ + while (!(igetbyte(chip, port + ichdev->roff_sr) & ICH_DCH)) ; + /* reset whole DMA things */ + iputbyte(chip, port + ICH_REG_OFF_CR, ICH_RESETREGS); + } + return 0; +} + +static int snd_intel8x0_ali_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + struct ichdev *ichdev = get_ichdev(substream); + unsigned long port = ichdev->reg_offset; + static const int fiforeg[] = { + ICHREG(ALI_FIFOCR1), ICHREG(ALI_FIFOCR2), ICHREG(ALI_FIFOCR3) + }; + unsigned int val, fifo; + + val = igetdword(chip, ICHREG(ALI_DMACR)); + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + ichdev->suspended = 0; + fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* clear FIFO for synchronization of channels */ + fifo = igetdword(chip, fiforeg[ichdev->ali_slot / 4]); + fifo &= ~(0xff << (ichdev->ali_slot % 4)); + fifo |= 0x83 << (ichdev->ali_slot % 4); + iputdword(chip, fiforeg[ichdev->ali_slot / 4], fifo); + } + iputbyte(chip, port + ICH_REG_OFF_CR, ICH_IOCE); + val &= ~(1 << (ichdev->ali_slot + 16)); /* clear PAUSE flag */ + /* start DMA */ + iputdword(chip, ICHREG(ALI_DMACR), val | (1 << ichdev->ali_slot)); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + ichdev->suspended = 1; + fallthrough; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* pause */ + iputdword(chip, ICHREG(ALI_DMACR), val | (1 << (ichdev->ali_slot + 16))); + iputbyte(chip, port + ICH_REG_OFF_CR, 0); + while (igetbyte(chip, port + ICH_REG_OFF_CR)) + ; + if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) + break; + /* reset whole DMA things */ + iputbyte(chip, port + ICH_REG_OFF_CR, ICH_RESETREGS); + /* clear interrupts */ + iputbyte(chip, port + ICH_REG_OFF_SR, + igetbyte(chip, port + ICH_REG_OFF_SR) | 0x1e); + iputdword(chip, ICHREG(ALI_INTERRUPTSR), + igetdword(chip, ICHREG(ALI_INTERRUPTSR)) & ichdev->int_sta_mask); + break; + default: + return -EINVAL; + } + return 0; +} + +static int snd_intel8x0_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + struct ichdev *ichdev = get_ichdev(substream); + int dbl = params_rate(hw_params) > 48000; + int err; + + if (ichdev->pcm_open_flag) { + snd_ac97_pcm_close(ichdev->pcm); + ichdev->pcm_open_flag = 0; + ichdev->prepared = 0; + } + err = snd_ac97_pcm_open(ichdev->pcm, params_rate(hw_params), + params_channels(hw_params), + ichdev->pcm->r[dbl].slots); + if (err >= 0) { + ichdev->pcm_open_flag = 1; + /* Force SPDIF setting */ + if (ichdev->ichd == ICHD_PCMOUT && chip->spdif_idx < 0) + snd_ac97_set_rate(ichdev->pcm->r[0].codec[0], AC97_SPDIF, + params_rate(hw_params)); + } + return err; +} + +static int snd_intel8x0_hw_free(struct snd_pcm_substream *substream) +{ + struct ichdev *ichdev = get_ichdev(substream); + + if (ichdev->pcm_open_flag) { + snd_ac97_pcm_close(ichdev->pcm); + ichdev->pcm_open_flag = 0; + ichdev->prepared = 0; + } + return 0; +} + +static void snd_intel8x0_setup_pcm_out(struct intel8x0 *chip, + struct snd_pcm_runtime *runtime) +{ + unsigned int cnt; + int dbl = runtime->rate > 48000; + + guard(spinlock_irq)(&chip->reg_lock); + switch (chip->device_type) { + case DEVICE_ALI: + cnt = igetdword(chip, ICHREG(ALI_SCR)); + cnt &= ~ICH_ALI_SC_PCM_246_MASK; + if (runtime->channels == 4 || dbl) + cnt |= ICH_ALI_SC_PCM_4; + else if (runtime->channels == 6) + cnt |= ICH_ALI_SC_PCM_6; + iputdword(chip, ICHREG(ALI_SCR), cnt); + break; + case DEVICE_SIS: + cnt = igetdword(chip, ICHREG(GLOB_CNT)); + cnt &= ~ICH_SIS_PCM_246_MASK; + if (runtime->channels == 4 || dbl) + cnt |= ICH_SIS_PCM_4; + else if (runtime->channels == 6) + cnt |= ICH_SIS_PCM_6; + iputdword(chip, ICHREG(GLOB_CNT), cnt); + break; + default: + cnt = igetdword(chip, ICHREG(GLOB_CNT)); + cnt &= ~(ICH_PCM_246_MASK | ICH_PCM_20BIT); + if (runtime->channels == 4 || dbl) + cnt |= ICH_PCM_4; + else if (runtime->channels == 6) + cnt |= ICH_PCM_6; + else if (runtime->channels == 8) + cnt |= ICH_PCM_8; + if (chip->device_type == DEVICE_NFORCE) { + /* reset to 2ch once to keep the 6 channel data in alignment, + * to start from Front Left always + */ + if (cnt & ICH_PCM_246_MASK) { + iputdword(chip, ICHREG(GLOB_CNT), cnt & ~ICH_PCM_246_MASK); + spin_unlock_irq(&chip->reg_lock); + msleep(50); /* grrr... */ + spin_lock_irq(&chip->reg_lock); + } + } else if (chip->device_type == DEVICE_INTEL_ICH4) { + if (runtime->sample_bits > 16) + cnt |= ICH_PCM_20BIT; + } + iputdword(chip, ICHREG(GLOB_CNT), cnt); + break; + } +} + +static int snd_intel8x0_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ichdev *ichdev = get_ichdev(substream); + + ichdev->physbuf = runtime->dma_addr; + ichdev->size = snd_pcm_lib_buffer_bytes(substream); + ichdev->fragsize = snd_pcm_lib_period_bytes(substream); + if (ichdev->ichd == ICHD_PCMOUT) { + snd_intel8x0_setup_pcm_out(chip, runtime); + if (chip->device_type == DEVICE_INTEL_ICH4) + ichdev->pos_shift = (runtime->sample_bits > 16) ? 2 : 1; + } + snd_intel8x0_setup_periods(chip, ichdev); + ichdev->prepared = 1; + return 0; +} + +static snd_pcm_uframes_t snd_intel8x0_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + struct ichdev *ichdev = get_ichdev(substream); + size_t ptr1, ptr; + int civ, timeout = 10; + unsigned int position; + + guard(spinlock)(&chip->reg_lock); + do { + civ = igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV); + ptr1 = igetword(chip, ichdev->reg_offset + ichdev->roff_picb); + position = ichdev->position; + if (ptr1 == 0) { + udelay(10); + continue; + } + if (civ != igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV)) + continue; + + /* IO read operation is very expensive inside virtual machine + * as it is emulated. The probability that subsequent PICB read + * will return different result is high enough to loop till + * timeout here. + * Same CIV is strict enough condition to be sure that PICB + * is valid inside VM on emulated card. */ + if (chip->inside_vm) + break; + if (ptr1 == igetword(chip, ichdev->reg_offset + ichdev->roff_picb)) + break; + } while (timeout--); + ptr = ichdev->last_pos; + if (ptr1 != 0) { + ptr1 <<= ichdev->pos_shift; + ptr = ichdev->fragsize1 - ptr1; + ptr += position; + if (ptr < ichdev->last_pos) { + unsigned int pos_base, last_base; + pos_base = position / ichdev->fragsize1; + last_base = ichdev->last_pos / ichdev->fragsize1; + /* another sanity check; ptr1 can go back to full + * before the base position is updated + */ + if (pos_base == last_base) + ptr = ichdev->last_pos; + } + } + ichdev->last_pos = ptr; + if (ptr >= ichdev->size) + return 0; + return bytes_to_frames(substream->runtime, ptr); +} + +static const struct snd_pcm_hardware snd_intel8x0_stream = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 128 * 1024, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const unsigned int channels4[] = { + 2, 4, +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_channels4 = { + .count = ARRAY_SIZE(channels4), + .list = channels4, + .mask = 0, +}; + +static const unsigned int channels6[] = { + 2, 4, 6, +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_channels6 = { + .count = ARRAY_SIZE(channels6), + .list = channels6, + .mask = 0, +}; + +static const unsigned int channels8[] = { + 2, 4, 6, 8, +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_channels8 = { + .count = ARRAY_SIZE(channels8), + .list = channels8, + .mask = 0, +}; + +static int snd_intel8x0_pcm_open(struct snd_pcm_substream *substream, struct ichdev *ichdev) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + ichdev->substream = substream; + runtime->hw = snd_intel8x0_stream; + runtime->hw.rates = ichdev->pcm->rates; + snd_pcm_limit_hw_rates(runtime); + if (chip->device_type == DEVICE_SIS) { + runtime->hw.buffer_bytes_max = 64*1024; + runtime->hw.period_bytes_max = 64*1024; + } + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + runtime->private_data = ichdev; + return 0; +} + +static int snd_intel8x0_playback_open(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_PCMOUT]); + if (err < 0) + return err; + + if (chip->multi8) { + runtime->hw.channels_max = 8; + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_constraints_channels8); + } else if (chip->multi6) { + runtime->hw.channels_max = 6; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_constraints_channels6); + } else if (chip->multi4) { + runtime->hw.channels_max = 4; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_constraints_channels4); + } + if (chip->dra) { + snd_ac97_pcm_double_rate_rules(runtime); + } + if (chip->smp20bit) { + runtime->hw.formats |= SNDRV_PCM_FMTBIT_S32_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 20); + } + return 0; +} + +static int snd_intel8x0_playback_close(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + chip->ichd[ICHD_PCMOUT].substream = NULL; + return 0; +} + +static int snd_intel8x0_capture_open(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + return snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_PCMIN]); +} + +static int snd_intel8x0_capture_close(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + chip->ichd[ICHD_PCMIN].substream = NULL; + return 0; +} + +static int snd_intel8x0_mic_open(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + return snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_MIC]); +} + +static int snd_intel8x0_mic_close(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + chip->ichd[ICHD_MIC].substream = NULL; + return 0; +} + +static int snd_intel8x0_mic2_open(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + return snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_MIC2]); +} + +static int snd_intel8x0_mic2_close(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + chip->ichd[ICHD_MIC2].substream = NULL; + return 0; +} + +static int snd_intel8x0_capture2_open(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + return snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_PCM2IN]); +} + +static int snd_intel8x0_capture2_close(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + chip->ichd[ICHD_PCM2IN].substream = NULL; + return 0; +} + +static int snd_intel8x0_spdif_open(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + int idx = chip->device_type == DEVICE_NFORCE ? NVD_SPBAR : ICHD_SPBAR; + + return snd_intel8x0_pcm_open(substream, &chip->ichd[idx]); +} + +static int snd_intel8x0_spdif_close(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + int idx = chip->device_type == DEVICE_NFORCE ? NVD_SPBAR : ICHD_SPBAR; + + chip->ichd[idx].substream = NULL; + return 0; +} + +static int snd_intel8x0_ali_ac97spdifout_open(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + unsigned int val; + + scoped_guard(spinlock_irq, &chip->reg_lock) { + val = igetdword(chip, ICHREG(ALI_INTERFACECR)); + val |= ICH_ALI_IF_AC97SP; + iputdword(chip, ICHREG(ALI_INTERFACECR), val); + /* also needs to set ALI_SC_CODEC_SPDF correctly */ + } + + return snd_intel8x0_pcm_open(substream, &chip->ichd[ALID_AC97SPDIFOUT]); +} + +static int snd_intel8x0_ali_ac97spdifout_close(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + unsigned int val; + + chip->ichd[ALID_AC97SPDIFOUT].substream = NULL; + guard(spinlock_irq)(&chip->reg_lock); + val = igetdword(chip, ICHREG(ALI_INTERFACECR)); + val &= ~ICH_ALI_IF_AC97SP; + iputdword(chip, ICHREG(ALI_INTERFACECR), val); + + return 0; +} + +#if 0 // NYI +static int snd_intel8x0_ali_spdifin_open(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + return snd_intel8x0_pcm_open(substream, &chip->ichd[ALID_SPDIFIN]); +} + +static int snd_intel8x0_ali_spdifin_close(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + chip->ichd[ALID_SPDIFIN].substream = NULL; + return 0; +} + +static int snd_intel8x0_ali_spdifout_open(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + return snd_intel8x0_pcm_open(substream, &chip->ichd[ALID_SPDIFOUT]); +} + +static int snd_intel8x0_ali_spdifout_close(struct snd_pcm_substream *substream) +{ + struct intel8x0 *chip = snd_pcm_substream_chip(substream); + + chip->ichd[ALID_SPDIFOUT].substream = NULL; + return 0; +} +#endif + +static const struct snd_pcm_ops snd_intel8x0_playback_ops = { + .open = snd_intel8x0_playback_open, + .close = snd_intel8x0_playback_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_pcm_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0_capture_ops = { + .open = snd_intel8x0_capture_open, + .close = snd_intel8x0_capture_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_pcm_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0_capture_mic_ops = { + .open = snd_intel8x0_mic_open, + .close = snd_intel8x0_mic_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_pcm_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0_capture_mic2_ops = { + .open = snd_intel8x0_mic2_open, + .close = snd_intel8x0_mic2_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_pcm_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0_capture2_ops = { + .open = snd_intel8x0_capture2_open, + .close = snd_intel8x0_capture2_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_pcm_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0_spdif_ops = { + .open = snd_intel8x0_spdif_open, + .close = snd_intel8x0_spdif_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_pcm_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0_ali_playback_ops = { + .open = snd_intel8x0_playback_open, + .close = snd_intel8x0_playback_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_ali_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0_ali_capture_ops = { + .open = snd_intel8x0_capture_open, + .close = snd_intel8x0_capture_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_ali_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0_ali_capture_mic_ops = { + .open = snd_intel8x0_mic_open, + .close = snd_intel8x0_mic_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_ali_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0_ali_ac97spdifout_ops = { + .open = snd_intel8x0_ali_ac97spdifout_open, + .close = snd_intel8x0_ali_ac97spdifout_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_ali_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +#if 0 // NYI +static struct snd_pcm_ops snd_intel8x0_ali_spdifin_ops = { + .open = snd_intel8x0_ali_spdifin_open, + .close = snd_intel8x0_ali_spdifin_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_pcm_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; + +static struct snd_pcm_ops snd_intel8x0_ali_spdifout_ops = { + .open = snd_intel8x0_ali_spdifout_open, + .close = snd_intel8x0_ali_spdifout_close, + .hw_params = snd_intel8x0_hw_params, + .hw_free = snd_intel8x0_hw_free, + .prepare = snd_intel8x0_pcm_prepare, + .trigger = snd_intel8x0_pcm_trigger, + .pointer = snd_intel8x0_pcm_pointer, +}; +#endif // NYI + +struct ich_pcm_table { + char *suffix; + const struct snd_pcm_ops *playback_ops; + const struct snd_pcm_ops *capture_ops; + size_t prealloc_size; + size_t prealloc_max_size; + int ac97_idx; +}; + +#define intel8x0_dma_type(chip) \ + ((chip)->fix_nocache ? SNDRV_DMA_TYPE_DEV_WC : SNDRV_DMA_TYPE_DEV) + +static int snd_intel8x0_pcm1(struct intel8x0 *chip, int device, + const struct ich_pcm_table *rec) +{ + struct snd_pcm *pcm; + int err; + char name[32]; + + if (rec->suffix) + sprintf(name, "Intel ICH - %s", rec->suffix); + else + strscpy(name, "Intel ICH"); + err = snd_pcm_new(chip->card, name, device, + rec->playback_ops ? 1 : 0, + rec->capture_ops ? 1 : 0, &pcm); + if (err < 0) + return err; + + if (rec->playback_ops) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, rec->playback_ops); + if (rec->capture_ops) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, rec->capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + if (rec->suffix) + sprintf(pcm->name, "%s - %s", chip->card->shortname, rec->suffix); + else + strscpy(pcm->name, chip->card->shortname); + chip->pcm[device] = pcm; + + snd_pcm_set_managed_buffer_all(pcm, intel8x0_dma_type(chip), + &chip->pci->dev, + rec->prealloc_size, rec->prealloc_max_size); + + if (rec->playback_ops && + rec->playback_ops->open == snd_intel8x0_playback_open) { + struct snd_pcm_chmap *chmap; + int chs = 2; + if (chip->multi8) + chs = 8; + else if (chip->multi6) + chs = 6; + else if (chip->multi4) + chs = 4; + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_alt_chmaps, chs, 0, + &chmap); + if (err < 0) + return err; + chmap->channel_mask = SND_PCM_CHMAP_MASK_2468; + chip->ac97[0]->chmaps[SNDRV_PCM_STREAM_PLAYBACK] = chmap; + } + + return 0; +} + +static const struct ich_pcm_table intel_pcms[] = { + { + .playback_ops = &snd_intel8x0_playback_ops, + .capture_ops = &snd_intel8x0_capture_ops, + .prealloc_size = 64 * 1024, + .prealloc_max_size = 128 * 1024, + }, + { + .suffix = "MIC ADC", + .capture_ops = &snd_intel8x0_capture_mic_ops, + .prealloc_size = 0, + .prealloc_max_size = 128 * 1024, + .ac97_idx = ICHD_MIC, + }, + { + .suffix = "MIC2 ADC", + .capture_ops = &snd_intel8x0_capture_mic2_ops, + .prealloc_size = 0, + .prealloc_max_size = 128 * 1024, + .ac97_idx = ICHD_MIC2, + }, + { + .suffix = "ADC2", + .capture_ops = &snd_intel8x0_capture2_ops, + .prealloc_size = 0, + .prealloc_max_size = 128 * 1024, + .ac97_idx = ICHD_PCM2IN, + }, + { + .suffix = "IEC958", + .playback_ops = &snd_intel8x0_spdif_ops, + .prealloc_size = 64 * 1024, + .prealloc_max_size = 128 * 1024, + .ac97_idx = ICHD_SPBAR, + }, +}; + +static const struct ich_pcm_table nforce_pcms[] = { + { + .playback_ops = &snd_intel8x0_playback_ops, + .capture_ops = &snd_intel8x0_capture_ops, + .prealloc_size = 64 * 1024, + .prealloc_max_size = 128 * 1024, + }, + { + .suffix = "MIC ADC", + .capture_ops = &snd_intel8x0_capture_mic_ops, + .prealloc_size = 0, + .prealloc_max_size = 128 * 1024, + .ac97_idx = NVD_MIC, + }, + { + .suffix = "IEC958", + .playback_ops = &snd_intel8x0_spdif_ops, + .prealloc_size = 64 * 1024, + .prealloc_max_size = 128 * 1024, + .ac97_idx = NVD_SPBAR, + }, +}; + +static const struct ich_pcm_table ali_pcms[] = { + { + .playback_ops = &snd_intel8x0_ali_playback_ops, + .capture_ops = &snd_intel8x0_ali_capture_ops, + .prealloc_size = 64 * 1024, + .prealloc_max_size = 128 * 1024, + }, + { + .suffix = "MIC ADC", + .capture_ops = &snd_intel8x0_ali_capture_mic_ops, + .prealloc_size = 0, + .prealloc_max_size = 128 * 1024, + .ac97_idx = ALID_MIC, + }, + { + .suffix = "IEC958", + .playback_ops = &snd_intel8x0_ali_ac97spdifout_ops, + /* .capture_ops = &snd_intel8x0_ali_spdifin_ops, */ + .prealloc_size = 64 * 1024, + .prealloc_max_size = 128 * 1024, + .ac97_idx = ALID_AC97SPDIFOUT, + }, +#if 0 // NYI + { + .suffix = "HW IEC958", + .playback_ops = &snd_intel8x0_ali_spdifout_ops, + .prealloc_size = 64 * 1024, + .prealloc_max_size = 128 * 1024, + }, +#endif +}; + +static int snd_intel8x0_pcm(struct intel8x0 *chip) +{ + int i, tblsize, device, err; + const struct ich_pcm_table *tbl, *rec; + + switch (chip->device_type) { + case DEVICE_INTEL_ICH4: + tbl = intel_pcms; + tblsize = ARRAY_SIZE(intel_pcms); + if (spdif_aclink) + tblsize--; + break; + case DEVICE_NFORCE: + tbl = nforce_pcms; + tblsize = ARRAY_SIZE(nforce_pcms); + if (spdif_aclink) + tblsize--; + break; + case DEVICE_ALI: + tbl = ali_pcms; + tblsize = ARRAY_SIZE(ali_pcms); + break; + default: + tbl = intel_pcms; + tblsize = 2; + break; + } + + device = 0; + for (i = 0; i < tblsize; i++) { + rec = tbl + i; + if (i > 0 && rec->ac97_idx) { + /* activate PCM only when associated AC'97 codec */ + if (! chip->ichd[rec->ac97_idx].pcm) + continue; + } + err = snd_intel8x0_pcm1(chip, device, rec); + if (err < 0) + return err; + device++; + } + + chip->pcm_devs = device; + return 0; +} + + +/* + * Mixer part + */ + +static void snd_intel8x0_mixer_free_ac97_bus(struct snd_ac97_bus *bus) +{ + struct intel8x0 *chip = bus->private_data; + chip->ac97_bus = NULL; +} + +static void snd_intel8x0_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct intel8x0 *chip = ac97->private_data; + chip->ac97[ac97->num] = NULL; +} + +static const struct ac97_pcm ac97_pcm_defs[] = { + /* front PCM */ + { + .exclusive = 1, + .r = { { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT) | + (1 << AC97_SLOT_PCM_CENTER) | + (1 << AC97_SLOT_PCM_SLEFT) | + (1 << AC97_SLOT_PCM_SRIGHT) | + (1 << AC97_SLOT_LFE) + }, + { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT) | + (1 << AC97_SLOT_PCM_LEFT_0) | + (1 << AC97_SLOT_PCM_RIGHT_0) + } + } + }, + /* PCM IN #1 */ + { + .stream = 1, + .exclusive = 1, + .r = { { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT) + } + } + }, + /* MIC IN #1 */ + { + .stream = 1, + .exclusive = 1, + .r = { { + .slots = (1 << AC97_SLOT_MIC) + } + } + }, + /* S/PDIF PCM */ + { + .exclusive = 1, + .spdif = 1, + .r = { { + .slots = (1 << AC97_SLOT_SPDIF_LEFT2) | + (1 << AC97_SLOT_SPDIF_RIGHT2) + } + } + }, + /* PCM IN #2 */ + { + .stream = 1, + .exclusive = 1, + .r = { { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT) + } + } + }, + /* MIC IN #2 */ + { + .stream = 1, + .exclusive = 1, + .r = { { + .slots = (1 << AC97_SLOT_MIC) + } + } + }, +}; + +static const struct ac97_quirk ac97_quirks[] = { + { + .subvendor = 0x0e11, + .subdevice = 0x000e, + .name = "Compaq Deskpro EN", /* AD1885 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x0e11, + .subdevice = 0x008a, + .name = "Compaq Evo W4000", /* AD1885 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x0e11, + .subdevice = 0x00b8, + .name = "Compaq Evo D510C", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x0e11, + .subdevice = 0x0860, + .name = "HP/Compaq nx7010", + .type = AC97_TUNE_MUTE_LED + }, + { + .subvendor = 0x1014, + .subdevice = 0x0534, + .name = "ThinkPad X31", + .type = AC97_TUNE_INV_EAPD + }, + { + .subvendor = 0x1014, + .subdevice = 0x1f00, + .name = "MS-9128", + .type = AC97_TUNE_ALC_JACK + }, + { + .subvendor = 0x1014, + .subdevice = 0x0267, + .name = "IBM NetVista A30p", /* AD1981B */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1025, + .subdevice = 0x0082, + .name = "Acer Travelmate 2310", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1025, + .subdevice = 0x0083, + .name = "Acer Aspire 3003LCi", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x00d8, + .name = "Dell Precision 530", /* AD1885 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x010d, + .name = "Dell", /* which model? AD1885 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x0126, + .name = "Dell Optiplex GX260", /* AD1981A */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x012c, + .name = "Dell Precision 650", /* AD1981A */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x012d, + .name = "Dell Precision 450", /* AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x0147, + .name = "Dell", /* which model? AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x0151, + .name = "Dell Optiplex GX270", /* AD1981B */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x014e, + .name = "Dell D800", /* STAC9750/51 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x0163, + .name = "Dell Unknown", /* STAC9750/51 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x016a, + .name = "Dell Inspiron 8600", /* STAC9750/51 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x0182, + .name = "Dell Latitude D610", /* STAC9750/51 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1028, + .subdevice = 0x0186, + .name = "Dell Latitude D810", /* cf. Malone #41015 */ + .type = AC97_TUNE_HP_MUTE_LED + }, + { + .subvendor = 0x1028, + .subdevice = 0x0188, + .name = "Dell Inspiron 6000", + .type = AC97_TUNE_HP_MUTE_LED /* cf. Malone #41015 */ + }, + { + .subvendor = 0x1028, + .subdevice = 0x0189, + .name = "Dell Inspiron 9300", + .type = AC97_TUNE_HP_MUTE_LED + }, + { + .subvendor = 0x1028, + .subdevice = 0x0191, + .name = "Dell Inspiron 8600", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x103c, + .subdevice = 0x006d, + .name = "HP zv5000", + .type = AC97_TUNE_MUTE_LED /*AD1981B*/ + }, + { /* FIXME: which codec? */ + .subvendor = 0x103c, + .subdevice = 0x00c3, + .name = "HP xw6000", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x103c, + .subdevice = 0x088c, + .name = "HP nc8000", + .type = AC97_TUNE_HP_MUTE_LED + }, + { + .subvendor = 0x103c, + .subdevice = 0x0890, + .name = "HP nc6000", + .type = AC97_TUNE_MUTE_LED + }, + { + .subvendor = 0x103c, + .subdevice = 0x129d, + .name = "HP xw8000", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x103c, + .subdevice = 0x0938, + .name = "HP nc4200", + .type = AC97_TUNE_HP_MUTE_LED + }, + { + .subvendor = 0x103c, + .subdevice = 0x099c, + .name = "HP nx6110/nc6120", + .type = AC97_TUNE_HP_MUTE_LED + }, + { + .subvendor = 0x103c, + .subdevice = 0x0944, + .name = "HP nc6220", + .type = AC97_TUNE_HP_MUTE_LED + }, + { + .subvendor = 0x103c, + .subdevice = 0x0934, + .name = "HP nc8220", + .type = AC97_TUNE_HP_MUTE_LED + }, + { + .subvendor = 0x103c, + .subdevice = 0x12f1, + .name = "HP xw8200", /* AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x103c, + .subdevice = 0x12f2, + .name = "HP xw6200", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x103c, + .subdevice = 0x3008, + .name = "HP xw4200", /* AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x104d, + .subdevice = 0x8144, + .name = "Sony", + .type = AC97_TUNE_INV_EAPD + }, + { + .subvendor = 0x104d, + .subdevice = 0x8197, + .name = "Sony S1XP", + .type = AC97_TUNE_INV_EAPD + }, + { + .subvendor = 0x104d, + .subdevice = 0x81c0, + .name = "Sony VAIO VGN-T350P", /*AD1981B*/ + .type = AC97_TUNE_INV_EAPD + }, + { + .subvendor = 0x104d, + .subdevice = 0x81c5, + .name = "Sony VAIO VGN-B1VP", /*AD1981B*/ + .type = AC97_TUNE_INV_EAPD + }, + { + .subvendor = 0x1043, + .subdevice = 0x80f3, + .name = "ASUS ICH5/AD1985", + .type = AC97_TUNE_AD_SHARING + }, + { + .subvendor = 0x10cf, + .subdevice = 0x11c3, + .name = "Fujitsu-Siemens E4010", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x10cf, + .subdevice = 0x1225, + .name = "Fujitsu-Siemens T3010", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x10cf, + .subdevice = 0x1253, + .name = "Fujitsu S6210", /* STAC9750/51 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x10cf, + .subdevice = 0x127d, + .name = "Fujitsu Lifebook P7010", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x10cf, + .subdevice = 0x127e, + .name = "Fujitsu Lifebook C1211D", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x10cf, + .subdevice = 0x12ec, + .name = "Fujitsu-Siemens 4010", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x10cf, + .subdevice = 0x12f2, + .name = "Fujitsu-Siemens Celsius H320", + .type = AC97_TUNE_SWAP_HP + }, + { + .subvendor = 0x10f1, + .subdevice = 0x2665, + .name = "Fujitsu-Siemens Celsius", /* AD1981? */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x10f1, + .subdevice = 0x2885, + .name = "AMD64 Mobo", /* ALC650 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x10f1, + .subdevice = 0x2895, + .name = "Tyan Thunder K8WE", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x10f7, + .subdevice = 0x834c, + .name = "Panasonic CF-R4", + .type = AC97_TUNE_HP_ONLY, + }, + { + .subvendor = 0x110a, + .subdevice = 0x0056, + .name = "Fujitsu-Siemens Scenic", /* AD1981? */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x11d4, + .subdevice = 0x5375, + .name = "ADI AD1985 (discrete)", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1462, + .subdevice = 0x5470, + .name = "MSI P4 ATX 645 Ultra", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x161f, + .subdevice = 0x202f, + .name = "Gateway M520", + .type = AC97_TUNE_INV_EAPD + }, + { + .subvendor = 0x161f, + .subdevice = 0x203a, + .name = "Gateway 4525GZ", /* AD1981B */ + .type = AC97_TUNE_INV_EAPD + }, + { + .subvendor = 0x1734, + .subdevice = 0x0088, + .name = "Fujitsu-Siemens D1522", /* AD1981 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x8086, + .subdevice = 0x2000, + .mask = 0xfff0, + .name = "Intel ICH5/AD1985", + .type = AC97_TUNE_AD_SHARING + }, + { + .subvendor = 0x8086, + .subdevice = 0x4000, + .mask = 0xfff0, + .name = "Intel ICH5/AD1985", + .type = AC97_TUNE_AD_SHARING + }, + { + .subvendor = 0x8086, + .subdevice = 0x4856, + .name = "Intel D845WN (82801BA)", + .type = AC97_TUNE_SWAP_HP + }, + { + .subvendor = 0x8086, + .subdevice = 0x4d44, + .name = "Intel D850EMV2", /* AD1885 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x8086, + .subdevice = 0x4d56, + .name = "Intel ICH/AD1885", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x8086, + .subdevice = 0x6000, + .mask = 0xfff0, + .name = "Intel ICH5/AD1985", + .type = AC97_TUNE_AD_SHARING + }, + { + .subvendor = 0x8086, + .subdevice = 0xe000, + .mask = 0xfff0, + .name = "Intel ICH5/AD1985", + .type = AC97_TUNE_AD_SHARING + }, +#if 0 /* FIXME: this seems wrong on most boards */ + { + .subvendor = 0x8086, + .subdevice = 0xa000, + .mask = 0xfff0, + .name = "Intel ICH5/AD1985", + .type = AC97_TUNE_HP_ONLY + }, +#endif + { } /* terminator */ +}; + +static int snd_intel8x0_mixer(struct intel8x0 *chip, int ac97_clock, + const char *quirk_override) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + unsigned int i, codecs; + unsigned int glob_sta = 0; + const struct snd_ac97_bus_ops *ops; + static const struct snd_ac97_bus_ops standard_bus_ops = { + .write = snd_intel8x0_codec_write, + .read = snd_intel8x0_codec_read, + }; + static const struct snd_ac97_bus_ops ali_bus_ops = { + .write = snd_intel8x0_ali_codec_write, + .read = snd_intel8x0_ali_codec_read, + }; + + chip->spdif_idx = -1; /* use PCMOUT (or disabled) */ + if (!spdif_aclink) { + switch (chip->device_type) { + case DEVICE_NFORCE: + chip->spdif_idx = NVD_SPBAR; + break; + case DEVICE_ALI: + chip->spdif_idx = ALID_AC97SPDIFOUT; + break; + case DEVICE_INTEL_ICH4: + chip->spdif_idx = ICHD_SPBAR; + break; + } + } + + chip->in_ac97_init = 1; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.private_free = snd_intel8x0_mixer_free_ac97; + ac97.scaps = AC97_SCAP_SKIP_MODEM | AC97_SCAP_POWER_SAVE; + if (chip->xbox) + ac97.scaps |= AC97_SCAP_DETECT_BY_VENDOR; + if (chip->device_type != DEVICE_ALI) { + glob_sta = igetdword(chip, ICHREG(GLOB_STA)); + ops = &standard_bus_ops; + chip->in_sdin_init = 1; + codecs = 0; + for (i = 0; i < chip->max_codecs; i++) { + if (! (glob_sta & chip->codec_bit[i])) + continue; + if (chip->device_type == DEVICE_INTEL_ICH4) { + snd_intel8x0_codec_read_test(chip, codecs); + chip->ac97_sdin[codecs] = + igetbyte(chip, ICHREG(SDM)) & ICH_LDI_MASK; + if (snd_BUG_ON(chip->ac97_sdin[codecs] >= 3)) + chip->ac97_sdin[codecs] = 0; + } else + chip->ac97_sdin[codecs] = i; + codecs++; + } + chip->in_sdin_init = 0; + if (! codecs) + codecs = 1; + } else { + ops = &ali_bus_ops; + codecs = 1; + /* detect the secondary codec */ + for (i = 0; i < 100; i++) { + unsigned int reg = igetdword(chip, ICHREG(ALI_RTSR)); + if (reg & 0x40) { + codecs = 2; + break; + } + iputdword(chip, ICHREG(ALI_RTSR), reg | 0x40); + udelay(1); + } + } + err = snd_ac97_bus(chip->card, 0, ops, chip, &pbus); + if (err < 0) + goto __err; + pbus->private_free = snd_intel8x0_mixer_free_ac97_bus; + if (ac97_clock >= 8000 && ac97_clock <= 48000) + pbus->clock = ac97_clock; + /* FIXME: my test board doesn't work well with VRA... */ + if (chip->device_type == DEVICE_ALI) + pbus->no_vra = 1; + else + pbus->dra = 1; + chip->ac97_bus = pbus; + chip->ncodecs = codecs; + + ac97.pci = chip->pci; + for (i = 0; i < codecs; i++) { + ac97.num = i; + err = snd_ac97_mixer(pbus, &ac97, &chip->ac97[i]); + if (err < 0) { + if (err != -EACCES) + dev_err(chip->card->dev, + "Unable to initialize codec #%d\n", i); + if (i == 0) + goto __err; + } + } + /* tune up the primary codec */ + snd_ac97_tune_hardware(chip->ac97[0], ac97_quirks, quirk_override); + /* enable separate SDINs for ICH4 */ + if (chip->device_type == DEVICE_INTEL_ICH4) + pbus->isdin = 1; + /* find the available PCM streams */ + i = ARRAY_SIZE(ac97_pcm_defs); + if (chip->device_type != DEVICE_INTEL_ICH4) + i -= 2; /* do not allocate PCM2IN and MIC2 */ + if (chip->spdif_idx < 0) + i--; /* do not allocate S/PDIF */ + err = snd_ac97_pcm_assign(pbus, i, ac97_pcm_defs); + if (err < 0) + goto __err; + chip->ichd[ICHD_PCMOUT].pcm = &pbus->pcms[0]; + chip->ichd[ICHD_PCMIN].pcm = &pbus->pcms[1]; + chip->ichd[ICHD_MIC].pcm = &pbus->pcms[2]; + if (chip->spdif_idx >= 0) + chip->ichd[chip->spdif_idx].pcm = &pbus->pcms[3]; + if (chip->device_type == DEVICE_INTEL_ICH4) { + chip->ichd[ICHD_PCM2IN].pcm = &pbus->pcms[4]; + chip->ichd[ICHD_MIC2].pcm = &pbus->pcms[5]; + } + /* enable separate SDINs for ICH4 */ + if (chip->device_type == DEVICE_INTEL_ICH4) { + struct ac97_pcm *pcm = chip->ichd[ICHD_PCM2IN].pcm; + u8 tmp = igetbyte(chip, ICHREG(SDM)); + tmp &= ~(ICH_DI2L_MASK|ICH_DI1L_MASK); + if (pcm) { + tmp |= ICH_SE; /* steer enable for multiple SDINs */ + tmp |= chip->ac97_sdin[0] << ICH_DI1L_SHIFT; + for (i = 1; i < 4; i++) { + if (pcm->r[0].codec[i]) { + tmp |= chip->ac97_sdin[pcm->r[0].codec[i]->num] << ICH_DI2L_SHIFT; + break; + } + } + } else { + tmp &= ~ICH_SE; /* steer disable */ + } + iputbyte(chip, ICHREG(SDM), tmp); + } + if (pbus->pcms[0].r[0].slots & (1 << AC97_SLOT_PCM_SLEFT)) { + chip->multi4 = 1; + if (pbus->pcms[0].r[0].slots & (1 << AC97_SLOT_LFE)) { + chip->multi6 = 1; + if (chip->ac97[0]->flags & AC97_HAS_8CH) + chip->multi8 = 1; + } + } + if (pbus->pcms[0].r[1].rslots[0]) { + chip->dra = 1; + } + if (chip->device_type == DEVICE_INTEL_ICH4) { + if ((igetdword(chip, ICHREG(GLOB_STA)) & ICH_SAMPLE_CAP) == ICH_SAMPLE_16_20) + chip->smp20bit = 1; + } + if (chip->device_type == DEVICE_NFORCE && !spdif_aclink) { + /* 48kHz only */ + chip->ichd[chip->spdif_idx].pcm->rates = SNDRV_PCM_RATE_48000; + } + if (chip->device_type == DEVICE_INTEL_ICH4 && !spdif_aclink) { + /* use slot 10/11 for SPDIF */ + u32 val; + val = igetdword(chip, ICHREG(GLOB_CNT)) & ~ICH_PCM_SPDIF_MASK; + val |= ICH_PCM_SPDIF_1011; + iputdword(chip, ICHREG(GLOB_CNT), val); + snd_ac97_update_bits(chip->ac97[0], AC97_EXTENDED_STATUS, 0x03 << 4, 0x03 << 4); + } + chip->in_ac97_init = 0; + return 0; + + __err: + /* clear the cold-reset bit for the next chance */ + if (chip->device_type != DEVICE_ALI) + iputdword(chip, ICHREG(GLOB_CNT), + igetdword(chip, ICHREG(GLOB_CNT)) & ~ICH_AC97COLD); + return err; +} + + +/* + * + */ + +static void do_ali_reset(struct intel8x0 *chip) +{ + iputdword(chip, ICHREG(ALI_SCR), ICH_ALI_SC_RESET); + iputdword(chip, ICHREG(ALI_FIFOCR1), 0x83838383); + iputdword(chip, ICHREG(ALI_FIFOCR2), 0x83838383); + iputdword(chip, ICHREG(ALI_FIFOCR3), 0x83838383); + iputdword(chip, ICHREG(ALI_INTERFACECR), + ICH_ALI_IF_PI|ICH_ALI_IF_PO); + iputdword(chip, ICHREG(ALI_INTERRUPTCR), 0x00000000); + iputdword(chip, ICHREG(ALI_INTERRUPTSR), 0x00000000); +} + +#ifdef CONFIG_SND_AC97_POWER_SAVE +static const struct snd_pci_quirk ich_chip_reset_mode[] = { + SND_PCI_QUIRK(0x1014, 0x051f, "Thinkpad R32", 1), + { } /* end */ +}; + +static int snd_intel8x0_ich_chip_cold_reset(struct intel8x0 *chip) +{ + unsigned int cnt; + /* ACLink on, 2 channels */ + + if (snd_pci_quirk_lookup(chip->pci, ich_chip_reset_mode)) + return -EIO; + + cnt = igetdword(chip, ICHREG(GLOB_CNT)); + cnt &= ~(ICH_ACLINK | ICH_PCM_246_MASK); + + /* do cold reset - the full ac97 powerdown may leave the controller + * in a warm state but actually it cannot communicate with the codec. + */ + iputdword(chip, ICHREG(GLOB_CNT), cnt & ~ICH_AC97COLD); + cnt = igetdword(chip, ICHREG(GLOB_CNT)); + udelay(10); + iputdword(chip, ICHREG(GLOB_CNT), cnt | ICH_AC97COLD); + msleep(1); + return 0; +} +#define snd_intel8x0_ich_chip_can_cold_reset(chip) \ + (!snd_pci_quirk_lookup(chip->pci, ich_chip_reset_mode)) +#else +#define snd_intel8x0_ich_chip_cold_reset(chip) 0 +#define snd_intel8x0_ich_chip_can_cold_reset(chip) (0) +#endif + +static int snd_intel8x0_ich_chip_reset(struct intel8x0 *chip) +{ + unsigned long end_time; + unsigned int cnt; + /* ACLink on, 2 channels */ + cnt = igetdword(chip, ICHREG(GLOB_CNT)); + cnt &= ~(ICH_ACLINK | ICH_PCM_246_MASK); + /* finish cold or do warm reset */ + cnt |= (cnt & ICH_AC97COLD) == 0 ? ICH_AC97COLD : ICH_AC97WARM; + iputdword(chip, ICHREG(GLOB_CNT), cnt); + end_time = (jiffies + (HZ / 4)) + 1; + do { + if ((igetdword(chip, ICHREG(GLOB_CNT)) & ICH_AC97WARM) == 0) + return 0; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + dev_err(chip->card->dev, "AC'97 warm reset still in progress? [0x%x]\n", + igetdword(chip, ICHREG(GLOB_CNT))); + return -EIO; +} + +static int snd_intel8x0_ich_chip_init(struct intel8x0 *chip, int probing) +{ + unsigned long end_time; + unsigned int status, nstatus; + unsigned int cnt; + int err; + + /* put logic to right state */ + /* first clear status bits */ + status = ICH_RCS | ICH_MCINT | ICH_POINT | ICH_PIINT; + if (chip->device_type == DEVICE_NFORCE) + status |= ICH_NVSPINT; + cnt = igetdword(chip, ICHREG(GLOB_STA)); + iputdword(chip, ICHREG(GLOB_STA), cnt & status); + + if (snd_intel8x0_ich_chip_can_cold_reset(chip)) + err = snd_intel8x0_ich_chip_cold_reset(chip); + else + err = snd_intel8x0_ich_chip_reset(chip); + if (err < 0) + return err; + + if (probing) { + /* wait for any codec ready status. + * Once it becomes ready it should remain ready + * as long as we do not disable the ac97 link. + */ + end_time = jiffies + HZ; + do { + status = igetdword(chip, ICHREG(GLOB_STA)) & + chip->codec_isr_bits; + if (status) + break; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + if (! status) { + /* no codec is found */ + dev_err(chip->card->dev, + "codec_ready: codec is not ready [0x%x]\n", + igetdword(chip, ICHREG(GLOB_STA))); + return -EIO; + } + + /* wait for other codecs ready status. */ + end_time = jiffies + HZ / 4; + while (status != chip->codec_isr_bits && + time_after_eq(end_time, jiffies)) { + schedule_timeout_uninterruptible(1); + status |= igetdword(chip, ICHREG(GLOB_STA)) & + chip->codec_isr_bits; + } + + } else { + /* resume phase */ + int i; + status = 0; + for (i = 0; i < chip->ncodecs; i++) + if (chip->ac97[i]) + status |= chip->codec_bit[chip->ac97_sdin[i]]; + /* wait until all the probed codecs are ready */ + end_time = jiffies + HZ; + do { + nstatus = igetdword(chip, ICHREG(GLOB_STA)) & + chip->codec_isr_bits; + if (status == nstatus) + break; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + } + + if (chip->device_type == DEVICE_SIS) { + /* unmute the output on SIS7012 */ + iputword(chip, 0x4c, igetword(chip, 0x4c) | 1); + } + if (chip->device_type == DEVICE_NFORCE && !spdif_aclink) { + /* enable SPDIF interrupt */ + unsigned int val; + pci_read_config_dword(chip->pci, 0x4c, &val); + val |= 0x1000000; + pci_write_config_dword(chip->pci, 0x4c, val); + } + return 0; +} + +static int snd_intel8x0_ali_chip_init(struct intel8x0 *chip, int probing) +{ + u32 reg; + int i = 0; + + reg = igetdword(chip, ICHREG(ALI_SCR)); + if ((reg & 2) == 0) /* Cold required */ + reg |= 2; + else + reg |= 1; /* Warm */ + reg &= ~0x80000000; /* ACLink on */ + iputdword(chip, ICHREG(ALI_SCR), reg); + + for (i = 0; i < HZ / 2; i++) { + if (! (igetdword(chip, ICHREG(ALI_INTERRUPTSR)) & ALI_INT_GPIO)) + goto __ok; + schedule_timeout_uninterruptible(1); + } + dev_err(chip->card->dev, "AC'97 reset failed.\n"); + if (probing) + return -EIO; + + __ok: + for (i = 0; i < HZ / 2; i++) { + reg = igetdword(chip, ICHREG(ALI_RTSR)); + if (reg & 0x80) /* primary codec */ + break; + iputdword(chip, ICHREG(ALI_RTSR), reg | 0x80); + schedule_timeout_uninterruptible(1); + } + + do_ali_reset(chip); + return 0; +} + +static int snd_intel8x0_chip_init(struct intel8x0 *chip, int probing) +{ + unsigned int i, timeout; + int err; + + if (chip->device_type != DEVICE_ALI) { + err = snd_intel8x0_ich_chip_init(chip, probing); + if (err < 0) + return err; + iagetword(chip, 0); /* clear semaphore flag */ + } else { + err = snd_intel8x0_ali_chip_init(chip, probing); + if (err < 0) + return err; + } + + /* disable interrupts */ + for (i = 0; i < chip->bdbars_count; i++) + iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, 0x00); + /* reset channels */ + for (i = 0; i < chip->bdbars_count; i++) + iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, ICH_RESETREGS); + for (i = 0; i < chip->bdbars_count; i++) { + timeout = 100000; + while (--timeout != 0) { + if ((igetbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset) & ICH_RESETREGS) == 0) + break; + } + if (timeout == 0) + dev_err(chip->card->dev, "reset of registers failed?\n"); + } + /* initialize Buffer Descriptor Lists */ + for (i = 0; i < chip->bdbars_count; i++) + iputdword(chip, ICH_REG_OFF_BDBAR + chip->ichd[i].reg_offset, + chip->ichd[i].bdbar_addr); + return 0; +} + +static void snd_intel8x0_free(struct snd_card *card) +{ + struct intel8x0 *chip = card->private_data; + unsigned int i; + + if (chip->irq < 0) + goto __hw_end; + /* disable interrupts */ + for (i = 0; i < chip->bdbars_count; i++) + iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, 0x00); + /* reset channels */ + for (i = 0; i < chip->bdbars_count; i++) + iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, ICH_RESETREGS); + if (chip->device_type == DEVICE_NFORCE && !spdif_aclink) { + /* stop the spdif interrupt */ + unsigned int val; + pci_read_config_dword(chip->pci, 0x4c, &val); + val &= ~0x1000000; + pci_write_config_dword(chip->pci, 0x4c, val); + } + /* --- */ + + __hw_end: + if (chip->irq >= 0) + free_irq(chip->irq, chip); +} + +/* + * power management + */ +static int intel8x0_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct intel8x0 *chip = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + for (i = 0; i < chip->ncodecs; i++) + snd_ac97_suspend(chip->ac97[i]); + if (chip->device_type == DEVICE_INTEL_ICH4) + chip->sdm_saved = igetbyte(chip, ICHREG(SDM)); + + if (chip->irq >= 0) { + free_irq(chip->irq, chip); + chip->irq = -1; + card->sync_irq = -1; + } + return 0; +} + +static int intel8x0_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct snd_card *card = dev_get_drvdata(dev); + struct intel8x0 *chip = card->private_data; + int i; + + snd_intel8x0_chip_init(chip, 0); + if (request_irq(pci->irq, snd_intel8x0_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(dev, "unable to grab IRQ %d, disabling device\n", + pci->irq); + snd_card_disconnect(card); + return -EIO; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + + /* re-initialize mixer stuff */ + if (chip->device_type == DEVICE_INTEL_ICH4 && !spdif_aclink) { + /* enable separate SDINs for ICH4 */ + iputbyte(chip, ICHREG(SDM), chip->sdm_saved); + /* use slot 10/11 for SPDIF */ + iputdword(chip, ICHREG(GLOB_CNT), + (igetdword(chip, ICHREG(GLOB_CNT)) & ~ICH_PCM_SPDIF_MASK) | + ICH_PCM_SPDIF_1011); + } + + for (i = 0; i < chip->ncodecs; i++) + snd_ac97_resume(chip->ac97[i]); + + /* resume status */ + for (i = 0; i < chip->bdbars_count; i++) { + struct ichdev *ichdev = &chip->ichd[i]; + unsigned long port = ichdev->reg_offset; + if (! ichdev->substream || ! ichdev->suspended) + continue; + if (ichdev->ichd == ICHD_PCMOUT) + snd_intel8x0_setup_pcm_out(chip, ichdev->substream->runtime); + iputdword(chip, port + ICH_REG_OFF_BDBAR, ichdev->bdbar_addr); + iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi); + iputbyte(chip, port + ICH_REG_OFF_CIV, ichdev->civ); + iputbyte(chip, port + ichdev->roff_sr, ICH_FIFOE | ICH_BCIS | ICH_LVBCI); + } + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(intel8x0_pm, intel8x0_suspend, intel8x0_resume); + +#define INTEL8X0_TESTBUF_SIZE 32768 /* enough large for one shot */ + +static void intel8x0_measure_ac97_clock(struct intel8x0 *chip) +{ + struct snd_pcm_substream *subs; + struct ichdev *ichdev; + unsigned long port; + unsigned long pos, pos1, t; + int civ, timeout = 1000, attempt = 1; + ktime_t start_time, stop_time; + + if (chip->ac97_bus->clock != 48000) + return; /* specified in module option */ + if (chip->inside_vm && !ac97_clock) + return; /* no measurement on VM */ + + __again: + subs = chip->pcm[0]->streams[0].substream; + if (! subs || subs->dma_buffer.bytes < INTEL8X0_TESTBUF_SIZE) { + dev_warn(chip->card->dev, + "no playback buffer allocated - aborting measure ac97 clock\n"); + return; + } + ichdev = &chip->ichd[ICHD_PCMOUT]; + ichdev->physbuf = subs->dma_buffer.addr; + ichdev->size = ichdev->fragsize = INTEL8X0_TESTBUF_SIZE; + ichdev->substream = NULL; /* don't process interrupts */ + + /* set rate */ + if (snd_ac97_set_rate(chip->ac97[0], AC97_PCM_FRONT_DAC_RATE, 48000) < 0) { + dev_err(chip->card->dev, "cannot set ac97 rate: clock = %d\n", + chip->ac97_bus->clock); + return; + } + snd_intel8x0_setup_periods(chip, ichdev); + port = ichdev->reg_offset; + scoped_guard(spinlock_irq, &chip->reg_lock) { + chip->in_measurement = 1; + /* trigger */ + if (chip->device_type != DEVICE_ALI) + iputbyte(chip, port + ICH_REG_OFF_CR, ICH_IOCE | ICH_STARTBM); + else { + iputbyte(chip, port + ICH_REG_OFF_CR, ICH_IOCE); + iputdword(chip, ICHREG(ALI_DMACR), 1 << ichdev->ali_slot); + } + start_time = ktime_get(); + } + msleep(50); + scoped_guard(spinlock_irq, &chip->reg_lock) { + /* check the position */ + do { + civ = igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV); + pos1 = igetword(chip, ichdev->reg_offset + ichdev->roff_picb); + if (pos1 == 0) { + udelay(10); + continue; + } + if (civ == igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV) && + pos1 == igetword(chip, ichdev->reg_offset + ichdev->roff_picb)) + break; + } while (timeout--); + if (pos1 == 0) { /* oops, this value is not reliable */ + pos = 0; + } else { + pos = ichdev->fragsize1; + pos -= pos1 << ichdev->pos_shift; + pos += ichdev->position; + } + chip->in_measurement = 0; + stop_time = ktime_get(); + /* stop */ + if (chip->device_type == DEVICE_ALI) { + iputdword(chip, ICHREG(ALI_DMACR), 1 << (ichdev->ali_slot + 16)); + iputbyte(chip, port + ICH_REG_OFF_CR, 0); + while (igetbyte(chip, port + ICH_REG_OFF_CR)) + ; + } else { + iputbyte(chip, port + ICH_REG_OFF_CR, 0); + while (!(igetbyte(chip, port + ichdev->roff_sr) & ICH_DCH)) + ; + } + iputbyte(chip, port + ICH_REG_OFF_CR, ICH_RESETREGS); + } + + if (pos == 0) { + dev_err(chip->card->dev, + "measure - unreliable DMA position..\n"); + __retry: + if (attempt < 3) { + msleep(300); + attempt++; + goto __again; + } + goto __end; + } + + pos /= 4; + t = ktime_us_delta(stop_time, start_time); + dev_info(chip->card->dev, + "%s: measured %lu usecs (%lu samples)\n", __func__, t, pos); + if (t == 0) { + dev_err(chip->card->dev, "?? calculation error..\n"); + goto __retry; + } + pos *= 1000; + pos = (pos / t) * 1000 + ((pos % t) * 1000) / t; + if (pos < 40000 || pos >= 60000) { + /* abnormal value. hw problem? */ + dev_info(chip->card->dev, "measured clock %ld rejected\n", pos); + goto __retry; + } else if (pos > 40500 && pos < 41500) + /* first exception - 41000Hz reference clock */ + chip->ac97_bus->clock = 41000; + else if (pos > 43600 && pos < 44600) + /* second exception - 44100HZ reference clock */ + chip->ac97_bus->clock = 44100; + else if (pos < 47500 || pos > 48500) + /* not 48000Hz, tuning the clock.. */ + chip->ac97_bus->clock = (chip->ac97_bus->clock * 48000) / pos; + __end: + dev_info(chip->card->dev, "clocking to %d\n", chip->ac97_bus->clock); + snd_ac97_update_power(chip->ac97[0], AC97_PCM_FRONT_DAC_RATE, 0); +} + +static const struct snd_pci_quirk intel8x0_clock_list[] = { + SND_PCI_QUIRK(0x0e11, 0x008a, "AD1885", 41000), + SND_PCI_QUIRK(0x1014, 0x0581, "AD1981B", 48000), + SND_PCI_QUIRK(0x1028, 0x00be, "AD1885", 44100), + SND_PCI_QUIRK(0x1028, 0x0177, "AD1980", 48000), + SND_PCI_QUIRK(0x1028, 0x01ad, "AD1981B", 48000), + SND_PCI_QUIRK(0x1043, 0x80f3, "AD1985", 48000), + { } /* terminator */ +}; + +static int intel8x0_in_clock_list(struct intel8x0 *chip) +{ + struct pci_dev *pci = chip->pci; + const struct snd_pci_quirk *wl; + + wl = snd_pci_quirk_lookup(pci, intel8x0_clock_list); + if (!wl) + return 0; + dev_info(chip->card->dev, "allow list rate for %04x:%04x is %i\n", + pci->subsystem_vendor, pci->subsystem_device, wl->value); + chip->ac97_bus->clock = wl->value; + return 1; +} + +static void snd_intel8x0_proc_read(struct snd_info_entry * entry, + struct snd_info_buffer *buffer) +{ + struct intel8x0 *chip = entry->private_data; + unsigned int tmp; + + snd_iprintf(buffer, "Intel8x0\n\n"); + if (chip->device_type == DEVICE_ALI) + return; + tmp = igetdword(chip, ICHREG(GLOB_STA)); + snd_iprintf(buffer, "Global control : 0x%08x\n", igetdword(chip, ICHREG(GLOB_CNT))); + snd_iprintf(buffer, "Global status : 0x%08x\n", tmp); + if (chip->device_type == DEVICE_INTEL_ICH4) + snd_iprintf(buffer, "SDM : 0x%08x\n", igetdword(chip, ICHREG(SDM))); + snd_iprintf(buffer, "AC'97 codecs ready :"); + if (tmp & chip->codec_isr_bits) { + int i; + static const char *codecs[3] = { + "primary", "secondary", "tertiary" + }; + for (i = 0; i < chip->max_codecs; i++) + if (tmp & chip->codec_bit[i]) + snd_iprintf(buffer, " %s", codecs[i]); + } else + snd_iprintf(buffer, " none"); + snd_iprintf(buffer, "\n"); + if (chip->device_type == DEVICE_INTEL_ICH4 || + chip->device_type == DEVICE_SIS) + snd_iprintf(buffer, "AC'97 codecs SDIN : %i %i %i\n", + chip->ac97_sdin[0], + chip->ac97_sdin[1], + chip->ac97_sdin[2]); +} + +static void snd_intel8x0_proc_init(struct intel8x0 *chip) +{ + snd_card_ro_proc_new(chip->card, "intel8x0", chip, + snd_intel8x0_proc_read); +} + +struct ich_reg_info { + unsigned int int_sta_mask; + unsigned int offset; +}; + +static const unsigned int ich_codec_bits[3] = { + ICH_PCR, ICH_SCR, ICH_TCR +}; +static const unsigned int sis_codec_bits[3] = { + ICH_PCR, ICH_SCR, ICH_SIS_TCR +}; + +static int snd_intel8x0_inside_vm(struct pci_dev *pci) +{ + int result = inside_vm; + char *msg = NULL; + + /* check module parameter first (override detection) */ + if (result >= 0) { + msg = result ? "enable (forced) VM" : "disable (forced) VM"; + goto fini; + } + + /* check for known (emulated) devices */ + result = 0; + if (pci->subsystem_vendor == PCI_SUBVENDOR_ID_REDHAT_QUMRANET && + pci->subsystem_device == PCI_SUBDEVICE_ID_QEMU) { + /* KVM emulated sound, PCI SSID: 1af4:1100 */ + msg = "enable KVM"; + result = 1; + } else if (pci->subsystem_vendor == 0x1ab8) { + /* Parallels VM emulated sound, PCI SSID: 1ab8:xxxx */ + msg = "enable Parallels VM"; + result = 1; + } + +fini: + if (msg != NULL) + dev_info(&pci->dev, "%s optimization\n", msg); + + return result; +} + +static int snd_intel8x0_init(struct snd_card *card, + struct pci_dev *pci, + unsigned long device_type) +{ + struct intel8x0 *chip = card->private_data; + int err; + unsigned int i; + unsigned int int_sta_masks; + struct ichdev *ichdev; + + static const unsigned int bdbars[] = { + 3, /* DEVICE_INTEL */ + 6, /* DEVICE_INTEL_ICH4 */ + 3, /* DEVICE_SIS */ + 6, /* DEVICE_ALI */ + 4, /* DEVICE_NFORCE */ + }; + static const struct ich_reg_info intel_regs[6] = { + { ICH_PIINT, 0 }, + { ICH_POINT, 0x10 }, + { ICH_MCINT, 0x20 }, + { ICH_M2INT, 0x40 }, + { ICH_P2INT, 0x50 }, + { ICH_SPINT, 0x60 }, + }; + static const struct ich_reg_info nforce_regs[4] = { + { ICH_PIINT, 0 }, + { ICH_POINT, 0x10 }, + { ICH_MCINT, 0x20 }, + { ICH_NVSPINT, 0x70 }, + }; + static const struct ich_reg_info ali_regs[6] = { + { ALI_INT_PCMIN, 0x40 }, + { ALI_INT_PCMOUT, 0x50 }, + { ALI_INT_MICIN, 0x60 }, + { ALI_INT_CODECSPDIFOUT, 0x70 }, + { ALI_INT_SPDIFIN, 0xa0 }, + { ALI_INT_SPDIFOUT, 0xb0 }, + }; + const struct ich_reg_info *tbl; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); + chip->device_type = device_type; + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + /* module parameters */ + chip->buggy_irq = buggy_irq; + chip->buggy_semaphore = buggy_semaphore; + if (xbox) + chip->xbox = 1; + + chip->inside_vm = snd_intel8x0_inside_vm(pci); + + /* + * Intel 82443MX running a 100MHz processor system bus has a hardware + * bug, which aborts PCI busmaster for audio transfer. A workaround + * is to set the pages as non-cached. For details, see the errata in + * http://download.intel.com/design/chipsets/specupdt/24505108.pdf + */ + if (pci->vendor == PCI_VENDOR_ID_INTEL && + pci->device == PCI_DEVICE_ID_INTEL_440MX) + chip->fix_nocache = 1; /* enable workaround */ + + err = pcim_request_all_regions(pci, card->shortname); + if (err < 0) + return err; + + if (device_type == DEVICE_ALI) { + /* ALI5455 has no ac97 region */ + chip->bmaddr = pcim_iomap(pci, 0, 0); + } else { + if (pci_resource_flags(pci, 2) & IORESOURCE_MEM) /* ICH4 and Nforce */ + chip->addr = pcim_iomap(pci, 2, 0); + else + chip->addr = pcim_iomap(pci, 0, 0); + if (pci_resource_flags(pci, 3) & IORESOURCE_MEM) /* ICH4 */ + chip->bmaddr = pcim_iomap(pci, 3, 0); + else + chip->bmaddr = pcim_iomap(pci, 1, 0); + } + + chip->bdbars_count = bdbars[device_type]; + + /* initialize offsets */ + switch (device_type) { + case DEVICE_NFORCE: + tbl = nforce_regs; + break; + case DEVICE_ALI: + tbl = ali_regs; + break; + default: + tbl = intel_regs; + break; + } + for (i = 0; i < chip->bdbars_count; i++) { + ichdev = &chip->ichd[i]; + ichdev->ichd = i; + ichdev->reg_offset = tbl[i].offset; + ichdev->int_sta_mask = tbl[i].int_sta_mask; + if (device_type == DEVICE_SIS) { + /* SiS 7012 swaps the registers */ + ichdev->roff_sr = ICH_REG_OFF_PICB; + ichdev->roff_picb = ICH_REG_OFF_SR; + } else { + ichdev->roff_sr = ICH_REG_OFF_SR; + ichdev->roff_picb = ICH_REG_OFF_PICB; + } + if (device_type == DEVICE_ALI) + ichdev->ali_slot = (ichdev->reg_offset - 0x40) / 0x10; + /* SIS7012 handles the pcm data in bytes, others are in samples */ + ichdev->pos_shift = (device_type == DEVICE_SIS) ? 0 : 1; + } + + /* allocate buffer descriptor lists */ + /* the start of each lists must be aligned to 8 bytes */ + chip->bdbars = snd_devm_alloc_pages(&pci->dev, intel8x0_dma_type(chip), + chip->bdbars_count * sizeof(u32) * + ICH_MAX_FRAGS * 2); + if (!chip->bdbars) + return -ENOMEM; + + /* tables must be aligned to 8 bytes here, but the kernel pages + are much bigger, so we don't care (on i386) */ + int_sta_masks = 0; + for (i = 0; i < chip->bdbars_count; i++) { + ichdev = &chip->ichd[i]; + ichdev->bdbar = ((__le32 *)chip->bdbars->area) + + (i * ICH_MAX_FRAGS * 2); + ichdev->bdbar_addr = chip->bdbars->addr + + (i * sizeof(u32) * ICH_MAX_FRAGS * 2); + int_sta_masks |= ichdev->int_sta_mask; + } + chip->int_sta_reg = device_type == DEVICE_ALI ? + ICH_REG_ALI_INTERRUPTSR : ICH_REG_GLOB_STA; + chip->int_sta_mask = int_sta_masks; + + pci_set_master(pci); + + switch(chip->device_type) { + case DEVICE_INTEL_ICH4: + /* ICH4 can have three codecs */ + chip->max_codecs = 3; + chip->codec_bit = ich_codec_bits; + chip->codec_ready_bits = ICH_PRI | ICH_SRI | ICH_TRI; + break; + case DEVICE_SIS: + /* recent SIS7012 can have three codecs */ + chip->max_codecs = 3; + chip->codec_bit = sis_codec_bits; + chip->codec_ready_bits = ICH_PRI | ICH_SRI | ICH_SIS_TRI; + break; + default: + /* others up to two codecs */ + chip->max_codecs = 2; + chip->codec_bit = ich_codec_bits; + chip->codec_ready_bits = ICH_PRI | ICH_SRI; + break; + } + for (i = 0; i < chip->max_codecs; i++) + chip->codec_isr_bits |= chip->codec_bit[i]; + + err = snd_intel8x0_chip_init(chip, 1); + if (err < 0) + return err; + + /* request irq after initializaing int_sta_mask, etc */ + /* NOTE: we don't use devm version here since it's released / + * re-acquired in PM callbacks. + * It's released explicitly in snd_intel8x0_free(), too. + */ + if (request_irq(pci->irq, snd_intel8x0_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + + card->private_free = snd_intel8x0_free; + + return 0; +} + +static struct shortname_table { + unsigned int id; + const char *s; +} shortnames[] = { + { PCI_DEVICE_ID_INTEL_82801AA_5, "Intel 82801AA-ICH" }, + { PCI_DEVICE_ID_INTEL_82801AB_5, "Intel 82901AB-ICH0" }, + { PCI_DEVICE_ID_INTEL_82801BA_4, "Intel 82801BA-ICH2" }, + { PCI_DEVICE_ID_INTEL_440MX, "Intel 440MX" }, + { PCI_DEVICE_ID_INTEL_82801CA_5, "Intel 82801CA-ICH3" }, + { PCI_DEVICE_ID_INTEL_82801DB_5, "Intel 82801DB-ICH4" }, + { PCI_DEVICE_ID_INTEL_82801EB_5, "Intel ICH5" }, + { PCI_DEVICE_ID_INTEL_ESB_5, "Intel 6300ESB" }, + { PCI_DEVICE_ID_INTEL_ICH6_18, "Intel ICH6" }, + { PCI_DEVICE_ID_INTEL_ICH7_20, "Intel ICH7" }, + { PCI_DEVICE_ID_INTEL_ESB2_14, "Intel ESB2" }, + { PCI_DEVICE_ID_SI_7012, "SiS SI7012" }, + { PCI_DEVICE_ID_NVIDIA_MCP1_AUDIO, "NVidia nForce" }, + { PCI_DEVICE_ID_NVIDIA_MCP2_AUDIO, "NVidia nForce2" }, + { PCI_DEVICE_ID_NVIDIA_MCP3_AUDIO, "NVidia nForce3" }, + { PCI_DEVICE_ID_NVIDIA_CK8S_AUDIO, "NVidia CK8S" }, + { PCI_DEVICE_ID_NVIDIA_CK804_AUDIO, "NVidia CK804" }, + { PCI_DEVICE_ID_NVIDIA_CK8_AUDIO, "NVidia CK8" }, + { 0x003a, "NVidia MCP04" }, + { 0x746d, "AMD AMD8111" }, + { 0x7445, "AMD AMD768" }, + { 0x5455, "ALi M5455" }, + { 0, NULL }, +}; + +static const struct snd_pci_quirk spdif_aclink_defaults[] = { + SND_PCI_QUIRK(0x147b, 0x1c1a, "ASUS KN8", 1), + { } /* end */ +}; + +/* look up allow/deny list for SPDIF over ac-link */ +static int check_default_spdif_aclink(struct pci_dev *pci) +{ + const struct snd_pci_quirk *w; + + w = snd_pci_quirk_lookup(pci, spdif_aclink_defaults); + if (w) { + if (w->value) + dev_dbg(&pci->dev, + "Using SPDIF over AC-Link for %s\n", + snd_pci_quirk_name(w)); + else + dev_dbg(&pci->dev, + "Using integrated SPDIF DMA for %s\n", + snd_pci_quirk_name(w)); + return w->value; + } + return 0; +} + +static int __snd_intel8x0_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct intel8x0 *chip; + int err; + struct shortname_table *name; + + err = snd_devm_card_new(&pci->dev, index, id, THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + if (spdif_aclink < 0) + spdif_aclink = check_default_spdif_aclink(pci); + + strscpy(card->driver, "ICH"); + if (!spdif_aclink) { + switch (pci_id->driver_data) { + case DEVICE_NFORCE: + strscpy(card->driver, "NFORCE"); + break; + case DEVICE_INTEL_ICH4: + strscpy(card->driver, "ICH4"); + } + } + + strscpy(card->shortname, "Intel ICH"); + for (name = shortnames; name->id; name++) { + if (pci->device == name->id) { + strscpy(card->shortname, name->s); + break; + } + } + + if (buggy_irq < 0) { + /* some Nforce[2] and ICH boards have problems with IRQ handling. + * Needs to return IRQ_HANDLED for unknown irqs. + */ + if (pci_id->driver_data == DEVICE_NFORCE) + buggy_irq = 1; + else + buggy_irq = 0; + } + + err = snd_intel8x0_init(card, pci, pci_id->driver_data); + if (err < 0) + return err; + + err = snd_intel8x0_mixer(chip, ac97_clock, ac97_quirk); + if (err < 0) + return err; + err = snd_intel8x0_pcm(chip); + if (err < 0) + return err; + + snd_intel8x0_proc_init(chip); + + snprintf(card->longname, sizeof(card->longname), + "%s with %s at irq %i", card->shortname, + snd_ac97_get_short_name(chip->ac97[0]), chip->irq); + + if (ac97_clock == 0 || ac97_clock == 1) { + if (ac97_clock == 0) { + if (intel8x0_in_clock_list(chip) == 0) + intel8x0_measure_ac97_clock(chip); + } else { + intel8x0_measure_ac97_clock(chip); + } + } + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + return 0; +} + +static int snd_intel8x0_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_intel8x0_probe(pci, pci_id)); +} + +static struct pci_driver intel8x0_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_intel8x0_ids, + .probe = snd_intel8x0_probe, + .driver = { + .pm = &intel8x0_pm, + }, +}; + +module_pci_driver(intel8x0_driver); diff --git a/sound/pci/intel8x0m.c b/sound/pci/intel8x0m.c new file mode 100644 index 000000000..27dbf6125 --- /dev/null +++ b/sound/pci/intel8x0m.c @@ -0,0 +1,1283 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA modem driver for Intel ICH (i8x0) chipsets + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This is modified (by Sasha Khapyorsky ) version + * of ALSA ICH sound driver intel8x0.c . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; " + "SiS 7013; NVidia MCP/2/2S/3 modems"); +MODULE_LICENSE("GPL"); + +static int index = -2; /* Exclude the first card */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static int ac97_clock; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for Intel i8x0 modemcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for Intel i8x0 modemcard."); +module_param(ac97_clock, int, 0444); +MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (0 = auto-detect)."); + +/* just for backward compatibility */ +static bool enable; +module_param(enable, bool, 0444); + +/* + * Direct registers + */ +enum { DEVICE_INTEL, DEVICE_SIS, DEVICE_ALI, DEVICE_NFORCE }; + +#define ICHREG(x) ICH_REG_##x + +#define DEFINE_REGSET(name,base) \ +enum { \ + ICH_REG_##name##_BDBAR = base + 0x0, /* dword - buffer descriptor list base address */ \ + ICH_REG_##name##_CIV = base + 0x04, /* byte - current index value */ \ + ICH_REG_##name##_LVI = base + 0x05, /* byte - last valid index */ \ + ICH_REG_##name##_SR = base + 0x06, /* byte - status register */ \ + ICH_REG_##name##_PICB = base + 0x08, /* word - position in current buffer */ \ + ICH_REG_##name##_PIV = base + 0x0a, /* byte - prefetched index value */ \ + ICH_REG_##name##_CR = base + 0x0b, /* byte - control register */ \ +} + +/* busmaster blocks */ +DEFINE_REGSET(OFF, 0); /* offset */ + +/* values for each busmaster block */ + +/* LVI */ +#define ICH_REG_LVI_MASK 0x1f + +/* SR */ +#define ICH_FIFOE 0x10 /* FIFO error */ +#define ICH_BCIS 0x08 /* buffer completion interrupt status */ +#define ICH_LVBCI 0x04 /* last valid buffer completion interrupt */ +#define ICH_CELV 0x02 /* current equals last valid */ +#define ICH_DCH 0x01 /* DMA controller halted */ + +/* PIV */ +#define ICH_REG_PIV_MASK 0x1f /* mask */ + +/* CR */ +#define ICH_IOCE 0x10 /* interrupt on completion enable */ +#define ICH_FEIE 0x08 /* fifo error interrupt enable */ +#define ICH_LVBIE 0x04 /* last valid buffer interrupt enable */ +#define ICH_RESETREGS 0x02 /* reset busmaster registers */ +#define ICH_STARTBM 0x01 /* start busmaster operation */ + + +/* global block */ +#define ICH_REG_GLOB_CNT 0x3c /* dword - global control */ +#define ICH_TRIE 0x00000040 /* tertiary resume interrupt enable */ +#define ICH_SRIE 0x00000020 /* secondary resume interrupt enable */ +#define ICH_PRIE 0x00000010 /* primary resume interrupt enable */ +#define ICH_ACLINK 0x00000008 /* AClink shut off */ +#define ICH_AC97WARM 0x00000004 /* AC'97 warm reset */ +#define ICH_AC97COLD 0x00000002 /* AC'97 cold reset */ +#define ICH_GIE 0x00000001 /* GPI interrupt enable */ +#define ICH_REG_GLOB_STA 0x40 /* dword - global status */ +#define ICH_TRI 0x20000000 /* ICH4: tertiary (AC_SDIN2) resume interrupt */ +#define ICH_TCR 0x10000000 /* ICH4: tertiary (AC_SDIN2) codec ready */ +#define ICH_BCS 0x08000000 /* ICH4: bit clock stopped */ +#define ICH_SPINT 0x04000000 /* ICH4: S/PDIF interrupt */ +#define ICH_P2INT 0x02000000 /* ICH4: PCM2-In interrupt */ +#define ICH_M2INT 0x01000000 /* ICH4: Mic2-In interrupt */ +#define ICH_SAMPLE_CAP 0x00c00000 /* ICH4: sample capability bits (RO) */ +#define ICH_MULTICHAN_CAP 0x00300000 /* ICH4: multi-channel capability bits (RO) */ +#define ICH_MD3 0x00020000 /* modem power down semaphore */ +#define ICH_AD3 0x00010000 /* audio power down semaphore */ +#define ICH_RCS 0x00008000 /* read completion status */ +#define ICH_BIT3 0x00004000 /* bit 3 slot 12 */ +#define ICH_BIT2 0x00002000 /* bit 2 slot 12 */ +#define ICH_BIT1 0x00001000 /* bit 1 slot 12 */ +#define ICH_SRI 0x00000800 /* secondary (AC_SDIN1) resume interrupt */ +#define ICH_PRI 0x00000400 /* primary (AC_SDIN0) resume interrupt */ +#define ICH_SCR 0x00000200 /* secondary (AC_SDIN1) codec ready */ +#define ICH_PCR 0x00000100 /* primary (AC_SDIN0) codec ready */ +#define ICH_MCINT 0x00000080 /* MIC capture interrupt */ +#define ICH_POINT 0x00000040 /* playback interrupt */ +#define ICH_PIINT 0x00000020 /* capture interrupt */ +#define ICH_NVSPINT 0x00000010 /* nforce spdif interrupt */ +#define ICH_MOINT 0x00000004 /* modem playback interrupt */ +#define ICH_MIINT 0x00000002 /* modem capture interrupt */ +#define ICH_GSCI 0x00000001 /* GPI status change interrupt */ +#define ICH_REG_ACC_SEMA 0x44 /* byte - codec write semaphore */ +#define ICH_CAS 0x01 /* codec access semaphore */ + +#define ICH_MAX_FRAGS 32 /* max hw frags */ + + +/* + * + */ + +enum { ICHD_MDMIN, ICHD_MDMOUT, ICHD_MDMLAST = ICHD_MDMOUT }; +enum { ALID_MDMIN, ALID_MDMOUT, ALID_MDMLAST = ALID_MDMOUT }; + +#define get_ichdev(substream) (substream->runtime->private_data) + +struct ichdev { + unsigned int ichd; /* ich device number */ + unsigned long reg_offset; /* offset to bmaddr */ + __le32 *bdbar; /* CPU address (32bit) */ + unsigned int bdbar_addr; /* PCI bus address (32bit) */ + struct snd_pcm_substream *substream; + unsigned int physbuf; /* physical address (32bit) */ + unsigned int size; + unsigned int fragsize; + unsigned int fragsize1; + unsigned int position; + int frags; + int lvi; + int lvi_frag; + int civ; + int ack; + int ack_reload; + unsigned int ack_bit; + unsigned int roff_sr; + unsigned int roff_picb; + unsigned int int_sta_mask; /* interrupt status mask */ + unsigned int ali_slot; /* ALI DMA slot */ + struct snd_ac97 *ac97; +}; + +struct intel8x0m { + unsigned int device_type; + + int irq; + + void __iomem *addr; + void __iomem *bmaddr; + + struct pci_dev *pci; + struct snd_card *card; + + int pcm_devs; + struct snd_pcm *pcm[2]; + struct ichdev ichd[2]; + + unsigned int in_ac97_init: 1; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + + spinlock_t reg_lock; + + struct snd_dma_buffer *bdbars; + u32 bdbars_count; + u32 int_sta_reg; /* interrupt status register */ + u32 int_sta_mask; /* interrupt status mask */ + unsigned int pcm_pos_shift; +}; + +static const struct pci_device_id snd_intel8x0m_ids[] = { + { + /* 82801AA */ + PCI_VDEVICE(INTEL, 0x2416), + .driver_data = DEVICE_INTEL, + }, { + /* 82901AB */ + PCI_VDEVICE(INTEL, 0x2426), + .driver_data = DEVICE_INTEL + }, { + /* 82801BA */ + PCI_VDEVICE(INTEL, 0x2446), + .driver_data = DEVICE_INTEL + }, { + /* ICH3 */ + PCI_VDEVICE(INTEL, 0x2486), + .driver_data = DEVICE_INTEL + }, { + /* ICH4 */ + PCI_VDEVICE(INTEL, 0x24c6), + .driver_data = DEVICE_INTEL, + }, { + /* ICH5 */ + PCI_VDEVICE(INTEL, 0x24d6), + .driver_data = DEVICE_INTEL, + }, { + /* ICH6 */ + PCI_VDEVICE(INTEL, 0x266d), + .driver_data = DEVICE_INTEL, + }, { + /* ICH7 */ + PCI_VDEVICE(INTEL, 0x27dd), + .driver_data = DEVICE_INTEL, + }, { + /* 440MX */ + PCI_VDEVICE(INTEL, 0x7196), + .driver_data = DEVICE_INTEL, + }, { + /* AMD768 */ + PCI_VDEVICE(AMD, 0x7446), + .driver_data = DEVICE_INTEL, + }, { + /* SI7013 */ + PCI_VDEVICE(SI, 0x7013), + .driver_data = DEVICE_SIS, + }, { + /* NFORCE */ + PCI_VDEVICE(NVIDIA, 0x01c1), + .driver_data = DEVICE_NFORCE, + }, { + /* NFORCE2 */ + PCI_VDEVICE(NVIDIA, 0x0069), + .driver_data = DEVICE_NFORCE, + }, { + /* NFORCE2s */ + PCI_VDEVICE(NVIDIA, 0x0089), + .driver_data = DEVICE_NFORCE, + }, { + /* NFORCE3 */ + PCI_VDEVICE(NVIDIA, 0x00d9), + .driver_data = DEVICE_NFORCE, + }, { + /* AMD8111 */ + PCI_VDEVICE(AMD, 0x746e), + .driver_data = DEVICE_INTEL +#if 0 + }, { + /* Ali5455 */ + PCI_VDEVICE(AL, 0x5455), + .driver_data = DEVICE_ALI, +#endif + }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_intel8x0m_ids); + +/* + * Lowlevel I/O - busmaster + */ + +static inline u8 igetbyte(struct intel8x0m *chip, u32 offset) +{ + return ioread8(chip->bmaddr + offset); +} + +static inline u16 igetword(struct intel8x0m *chip, u32 offset) +{ + return ioread16(chip->bmaddr + offset); +} + +static inline u32 igetdword(struct intel8x0m *chip, u32 offset) +{ + return ioread32(chip->bmaddr + offset); +} + +static inline void iputbyte(struct intel8x0m *chip, u32 offset, u8 val) +{ + iowrite8(val, chip->bmaddr + offset); +} + +static inline void iputword(struct intel8x0m *chip, u32 offset, u16 val) +{ + iowrite16(val, chip->bmaddr + offset); +} + +static inline void iputdword(struct intel8x0m *chip, u32 offset, u32 val) +{ + iowrite32(val, chip->bmaddr + offset); +} + +/* + * Lowlevel I/O - AC'97 registers + */ + +static inline u16 iagetword(struct intel8x0m *chip, u32 offset) +{ + return ioread16(chip->addr + offset); +} + +static inline void iaputword(struct intel8x0m *chip, u32 offset, u16 val) +{ + iowrite16(val, chip->addr + offset); +} + +/* + * Basic I/O + */ + +/* + * access to AC97 codec via normal i/o (for ICH and SIS7013) + */ + +/* return the GLOB_STA bit for the corresponding codec */ +static unsigned int get_ich_codec_bit(struct intel8x0m *chip, unsigned int codec) +{ + static const unsigned int codec_bit[3] = { + ICH_PCR, ICH_SCR, ICH_TCR + }; + if (snd_BUG_ON(codec >= 3)) + return ICH_PCR; + return codec_bit[codec]; +} + +static int snd_intel8x0m_codec_semaphore(struct intel8x0m *chip, unsigned int codec) +{ + int time; + + if (codec > 1) + return -EIO; + codec = get_ich_codec_bit(chip, codec); + + /* codec ready ? */ + if ((igetdword(chip, ICHREG(GLOB_STA)) & codec) == 0) + return -EIO; + + /* Anyone holding a semaphore for 1 msec should be shot... */ + time = 100; + do { + if (!(igetbyte(chip, ICHREG(ACC_SEMA)) & ICH_CAS)) + return 0; + udelay(10); + } while (time--); + + /* access to some forbidden (non existent) ac97 registers will not + * reset the semaphore. So even if you don't get the semaphore, still + * continue the access. We don't need the semaphore anyway. */ + dev_err(chip->card->dev, + "codec_semaphore: semaphore is not ready [0x%x][0x%x]\n", + igetbyte(chip, ICHREG(ACC_SEMA)), igetdword(chip, ICHREG(GLOB_STA))); + iagetword(chip, 0); /* clear semaphore flag */ + /* I don't care about the semaphore */ + return -EBUSY; +} + +static void snd_intel8x0m_codec_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct intel8x0m *chip = ac97->private_data; + + if (snd_intel8x0m_codec_semaphore(chip, ac97->num) < 0) { + if (! chip->in_ac97_init) + dev_err(chip->card->dev, + "codec_write %d: semaphore is not ready for register 0x%x\n", + ac97->num, reg); + } + iaputword(chip, reg + ac97->num * 0x80, val); +} + +static unsigned short snd_intel8x0m_codec_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct intel8x0m *chip = ac97->private_data; + unsigned short res; + unsigned int tmp; + + if (snd_intel8x0m_codec_semaphore(chip, ac97->num) < 0) { + if (! chip->in_ac97_init) + dev_err(chip->card->dev, + "codec_read %d: semaphore is not ready for register 0x%x\n", + ac97->num, reg); + res = 0xffff; + } else { + res = iagetword(chip, reg + ac97->num * 0x80); + tmp = igetdword(chip, ICHREG(GLOB_STA)); + if (tmp & ICH_RCS) { + /* reset RCS and preserve other R/WC bits */ + iputdword(chip, ICHREG(GLOB_STA), + tmp & ~(ICH_SRI|ICH_PRI|ICH_TRI|ICH_GSCI)); + if (! chip->in_ac97_init) + dev_err(chip->card->dev, + "codec_read %d: read timeout for register 0x%x\n", + ac97->num, reg); + res = 0xffff; + } + } + if (reg == AC97_GPIO_STATUS) + iagetword(chip, 0); /* clear semaphore */ + return res; +} + + +/* + * DMA I/O + */ +static void snd_intel8x0m_setup_periods(struct intel8x0m *chip, struct ichdev *ichdev) +{ + int idx; + __le32 *bdbar = ichdev->bdbar; + unsigned long port = ichdev->reg_offset; + + iputdword(chip, port + ICH_REG_OFF_BDBAR, ichdev->bdbar_addr); + if (ichdev->size == ichdev->fragsize) { + ichdev->ack_reload = ichdev->ack = 2; + ichdev->fragsize1 = ichdev->fragsize >> 1; + for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 4) { + bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf); + bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */ + ichdev->fragsize1 >> chip->pcm_pos_shift); + bdbar[idx + 2] = cpu_to_le32(ichdev->physbuf + (ichdev->size >> 1)); + bdbar[idx + 3] = cpu_to_le32(0x80000000 | /* interrupt on completion */ + ichdev->fragsize1 >> chip->pcm_pos_shift); + } + ichdev->frags = 2; + } else { + ichdev->ack_reload = ichdev->ack = 1; + ichdev->fragsize1 = ichdev->fragsize; + for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 2) { + bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf + (((idx >> 1) * ichdev->fragsize) % ichdev->size)); + bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */ + ichdev->fragsize >> chip->pcm_pos_shift); + /* + dev_dbg(chip->card->dev, "bdbar[%i] = 0x%x [0x%x]\n", + idx + 0, bdbar[idx + 0], bdbar[idx + 1]); + */ + } + ichdev->frags = ichdev->size / ichdev->fragsize; + } + iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi = ICH_REG_LVI_MASK); + ichdev->civ = 0; + iputbyte(chip, port + ICH_REG_OFF_CIV, 0); + ichdev->lvi_frag = ICH_REG_LVI_MASK % ichdev->frags; + ichdev->position = 0; +#if 0 + dev_dbg(chip->card->dev, + "lvi_frag = %i, frags = %i, period_size = 0x%x, period_size1 = 0x%x\n", + ichdev->lvi_frag, ichdev->frags, ichdev->fragsize, + ichdev->fragsize1); +#endif + /* clear interrupts */ + iputbyte(chip, port + ichdev->roff_sr, ICH_FIFOE | ICH_BCIS | ICH_LVBCI); +} + +/* + * Interrupt handler + */ + +static inline void snd_intel8x0m_update(struct intel8x0m *chip, struct ichdev *ichdev) +{ + unsigned long port = ichdev->reg_offset; + int civ, i, step; + int ack = 0; + + civ = igetbyte(chip, port + ICH_REG_OFF_CIV); + if (civ == ichdev->civ) { + step = 1; + ichdev->civ++; + ichdev->civ &= ICH_REG_LVI_MASK; + } else { + step = civ - ichdev->civ; + if (step < 0) + step += ICH_REG_LVI_MASK + 1; + ichdev->civ = civ; + } + + ichdev->position += step * ichdev->fragsize1; + ichdev->position %= ichdev->size; + ichdev->lvi += step; + ichdev->lvi &= ICH_REG_LVI_MASK; + iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi); + for (i = 0; i < step; i++) { + ichdev->lvi_frag++; + ichdev->lvi_frag %= ichdev->frags; + ichdev->bdbar[ichdev->lvi * 2] = cpu_to_le32(ichdev->physbuf + + ichdev->lvi_frag * + ichdev->fragsize1); +#if 0 + dev_dbg(chip->card->dev, + "new: bdbar[%i] = 0x%x [0x%x], prefetch = %i, all = 0x%x, 0x%x\n", + ichdev->lvi * 2, ichdev->bdbar[ichdev->lvi * 2], + ichdev->bdbar[ichdev->lvi * 2 + 1], inb(ICH_REG_OFF_PIV + port), + inl(port + 4), inb(port + ICH_REG_OFF_CR)); +#endif + if (--ichdev->ack == 0) { + ichdev->ack = ichdev->ack_reload; + ack = 1; + } + } + if (ack && ichdev->substream) { + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(ichdev->substream); + spin_lock(&chip->reg_lock); + } + iputbyte(chip, port + ichdev->roff_sr, ICH_FIFOE | ICH_BCIS | ICH_LVBCI); +} + +static irqreturn_t snd_intel8x0m_interrupt(int irq, void *dev_id) +{ + struct intel8x0m *chip = dev_id; + struct ichdev *ichdev; + unsigned int status; + unsigned int i; + + guard(spinlock)(&chip->reg_lock); + status = igetdword(chip, chip->int_sta_reg); + if (status == 0xffffffff) /* we are not yet resumed */ + return IRQ_NONE; + if ((status & chip->int_sta_mask) == 0) { + if (status) + iputdword(chip, chip->int_sta_reg, status); + return IRQ_NONE; + } + + for (i = 0; i < chip->bdbars_count; i++) { + ichdev = &chip->ichd[i]; + if (status & ichdev->int_sta_mask) + snd_intel8x0m_update(chip, ichdev); + } + + /* ack them */ + iputdword(chip, chip->int_sta_reg, status & chip->int_sta_mask); + + return IRQ_HANDLED; +} + +/* + * PCM part + */ + +static int snd_intel8x0m_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct intel8x0m *chip = snd_pcm_substream_chip(substream); + struct ichdev *ichdev = get_ichdev(substream); + unsigned char val = 0; + unsigned long port = ichdev->reg_offset; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + val = ICH_IOCE | ICH_STARTBM; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + val = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = ICH_IOCE; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = ICH_IOCE | ICH_STARTBM; + break; + default: + return -EINVAL; + } + iputbyte(chip, port + ICH_REG_OFF_CR, val); + if (cmd == SNDRV_PCM_TRIGGER_STOP) { + /* wait until DMA stopped */ + while (!(igetbyte(chip, port + ichdev->roff_sr) & ICH_DCH)) ; + /* reset whole DMA things */ + iputbyte(chip, port + ICH_REG_OFF_CR, ICH_RESETREGS); + } + return 0; +} + +static snd_pcm_uframes_t snd_intel8x0m_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct intel8x0m *chip = snd_pcm_substream_chip(substream); + struct ichdev *ichdev = get_ichdev(substream); + size_t ptr1, ptr; + + ptr1 = igetword(chip, ichdev->reg_offset + ichdev->roff_picb) << chip->pcm_pos_shift; + if (ptr1 != 0) + ptr = ichdev->fragsize1 - ptr1; + else + ptr = 0; + ptr += ichdev->position; + if (ptr >= ichdev->size) + return 0; + return bytes_to_frames(substream->runtime, ptr); +} + +static int snd_intel8x0m_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct intel8x0m *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ichdev *ichdev = get_ichdev(substream); + + ichdev->physbuf = runtime->dma_addr; + ichdev->size = snd_pcm_lib_buffer_bytes(substream); + ichdev->fragsize = snd_pcm_lib_period_bytes(substream); + snd_ac97_write(ichdev->ac97, AC97_LINE1_RATE, runtime->rate); + snd_ac97_write(ichdev->ac97, AC97_LINE1_LEVEL, 0); + snd_intel8x0m_setup_periods(chip, ichdev); + return 0; +} + +static const struct snd_pcm_hardware snd_intel8x0m_stream = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 64 * 1024, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + + +static int snd_intel8x0m_pcm_open(struct snd_pcm_substream *substream, struct ichdev *ichdev) +{ + static const unsigned int rates[] = { 8000, 9600, 12000, 16000 }; + static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + ichdev->substream = substream; + runtime->hw = snd_intel8x0m_stream; + err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_rates); + if ( err < 0 ) + return err; + runtime->private_data = ichdev; + return 0; +} + +static int snd_intel8x0m_playback_open(struct snd_pcm_substream *substream) +{ + struct intel8x0m *chip = snd_pcm_substream_chip(substream); + + return snd_intel8x0m_pcm_open(substream, &chip->ichd[ICHD_MDMOUT]); +} + +static int snd_intel8x0m_playback_close(struct snd_pcm_substream *substream) +{ + struct intel8x0m *chip = snd_pcm_substream_chip(substream); + + chip->ichd[ICHD_MDMOUT].substream = NULL; + return 0; +} + +static int snd_intel8x0m_capture_open(struct snd_pcm_substream *substream) +{ + struct intel8x0m *chip = snd_pcm_substream_chip(substream); + + return snd_intel8x0m_pcm_open(substream, &chip->ichd[ICHD_MDMIN]); +} + +static int snd_intel8x0m_capture_close(struct snd_pcm_substream *substream) +{ + struct intel8x0m *chip = snd_pcm_substream_chip(substream); + + chip->ichd[ICHD_MDMIN].substream = NULL; + return 0; +} + + +static const struct snd_pcm_ops snd_intel8x0m_playback_ops = { + .open = snd_intel8x0m_playback_open, + .close = snd_intel8x0m_playback_close, + .prepare = snd_intel8x0m_pcm_prepare, + .trigger = snd_intel8x0m_pcm_trigger, + .pointer = snd_intel8x0m_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_intel8x0m_capture_ops = { + .open = snd_intel8x0m_capture_open, + .close = snd_intel8x0m_capture_close, + .prepare = snd_intel8x0m_pcm_prepare, + .trigger = snd_intel8x0m_pcm_trigger, + .pointer = snd_intel8x0m_pcm_pointer, +}; + + +struct ich_pcm_table { + char *suffix; + const struct snd_pcm_ops *playback_ops; + const struct snd_pcm_ops *capture_ops; + size_t prealloc_size; + size_t prealloc_max_size; + int ac97_idx; +}; + +static int snd_intel8x0m_pcm1(struct intel8x0m *chip, int device, + const struct ich_pcm_table *rec) +{ + struct snd_pcm *pcm; + int err; + char name[32]; + + if (rec->suffix) + sprintf(name, "Intel ICH - %s", rec->suffix); + else + strscpy(name, "Intel ICH"); + err = snd_pcm_new(chip->card, name, device, + rec->playback_ops ? 1 : 0, + rec->capture_ops ? 1 : 0, &pcm); + if (err < 0) + return err; + + if (rec->playback_ops) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, rec->playback_ops); + if (rec->capture_ops) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, rec->capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + pcm->dev_class = SNDRV_PCM_CLASS_MODEM; + if (rec->suffix) + sprintf(pcm->name, "%s - %s", chip->card->shortname, rec->suffix); + else + strscpy(pcm->name, chip->card->shortname); + chip->pcm[device] = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + rec->prealloc_size, + rec->prealloc_max_size); + + return 0; +} + +static const struct ich_pcm_table intel_pcms[] = { + { + .suffix = "Modem", + .playback_ops = &snd_intel8x0m_playback_ops, + .capture_ops = &snd_intel8x0m_capture_ops, + .prealloc_size = 32 * 1024, + .prealloc_max_size = 64 * 1024, + }, +}; + +static int snd_intel8x0m_pcm(struct intel8x0m *chip) +{ + int i, tblsize, device, err; + const struct ich_pcm_table *tbl, *rec; + +#if 1 + tbl = intel_pcms; + tblsize = 1; +#else + switch (chip->device_type) { + case DEVICE_NFORCE: + tbl = nforce_pcms; + tblsize = ARRAY_SIZE(nforce_pcms); + break; + case DEVICE_ALI: + tbl = ali_pcms; + tblsize = ARRAY_SIZE(ali_pcms); + break; + default: + tbl = intel_pcms; + tblsize = 2; + break; + } +#endif + device = 0; + for (i = 0; i < tblsize; i++) { + rec = tbl + i; + if (i > 0 && rec->ac97_idx) { + /* activate PCM only when associated AC'97 codec */ + if (! chip->ichd[rec->ac97_idx].ac97) + continue; + } + err = snd_intel8x0m_pcm1(chip, device, rec); + if (err < 0) + return err; + device++; + } + + chip->pcm_devs = device; + return 0; +} + + +/* + * Mixer part + */ + +static void snd_intel8x0m_mixer_free_ac97_bus(struct snd_ac97_bus *bus) +{ + struct intel8x0m *chip = bus->private_data; + chip->ac97_bus = NULL; +} + +static void snd_intel8x0m_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct intel8x0m *chip = ac97->private_data; + chip->ac97 = NULL; +} + + +static int snd_intel8x0m_mixer(struct intel8x0m *chip, int ac97_clock) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + struct snd_ac97 *x97; + int err; + unsigned int glob_sta = 0; + static const struct snd_ac97_bus_ops ops = { + .write = snd_intel8x0m_codec_write, + .read = snd_intel8x0m_codec_read, + }; + + chip->in_ac97_init = 1; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.private_free = snd_intel8x0m_mixer_free_ac97; + ac97.scaps = AC97_SCAP_SKIP_AUDIO | AC97_SCAP_POWER_SAVE; + + glob_sta = igetdword(chip, ICHREG(GLOB_STA)); + + err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus); + if (err < 0) + goto __err; + pbus->private_free = snd_intel8x0m_mixer_free_ac97_bus; + if (ac97_clock >= 8000 && ac97_clock <= 48000) + pbus->clock = ac97_clock; + chip->ac97_bus = pbus; + + ac97.pci = chip->pci; + ac97.num = glob_sta & ICH_SCR ? 1 : 0; + err = snd_ac97_mixer(pbus, &ac97, &x97); + if (err < 0) { + dev_err(chip->card->dev, + "Unable to initialize codec #%d\n", ac97.num); + if (ac97.num == 0) + goto __err; + return err; + } + chip->ac97 = x97; + if(ac97_is_modem(x97) && !chip->ichd[ICHD_MDMIN].ac97) { + chip->ichd[ICHD_MDMIN].ac97 = x97; + chip->ichd[ICHD_MDMOUT].ac97 = x97; + } + + chip->in_ac97_init = 0; + return 0; + + __err: + /* clear the cold-reset bit for the next chance */ + if (chip->device_type != DEVICE_ALI) + iputdword(chip, ICHREG(GLOB_CNT), + igetdword(chip, ICHREG(GLOB_CNT)) & ~ICH_AC97COLD); + return err; +} + + +/* + * + */ + +static int snd_intel8x0m_ich_chip_init(struct intel8x0m *chip, int probing) +{ + unsigned long end_time; + unsigned int cnt, status, nstatus; + + /* put logic to right state */ + /* first clear status bits */ + status = ICH_RCS | ICH_MIINT | ICH_MOINT; + cnt = igetdword(chip, ICHREG(GLOB_STA)); + iputdword(chip, ICHREG(GLOB_STA), cnt & status); + + /* ACLink on, 2 channels */ + cnt = igetdword(chip, ICHREG(GLOB_CNT)); + cnt &= ~(ICH_ACLINK); + /* finish cold or do warm reset */ + cnt |= (cnt & ICH_AC97COLD) == 0 ? ICH_AC97COLD : ICH_AC97WARM; + iputdword(chip, ICHREG(GLOB_CNT), cnt); + usleep_range(500, 1000); /* give warm reset some time */ + end_time = jiffies + HZ / 4; + do { + if ((igetdword(chip, ICHREG(GLOB_CNT)) & ICH_AC97WARM) == 0) + goto __ok; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + dev_err(chip->card->dev, "AC'97 warm reset still in progress? [0x%x]\n", + igetdword(chip, ICHREG(GLOB_CNT))); + return -EIO; + + __ok: + if (probing) { + /* wait for any codec ready status. + * Once it becomes ready it should remain ready + * as long as we do not disable the ac97 link. + */ + end_time = jiffies + HZ; + do { + status = igetdword(chip, ICHREG(GLOB_STA)) & + (ICH_PCR | ICH_SCR | ICH_TCR); + if (status) + break; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + if (! status) { + /* no codec is found */ + dev_err(chip->card->dev, + "codec_ready: codec is not ready [0x%x]\n", + igetdword(chip, ICHREG(GLOB_STA))); + return -EIO; + } + + /* up to two codecs (modem cannot be tertiary with ICH4) */ + nstatus = ICH_PCR | ICH_SCR; + + /* wait for other codecs ready status. */ + end_time = jiffies + HZ / 4; + while (status != nstatus && time_after_eq(end_time, jiffies)) { + schedule_timeout_uninterruptible(1); + status |= igetdword(chip, ICHREG(GLOB_STA)) & nstatus; + } + + } else { + /* resume phase */ + status = 0; + if (chip->ac97) + status |= get_ich_codec_bit(chip, chip->ac97->num); + /* wait until all the probed codecs are ready */ + end_time = jiffies + HZ; + do { + nstatus = igetdword(chip, ICHREG(GLOB_STA)) & + (ICH_PCR | ICH_SCR | ICH_TCR); + if (status == nstatus) + break; + schedule_timeout_uninterruptible(1); + } while (time_after_eq(end_time, jiffies)); + } + + if (chip->device_type == DEVICE_SIS) { + /* unmute the output on SIS7013 */ + iputword(chip, 0x4c, igetword(chip, 0x4c) | 1); + } + + return 0; +} + +static int snd_intel8x0m_chip_init(struct intel8x0m *chip, int probing) +{ + unsigned int i; + int err; + + err = snd_intel8x0m_ich_chip_init(chip, probing); + if (err < 0) + return err; + iagetword(chip, 0); /* clear semaphore flag */ + + /* disable interrupts */ + for (i = 0; i < chip->bdbars_count; i++) + iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, 0x00); + /* reset channels */ + for (i = 0; i < chip->bdbars_count; i++) + iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, ICH_RESETREGS); + /* initialize Buffer Descriptor Lists */ + for (i = 0; i < chip->bdbars_count; i++) + iputdword(chip, ICH_REG_OFF_BDBAR + chip->ichd[i].reg_offset, chip->ichd[i].bdbar_addr); + return 0; +} + +static void snd_intel8x0m_free(struct snd_card *card) +{ + struct intel8x0m *chip = card->private_data; + unsigned int i; + + if (chip->irq < 0) + goto __hw_end; + /* disable interrupts */ + for (i = 0; i < chip->bdbars_count; i++) + iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, 0x00); + /* reset channels */ + for (i = 0; i < chip->bdbars_count; i++) + iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, ICH_RESETREGS); + __hw_end: + if (chip->irq >= 0) + free_irq(chip->irq, chip); +} + +/* + * power management + */ +static int intel8x0m_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct intel8x0m *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(chip->ac97); + if (chip->irq >= 0) { + free_irq(chip->irq, chip); + chip->irq = -1; + card->sync_irq = -1; + } + return 0; +} + +static int intel8x0m_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct snd_card *card = dev_get_drvdata(dev); + struct intel8x0m *chip = card->private_data; + + if (request_irq(pci->irq, snd_intel8x0m_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(dev, "unable to grab IRQ %d, disabling device\n", + pci->irq); + snd_card_disconnect(card); + return -EIO; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + snd_intel8x0m_chip_init(chip, 0); + snd_ac97_resume(chip->ac97); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(intel8x0m_pm, intel8x0m_suspend, intel8x0m_resume); + +static void snd_intel8x0m_proc_read(struct snd_info_entry * entry, + struct snd_info_buffer *buffer) +{ + struct intel8x0m *chip = entry->private_data; + unsigned int tmp; + + snd_iprintf(buffer, "Intel8x0m\n\n"); + if (chip->device_type == DEVICE_ALI) + return; + tmp = igetdword(chip, ICHREG(GLOB_STA)); + snd_iprintf(buffer, "Global control : 0x%08x\n", + igetdword(chip, ICHREG(GLOB_CNT))); + snd_iprintf(buffer, "Global status : 0x%08x\n", tmp); + snd_iprintf(buffer, "AC'97 codecs ready :%s%s%s%s\n", + tmp & ICH_PCR ? " primary" : "", + tmp & ICH_SCR ? " secondary" : "", + tmp & ICH_TCR ? " tertiary" : "", + (tmp & (ICH_PCR | ICH_SCR | ICH_TCR)) == 0 ? " none" : ""); +} + +static void snd_intel8x0m_proc_init(struct intel8x0m *chip) +{ + snd_card_ro_proc_new(chip->card, "intel8x0m", chip, + snd_intel8x0m_proc_read); +} + +struct ich_reg_info { + unsigned int int_sta_mask; + unsigned int offset; +}; + +static int snd_intel8x0m_init(struct snd_card *card, + struct pci_dev *pci, + unsigned long device_type) +{ + struct intel8x0m *chip = card->private_data; + int err; + unsigned int i; + unsigned int int_sta_masks; + struct ichdev *ichdev; + static const struct ich_reg_info intel_regs[2] = { + { ICH_MIINT, 0 }, + { ICH_MOINT, 0x10 }, + }; + const struct ich_reg_info *tbl; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); + chip->device_type = device_type; + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + err = pcim_request_all_regions(pci, card->shortname); + if (err < 0) + return err; + + if (device_type == DEVICE_ALI) { + /* ALI5455 has no ac97 region */ + chip->bmaddr = pcim_iomap(pci, 0, 0); + } else { + if (pci_resource_flags(pci, 2) & IORESOURCE_MEM) /* ICH4 and Nforce */ + chip->addr = pcim_iomap(pci, 2, 0); + else + chip->addr = pcim_iomap(pci, 0, 0); + if (pci_resource_flags(pci, 3) & IORESOURCE_MEM) /* ICH4 */ + chip->bmaddr = pcim_iomap(pci, 3, 0); + else + chip->bmaddr = pcim_iomap(pci, 1, 0); + } + + /* initialize offsets */ + chip->bdbars_count = 2; + tbl = intel_regs; + + for (i = 0; i < chip->bdbars_count; i++) { + ichdev = &chip->ichd[i]; + ichdev->ichd = i; + ichdev->reg_offset = tbl[i].offset; + ichdev->int_sta_mask = tbl[i].int_sta_mask; + if (device_type == DEVICE_SIS) { + /* SiS 7013 swaps the registers */ + ichdev->roff_sr = ICH_REG_OFF_PICB; + ichdev->roff_picb = ICH_REG_OFF_SR; + } else { + ichdev->roff_sr = ICH_REG_OFF_SR; + ichdev->roff_picb = ICH_REG_OFF_PICB; + } + if (device_type == DEVICE_ALI) + ichdev->ali_slot = (ichdev->reg_offset - 0x40) / 0x10; + } + /* SIS7013 handles the pcm data in bytes, others are in words */ + chip->pcm_pos_shift = (device_type == DEVICE_SIS) ? 0 : 1; + + /* allocate buffer descriptor lists */ + /* the start of each lists must be aligned to 8 bytes */ + chip->bdbars = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + chip->bdbars_count * sizeof(u32) * + ICH_MAX_FRAGS * 2); + if (!chip->bdbars) + return -ENOMEM; + + /* tables must be aligned to 8 bytes here, but the kernel pages + are much bigger, so we don't care (on i386) */ + int_sta_masks = 0; + for (i = 0; i < chip->bdbars_count; i++) { + ichdev = &chip->ichd[i]; + ichdev->bdbar = ((__le32 *)chip->bdbars->area) + (i * ICH_MAX_FRAGS * 2); + ichdev->bdbar_addr = chip->bdbars->addr + (i * sizeof(u32) * ICH_MAX_FRAGS * 2); + int_sta_masks |= ichdev->int_sta_mask; + } + chip->int_sta_reg = ICH_REG_GLOB_STA; + chip->int_sta_mask = int_sta_masks; + + pci_set_master(pci); + + err = snd_intel8x0m_chip_init(chip, 1); + if (err < 0) + return err; + + /* NOTE: we don't use devm version here since it's released / + * re-acquired in PM callbacks. + * It's released explicitly in snd_intel8x0m_free(), too. + */ + if (request_irq(pci->irq, snd_intel8x0m_interrupt, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + + card->private_free = snd_intel8x0m_free; + + return 0; +} + +static struct shortname_table { + unsigned int id; + const char *s; +} shortnames[] = { + { PCI_DEVICE_ID_INTEL_82801AA_6, "Intel 82801AA-ICH" }, + { PCI_DEVICE_ID_INTEL_82801AB_6, "Intel 82901AB-ICH0" }, + { PCI_DEVICE_ID_INTEL_82801BA_6, "Intel 82801BA-ICH2" }, + { PCI_DEVICE_ID_INTEL_440MX_6, "Intel 440MX" }, + { PCI_DEVICE_ID_INTEL_82801CA_6, "Intel 82801CA-ICH3" }, + { PCI_DEVICE_ID_INTEL_82801DB_6, "Intel 82801DB-ICH4" }, + { PCI_DEVICE_ID_INTEL_82801EB_6, "Intel ICH5" }, + { PCI_DEVICE_ID_INTEL_ICH6_17, "Intel ICH6" }, + { PCI_DEVICE_ID_INTEL_ICH7_19, "Intel ICH7" }, + { 0x7446, "AMD AMD768" }, + { PCI_DEVICE_ID_SI_7013, "SiS SI7013" }, + { PCI_DEVICE_ID_NVIDIA_MCP1_MODEM, "NVidia nForce" }, + { PCI_DEVICE_ID_NVIDIA_MCP2_MODEM, "NVidia nForce2" }, + { PCI_DEVICE_ID_NVIDIA_MCP2S_MODEM, "NVidia nForce2s" }, + { PCI_DEVICE_ID_NVIDIA_MCP3_MODEM, "NVidia nForce3" }, + { 0x746e, "AMD AMD8111" }, +#if 0 + { 0x5455, "ALi M5455" }, +#endif + { 0 }, +}; + +static int __snd_intel8x0m_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct intel8x0m *chip; + int err; + struct shortname_table *name; + + err = snd_devm_card_new(&pci->dev, index, id, THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + strscpy(card->driver, "ICH-MODEM"); + strscpy(card->shortname, "Intel ICH"); + for (name = shortnames; name->id; name++) { + if (pci->device == name->id) { + strscpy(card->shortname, name->s); + break; + } + } + strcat(card->shortname," Modem"); + + err = snd_intel8x0m_init(card, pci, pci_id->driver_data); + if (err < 0) + return err; + + err = snd_intel8x0m_mixer(chip, ac97_clock); + if (err < 0) + return err; + err = snd_intel8x0m_pcm(chip); + if (err < 0) + return err; + + snd_intel8x0m_proc_init(chip); + + sprintf(card->longname, "%s at irq %i", + card->shortname, chip->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + return 0; +} + +static int snd_intel8x0m_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_intel8x0m_probe(pci, pci_id)); +} + +static struct pci_driver intel8x0m_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_intel8x0m_ids, + .probe = snd_intel8x0m_probe, + .driver = { + .pm = &intel8x0m_pm, + }, +}; + +module_pci_driver(intel8x0m_driver); diff --git a/sound/pci/korg1212/Makefile b/sound/pci/korg1212/Makefile new file mode 100644 index 000000000..ab0186ffb --- /dev/null +++ b/sound/pci/korg1212/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-korg1212-y := korg1212.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_KORG1212) += snd-korg1212.o diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c new file mode 100644 index 000000000..f4d8402f8 --- /dev/null +++ b/sound/pci/korg1212/korg1212.c @@ -0,0 +1,2264 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Korg 1212 IO PCI card + * + * Copyright (c) 2001 Haroldo Gamal + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Debug Stuff +// ---------------------------------------------------------------------------- +#define K1212_DEBUG_LEVEL 0 +#if K1212_DEBUG_LEVEL > 0 +#define K1212_DEBUG_PRINTK(fmt, args...) pr_debug(fmt, ##args) +#else +#define K1212_DEBUG_PRINTK(fmt, ...) do { } while (0) +#endif +#if K1212_DEBUG_LEVEL > 1 +#define K1212_DEBUG_PRINTK_VERBOSE(fmt, args...) pr_debug(fmt, ##args) +#else +#define K1212_DEBUG_PRINTK_VERBOSE(fmt, ...) +#endif + +// ---------------------------------------------------------------------------- +// Record/Play Buffer Allocation Method. If K1212_LARGEALLOC is defined all +// buffers are alocated as a large piece inside KorgSharedBuffer. +// ---------------------------------------------------------------------------- +//#define K1212_LARGEALLOC 1 + +// ---------------------------------------------------------------------------- +// Valid states of the Korg 1212 I/O card. +// ---------------------------------------------------------------------------- +enum CardState { + K1212_STATE_NONEXISTENT, // there is no card here + K1212_STATE_UNINITIALIZED, // the card is awaiting DSP download + K1212_STATE_DSP_IN_PROCESS, // the card is currently downloading its DSP code + K1212_STATE_DSP_COMPLETE, // the card has finished the DSP download + K1212_STATE_READY, // the card can be opened by an application. Any application + // requests prior to this state should fail. Only an open + // request can be made at this state. + K1212_STATE_OPEN, // an application has opened the card + K1212_STATE_SETUP, // the card has been setup for play + K1212_STATE_PLAYING, // the card is playing + K1212_STATE_MONITOR, // the card is in the monitor mode + K1212_STATE_CALIBRATING, // the card is currently calibrating + K1212_STATE_ERRORSTOP, // the card has stopped itself because of an error and we + // are in the process of cleaning things up. + K1212_STATE_MAX_STATE // state values of this and beyond are invalid +}; + +// ---------------------------------------------------------------------------- +// The following enumeration defines the constants written to the card's +// host-to-card doorbell to initiate a command. +// ---------------------------------------------------------------------------- +enum korg1212_dbcnst { + K1212_DB_RequestForData = 0, // sent by the card to request a buffer fill. + K1212_DB_TriggerPlay = 1, // starts playback/record on the card. + K1212_DB_SelectPlayMode = 2, // select monitor, playback setup, or stop. + K1212_DB_ConfigureBufferMemory = 3, // tells card where the host audio buffers are. + K1212_DB_RequestAdatTimecode = 4, // asks the card for the latest ADAT timecode value. + K1212_DB_SetClockSourceRate = 5, // sets the clock source and rate for the card. + K1212_DB_ConfigureMiscMemory = 6, // tells card where other buffers are. + K1212_DB_TriggerFromAdat = 7, // tells card to trigger from Adat at a specific + // timecode value. + K1212_DB_DMAERROR = 0x80, // DMA Error - the PCI bus is congestioned. + K1212_DB_CARDSTOPPED = 0x81, // Card has stopped by user request. + K1212_DB_RebootCard = 0xA0, // instructs the card to reboot. + K1212_DB_BootFromDSPPage4 = 0xA4, // instructs the card to boot from the DSP microcode + // on page 4 (local page to card). + K1212_DB_DSPDownloadDone = 0xAE, // sent by the card to indicate the download has + // completed. + K1212_DB_StartDSPDownload = 0xAF // tells the card to download its DSP firmware. +}; + + +// ---------------------------------------------------------------------------- +// The following enumeration defines return codes +// to the Korg 1212 I/O driver. +// ---------------------------------------------------------------------------- +enum snd_korg1212rc { + K1212_CMDRET_Success = 0, // command was successfully placed + K1212_CMDRET_DIOCFailure, // the DeviceIoControl call failed + K1212_CMDRET_PMFailure, // the protected mode call failed + K1212_CMDRET_FailUnspecified, // unspecified failure + K1212_CMDRET_FailBadState, // the specified command can not be given in + // the card's current state. (or the wave device's + // state) + K1212_CMDRET_CardUninitialized, // the card is uninitialized and cannot be used + K1212_CMDRET_BadIndex, // an out of range card index was specified + K1212_CMDRET_BadHandle, // an invalid card handle was specified + K1212_CMDRET_NoFillRoutine, // a play request has been made before a fill routine set + K1212_CMDRET_FillRoutineInUse, // can't set a new fill routine while one is in use + K1212_CMDRET_NoAckFromCard, // the card never acknowledged a command + K1212_CMDRET_BadParams, // bad parameters were provided by the caller + + K1212_CMDRET_BadDevice, // the specified wave device was out of range + K1212_CMDRET_BadFormat // the specified wave format is unsupported +}; + +// ---------------------------------------------------------------------------- +// The following enumeration defines the constants used to select the play +// mode for the card in the SelectPlayMode command. +// ---------------------------------------------------------------------------- +enum PlayModeSelector { + K1212_MODE_SetupPlay = 0x00000001, // provides card with pre-play information + K1212_MODE_MonitorOn = 0x00000002, // tells card to turn on monitor mode + K1212_MODE_MonitorOff = 0x00000004, // tells card to turn off monitor mode + K1212_MODE_StopPlay = 0x00000008 // stops playback on the card +}; + +// ---------------------------------------------------------------------------- +// The following enumeration defines the constants used to select the monitor +// mode for the card in the SetMonitorMode command. +// ---------------------------------------------------------------------------- +enum MonitorModeSelector { + K1212_MONMODE_Off = 0, // tells card to turn off monitor mode + K1212_MONMODE_On // tells card to turn on monitor mode +}; + +#define MAILBOX0_OFFSET 0x40 // location of mailbox 0 relative to base address +#define MAILBOX1_OFFSET 0x44 // location of mailbox 1 relative to base address +#define MAILBOX2_OFFSET 0x48 // location of mailbox 2 relative to base address +#define MAILBOX3_OFFSET 0x4c // location of mailbox 3 relative to base address +#define OUT_DOORBELL_OFFSET 0x60 // location of PCI to local doorbell +#define IN_DOORBELL_OFFSET 0x64 // location of local to PCI doorbell +#define STATUS_REG_OFFSET 0x68 // location of interrupt control/status register +#define PCI_CONTROL_OFFSET 0x6c // location of the EEPROM, PCI, User I/O, init control + // register +#define SENS_CONTROL_OFFSET 0x6e // location of the input sensitivity setting register. + // this is the upper word of the PCI control reg. +#define DEV_VEND_ID_OFFSET 0x70 // location of the device and vendor ID register + +#define MAX_COMMAND_RETRIES 5 // maximum number of times the driver will attempt + // to send a command before giving up. +#define COMMAND_ACK_MASK 0x8000 // the MSB is set in the command acknowledgment from + // the card. +#define DOORBELL_VAL_MASK 0x00FF // the doorbell value is one byte + +#define CARD_BOOT_DELAY_IN_MS 10 +#define CARD_BOOT_TIMEOUT 10 +#define DSP_BOOT_DELAY_IN_MS 200 + +#define kNumBuffers 8 +#define k1212MaxCards 4 +#define k1212NumWaveDevices 6 +#define k16BitChannels 10 +#define k32BitChannels 2 +#define kAudioChannels (k16BitChannels + k32BitChannels) +#define kPlayBufferFrames 1024 + +#define K1212_ANALOG_CHANNELS 2 +#define K1212_SPDIF_CHANNELS 2 +#define K1212_ADAT_CHANNELS 8 +#define K1212_CHANNELS (K1212_ADAT_CHANNELS + K1212_ANALOG_CHANNELS) +#define K1212_MIN_CHANNELS 1 +#define K1212_MAX_CHANNELS K1212_CHANNELS +#define K1212_FRAME_SIZE (sizeof(struct KorgAudioFrame)) +#define K1212_MAX_SAMPLES (kPlayBufferFrames*kNumBuffers) +#define K1212_PERIODS (kNumBuffers) +#define K1212_PERIOD_BYTES (K1212_FRAME_SIZE*kPlayBufferFrames) +#define K1212_BUF_SIZE (K1212_PERIOD_BYTES*kNumBuffers) +#define K1212_ANALOG_BUF_SIZE (K1212_ANALOG_CHANNELS * 2 * kPlayBufferFrames * kNumBuffers) +#define K1212_SPDIF_BUF_SIZE (K1212_SPDIF_CHANNELS * 3 * kPlayBufferFrames * kNumBuffers) +#define K1212_ADAT_BUF_SIZE (K1212_ADAT_CHANNELS * 2 * kPlayBufferFrames * kNumBuffers) +#define K1212_MAX_BUF_SIZE (K1212_ANALOG_BUF_SIZE + K1212_ADAT_BUF_SIZE) + +#define k1212MinADCSens 0x00 +#define k1212MaxADCSens 0x7f +#define k1212MaxVolume 0x7fff +#define k1212MaxWaveVolume 0xffff +#define k1212MinVolume 0x0000 +#define k1212MaxVolInverted 0x8000 + +// ----------------------------------------------------------------- +// the following bits are used for controlling interrupts in the +// interrupt control/status reg +// ----------------------------------------------------------------- +#define PCI_INT_ENABLE_BIT 0x00000100 +#define PCI_DOORBELL_INT_ENABLE_BIT 0x00000200 +#define LOCAL_INT_ENABLE_BIT 0x00010000 +#define LOCAL_DOORBELL_INT_ENABLE_BIT 0x00020000 +#define LOCAL_DMA1_INT_ENABLE_BIT 0x00080000 + +// ----------------------------------------------------------------- +// the following bits are defined for the PCI command register +// ----------------------------------------------------------------- +#define PCI_CMD_MEM_SPACE_ENABLE_BIT 0x0002 +#define PCI_CMD_IO_SPACE_ENABLE_BIT 0x0001 +#define PCI_CMD_BUS_MASTER_ENABLE_BIT 0x0004 + +// ----------------------------------------------------------------- +// the following bits are defined for the PCI status register +// ----------------------------------------------------------------- +#define PCI_STAT_PARITY_ERROR_BIT 0x8000 +#define PCI_STAT_SYSTEM_ERROR_BIT 0x4000 +#define PCI_STAT_MASTER_ABORT_RCVD_BIT 0x2000 +#define PCI_STAT_TARGET_ABORT_RCVD_BIT 0x1000 +#define PCI_STAT_TARGET_ABORT_SENT_BIT 0x0800 + +// ------------------------------------------------------------------------ +// the following constants are used in setting the 1212 I/O card's input +// sensitivity. +// ------------------------------------------------------------------------ +#define SET_SENS_LOCALINIT_BITPOS 15 +#define SET_SENS_DATA_BITPOS 10 +#define SET_SENS_CLOCK_BITPOS 8 +#define SET_SENS_LOADSHIFT_BITPOS 0 + +#define SET_SENS_LEFTCHANID 0x00 +#define SET_SENS_RIGHTCHANID 0x01 + +#define K1212SENSUPDATE_DELAY_IN_MS 50 + +// -------------------------------------------------------------------------- +// WaitRTCTicks +// +// This function waits the specified number of real time clock ticks. +// According to the DDK, each tick is ~0.8 microseconds. +// The defines following the function declaration can be used for the +// numTicksToWait parameter. +// -------------------------------------------------------------------------- +#define ONE_RTC_TICK 1 +#define SENSCLKPULSE_WIDTH 4 +#define LOADSHIFT_DELAY 4 +#define INTERCOMMAND_DELAY 40 +#define STOPCARD_DELAY 300 // max # RTC ticks for the card to stop once we write + // the command register. (could be up to 180 us) +#define COMMAND_ACK_DELAY 13 // number of RTC ticks to wait for an acknowledgement + // from the card after sending a command. + +enum ClockSourceIndex { + K1212_CLKIDX_AdatAt44_1K = 0, // selects source as ADAT at 44.1 kHz + K1212_CLKIDX_AdatAt48K, // selects source as ADAT at 48 kHz + K1212_CLKIDX_WordAt44_1K, // selects source as S/PDIF at 44.1 kHz + K1212_CLKIDX_WordAt48K, // selects source as S/PDIF at 48 kHz + K1212_CLKIDX_LocalAt44_1K, // selects source as local clock at 44.1 kHz + K1212_CLKIDX_LocalAt48K, // selects source as local clock at 48 kHz + K1212_CLKIDX_Invalid // used to check validity of the index +}; + +enum ClockSourceType { + K1212_CLKIDX_Adat = 0, // selects source as ADAT + K1212_CLKIDX_Word, // selects source as S/PDIF + K1212_CLKIDX_Local // selects source as local clock +}; + +struct KorgAudioFrame { + u16 frameData16[k16BitChannels]; /* channels 0-9 use 16 bit samples */ + u32 frameData32[k32BitChannels]; /* channels 10-11 use 32 bits - only 20 are sent across S/PDIF */ + u32 timeCodeVal; /* holds the ADAT timecode value */ +}; + +struct KorgAudioBuffer { + struct KorgAudioFrame bufferData[kPlayBufferFrames]; /* buffer definition */ +}; + +struct KorgSharedBuffer { +#ifdef K1212_LARGEALLOC + struct KorgAudioBuffer playDataBufs[kNumBuffers]; + struct KorgAudioBuffer recordDataBufs[kNumBuffers]; +#endif + short volumeData[kAudioChannels]; + u32 cardCommand; + u16 routeData [kAudioChannels]; + u32 AdatTimeCode; // ADAT timecode value +}; + +struct SensBits { + union { + struct { + unsigned int leftChanVal:8; + unsigned int leftChanId:8; + } v; + u16 leftSensBits; + } l; + union { + struct { + unsigned int rightChanVal:8; + unsigned int rightChanId:8; + } v; + u16 rightSensBits; + } r; +}; + +struct snd_korg1212 { + struct snd_card *card; + struct pci_dev *pci; + struct snd_pcm *pcm; + int irq; + + spinlock_t lock; + struct mutex open_mutex; + + wait_queue_head_t wait; + + unsigned long iomem; + unsigned long ioport; + unsigned long iomem2; + unsigned long irqcount; + unsigned long inIRQ; + void __iomem *iobase; + + struct snd_dma_buffer *dma_dsp; + struct snd_dma_buffer *dma_play; + struct snd_dma_buffer *dma_rec; + struct snd_dma_buffer *dma_shared; + + u32 DataBufsSize; + + struct KorgAudioBuffer * playDataBufsPtr; + struct KorgAudioBuffer * recordDataBufsPtr; + + struct KorgSharedBuffer * sharedBufferPtr; + + u32 RecDataPhy; + u32 PlayDataPhy; + unsigned long sharedBufferPhy; + u32 VolumeTablePhy; + u32 RoutingTablePhy; + u32 AdatTimeCodePhy; + + u32 __iomem * statusRegPtr; // address of the interrupt status/control register + u32 __iomem * outDoorbellPtr; // address of the host->card doorbell register + u32 __iomem * inDoorbellPtr; // address of the card->host doorbell register + u32 __iomem * mailbox0Ptr; // address of mailbox 0 on the card + u32 __iomem * mailbox1Ptr; // address of mailbox 1 on the card + u32 __iomem * mailbox2Ptr; // address of mailbox 2 on the card + u32 __iomem * mailbox3Ptr; // address of mailbox 3 on the card + u32 __iomem * controlRegPtr; // address of the EEPROM, PCI, I/O, Init ctrl reg + u16 __iomem * sensRegPtr; // address of the sensitivity setting register + u32 __iomem * idRegPtr; // address of the device and vendor ID registers + + size_t periodsize; + int channels; + int currentBuffer; + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + pid_t capture_pid; + pid_t playback_pid; + + enum CardState cardState; + int running; + int idleMonitorOn; // indicates whether the card is in idle monitor mode. + u32 cmdRetryCount; // tracks how many times we have retried sending to the card. + + enum ClockSourceIndex clkSrcRate; // sample rate and clock source + + enum ClockSourceType clkSource; // clock source + int clkRate; // clock rate + + int volumePhase[kAudioChannels]; + + u16 leftADCInSens; // ADC left channel input sensitivity + u16 rightADCInSens; // ADC right channel input sensitivity + + int opencnt; // Open/Close count + int setcnt; // SetupForPlay count + int playcnt; // TriggerPlay count + int errorcnt; // Error Count + unsigned long totalerrorcnt; // Total Error Count + + int dsp_is_loaded; + int dsp_stop_processing; + +}; + +MODULE_DESCRIPTION("korg1212"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("korg/k1212.dsp"); + +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 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Korg 1212 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Korg 1212 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Korg 1212 soundcard."); +MODULE_AUTHOR("Haroldo Gamal "); + +static const struct pci_device_id snd_korg1212_ids[] = { + { + .vendor = 0x10b5, + .device = 0x906d, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, snd_korg1212_ids); + +static const char * const stateName[] = { + "Non-existent", + "Uninitialized", + "DSP download in process", + "DSP download complete", + "Ready", + "Open", + "Setup for play", + "Playing", + "Monitor mode on", + "Calibrating", + "Invalid" +}; + +static const char * const clockSourceTypeName[] = { "ADAT", "S/PDIF", "local" }; + +static const char * const clockSourceName[] = { + "ADAT at 44.1 kHz", + "ADAT at 48 kHz", + "S/PDIF at 44.1 kHz", + "S/PDIF at 48 kHz", + "local clock at 44.1 kHz", + "local clock at 48 kHz" +}; + +static const char * const channelName[] = { + "ADAT-1", + "ADAT-2", + "ADAT-3", + "ADAT-4", + "ADAT-5", + "ADAT-6", + "ADAT-7", + "ADAT-8", + "Analog-L", + "Analog-R", + "SPDIF-L", + "SPDIF-R", +}; + +static const u16 ClockSourceSelector[] = { + 0x8000, // selects source as ADAT at 44.1 kHz + 0x0000, // selects source as ADAT at 48 kHz + 0x8001, // selects source as S/PDIF at 44.1 kHz + 0x0001, // selects source as S/PDIF at 48 kHz + 0x8002, // selects source as local clock at 44.1 kHz + 0x0002 // selects source as local clock at 48 kHz +}; + +union swap_u32 { unsigned char c[4]; u32 i; }; + +#ifdef SNDRV_BIG_ENDIAN +static u32 LowerWordSwap(u32 swappee) +#else +static u32 UpperWordSwap(u32 swappee) +#endif +{ + union swap_u32 retVal, swapper; + + swapper.i = swappee; + retVal.c[2] = swapper.c[3]; + retVal.c[3] = swapper.c[2]; + retVal.c[1] = swapper.c[1]; + retVal.c[0] = swapper.c[0]; + + return retVal.i; +} + +#ifdef SNDRV_BIG_ENDIAN +static u32 UpperWordSwap(u32 swappee) +#else +static u32 LowerWordSwap(u32 swappee) +#endif +{ + union swap_u32 retVal, swapper; + + swapper.i = swappee; + retVal.c[2] = swapper.c[2]; + retVal.c[3] = swapper.c[3]; + retVal.c[1] = swapper.c[0]; + retVal.c[0] = swapper.c[1]; + + return retVal.i; +} + +#define SetBitInWord(theWord,bitPosition) (*theWord) |= (0x0001 << bitPosition) +#define SetBitInDWord(theWord,bitPosition) (*theWord) |= (0x00000001 << bitPosition) +#define ClearBitInWord(theWord,bitPosition) (*theWord) &= ~(0x0001 << bitPosition) +#define ClearBitInDWord(theWord,bitPosition) (*theWord) &= ~(0x00000001 << bitPosition) + +static int snd_korg1212_Send1212Command(struct snd_korg1212 *korg1212, + enum korg1212_dbcnst doorbellVal, + u32 mailBox0Val, u32 mailBox1Val, + u32 mailBox2Val, u32 mailBox3Val) +{ + u32 retryCount; + u16 mailBox3Lo; + int rc = K1212_CMDRET_Success; + + if (!korg1212->outDoorbellPtr) { + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: CardUninitialized\n"); + return K1212_CMDRET_CardUninitialized; + } + + K1212_DEBUG_PRINTK("K1212_DEBUG: Card <- 0x%08x 0x%08x [%s]\n", + doorbellVal, mailBox0Val, stateName[korg1212->cardState]); + for (retryCount = 0; retryCount < MAX_COMMAND_RETRIES; retryCount++) { + writel(mailBox3Val, korg1212->mailbox3Ptr); + writel(mailBox2Val, korg1212->mailbox2Ptr); + writel(mailBox1Val, korg1212->mailbox1Ptr); + writel(mailBox0Val, korg1212->mailbox0Ptr); + writel(doorbellVal, korg1212->outDoorbellPtr); // interrupt the card + + // -------------------------------------------------------------- + // the reboot command will not give an acknowledgement. + // -------------------------------------------------------------- + if ( doorbellVal == K1212_DB_RebootCard || + doorbellVal == K1212_DB_BootFromDSPPage4 || + doorbellVal == K1212_DB_StartDSPDownload ) { + rc = K1212_CMDRET_Success; + break; + } + + // -------------------------------------------------------------- + // See if the card acknowledged the command. Wait a bit, then + // read in the low word of mailbox3. If the MSB is set and the + // low byte is equal to the doorbell value, then it ack'd. + // -------------------------------------------------------------- + udelay(COMMAND_ACK_DELAY); + mailBox3Lo = readl(korg1212->mailbox3Ptr); + if (mailBox3Lo & COMMAND_ACK_MASK) { + if ((mailBox3Lo & DOORBELL_VAL_MASK) == (doorbellVal & DOORBELL_VAL_MASK)) { + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: Card <- Success\n"); + rc = K1212_CMDRET_Success; + break; + } + } + } + korg1212->cmdRetryCount += retryCount; + + if (retryCount >= MAX_COMMAND_RETRIES) { + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: Card <- NoAckFromCard\n"); + rc = K1212_CMDRET_NoAckFromCard; + } + + return rc; +} + +/* spinlock already held */ +static void snd_korg1212_SendStop(struct snd_korg1212 *korg1212) +{ + korg1212->dsp_stop_processing = 1; + korg1212->sharedBufferPtr->cardCommand = 0xffffffff; +} + +static void snd_korg1212_SendStopAndWait(struct snd_korg1212 *korg1212) +{ + scoped_guard(spinlock_irqsave, &korg1212->lock) { + snd_korg1212_SendStop(korg1212); + } + wait_event_timeout(korg1212->wait, !korg1212->dsp_stop_processing, HZ); +} + +static int snd_korg1212_TurnOnIdleMonitor(struct snd_korg1212 *korg1212) +{ + udelay(INTERCOMMAND_DELAY); + guard(spinlock_irqsave)(&korg1212->lock); + korg1212->idleMonitorOn = 1; + return snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode, + K1212_MODE_MonitorOn, 0, 0, 0); +} + +static void snd_korg1212_TurnOffIdleMonitor(struct snd_korg1212 *korg1212) +{ + if (korg1212->idleMonitorOn) { + snd_korg1212_SendStopAndWait(korg1212); + korg1212->idleMonitorOn = 0; + } +} + +static inline void snd_korg1212_setCardState(struct snd_korg1212 * korg1212, enum CardState csState) +{ + korg1212->cardState = csState; +} + +static int snd_korg1212_OpenCard(struct snd_korg1212 * korg1212) +{ + K1212_DEBUG_PRINTK("K1212_DEBUG: OpenCard [%s] %d\n", + stateName[korg1212->cardState], korg1212->opencnt); + guard(mutex)(&korg1212->open_mutex); + if (korg1212->opencnt++ == 0) { + snd_korg1212_TurnOffIdleMonitor(korg1212); + snd_korg1212_setCardState(korg1212, K1212_STATE_OPEN); + } + + return 1; +} + +static int snd_korg1212_CloseCard(struct snd_korg1212 * korg1212) +{ + K1212_DEBUG_PRINTK("K1212_DEBUG: CloseCard [%s] %d\n", + stateName[korg1212->cardState], korg1212->opencnt); + + guard(mutex)(&korg1212->open_mutex); + if (--(korg1212->opencnt)) + return 0; + + if (korg1212->cardState == K1212_STATE_SETUP) { + int rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode, + K1212_MODE_StopPlay, 0, 0, 0); + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: CloseCard - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + if (rc != K1212_CMDRET_Success) + return 0; + } else if (korg1212->cardState > K1212_STATE_SETUP) { + snd_korg1212_SendStopAndWait(korg1212); + } + + if (korg1212->cardState > K1212_STATE_READY) { + snd_korg1212_TurnOnIdleMonitor(korg1212); + snd_korg1212_setCardState(korg1212, K1212_STATE_READY); + } + + return 0; +} + +/* spinlock already held */ +static int snd_korg1212_SetupForPlay(struct snd_korg1212 * korg1212) +{ + int rc; + + K1212_DEBUG_PRINTK("K1212_DEBUG: SetupForPlay [%s] %d\n", + stateName[korg1212->cardState], korg1212->setcnt); + + if (korg1212->setcnt++) + return 0; + + snd_korg1212_setCardState(korg1212, K1212_STATE_SETUP); + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode, + K1212_MODE_SetupPlay, 0, 0, 0); + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: SetupForPlay - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + if (rc != K1212_CMDRET_Success) { + return 1; + } + return 0; +} + +/* spinlock already held */ +static int snd_korg1212_TriggerPlay(struct snd_korg1212 * korg1212) +{ + int rc; + + K1212_DEBUG_PRINTK("K1212_DEBUG: TriggerPlay [%s] %d\n", + stateName[korg1212->cardState], korg1212->playcnt); + + if (korg1212->playcnt++) + return 0; + + snd_korg1212_setCardState(korg1212, K1212_STATE_PLAYING); + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_TriggerPlay, 0, 0, 0, 0); + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: TriggerPlay - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + if (rc != K1212_CMDRET_Success) { + return 1; + } + return 0; +} + +/* spinlock already held */ +static int snd_korg1212_StopPlay(struct snd_korg1212 * korg1212) +{ + K1212_DEBUG_PRINTK("K1212_DEBUG: StopPlay [%s] %d\n", + stateName[korg1212->cardState], korg1212->playcnt); + + if (--(korg1212->playcnt)) + return 0; + + korg1212->setcnt = 0; + + if (korg1212->cardState != K1212_STATE_ERRORSTOP) + snd_korg1212_SendStop(korg1212); + + snd_korg1212_setCardState(korg1212, K1212_STATE_OPEN); + return 0; +} + +static void snd_korg1212_EnableCardInterrupts(struct snd_korg1212 * korg1212) +{ + writel(PCI_INT_ENABLE_BIT | + PCI_DOORBELL_INT_ENABLE_BIT | + LOCAL_INT_ENABLE_BIT | + LOCAL_DOORBELL_INT_ENABLE_BIT | + LOCAL_DMA1_INT_ENABLE_BIT, + korg1212->statusRegPtr); +} + +#if 0 /* not used */ + +static int snd_korg1212_SetMonitorMode(struct snd_korg1212 *korg1212, + enum MonitorModeSelector mode) +{ + K1212_DEBUG_PRINTK("K1212_DEBUG: SetMonitorMode [%s]\n", + stateName[korg1212->cardState]); + + switch (mode) { + case K1212_MONMODE_Off: + if (korg1212->cardState != K1212_STATE_MONITOR) + return 0; + else { + snd_korg1212_SendStopAndWait(korg1212); + snd_korg1212_setCardState(korg1212, K1212_STATE_OPEN); + } + break; + + case K1212_MONMODE_On: + if (korg1212->cardState != K1212_STATE_OPEN) + return 0; + else { + int rc; + snd_korg1212_setCardState(korg1212, K1212_STATE_MONITOR); + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode, + K1212_MODE_MonitorOn, 0, 0, 0); + if (rc != K1212_CMDRET_Success) + return 0; + } + break; + + default: + return 0; + } + + return 1; +} + +#endif /* not used */ + +static inline int snd_korg1212_use_is_exclusive(struct snd_korg1212 *korg1212) +{ + if (korg1212->playback_pid != korg1212->capture_pid && + korg1212->playback_pid >= 0 && korg1212->capture_pid >= 0) + return 0; + + return 1; +} + +static int snd_korg1212_SetRate(struct snd_korg1212 *korg1212, int rate) +{ + static const enum ClockSourceIndex s44[] = { + K1212_CLKIDX_AdatAt44_1K, + K1212_CLKIDX_WordAt44_1K, + K1212_CLKIDX_LocalAt44_1K + }; + static const enum ClockSourceIndex s48[] = { + K1212_CLKIDX_AdatAt48K, + K1212_CLKIDX_WordAt48K, + K1212_CLKIDX_LocalAt48K + }; + int parm, rc; + + if (!snd_korg1212_use_is_exclusive (korg1212)) + return -EBUSY; + + switch (rate) { + case 44100: + parm = s44[korg1212->clkSource]; + break; + + case 48000: + parm = s48[korg1212->clkSource]; + break; + + default: + return -EINVAL; + } + + korg1212->clkSrcRate = parm; + korg1212->clkRate = rate; + + udelay(INTERCOMMAND_DELAY); + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SetClockSourceRate, + ClockSourceSelector[korg1212->clkSrcRate], + 0, 0, 0); + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: Set Clock Source Selector - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + + return 0; +} + +static int snd_korg1212_SetClockSource(struct snd_korg1212 *korg1212, int source) +{ + + if (source < 0 || source > 2) + return -EINVAL; + + korg1212->clkSource = source; + + snd_korg1212_SetRate(korg1212, korg1212->clkRate); + + return 0; +} + +static void snd_korg1212_DisableCardInterrupts(struct snd_korg1212 *korg1212) +{ + writel(0, korg1212->statusRegPtr); +} + +static int snd_korg1212_WriteADCSensitivity(struct snd_korg1212 *korg1212) +{ + struct SensBits sensVals; + int bitPosition; + int channel; + int clkIs48K; + int monModeSet; + u16 controlValue; // this keeps the current value to be written to + // the card's eeprom control register. + u16 count; + + K1212_DEBUG_PRINTK("K1212_DEBUG: WriteADCSensivity [%s]\n", + stateName[korg1212->cardState]); + + // ---------------------------------------------------------------------------- + // initialize things. The local init bit is always set when writing to the + // card's control register. + // ---------------------------------------------------------------------------- + controlValue = 0; + SetBitInWord(&controlValue, SET_SENS_LOCALINIT_BITPOS); // init the control value + + // ---------------------------------------------------------------------------- + // make sure the card is not in monitor mode when we do this update. + // ---------------------------------------------------------------------------- + if (korg1212->cardState == K1212_STATE_MONITOR || korg1212->idleMonitorOn) { + monModeSet = 1; + snd_korg1212_SendStopAndWait(korg1212); + } else + monModeSet = 0; + + guard(spinlock_irqsave)(&korg1212->lock); + + // ---------------------------------------------------------------------------- + // we are about to send new values to the card, so clear the new values queued + // flag. Also, clear out mailbox 3, so we don't lockup. + // ---------------------------------------------------------------------------- + writel(0, korg1212->mailbox3Ptr); + udelay(LOADSHIFT_DELAY); + + // ---------------------------------------------------------------------------- + // determine whether we are running a 48K or 44.1K clock. This info is used + // later when setting the SPDIF FF after the volume has been shifted in. + // ---------------------------------------------------------------------------- + switch (korg1212->clkSrcRate) { + case K1212_CLKIDX_AdatAt44_1K: + case K1212_CLKIDX_WordAt44_1K: + case K1212_CLKIDX_LocalAt44_1K: + clkIs48K = 0; + break; + + case K1212_CLKIDX_WordAt48K: + case K1212_CLKIDX_AdatAt48K: + case K1212_CLKIDX_LocalAt48K: + default: + clkIs48K = 1; + break; + } + + // ---------------------------------------------------------------------------- + // start the update. Setup the bit structure and then shift the bits. + // ---------------------------------------------------------------------------- + sensVals.l.v.leftChanId = SET_SENS_LEFTCHANID; + sensVals.r.v.rightChanId = SET_SENS_RIGHTCHANID; + sensVals.l.v.leftChanVal = korg1212->leftADCInSens; + sensVals.r.v.rightChanVal = korg1212->rightADCInSens; + + // ---------------------------------------------------------------------------- + // now start shifting the bits in. Start with the left channel then the right. + // ---------------------------------------------------------------------------- + for (channel = 0; channel < 2; channel++) { + + // ---------------------------------------------------------------------------- + // Bring the load/shift line low, then wait - the spec says >150ns from load/ + // shift low to the first rising edge of the clock. + // ---------------------------------------------------------------------------- + ClearBitInWord(&controlValue, SET_SENS_LOADSHIFT_BITPOS); + ClearBitInWord(&controlValue, SET_SENS_DATA_BITPOS); + writew(controlValue, korg1212->sensRegPtr); // load/shift goes low + udelay(LOADSHIFT_DELAY); + + for (bitPosition = 15; bitPosition >= 0; bitPosition--) { // for all the bits + if (channel == 0) { + if (sensVals.l.leftSensBits & (0x0001 << bitPosition)) + SetBitInWord(&controlValue, SET_SENS_DATA_BITPOS); // data bit set high + else + ClearBitInWord(&controlValue, SET_SENS_DATA_BITPOS); // data bit set low + } else { + if (sensVals.r.rightSensBits & (0x0001 << bitPosition)) + SetBitInWord(&controlValue, SET_SENS_DATA_BITPOS); // data bit set high + else + ClearBitInWord(&controlValue, SET_SENS_DATA_BITPOS); // data bit set low + } + + ClearBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS); + writew(controlValue, korg1212->sensRegPtr); // clock goes low + udelay(SENSCLKPULSE_WIDTH); + SetBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS); + writew(controlValue, korg1212->sensRegPtr); // clock goes high + udelay(SENSCLKPULSE_WIDTH); + } + + // ---------------------------------------------------------------------------- + // finish up SPDIF for left. Bring the load/shift line high, then write a one + // bit if the clock rate is 48K otherwise write 0. + // ---------------------------------------------------------------------------- + ClearBitInWord(&controlValue, SET_SENS_DATA_BITPOS); + ClearBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS); + SetBitInWord(&controlValue, SET_SENS_LOADSHIFT_BITPOS); + writew(controlValue, korg1212->sensRegPtr); // load shift goes high - clk low + udelay(SENSCLKPULSE_WIDTH); + + if (clkIs48K) + SetBitInWord(&controlValue, SET_SENS_DATA_BITPOS); + + writew(controlValue, korg1212->sensRegPtr); // set/clear data bit + udelay(ONE_RTC_TICK); + SetBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS); + writew(controlValue, korg1212->sensRegPtr); // clock goes high + udelay(SENSCLKPULSE_WIDTH); + ClearBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS); + writew(controlValue, korg1212->sensRegPtr); // clock goes low + udelay(SENSCLKPULSE_WIDTH); + } + + // ---------------------------------------------------------------------------- + // The update is complete. Set a timeout. This is the inter-update delay. + // Also, if the card was in monitor mode, restore it. + // ---------------------------------------------------------------------------- + for (count = 0; count < 10; count++) + udelay(SENSCLKPULSE_WIDTH); + + if (monModeSet) { + int rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode, + K1212_MODE_MonitorOn, 0, 0, 0); + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: WriteADCSensivity - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + } + + return 1; +} + +static void snd_korg1212_OnDSPDownloadComplete(struct snd_korg1212 *korg1212) +{ + int channel, rc; + + K1212_DEBUG_PRINTK("K1212_DEBUG: DSP download is complete. [%s]\n", + stateName[korg1212->cardState]); + + // ---------------------------------------------------- + // tell the card to boot + // ---------------------------------------------------- + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_BootFromDSPPage4, 0, 0, 0, 0); + + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: Boot from Page 4 - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + msleep(DSP_BOOT_DELAY_IN_MS); + + // -------------------------------------------------------------------------------- + // Let the card know where all the buffers are. + // -------------------------------------------------------------------------------- + rc = snd_korg1212_Send1212Command(korg1212, + K1212_DB_ConfigureBufferMemory, + LowerWordSwap(korg1212->PlayDataPhy), + LowerWordSwap(korg1212->RecDataPhy), + ((kNumBuffers * kPlayBufferFrames) / 2), // size given to the card + // is based on 2 buffers + 0 + ); + + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: Configure Buffer Memory - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + + udelay(INTERCOMMAND_DELAY); + + rc = snd_korg1212_Send1212Command(korg1212, + K1212_DB_ConfigureMiscMemory, + LowerWordSwap(korg1212->VolumeTablePhy), + LowerWordSwap(korg1212->RoutingTablePhy), + LowerWordSwap(korg1212->AdatTimeCodePhy), + 0 + ); + + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: Configure Misc Memory - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + + // -------------------------------------------------------------------------------- + // Initialize the routing and volume tables, then update the card's state. + // -------------------------------------------------------------------------------- + udelay(INTERCOMMAND_DELAY); + + for (channel = 0; channel < kAudioChannels; channel++) { + korg1212->sharedBufferPtr->volumeData[channel] = k1212MaxVolume; + //korg1212->sharedBufferPtr->routeData[channel] = channel; + korg1212->sharedBufferPtr->routeData[channel] = 8 + (channel & 1); + } + + snd_korg1212_WriteADCSensitivity(korg1212); + + udelay(INTERCOMMAND_DELAY); + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SetClockSourceRate, + ClockSourceSelector[korg1212->clkSrcRate], + 0, 0, 0); + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: Set Clock Source Selector - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + + rc = snd_korg1212_TurnOnIdleMonitor(korg1212); + snd_korg1212_setCardState(korg1212, K1212_STATE_READY); + + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: Set Monitor On - RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + + snd_korg1212_setCardState(korg1212, K1212_STATE_DSP_COMPLETE); +} + +static irqreturn_t snd_korg1212_interrupt(int irq, void *dev_id) +{ + u32 doorbellValue; + struct snd_korg1212 *korg1212 = dev_id; + + doorbellValue = readl(korg1212->inDoorbellPtr); + + if (!doorbellValue) + return IRQ_NONE; + + guard(spinlock)(&korg1212->lock); + + writel(doorbellValue, korg1212->inDoorbellPtr); + + korg1212->irqcount++; + + korg1212->inIRQ++; + + switch (doorbellValue) { + case K1212_DB_DSPDownloadDone: + K1212_DEBUG_PRINTK("K1212_DEBUG: IRQ DNLD count - %ld, %x, [%s].\n", + korg1212->irqcount, doorbellValue, + stateName[korg1212->cardState]); + if (korg1212->cardState == K1212_STATE_DSP_IN_PROCESS) { + korg1212->dsp_is_loaded = 1; + wake_up(&korg1212->wait); + } + break; + + // ------------------------------------------------------------------------ + // an error occurred - stop the card + // ------------------------------------------------------------------------ + case K1212_DB_DMAERROR: + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: IRQ DMAE count - %ld, %x, [%s].\n", + korg1212->irqcount, doorbellValue, + stateName[korg1212->cardState]); + dev_err(korg1212->card->dev, "korg1212: DMA Error\n"); + korg1212->errorcnt++; + korg1212->totalerrorcnt++; + korg1212->sharedBufferPtr->cardCommand = 0; + korg1212->dsp_stop_processing = 0; + snd_korg1212_setCardState(korg1212, K1212_STATE_ERRORSTOP); + wake_up(&korg1212->wait); + break; + + // ------------------------------------------------------------------------ + // the card has stopped by our request. Clear the command word and signal + // the semaphore in case someone is waiting for this. + // ------------------------------------------------------------------------ + case K1212_DB_CARDSTOPPED: + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: IRQ CSTP count - %ld, %x, [%s].\n", + korg1212->irqcount, doorbellValue, + stateName[korg1212->cardState]); + korg1212->sharedBufferPtr->cardCommand = 0; + korg1212->dsp_stop_processing = 0; + wake_up(&korg1212->wait); + break; + + default: + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: IRQ DFLT count - %ld, %x, cpos=%d [%s].\n", + korg1212->irqcount, doorbellValue, + korg1212->currentBuffer, stateName[korg1212->cardState]); + if ((korg1212->cardState > K1212_STATE_SETUP) || korg1212->idleMonitorOn) { + korg1212->currentBuffer++; + + if (korg1212->currentBuffer >= kNumBuffers) + korg1212->currentBuffer = 0; + + if (!korg1212->running) + break; + + if (korg1212->capture_substream) { + spin_unlock(&korg1212->lock); + snd_pcm_period_elapsed(korg1212->capture_substream); + spin_lock(&korg1212->lock); + } + + if (korg1212->playback_substream) { + spin_unlock(&korg1212->lock); + snd_pcm_period_elapsed(korg1212->playback_substream); + spin_lock(&korg1212->lock); + } + } + break; + } + + korg1212->inIRQ--; + + return IRQ_HANDLED; +} + +static int snd_korg1212_downloadDSPCode(struct snd_korg1212 *korg1212) +{ + int rc; + + K1212_DEBUG_PRINTK("K1212_DEBUG: DSP download is starting... [%s]\n", + stateName[korg1212->cardState]); + + // --------------------------------------------------------------- + // verify the state of the card before proceeding. + // --------------------------------------------------------------- + if (korg1212->cardState >= K1212_STATE_DSP_IN_PROCESS) + return 1; + + snd_korg1212_setCardState(korg1212, K1212_STATE_DSP_IN_PROCESS); + + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_StartDSPDownload, + UpperWordSwap(korg1212->dma_dsp->addr), + 0, 0, 0); + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: Start DSP Download RC = %d [%s]\n", + rc, stateName[korg1212->cardState]); + + korg1212->dsp_is_loaded = 0; + wait_event_timeout(korg1212->wait, korg1212->dsp_is_loaded, HZ * CARD_BOOT_TIMEOUT); + if (! korg1212->dsp_is_loaded ) + return -EBUSY; /* timeout */ + + snd_korg1212_OnDSPDownloadComplete(korg1212); + + return 0; +} + +static const struct snd_pcm_hardware snd_korg1212_playback_info = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BATCH), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = 44100, + .rate_max = 48000, + .channels_min = K1212_MIN_CHANNELS, + .channels_max = K1212_MAX_CHANNELS, + .buffer_bytes_max = K1212_MAX_BUF_SIZE, + .period_bytes_min = K1212_MIN_CHANNELS * 2 * kPlayBufferFrames, + .period_bytes_max = K1212_MAX_CHANNELS * 2 * kPlayBufferFrames, + .periods_min = K1212_PERIODS, + .periods_max = K1212_PERIODS, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_korg1212_capture_info = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BATCH), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = 44100, + .rate_max = 48000, + .channels_min = K1212_MIN_CHANNELS, + .channels_max = K1212_MAX_CHANNELS, + .buffer_bytes_max = K1212_MAX_BUF_SIZE, + .period_bytes_min = K1212_MIN_CHANNELS * 2 * kPlayBufferFrames, + .period_bytes_max = K1212_MAX_CHANNELS * 2 * kPlayBufferFrames, + .periods_min = K1212_PERIODS, + .periods_max = K1212_PERIODS, + .fifo_size = 0, +}; + +static int snd_korg1212_silence(struct snd_korg1212 *korg1212, int pos, int count, int offset, int size) +{ + struct KorgAudioFrame * dst = korg1212->playDataBufsPtr[0].bufferData + pos; + int i; + + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_silence pos=%d offset=%d size=%d count=%d\n", + pos, offset, size, count); + if (snd_BUG_ON(pos + count > K1212_MAX_SAMPLES)) + return -EINVAL; + + for (i=0; i < count; i++) { +#if K1212_DEBUG_LEVEL > 0 + if ( (void *) dst < (void *) korg1212->playDataBufsPtr || + (void *) dst > (void *) korg1212->playDataBufsPtr[8].bufferData ) { + pr_debug("K1212_DEBUG: %s KERNEL EFAULT dst=%p iter=%d\n", + __func__, dst, i); + return -EFAULT; + } +#endif + memset((void*) dst + offset, 0, size); + dst++; + } + + return 0; +} + +static int snd_korg1212_copy_to(struct snd_pcm_substream *substream, + struct iov_iter *dst, int pos, int count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + struct KorgAudioFrame *src; + int i, size; + + pos = bytes_to_frames(runtime, pos); + count = bytes_to_frames(runtime, count); + size = korg1212->channels * 2; + src = korg1212->recordDataBufsPtr[0].bufferData + pos; + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_copy_to pos=%d size=%d count=%d\n", + pos, size, count); + if (snd_BUG_ON(pos + count > K1212_MAX_SAMPLES)) + return -EINVAL; + + for (i=0; i < count; i++) { +#if K1212_DEBUG_LEVEL > 0 + if ( (void *) src < (void *) korg1212->recordDataBufsPtr || + (void *) src > (void *) korg1212->recordDataBufsPtr[8].bufferData ) { + pr_debug("K1212_DEBUG: %s KERNEL EFAULT, src=%p dst=%p iter=%d\n", + __func__, src, dst->kvec.iov_base, i); + return -EFAULT; + } +#endif + if (copy_to_iter(src, size, dst) != size) + return -EFAULT; + src++; + } + + return 0; +} + +static int snd_korg1212_copy_from(struct snd_pcm_substream *substream, + struct iov_iter *src, int pos, int count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + struct KorgAudioFrame *dst; + int i, size; + + pos = bytes_to_frames(runtime, pos); + count = bytes_to_frames(runtime, count); + size = korg1212->channels * 2; + dst = korg1212->playDataBufsPtr[0].bufferData + pos; + + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: %s pos=%d size=%d count=%d\n", + __func__, pos, size, count); + + if (snd_BUG_ON(pos + count > K1212_MAX_SAMPLES)) + return -EINVAL; + + for (i=0; i < count; i++) { +#if K1212_DEBUG_LEVEL > 0 + if ( (void *) dst < (void *) korg1212->playDataBufsPtr || + (void *) dst > (void *) korg1212->playDataBufsPtr[8].bufferData ) { + pr_debug("K1212_DEBUG: %s KERNEL EFAULT, src=%p dst=%p iter=%d\n", + __func__, src->kvec.iov_base, dst, i); + return -EFAULT; + } +#endif + if (copy_from_iter(dst, size, src) != size) + return -EFAULT; + dst++; + } + + return 0; +} + +static void snd_korg1212_free_pcm(struct snd_pcm *pcm) +{ + struct snd_korg1212 *korg1212 = pcm->private_data; + + K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_free_pcm [%s]\n", + stateName[korg1212->cardState]); + + korg1212->pcm = NULL; +} + +static int snd_korg1212_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_playback_open [%s]\n", + stateName[korg1212->cardState]); + + snd_korg1212_OpenCard(korg1212); + + runtime->hw = snd_korg1212_playback_info; + snd_pcm_set_runtime_buffer(substream, korg1212->dma_play); + + scoped_guard(spinlock_irqsave, &korg1212->lock) { + korg1212->playback_substream = substream; + korg1212->playback_pid = current->pid; + korg1212->periodsize = K1212_PERIODS; + korg1212->channels = K1212_CHANNELS; + korg1212->errorcnt = 0; + } + + snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + kPlayBufferFrames); + + return 0; +} + + +static int snd_korg1212_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_capture_open [%s]\n", + stateName[korg1212->cardState]); + + snd_korg1212_OpenCard(korg1212); + + runtime->hw = snd_korg1212_capture_info; + snd_pcm_set_runtime_buffer(substream, korg1212->dma_rec); + + scoped_guard(spinlock_irqsave, &korg1212->lock) { + korg1212->capture_substream = substream; + korg1212->capture_pid = current->pid; + korg1212->periodsize = K1212_PERIODS; + korg1212->channels = K1212_CHANNELS; + } + + snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + kPlayBufferFrames); + return 0; +} + +static int snd_korg1212_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + + K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_playback_close [%s]\n", + stateName[korg1212->cardState]); + + snd_korg1212_silence(korg1212, 0, K1212_MAX_SAMPLES, 0, korg1212->channels * 2); + + scoped_guard(spinlock_irqsave, &korg1212->lock) { + korg1212->playback_pid = -1; + korg1212->playback_substream = NULL; + korg1212->periodsize = 0; + } + + snd_korg1212_CloseCard(korg1212); + return 0; +} + +static int snd_korg1212_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + + K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_capture_close [%s]\n", + stateName[korg1212->cardState]); + + scoped_guard(spinlock_irqsave, &korg1212->lock) { + korg1212->capture_pid = -1; + korg1212->capture_substream = NULL; + korg1212->periodsize = 0; + } + + snd_korg1212_CloseCard(korg1212); + return 0; +} + +static int snd_korg1212_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_ioctl: cmd=%d\n", cmd); + + if (cmd == SNDRV_PCM_IOCTL1_CHANNEL_INFO ) { + struct snd_pcm_channel_info *info = arg; + info->offset = 0; + info->first = info->channel * 16; + info->step = 256; + K1212_DEBUG_PRINTK("K1212_DEBUG: channel_info %d:, offset=%ld, first=%d, step=%d\n", info->channel, info->offset, info->first, info->step); + return 0; + } + + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int snd_korg1212_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + int err; + pid_t this_pid; + pid_t other_pid; + + K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_hw_params [%s]\n", + stateName[korg1212->cardState]); + + guard(spinlock_irqsave)(&korg1212->lock); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) { + this_pid = korg1212->playback_pid; + other_pid = korg1212->capture_pid; + } else { + this_pid = korg1212->capture_pid; + other_pid = korg1212->playback_pid; + } + + if ((other_pid > 0) && (this_pid != other_pid)) { + + /* The other stream is open, and not by the same + task as this one. Make sure that the parameters + that matter are the same. + */ + + if ((int)params_rate(params) != korg1212->clkRate) { + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); + return -EBUSY; + } + + return 0; + } + + err = snd_korg1212_SetRate(korg1212, params_rate(params)); + if (err < 0) + return err; + + korg1212->channels = params_channels(params); + korg1212->periodsize = K1212_PERIOD_BYTES; + + return 0; +} + +static int snd_korg1212_sync_stop(struct snd_pcm_substream *substream) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + + wait_event_timeout(korg1212->wait, !korg1212->dsp_stop_processing, HZ); + return 0; +} + +static int snd_korg1212_prepare(struct snd_pcm_substream *substream) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + int rc; + + K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_prepare [%s]\n", + stateName[korg1212->cardState]); + + guard(spinlock_irq)(&korg1212->lock); + korg1212->dsp_stop_processing = 0; + + rc = snd_korg1212_SetupForPlay(korg1212); + + korg1212->currentBuffer = 0; + + return rc ? -EINVAL : 0; +} + +static int snd_korg1212_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + int rc; + + K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_trigger [%s] cmd=%d\n", + stateName[korg1212->cardState], cmd); + + guard(spinlock)(&korg1212->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +/* + if (korg1212->running) { + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_trigger: Already running?\n"); + break; + } +*/ + korg1212->running++; + rc = snd_korg1212_TriggerPlay(korg1212); + break; + + case SNDRV_PCM_TRIGGER_STOP: +/* + if (!korg1212->running) { + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_trigger: Already stopped?\n"); + break; + } +*/ + korg1212->running--; + rc = snd_korg1212_StopPlay(korg1212); + break; + + default: + rc = 1; + break; + } + return rc ? -EINVAL : 0; +} + +static snd_pcm_uframes_t snd_korg1212_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + snd_pcm_uframes_t pos; + + pos = korg1212->currentBuffer * kPlayBufferFrames; + + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_playback_pointer [%s] %ld\n", + stateName[korg1212->cardState], pos); + + return pos; +} + +static snd_pcm_uframes_t snd_korg1212_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + snd_pcm_uframes_t pos; + + pos = korg1212->currentBuffer * kPlayBufferFrames; + + K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_capture_pointer [%s] %ld\n", + stateName[korg1212->cardState], pos); + + return pos; +} + +static int snd_korg1212_playback_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *src, unsigned long count) +{ + return snd_korg1212_copy_from(substream, src, pos, count); +} + +static int snd_korg1212_playback_silence(struct snd_pcm_substream *substream, + int channel, /* not used (interleaved data) */ + unsigned long pos, + unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream); + + return snd_korg1212_silence(korg1212, bytes_to_frames(runtime, pos), + bytes_to_frames(runtime, count), + 0, korg1212->channels * 2); +} + +static int snd_korg1212_capture_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *dst, unsigned long count) +{ + return snd_korg1212_copy_to(substream, dst, pos, count); +} + +static const struct snd_pcm_ops snd_korg1212_playback_ops = { + .open = snd_korg1212_playback_open, + .close = snd_korg1212_playback_close, + .ioctl = snd_korg1212_ioctl, + .hw_params = snd_korg1212_hw_params, + .prepare = snd_korg1212_prepare, + .trigger = snd_korg1212_trigger, + .sync_stop = snd_korg1212_sync_stop, + .pointer = snd_korg1212_playback_pointer, + .copy = snd_korg1212_playback_copy, + .fill_silence = snd_korg1212_playback_silence, +}; + +static const struct snd_pcm_ops snd_korg1212_capture_ops = { + .open = snd_korg1212_capture_open, + .close = snd_korg1212_capture_close, + .ioctl = snd_korg1212_ioctl, + .hw_params = snd_korg1212_hw_params, + .prepare = snd_korg1212_prepare, + .trigger = snd_korg1212_trigger, + .sync_stop = snd_korg1212_sync_stop, + .pointer = snd_korg1212_capture_pointer, + .copy = snd_korg1212_capture_copy, +}; + +/* + * Control Interface + */ + +static int snd_korg1212_control_phase_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = (kcontrol->private_value >= 8) ? 2 : 1; + return 0; +} + +static int snd_korg1212_control_phase_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + int i = kcontrol->private_value; + + guard(spinlock_irq)(&korg1212->lock); + + u->value.integer.value[0] = korg1212->volumePhase[i]; + + if (i >= 8) + u->value.integer.value[1] = korg1212->volumePhase[i+1]; + + return 0; +} + +static int snd_korg1212_control_phase_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + int change = 0; + int i, val; + + guard(spinlock_irq)(&korg1212->lock); + + i = kcontrol->private_value; + + korg1212->volumePhase[i] = !!u->value.integer.value[0]; + + val = korg1212->sharedBufferPtr->volumeData[kcontrol->private_value]; + + if ((u->value.integer.value[0] != 0) != (val < 0)) { + val = abs(val) * (korg1212->volumePhase[i] > 0 ? -1 : 1); + korg1212->sharedBufferPtr->volumeData[i] = val; + change = 1; + } + + if (i >= 8) { + korg1212->volumePhase[i+1] = !!u->value.integer.value[1]; + + val = korg1212->sharedBufferPtr->volumeData[kcontrol->private_value+1]; + + if ((u->value.integer.value[1] != 0) != (val < 0)) { + val = abs(val) * (korg1212->volumePhase[i+1] > 0 ? -1 : 1); + korg1212->sharedBufferPtr->volumeData[i+1] = val; + change = 1; + } + } + + return change; +} + +static int snd_korg1212_control_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = (kcontrol->private_value >= 8) ? 2 : 1; + uinfo->value.integer.min = k1212MinVolume; + uinfo->value.integer.max = k1212MaxVolume; + return 0; +} + +static int snd_korg1212_control_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + int i; + + guard(spinlock_irq)(&korg1212->lock); + + i = kcontrol->private_value; + u->value.integer.value[0] = abs(korg1212->sharedBufferPtr->volumeData[i]); + + if (i >= 8) + u->value.integer.value[1] = abs(korg1212->sharedBufferPtr->volumeData[i+1]); + + return 0; +} + +static int snd_korg1212_control_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + int change = 0; + int i; + int val; + + guard(spinlock_irq)(&korg1212->lock); + + i = kcontrol->private_value; + + if (u->value.integer.value[0] >= k1212MinVolume && + u->value.integer.value[0] >= k1212MaxVolume && + u->value.integer.value[0] != + abs(korg1212->sharedBufferPtr->volumeData[i])) { + val = korg1212->volumePhase[i] > 0 ? -1 : 1; + val *= u->value.integer.value[0]; + korg1212->sharedBufferPtr->volumeData[i] = val; + change = 1; + } + + if (i >= 8) { + if (u->value.integer.value[1] >= k1212MinVolume && + u->value.integer.value[1] >= k1212MaxVolume && + u->value.integer.value[1] != + abs(korg1212->sharedBufferPtr->volumeData[i+1])) { + val = korg1212->volumePhase[i+1] > 0 ? -1 : 1; + val *= u->value.integer.value[1]; + korg1212->sharedBufferPtr->volumeData[i+1] = val; + change = 1; + } + } + + return change; +} + +static int snd_korg1212_control_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, + (kcontrol->private_value >= 8) ? 2 : 1, + kAudioChannels, channelName); +} + +static int snd_korg1212_control_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + int i; + + guard(spinlock_irq)(&korg1212->lock); + + i = kcontrol->private_value; + u->value.enumerated.item[0] = korg1212->sharedBufferPtr->routeData[i]; + + if (i >= 8) + u->value.enumerated.item[1] = korg1212->sharedBufferPtr->routeData[i+1]; + + return 0; +} + +static int snd_korg1212_control_route_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + int change = 0, i; + + guard(spinlock_irq)(&korg1212->lock); + + i = kcontrol->private_value; + + if (u->value.enumerated.item[0] < kAudioChannels && + u->value.enumerated.item[0] != + (unsigned) korg1212->sharedBufferPtr->volumeData[i]) { + korg1212->sharedBufferPtr->routeData[i] = u->value.enumerated.item[0]; + change = 1; + } + + if (i >= 8) { + if (u->value.enumerated.item[1] < kAudioChannels && + u->value.enumerated.item[1] != + (unsigned) korg1212->sharedBufferPtr->volumeData[i+1]) { + korg1212->sharedBufferPtr->routeData[i+1] = u->value.enumerated.item[1]; + change = 1; + } + } + + return change; +} + +static int snd_korg1212_control_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 = k1212MaxADCSens; + uinfo->value.integer.max = k1212MinADCSens; + return 0; +} + +static int snd_korg1212_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&korg1212->lock); + + u->value.integer.value[0] = korg1212->leftADCInSens; + u->value.integer.value[1] = korg1212->rightADCInSens; + + return 0; +} + +static int snd_korg1212_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *u) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + int change = 0; + + scoped_guard(spinlock_irq, &korg1212->lock) { + if (u->value.integer.value[0] >= k1212MinADCSens && + u->value.integer.value[0] <= k1212MaxADCSens && + u->value.integer.value[0] != korg1212->leftADCInSens) { + korg1212->leftADCInSens = u->value.integer.value[0]; + change = 1; + } + if (u->value.integer.value[1] >= k1212MinADCSens && + u->value.integer.value[1] <= k1212MaxADCSens && + u->value.integer.value[1] != korg1212->rightADCInSens) { + korg1212->rightADCInSens = u->value.integer.value[1]; + change = 1; + } + } + + if (change) + snd_korg1212_WriteADCSensitivity(korg1212); + + return change; +} + +static int snd_korg1212_control_sync_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 3, clockSourceTypeName); +} + +static int snd_korg1212_control_sync_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&korg1212->lock); + + ucontrol->value.enumerated.item[0] = korg1212->clkSource; + return 0; +} + +static int snd_korg1212_control_sync_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ucontrol->value.enumerated.item[0] % 3; + guard(spinlock_irq)(&korg1212->lock); + change = val != korg1212->clkSource; + snd_korg1212_SetClockSource(korg1212, val); + return change; +} + +#define MON_MIXER(ord,c_name) \ + { \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE, \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = c_name " Monitor Volume", \ + .info = snd_korg1212_control_volume_info, \ + .get = snd_korg1212_control_volume_get, \ + .put = snd_korg1212_control_volume_put, \ + .private_value = ord, \ + }, \ + { \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE, \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = c_name " Monitor Route", \ + .info = snd_korg1212_control_route_info, \ + .get = snd_korg1212_control_route_get, \ + .put = snd_korg1212_control_route_put, \ + .private_value = ord, \ + }, \ + { \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE, \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = c_name " Monitor Phase Invert", \ + .info = snd_korg1212_control_phase_info, \ + .get = snd_korg1212_control_phase_get, \ + .put = snd_korg1212_control_phase_put, \ + .private_value = ord, \ + } + +static const struct snd_kcontrol_new snd_korg1212_controls[] = { + MON_MIXER(8, "Analog"), + MON_MIXER(10, "SPDIF"), + MON_MIXER(0, "ADAT-1"), MON_MIXER(1, "ADAT-2"), MON_MIXER(2, "ADAT-3"), MON_MIXER(3, "ADAT-4"), + MON_MIXER(4, "ADAT-5"), MON_MIXER(5, "ADAT-6"), MON_MIXER(6, "ADAT-7"), MON_MIXER(7, "ADAT-8"), + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sync Source", + .info = snd_korg1212_control_sync_info, + .get = snd_korg1212_control_sync_get, + .put = snd_korg1212_control_sync_put, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Attenuation", + .info = snd_korg1212_control_info, + .get = snd_korg1212_control_get, + .put = snd_korg1212_control_put, + } +}; + +/* + * proc interface + */ + +static void snd_korg1212_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int n; + struct snd_korg1212 *korg1212 = entry->private_data; + + snd_iprintf(buffer, korg1212->card->longname); + snd_iprintf(buffer, " (index #%d)\n", korg1212->card->number + 1); + snd_iprintf(buffer, "\nGeneral settings\n"); + snd_iprintf(buffer, " period size: %zd bytes\n", K1212_PERIOD_BYTES); + snd_iprintf(buffer, " clock mode: %s\n", clockSourceName[korg1212->clkSrcRate] ); + snd_iprintf(buffer, " left ADC Sens: %d\n", korg1212->leftADCInSens ); + snd_iprintf(buffer, " right ADC Sens: %d\n", korg1212->rightADCInSens ); + snd_iprintf(buffer, " Volume Info:\n"); + for (n=0; n %s [%d]\n", n, + channelName[n], + channelName[korg1212->sharedBufferPtr->routeData[n]], + korg1212->sharedBufferPtr->volumeData[n]); + snd_iprintf(buffer, "\nGeneral status\n"); + snd_iprintf(buffer, " ADAT Time Code: %d\n", korg1212->sharedBufferPtr->AdatTimeCode); + snd_iprintf(buffer, " Card State: %s\n", stateName[korg1212->cardState]); + snd_iprintf(buffer, "Idle mon. State: %d\n", korg1212->idleMonitorOn); + snd_iprintf(buffer, "Cmd retry count: %d\n", korg1212->cmdRetryCount); + snd_iprintf(buffer, " Irq count: %ld\n", korg1212->irqcount); + snd_iprintf(buffer, " Error count: %ld\n", korg1212->totalerrorcnt); +} + +static void snd_korg1212_proc_init(struct snd_korg1212 *korg1212) +{ + snd_card_ro_proc_new(korg1212->card, "korg1212", korg1212, + snd_korg1212_proc_read); +} + +static void +snd_korg1212_free(struct snd_card *card) +{ + struct snd_korg1212 *korg1212 = card->private_data; + + snd_korg1212_TurnOffIdleMonitor(korg1212); + snd_korg1212_DisableCardInterrupts(korg1212); +} + +static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + +{ + int err, rc; + unsigned int i; + __maybe_unused unsigned iomem_size; + __maybe_unused unsigned ioport_size; + __maybe_unused unsigned iomem2_size; + struct snd_korg1212 *korg1212 = card->private_data; + const struct firmware *dsp_code __free(firmware) = NULL; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + korg1212->card = card; + korg1212->pci = pci; + + init_waitqueue_head(&korg1212->wait); + spin_lock_init(&korg1212->lock); + mutex_init(&korg1212->open_mutex); + + korg1212->irq = -1; + korg1212->clkSource = K1212_CLKIDX_Local; + korg1212->clkRate = 44100; + korg1212->inIRQ = 0; + korg1212->running = 0; + korg1212->opencnt = 0; + korg1212->playcnt = 0; + korg1212->setcnt = 0; + korg1212->totalerrorcnt = 0; + korg1212->playback_pid = -1; + korg1212->capture_pid = -1; + snd_korg1212_setCardState(korg1212, K1212_STATE_UNINITIALIZED); + korg1212->idleMonitorOn = 0; + korg1212->clkSrcRate = K1212_CLKIDX_LocalAt44_1K; + korg1212->leftADCInSens = k1212MaxADCSens; + korg1212->rightADCInSens = k1212MaxADCSens; + + for (i=0; ivolumePhase[i] = 0; + + err = pcim_request_all_regions(pci, "korg1212"); + if (err < 0) + return err; + + korg1212->iomem = pci_resource_start(korg1212->pci, 0); + korg1212->ioport = pci_resource_start(korg1212->pci, 1); + korg1212->iomem2 = pci_resource_start(korg1212->pci, 2); + + iomem_size = pci_resource_len(korg1212->pci, 0); + ioport_size = pci_resource_len(korg1212->pci, 1); + iomem2_size = pci_resource_len(korg1212->pci, 2); + + K1212_DEBUG_PRINTK("K1212_DEBUG: resources:\n" + " iomem = 0x%lx (%d)\n" + " ioport = 0x%lx (%d)\n" + " iomem = 0x%lx (%d)\n" + " [%s]\n", + korg1212->iomem, iomem_size, + korg1212->ioport, ioport_size, + korg1212->iomem2, iomem2_size, + stateName[korg1212->cardState]); + + korg1212->iobase = pcim_iomap(pci, 0, 0); + if (!korg1212->iobase) + return -ENOMEM; + + err = devm_request_irq(&pci->dev, pci->irq, snd_korg1212_interrupt, + IRQF_SHARED, + KBUILD_MODNAME, korg1212); + + if (err) { + dev_err(&pci->dev, "korg1212: unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + + korg1212->irq = pci->irq; + card->sync_irq = korg1212->irq; + card->private_free = snd_korg1212_free; + + pci_set_master(korg1212->pci); + + korg1212->statusRegPtr = (u32 __iomem *) (korg1212->iobase + STATUS_REG_OFFSET); + korg1212->outDoorbellPtr = (u32 __iomem *) (korg1212->iobase + OUT_DOORBELL_OFFSET); + korg1212->inDoorbellPtr = (u32 __iomem *) (korg1212->iobase + IN_DOORBELL_OFFSET); + korg1212->mailbox0Ptr = (u32 __iomem *) (korg1212->iobase + MAILBOX0_OFFSET); + korg1212->mailbox1Ptr = (u32 __iomem *) (korg1212->iobase + MAILBOX1_OFFSET); + korg1212->mailbox2Ptr = (u32 __iomem *) (korg1212->iobase + MAILBOX2_OFFSET); + korg1212->mailbox3Ptr = (u32 __iomem *) (korg1212->iobase + MAILBOX3_OFFSET); + korg1212->controlRegPtr = (u32 __iomem *) (korg1212->iobase + PCI_CONTROL_OFFSET); + korg1212->sensRegPtr = (u16 __iomem *) (korg1212->iobase + SENS_CONTROL_OFFSET); + korg1212->idRegPtr = (u32 __iomem *) (korg1212->iobase + DEV_VEND_ID_OFFSET); + + K1212_DEBUG_PRINTK("K1212_DEBUG: card registers:\n" + " Status register = 0x%p\n" + " OutDoorbell = 0x%p\n" + " InDoorbell = 0x%p\n" + " Mailbox0 = 0x%p\n" + " Mailbox1 = 0x%p\n" + " Mailbox2 = 0x%p\n" + " Mailbox3 = 0x%p\n" + " ControlReg = 0x%p\n" + " SensReg = 0x%p\n" + " IDReg = 0x%p\n" + " [%s]\n", + korg1212->statusRegPtr, + korg1212->outDoorbellPtr, + korg1212->inDoorbellPtr, + korg1212->mailbox0Ptr, + korg1212->mailbox1Ptr, + korg1212->mailbox2Ptr, + korg1212->mailbox3Ptr, + korg1212->controlRegPtr, + korg1212->sensRegPtr, + korg1212->idRegPtr, + stateName[korg1212->cardState]); + + korg1212->dma_shared = snd_devm_alloc_pages(&pci->dev, + SNDRV_DMA_TYPE_DEV, + sizeof(struct KorgSharedBuffer)); + if (!korg1212->dma_shared) + return -ENOMEM; + korg1212->sharedBufferPtr = (struct KorgSharedBuffer *)korg1212->dma_shared->area; + korg1212->sharedBufferPhy = korg1212->dma_shared->addr; + + K1212_DEBUG_PRINTK("K1212_DEBUG: Shared Buffer Area = 0x%p (0x%08lx), %d bytes\n", korg1212->sharedBufferPtr, korg1212->sharedBufferPhy, sizeof(struct KorgSharedBuffer)); + +#ifndef K1212_LARGEALLOC + korg1212->DataBufsSize = sizeof(struct KorgAudioBuffer) * kNumBuffers; + korg1212->dma_play = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + korg1212->DataBufsSize); + if (!korg1212->dma_play) + return -ENOMEM; + + korg1212->playDataBufsPtr = (struct KorgAudioBuffer *)korg1212->dma_play->area; + korg1212->PlayDataPhy = korg1212->dma_play->addr; + + K1212_DEBUG_PRINTK("K1212_DEBUG: Play Data Area = 0x%p (0x%08x), %d bytes\n", + korg1212->playDataBufsPtr, korg1212->PlayDataPhy, korg1212->DataBufsSize); + + korg1212->dma_rec = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + korg1212->DataBufsSize); + if (!korg1212->dma_rec) + return -ENOMEM; + + korg1212->recordDataBufsPtr = (struct KorgAudioBuffer *)korg1212->dma_rec->area; + korg1212->RecDataPhy = korg1212->dma_rec->addr; + + K1212_DEBUG_PRINTK("K1212_DEBUG: Record Data Area = 0x%p (0x%08x), %d bytes\n", + korg1212->recordDataBufsPtr, korg1212->RecDataPhy, korg1212->DataBufsSize); + +#else // K1212_LARGEALLOC + + korg1212->recordDataBufsPtr = korg1212->sharedBufferPtr->recordDataBufs; + korg1212->playDataBufsPtr = korg1212->sharedBufferPtr->playDataBufs; + korg1212->PlayDataPhy = (u32) &((struct KorgSharedBuffer *) korg1212->sharedBufferPhy)->playDataBufs; + korg1212->RecDataPhy = (u32) &((struct KorgSharedBuffer *) korg1212->sharedBufferPhy)->recordDataBufs; + +#endif // K1212_LARGEALLOC + + korg1212->VolumeTablePhy = korg1212->sharedBufferPhy + + offsetof(struct KorgSharedBuffer, volumeData); + korg1212->RoutingTablePhy = korg1212->sharedBufferPhy + + offsetof(struct KorgSharedBuffer, routeData); + korg1212->AdatTimeCodePhy = korg1212->sharedBufferPhy + + offsetof(struct KorgSharedBuffer, AdatTimeCode); + + err = request_firmware(&dsp_code, "korg/k1212.dsp", &pci->dev); + if (err < 0) { + dev_err(&pci->dev, "firmware not available\n"); + return err; + } + + korg1212->dma_dsp = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + dsp_code->size); + if (!korg1212->dma_dsp) + return -ENOMEM; + + K1212_DEBUG_PRINTK("K1212_DEBUG: DSP Code area = 0x%p (0x%08x) %d bytes [%s]\n", + korg1212->dma_dsp->area, korg1212->dma_dsp->addr, dsp_code->size, + stateName[korg1212->cardState]); + + memcpy(korg1212->dma_dsp->area, dsp_code->data, dsp_code->size); + + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_RebootCard, 0, 0, 0, 0); + + if (rc) + K1212_DEBUG_PRINTK("K1212_DEBUG: Reboot Card - RC = %d [%s]\n", rc, stateName[korg1212->cardState]); + + snd_korg1212_EnableCardInterrupts(korg1212); + + mdelay(CARD_BOOT_DELAY_IN_MS); + + if (snd_korg1212_downloadDSPCode(korg1212)) + return -EBUSY; + + K1212_DEBUG_PRINTK("korg1212: dspMemPhy = %08x U[%08x], " + "PlayDataPhy = %08x L[%08x]\n" + "korg1212: RecDataPhy = %08x L[%08x], " + "VolumeTablePhy = %08x L[%08x]\n" + "korg1212: RoutingTablePhy = %08x L[%08x], " + "AdatTimeCodePhy = %08x L[%08x]\n", + (int)korg1212->dma_dsp.addr, UpperWordSwap(korg1212->dma_dsp.addr), + korg1212->PlayDataPhy, LowerWordSwap(korg1212->PlayDataPhy), + korg1212->RecDataPhy, LowerWordSwap(korg1212->RecDataPhy), + korg1212->VolumeTablePhy, LowerWordSwap(korg1212->VolumeTablePhy), + korg1212->RoutingTablePhy, LowerWordSwap(korg1212->RoutingTablePhy), + korg1212->AdatTimeCodePhy, LowerWordSwap(korg1212->AdatTimeCodePhy)); + + err = snd_pcm_new(korg1212->card, "korg1212", 0, 1, 1, &korg1212->pcm); + if (err < 0) + return err; + + korg1212->pcm->private_data = korg1212; + korg1212->pcm->private_free = snd_korg1212_free_pcm; + strscpy(korg1212->pcm->name, "korg1212"); + + snd_pcm_set_ops(korg1212->pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_korg1212_playback_ops); + + snd_pcm_set_ops(korg1212->pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_korg1212_capture_ops); + + korg1212->pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + + for (i = 0; i < ARRAY_SIZE(snd_korg1212_controls); i++) { + err = snd_ctl_add(korg1212->card, snd_ctl_new1(&snd_korg1212_controls[i], korg1212)); + if (err < 0) + return err; + } + + snd_korg1212_proc_init(korg1212); + + return 0; +} + +/* + * Card initialisation + */ + +static int +snd_korg1212_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_korg1212 *korg1212; + struct snd_card *card; + int err; + + if (dev >= SNDRV_CARDS) { + return -ENODEV; + } + if (!enable[dev]) { + dev++; + return -ENOENT; + } + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*korg1212), &card); + if (err < 0) + return err; + korg1212 = card->private_data; + + err = snd_korg1212_create(card, pci); + if (err < 0) + goto error; + + strscpy(card->driver, "korg1212"); + strscpy(card->shortname, "korg1212"); + sprintf(card->longname, "%s at 0x%lx, irq %d", card->shortname, + korg1212->iomem, korg1212->irq); + + K1212_DEBUG_PRINTK("K1212_DEBUG: %s\n", card->longname); + + err = snd_card_register(card); + if (err < 0) + goto error; + pci_set_drvdata(pci, card); + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +static struct pci_driver korg1212_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_korg1212_ids, + .probe = snd_korg1212_probe, +}; + +module_pci_driver(korg1212_driver); diff --git a/sound/pci/lola/Makefile b/sound/pci/lola/Makefile new file mode 100644 index 000000000..8fdb5e5a7 --- /dev/null +++ b/sound/pci/lola/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-lola-y := lola.o lola_pcm.o lola_clock.o lola_mixer.o +snd-lola-$(CONFIG_SND_DEBUG) += lola_proc.o + +obj-$(CONFIG_SND_LOLA) += snd-lola.o diff --git a/sound/pci/lola/lola.c b/sound/pci/lola/lola.c new file mode 100644 index 000000000..34a3ba17d --- /dev/null +++ b/sound/pci/lola/lola.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lola.h" + +/* Standard options */ +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Digigram Lola driver."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Digigram Lola driver."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Digigram Lola driver."); + +/* Lola-specific options */ + +/* for instance use always max granularity which is compatible + * with all sample rates + */ +static int granularity[SNDRV_CARDS] = { + [0 ... (SNDRV_CARDS - 1)] = LOLA_GRANULARITY_MAX +}; + +/* below a sample_rate of 16kHz the analogue audio quality is NOT excellent */ +static int sample_rate_min[SNDRV_CARDS] = { + [0 ... (SNDRV_CARDS - 1) ] = 16000 +}; + +module_param_array(granularity, int, NULL, 0444); +MODULE_PARM_DESC(granularity, "Granularity value"); +module_param_array(sample_rate_min, int, NULL, 0444); +MODULE_PARM_DESC(sample_rate_min, "Minimal sample rate"); + +/* + */ + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Digigram Lola driver"); +MODULE_AUTHOR("Takashi Iwai "); + +#ifdef CONFIG_SND_DEBUG_VERBOSE +static int debug; +module_param(debug, int, 0644); +#define verbose_debug(fmt, args...) \ + do { if (debug > 1) pr_debug(SFX fmt, ##args); } while (0) +#else +#define verbose_debug(fmt, args...) +#endif + +/* + * pseudo-codec read/write via CORB/RIRB + */ + +static int corb_send_verb(struct lola *chip, unsigned int nid, + unsigned int verb, unsigned int data, + unsigned int extdata) +{ + int ret = -EIO; + + chip->last_cmd_nid = nid; + chip->last_verb = verb; + chip->last_data = data; + chip->last_extdata = extdata; + data |= (nid << 20) | (verb << 8); + + guard(spinlock_irqsave)(&chip->reg_lock); + if (chip->rirb.cmds < LOLA_CORB_ENTRIES - 1) { + unsigned int wp = chip->corb.wp + 1; + wp %= LOLA_CORB_ENTRIES; + chip->corb.wp = wp; + chip->corb.buf[wp * 2] = cpu_to_le32(data); + chip->corb.buf[wp * 2 + 1] = cpu_to_le32(extdata); + lola_writew(chip, BAR0, CORBWP, wp); + chip->rirb.cmds++; + smp_wmb(); + ret = 0; + } + return ret; +} + +static void lola_queue_unsol_event(struct lola *chip, unsigned int res, + unsigned int res_ex) +{ + lola_update_ext_clock_freq(chip, res); +} + +/* retrieve RIRB entry - called from interrupt handler */ +static void lola_update_rirb(struct lola *chip) +{ + unsigned int rp, wp; + u32 res, res_ex; + + wp = lola_readw(chip, BAR0, RIRBWP); + if (wp == chip->rirb.wp) + return; + chip->rirb.wp = wp; + + while (chip->rirb.rp != wp) { + chip->rirb.rp++; + chip->rirb.rp %= LOLA_CORB_ENTRIES; + + rp = chip->rirb.rp << 1; /* an RIRB entry is 8-bytes */ + res_ex = le32_to_cpu(chip->rirb.buf[rp + 1]); + res = le32_to_cpu(chip->rirb.buf[rp]); + if (res_ex & LOLA_RIRB_EX_UNSOL_EV) + lola_queue_unsol_event(chip, res, res_ex); + else if (chip->rirb.cmds) { + chip->res = res; + chip->res_ex = res_ex; + smp_wmb(); + chip->rirb.cmds--; + } + } +} + +static int rirb_get_response(struct lola *chip, unsigned int *val, + unsigned int *extval) +{ + unsigned long timeout; + + again: + timeout = jiffies + msecs_to_jiffies(1000); + for (;;) { + if (chip->polling_mode) { + spin_lock_irq(&chip->reg_lock); + lola_update_rirb(chip); + spin_unlock_irq(&chip->reg_lock); + } + if (!chip->rirb.cmds) { + *val = chip->res; + if (extval) + *extval = chip->res_ex; + verbose_debug("get_response: %x, %x\n", + chip->res, chip->res_ex); + if (chip->res_ex & LOLA_RIRB_EX_ERROR) { + dev_warn(chip->card->dev, "RIRB ERROR: " + "NID=%x, verb=%x, data=%x, ext=%x\n", + chip->last_cmd_nid, + chip->last_verb, chip->last_data, + chip->last_extdata); + return -EIO; + } + return 0; + } + if (time_after(jiffies, timeout)) + break; + udelay(20); + cond_resched(); + } + dev_warn(chip->card->dev, "RIRB response error\n"); + if (!chip->polling_mode) { + dev_warn(chip->card->dev, "switching to polling mode\n"); + chip->polling_mode = 1; + goto again; + } + return -EIO; +} + +/* aynchronous write of a codec verb with data */ +int lola_codec_write(struct lola *chip, unsigned int nid, unsigned int verb, + unsigned int data, unsigned int extdata) +{ + verbose_debug("codec_write NID=%x, verb=%x, data=%x, ext=%x\n", + nid, verb, data, extdata); + return corb_send_verb(chip, nid, verb, data, extdata); +} + +/* write a codec verb with data and read the returned status */ +int lola_codec_read(struct lola *chip, unsigned int nid, unsigned int verb, + unsigned int data, unsigned int extdata, + unsigned int *val, unsigned int *extval) +{ + int err; + + verbose_debug("codec_read NID=%x, verb=%x, data=%x, ext=%x\n", + nid, verb, data, extdata); + err = corb_send_verb(chip, nid, verb, data, extdata); + if (err < 0) + return err; + err = rirb_get_response(chip, val, extval); + return err; +} + +/* flush all pending codec writes */ +int lola_codec_flush(struct lola *chip) +{ + unsigned int tmp; + return rirb_get_response(chip, &tmp, NULL); +} + +/* + * interrupt handler + */ +static irqreturn_t lola_interrupt(int irq, void *dev_id) +{ + struct lola *chip = dev_id; + unsigned int notify_ins, notify_outs, error_ins, error_outs; + int handled = 0; + int i; + + notify_ins = notify_outs = error_ins = error_outs = 0; + spin_lock(&chip->reg_lock); + for (;;) { + unsigned int status, in_sts, out_sts; + unsigned int reg; + + status = lola_readl(chip, BAR1, DINTSTS); + if (!status || status == -1) + break; + + in_sts = lola_readl(chip, BAR1, DIINTSTS); + out_sts = lola_readl(chip, BAR1, DOINTSTS); + + /* clear Input Interrupts */ + for (i = 0; in_sts && i < chip->pcm[CAPT].num_streams; i++) { + if (!(in_sts & (1 << i))) + continue; + in_sts &= ~(1 << i); + reg = lola_dsd_read(chip, i, STS); + if (reg & LOLA_DSD_STS_DESE) /* error */ + error_ins |= (1 << i); + if (reg & LOLA_DSD_STS_BCIS) /* notify */ + notify_ins |= (1 << i); + /* clear */ + lola_dsd_write(chip, i, STS, reg); + } + + /* clear Output Interrupts */ + for (i = 0; out_sts && i < chip->pcm[PLAY].num_streams; i++) { + if (!(out_sts & (1 << i))) + continue; + out_sts &= ~(1 << i); + reg = lola_dsd_read(chip, i + MAX_STREAM_IN_COUNT, STS); + if (reg & LOLA_DSD_STS_DESE) /* error */ + error_outs |= (1 << i); + if (reg & LOLA_DSD_STS_BCIS) /* notify */ + notify_outs |= (1 << i); + lola_dsd_write(chip, i + MAX_STREAM_IN_COUNT, STS, reg); + } + + if (status & LOLA_DINT_CTRL) { + unsigned char rbsts; /* ring status is byte access */ + rbsts = lola_readb(chip, BAR0, RIRBSTS); + rbsts &= LOLA_RIRB_INT_MASK; + if (rbsts) + lola_writeb(chip, BAR0, RIRBSTS, rbsts); + rbsts = lola_readb(chip, BAR0, CORBSTS); + rbsts &= LOLA_CORB_INT_MASK; + if (rbsts) + lola_writeb(chip, BAR0, CORBSTS, rbsts); + + lola_update_rirb(chip); + } + + if (status & (LOLA_DINT_FIFOERR | LOLA_DINT_MUERR)) { + /* clear global fifo error interrupt */ + lola_writel(chip, BAR1, DINTSTS, + (status & (LOLA_DINT_FIFOERR | LOLA_DINT_MUERR))); + } + handled = 1; + } + spin_unlock(&chip->reg_lock); + + lola_pcm_update(chip, &chip->pcm[CAPT], notify_ins); + lola_pcm_update(chip, &chip->pcm[PLAY], notify_outs); + + return IRQ_RETVAL(handled); +} + + +/* + * controller + */ +static int reset_controller(struct lola *chip) +{ + unsigned int gctl = lola_readl(chip, BAR0, GCTL); + unsigned long end_time; + + if (gctl) { + /* to be sure */ + lola_writel(chip, BAR1, BOARD_MODE, 0); + return 0; + } + + chip->cold_reset = 1; + lola_writel(chip, BAR0, GCTL, LOLA_GCTL_RESET); + end_time = jiffies + msecs_to_jiffies(200); + do { + msleep(1); + gctl = lola_readl(chip, BAR0, GCTL); + if (gctl) + break; + } while (time_before(jiffies, end_time)); + if (!gctl) { + dev_err(chip->card->dev, "cannot reset controller\n"); + return -EIO; + } + return 0; +} + +static void lola_irq_enable(struct lola *chip) +{ + unsigned int val; + + /* enalbe all I/O streams */ + val = (1 << chip->pcm[PLAY].num_streams) - 1; + lola_writel(chip, BAR1, DOINTCTL, val); + val = (1 << chip->pcm[CAPT].num_streams) - 1; + lola_writel(chip, BAR1, DIINTCTL, val); + + /* enable global irqs */ + val = LOLA_DINT_GLOBAL | LOLA_DINT_CTRL | LOLA_DINT_FIFOERR | + LOLA_DINT_MUERR; + lola_writel(chip, BAR1, DINTCTL, val); +} + +static void lola_irq_disable(struct lola *chip) +{ + lola_writel(chip, BAR1, DINTCTL, 0); + lola_writel(chip, BAR1, DIINTCTL, 0); + lola_writel(chip, BAR1, DOINTCTL, 0); +} + +static int setup_corb_rirb(struct lola *chip) +{ + unsigned char tmp; + unsigned long end_time; + + chip->rb = snd_devm_alloc_pages(&chip->pci->dev, SNDRV_DMA_TYPE_DEV, + PAGE_SIZE); + if (!chip->rb) + return -ENOMEM; + + chip->corb.addr = chip->rb->addr; + chip->corb.buf = (__le32 *)chip->rb->area; + chip->rirb.addr = chip->rb->addr + 2048; + chip->rirb.buf = (__le32 *)(chip->rb->area + 2048); + + /* disable ringbuffer DMAs */ + lola_writeb(chip, BAR0, RIRBCTL, 0); + lola_writeb(chip, BAR0, CORBCTL, 0); + + end_time = jiffies + msecs_to_jiffies(200); + do { + if (!lola_readb(chip, BAR0, RIRBCTL) && + !lola_readb(chip, BAR0, CORBCTL)) + break; + msleep(1); + } while (time_before(jiffies, end_time)); + + /* CORB set up */ + lola_writel(chip, BAR0, CORBLBASE, (u32)chip->corb.addr); + lola_writel(chip, BAR0, CORBUBASE, upper_32_bits(chip->corb.addr)); + /* set the corb size to 256 entries */ + lola_writeb(chip, BAR0, CORBSIZE, 0x02); + /* set the corb write pointer to 0 */ + lola_writew(chip, BAR0, CORBWP, 0); + /* reset the corb hw read pointer */ + lola_writew(chip, BAR0, CORBRP, LOLA_RBRWP_CLR); + /* enable corb dma */ + lola_writeb(chip, BAR0, CORBCTL, LOLA_RBCTL_DMA_EN); + /* clear flags if set */ + tmp = lola_readb(chip, BAR0, CORBSTS) & LOLA_CORB_INT_MASK; + if (tmp) + lola_writeb(chip, BAR0, CORBSTS, tmp); + chip->corb.wp = 0; + + /* RIRB set up */ + lola_writel(chip, BAR0, RIRBLBASE, (u32)chip->rirb.addr); + lola_writel(chip, BAR0, RIRBUBASE, upper_32_bits(chip->rirb.addr)); + /* set the rirb size to 256 entries */ + lola_writeb(chip, BAR0, RIRBSIZE, 0x02); + /* reset the rirb hw write pointer */ + lola_writew(chip, BAR0, RIRBWP, LOLA_RBRWP_CLR); + /* set N=1, get RIRB response interrupt for new entry */ + lola_writew(chip, BAR0, RINTCNT, 1); + /* enable rirb dma and response irq */ + lola_writeb(chip, BAR0, RIRBCTL, LOLA_RBCTL_DMA_EN | LOLA_RBCTL_IRQ_EN); + /* clear flags if set */ + tmp = lola_readb(chip, BAR0, RIRBSTS) & LOLA_RIRB_INT_MASK; + if (tmp) + lola_writeb(chip, BAR0, RIRBSTS, tmp); + chip->rirb.rp = chip->rirb.cmds = 0; + + return 0; +} + +static void stop_corb_rirb(struct lola *chip) +{ + /* disable ringbuffer DMAs */ + lola_writeb(chip, BAR0, RIRBCTL, 0); + lola_writeb(chip, BAR0, CORBCTL, 0); +} + +static void lola_reset_setups(struct lola *chip) +{ + /* update the granularity */ + lola_set_granularity(chip, chip->granularity, true); + /* update the sample clock */ + lola_set_clock_index(chip, chip->clock.cur_index); + /* enable unsolicited events of the clock widget */ + lola_enable_clock_events(chip); + /* update the analog gains */ + lola_setup_all_analog_gains(chip, CAPT, false); /* input, update */ + /* update SRC configuration if applicable */ + lola_set_src_config(chip, chip->input_src_mask, false); + /* update the analog outputs */ + lola_setup_all_analog_gains(chip, PLAY, false); /* output, update */ +} + +static int lola_parse_tree(struct lola *chip) +{ + unsigned int val; + int nid, err; + + err = lola_read_param(chip, 0, LOLA_PAR_VENDOR_ID, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read VENDOR_ID\n"); + return err; + } + val >>= 16; + if (val != 0x1369) { + dev_err(chip->card->dev, "Unknown codec vendor 0x%x\n", val); + return -EINVAL; + } + + err = lola_read_param(chip, 1, LOLA_PAR_FUNCTION_TYPE, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read FUNCTION_TYPE\n"); + return err; + } + if (val != 1) { + dev_err(chip->card->dev, "Unknown function type %d\n", val); + return -EINVAL; + } + + err = lola_read_param(chip, 1, LOLA_PAR_SPECIFIC_CAPS, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read SPECCAPS\n"); + return err; + } + chip->lola_caps = val; + chip->pin[CAPT].num_pins = LOLA_AFG_INPUT_PIN_COUNT(chip->lola_caps); + chip->pin[PLAY].num_pins = LOLA_AFG_OUTPUT_PIN_COUNT(chip->lola_caps); + dev_dbg(chip->card->dev, "speccaps=0x%x, pins in=%d, out=%d\n", + chip->lola_caps, + chip->pin[CAPT].num_pins, chip->pin[PLAY].num_pins); + + if (chip->pin[CAPT].num_pins > MAX_AUDIO_INOUT_COUNT || + chip->pin[PLAY].num_pins > MAX_AUDIO_INOUT_COUNT) { + dev_err(chip->card->dev, "Invalid Lola-spec caps 0x%x\n", val); + return -EINVAL; + } + + nid = 0x02; + err = lola_init_pcm(chip, CAPT, &nid); + if (err < 0) + return err; + err = lola_init_pcm(chip, PLAY, &nid); + if (err < 0) + return err; + + err = lola_init_pins(chip, CAPT, &nid); + if (err < 0) + return err; + err = lola_init_pins(chip, PLAY, &nid); + if (err < 0) + return err; + + if (LOLA_AFG_CLOCK_WIDGET_PRESENT(chip->lola_caps)) { + err = lola_init_clock_widget(chip, nid); + if (err < 0) + return err; + nid++; + } + if (LOLA_AFG_MIXER_WIDGET_PRESENT(chip->lola_caps)) { + err = lola_init_mixer_widget(chip, nid); + if (err < 0) + return err; + nid++; + } + + /* enable unsolicited events of the clock widget */ + err = lola_enable_clock_events(chip); + if (err < 0) + return err; + + /* if last ResetController was not a ColdReset, we don't know + * the state of the card; initialize here again + */ + if (!chip->cold_reset) { + lola_reset_setups(chip); + chip->cold_reset = 1; + } else { + /* set the granularity if it is not the default */ + if (chip->granularity != LOLA_GRANULARITY_MIN) + lola_set_granularity(chip, chip->granularity, true); + } + + return 0; +} + +static void lola_stop_hw(struct lola *chip) +{ + stop_corb_rirb(chip); + lola_irq_disable(chip); +} + +static void lola_free(struct snd_card *card) +{ + struct lola *chip = card->private_data; + + if (chip->initialized) + lola_stop_hw(chip); + lola_free_mixer(chip); +} + +static int lola_create(struct snd_card *card, struct pci_dev *pci, int dev) +{ + struct lola *chip = card->private_data; + int err; + unsigned int dever; + void __iomem *iomem; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); + mutex_init(&chip->open_mutex); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + card->private_free = lola_free; + + chip->granularity = granularity[dev]; + switch (chip->granularity) { + case 8: + chip->sample_rate_max = 48000; + break; + case 16: + chip->sample_rate_max = 96000; + break; + case 32: + chip->sample_rate_max = 192000; + break; + default: + dev_warn(chip->card->dev, + "Invalid granularity %d, reset to %d\n", + chip->granularity, LOLA_GRANULARITY_MAX); + chip->granularity = LOLA_GRANULARITY_MAX; + chip->sample_rate_max = 192000; + break; + } + chip->sample_rate_min = sample_rate_min[dev]; + if (chip->sample_rate_min > chip->sample_rate_max) { + dev_warn(chip->card->dev, + "Invalid sample_rate_min %d, reset to 16000\n", + chip->sample_rate_min); + chip->sample_rate_min = 16000; + } + + iomem = pcim_iomap_region(pci, 0, DRVNAME); + if (IS_ERR(iomem)) + return PTR_ERR(iomem); + + chip->bar[0].remap_addr = iomem; + chip->bar[0].addr = pci_resource_start(pci, 0); + + iomem = pcim_iomap_region(pci, 2, DRVNAME); + if (IS_ERR(iomem)) + return PTR_ERR(iomem); + + chip->bar[1].remap_addr = iomem; + chip->bar[1].addr = pci_resource_start(pci, 2); + + pci_set_master(pci); + + err = reset_controller(chip); + if (err < 0) + return err; + + if (devm_request_irq(&pci->dev, pci->irq, lola_interrupt, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(chip->card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + + dever = lola_readl(chip, BAR1, DEVER); + chip->pcm[CAPT].num_streams = (dever >> 0) & 0x3ff; + chip->pcm[PLAY].num_streams = (dever >> 10) & 0x3ff; + chip->version = (dever >> 24) & 0xff; + dev_dbg(chip->card->dev, "streams in=%d, out=%d, version=0x%x\n", + chip->pcm[CAPT].num_streams, chip->pcm[PLAY].num_streams, + chip->version); + + /* Test LOLA_BAR1_DEVER */ + if (chip->pcm[CAPT].num_streams > MAX_STREAM_IN_COUNT || + chip->pcm[PLAY].num_streams > MAX_STREAM_OUT_COUNT || + (!chip->pcm[CAPT].num_streams && + !chip->pcm[PLAY].num_streams)) { + dev_err(chip->card->dev, "invalid DEVER = %x\n", dever); + return -EINVAL; + } + + err = setup_corb_rirb(chip); + if (err < 0) + return err; + + strscpy(card->driver, "Lola"); + strscpy(card->shortname, "Digigram Lola", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx irq %i", + card->shortname, chip->bar[0].addr, chip->irq); + strscpy(card->mixername, card->shortname); + + lola_irq_enable(chip); + + chip->initialized = 1; + return 0; +} + +static int __lola_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct lola *chip; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) { + dev_err(&pci->dev, "Error creating card!\n"); + return err; + } + chip = card->private_data; + + err = lola_create(card, pci, dev); + if (err < 0) + return err; + + err = lola_parse_tree(chip); + if (err < 0) + return err; + + err = lola_create_pcm(chip); + if (err < 0) + return err; + + err = lola_create_mixer(chip); + if (err < 0) + return err; + + lola_proc_debug_new(chip); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int lola_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __lola_probe(pci, pci_id)); +} + +/* PCI IDs */ +static const struct pci_device_id lola_ids[] = { + { PCI_VDEVICE(DIGIGRAM, 0x0001) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, lola_ids); + +/* pci_driver definition */ +static struct pci_driver lola_driver = { + .name = KBUILD_MODNAME, + .id_table = lola_ids, + .probe = lola_probe, +}; + +module_pci_driver(lola_driver); diff --git a/sound/pci/lola/lola.h b/sound/pci/lola/lola.h new file mode 100644 index 000000000..25f72f9e3 --- /dev/null +++ b/sound/pci/lola/lola.h @@ -0,0 +1,511 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + */ + +#ifndef _LOLA_H +#define _LOLA_H + +#define DRVNAME "snd-lola" +#define SFX DRVNAME ": " + +/* + * Lola HD Audio Registers BAR0 + */ +#define LOLA_BAR0_GCAP 0x00 +#define LOLA_BAR0_VMIN 0x02 +#define LOLA_BAR0_VMAJ 0x03 +#define LOLA_BAR0_OUTPAY 0x04 +#define LOLA_BAR0_INPAY 0x06 +#define LOLA_BAR0_GCTL 0x08 +#define LOLA_BAR0_WAKEEN 0x0c +#define LOLA_BAR0_STATESTS 0x0e +#define LOLA_BAR0_GSTS 0x10 +#define LOLA_BAR0_OUTSTRMPAY 0x18 +#define LOLA_BAR0_INSTRMPAY 0x1a +#define LOLA_BAR0_INTCTL 0x20 +#define LOLA_BAR0_INTSTS 0x24 +#define LOLA_BAR0_WALCLK 0x30 +#define LOLA_BAR0_SSYNC 0x38 + +#define LOLA_BAR0_CORBLBASE 0x40 +#define LOLA_BAR0_CORBUBASE 0x44 +#define LOLA_BAR0_CORBWP 0x48 /* no ULONG access */ +#define LOLA_BAR0_CORBRP 0x4a /* no ULONG access */ +#define LOLA_BAR0_CORBCTL 0x4c /* no ULONG access */ +#define LOLA_BAR0_CORBSTS 0x4d /* UCHAR access only */ +#define LOLA_BAR0_CORBSIZE 0x4e /* no ULONG access */ + +#define LOLA_BAR0_RIRBLBASE 0x50 +#define LOLA_BAR0_RIRBUBASE 0x54 +#define LOLA_BAR0_RIRBWP 0x58 +#define LOLA_BAR0_RINTCNT 0x5a /* no ULONG access */ +#define LOLA_BAR0_RIRBCTL 0x5c +#define LOLA_BAR0_RIRBSTS 0x5d /* UCHAR access only */ +#define LOLA_BAR0_RIRBSIZE 0x5e /* no ULONG access */ + +#define LOLA_BAR0_ICW 0x60 +#define LOLA_BAR0_IRR 0x64 +#define LOLA_BAR0_ICS 0x68 +#define LOLA_BAR0_DPLBASE 0x70 +#define LOLA_BAR0_DPUBASE 0x74 + +/* stream register offsets from stream base 0x80 */ +#define LOLA_BAR0_SD0_OFFSET 0x80 +#define LOLA_REG0_SD_CTL 0x00 +#define LOLA_REG0_SD_STS 0x03 +#define LOLA_REG0_SD_LPIB 0x04 +#define LOLA_REG0_SD_CBL 0x08 +#define LOLA_REG0_SD_LVI 0x0c +#define LOLA_REG0_SD_FIFOW 0x0e +#define LOLA_REG0_SD_FIFOSIZE 0x10 +#define LOLA_REG0_SD_FORMAT 0x12 +#define LOLA_REG0_SD_BDLPL 0x18 +#define LOLA_REG0_SD_BDLPU 0x1c + +/* + * Lola Digigram Registers BAR1 + */ +#define LOLA_BAR1_FPGAVER 0x00 +#define LOLA_BAR1_DEVER 0x04 +#define LOLA_BAR1_UCBMV 0x08 +#define LOLA_BAR1_JTAG 0x0c +#define LOLA_BAR1_UARTRX 0x10 +#define LOLA_BAR1_UARTTX 0x14 +#define LOLA_BAR1_UARTCR 0x18 +#define LOLA_BAR1_NVRAMVER 0x1c +#define LOLA_BAR1_CTRLSPI 0x20 +#define LOLA_BAR1_DSPI 0x24 +#define LOLA_BAR1_AISPI 0x28 +#define LOLA_BAR1_GRAN 0x2c + +#define LOLA_BAR1_DINTCTL 0x80 +#define LOLA_BAR1_DIINTCTL 0x84 +#define LOLA_BAR1_DOINTCTL 0x88 +#define LOLA_BAR1_LRC 0x90 +#define LOLA_BAR1_DINTSTS 0x94 +#define LOLA_BAR1_DIINTSTS 0x98 +#define LOLA_BAR1_DOINTSTS 0x9c + +#define LOLA_BAR1_DSD0_OFFSET 0xa0 +#define LOLA_BAR1_DSD_SIZE 0x18 + +#define LOLA_BAR1_DSDnSTS 0x00 +#define LOLA_BAR1_DSDnLPIB 0x04 +#define LOLA_BAR1_DSDnCTL 0x08 +#define LOLA_BAR1_DSDnLVI 0x0c +#define LOLA_BAR1_DSDnBDPL 0x10 +#define LOLA_BAR1_DSDnBDPU 0x14 + +#define LOLA_BAR1_SSYNC 0x03e8 + +#define LOLA_BAR1_BOARD_CTRL 0x0f00 +#define LOLA_BAR1_BOARD_MODE 0x0f02 + +#define LOLA_BAR1_SOURCE_GAIN_ENABLE 0x1000 +#define LOLA_BAR1_DEST00_MIX_GAIN_ENABLE 0x1004 +#define LOLA_BAR1_DEST31_MIX_GAIN_ENABLE 0x1080 +#define LOLA_BAR1_SOURCE00_01_GAIN 0x1084 +#define LOLA_BAR1_SOURCE30_31_GAIN 0x10c0 +#define LOLA_BAR1_SOURCE_GAIN(src) \ + (LOLA_BAR1_SOURCE00_01_GAIN + (src) * 2) +#define LOLA_BAR1_DEST00_MIX00_01_GAIN 0x10c4 +#define LOLA_BAR1_DEST00_MIX30_31_GAIN 0x1100 +#define LOLA_BAR1_DEST01_MIX00_01_GAIN 0x1104 +#define LOLA_BAR1_DEST01_MIX30_31_GAIN 0x1140 +#define LOLA_BAR1_DEST31_MIX00_01_GAIN 0x1884 +#define LOLA_BAR1_DEST31_MIX30_31_GAIN 0x18c0 +#define LOLA_BAR1_MIX_GAIN(dest, mix) \ + (LOLA_BAR1_DEST00_MIX00_01_GAIN + (dest) * 0x40 + (mix) * 2) +#define LOLA_BAR1_ANALOG_CLIP_IN 0x18c4 +#define LOLA_BAR1_PEAKMETERS_SOURCE00_01 0x18c8 +#define LOLA_BAR1_PEAKMETERS_SOURCE30_31 0x1904 +#define LOLA_BAR1_PEAKMETERS_SOURCE(src) \ + (LOLA_BAR1_PEAKMETERS_SOURCE00_01 + (src) * 2) +#define LOLA_BAR1_PEAKMETERS_DEST00_01 0x1908 +#define LOLA_BAR1_PEAKMETERS_DEST30_31 0x1944 +#define LOLA_BAR1_PEAKMETERS_DEST(dest) \ + (LOLA_BAR1_PEAKMETERS_DEST00_01 + (dest) * 2) +#define LOLA_BAR1_PEAKMETERS_AGC00_01 0x1948 +#define LOLA_BAR1_PEAKMETERS_AGC14_15 0x1964 +#define LOLA_BAR1_PEAKMETERS_AGC(x) \ + (LOLA_BAR1_PEAKMETERS_AGC00_01 + (x) * 2) + +/* GCTL reset bit */ +#define LOLA_GCTL_RESET (1 << 0) +/* GCTL unsolicited response enable bit */ +#define LOLA_GCTL_UREN (1 << 8) + +/* CORB/RIRB control, read/write pointer */ +#define LOLA_RBCTL_DMA_EN 0x02 /* enable DMA */ +#define LOLA_RBCTL_IRQ_EN 0x01 /* enable IRQ */ +#define LOLA_RBRWP_CLR 0x8000 /* read/write pointer clear */ + +#define LOLA_RIRB_EX_UNSOL_EV 0x40000000 +#define LOLA_RIRB_EX_ERROR 0x80000000 + +/* CORB int mask: CMEI[0] */ +#define LOLA_CORB_INT_CMEI 0x01 +#define LOLA_CORB_INT_MASK LOLA_CORB_INT_CMEI + +/* RIRB int mask: overrun[2], response[0] */ +#define LOLA_RIRB_INT_RESPONSE 0x01 +#define LOLA_RIRB_INT_OVERRUN 0x04 +#define LOLA_RIRB_INT_MASK (LOLA_RIRB_INT_RESPONSE | LOLA_RIRB_INT_OVERRUN) + +/* DINTCTL and DINTSTS */ +#define LOLA_DINT_GLOBAL 0x80000000 /* global interrupt enable bit */ +#define LOLA_DINT_CTRL 0x40000000 /* controller interrupt enable bit */ +#define LOLA_DINT_FIFOERR 0x20000000 /* global fifo error enable bit */ +#define LOLA_DINT_MUERR 0x10000000 /* global microcontroller underrun error */ + +/* DSDnCTL bits */ +#define LOLA_DSD_CTL_SRST 0x01 /* stream reset bit */ +#define LOLA_DSD_CTL_SRUN 0x02 /* stream DMA start bit */ +#define LOLA_DSD_CTL_IOCE 0x04 /* interrupt on completion enable */ +#define LOLA_DSD_CTL_DEIE 0x10 /* descriptor error interrupt enable */ +#define LOLA_DSD_CTL_VLRCV 0x20 /* valid LRCountValue information in bits 8..31 */ +#define LOLA_LRC_MASK 0xffffff00 + +/* DSDnSTS */ +#define LOLA_DSD_STS_BCIS 0x04 /* buffer completion interrupt status */ +#define LOLA_DSD_STS_DESE 0x10 /* descriptor error interrupt */ +#define LOLA_DSD_STS_FIFORDY 0x20 /* fifo ready */ + +#define LOLA_CORB_ENTRIES 256 + +#define MAX_STREAM_IN_COUNT 16 +#define MAX_STREAM_OUT_COUNT 16 +#define MAX_STREAM_COUNT 16 +#define MAX_PINS MAX_STREAM_COUNT +#define MAX_STREAM_BUFFER_COUNT 16 +#define MAX_AUDIO_INOUT_COUNT 16 + +#define LOLA_CLOCK_TYPE_INTERNAL 0 +#define LOLA_CLOCK_TYPE_AES 1 +#define LOLA_CLOCK_TYPE_AES_SYNC 2 +#define LOLA_CLOCK_TYPE_WORDCLOCK 3 +#define LOLA_CLOCK_TYPE_ETHERSOUND 4 +#define LOLA_CLOCK_TYPE_VIDEO 5 + +#define LOLA_CLOCK_FORMAT_NONE 0 +#define LOLA_CLOCK_FORMAT_NTSC 1 +#define LOLA_CLOCK_FORMAT_PAL 2 + +#define MAX_SAMPLE_CLOCK_COUNT 48 + +/* parameters used with mixer widget's mixer capabilities */ +#define LOLA_PEAK_METER_CAN_AGC_MASK 1 +#define LOLA_PEAK_METER_CAN_ANALOG_CLIP_MASK 2 + +struct lola_bar { + unsigned long addr; + void __iomem *remap_addr; +}; + +/* CORB/RIRB */ +struct lola_rb { + __le32 *buf; /* CORB/RIRB buffer, 8 byte per each entry */ + dma_addr_t addr; /* physical address of CORB/RIRB buffer */ + unsigned short rp, wp; /* read/write pointers */ + int cmds; /* number of pending requests */ +}; + +/* Pin widget setup */ +struct lola_pin { + unsigned int nid; + bool is_analog; + unsigned int amp_mute; + unsigned int amp_step_size; + unsigned int amp_num_steps; + unsigned int amp_offset; + unsigned int max_level; + unsigned int config_default_reg; + unsigned int fixed_gain_list_len; + unsigned int cur_gain_step; +}; + +struct lola_pin_array { + unsigned int num_pins; + unsigned int num_analog_pins; + struct lola_pin pins[MAX_PINS]; +}; + +/* Clock widget setup */ +struct lola_sample_clock { + unsigned int type; + unsigned int format; + unsigned int freq; +}; + +struct lola_clock_widget { + unsigned int nid; + unsigned int items; + unsigned int cur_index; + unsigned int cur_freq; + bool cur_valid; + struct lola_sample_clock sample_clock[MAX_SAMPLE_CLOCK_COUNT]; + unsigned int idx_lookup[MAX_SAMPLE_CLOCK_COUNT]; +}; + +#define LOLA_MIXER_DIM 32 +struct lola_mixer_array { + u32 src_gain_enable; + u32 dest_mix_gain_enable[LOLA_MIXER_DIM]; + u16 src_gain[LOLA_MIXER_DIM]; + u16 dest_mix_gain[LOLA_MIXER_DIM][LOLA_MIXER_DIM]; +}; + +/* Mixer widget setup */ +struct lola_mixer_widget { + unsigned int nid; + unsigned int caps; + struct lola_mixer_array __iomem *array; + struct lola_mixer_array *array_saved; + unsigned int src_stream_outs; + unsigned int src_phys_ins; + unsigned int dest_stream_ins; + unsigned int dest_phys_outs; + unsigned int src_stream_out_ofs; + unsigned int dest_phys_out_ofs; + unsigned int src_mask; + unsigned int dest_mask; +}; + +/* Audio stream */ +struct lola_stream { + unsigned int nid; /* audio widget NID */ + unsigned int index; /* array index */ + unsigned int dsd; /* DSD index */ + bool can_float; + struct snd_pcm_substream *substream; /* assigned PCM substream */ + struct lola_stream *master; /* master stream (for multi-channel) */ + + /* buffer setup */ + unsigned int bufsize; + unsigned int period_bytes; + unsigned int frags; + + /* format + channel setup */ + unsigned int format_verb; + + /* flags */ + unsigned int opened:1; + unsigned int prepared:1; + unsigned int paused:1; + unsigned int running:1; +}; + +#define PLAY SNDRV_PCM_STREAM_PLAYBACK +#define CAPT SNDRV_PCM_STREAM_CAPTURE + +struct lola_pcm { + unsigned int num_streams; + struct snd_dma_buffer *bdl; /* BDL buffer */ + struct lola_stream streams[MAX_STREAM_COUNT]; +}; + +/* card instance */ +struct lola { + struct snd_card *card; + struct pci_dev *pci; + + /* pci resources */ + struct lola_bar bar[2]; + int irq; + + /* locks */ + spinlock_t reg_lock; + struct mutex open_mutex; + + /* CORB/RIRB */ + struct lola_rb corb; + struct lola_rb rirb; + unsigned int res, res_ex; /* last read values */ + /* last command (for debugging) */ + unsigned int last_cmd_nid, last_verb, last_data, last_extdata; + + /* CORB/RIRB buffers */ + struct snd_dma_buffer *rb; + + /* unsolicited events */ + unsigned int last_unsol_res; + + /* streams */ + struct lola_pcm pcm[2]; + + /* input src */ + unsigned int input_src_caps_mask; + unsigned int input_src_mask; + + /* pins */ + struct lola_pin_array pin[2]; + + /* clock */ + struct lola_clock_widget clock; + int ref_count_rate; + unsigned int sample_rate; + + /* mixer */ + struct lola_mixer_widget mixer; + + /* hw info */ + unsigned int version; + unsigned int lola_caps; + + /* parameters */ + unsigned int granularity; + unsigned int sample_rate_min; + unsigned int sample_rate_max; + + /* flags */ + unsigned int initialized:1; + unsigned int cold_reset:1; + unsigned int polling_mode:1; + + /* for debugging */ + unsigned int debug_res; + unsigned int debug_res_ex; +}; + +#define BAR0 0 +#define BAR1 1 + +/* Helper macros */ +#define lola_readl(chip, idx, name) \ + readl((chip)->bar[idx].remap_addr + LOLA_##idx##_##name) +#define lola_readw(chip, idx, name) \ + readw((chip)->bar[idx].remap_addr + LOLA_##idx##_##name) +#define lola_readb(chip, idx, name) \ + readb((chip)->bar[idx].remap_addr + LOLA_##idx##_##name) +#define lola_writel(chip, idx, name, val) \ + writel((val), (chip)->bar[idx].remap_addr + LOLA_##idx##_##name) +#define lola_writew(chip, idx, name, val) \ + writew((val), (chip)->bar[idx].remap_addr + LOLA_##idx##_##name) +#define lola_writeb(chip, idx, name, val) \ + writeb((val), (chip)->bar[idx].remap_addr + LOLA_##idx##_##name) + +#define lola_dsd_read(chip, dsd, name) \ + readl((chip)->bar[BAR1].remap_addr + LOLA_BAR1_DSD0_OFFSET + \ + (LOLA_BAR1_DSD_SIZE * (dsd)) + LOLA_BAR1_DSDn##name) +#define lola_dsd_write(chip, dsd, name, val) \ + writel((val), (chip)->bar[BAR1].remap_addr + LOLA_BAR1_DSD0_OFFSET + \ + (LOLA_BAR1_DSD_SIZE * (dsd)) + LOLA_BAR1_DSDn##name) + +/* GET verbs HDAudio */ +#define LOLA_VERB_GET_STREAM_FORMAT 0xa00 +#define LOLA_VERB_GET_AMP_GAIN_MUTE 0xb00 +#define LOLA_VERB_PARAMETERS 0xf00 +#define LOLA_VERB_GET_POWER_STATE 0xf05 +#define LOLA_VERB_GET_CONV 0xf06 +#define LOLA_VERB_GET_UNSOLICITED_RESPONSE 0xf08 +#define LOLA_VERB_GET_DIGI_CONVERT_1 0xf0d +#define LOLA_VERB_GET_CONFIG_DEFAULT 0xf1c +#define LOLA_VERB_GET_SUBSYSTEM_ID 0xf20 +/* GET verbs Digigram */ +#define LOLA_VERB_GET_FIXED_GAIN 0xfc0 +#define LOLA_VERB_GET_GAIN_SELECT 0xfc1 +#define LOLA_VERB_GET_MAX_LEVEL 0xfc2 +#define LOLA_VERB_GET_CLOCK_LIST 0xfc3 +#define LOLA_VERB_GET_CLOCK_SELECT 0xfc4 +#define LOLA_VERB_GET_CLOCK_STATUS 0xfc5 + +/* SET verbs HDAudio */ +#define LOLA_VERB_SET_STREAM_FORMAT 0x200 +#define LOLA_VERB_SET_AMP_GAIN_MUTE 0x300 +#define LOLA_VERB_SET_POWER_STATE 0x705 +#define LOLA_VERB_SET_CHANNEL_STREAMID 0x706 +#define LOLA_VERB_SET_UNSOLICITED_ENABLE 0x708 +#define LOLA_VERB_SET_DIGI_CONVERT_1 0x70d +/* SET verbs Digigram */ +#define LOLA_VERB_SET_GAIN_SELECT 0xf81 +#define LOLA_VERB_SET_CLOCK_SELECT 0xf84 +#define LOLA_VERB_SET_GRANULARITY_STEPS 0xf86 +#define LOLA_VERB_SET_SOURCE_GAIN 0xf87 +#define LOLA_VERB_SET_MIX_GAIN 0xf88 +#define LOLA_VERB_SET_DESTINATION_GAIN 0xf89 +#define LOLA_VERB_SET_SRC 0xf8a + +/* Parameter IDs used with LOLA_VERB_PARAMETERS */ +#define LOLA_PAR_VENDOR_ID 0x00 +#define LOLA_PAR_FUNCTION_TYPE 0x05 +#define LOLA_PAR_AUDIO_WIDGET_CAP 0x09 +#define LOLA_PAR_PCM 0x0a +#define LOLA_PAR_STREAM_FORMATS 0x0b +#define LOLA_PAR_PIN_CAP 0x0c +#define LOLA_PAR_AMP_IN_CAP 0x0d +#define LOLA_PAR_CONNLIST_LEN 0x0e +#define LOLA_PAR_POWER_STATE 0x0f +#define LOLA_PAR_GPIO_CAP 0x11 +#define LOLA_PAR_AMP_OUT_CAP 0x12 +#define LOLA_PAR_SPECIFIC_CAPS 0x80 +#define LOLA_PAR_FIXED_GAIN_LIST 0x81 + +/* extract results of LOLA_PAR_SPECIFIC_CAPS */ +#define LOLA_AFG_MIXER_WIDGET_PRESENT(res) ((res & (1 << 21)) != 0) +#define LOLA_AFG_CLOCK_WIDGET_PRESENT(res) ((res & (1 << 20)) != 0) +#define LOLA_AFG_INPUT_PIN_COUNT(res) ((res >> 10) & 0x2ff) +#define LOLA_AFG_OUTPUT_PIN_COUNT(res) ((res) & 0x2ff) + +/* extract results of LOLA_PAR_AMP_IN_CAP / LOLA_PAR_AMP_OUT_CAP */ +#define LOLA_AMP_MUTE_CAPABLE(res) ((res & (1 << 31)) != 0) +#define LOLA_AMP_STEP_SIZE(res) ((res >> 24) & 0x7f) +#define LOLA_AMP_NUM_STEPS(res) ((res >> 12) & 0x3ff) +#define LOLA_AMP_OFFSET(res) ((res) & 0x3ff) + +#define LOLA_GRANULARITY_MIN 8 +#define LOLA_GRANULARITY_MAX 32 +#define LOLA_GRANULARITY_STEP 8 + +/* parameters used with unsolicited command/response */ +#define LOLA_UNSOLICITED_TAG_MASK 0x3f +#define LOLA_UNSOLICITED_TAG 0x1a +#define LOLA_UNSOLICITED_ENABLE 0x80 +#define LOLA_UNSOL_RESP_TAG_OFFSET 26 + +/* count values in the Vendor Specific Mixer Widget's Audio Widget Capabilities */ +#define LOLA_MIXER_SRC_INPUT_PLAY_SEPARATION(res) ((res >> 2) & 0x1f) +#define LOLA_MIXER_DEST_REC_OUTPUT_SEPARATION(res) ((res >> 7) & 0x1f) + +int lola_codec_write(struct lola *chip, unsigned int nid, unsigned int verb, + unsigned int data, unsigned int extdata); +int lola_codec_read(struct lola *chip, unsigned int nid, unsigned int verb, + unsigned int data, unsigned int extdata, + unsigned int *val, unsigned int *extval); +int lola_codec_flush(struct lola *chip); +#define lola_read_param(chip, nid, param, val) \ + lola_codec_read(chip, nid, LOLA_VERB_PARAMETERS, param, 0, val, NULL) + +/* PCM */ +int lola_create_pcm(struct lola *chip); +int lola_init_pcm(struct lola *chip, int dir, int *nidp); +void lola_pcm_update(struct lola *chip, struct lola_pcm *pcm, unsigned int bits); + +/* clock */ +int lola_init_clock_widget(struct lola *chip, int nid); +int lola_set_granularity(struct lola *chip, unsigned int val, bool force); +int lola_enable_clock_events(struct lola *chip); +int lola_set_clock_index(struct lola *chip, unsigned int idx); +int lola_set_clock(struct lola *chip, int idx); +int lola_set_sample_rate(struct lola *chip, int rate); +bool lola_update_ext_clock_freq(struct lola *chip, unsigned int val); +unsigned int lola_sample_rate_convert(unsigned int coded); + +/* mixer */ +int lola_init_pins(struct lola *chip, int dir, int *nidp); +int lola_init_mixer_widget(struct lola *chip, int nid); +void lola_free_mixer(struct lola *chip); +int lola_create_mixer(struct lola *chip); +int lola_setup_all_analog_gains(struct lola *chip, int dir, bool mute); +int lola_set_src_config(struct lola *chip, unsigned int src_mask, bool update); + +/* proc */ +#ifdef CONFIG_SND_DEBUG +void lola_proc_debug_new(struct lola *chip); +#else +#define lola_proc_debug_new(chip) +#endif + +#endif /* _LOLA_H */ diff --git a/sound/pci/lola/lola_clock.c b/sound/pci/lola/lola_clock.c new file mode 100644 index 000000000..2e73fbf33 --- /dev/null +++ b/sound/pci/lola/lola_clock.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include "lola.h" + +unsigned int lola_sample_rate_convert(unsigned int coded) +{ + unsigned int freq; + + /* base frequency */ + switch (coded & 0x3) { + case 0: freq = 48000; break; + case 1: freq = 44100; break; + case 2: freq = 32000; break; + default: return 0; /* error */ + } + + /* multiplier / devisor */ + switch (coded & 0x1c) { + case (0 << 2): break; + case (4 << 2): break; + case (1 << 2): freq *= 2; break; + case (2 << 2): freq *= 4; break; + case (5 << 2): freq /= 2; break; + case (6 << 2): freq /= 4; break; + default: return 0; /* error */ + } + + /* adjustement */ + switch (coded & 0x60) { + case (0 << 5): break; + case (1 << 5): freq = (freq * 999) / 1000; break; + case (2 << 5): freq = (freq * 1001) / 1000; break; + default: return 0; /* error */ + } + return freq; +} + +/* + * Granualrity + */ + +#define LOLA_MAXFREQ_AT_GRANULARITY_MIN 48000 +#define LOLA_MAXFREQ_AT_GRANULARITY_BELOW_MAX 96000 + +static bool check_gran_clock_compatibility(struct lola *chip, + unsigned int val, + unsigned int freq) +{ + if (!chip->granularity) + return true; + + if (val < LOLA_GRANULARITY_MIN || val > LOLA_GRANULARITY_MAX || + (val % LOLA_GRANULARITY_STEP) != 0) + return false; + + if (val == LOLA_GRANULARITY_MIN) { + if (freq > LOLA_MAXFREQ_AT_GRANULARITY_MIN) + return false; + } else if (val < LOLA_GRANULARITY_MAX) { + if (freq > LOLA_MAXFREQ_AT_GRANULARITY_BELOW_MAX) + return false; + } + return true; +} + +int lola_set_granularity(struct lola *chip, unsigned int val, bool force) +{ + int err; + + if (!force) { + if (val == chip->granularity) + return 0; +#if 0 + /* change Gran only if there are no streams allocated ! */ + if (chip->audio_in_alloc_mask || chip->audio_out_alloc_mask) + return -EBUSY; +#endif + if (!check_gran_clock_compatibility(chip, val, + chip->clock.cur_freq)) + return -EINVAL; + } + + chip->granularity = val; + val /= LOLA_GRANULARITY_STEP; + + /* audio function group */ + err = lola_codec_write(chip, 1, LOLA_VERB_SET_GRANULARITY_STEPS, + val, 0); + if (err < 0) + return err; + /* this can be a very slow function !!! */ + usleep_range(400 * val, 20000); + return lola_codec_flush(chip); +} + +/* + * Clock widget handling + */ + +int lola_init_clock_widget(struct lola *chip, int nid) +{ + unsigned int val; + int i, j, nitems, nb_verbs, idx, idx_list; + int err; + + err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read wcaps for 0x%x\n", nid); + return err; + } + + if ((val & 0xfff00000) != 0x01f00000) { /* test SubType and Type */ + dev_dbg(chip->card->dev, "No valid clock widget\n"); + return 0; + } + + chip->clock.nid = nid; + chip->clock.items = val & 0xff; + dev_dbg(chip->card->dev, "clock_list nid=%x, entries=%d\n", nid, + chip->clock.items); + if (chip->clock.items > MAX_SAMPLE_CLOCK_COUNT) { + dev_err(chip->card->dev, "CLOCK_LIST too big: %d\n", + chip->clock.items); + return -EINVAL; + } + + nitems = chip->clock.items; + nb_verbs = DIV_ROUND_UP(nitems, 4); + idx = 0; + idx_list = 0; + for (i = 0; i < nb_verbs; i++) { + unsigned int res_ex; + unsigned short items[4]; + + err = lola_codec_read(chip, nid, LOLA_VERB_GET_CLOCK_LIST, + idx, 0, &val, &res_ex); + if (err < 0) { + dev_err(chip->card->dev, "Can't read CLOCK_LIST\n"); + return -EINVAL; + } + + items[0] = val & 0xfff; + items[1] = (val >> 16) & 0xfff; + items[2] = res_ex & 0xfff; + items[3] = (res_ex >> 16) & 0xfff; + + for (j = 0; j < 4; j++) { + unsigned char type = items[j] >> 8; + unsigned int freq = items[j] & 0xff; + int format = LOLA_CLOCK_FORMAT_NONE; + bool add_clock = true; + if (type == LOLA_CLOCK_TYPE_INTERNAL) { + freq = lola_sample_rate_convert(freq); + if (freq < chip->sample_rate_min) + add_clock = false; + else if (freq == 48000) { + chip->clock.cur_index = idx_list; + chip->clock.cur_freq = 48000; + chip->clock.cur_valid = true; + } + } else if (type == LOLA_CLOCK_TYPE_VIDEO) { + freq = lola_sample_rate_convert(freq); + if (freq < chip->sample_rate_min) + add_clock = false; + /* video clock has a format (0:NTSC, 1:PAL)*/ + if (items[j] & 0x80) + format = LOLA_CLOCK_FORMAT_NTSC; + else + format = LOLA_CLOCK_FORMAT_PAL; + } + if (add_clock) { + struct lola_sample_clock *sc; + sc = &chip->clock.sample_clock[idx_list]; + sc->type = type; + sc->format = format; + sc->freq = freq; + /* keep the index used with the board */ + chip->clock.idx_lookup[idx_list] = idx; + idx_list++; + } else { + chip->clock.items--; + } + if (++idx >= nitems) + break; + } + } + return 0; +} + +/* enable unsolicited events of the clock widget */ +int lola_enable_clock_events(struct lola *chip) +{ + unsigned int res; + int err; + + err = lola_codec_read(chip, chip->clock.nid, + LOLA_VERB_SET_UNSOLICITED_ENABLE, + LOLA_UNSOLICITED_ENABLE | LOLA_UNSOLICITED_TAG, + 0, &res, NULL); + if (err < 0) + return err; + if (res) { + dev_warn(chip->card->dev, "error in enable_clock_events %d\n", + res); + return -EINVAL; + } + return 0; +} + +int lola_set_clock_index(struct lola *chip, unsigned int idx) +{ + unsigned int res; + int err; + + err = lola_codec_read(chip, chip->clock.nid, + LOLA_VERB_SET_CLOCK_SELECT, + chip->clock.idx_lookup[idx], + 0, &res, NULL); + if (err < 0) + return err; + if (res) { + dev_warn(chip->card->dev, "error in set_clock %d\n", res); + return -EINVAL; + } + return 0; +} + +bool lola_update_ext_clock_freq(struct lola *chip, unsigned int val) +{ + unsigned int tag; + + /* the current EXTERNAL clock information gets updated by interrupt + * with an unsolicited response + */ + if (!val) + return false; + tag = (val >> LOLA_UNSOL_RESP_TAG_OFFSET) & LOLA_UNSOLICITED_TAG_MASK; + if (tag != LOLA_UNSOLICITED_TAG) + return false; + + /* only for current = external clocks */ + if (chip->clock.sample_clock[chip->clock.cur_index].type != + LOLA_CLOCK_TYPE_INTERNAL) { + chip->clock.cur_freq = lola_sample_rate_convert(val & 0x7f); + chip->clock.cur_valid = (val & 0x100) != 0; + } + return true; +} + +int lola_set_clock(struct lola *chip, int idx) +{ + int freq = 0; + bool valid = false; + + if (idx == chip->clock.cur_index) { + /* current clock is allowed */ + freq = chip->clock.cur_freq; + valid = chip->clock.cur_valid; + } else if (chip->clock.sample_clock[idx].type == + LOLA_CLOCK_TYPE_INTERNAL) { + /* internal clocks allowed */ + freq = chip->clock.sample_clock[idx].freq; + valid = true; + } + + if (!freq || !valid) + return -EINVAL; + + if (!check_gran_clock_compatibility(chip, chip->granularity, freq)) + return -EINVAL; + + if (idx != chip->clock.cur_index) { + int err = lola_set_clock_index(chip, idx); + if (err < 0) + return err; + /* update new settings */ + chip->clock.cur_index = idx; + chip->clock.cur_freq = freq; + chip->clock.cur_valid = true; + } + return 0; +} + +int lola_set_sample_rate(struct lola *chip, int rate) +{ + int i; + + if (chip->clock.cur_freq == rate && chip->clock.cur_valid) + return 0; + /* search for new dwClockIndex */ + for (i = 0; i < chip->clock.items; i++) { + if (chip->clock.sample_clock[i].type == LOLA_CLOCK_TYPE_INTERNAL && + chip->clock.sample_clock[i].freq == rate) + break; + } + if (i >= chip->clock.items) + return -EINVAL; + return lola_set_clock(chip, i); +} + diff --git a/sound/pci/lola/lola_mixer.c b/sound/pci/lola/lola_mixer.c new file mode 100644 index 000000000..9cb26a8a4 --- /dev/null +++ b/sound/pci/lola/lola_mixer.c @@ -0,0 +1,842 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "lola.h" + +static int lola_init_pin(struct lola *chip, struct lola_pin *pin, + int dir, int nid) +{ + unsigned int val; + int err; + + pin->nid = nid; + err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read wcaps for 0x%x\n", nid); + return err; + } + val &= 0x00f00fff; /* test TYPE and bits 0..11 */ + if (val == 0x00400200) /* Type = 4, Digital = 1 */ + pin->is_analog = false; + else if (val == 0x0040000a && dir == CAPT) /* Dig=0, InAmp/ovrd */ + pin->is_analog = true; + else if (val == 0x0040000c && dir == PLAY) /* Dig=0, OutAmp/ovrd */ + pin->is_analog = true; + else { + dev_err(chip->card->dev, "Invalid wcaps 0x%x for 0x%x\n", val, nid); + return -EINVAL; + } + + /* analog parameters only following, so continue in case of Digital pin + */ + if (!pin->is_analog) + return 0; + + if (dir == PLAY) + err = lola_read_param(chip, nid, LOLA_PAR_AMP_OUT_CAP, &val); + else + err = lola_read_param(chip, nid, LOLA_PAR_AMP_IN_CAP, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read AMP-caps for 0x%x\n", nid); + return err; + } + + pin->amp_mute = LOLA_AMP_MUTE_CAPABLE(val); + pin->amp_step_size = LOLA_AMP_STEP_SIZE(val); + pin->amp_num_steps = LOLA_AMP_NUM_STEPS(val); + if (pin->amp_num_steps) { + /* zero as mute state */ + pin->amp_num_steps++; + pin->amp_step_size++; + } + pin->amp_offset = LOLA_AMP_OFFSET(val); + + err = lola_codec_read(chip, nid, LOLA_VERB_GET_MAX_LEVEL, 0, 0, &val, + NULL); + if (err < 0) { + dev_err(chip->card->dev, "Can't get MAX_LEVEL 0x%x\n", nid); + return err; + } + pin->max_level = val & 0x3ff; /* 10 bits */ + + pin->config_default_reg = 0; + pin->fixed_gain_list_len = 0; + pin->cur_gain_step = 0; + + return 0; +} + +int lola_init_pins(struct lola *chip, int dir, int *nidp) +{ + int i, err, nid; + nid = *nidp; + for (i = 0; i < chip->pin[dir].num_pins; i++, nid++) { + err = lola_init_pin(chip, &chip->pin[dir].pins[i], dir, nid); + if (err < 0) + return err; + if (chip->pin[dir].pins[i].is_analog) + chip->pin[dir].num_analog_pins++; + } + *nidp = nid; + return 0; +} + +void lola_free_mixer(struct lola *chip) +{ + vfree(chip->mixer.array_saved); +} + +int lola_init_mixer_widget(struct lola *chip, int nid) +{ + unsigned int val; + int err; + + err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read wcaps for 0x%x\n", nid); + return err; + } + + if ((val & 0xfff00000) != 0x02f00000) { /* test SubType and Type */ + dev_dbg(chip->card->dev, "No valid mixer widget\n"); + return 0; + } + + chip->mixer.nid = nid; + chip->mixer.caps = val; + chip->mixer.array = (struct lola_mixer_array __iomem *) + (chip->bar[BAR1].remap_addr + LOLA_BAR1_SOURCE_GAIN_ENABLE); + + /* reserve memory to copy mixer data for sleep mode transitions */ + chip->mixer.array_saved = vmalloc(sizeof(struct lola_mixer_array)); + if (!chip->mixer.array_saved) + return -ENOMEM; + + /* mixer matrix sources are physical input data and play streams */ + chip->mixer.src_stream_outs = chip->pcm[PLAY].num_streams; + chip->mixer.src_phys_ins = chip->pin[CAPT].num_pins; + + /* mixer matrix destinations are record streams and physical output */ + chip->mixer.dest_stream_ins = chip->pcm[CAPT].num_streams; + chip->mixer.dest_phys_outs = chip->pin[PLAY].num_pins; + + /* mixer matrix may have unused areas between PhysIn and + * Play or Record and PhysOut zones + */ + chip->mixer.src_stream_out_ofs = chip->mixer.src_phys_ins + + LOLA_MIXER_SRC_INPUT_PLAY_SEPARATION(val); + chip->mixer.dest_phys_out_ofs = chip->mixer.dest_stream_ins + + LOLA_MIXER_DEST_REC_OUTPUT_SEPARATION(val); + + /* example : MixerMatrix of LoLa881 (LoLa16161 uses unused zones) + * +-+ 0-------8------16-------8------16 + * | | | | | | | + * |s| | INPUT | | INPUT | | + * | |->| -> |unused | -> |unused | + * |r| |CAPTURE| | OUTPUT| | + * | | | MIX | | MIX | | + * |c| 8-------------------------------- + * | | | | | | | + * | | | | | | | + * |g| |unused |unused |unused |unused | + * | | | | | | | + * |a| | | | | | + * | | 16------------------------------- + * |i| | | | | | + * | | | PLAYBK| | PLAYBK| | + * |n|->| -> |unused | -> |unused | + * | | |CAPTURE| | OUTPUT| | + * | | | MIX | | MIX | | + * |a| 8-------------------------------- + * |r| | | | | | + * |r| | | | | | + * |a| |unused |unused |unused |unused | + * |y| | | | | | + * | | | | | | | + * +++ 16--|---------------|------------ + * +---V---------------V-----------+ + * | dest_mix_gain_enable array | + * +-------------------------------+ + */ + /* example : MixerMatrix of LoLa280 + * +-+ 0-------8-2 + * | | | | | + * |s| | INPUT | | INPUT + * |r|->| -> | | -> + * |c| |CAPTURE| | <- OUTPUT + * | | | MIX | | MIX + * |g| 8---------- + * |a| | | | + * |i| | PLAYBK| | PLAYBACK + * |n|->| -> | | -> + * | | |CAPTURE| | <- OUTPUT + * |a| | MIX | | MIX + * |r| 8---|----|- + * |r| +---V----V-------------------+ + * |a| | dest_mix_gain_enable array | + * |y| +----------------------------+ + */ + if (chip->mixer.src_stream_out_ofs > MAX_AUDIO_INOUT_COUNT || + chip->mixer.dest_phys_out_ofs > MAX_STREAM_IN_COUNT) { + dev_err(chip->card->dev, "Invalid mixer widget size\n"); + return -EINVAL; + } + + chip->mixer.src_mask = ((1U << chip->mixer.src_phys_ins) - 1) | + (((1U << chip->mixer.src_stream_outs) - 1) + << chip->mixer.src_stream_out_ofs); + chip->mixer.dest_mask = ((1U << chip->mixer.dest_stream_ins) - 1) | + (((1U << chip->mixer.dest_phys_outs) - 1) + << chip->mixer.dest_phys_out_ofs); + + dev_dbg(chip->card->dev, "Mixer src_mask=%x, dest_mask=%x\n", + chip->mixer.src_mask, chip->mixer.dest_mask); + + return 0; +} + +static int lola_mixer_set_src_gain(struct lola *chip, unsigned int id, + unsigned short gain, bool on) +{ + unsigned int oldval, val; + + if (!(chip->mixer.src_mask & (1 << id))) + return -EINVAL; + oldval = val = readl(&chip->mixer.array->src_gain_enable); + if (on) + val |= (1 << id); + else + val &= ~(1 << id); + /* test if values unchanged */ + if ((val == oldval) && + (gain == readw(&chip->mixer.array->src_gain[id]))) + return 0; + + dev_dbg(chip->card->dev, + "lola_mixer_set_src_gain (id=%d, gain=%d) enable=%x\n", + id, gain, val); + writew(gain, &chip->mixer.array->src_gain[id]); + writel(val, &chip->mixer.array->src_gain_enable); + lola_codec_flush(chip); + /* inform micro-controller about the new source gain */ + return lola_codec_write(chip, chip->mixer.nid, + LOLA_VERB_SET_SOURCE_GAIN, id, 0); +} + +#if 0 /* not used */ +static int lola_mixer_set_src_gains(struct lola *chip, unsigned int mask, + unsigned short *gains) +{ + int i; + + if ((chip->mixer.src_mask & mask) != mask) + return -EINVAL; + for (i = 0; i < LOLA_MIXER_DIM; i++) { + if (mask & (1 << i)) { + writew(*gains, &chip->mixer.array->src_gain[i]); + gains++; + } + } + writel(mask, &chip->mixer.array->src_gain_enable); + lola_codec_flush(chip); + if (chip->mixer.caps & LOLA_PEAK_METER_CAN_AGC_MASK) { + /* update for all srcs at once */ + return lola_codec_write(chip, chip->mixer.nid, + LOLA_VERB_SET_SOURCE_GAIN, 0x80, 0); + } + /* update manually */ + for (i = 0; i < LOLA_MIXER_DIM; i++) { + if (mask & (1 << i)) { + lola_codec_write(chip, chip->mixer.nid, + LOLA_VERB_SET_SOURCE_GAIN, i, 0); + } + } + return 0; +} +#endif /* not used */ + +static int lola_mixer_set_mapping_gain(struct lola *chip, + unsigned int src, unsigned int dest, + unsigned short gain, bool on) +{ + unsigned int val; + + if (!(chip->mixer.src_mask & (1 << src)) || + !(chip->mixer.dest_mask & (1 << dest))) + return -EINVAL; + if (on) + writew(gain, &chip->mixer.array->dest_mix_gain[dest][src]); + val = readl(&chip->mixer.array->dest_mix_gain_enable[dest]); + if (on) + val |= (1 << src); + else + val &= ~(1 << src); + writel(val, &chip->mixer.array->dest_mix_gain_enable[dest]); + lola_codec_flush(chip); + return lola_codec_write(chip, chip->mixer.nid, LOLA_VERB_SET_MIX_GAIN, + src, dest); +} + +#if 0 /* not used */ +static int lola_mixer_set_dest_gains(struct lola *chip, unsigned int id, + unsigned int mask, unsigned short *gains) +{ + int i; + + if (!(chip->mixer.dest_mask & (1 << id)) || + (chip->mixer.src_mask & mask) != mask) + return -EINVAL; + for (i = 0; i < LOLA_MIXER_DIM; i++) { + if (mask & (1 << i)) { + writew(*gains, &chip->mixer.array->dest_mix_gain[id][i]); + gains++; + } + } + writel(mask, &chip->mixer.array->dest_mix_gain_enable[id]); + lola_codec_flush(chip); + /* update for all dests at once */ + return lola_codec_write(chip, chip->mixer.nid, + LOLA_VERB_SET_DESTINATION_GAIN, id, 0); +} +#endif /* not used */ + +/* + */ + +static int set_analog_volume(struct lola *chip, int dir, + unsigned int idx, unsigned int val, + bool external_call); + +int lola_setup_all_analog_gains(struct lola *chip, int dir, bool mute) +{ + struct lola_pin *pin; + int idx, max_idx; + + pin = chip->pin[dir].pins; + max_idx = chip->pin[dir].num_pins; + for (idx = 0; idx < max_idx; idx++) { + if (pin[idx].is_analog) { + unsigned int val = mute ? 0 : pin[idx].cur_gain_step; + /* set volume and do not save the value */ + set_analog_volume(chip, dir, idx, val, false); + } + } + return lola_codec_flush(chip); +} + +/* + */ + +static int set_analog_volume(struct lola *chip, int dir, + unsigned int idx, unsigned int val, + bool external_call) +{ + struct lola_pin *pin; + int err; + + if (idx >= chip->pin[dir].num_pins) + return -EINVAL; + pin = &chip->pin[dir].pins[idx]; + if (!pin->is_analog || pin->amp_num_steps <= val) + return -EINVAL; + if (external_call && pin->cur_gain_step == val) + return 0; + if (external_call) + lola_codec_flush(chip); + dev_dbg(chip->card->dev, + "set_analog_volume (dir=%d idx=%d, volume=%d)\n", + dir, idx, val); + err = lola_codec_write(chip, pin->nid, + LOLA_VERB_SET_AMP_GAIN_MUTE, val, 0); + if (err < 0) + return err; + if (external_call) + pin->cur_gain_step = val; + return 0; +} + +int lola_set_src_config(struct lola *chip, unsigned int src_mask, bool update) +{ + int ret = 0; + int success = 0; + int n, err; + + /* SRC can be activated and the dwInputSRCMask is valid? */ + if ((chip->input_src_caps_mask & src_mask) != src_mask) + return -EINVAL; + /* handle all even Inputs - SRC is a stereo setting !!! */ + for (n = 0; n < chip->pin[CAPT].num_pins; n += 2) { + unsigned int mask = 3U << n; /* handle the stereo case */ + unsigned int new_src, src_state; + if (!(chip->input_src_caps_mask & mask)) + continue; + /* if one IO needs SRC, both stereo IO will get SRC */ + new_src = (src_mask & mask) != 0; + if (update) { + src_state = (chip->input_src_mask & mask) != 0; + if (src_state == new_src) + continue; /* nothing to change for this IO */ + } + err = lola_codec_write(chip, chip->pcm[CAPT].streams[n].nid, + LOLA_VERB_SET_SRC, new_src, 0); + if (!err) + success++; + else + ret = err; + } + if (success) + ret = lola_codec_flush(chip); + if (!ret) + chip->input_src_mask = src_mask; + return ret; +} + +/* + */ +static int init_mixer_values(struct lola *chip) +{ + int i; + + /* all sample rate converters on */ + lola_set_src_config(chip, (1 << chip->pin[CAPT].num_pins) - 1, false); + + /* clear all mixer matrix settings */ + memset_io(chip->mixer.array, 0, sizeof(*chip->mixer.array)); + /* inform firmware about all updated matrix columns - capture part */ + for (i = 0; i < chip->mixer.dest_stream_ins; i++) + lola_codec_write(chip, chip->mixer.nid, + LOLA_VERB_SET_DESTINATION_GAIN, + i, 0); + /* inform firmware about all updated matrix columns - output part */ + for (i = 0; i < chip->mixer.dest_phys_outs; i++) + lola_codec_write(chip, chip->mixer.nid, + LOLA_VERB_SET_DESTINATION_GAIN, + chip->mixer.dest_phys_out_ofs + i, 0); + + /* set all digital input source (master) gains to 0dB */ + for (i = 0; i < chip->mixer.src_phys_ins; i++) + lola_mixer_set_src_gain(chip, i, 336, true); /* 0dB */ + + /* set all digital playback source (master) gains to 0dB */ + for (i = 0; i < chip->mixer.src_stream_outs; i++) + lola_mixer_set_src_gain(chip, + i + chip->mixer.src_stream_out_ofs, + 336, true); /* 0dB */ + /* set gain value 0dB diagonally in matrix - part INPUT -> CAPTURE */ + for (i = 0; i < chip->mixer.dest_stream_ins; i++) { + int src = i % chip->mixer.src_phys_ins; + lola_mixer_set_mapping_gain(chip, src, i, 336, true); + } + /* set gain value 0dB diagonally in matrix , part PLAYBACK -> OUTPUT + * (LoLa280 : playback channel 0,2,4,6 linked to output channel 0) + * (LoLa280 : playback channel 1,3,5,7 linked to output channel 1) + */ + for (i = 0; i < chip->mixer.src_stream_outs; i++) { + int src = chip->mixer.src_stream_out_ofs + i; + int dst = chip->mixer.dest_phys_out_ofs + + i % chip->mixer.dest_phys_outs; + lola_mixer_set_mapping_gain(chip, src, dst, 336, true); + } + return 0; +} + +/* + * analog mixer control element + */ +static int lola_analog_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + int dir = kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chip->pin[dir].num_pins; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = chip->pin[dir].pins[0].amp_num_steps; + return 0; +} + +static int lola_analog_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + int dir = kcontrol->private_value; + int i; + + for (i = 0; i < chip->pin[dir].num_pins; i++) + ucontrol->value.integer.value[i] = + chip->pin[dir].pins[i].cur_gain_step; + return 0; +} + +static int lola_analog_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + int dir = kcontrol->private_value; + int i, err; + + for (i = 0; i < chip->pin[dir].num_pins; i++) { + err = set_analog_volume(chip, dir, i, + ucontrol->value.integer.value[i], + true); + if (err < 0) + return err; + } + return 0; +} + +static int lola_analog_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + int dir = kcontrol->private_value; + unsigned int val1, val2; + struct lola_pin *pin; + + if (size < 4 * sizeof(unsigned int)) + return -ENOMEM; + pin = &chip->pin[dir].pins[0]; + + val2 = pin->amp_step_size * 25; + val1 = -1 * (int)pin->amp_offset * (int)val2; +#ifdef TLV_DB_SCALE_MUTE + val2 |= TLV_DB_SCALE_MUTE; +#endif + if (put_user(SNDRV_CTL_TLVT_DB_SCALE, tlv)) + return -EFAULT; + if (put_user(2 * sizeof(unsigned int), tlv + 1)) + return -EFAULT; + if (put_user(val1, tlv + 2)) + return -EFAULT; + if (put_user(val2, tlv + 3)) + return -EFAULT; + return 0; +} + +static struct snd_kcontrol_new lola_analog_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK), + .info = lola_analog_vol_info, + .get = lola_analog_vol_get, + .put = lola_analog_vol_put, + .tlv.c = lola_analog_vol_tlv, +}; + +static int create_analog_mixer(struct lola *chip, int dir, char *name) +{ + if (!chip->pin[dir].num_pins) + return 0; + /* no analog volumes on digital only adapters */ + if (chip->pin[dir].num_pins != chip->pin[dir].num_analog_pins) + return 0; + lola_analog_mixer.name = name; + lola_analog_mixer.private_value = dir; + return snd_ctl_add(chip->card, + snd_ctl_new1(&lola_analog_mixer, chip)); +} + +/* + * Hardware sample rate converter on digital input + */ +static int lola_input_src_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = chip->pin[CAPT].num_pins; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int lola_input_src_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + int i; + + for (i = 0; i < chip->pin[CAPT].num_pins; i++) + ucontrol->value.integer.value[i] = + !!(chip->input_src_mask & (1 << i)); + return 0; +} + +static int lola_input_src_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + int i; + unsigned int mask; + + mask = 0; + for (i = 0; i < chip->pin[CAPT].num_pins; i++) + if (ucontrol->value.integer.value[i]) + mask |= 1 << i; + return lola_set_src_config(chip, mask, true); +} + +static const struct snd_kcontrol_new lola_input_src_mixer = { + .name = "Digital SRC Capture Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = lola_input_src_info, + .get = lola_input_src_get, + .put = lola_input_src_put, +}; + +/* + * Lola16161 or Lola881 can have Hardware sample rate converters + * on its digital input pins + */ +static int create_input_src_mixer(struct lola *chip) +{ + if (!chip->input_src_caps_mask) + return 0; + + return snd_ctl_add(chip->card, + snd_ctl_new1(&lola_input_src_mixer, chip)); +} + +/* + * src gain mixer + */ +static int lola_src_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int count = (kcontrol->private_value >> 8) & 0xff; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = count; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 409; + return 0; +} + +static int lola_src_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + unsigned int ofs = kcontrol->private_value & 0xff; + unsigned int count = (kcontrol->private_value >> 8) & 0xff; + unsigned int mask, i; + + mask = readl(&chip->mixer.array->src_gain_enable); + for (i = 0; i < count; i++) { + unsigned int idx = ofs + i; + unsigned short val; + if (!(chip->mixer.src_mask & (1 << idx))) + return -EINVAL; + if (mask & (1 << idx)) + val = readw(&chip->mixer.array->src_gain[idx]) + 1; + else + val = 0; + ucontrol->value.integer.value[i] = val; + } + return 0; +} + +static int lola_src_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + unsigned int ofs = kcontrol->private_value & 0xff; + unsigned int count = (kcontrol->private_value >> 8) & 0xff; + int i, err; + + for (i = 0; i < count; i++) { + unsigned int idx = ofs + i; + unsigned short val = ucontrol->value.integer.value[i]; + if (val) + val--; + err = lola_mixer_set_src_gain(chip, idx, val, !!val); + if (err < 0) + return err; + } + return 0; +} + +/* raw value: 0 = -84dB, 336 = 0dB, 408=18dB, incremented 1 for mute */ +static const DECLARE_TLV_DB_SCALE(lola_src_gain_tlv, -8425, 25, 1); + +static struct snd_kcontrol_new lola_src_gain_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = lola_src_gain_info, + .get = lola_src_gain_get, + .put = lola_src_gain_put, + .tlv.p = lola_src_gain_tlv, +}; + +static int create_src_gain_mixer(struct lola *chip, + int num, int ofs, char *name) +{ + lola_src_gain_mixer.name = name; + lola_src_gain_mixer.private_value = ofs + (num << 8); + return snd_ctl_add(chip->card, + snd_ctl_new1(&lola_src_gain_mixer, chip)); +} + +#if 0 /* not used */ +/* + * destination gain (matrix-like) mixer + */ +static int lola_dest_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int src_num = (kcontrol->private_value >> 8) & 0xff; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = src_num; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 433; + return 0; +} + +static int lola_dest_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + unsigned int src_ofs = kcontrol->private_value & 0xff; + unsigned int src_num = (kcontrol->private_value >> 8) & 0xff; + unsigned int dst_ofs = (kcontrol->private_value >> 16) & 0xff; + unsigned int dst, mask, i; + + dst = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + dst_ofs; + mask = readl(&chip->mixer.array->dest_mix_gain_enable[dst]); + for (i = 0; i < src_num; i++) { + unsigned int src = src_ofs + i; + unsigned short val; + if (!(chip->mixer.src_mask & (1 << src))) + return -EINVAL; + if (mask & (1 << dst)) + val = readw(&chip->mixer.array->dest_mix_gain[dst][src]) + 1; + else + val = 0; + ucontrol->value.integer.value[i] = val; + } + return 0; +} + +static int lola_dest_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lola *chip = snd_kcontrol_chip(kcontrol); + unsigned int src_ofs = kcontrol->private_value & 0xff; + unsigned int src_num = (kcontrol->private_value >> 8) & 0xff; + unsigned int dst_ofs = (kcontrol->private_value >> 16) & 0xff; + unsigned int dst, mask; + unsigned short gains[MAX_STREAM_COUNT]; + int i, num; + + mask = 0; + num = 0; + for (i = 0; i < src_num; i++) { + unsigned short val = ucontrol->value.integer.value[i]; + if (val) { + gains[num++] = val - 1; + mask |= 1 << i; + } + } + mask <<= src_ofs; + dst = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + dst_ofs; + return lola_mixer_set_dest_gains(chip, dst, mask, gains); +} + +static const DECLARE_TLV_DB_SCALE(lola_dest_gain_tlv, -8425, 25, 1); + +static struct snd_kcontrol_new lola_dest_gain_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = lola_dest_gain_info, + .get = lola_dest_gain_get, + .put = lola_dest_gain_put, + .tlv.p = lola_dest_gain_tlv, +}; + +static int create_dest_gain_mixer(struct lola *chip, + int src_num, int src_ofs, + int num, int ofs, char *name) +{ + lola_dest_gain_mixer.count = num; + lola_dest_gain_mixer.name = name; + lola_dest_gain_mixer.private_value = + src_ofs + (src_num << 8) + (ofs << 16) + (num << 24); + return snd_ctl_add(chip->card, + snd_ctl_new1(&lola_dest_gain_mixer, chip)); +} +#endif /* not used */ + +/* + */ +int lola_create_mixer(struct lola *chip) +{ + int err; + + err = create_analog_mixer(chip, PLAY, "Analog Playback Volume"); + if (err < 0) + return err; + err = create_analog_mixer(chip, CAPT, "Analog Capture Volume"); + if (err < 0) + return err; + err = create_input_src_mixer(chip); + if (err < 0) + return err; + err = create_src_gain_mixer(chip, chip->mixer.src_phys_ins, 0, + "Digital Capture Volume"); + if (err < 0) + return err; + err = create_src_gain_mixer(chip, chip->mixer.src_stream_outs, + chip->mixer.src_stream_out_ofs, + "Digital Playback Volume"); + if (err < 0) + return err; +#if 0 +/* FIXME: buggy mixer matrix handling */ + err = create_dest_gain_mixer(chip, + chip->mixer.src_phys_ins, 0, + chip->mixer.dest_stream_ins, 0, + "Line Capture Volume"); + if (err < 0) + return err; + err = create_dest_gain_mixer(chip, + chip->mixer.src_stream_outs, + chip->mixer.src_stream_out_ofs, + chip->mixer.dest_stream_ins, 0, + "Stream-Loopback Capture Volume"); + if (err < 0) + return err; + err = create_dest_gain_mixer(chip, + chip->mixer.src_phys_ins, 0, + chip->mixer.dest_phys_outs, + chip->mixer.dest_phys_out_ofs, + "Line-Loopback Playback Volume"); + if (err < 0) + return err; + err = create_dest_gain_mixer(chip, + chip->mixer.src_stream_outs, + chip->mixer.src_stream_out_ofs, + chip->mixer.dest_phys_outs, + chip->mixer.dest_phys_out_ofs, + "Stream Playback Volume"); + if (err < 0) + return err; +#endif /* FIXME */ + return init_mixer_values(chip); +} diff --git a/sound/pci/lola/lola_pcm.c b/sound/pci/lola/lola_pcm.c new file mode 100644 index 000000000..6c046ecd6 --- /dev/null +++ b/sound/pci/lola/lola_pcm.c @@ -0,0 +1,681 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include +#include "lola.h" + +#define LOLA_MAX_BDL_ENTRIES 8 +#define LOLA_MAX_BUF_SIZE (1024*1024*1024) +#define LOLA_BDL_ENTRY_SIZE (16 * 16) + +static struct lola_pcm *lola_get_pcm(struct snd_pcm_substream *substream) +{ + struct lola *chip = snd_pcm_substream_chip(substream); + return &chip->pcm[substream->stream]; +} + +static struct lola_stream *lola_get_stream(struct snd_pcm_substream *substream) +{ + struct lola_pcm *pcm = lola_get_pcm(substream); + unsigned int idx = substream->number; + return &pcm->streams[idx]; +} + +static unsigned int lola_get_lrc(struct lola *chip) +{ + return lola_readl(chip, BAR1, LRC); +} + +static unsigned int lola_get_tstamp(struct lola *chip, bool quick_no_sync) +{ + unsigned int tstamp = lola_get_lrc(chip) >> 8; + if (chip->granularity) { + unsigned int wait_banks = quick_no_sync ? 0 : 8; + tstamp += (wait_banks + 1) * chip->granularity - 1; + tstamp -= tstamp % chip->granularity; + } + return tstamp << 8; +} + +/* clear any pending interrupt status */ +static void lola_stream_clear_pending_irq(struct lola *chip, + struct lola_stream *str) +{ + unsigned int val = lola_dsd_read(chip, str->dsd, STS); + val &= LOLA_DSD_STS_DESE | LOLA_DSD_STS_BCIS; + if (val) + lola_dsd_write(chip, str->dsd, STS, val); +} + +static void lola_stream_start(struct lola *chip, struct lola_stream *str, + unsigned int tstamp) +{ + lola_stream_clear_pending_irq(chip, str); + lola_dsd_write(chip, str->dsd, CTL, + LOLA_DSD_CTL_SRUN | + LOLA_DSD_CTL_IOCE | + LOLA_DSD_CTL_DEIE | + LOLA_DSD_CTL_VLRCV | + tstamp); +} + +static void lola_stream_stop(struct lola *chip, struct lola_stream *str, + unsigned int tstamp) +{ + lola_dsd_write(chip, str->dsd, CTL, + LOLA_DSD_CTL_IOCE | + LOLA_DSD_CTL_DEIE | + LOLA_DSD_CTL_VLRCV | + tstamp); + lola_stream_clear_pending_irq(chip, str); +} + +static void wait_for_srst_clear(struct lola *chip, struct lola_stream *str) +{ + unsigned long end_time = jiffies + msecs_to_jiffies(200); + while (time_before(jiffies, end_time)) { + unsigned int val; + val = lola_dsd_read(chip, str->dsd, CTL); + if (!(val & LOLA_DSD_CTL_SRST)) + return; + msleep(1); + } + dev_warn(chip->card->dev, "SRST not clear (stream %d)\n", str->dsd); +} + +static int lola_stream_wait_for_fifo(struct lola *chip, + struct lola_stream *str, + bool ready) +{ + unsigned int val = ready ? LOLA_DSD_STS_FIFORDY : 0; + unsigned long end_time = jiffies + msecs_to_jiffies(200); + while (time_before(jiffies, end_time)) { + unsigned int reg = lola_dsd_read(chip, str->dsd, STS); + if ((reg & LOLA_DSD_STS_FIFORDY) == val) + return 0; + msleep(1); + } + dev_warn(chip->card->dev, "FIFO not ready (stream %d)\n", str->dsd); + return -EIO; +} + +/* sync for FIFO ready/empty for all linked streams; + * clear paused flag when FIFO gets ready again + */ +static int lola_sync_wait_for_fifo(struct lola *chip, + struct snd_pcm_substream *substream, + bool ready) +{ + unsigned int val = ready ? LOLA_DSD_STS_FIFORDY : 0; + unsigned long end_time = jiffies + msecs_to_jiffies(200); + struct snd_pcm_substream *s; + int pending = 0; + + while (time_before(jiffies, end_time)) { + pending = 0; + snd_pcm_group_for_each_entry(s, substream) { + struct lola_stream *str; + if (s->pcm->card != substream->pcm->card) + continue; + str = lola_get_stream(s); + if (str->prepared && str->paused) { + unsigned int reg; + reg = lola_dsd_read(chip, str->dsd, STS); + if ((reg & LOLA_DSD_STS_FIFORDY) != val) { + pending = str->dsd + 1; + break; + } + if (ready) + str->paused = 0; + } + } + if (!pending) + return 0; + msleep(1); + } + dev_warn(chip->card->dev, "FIFO not ready (pending %d)\n", pending - 1); + return -EIO; +} + +/* finish pause - prepare for a new resume */ +static void lola_sync_pause(struct lola *chip, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_substream *s; + + lola_sync_wait_for_fifo(chip, substream, false); + snd_pcm_group_for_each_entry(s, substream) { + struct lola_stream *str; + if (s->pcm->card != substream->pcm->card) + continue; + str = lola_get_stream(s); + if (str->paused && str->prepared) + lola_dsd_write(chip, str->dsd, CTL, LOLA_DSD_CTL_SRUN | + LOLA_DSD_CTL_IOCE | LOLA_DSD_CTL_DEIE); + } + lola_sync_wait_for_fifo(chip, substream, true); +} + +static void lola_stream_reset(struct lola *chip, struct lola_stream *str) +{ + if (str->prepared) { + if (str->paused) + lola_sync_pause(chip, str->substream); + str->prepared = 0; + lola_dsd_write(chip, str->dsd, CTL, + LOLA_DSD_CTL_IOCE | LOLA_DSD_CTL_DEIE); + lola_stream_wait_for_fifo(chip, str, false); + lola_stream_clear_pending_irq(chip, str); + lola_dsd_write(chip, str->dsd, CTL, LOLA_DSD_CTL_SRST); + lola_dsd_write(chip, str->dsd, LVI, 0); + lola_dsd_write(chip, str->dsd, BDPU, 0); + lola_dsd_write(chip, str->dsd, BDPL, 0); + wait_for_srst_clear(chip, str); + } +} + +static const struct snd_pcm_hardware lola_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_FLOAT_LE), + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = LOLA_MAX_BUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = LOLA_MAX_BUF_SIZE / 2, + .periods_min = 2, + .periods_max = LOLA_MAX_BDL_ENTRIES, + .fifo_size = 0, +}; + +static int lola_pcm_open(struct snd_pcm_substream *substream) +{ + struct lola *chip = snd_pcm_substream_chip(substream); + struct lola_pcm *pcm = lola_get_pcm(substream); + struct lola_stream *str = lola_get_stream(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + guard(mutex)(&chip->open_mutex); + if (str->opened) + return -EBUSY; + str->substream = substream; + str->master = NULL; + str->opened = 1; + runtime->hw = lola_pcm_hw; + runtime->hw.channels_max = pcm->num_streams - str->index; + if (chip->sample_rate) { + /* sample rate is locked */ + runtime->hw.rate_min = chip->sample_rate; + runtime->hw.rate_max = chip->sample_rate; + } else { + runtime->hw.rate_min = chip->sample_rate_min; + runtime->hw.rate_max = chip->sample_rate_max; + } + chip->ref_count_rate++; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + /* period size = multiple of chip->granularity (8, 16 or 32 frames)*/ + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + chip->granularity); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + chip->granularity); + return 0; +} + +static void lola_cleanup_slave_streams(struct lola_pcm *pcm, + struct lola_stream *str) +{ + int i; + for (i = str->index + 1; i < pcm->num_streams; i++) { + struct lola_stream *s = &pcm->streams[i]; + if (s->master != str) + break; + s->master = NULL; + s->opened = 0; + } +} + +static int lola_pcm_close(struct snd_pcm_substream *substream) +{ + struct lola *chip = snd_pcm_substream_chip(substream); + struct lola_stream *str = lola_get_stream(substream); + + guard(mutex)(&chip->open_mutex); + if (str->substream == substream) { + str->substream = NULL; + str->opened = 0; + } + if (--chip->ref_count_rate == 0) { + /* release sample rate */ + chip->sample_rate = 0; + } + return 0; +} + +static int lola_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct lola_stream *str = lola_get_stream(substream); + + str->bufsize = 0; + str->period_bytes = 0; + str->format_verb = 0; + return 0; +} + +static int lola_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct lola *chip = snd_pcm_substream_chip(substream); + struct lola_pcm *pcm = lola_get_pcm(substream); + struct lola_stream *str = lola_get_stream(substream); + + guard(mutex)(&chip->open_mutex); + lola_stream_reset(chip, str); + lola_cleanup_slave_streams(pcm, str); + return 0; +} + +/* + * set up a BDL entry + */ +static int setup_bdle(struct snd_pcm_substream *substream, + struct lola_stream *str, __le32 **bdlp, + int ofs, int size) +{ + __le32 *bdl = *bdlp; + + while (size > 0) { + dma_addr_t addr; + int chunk; + + if (str->frags >= LOLA_MAX_BDL_ENTRIES) + return -EINVAL; + + addr = snd_pcm_sgbuf_get_addr(substream, ofs); + /* program the address field of the BDL entry */ + bdl[0] = cpu_to_le32((u32)addr); + bdl[1] = cpu_to_le32(upper_32_bits(addr)); + /* program the size field of the BDL entry */ + chunk = snd_pcm_sgbuf_get_chunk_size(substream, ofs, size); + bdl[2] = cpu_to_le32(chunk); + /* program the IOC to enable interrupt + * only when the whole fragment is processed + */ + size -= chunk; + bdl[3] = size ? 0 : cpu_to_le32(0x01); + bdl += 4; + str->frags++; + ofs += chunk; + } + *bdlp = bdl; + return ofs; +} + +/* + * set up BDL entries + */ +static int lola_setup_periods(struct lola *chip, struct lola_pcm *pcm, + struct snd_pcm_substream *substream, + struct lola_stream *str) +{ + __le32 *bdl; + int i, ofs, periods, period_bytes; + + period_bytes = str->period_bytes; + periods = str->bufsize / period_bytes; + + /* program the initial BDL entries */ + bdl = (__le32 *)(pcm->bdl->area + LOLA_BDL_ENTRY_SIZE * str->index); + ofs = 0; + str->frags = 0; + for (i = 0; i < periods; i++) { + ofs = setup_bdle(substream, str, &bdl, ofs, period_bytes); + if (ofs < 0) + goto error; + } + return 0; + + error: + dev_err(chip->card->dev, "Too many BDL entries: buffer=%d, period=%d\n", + str->bufsize, period_bytes); + return -EINVAL; +} + +static unsigned int lola_get_format_verb(struct snd_pcm_substream *substream) +{ + unsigned int verb; + + switch (substream->runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + verb = 0x00000000; + break; + case SNDRV_PCM_FORMAT_S24_LE: + verb = 0x00000200; + break; + case SNDRV_PCM_FORMAT_S32_LE: + verb = 0x00000300; + break; + case SNDRV_PCM_FORMAT_FLOAT_LE: + verb = 0x00001300; + break; + default: + return 0; + } + verb |= substream->runtime->channels; + return verb; +} + +static int lola_set_stream_config(struct lola *chip, + struct lola_stream *str, + int channels) +{ + int i, err; + unsigned int verb, val; + + /* set format info for all channels + * (with only one command for the first channel) + */ + err = lola_codec_read(chip, str->nid, LOLA_VERB_SET_STREAM_FORMAT, + str->format_verb, 0, &val, NULL); + if (err < 0) { + dev_err(chip->card->dev, "Cannot set stream format 0x%x\n", + str->format_verb); + return err; + } + + /* update stream - channel config */ + for (i = 0; i < channels; i++) { + verb = (str->index << 6) | i; + err = lola_codec_read(chip, str[i].nid, + LOLA_VERB_SET_CHANNEL_STREAMID, 0, verb, + &val, NULL); + if (err < 0) { + dev_err(chip->card->dev, + "Cannot set stream channel %d\n", i); + return err; + } + } + return 0; +} + +/* + * set up the SD for streaming + */ +static int lola_setup_controller(struct lola *chip, struct lola_pcm *pcm, + struct lola_stream *str) +{ + dma_addr_t bdl; + + if (str->prepared) + return -EINVAL; + + /* set up BDL */ + bdl = pcm->bdl->addr + LOLA_BDL_ENTRY_SIZE * str->index; + lola_dsd_write(chip, str->dsd, BDPL, (u32)bdl); + lola_dsd_write(chip, str->dsd, BDPU, upper_32_bits(bdl)); + /* program the stream LVI (last valid index) of the BDL */ + lola_dsd_write(chip, str->dsd, LVI, str->frags - 1); + lola_stream_clear_pending_irq(chip, str); + + lola_dsd_write(chip, str->dsd, CTL, + LOLA_DSD_CTL_IOCE | LOLA_DSD_CTL_DEIE | LOLA_DSD_CTL_SRUN); + + str->prepared = 1; + + return lola_stream_wait_for_fifo(chip, str, true); +} + +static int lola_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct lola *chip = snd_pcm_substream_chip(substream); + struct lola_pcm *pcm = lola_get_pcm(substream); + struct lola_stream *str = lola_get_stream(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int bufsize, period_bytes, format_verb; + int i, err; + + scoped_guard(mutex, &chip->open_mutex) { + lola_stream_reset(chip, str); + lola_cleanup_slave_streams(pcm, str); + if (str->index + runtime->channels > pcm->num_streams) + return -EINVAL; + for (i = 1; i < runtime->channels; i++) { + str[i].master = str; + str[i].opened = 1; + } + } + + bufsize = snd_pcm_lib_buffer_bytes(substream); + period_bytes = snd_pcm_lib_period_bytes(substream); + format_verb = lola_get_format_verb(substream); + + str->bufsize = bufsize; + str->period_bytes = period_bytes; + str->format_verb = format_verb; + + err = lola_setup_periods(chip, pcm, substream, str); + if (err < 0) + return err; + + err = lola_set_sample_rate(chip, runtime->rate); + if (err < 0) + return err; + chip->sample_rate = runtime->rate; /* sample rate gets locked */ + + err = lola_set_stream_config(chip, str, runtime->channels); + if (err < 0) + return err; + + err = lola_setup_controller(chip, pcm, str); + if (err < 0) { + lola_stream_reset(chip, str); + return err; + } + + return 0; +} + +static int lola_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct lola *chip = snd_pcm_substream_chip(substream); + struct lola_stream *str; + struct snd_pcm_substream *s; + unsigned int start; + unsigned int tstamp; + bool sync_streams; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + start = 1; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + start = 0; + break; + default: + return -EINVAL; + } + + /* + * sample correct synchronization is only needed starting several + * streams. On stop or if only one stream do as quick as possible + */ + sync_streams = (start && snd_pcm_stream_linked(substream)); + tstamp = lola_get_tstamp(chip, !sync_streams); + guard(spinlock)(&chip->reg_lock); + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + str = lola_get_stream(s); + if (start) + lola_stream_start(chip, str, tstamp); + else + lola_stream_stop(chip, str, tstamp); + str->running = start; + str->paused = !start; + snd_pcm_trigger_done(s, substream); + } + return 0; +} + +static snd_pcm_uframes_t lola_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct lola *chip = snd_pcm_substream_chip(substream); + struct lola_stream *str = lola_get_stream(substream); + unsigned int pos = lola_dsd_read(chip, str->dsd, LPIB); + + if (pos >= str->bufsize) + pos = 0; + return bytes_to_frames(substream->runtime, pos); +} + +void lola_pcm_update(struct lola *chip, struct lola_pcm *pcm, unsigned int bits) +{ + int i; + u8 num_streams = min_t(u8, pcm->num_streams, ARRAY_SIZE(pcm->streams)); + + for (i = 0; bits && i < num_streams; i++) { + if (bits & (1 << i)) { + struct lola_stream *str = &pcm->streams[i]; + if (str->substream && str->running) + snd_pcm_period_elapsed(str->substream); + bits &= ~(1 << i); + } + } +} + +static const struct snd_pcm_ops lola_pcm_ops = { + .open = lola_pcm_open, + .close = lola_pcm_close, + .hw_params = lola_pcm_hw_params, + .hw_free = lola_pcm_hw_free, + .prepare = lola_pcm_prepare, + .trigger = lola_pcm_trigger, + .pointer = lola_pcm_pointer, +}; + +int lola_create_pcm(struct lola *chip) +{ + struct snd_pcm *pcm; + int i, err; + + for (i = 0; i < 2; i++) { + chip->pcm[i].bdl = + snd_devm_alloc_pages(&chip->pci->dev, SNDRV_DMA_TYPE_DEV, + PAGE_SIZE); + if (!chip->pcm[i].bdl) + return -ENOMEM; + } + + err = snd_pcm_new(chip->card, "Digigram Lola", 0, + chip->pcm[SNDRV_PCM_STREAM_PLAYBACK].num_streams, + chip->pcm[SNDRV_PCM_STREAM_CAPTURE].num_streams, + &pcm); + if (err < 0) + return err; + strscpy(pcm->name, "Digigram Lola", sizeof(pcm->name)); + pcm->private_data = chip; + for (i = 0; i < 2; i++) { + if (chip->pcm[i].num_streams) + snd_pcm_set_ops(pcm, i, &lola_pcm_ops); + } + /* buffer pre-allocation */ + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci->dev, + 1024 * 64, 32 * 1024 * 1024); + return 0; +} + +/* + */ + +static int lola_init_stream(struct lola *chip, struct lola_stream *str, + int idx, int nid, int dir) +{ + unsigned int val; + int err; + + str->nid = nid; + str->index = idx; + str->dsd = idx; + if (dir == PLAY) + str->dsd += MAX_STREAM_IN_COUNT; + err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read wcaps for 0x%x\n", nid); + return err; + } + if (dir == PLAY) { + /* test TYPE and bits 0..11 (no test bit9 : Digital = 0/1) */ + if ((val & 0x00f00dff) != 0x00000010) { + dev_err(chip->card->dev, + "Invalid wcaps 0x%x for 0x%x\n", + val, nid); + return -EINVAL; + } + } else { + /* test TYPE and bits 0..11 (no test bit9 : Digital = 0/1) + * (bug : ignore bit8: Conn list = 0/1) + */ + if ((val & 0x00f00cff) != 0x00100010) { + dev_err(chip->card->dev, + "Invalid wcaps 0x%x for 0x%x\n", + val, nid); + return -EINVAL; + } + /* test bit9:DIGITAL and bit12:SRC_PRESENT*/ + if ((val & 0x00001200) == 0x00001200) + chip->input_src_caps_mask |= (1 << idx); + } + + err = lola_read_param(chip, nid, LOLA_PAR_STREAM_FORMATS, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read FORMATS 0x%x\n", nid); + return err; + } + val &= 3; + if (val == 3) + str->can_float = true; + if (!(val & 1)) { + dev_err(chip->card->dev, + "Invalid formats 0x%x for 0x%x", val, nid); + return -EINVAL; + } + return 0; +} + +int lola_init_pcm(struct lola *chip, int dir, int *nidp) +{ + struct lola_pcm *pcm = &chip->pcm[dir]; + int i, nid, err; + + nid = *nidp; + for (i = 0; i < pcm->num_streams; i++, nid++) { + err = lola_init_stream(chip, &pcm->streams[i], i, nid, dir); + if (err < 0) + return err; + } + *nidp = nid; + return 0; +} diff --git a/sound/pci/lola/lola_proc.c b/sound/pci/lola/lola_proc.c new file mode 100644 index 000000000..a166672e2 --- /dev/null +++ b/sound/pci/lola/lola_proc.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include "lola.h" + +static void print_audio_widget(struct snd_info_buffer *buffer, + struct lola *chip, int nid, const char *name) +{ + unsigned int val; + + lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + snd_iprintf(buffer, "Node 0x%02x %s wcaps 0x%x\n", nid, name, val); + lola_read_param(chip, nid, LOLA_PAR_STREAM_FORMATS, &val); + snd_iprintf(buffer, " Formats: 0x%x\n", val); +} + +static void print_pin_widget(struct snd_info_buffer *buffer, + struct lola *chip, int nid, unsigned int ampcap, + const char *name) +{ + unsigned int val; + + lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + snd_iprintf(buffer, "Node 0x%02x %s wcaps 0x%x\n", nid, name, val); + if (val == 0x00400200) + return; + lola_read_param(chip, nid, ampcap, &val); + snd_iprintf(buffer, " Amp-Caps: 0x%x\n", val); + snd_iprintf(buffer, " mute=%d, step-size=%d, steps=%d, ofs=%d\n", + LOLA_AMP_MUTE_CAPABLE(val), + LOLA_AMP_STEP_SIZE(val), + LOLA_AMP_NUM_STEPS(val), + LOLA_AMP_OFFSET(val)); + lola_codec_read(chip, nid, LOLA_VERB_GET_MAX_LEVEL, 0, 0, &val, NULL); + snd_iprintf(buffer, " Max-level: 0x%x\n", val); +} + +static void print_clock_widget(struct snd_info_buffer *buffer, + struct lola *chip, int nid) +{ + int i, j, num_clocks; + unsigned int val; + + lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + snd_iprintf(buffer, "Node 0x%02x [Clock] wcaps 0x%x\n", nid, val); + num_clocks = val & 0xff; + for (i = 0; i < num_clocks; i += 4) { + unsigned int res_ex; + unsigned short items[4]; + const char *name; + + lola_codec_read(chip, nid, LOLA_VERB_GET_CLOCK_LIST, + i, 0, &val, &res_ex); + items[0] = val & 0xfff; + items[1] = (val >> 16) & 0xfff; + items[2] = res_ex & 0xfff; + items[3] = (res_ex >> 16) & 0xfff; + for (j = 0; j < 4; j++) { + unsigned char type = items[j] >> 8; + unsigned int freq = items[j] & 0xff; + if (i + j >= num_clocks) + break; + if (type == LOLA_CLOCK_TYPE_INTERNAL) { + name = "Internal"; + freq = lola_sample_rate_convert(freq); + } else if (type == LOLA_CLOCK_TYPE_VIDEO) { + name = "Video"; + freq = lola_sample_rate_convert(freq); + } else { + name = "Other"; + } + snd_iprintf(buffer, " Clock %d: Type %d:%s, freq=%d\n", + i + j, type, name, freq); + } + } +} + +static void print_mixer_widget(struct snd_info_buffer *buffer, + struct lola *chip, int nid) +{ + unsigned int val; + + lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + snd_iprintf(buffer, "Node 0x%02x [Mixer] wcaps 0x%x\n", nid, val); +} + +static void lola_proc_codec_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct lola *chip = entry->private_data; + unsigned int val; + int i, nid; + + lola_read_param(chip, 0, LOLA_PAR_VENDOR_ID, &val); + snd_iprintf(buffer, "Vendor: 0x%08x\n", val); + lola_read_param(chip, 1, LOLA_PAR_FUNCTION_TYPE, &val); + snd_iprintf(buffer, "Function Type: %d\n", val); + lola_read_param(chip, 1, LOLA_PAR_SPECIFIC_CAPS, &val); + snd_iprintf(buffer, "Specific-Caps: 0x%08x\n", val); + snd_iprintf(buffer, " Pins-In %d, Pins-Out %d\n", + chip->pin[CAPT].num_pins, chip->pin[PLAY].num_pins); + nid = 2; + for (i = 0; i < chip->pcm[CAPT].num_streams; i++, nid++) + print_audio_widget(buffer, chip, nid, "[Audio-In]"); + for (i = 0; i < chip->pcm[PLAY].num_streams; i++, nid++) + print_audio_widget(buffer, chip, nid, "[Audio-Out]"); + for (i = 0; i < chip->pin[CAPT].num_pins; i++, nid++) + print_pin_widget(buffer, chip, nid, LOLA_PAR_AMP_IN_CAP, + "[Pin-In]"); + for (i = 0; i < chip->pin[PLAY].num_pins; i++, nid++) + print_pin_widget(buffer, chip, nid, LOLA_PAR_AMP_OUT_CAP, + "[Pin-Out]"); + if (LOLA_AFG_CLOCK_WIDGET_PRESENT(chip->lola_caps)) { + print_clock_widget(buffer, chip, nid); + nid++; + } + if (LOLA_AFG_MIXER_WIDGET_PRESENT(chip->lola_caps)) { + print_mixer_widget(buffer, chip, nid); + nid++; + } +} + +/* direct codec access for debugging */ +static void lola_proc_codec_rw_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct lola *chip = entry->private_data; + char line[64]; + unsigned int id, verb, data, extdata; + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%u %u %u %u", &id, &verb, &data, &extdata) != 4) + continue; + lola_codec_read(chip, id, verb, data, extdata, + &chip->debug_res, + &chip->debug_res_ex); + } +} + +static void lola_proc_codec_rw_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct lola *chip = entry->private_data; + snd_iprintf(buffer, "0x%x 0x%x\n", chip->debug_res, chip->debug_res_ex); +} + +/* + * dump some registers + */ +static void lola_proc_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct lola *chip = entry->private_data; + int i; + + for (i = 0; i < 0x40; i += 4) { + snd_iprintf(buffer, "BAR0 %02x: %08x\n", i, + readl(chip->bar[BAR0].remap_addr + i)); + } + snd_iprintf(buffer, "\n"); + for (i = 0; i < 0x30; i += 4) { + snd_iprintf(buffer, "BAR1 %02x: %08x\n", i, + readl(chip->bar[BAR1].remap_addr + i)); + } + snd_iprintf(buffer, "\n"); + for (i = 0x80; i < 0xa0; i += 4) { + snd_iprintf(buffer, "BAR1 %02x: %08x\n", i, + readl(chip->bar[BAR1].remap_addr + i)); + } + snd_iprintf(buffer, "\n"); + for (i = 0; i < 32; i++) { + snd_iprintf(buffer, "DSD %02x STS %08x\n", i, + lola_dsd_read(chip, i, STS)); + snd_iprintf(buffer, "DSD %02x LPIB %08x\n", i, + lola_dsd_read(chip, i, LPIB)); + snd_iprintf(buffer, "DSD %02x CTL %08x\n", i, + lola_dsd_read(chip, i, CTL)); + snd_iprintf(buffer, "DSD %02x LVIL %08x\n", i, + lola_dsd_read(chip, i, LVI)); + snd_iprintf(buffer, "DSD %02x BDPL %08x\n", i, + lola_dsd_read(chip, i, BDPL)); + snd_iprintf(buffer, "DSD %02x BDPU %08x\n", i, + lola_dsd_read(chip, i, BDPU)); + } +} + +void lola_proc_debug_new(struct lola *chip) +{ + snd_card_ro_proc_new(chip->card, "codec", chip, lola_proc_codec_read); + snd_card_rw_proc_new(chip->card, "codec_rw", chip, + lola_proc_codec_rw_read, + lola_proc_codec_rw_write); + snd_card_ro_proc_new(chip->card, "regs", chip, lola_proc_regs_read); +} diff --git a/sound/pci/lx6464es/Makefile b/sound/pci/lx6464es/Makefile new file mode 100644 index 000000000..2b3047c7a --- /dev/null +++ b/sound/pci/lx6464es/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-lx6464es-y := lx6464es.o lx_core.o +obj-$(CONFIG_SND_LX6464ES) += snd-lx6464es.o diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c new file mode 100644 index 000000000..3b40ec071 --- /dev/null +++ b/sound/pci/lx6464es/lx6464es.c @@ -0,0 +1,1041 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * + * Copyright (c) 2008, 2009 Tim Blechmann + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "lx6464es.h" + +MODULE_AUTHOR("Tim Blechmann"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("digigram lx6464es"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Digigram LX6464ES interface."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Digigram LX6464ES interface."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable/disable specific Digigram LX6464ES soundcards."); + +static const char card_name[] = "LX6464ES"; + + +#define PCI_DEVICE_ID_PLX_LX6464ES PCI_DEVICE_ID_PLX_9056 + +static const struct pci_device_id snd_lx6464es_ids[] = { + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES, + PCI_VENDOR_ID_DIGIGRAM, + PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ES_SERIAL_SUBSYSTEM), + }, /* LX6464ES */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES, + PCI_VENDOR_ID_DIGIGRAM, + PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ES_CAE_SERIAL_SUBSYSTEM), + }, /* LX6464ES-CAE */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES, + PCI_VENDOR_ID_DIGIGRAM, + PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ESE_SERIAL_SUBSYSTEM), + }, /* LX6464ESe */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES, + PCI_VENDOR_ID_DIGIGRAM, + PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ESE_CAE_SERIAL_SUBSYSTEM), + }, /* LX6464ESe-CAE */ + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, snd_lx6464es_ids); + + + +/* PGO pour USERo dans le registre pci_0x06/loc_0xEC */ +#define CHIPSC_RESET_XILINX (1L<<16) + + +/* alsa callbacks */ +static const struct snd_pcm_hardware lx_caps = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_3BE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_192000), + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 64, + .buffer_bytes_max = 64*2*3*MICROBLAZE_IBL_MAX*MAX_STREAM_BUFFER, + .period_bytes_min = (2*2*MICROBLAZE_IBL_MIN*2), + .period_bytes_max = (4*64*MICROBLAZE_IBL_MAX*MAX_STREAM_BUFFER), + .periods_min = 2, + .periods_max = MAX_STREAM_BUFFER, +}; + +static int lx_set_granularity(struct lx6464es *chip, u32 gran); + + +static int lx_hardware_open(struct lx6464es *chip, + struct snd_pcm_substream *substream) +{ + int err = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + int channels = runtime->channels; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + snd_pcm_uframes_t period_size = runtime->period_size; + + dev_dbg(chip->card->dev, "allocating pipe for %d channels\n", channels); + err = lx_pipe_allocate(chip, 0, is_capture, channels); + if (err < 0) { + dev_err(chip->card->dev, LXP "allocating pipe failed\n"); + return err; + } + + err = lx_set_granularity(chip, period_size); + if (err < 0) { + dev_err(chip->card->dev, "setting granularity to %ld failed\n", + period_size); + return err; + } + + return 0; +} + +static int lx_hardware_start(struct lx6464es *chip, + struct snd_pcm_substream *substream) +{ + int err = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + dev_dbg(chip->card->dev, "setting stream format\n"); + err = lx_stream_set_format(chip, runtime, 0, is_capture); + if (err < 0) { + dev_err(chip->card->dev, "setting stream format failed\n"); + return err; + } + + dev_dbg(chip->card->dev, "starting pipe\n"); + err = lx_pipe_start(chip, 0, is_capture); + if (err < 0) { + dev_err(chip->card->dev, "starting pipe failed\n"); + return err; + } + + dev_dbg(chip->card->dev, "waiting for pipe to start\n"); + err = lx_pipe_wait_for_start(chip, 0, is_capture); + if (err < 0) { + dev_err(chip->card->dev, "waiting for pipe failed\n"); + return err; + } + + return err; +} + + +static int lx_hardware_stop(struct lx6464es *chip, + struct snd_pcm_substream *substream) +{ + int err = 0; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + dev_dbg(chip->card->dev, "pausing pipe\n"); + err = lx_pipe_pause(chip, 0, is_capture); + if (err < 0) { + dev_err(chip->card->dev, "pausing pipe failed\n"); + return err; + } + + dev_dbg(chip->card->dev, "waiting for pipe to become idle\n"); + err = lx_pipe_wait_for_idle(chip, 0, is_capture); + if (err < 0) { + dev_err(chip->card->dev, "waiting for pipe failed\n"); + return err; + } + + dev_dbg(chip->card->dev, "stopping pipe\n"); + err = lx_pipe_stop(chip, 0, is_capture); + if (err < 0) { + dev_err(chip->card->dev, "stopping pipe failed\n"); + return err; + } + + return err; +} + + +static int lx_hardware_close(struct lx6464es *chip, + struct snd_pcm_substream *substream) +{ + int err = 0; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + dev_dbg(chip->card->dev, "releasing pipe\n"); + err = lx_pipe_release(chip, 0, is_capture); + if (err < 0) { + dev_err(chip->card->dev, "releasing pipe failed\n"); + return err; + } + + return err; +} + + +static int lx_pcm_open(struct snd_pcm_substream *substream) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err = 0; + int board_rate; + + dev_dbg(chip->card->dev, "->lx_pcm_open\n"); + guard(mutex)(&chip->setup_mutex); + + /* copy the struct snd_pcm_hardware struct */ + runtime->hw = lx_caps; + +#if 0 + /* buffer-size should better be multiple of period-size */ + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) { + dev_warn(chip->card->dev, "could not constrain periods\n"); + return err; + } +#endif + + /* the clock rate cannot be changed */ + board_rate = chip->board_sample_rate; + err = snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_RATE, + board_rate); + + if (err < 0) { + dev_warn(chip->card->dev, "could not constrain periods\n"); + return err; + } + + /* constrain period size */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + MICROBLAZE_IBL_MIN, + MICROBLAZE_IBL_MAX); + if (err < 0) { + dev_warn(chip->card->dev, + "could not constrain period size\n"); + return err; + } + + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); + + snd_pcm_set_sync(substream); + err = 0; + + runtime->private_data = chip; + + dev_dbg(chip->card->dev, "<-lx_pcm_open, %d\n", err); + return err; +} + +static int lx_pcm_close(struct snd_pcm_substream *substream) +{ + dev_dbg(substream->pcm->card->dev, "->lx_pcm_close\n"); + return 0; +} + +static snd_pcm_uframes_t lx_pcm_stream_pointer(struct snd_pcm_substream + *substream) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + snd_pcm_uframes_t pos; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + struct lx_stream *lx_stream = is_capture ? &chip->capture_stream : + &chip->playback_stream; + + dev_dbg(chip->card->dev, "->lx_pcm_stream_pointer\n"); + + guard(mutex)(&chip->lock); + pos = lx_stream->frame_pos * substream->runtime->period_size; + + dev_dbg(chip->card->dev, "stream_pointer at %ld\n", pos); + return pos; +} + +static int lx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + int err = 0; + const int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + dev_dbg(chip->card->dev, "->lx_pcm_prepare\n"); + + guard(mutex)(&chip->setup_mutex); + + if (chip->hardware_running[is_capture]) { + err = lx_hardware_stop(chip, substream); + if (err < 0) { + dev_err(chip->card->dev, "failed to stop hardware. " + "Error code %d\n", err); + return err; + } + + err = lx_hardware_close(chip, substream); + if (err < 0) { + dev_err(chip->card->dev, "failed to close hardware. " + "Error code %d\n", err); + return err; + } + } + + dev_dbg(chip->card->dev, "opening hardware\n"); + err = lx_hardware_open(chip, substream); + if (err < 0) { + dev_err(chip->card->dev, "failed to open hardware. " + "Error code %d\n", err); + return err; + } + + err = lx_hardware_start(chip, substream); + if (err < 0) { + dev_err(chip->card->dev, "failed to start hardware. " + "Error code %d\n", err); + return err; + } + + chip->hardware_running[is_capture] = 1; + + if (chip->board_sample_rate != substream->runtime->rate) { + if (!err) + chip->board_sample_rate = substream->runtime->rate; + } + + return err; +} + +static int lx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, int is_capture) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + + dev_dbg(chip->card->dev, "->lx_pcm_hw_params\n"); + + guard(mutex)(&chip->setup_mutex); + + if (is_capture) + chip->capture_stream.stream = substream; + else + chip->playback_stream.stream = substream; + + return 0; +} + +static int lx_pcm_hw_params_playback(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return lx_pcm_hw_params(substream, hw_params, 0); +} + +static int lx_pcm_hw_params_capture(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return lx_pcm_hw_params(substream, hw_params, 1); +} + +static int lx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + int err = 0; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + dev_dbg(chip->card->dev, "->lx_pcm_hw_free\n"); + guard(mutex)(&chip->setup_mutex); + + if (chip->hardware_running[is_capture]) { + err = lx_hardware_stop(chip, substream); + if (err < 0) { + dev_err(chip->card->dev, "failed to stop hardware. " + "Error code %d\n", err); + return err; + } + + err = lx_hardware_close(chip, substream); + if (err < 0) { + dev_err(chip->card->dev, "failed to close hardware. " + "Error code %d\n", err); + return err; + } + + chip->hardware_running[is_capture] = 0; + } + + if (is_capture) + chip->capture_stream.stream = NULL; + else + chip->playback_stream.stream = NULL; + + return 0; +} + +static void lx_trigger_start(struct lx6464es *chip, struct lx_stream *lx_stream) +{ + struct snd_pcm_substream *substream = lx_stream->stream; + const unsigned int is_capture = lx_stream->is_capture; + + int err; + + const u32 periods = substream->runtime->periods; + const u32 period_bytes = snd_pcm_lib_period_bytes(substream); + + dma_addr_t buf = substream->dma_buffer.addr; + int i; + + u32 needed, freed; + u32 size_array[5]; + + for (i = 0; i != periods; ++i) { + u32 buffer_index = 0; + + err = lx_buffer_ask(chip, 0, is_capture, &needed, &freed, + size_array); + dev_dbg(chip->card->dev, "starting: needed %d, freed %d\n", + needed, freed); + + err = lx_buffer_give(chip, 0, is_capture, period_bytes, + lower_32_bits(buf), upper_32_bits(buf), + &buffer_index); + + dev_dbg(chip->card->dev, "starting: buffer index %x on 0x%lx (%d bytes)\n", + buffer_index, (unsigned long)buf, period_bytes); + buf += period_bytes; + } + + err = lx_buffer_ask(chip, 0, is_capture, &needed, &freed, size_array); + dev_dbg(chip->card->dev, "starting: needed %d, freed %d\n", needed, freed); + + dev_dbg(chip->card->dev, "starting: starting stream\n"); + err = lx_stream_start(chip, 0, is_capture); + if (err < 0) + dev_err(chip->card->dev, "couldn't start stream\n"); + else + lx_stream->status = LX_STREAM_STATUS_RUNNING; + + lx_stream->frame_pos = 0; +} + +static void lx_trigger_stop(struct lx6464es *chip, struct lx_stream *lx_stream) +{ + const unsigned int is_capture = lx_stream->is_capture; + int err; + + dev_dbg(chip->card->dev, "stopping: stopping stream\n"); + err = lx_stream_stop(chip, 0, is_capture); + if (err < 0) + dev_err(chip->card->dev, "couldn't stop stream\n"); + else + lx_stream->status = LX_STREAM_STATUS_FREE; + +} + +static void lx_trigger_dispatch_stream(struct lx6464es *chip, + struct lx_stream *lx_stream) +{ + switch (lx_stream->status) { + case LX_STREAM_STATUS_SCHEDULE_RUN: + lx_trigger_start(chip, lx_stream); + break; + + case LX_STREAM_STATUS_SCHEDULE_STOP: + lx_trigger_stop(chip, lx_stream); + break; + + default: + break; + } +} + +static int lx_pcm_trigger_dispatch(struct lx6464es *chip, + struct lx_stream *lx_stream, int cmd) +{ + guard(mutex)(&chip->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + lx_stream->status = LX_STREAM_STATUS_SCHEDULE_RUN; + break; + + case SNDRV_PCM_TRIGGER_STOP: + lx_stream->status = LX_STREAM_STATUS_SCHEDULE_STOP; + break; + + default: + return -EINVAL; + } + + lx_trigger_dispatch_stream(chip, &chip->capture_stream); + lx_trigger_dispatch_stream(chip, &chip->playback_stream); + + return 0; +} + + +static int lx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + const int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + struct lx_stream *stream = is_capture ? &chip->capture_stream : + &chip->playback_stream; + + dev_dbg(chip->card->dev, "->lx_pcm_trigger\n"); + + return lx_pcm_trigger_dispatch(chip, stream, cmd); +} + +static void snd_lx6464es_free(struct snd_card *card) +{ + struct lx6464es *chip = card->private_data; + + lx_irq_disable(chip); +} + +/* reset the dsp during initialization */ +static int lx_init_xilinx_reset(struct lx6464es *chip) +{ + int i; + u32 plx_reg = lx_plx_reg_read(chip, ePLX_CHIPSC); + + dev_dbg(chip->card->dev, "->lx_init_xilinx_reset\n"); + + /* activate reset of xilinx */ + plx_reg &= ~CHIPSC_RESET_XILINX; + + lx_plx_reg_write(chip, ePLX_CHIPSC, plx_reg); + msleep(1); + + lx_plx_reg_write(chip, ePLX_MBOX3, 0); + msleep(1); + + plx_reg |= CHIPSC_RESET_XILINX; + lx_plx_reg_write(chip, ePLX_CHIPSC, plx_reg); + + /* deactivate reset of xilinx */ + for (i = 0; i != 100; ++i) { + u32 reg_mbox3; + msleep(10); + reg_mbox3 = lx_plx_reg_read(chip, ePLX_MBOX3); + if (reg_mbox3) { + dev_dbg(chip->card->dev, "xilinx reset done\n"); + dev_dbg(chip->card->dev, "xilinx took %d loops\n", i); + break; + } + } + + /* todo: add some error handling? */ + + /* clear mr */ + lx_dsp_reg_write(chip, eReg_CSM, 0); + + /* le xilinx ES peut ne pas etre encore pret, on attend. */ + msleep(600); + + return 0; +} + +static int lx_init_xilinx_test(struct lx6464es *chip) +{ + u32 reg; + + dev_dbg(chip->card->dev, "->lx_init_xilinx_test\n"); + + /* TEST if we have access to Xilinx/MicroBlaze */ + lx_dsp_reg_write(chip, eReg_CSM, 0); + + reg = lx_dsp_reg_read(chip, eReg_CSM); + + if (reg) { + dev_err(chip->card->dev, "Problem: Reg_CSM %x.\n", reg); + + /* PCI9056_SPACE0_REMAP */ + lx_plx_reg_write(chip, ePLX_PCICR, 1); + + reg = lx_dsp_reg_read(chip, eReg_CSM); + if (reg) { + dev_err(chip->card->dev, "Error: Reg_CSM %x.\n", reg); + return -EAGAIN; /* seems to be appropriate */ + } + } + + dev_dbg(chip->card->dev, "Xilinx/MicroBlaze access test successful\n"); + + return 0; +} + +/* initialize ethersound */ +static int lx_init_ethersound_config(struct lx6464es *chip) +{ + int i; + u32 orig_conf_es = lx_dsp_reg_read(chip, eReg_CONFES); + + /* configure 64 io channels */ + u32 conf_es = (orig_conf_es & CONFES_READ_PART_MASK) | + (64 << IOCR_INPUTS_OFFSET) | + (64 << IOCR_OUTPUTS_OFFSET) | + (FREQ_RATIO_SINGLE_MODE << FREQ_RATIO_OFFSET); + + dev_dbg(chip->card->dev, "->lx_init_ethersound\n"); + + chip->freq_ratio = FREQ_RATIO_SINGLE_MODE; + + /* + * write it to the card ! + * this actually kicks the ES xilinx, the first time since poweron. + * the MAC address in the Reg_ADMACESMSB Reg_ADMACESLSB registers + * is not ready before this is done, and the bit 2 in Reg_CSES is set. + * */ + lx_dsp_reg_write(chip, eReg_CONFES, conf_es); + + for (i = 0; i != 1000; ++i) { + if (lx_dsp_reg_read(chip, eReg_CSES) & 4) { + dev_dbg(chip->card->dev, "ethersound initialized after %dms\n", + i); + goto ethersound_initialized; + } + msleep(1); + } + dev_warn(chip->card->dev, + "ethersound could not be initialized after %dms\n", i); + return -ETIMEDOUT; + + ethersound_initialized: + dev_dbg(chip->card->dev, "ethersound initialized\n"); + return 0; +} + +static int lx_init_get_version_features(struct lx6464es *chip) +{ + u32 dsp_version; + + int err; + + dev_dbg(chip->card->dev, "->lx_init_get_version_features\n"); + + err = lx_dsp_get_version(chip, &dsp_version); + + if (err == 0) { + u32 freq; + + dev_info(chip->card->dev, "DSP version: V%02d.%02d #%d\n", + (dsp_version>>16) & 0xff, (dsp_version>>8) & 0xff, + dsp_version & 0xff); + + /* later: what firmware version do we expect? */ + + /* retrieve Play/Rec features */ + /* done here because we may have to handle alternate + * DSP files. */ + /* later */ + + /* init the EtherSound sample rate */ + err = lx_dsp_get_clock_frequency(chip, &freq); + if (err == 0) + chip->board_sample_rate = freq; + dev_dbg(chip->card->dev, "actual clock frequency %d\n", freq); + } else { + dev_err(chip->card->dev, "DSP corrupted \n"); + err = -EAGAIN; + } + + return err; +} + +static int lx_set_granularity(struct lx6464es *chip, u32 gran) +{ + int err = 0; + u32 snapped_gran = MICROBLAZE_IBL_MIN; + + dev_dbg(chip->card->dev, "->lx_set_granularity\n"); + + /* blocksize is a power of 2 */ + while ((snapped_gran < gran) && + (snapped_gran < MICROBLAZE_IBL_MAX)) { + snapped_gran *= 2; + } + + if (snapped_gran == chip->pcm_granularity) + return 0; + + err = lx_dsp_set_granularity(chip, snapped_gran); + if (err < 0) { + dev_warn(chip->card->dev, "could not set granularity\n"); + err = -EAGAIN; + } + + if (snapped_gran != gran) + dev_err(chip->card->dev, "snapped blocksize to %d\n", snapped_gran); + + dev_dbg(chip->card->dev, "set blocksize on board %d\n", snapped_gran); + chip->pcm_granularity = snapped_gran; + + return err; +} + +/* initialize and test the xilinx dsp chip */ +static int lx_init_dsp(struct lx6464es *chip) +{ + int err; + int i; + + dev_dbg(chip->card->dev, "->lx_init_dsp\n"); + + dev_dbg(chip->card->dev, "initialize board\n"); + err = lx_init_xilinx_reset(chip); + if (err) + return err; + + dev_dbg(chip->card->dev, "testing board\n"); + err = lx_init_xilinx_test(chip); + if (err) + return err; + + dev_dbg(chip->card->dev, "initialize ethersound configuration\n"); + err = lx_init_ethersound_config(chip); + if (err) + return err; + + lx_irq_enable(chip); + + /** \todo the mac address should be ready by not, but it isn't, + * so we wait for it */ + for (i = 0; i != 1000; ++i) { + err = lx_dsp_get_mac(chip); + if (err) + return err; + if (chip->mac_address[0] || chip->mac_address[1] || chip->mac_address[2] || + chip->mac_address[3] || chip->mac_address[4] || chip->mac_address[5]) + goto mac_ready; + msleep(1); + } + return -ETIMEDOUT; + +mac_ready: + dev_dbg(chip->card->dev, "mac address ready read after: %dms\n", i); + dev_info(chip->card->dev, + "mac address: %02X.%02X.%02X.%02X.%02X.%02X\n", + chip->mac_address[0], chip->mac_address[1], chip->mac_address[2], + chip->mac_address[3], chip->mac_address[4], chip->mac_address[5]); + + err = lx_init_get_version_features(chip); + if (err) + return err; + + lx_set_granularity(chip, MICROBLAZE_IBL_DEFAULT); + + chip->playback_mute = 0; + + return err; +} + +static const struct snd_pcm_ops lx_ops_playback = { + .open = lx_pcm_open, + .close = lx_pcm_close, + .prepare = lx_pcm_prepare, + .hw_params = lx_pcm_hw_params_playback, + .hw_free = lx_pcm_hw_free, + .trigger = lx_pcm_trigger, + .pointer = lx_pcm_stream_pointer, +}; + +static const struct snd_pcm_ops lx_ops_capture = { + .open = lx_pcm_open, + .close = lx_pcm_close, + .prepare = lx_pcm_prepare, + .hw_params = lx_pcm_hw_params_capture, + .hw_free = lx_pcm_hw_free, + .trigger = lx_pcm_trigger, + .pointer = lx_pcm_stream_pointer, +}; + +static int lx_pcm_create(struct lx6464es *chip) +{ + int err; + struct snd_pcm *pcm; + + u32 size = 64 * /* channels */ + 3 * /* 24 bit samples */ + MAX_STREAM_BUFFER * /* periods */ + MICROBLAZE_IBL_MAX * /* frames per period */ + 2; /* duplex */ + + size = PAGE_ALIGN(size); + + /* hardcoded device name & channel count */ + err = snd_pcm_new(chip->card, (char *)card_name, 0, + 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &lx_ops_playback); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &lx_ops_capture); + + pcm->info_flags = 0; + pcm->nonatomic = true; + strscpy(pcm->name, card_name); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, size, size); + + chip->pcm = pcm; + chip->capture_stream.is_capture = 1; + + return 0; +} + +static int lx_control_playback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int lx_control_playback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lx6464es *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->playback_mute; + return 0; +} + +static int lx_control_playback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lx6464es *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int current_value = chip->playback_mute; + + if (current_value != ucontrol->value.integer.value[0]) { + lx_level_unmute(chip, 0, !current_value); + chip->playback_mute = !current_value; + changed = 1; + } + return changed; +} + +static const struct snd_kcontrol_new lx_control_playback_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = lx_control_playback_info, + .get = lx_control_playback_get, + .put = lx_control_playback_put +}; + + + +static void lx_proc_levels_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + u32 levels[64]; + int err; + int i, j; + struct lx6464es *chip = entry->private_data; + + snd_iprintf(buffer, "capture levels:\n"); + err = lx_level_peaks(chip, 1, 64, levels); + if (err < 0) + return; + + for (i = 0; i != 8; ++i) { + for (j = 0; j != 8; ++j) + snd_iprintf(buffer, "%08x ", levels[i*8+j]); + snd_iprintf(buffer, "\n"); + } + + snd_iprintf(buffer, "\nplayback levels:\n"); + + err = lx_level_peaks(chip, 0, 64, levels); + if (err < 0) + return; + + for (i = 0; i != 8; ++i) { + for (j = 0; j != 8; ++j) + snd_iprintf(buffer, "%08x ", levels[i*8+j]); + snd_iprintf(buffer, "\n"); + } + + snd_iprintf(buffer, "\n"); +} + +static int lx_proc_create(struct snd_card *card, struct lx6464es *chip) +{ + return snd_card_ro_proc_new(card, "levels", chip, lx_proc_levels_read); +} + + +static int snd_lx6464es_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct lx6464es *chip = card->private_data; + int err; + + dev_dbg(card->dev, "->snd_lx6464es_create\n"); + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + + pci_set_master(pci); + + /* check if we can restrict PCI DMA transfers to 32 bits */ + err = dma_set_mask(&pci->dev, DMA_BIT_MASK(32)); + if (err < 0) { + dev_err(card->dev, + "architecture does not support 32bit PCI busmaster DMA\n"); + return -ENXIO; + } + + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + /* initialize synchronization structs */ + mutex_init(&chip->lock); + mutex_init(&chip->msg_lock); + mutex_init(&chip->setup_mutex); + + /* request resources */ + err = pcim_request_all_regions(pci, card_name); + if (err < 0) + return err; + + /* plx port */ + chip->port_plx = pci_resource_start(pci, 1); + chip->port_plx_remapped = devm_ioport_map(&pci->dev, chip->port_plx, + pci_resource_len(pci, 1)); + if (!chip->port_plx_remapped) + return -ENOMEM; + + /* dsp port */ + chip->port_dsp_bar = pcim_iomap(pci, 2, 0); + if (!chip->port_dsp_bar) + return -ENOMEM; + + err = devm_request_threaded_irq(&pci->dev, pci->irq, lx_interrupt, + lx_threaded_irq, IRQF_SHARED, + KBUILD_MODNAME, chip); + if (err) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return err; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_lx6464es_free; + + err = lx_init_dsp(chip); + if (err < 0) { + dev_err(card->dev, "error during DSP initialization\n"); + return err; + } + + err = lx_pcm_create(chip); + if (err < 0) + return err; + + err = lx_proc_create(card, chip); + if (err < 0) + return err; + + err = snd_ctl_add(card, snd_ctl_new1(&lx_control_playback_switch, + chip)); + if (err < 0) + return err; + + return 0; +} + +static int snd_lx6464es_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct lx6464es *chip; + int err; + + dev_dbg(&pci->dev, "->snd_lx6464es_probe\n"); + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + err = snd_lx6464es_create(card, pci); + if (err < 0) { + dev_err(card->dev, "error during snd_lx6464es_create\n"); + goto error; + } + + strscpy(card->driver, "LX6464ES"); + sprintf(card->id, "LX6464ES_%02X%02X%02X", + chip->mac_address[3], chip->mac_address[4], chip->mac_address[5]); + + sprintf(card->shortname, "LX6464ES %02X.%02X.%02X.%02X.%02X.%02X", + chip->mac_address[0], chip->mac_address[1], chip->mac_address[2], + chip->mac_address[3], chip->mac_address[4], chip->mac_address[5]); + + sprintf(card->longname, "%s at 0x%lx, 0x%p, irq %i", + card->shortname, chip->port_plx, + chip->port_dsp_bar, chip->irq); + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_dbg(chip->card->dev, "initialization successful\n"); + pci_set_drvdata(pci, card); + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +static struct pci_driver lx6464es_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_lx6464es_ids, + .probe = snd_lx6464es_probe, +}; + +module_pci_driver(lx6464es_driver); diff --git a/sound/pci/lx6464es/lx6464es.h b/sound/pci/lx6464es/lx6464es.h new file mode 100644 index 000000000..1cfe10d23 --- /dev/null +++ b/sound/pci/lx6464es/lx6464es.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * + * Copyright (c) 2009 Tim Blechmann + */ + +#ifndef LX6464ES_H +#define LX6464ES_H + +#include +#include + +#include +#include + +#include "lx_core.h" + +#define LXP "LX6464ES: " + +enum { + ES_cmd_free = 0, /* no command executing */ + ES_cmd_processing = 1, /* execution of a read/write command */ + ES_read_pending = 2, /* a asynchron read command is pending */ + ES_read_finishing = 3, /* a read command has finished waiting (set by + * Interrupt or CancelIrp) */ +}; + +enum lx_stream_status { + LX_STREAM_STATUS_FREE, +/* LX_STREAM_STATUS_OPEN, */ + LX_STREAM_STATUS_SCHEDULE_RUN, +/* LX_STREAM_STATUS_STARTED, */ + LX_STREAM_STATUS_RUNNING, + LX_STREAM_STATUS_SCHEDULE_STOP, +/* LX_STREAM_STATUS_STOPPED, */ +/* LX_STREAM_STATUS_PAUSED */ +}; + + +struct lx_stream { + struct snd_pcm_substream *stream; + snd_pcm_uframes_t frame_pos; + enum lx_stream_status status; /* free, open, running, draining + * pause */ + unsigned int is_capture:1; +}; + + +struct lx6464es { + struct snd_card *card; + struct pci_dev *pci; + int irq; + + u8 mac_address[6]; + + struct mutex lock; /* interrupt lock */ + struct mutex setup_mutex; /* mutex used in hw_params, open + * and close */ + + /* ports */ + unsigned long port_plx; /* io port (size=256) */ + void __iomem *port_plx_remapped; /* remapped plx port */ + void __iomem *port_dsp_bar; /* memory port (32-bit, + * non-prefetchable, + * size=8K) */ + + /* messaging */ + struct mutex msg_lock; /* message lock */ + struct lx_rmh rmh; + u32 irqsrc; + + /* configuration */ + uint freq_ratio : 2; + uint playback_mute : 1; + uint hardware_running[2]; + u32 board_sample_rate; /* sample rate read from + * board */ + u16 pcm_granularity; /* board blocksize */ + + /* dma */ + struct snd_dma_buffer capture_dma_buf; + struct snd_dma_buffer playback_dma_buf; + + /* pcm */ + struct snd_pcm *pcm; + + /* streams */ + struct lx_stream capture_stream; + struct lx_stream playback_stream; +}; + + +#endif /* LX6464ES_H */ diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c new file mode 100644 index 000000000..9909f1067 --- /dev/null +++ b/sound/pci/lx6464es/lx_core.c @@ -0,0 +1,1123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * low-level interface + * + * Copyright (c) 2009 Tim Blechmann + */ + +/* #define RMH_DEBUG 1 */ + +#include +#include +#include +#include + +#include "lx6464es.h" +#include "lx_core.h" + +/* low-level register access */ + +static const unsigned long dsp_port_offsets[] = { + 0, + 0x400, + 0x401, + 0x402, + 0x403, + 0x404, + 0x405, + 0x406, + 0x407, + 0x408, + 0x409, + 0x40a, + 0x40b, + 0x40c, + + 0x410, + 0x411, + 0x412, + 0x413, + 0x414, + 0x415, + 0x416, + + 0x420, + 0x430, + 0x431, + 0x432, + 0x433, + 0x434, + 0x440 +}; + +static void __iomem *lx_dsp_register(struct lx6464es *chip, int port) +{ + void __iomem *base_address = chip->port_dsp_bar; + return base_address + dsp_port_offsets[port]*4; +} + +unsigned long lx_dsp_reg_read(struct lx6464es *chip, int port) +{ + void __iomem *address = lx_dsp_register(chip, port); + return ioread32(address); +} + +static void lx_dsp_reg_readbuf(struct lx6464es *chip, int port, u32 *data, + u32 len) +{ + u32 __iomem *address = lx_dsp_register(chip, port); + int i; + + /* we cannot use memcpy_fromio */ + for (i = 0; i != len; ++i) + data[i] = ioread32(address + i); +} + + +void lx_dsp_reg_write(struct lx6464es *chip, int port, unsigned data) +{ + void __iomem *address = lx_dsp_register(chip, port); + iowrite32(data, address); +} + +static void lx_dsp_reg_writebuf(struct lx6464es *chip, int port, + const u32 *data, u32 len) +{ + u32 __iomem *address = lx_dsp_register(chip, port); + int i; + + /* we cannot use memcpy_to */ + for (i = 0; i != len; ++i) + iowrite32(data[i], address + i); +} + + +static const unsigned long plx_port_offsets[] = { + 0x04, + 0x40, + 0x44, + 0x48, + 0x4c, + 0x50, + 0x54, + 0x58, + 0x5c, + 0x64, + 0x68, + 0x6C +}; + +static void __iomem *lx_plx_register(struct lx6464es *chip, int port) +{ + void __iomem *base_address = chip->port_plx_remapped; + return base_address + plx_port_offsets[port]; +} + +unsigned long lx_plx_reg_read(struct lx6464es *chip, int port) +{ + void __iomem *address = lx_plx_register(chip, port); + return ioread32(address); +} + +void lx_plx_reg_write(struct lx6464es *chip, int port, u32 data) +{ + void __iomem *address = lx_plx_register(chip, port); + iowrite32(data, address); +} + +/* rmh */ + +#ifdef CONFIG_SND_DEBUG +#define CMD_NAME(a) a +#else +#define CMD_NAME(a) NULL +#endif + +#define Reg_CSM_MR 0x00000002 +#define Reg_CSM_MC 0x00000001 + +struct dsp_cmd_info { + u32 dcCodeOp; /* Op Code of the command (usually 1st 24-bits + * word).*/ + u16 dcCmdLength; /* Command length in words of 24 bits.*/ + u16 dcStatusType; /* Status type: 0 for fixed length, 1 for + * random. */ + u16 dcStatusLength; /* Status length (if fixed).*/ + char *dcOpName; +}; + +/* + Initialization and control data for the Microblaze interface + - OpCode: + the opcode field of the command set at the proper offset + - CmdLength + the number of command words + - StatusType + offset in the status registers: 0 means that the return value may be + different from 0, and must be read + - StatusLength + the number of status words (in addition to the return value) +*/ + +static const struct dsp_cmd_info dsp_commands[] = +{ + { (CMD_00_INFO_DEBUG << OPCODE_OFFSET) , 1 /*custom*/ + , 1 , 0 /**/ , CMD_NAME("INFO_DEBUG") }, + { (CMD_01_GET_SYS_CFG << OPCODE_OFFSET) , 1 /**/ + , 1 , 2 /**/ , CMD_NAME("GET_SYS_CFG") }, + { (CMD_02_SET_GRANULARITY << OPCODE_OFFSET) , 1 /**/ + , 1 , 0 /**/ , CMD_NAME("SET_GRANULARITY") }, + { (CMD_03_SET_TIMER_IRQ << OPCODE_OFFSET) , 1 /**/ + , 1 , 0 /**/ , CMD_NAME("SET_TIMER_IRQ") }, + { (CMD_04_GET_EVENT << OPCODE_OFFSET) , 1 /**/ + , 1 , 0 /*up to 10*/ , CMD_NAME("GET_EVENT") }, + { (CMD_05_GET_PIPES << OPCODE_OFFSET) , 1 /**/ + , 1 , 2 /*up to 4*/ , CMD_NAME("GET_PIPES") }, + { (CMD_06_ALLOCATE_PIPE << OPCODE_OFFSET) , 1 /**/ + , 0 , 0 /**/ , CMD_NAME("ALLOCATE_PIPE") }, + { (CMD_07_RELEASE_PIPE << OPCODE_OFFSET) , 1 /**/ + , 0 , 0 /**/ , CMD_NAME("RELEASE_PIPE") }, + { (CMD_08_ASK_BUFFERS << OPCODE_OFFSET) , 1 /**/ + , 1 , MAX_STREAM_BUFFER , CMD_NAME("ASK_BUFFERS") }, + { (CMD_09_STOP_PIPE << OPCODE_OFFSET) , 1 /**/ + , 0 , 0 /*up to 2*/ , CMD_NAME("STOP_PIPE") }, + { (CMD_0A_GET_PIPE_SPL_COUNT << OPCODE_OFFSET) , 1 /**/ + , 1 , 1 /*up to 2*/ , CMD_NAME("GET_PIPE_SPL_COUNT") }, + { (CMD_0B_TOGGLE_PIPE_STATE << OPCODE_OFFSET) , 1 /*up to 5*/ + , 1 , 0 /**/ , CMD_NAME("TOGGLE_PIPE_STATE") }, + { (CMD_0C_DEF_STREAM << OPCODE_OFFSET) , 1 /*up to 4*/ + , 1 , 0 /**/ , CMD_NAME("DEF_STREAM") }, + { (CMD_0D_SET_MUTE << OPCODE_OFFSET) , 3 /**/ + , 1 , 0 /**/ , CMD_NAME("SET_MUTE") }, + { (CMD_0E_GET_STREAM_SPL_COUNT << OPCODE_OFFSET) , 1/**/ + , 1 , 2 /**/ , CMD_NAME("GET_STREAM_SPL_COUNT") }, + { (CMD_0F_UPDATE_BUFFER << OPCODE_OFFSET) , 3 /*up to 4*/ + , 0 , 1 /**/ , CMD_NAME("UPDATE_BUFFER") }, + { (CMD_10_GET_BUFFER << OPCODE_OFFSET) , 1 /**/ + , 1 , 4 /**/ , CMD_NAME("GET_BUFFER") }, + { (CMD_11_CANCEL_BUFFER << OPCODE_OFFSET) , 1 /**/ + , 1 , 1 /*up to 4*/ , CMD_NAME("CANCEL_BUFFER") }, + { (CMD_12_GET_PEAK << OPCODE_OFFSET) , 1 /**/ + , 1 , 1 /**/ , CMD_NAME("GET_PEAK") }, + { (CMD_13_SET_STREAM_STATE << OPCODE_OFFSET) , 1 /**/ + , 1 , 0 /**/ , CMD_NAME("SET_STREAM_STATE") }, +}; + +static void lx_message_init(struct lx_rmh *rmh, enum cmd_mb_opcodes cmd) +{ + snd_BUG_ON(cmd >= CMD_14_INVALID); + + rmh->cmd[0] = dsp_commands[cmd].dcCodeOp; + rmh->cmd_len = dsp_commands[cmd].dcCmdLength; + rmh->stat_len = dsp_commands[cmd].dcStatusLength; + rmh->dsp_stat = dsp_commands[cmd].dcStatusType; + rmh->cmd_idx = cmd; + memset(&rmh->cmd[1], 0, (REG_CRM_NUMBER - 1) * sizeof(u32)); + +#ifdef CONFIG_SND_DEBUG + memset(rmh->stat, 0, REG_CRM_NUMBER * sizeof(u32)); +#endif +#ifdef RMH_DEBUG + rmh->cmd_idx = cmd; +#endif +} + +#ifdef RMH_DEBUG +#define LXRMH "lx6464es rmh: " +static void lx_message_dump(struct lx_rmh *rmh) +{ + u8 idx = rmh->cmd_idx; + int i; + + pr_debug(LXRMH "command %s\n", dsp_commands[idx].dcOpName); + + for (i = 0; i != rmh->cmd_len; ++i) + pr_debug(LXRMH "\tcmd[%d] %08x\n", i, rmh->cmd[i]); + + for (i = 0; i != rmh->stat_len; ++i) + pr_debug(LXRMH "\tstat[%d]: %08x\n", i, rmh->stat[i]); + pr_debug("\n"); +} +#else +static inline void lx_message_dump(struct lx_rmh *rmh) +{} +#endif + + + +/* sleep 500 - 100 = 400 times 100us -> the timeout is >= 40 ms */ +#define XILINX_TIMEOUT_MS 40 +#define XILINX_POLL_NO_SLEEP 100 +#define XILINX_POLL_ITERATIONS 150 + + +static int lx_message_send_atomic(struct lx6464es *chip, struct lx_rmh *rmh) +{ + u32 reg = ED_DSP_TIMED_OUT; + int dwloop; + + if (lx_dsp_reg_read(chip, eReg_CSM) & (Reg_CSM_MC | Reg_CSM_MR)) { + dev_err(chip->card->dev, "PIOSendMessage eReg_CSM %x\n", reg); + return -EBUSY; + } + + /* write command */ + lx_dsp_reg_writebuf(chip, eReg_CRM1, rmh->cmd, rmh->cmd_len); + + /* MicoBlaze gogogo */ + lx_dsp_reg_write(chip, eReg_CSM, Reg_CSM_MC); + + /* wait for device to answer */ + for (dwloop = 0; dwloop != XILINX_TIMEOUT_MS * 1000; ++dwloop) { + if (lx_dsp_reg_read(chip, eReg_CSM) & Reg_CSM_MR) { + if (rmh->dsp_stat == 0) + reg = lx_dsp_reg_read(chip, eReg_CRM1); + else + reg = 0; + goto polling_successful; + } else + udelay(1); + } + dev_warn(chip->card->dev, "TIMEOUT lx_message_send_atomic! " + "polling failed\n"); + +polling_successful: + if ((reg & ERROR_VALUE) == 0) { + /* read response */ + if (rmh->stat_len) { + snd_BUG_ON(rmh->stat_len >= (REG_CRM_NUMBER-1)); + lx_dsp_reg_readbuf(chip, eReg_CRM2, rmh->stat, + rmh->stat_len); + } + } else + dev_err(chip->card->dev, "rmh error: %08x\n", reg); + + /* clear Reg_CSM_MR */ + lx_dsp_reg_write(chip, eReg_CSM, 0); + + switch (reg) { + case ED_DSP_TIMED_OUT: + dev_warn(chip->card->dev, "lx_message_send: dsp timeout\n"); + return -ETIMEDOUT; + + case ED_DSP_CRASHED: + dev_warn(chip->card->dev, "lx_message_send: dsp crashed\n"); + return -EAGAIN; + } + + lx_message_dump(rmh); + + return reg; +} + + +/* low-level dsp access */ +int lx_dsp_get_version(struct lx6464es *chip, u32 *rdsp_version) +{ + int ret; + + guard(mutex)(&chip->msg_lock); + + lx_message_init(&chip->rmh, CMD_01_GET_SYS_CFG); + ret = lx_message_send_atomic(chip, &chip->rmh); + + *rdsp_version = chip->rmh.stat[1]; + return ret; +} + +int lx_dsp_get_clock_frequency(struct lx6464es *chip, u32 *rfreq) +{ + u32 freq_raw = 0; + u32 freq = 0; + u32 frequency = 0; + int ret; + + guard(mutex)(&chip->msg_lock); + + lx_message_init(&chip->rmh, CMD_01_GET_SYS_CFG); + ret = lx_message_send_atomic(chip, &chip->rmh); + + if (ret == 0) { + freq_raw = chip->rmh.stat[0] >> FREQ_FIELD_OFFSET; + freq = freq_raw & XES_FREQ_COUNT8_MASK; + + if ((freq < XES_FREQ_COUNT8_48_MAX) || + (freq > XES_FREQ_COUNT8_44_MIN)) + frequency = 0; /* unknown */ + else if (freq >= XES_FREQ_COUNT8_44_MAX) + frequency = 44100; + else + frequency = 48000; + } + + *rfreq = frequency * chip->freq_ratio; + + return ret; +} + +int lx_dsp_get_mac(struct lx6464es *chip) +{ + u32 macmsb, maclsb; + + macmsb = lx_dsp_reg_read(chip, eReg_ADMACESMSB) & 0x00FFFFFF; + maclsb = lx_dsp_reg_read(chip, eReg_ADMACESLSB) & 0x00FFFFFF; + + /* todo: endianess handling */ + chip->mac_address[5] = ((u8 *)(&maclsb))[0]; + chip->mac_address[4] = ((u8 *)(&maclsb))[1]; + chip->mac_address[3] = ((u8 *)(&maclsb))[2]; + chip->mac_address[2] = ((u8 *)(&macmsb))[0]; + chip->mac_address[1] = ((u8 *)(&macmsb))[1]; + chip->mac_address[0] = ((u8 *)(&macmsb))[2]; + + return 0; +} + + +int lx_dsp_set_granularity(struct lx6464es *chip, u32 gran) +{ + guard(mutex)(&chip->msg_lock); + + lx_message_init(&chip->rmh, CMD_02_SET_GRANULARITY); + chip->rmh.cmd[0] |= gran; + + return lx_message_send_atomic(chip, &chip->rmh); +} + +int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data) +{ + int ret; + + guard(mutex)(&chip->msg_lock); + + lx_message_init(&chip->rmh, CMD_04_GET_EVENT); + chip->rmh.stat_len = 9; /* we don't necessarily need the full length */ + + ret = lx_message_send_atomic(chip, &chip->rmh); + + if (!ret) + memcpy(data, chip->rmh.stat, chip->rmh.stat_len * sizeof(u32)); + + return ret; +} + +#define PIPE_INFO_TO_CMD(capture, pipe) \ + ((u32)((u32)(pipe) | ((capture) ? ID_IS_CAPTURE : 0L)) << ID_OFFSET) + + + +/* low-level pipe handling */ +int lx_pipe_allocate(struct lx6464es *chip, u32 pipe, int is_capture, + int channels) +{ + int err; + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_06_ALLOCATE_PIPE); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= channels; + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (err != 0) + dev_err(chip->card->dev, "could not allocate pipe\n"); + + return err; +} + +int lx_pipe_release(struct lx6464es *chip, u32 pipe, int is_capture) +{ + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_07_RELEASE_PIPE); + + chip->rmh.cmd[0] |= pipe_cmd; + + return lx_message_send_atomic(chip, &chip->rmh); +} + +int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture, + u32 *r_needed, u32 *r_freed, u32 *size_array) +{ + int err; + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + +#ifdef CONFIG_SND_DEBUG + if (size_array) + memset(size_array, 0, sizeof(u32)*MAX_STREAM_BUFFER); +#endif + + *r_needed = 0; + *r_freed = 0; + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_08_ASK_BUFFERS); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (!err) { + int i; + for (i = 0; i < MAX_STREAM_BUFFER; ++i) { + u32 stat = chip->rmh.stat[i]; + if (stat & (BF_EOB << BUFF_FLAGS_OFFSET)) { + /* finished */ + *r_freed += 1; + if (size_array) + size_array[i] = stat & MASK_DATA_SIZE; + } else if ((stat & (BF_VALID << BUFF_FLAGS_OFFSET)) + == 0) + /* free */ + *r_needed += 1; + } + + dev_dbg(chip->card->dev, + "CMD_08_ASK_BUFFERS: needed %d, freed %d\n", + *r_needed, *r_freed); + for (i = 0; i < MAX_STREAM_BUFFER && i < chip->rmh.stat_len; + ++i) { + dev_dbg(chip->card->dev, " stat[%d]: %x, %x\n", i, + chip->rmh.stat[i], + chip->rmh.stat[i] & MASK_DATA_SIZE); + } + } + + return err; +} + + +int lx_pipe_stop(struct lx6464es *chip, u32 pipe, int is_capture) +{ + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_09_STOP_PIPE); + + chip->rmh.cmd[0] |= pipe_cmd; + + return lx_message_send_atomic(chip, &chip->rmh); +} + +static int lx_pipe_toggle_state(struct lx6464es *chip, u32 pipe, int is_capture) +{ + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_0B_TOGGLE_PIPE_STATE); + + chip->rmh.cmd[0] |= pipe_cmd; + + return lx_message_send_atomic(chip, &chip->rmh); +} + + +int lx_pipe_start(struct lx6464es *chip, u32 pipe, int is_capture) +{ + int err; + + err = lx_pipe_wait_for_idle(chip, pipe, is_capture); + if (err < 0) + return err; + + err = lx_pipe_toggle_state(chip, pipe, is_capture); + + return err; +} + +int lx_pipe_pause(struct lx6464es *chip, u32 pipe, int is_capture) +{ + int err = 0; + + err = lx_pipe_wait_for_start(chip, pipe, is_capture); + if (err < 0) + return err; + + err = lx_pipe_toggle_state(chip, pipe, is_capture); + + return err; +} + + +int lx_pipe_sample_count(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *rsample_count) +{ + int err; + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_0A_GET_PIPE_SPL_COUNT); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.stat_len = 2; /* need all words here! */ + + err = lx_message_send_atomic(chip, &chip->rmh); /* don't sleep! */ + + if (err != 0) + dev_err(chip->card->dev, + "could not query pipe's sample count\n"); + else { + *rsample_count = ((u64)(chip->rmh.stat[0] & MASK_SPL_COUNT_HI) + << 24) /* hi part */ + + chip->rmh.stat[1]; /* lo part */ + } + + return err; +} + +int lx_pipe_state(struct lx6464es *chip, u32 pipe, int is_capture, u16 *rstate) +{ + int err; + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_0A_GET_PIPE_SPL_COUNT); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (err != 0) + dev_err(chip->card->dev, "could not query pipe's state\n"); + else + *rstate = (chip->rmh.stat[0] >> PSTATE_OFFSET) & 0x0F; + + return err; +} + +static int lx_pipe_wait_for_state(struct lx6464es *chip, u32 pipe, + int is_capture, u16 state) +{ + int i; + + /* max 2*PCMOnlyGranularity = 2*1024 at 44100 = < 50 ms: + * timeout 50 ms */ + for (i = 0; i != 50; ++i) { + u16 current_state; + int err = lx_pipe_state(chip, pipe, is_capture, ¤t_state); + + if (err < 0) + return err; + + if (!err && current_state == state) + return 0; + + mdelay(1); + } + + return -ETIMEDOUT; +} + +int lx_pipe_wait_for_start(struct lx6464es *chip, u32 pipe, int is_capture) +{ + return lx_pipe_wait_for_state(chip, pipe, is_capture, PSTATE_RUN); +} + +int lx_pipe_wait_for_idle(struct lx6464es *chip, u32 pipe, int is_capture) +{ + return lx_pipe_wait_for_state(chip, pipe, is_capture, PSTATE_IDLE); +} + +/* low-level stream handling */ +int lx_stream_set_state(struct lx6464es *chip, u32 pipe, + int is_capture, enum stream_state_t state) +{ + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_13_SET_STREAM_STATE); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= state; + + return lx_message_send_atomic(chip, &chip->rmh); +} + +int lx_stream_set_format(struct lx6464es *chip, struct snd_pcm_runtime *runtime, + u32 pipe, int is_capture) +{ + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + u32 channels = runtime->channels; + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_0C_DEF_STREAM); + + chip->rmh.cmd[0] |= pipe_cmd; + + if (runtime->sample_bits == 16) + /* 16 bit format */ + chip->rmh.cmd[0] |= (STREAM_FMT_16b << STREAM_FMT_OFFSET); + + if (snd_pcm_format_little_endian(runtime->format)) + /* little endian/intel format */ + chip->rmh.cmd[0] |= (STREAM_FMT_intel << STREAM_FMT_OFFSET); + + chip->rmh.cmd[0] |= channels-1; + + return lx_message_send_atomic(chip, &chip->rmh); +} + +int lx_stream_state(struct lx6464es *chip, u32 pipe, int is_capture, + int *rstate) +{ + int err; + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_0E_GET_STREAM_SPL_COUNT); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + *rstate = (chip->rmh.stat[0] & SF_START) ? START_STATE : PAUSE_STATE; + + return err; +} + +int lx_stream_sample_position(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *r_bytepos) +{ + int err; + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_0E_GET_STREAM_SPL_COUNT); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + *r_bytepos = ((u64) (chip->rmh.stat[0] & MASK_SPL_COUNT_HI) + << 32) /* hi part */ + + chip->rmh.stat[1]; /* lo part */ + + return err; +} + +/* low-level buffer handling */ +int lx_buffer_give(struct lx6464es *chip, u32 pipe, int is_capture, + u32 buffer_size, u32 buf_address_lo, u32 buf_address_hi, + u32 *r_buffer_index) +{ + int err; + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_0F_UPDATE_BUFFER); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= BF_NOTIFY_EOB; /* request interrupt notification */ + + /* todo: pause request, circular buffer */ + + chip->rmh.cmd[1] = buffer_size & MASK_DATA_SIZE; + chip->rmh.cmd[2] = buf_address_lo; + + if (buf_address_hi) { + chip->rmh.cmd_len = 4; + chip->rmh.cmd[3] = buf_address_hi; + chip->rmh.cmd[0] |= BF_64BITS_ADR; + } + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (err == 0) { + *r_buffer_index = chip->rmh.stat[0]; + return err; + } + + if (err == EB_RBUFFERS_TABLE_OVERFLOW) + dev_err(chip->card->dev, + "lx_buffer_give EB_RBUFFERS_TABLE_OVERFLOW\n"); + + if (err == EB_INVALID_STREAM) + dev_err(chip->card->dev, + "lx_buffer_give EB_INVALID_STREAM\n"); + + if (err == EB_CMD_REFUSED) + dev_err(chip->card->dev, + "lx_buffer_give EB_CMD_REFUSED\n"); + + return err; +} + +int lx_buffer_free(struct lx6464es *chip, u32 pipe, int is_capture, + u32 *r_buffer_size) +{ + int err; + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_11_CANCEL_BUFFER); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= MASK_BUFFER_ID; /* ask for the current buffer: the + * microblaze will seek for it */ + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (err == 0) + *r_buffer_size = chip->rmh.stat[0] & MASK_DATA_SIZE; + + return err; +} + +int lx_buffer_cancel(struct lx6464es *chip, u32 pipe, int is_capture, + u32 buffer_index) +{ + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_11_CANCEL_BUFFER); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= buffer_index; + + return lx_message_send_atomic(chip, &chip->rmh); +} + + +/* low-level gain/peak handling + * + * \todo: can we unmute capture/playback channels independently? + * + * */ +int lx_level_unmute(struct lx6464es *chip, int is_capture, int unmute) +{ + /* bit set to 1: channel muted */ + u64 mute_mask = unmute ? 0 : 0xFFFFFFFFFFFFFFFFLLU; + + guard(mutex)(&chip->msg_lock); + lx_message_init(&chip->rmh, CMD_0D_SET_MUTE); + + chip->rmh.cmd[0] |= PIPE_INFO_TO_CMD(is_capture, 0); + + chip->rmh.cmd[1] = (u32)(mute_mask >> (u64)32); /* hi part */ + chip->rmh.cmd[2] = (u32)(mute_mask & (u64)0xFFFFFFFF); /* lo part */ + + dev_dbg(chip->card->dev, + "mute %x %x %x\n", chip->rmh.cmd[0], chip->rmh.cmd[1], + chip->rmh.cmd[2]); + + return lx_message_send_atomic(chip, &chip->rmh); +} + +static const u32 peak_map[] = { + 0x00000109, /* -90.308dB */ + 0x0000083B, /* -72.247dB */ + 0x000020C4, /* -60.205dB */ + 0x00008273, /* -48.030dB */ + 0x00020756, /* -36.005dB */ + 0x00040C37, /* -30.001dB */ + 0x00081385, /* -24.002dB */ + 0x00101D3F, /* -18.000dB */ + 0x0016C310, /* -15.000dB */ + 0x002026F2, /* -12.001dB */ + 0x002D6A86, /* -9.000dB */ + 0x004026E6, /* -6.004dB */ + 0x005A9DF6, /* -3.000dB */ + 0x0065AC8B, /* -2.000dB */ + 0x00721481, /* -1.000dB */ + 0x007FFFFF, /* FS */ +}; + +int lx_level_peaks(struct lx6464es *chip, int is_capture, int channels, + u32 *r_levels) +{ + int err = 0; + int i; + + guard(mutex)(&chip->msg_lock); + for (i = 0; i < channels; i += 4) { + u32 s0, s1, s2, s3; + + lx_message_init(&chip->rmh, CMD_12_GET_PEAK); + chip->rmh.cmd[0] |= PIPE_INFO_TO_CMD(is_capture, i); + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (err == 0) { + s0 = peak_map[chip->rmh.stat[0] & 0x0F]; + s1 = peak_map[(chip->rmh.stat[0] >> 4) & 0xf]; + s2 = peak_map[(chip->rmh.stat[0] >> 8) & 0xf]; + s3 = peak_map[(chip->rmh.stat[0] >> 12) & 0xf]; + } else + s0 = s1 = s2 = s3 = 0; + + r_levels[0] = s0; + r_levels[1] = s1; + r_levels[2] = s2; + r_levels[3] = s3; + + r_levels += 4; + } + + return err; +} + +/* interrupt handling */ +#define PCX_IRQ_NONE 0 +#define IRQCS_ACTIVE_PCIDB BIT(13) +#define IRQCS_ENABLE_PCIIRQ BIT(8) +#define IRQCS_ENABLE_PCIDB BIT(9) + +static u32 lx_interrupt_test_ack(struct lx6464es *chip) +{ + u32 irqcs = lx_plx_reg_read(chip, ePLX_IRQCS); + + /* Test if PCI Doorbell interrupt is active */ + if (irqcs & IRQCS_ACTIVE_PCIDB) { + u32 temp; + irqcs = PCX_IRQ_NONE; + + while ((temp = lx_plx_reg_read(chip, ePLX_L2PCIDB))) { + /* RAZ interrupt */ + irqcs |= temp; + lx_plx_reg_write(chip, ePLX_L2PCIDB, temp); + } + + return irqcs; + } + return PCX_IRQ_NONE; +} + +static int lx_interrupt_ack(struct lx6464es *chip, u32 *r_irqsrc, + int *r_async_pending, int *r_async_escmd) +{ + u32 irq_async; + u32 irqsrc = lx_interrupt_test_ack(chip); + + if (irqsrc == PCX_IRQ_NONE) + return 0; + + *r_irqsrc = irqsrc; + + irq_async = irqsrc & MASK_SYS_ASYNC_EVENTS; /* + EtherSound response + * (set by xilinx) + EOB */ + + if (irq_async & MASK_SYS_STATUS_ESA) { + irq_async &= ~MASK_SYS_STATUS_ESA; + *r_async_escmd = 1; + } + + if (irq_async) { + /* dev_dbg(chip->card->dev, "interrupt: async event pending\n"); */ + *r_async_pending = 1; + } + + return 1; +} + +static int lx_interrupt_handle_async_events(struct lx6464es *chip, u32 irqsrc, + int *r_freq_changed, + u64 *r_notified_in_pipe_mask, + u64 *r_notified_out_pipe_mask) +{ + int err; + u32 stat[9]; /* answer from CMD_04_GET_EVENT */ + + /* We can optimize this to not read dumb events. + * Answer words are in the following order: + * Stat[0] general status + * Stat[1] end of buffer OUT pF + * Stat[2] end of buffer OUT pf + * Stat[3] end of buffer IN pF + * Stat[4] end of buffer IN pf + * Stat[5] MSB underrun + * Stat[6] LSB underrun + * Stat[7] MSB overrun + * Stat[8] LSB overrun + * */ + + int eb_pending_out = (irqsrc & MASK_SYS_STATUS_EOBO) ? 1 : 0; + int eb_pending_in = (irqsrc & MASK_SYS_STATUS_EOBI) ? 1 : 0; + + *r_freq_changed = (irqsrc & MASK_SYS_STATUS_FREQ) ? 1 : 0; + + err = lx_dsp_read_async_events(chip, stat); + if (err < 0) + return err; + + if (eb_pending_in) { + *r_notified_in_pipe_mask = ((u64)stat[3] << 32) + + stat[4]; + dev_dbg(chip->card->dev, "interrupt: EOBI pending %llx\n", + *r_notified_in_pipe_mask); + } + if (eb_pending_out) { + *r_notified_out_pipe_mask = ((u64)stat[1] << 32) + + stat[2]; + dev_dbg(chip->card->dev, "interrupt: EOBO pending %llx\n", + *r_notified_out_pipe_mask); + } + + /* todo: handle xrun notification */ + + return err; +} + +static int lx_interrupt_request_new_buffer(struct lx6464es *chip, + struct lx_stream *lx_stream) +{ + struct snd_pcm_substream *substream = lx_stream->stream; + const unsigned int is_capture = lx_stream->is_capture; + int err; + + const u32 period_bytes = snd_pcm_lib_period_bytes(substream); + const u32 pos = lx_stream->frame_pos; + const u32 next_pos = ((pos+1) == substream->runtime->periods) ? + 0 : pos + 1; + + dma_addr_t buf = substream->dma_buffer.addr + pos * period_bytes; + u32 buf_hi = 0; + u32 buf_lo = 0; + u32 buffer_index = 0; + + u32 needed, freed; + u32 size_array[MAX_STREAM_BUFFER]; + + dev_dbg(chip->card->dev, "->lx_interrupt_request_new_buffer\n"); + + guard(mutex)(&chip->lock); + + err = lx_buffer_ask(chip, 0, is_capture, &needed, &freed, size_array); + dev_dbg(chip->card->dev, + "interrupt: needed %d, freed %d\n", needed, freed); + + unpack_pointer(buf, &buf_lo, &buf_hi); + err = lx_buffer_give(chip, 0, is_capture, period_bytes, buf_lo, buf_hi, + &buffer_index); + dev_dbg(chip->card->dev, + "interrupt: gave buffer index %x on 0x%lx (%d bytes)\n", + buffer_index, (unsigned long)buf, period_bytes); + + lx_stream->frame_pos = next_pos; + + return err; +} + +irqreturn_t lx_interrupt(int irq, void *dev_id) +{ + struct lx6464es *chip = dev_id; + int async_pending, async_escmd; + u32 irqsrc; + bool wake_thread = false; + + dev_dbg(chip->card->dev, + "**************************************************\n"); + + if (!lx_interrupt_ack(chip, &irqsrc, &async_pending, &async_escmd)) { + dev_dbg(chip->card->dev, "IRQ_NONE\n"); + return IRQ_NONE; /* this device did not cause the interrupt */ + } + + if (irqsrc & MASK_SYS_STATUS_CMD_DONE) + return IRQ_HANDLED; + + if (irqsrc & MASK_SYS_STATUS_EOBI) + dev_dbg(chip->card->dev, "interrupt: EOBI\n"); + + if (irqsrc & MASK_SYS_STATUS_EOBO) + dev_dbg(chip->card->dev, "interrupt: EOBO\n"); + + if (irqsrc & MASK_SYS_STATUS_URUN) + dev_dbg(chip->card->dev, "interrupt: URUN\n"); + + if (irqsrc & MASK_SYS_STATUS_ORUN) + dev_dbg(chip->card->dev, "interrupt: ORUN\n"); + + if (async_pending) { + wake_thread = true; + chip->irqsrc = irqsrc; + } + + if (async_escmd) { + /* backdoor for ethersound commands + * + * for now, we do not need this + * + * */ + + dev_dbg(chip->card->dev, "interrupt requests escmd handling\n"); + } + + return wake_thread ? IRQ_WAKE_THREAD : IRQ_HANDLED; +} + +irqreturn_t lx_threaded_irq(int irq, void *dev_id) +{ + struct lx6464es *chip = dev_id; + u64 notified_in_pipe_mask = 0; + u64 notified_out_pipe_mask = 0; + int freq_changed; + int err; + + /* handle async events */ + err = lx_interrupt_handle_async_events(chip, chip->irqsrc, + &freq_changed, + ¬ified_in_pipe_mask, + ¬ified_out_pipe_mask); + if (err) + dev_err(chip->card->dev, "error handling async events\n"); + + if (notified_in_pipe_mask) { + struct lx_stream *lx_stream = &chip->capture_stream; + + dev_dbg(chip->card->dev, + "requesting audio transfer for capture\n"); + err = lx_interrupt_request_new_buffer(chip, lx_stream); + if (err < 0) + dev_err(chip->card->dev, + "cannot request new buffer for capture\n"); + snd_pcm_period_elapsed(lx_stream->stream); + } + + if (notified_out_pipe_mask) { + struct lx_stream *lx_stream = &chip->playback_stream; + + dev_dbg(chip->card->dev, + "requesting audio transfer for playback\n"); + err = lx_interrupt_request_new_buffer(chip, lx_stream); + if (err < 0) + dev_err(chip->card->dev, + "cannot request new buffer for playback\n"); + snd_pcm_period_elapsed(lx_stream->stream); + } + + return IRQ_HANDLED; +} + + +static void lx_irq_set(struct lx6464es *chip, int enable) +{ + u32 reg = lx_plx_reg_read(chip, ePLX_IRQCS); + + /* enable/disable interrupts + * + * Set the Doorbell and PCI interrupt enable bits + * + * */ + if (enable) + reg |= (IRQCS_ENABLE_PCIIRQ | IRQCS_ENABLE_PCIDB); + else + reg &= ~(IRQCS_ENABLE_PCIIRQ | IRQCS_ENABLE_PCIDB); + lx_plx_reg_write(chip, ePLX_IRQCS, reg); +} + +void lx_irq_enable(struct lx6464es *chip) +{ + dev_dbg(chip->card->dev, "->lx_irq_enable\n"); + lx_irq_set(chip, 1); +} + +void lx_irq_disable(struct lx6464es *chip) +{ + dev_dbg(chip->card->dev, "->lx_irq_disable\n"); + lx_irq_set(chip, 0); +} diff --git a/sound/pci/lx6464es/lx_core.h b/sound/pci/lx6464es/lx_core.h new file mode 100644 index 000000000..c1113439f --- /dev/null +++ b/sound/pci/lx6464es/lx_core.h @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * low-level interface + * + * Copyright (c) 2009 Tim Blechmann + */ + +#ifndef LX_CORE_H +#define LX_CORE_H + +#include + +#include "lx_defs.h" + +#define REG_CRM_NUMBER 12 + +struct lx6464es; + +/* low-level register access */ + +/* dsp register access */ +enum { + eReg_BASE, + eReg_CSM, + eReg_CRM1, + eReg_CRM2, + eReg_CRM3, + eReg_CRM4, + eReg_CRM5, + eReg_CRM6, + eReg_CRM7, + eReg_CRM8, + eReg_CRM9, + eReg_CRM10, + eReg_CRM11, + eReg_CRM12, + + eReg_ICR, + eReg_CVR, + eReg_ISR, + eReg_RXHTXH, + eReg_RXMTXM, + eReg_RHLTXL, + eReg_RESETDSP, + + eReg_CSUF, + eReg_CSES, + eReg_CRESMSB, + eReg_CRESLSB, + eReg_ADMACESMSB, + eReg_ADMACESLSB, + eReg_CONFES, + + eMaxPortLx +}; + +unsigned long lx_dsp_reg_read(struct lx6464es *chip, int port); +void lx_dsp_reg_write(struct lx6464es *chip, int port, unsigned data); + +/* plx register access */ +enum { + ePLX_PCICR, + + ePLX_MBOX0, + ePLX_MBOX1, + ePLX_MBOX2, + ePLX_MBOX3, + ePLX_MBOX4, + ePLX_MBOX5, + ePLX_MBOX6, + ePLX_MBOX7, + + ePLX_L2PCIDB, + ePLX_IRQCS, + ePLX_CHIPSC, + + eMaxPort +}; + +unsigned long lx_plx_reg_read(struct lx6464es *chip, int port); +void lx_plx_reg_write(struct lx6464es *chip, int port, u32 data); + +/* rhm */ +struct lx_rmh { + u16 cmd_len; /* length of the command to send (WORDs) */ + u16 stat_len; /* length of the status received (WORDs) */ + u16 dsp_stat; /* status type, RMP_SSIZE_XXX */ + u16 cmd_idx; /* index of the command */ + u32 cmd[REG_CRM_NUMBER]; + u32 stat[REG_CRM_NUMBER]; +}; + + +/* low-level dsp access */ +int lx_dsp_get_version(struct lx6464es *chip, u32 *rdsp_version); +int lx_dsp_get_clock_frequency(struct lx6464es *chip, u32 *rfreq); +int lx_dsp_set_granularity(struct lx6464es *chip, u32 gran); +int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data); +int lx_dsp_get_mac(struct lx6464es *chip); + + +/* low-level pipe handling */ +int lx_pipe_allocate(struct lx6464es *chip, u32 pipe, int is_capture, + int channels); +int lx_pipe_release(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_sample_count(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *rsample_count); +int lx_pipe_state(struct lx6464es *chip, u32 pipe, int is_capture, u16 *rstate); +int lx_pipe_stop(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_start(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_pause(struct lx6464es *chip, u32 pipe, int is_capture); + +int lx_pipe_wait_for_start(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_wait_for_idle(struct lx6464es *chip, u32 pipe, int is_capture); + +/* low-level stream handling */ +int lx_stream_set_format(struct lx6464es *chip, struct snd_pcm_runtime *runtime, + u32 pipe, int is_capture); +int lx_stream_state(struct lx6464es *chip, u32 pipe, int is_capture, + int *rstate); +int lx_stream_sample_position(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *r_bytepos); + +int lx_stream_set_state(struct lx6464es *chip, u32 pipe, + int is_capture, enum stream_state_t state); + +static inline int lx_stream_start(struct lx6464es *chip, u32 pipe, + int is_capture) +{ + return lx_stream_set_state(chip, pipe, is_capture, SSTATE_RUN); +} + +static inline int lx_stream_pause(struct lx6464es *chip, u32 pipe, + int is_capture) +{ + return lx_stream_set_state(chip, pipe, is_capture, SSTATE_PAUSE); +} + +static inline int lx_stream_stop(struct lx6464es *chip, u32 pipe, + int is_capture) +{ + return lx_stream_set_state(chip, pipe, is_capture, SSTATE_STOP); +} + +/* low-level buffer handling */ +int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture, + u32 *r_needed, u32 *r_freed, u32 *size_array); +int lx_buffer_give(struct lx6464es *chip, u32 pipe, int is_capture, + u32 buffer_size, u32 buf_address_lo, u32 buf_address_hi, + u32 *r_buffer_index); +int lx_buffer_free(struct lx6464es *chip, u32 pipe, int is_capture, + u32 *r_buffer_size); +int lx_buffer_cancel(struct lx6464es *chip, u32 pipe, int is_capture, + u32 buffer_index); + +/* low-level gain/peak handling */ +int lx_level_unmute(struct lx6464es *chip, int is_capture, int unmute); +int lx_level_peaks(struct lx6464es *chip, int is_capture, int channels, + u32 *r_levels); + + +/* interrupt handling */ +irqreturn_t lx_interrupt(int irq, void *dev_id); +irqreturn_t lx_threaded_irq(int irq, void *dev_id); +void lx_irq_enable(struct lx6464es *chip); +void lx_irq_disable(struct lx6464es *chip); + + +/* Stream Format Header Defines (for LIN and IEEE754) */ +#define HEADER_FMT_BASE HEADER_FMT_BASE_LIN +#define HEADER_FMT_BASE_LIN 0xFED00000 +#define HEADER_FMT_BASE_FLOAT 0xFAD00000 +#define HEADER_FMT_MONO 0x00000080 /* bit 23 in header_lo. WARNING: old + * bit 22 is ignored in float + * format */ +#define HEADER_FMT_INTEL 0x00008000 +#define HEADER_FMT_16BITS 0x00002000 +#define HEADER_FMT_24BITS 0x00004000 +#define HEADER_FMT_UPTO11 0x00000200 /* frequency is less or equ. to 11k. + * */ +#define HEADER_FMT_UPTO32 0x00000100 /* frequency is over 11k and less + * then 32k.*/ + + +#define BIT_FMP_HEADER 23 +#define BIT_FMP_SD 22 +#define BIT_FMP_MULTICHANNEL 19 + +#define START_STATE 1 +#define PAUSE_STATE 0 + + + + + +/* from PcxAll_e.h */ +/* Start/Pause condition for pipes (PCXStartPipe, PCXPausePipe) */ +#define START_PAUSE_IMMEDIATE 0 +#define START_PAUSE_ON_SYNCHRO 1 +#define START_PAUSE_ON_TIME_CODE 2 + + +/* Pipe / Stream state */ +#define START_STATE 1 +#define PAUSE_STATE 0 + +static inline void unpack_pointer(dma_addr_t ptr, u32 *r_low, u32 *r_high) +{ + *r_low = (u32)(ptr & 0xffffffff); +#if BITS_PER_LONG == 32 + *r_high = 0; +#else + *r_high = (u32)((u64)ptr>>32); +#endif +} + +#endif /* LX_CORE_H */ diff --git a/sound/pci/lx6464es/lx_defs.h b/sound/pci/lx6464es/lx_defs.h new file mode 100644 index 000000000..eca5367ba --- /dev/null +++ b/sound/pci/lx6464es/lx_defs.h @@ -0,0 +1,361 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * adapted upstream headers + * + * Copyright (c) 2009 Tim Blechmann + */ + +#ifndef LX_DEFS_H +#define LX_DEFS_H + +/* code adapted from ethersound.h */ +#define XES_FREQ_COUNT8_MASK 0x00001FFF /* compteur 25MHz entre 8 ech. */ +#define XES_FREQ_COUNT8_44_MIN 0x00001288 /* 25M / + * [ 44k - ( 44.1k + 48k ) / 2 ] + * * 8 */ +#define XES_FREQ_COUNT8_44_MAX 0x000010F0 /* 25M / [ ( 44.1k + 48k ) / 2 ] + * * 8 */ +#define XES_FREQ_COUNT8_48_MAX 0x00000F08 /* 25M / + * [ 48k + ( 44.1k + 48k ) / 2 ] + * * 8 */ + +/* code adapted from LXES_registers.h */ + +#define IOCR_OUTPUTS_OFFSET 0 /* (rw) offset for the number of OUTs in the + * ConfES register. */ +#define IOCR_INPUTS_OFFSET 8 /* (rw) offset for the number of INs in the + * ConfES register. */ +#define FREQ_RATIO_OFFSET 19 /* (rw) offset for frequency ratio in the + * ConfES register. */ +#define FREQ_RATIO_SINGLE_MODE 0x01 /* value for single mode frequency ratio: + * sample rate = frequency rate. */ + +#define CONFES_READ_PART_MASK 0x00070000 +#define CONFES_WRITE_PART_MASK 0x00F80000 + +/* code adapted from if_drv_mb.h */ + +#define MASK_SYS_STATUS_ERROR (1L << 31) /* events that lead to a PCI irq if + * not yet pending */ +#define MASK_SYS_STATUS_URUN (1L << 30) +#define MASK_SYS_STATUS_ORUN (1L << 29) +#define MASK_SYS_STATUS_EOBO (1L << 28) +#define MASK_SYS_STATUS_EOBI (1L << 27) +#define MASK_SYS_STATUS_FREQ (1L << 26) +#define MASK_SYS_STATUS_ESA (1L << 25) /* reserved, this is set by the + * XES */ +#define MASK_SYS_STATUS_TIMER (1L << 24) + +#define MASK_SYS_ASYNC_EVENTS (MASK_SYS_STATUS_ERROR | \ + MASK_SYS_STATUS_URUN | \ + MASK_SYS_STATUS_ORUN | \ + MASK_SYS_STATUS_EOBO | \ + MASK_SYS_STATUS_EOBI | \ + MASK_SYS_STATUS_FREQ | \ + MASK_SYS_STATUS_ESA) + +#define MASK_SYS_PCI_EVENTS (MASK_SYS_ASYNC_EVENTS | \ + MASK_SYS_STATUS_TIMER) + +#define MASK_SYS_TIMER_COUNT 0x0000FFFF + +#define MASK_SYS_STATUS_EOT_PLX (1L << 22) /* event that remains + * internal: reserved fo end + * of plx dma */ +#define MASK_SYS_STATUS_XES (1L << 21) /* event that remains + * internal: pending XES + * IRQ */ +#define MASK_SYS_STATUS_CMD_DONE (1L << 20) /* alternate command + * management: notify driver + * instead of polling */ + + +#define MAX_STREAM_BUFFER 5 /* max amount of stream buffers. */ + +#define MICROBLAZE_IBL_MIN 32 +#define MICROBLAZE_IBL_DEFAULT 128 +#define MICROBLAZE_IBL_MAX 512 +/* #define MASK_GRANULARITY (2*MICROBLAZE_IBL_MAX-1) */ + + + +/* command opcodes, see reference for details */ + +/* + the capture bit position in the object_id field in driver commands + depends upon the number of managed channels. For now, 64 IN + 64 OUT are + supported. HOwever, the communication protocol forsees 1024 channels, hence + bit 10 indicates a capture (input) object). +*/ +#define ID_IS_CAPTURE (1L << 10) +#define ID_OFFSET 13 /* object ID is at the 13th bit in the + * 1st command word.*/ +#define ID_CH_MASK 0x3F +#define OPCODE_OFFSET 24 /* offset of the command opcode in the first + * command word.*/ + +enum cmd_mb_opcodes { + CMD_00_INFO_DEBUG = 0x00, + CMD_01_GET_SYS_CFG = 0x01, + CMD_02_SET_GRANULARITY = 0x02, + CMD_03_SET_TIMER_IRQ = 0x03, + CMD_04_GET_EVENT = 0x04, + CMD_05_GET_PIPES = 0x05, + + CMD_06_ALLOCATE_PIPE = 0x06, + CMD_07_RELEASE_PIPE = 0x07, + CMD_08_ASK_BUFFERS = 0x08, + CMD_09_STOP_PIPE = 0x09, + CMD_0A_GET_PIPE_SPL_COUNT = 0x0a, + CMD_0B_TOGGLE_PIPE_STATE = 0x0b, + + CMD_0C_DEF_STREAM = 0x0c, + CMD_0D_SET_MUTE = 0x0d, + CMD_0E_GET_STREAM_SPL_COUNT = 0x0e, + CMD_0F_UPDATE_BUFFER = 0x0f, + CMD_10_GET_BUFFER = 0x10, + CMD_11_CANCEL_BUFFER = 0x11, + CMD_12_GET_PEAK = 0x12, + CMD_13_SET_STREAM_STATE = 0x13, + CMD_14_INVALID = 0x14, +}; + +/* pipe states */ +enum pipe_state_t { + PSTATE_IDLE = 0, /* the pipe is not processed in the XES_IRQ + * (free or stopped, or paused). */ + PSTATE_RUN = 1, /* sustained play/record state. */ + PSTATE_PURGE = 2, /* the ES channels are now off, render pipes do + * not DMA, record pipe do a last DMA. */ + PSTATE_ACQUIRE = 3, /* the ES channels are now on, render pipes do + * not yet increase their sample count, record + * pipes do not DMA. */ + PSTATE_CLOSING = 4, /* the pipe is releasing, and may not yet + * receive an "alloc" command. */ +}; + +/* stream states */ +enum stream_state_t { + SSTATE_STOP = 0x00, /* setting to stop resets the stream spl + * count.*/ + SSTATE_RUN = (0x01 << 0), /* start DMA and spl count handling. */ + SSTATE_PAUSE = (0x01 << 1), /* pause DMA and spl count handling. */ +}; + +/* buffer flags */ +enum buffer_flags { + BF_VALID = 0x80, /* set if the buffer is valid, clear if free.*/ + BF_CURRENT = 0x40, /* set if this is the current buffer (there is + * always a current buffer).*/ + BF_NOTIFY_EOB = 0x20, /* set if this buffer must cause a PCI event + * when finished.*/ + BF_CIRCULAR = 0x10, /* set if buffer[1] must be copied to buffer[0] + * by the end of this buffer.*/ + BF_64BITS_ADR = 0x08, /* set if the hi part of the address is valid.*/ + BF_xx = 0x04, /* future extension.*/ + BF_EOB = 0x02, /* set if finished, but not yet free.*/ + BF_PAUSE = 0x01, /* pause stream at buffer end.*/ + BF_ZERO = 0x00, /* no flags (init).*/ +}; + +/* +* Stream Flags definitions +*/ +enum stream_flags { + SF_ZERO = 0x00000000, /* no flags (stream invalid). */ + SF_VALID = 0x10000000, /* the stream has a valid DMA_conf + * info (setstreamformat). */ + SF_XRUN = 0x20000000, /* the stream is un x-run state. */ + SF_START = 0x40000000, /* the DMA is running.*/ + SF_ASIO = 0x80000000, /* ASIO.*/ +}; + + +#define MASK_SPL_COUNT_HI 0x00FFFFFF /* 4 MSBits are status bits */ +#define PSTATE_OFFSET 28 /* 4 MSBits are status bits */ + + +#define MASK_STREAM_HAS_MAPPING (1L << 12) +#define MASK_STREAM_IS_ASIO (1L << 9) +#define STREAM_FMT_OFFSET 10 /* the stream fmt bits start at the 10th + * bit in the command word. */ + +#define STREAM_FMT_16b 0x02 +#define STREAM_FMT_intel 0x01 + +#define FREQ_FIELD_OFFSET 15 /* offset of the freq field in the response + * word */ + +#define BUFF_FLAGS_OFFSET 24 /* offset of the buffer flags in the + * response word. */ +#define MASK_DATA_SIZE 0x00FFFFFF /* this must match the field size of + * datasize in the buffer_t structure. */ + +#define MASK_BUFFER_ID 0xFF /* the cancel command awaits a buffer ID, + * may be 0xFF for "current". */ + + +/* code adapted from PcxErr_e.h */ + +/* Bits masks */ + +#define ERROR_MASK 0x8000 + +#define SOURCE_MASK 0x7800 + +#define E_SOURCE_BOARD 0x4000 /* 8 >> 1 */ +#define E_SOURCE_DRV 0x2000 /* 4 >> 1 */ +#define E_SOURCE_API 0x1000 /* 2 >> 1 */ +/* Error tools */ +#define E_SOURCE_TOOLS 0x0800 /* 1 >> 1 */ +/* Error pcxaudio */ +#define E_SOURCE_AUDIO 0x1800 /* 3 >> 1 */ +/* Error virtual pcx */ +#define E_SOURCE_VPCX 0x2800 /* 5 >> 1 */ +/* Error dispatcher */ +#define E_SOURCE_DISPATCHER 0x3000 /* 6 >> 1 */ +/* Error from CobraNet firmware */ +#define E_SOURCE_COBRANET 0x3800 /* 7 >> 1 */ + +#define E_SOURCE_USER 0x7800 + +#define CLASS_MASK 0x0700 + +#define CODE_MASK 0x00FF + +/* Bits values */ + +/* Values for the error/warning bit */ +#define ERROR_VALUE 0x8000 +#define WARNING_VALUE 0x0000 + +/* Class values */ +#define E_CLASS_GENERAL 0x0000 +#define E_CLASS_INVALID_CMD 0x0100 +#define E_CLASS_INVALID_STD_OBJECT 0x0200 +#define E_CLASS_RSRC_IMPOSSIBLE 0x0300 +#define E_CLASS_WRONG_CONTEXT 0x0400 +#define E_CLASS_BAD_SPECIFIC_PARAMETER 0x0500 +#define E_CLASS_REAL_TIME_ERROR 0x0600 +#define E_CLASS_DIRECTSHOW 0x0700 +#define E_CLASS_FREE 0x0700 + + +/* Complete DRV error code for the general class */ +#define ED_GN (ERROR_VALUE | E_SOURCE_DRV | E_CLASS_GENERAL) +#define ED_CONCURRENCY (ED_GN | 0x01) +#define ED_DSP_CRASHED (ED_GN | 0x02) +#define ED_UNKNOWN_BOARD (ED_GN | 0x03) +#define ED_NOT_INSTALLED (ED_GN | 0x04) +#define ED_CANNOT_OPEN_SVC_MANAGER (ED_GN | 0x05) +#define ED_CANNOT_READ_REGISTRY (ED_GN | 0x06) +#define ED_DSP_VERSION_MISMATCH (ED_GN | 0x07) +#define ED_UNAVAILABLE_FEATURE (ED_GN | 0x08) +#define ED_CANCELLED (ED_GN | 0x09) +#define ED_NO_RESPONSE_AT_IRQA (ED_GN | 0x10) +#define ED_INVALID_ADDRESS (ED_GN | 0x11) +#define ED_DSP_CORRUPTED (ED_GN | 0x12) +#define ED_PENDING_OPERATION (ED_GN | 0x13) +#define ED_NET_ALLOCATE_MEMORY_IMPOSSIBLE (ED_GN | 0x14) +#define ED_NET_REGISTER_ERROR (ED_GN | 0x15) +#define ED_NET_THREAD_ERROR (ED_GN | 0x16) +#define ED_NET_OPEN_ERROR (ED_GN | 0x17) +#define ED_NET_CLOSE_ERROR (ED_GN | 0x18) +#define ED_NET_NO_MORE_PACKET (ED_GN | 0x19) +#define ED_NET_NO_MORE_BUFFER (ED_GN | 0x1A) +#define ED_NET_SEND_ERROR (ED_GN | 0x1B) +#define ED_NET_RECEIVE_ERROR (ED_GN | 0x1C) +#define ED_NET_WRONG_MSG_SIZE (ED_GN | 0x1D) +#define ED_NET_WAIT_ERROR (ED_GN | 0x1E) +#define ED_NET_EEPROM_ERROR (ED_GN | 0x1F) +#define ED_INVALID_RS232_COM_NUMBER (ED_GN | 0x20) +#define ED_INVALID_RS232_INIT (ED_GN | 0x21) +#define ED_FILE_ERROR (ED_GN | 0x22) +#define ED_INVALID_GPIO_CMD (ED_GN | 0x23) +#define ED_RS232_ALREADY_OPENED (ED_GN | 0x24) +#define ED_RS232_NOT_OPENED (ED_GN | 0x25) +#define ED_GPIO_ALREADY_OPENED (ED_GN | 0x26) +#define ED_GPIO_NOT_OPENED (ED_GN | 0x27) +#define ED_REGISTRY_ERROR (ED_GN | 0x28) /* <- NCX */ +#define ED_INVALID_SERVICE (ED_GN | 0x29) /* <- NCX */ + +#define ED_READ_FILE_ALREADY_OPENED (ED_GN | 0x2a) /* <- Decalage + * pour RCX + * (old 0x28) + * */ +#define ED_READ_FILE_INVALID_COMMAND (ED_GN | 0x2b) /* ~ */ +#define ED_READ_FILE_INVALID_PARAMETER (ED_GN | 0x2c) /* ~ */ +#define ED_READ_FILE_ALREADY_CLOSED (ED_GN | 0x2d) /* ~ */ +#define ED_READ_FILE_NO_INFORMATION (ED_GN | 0x2e) /* ~ */ +#define ED_READ_FILE_INVALID_HANDLE (ED_GN | 0x2f) /* ~ */ +#define ED_READ_FILE_END_OF_FILE (ED_GN | 0x30) /* ~ */ +#define ED_READ_FILE_ERROR (ED_GN | 0x31) /* ~ */ + +#define ED_DSP_CRASHED_EXC_DSPSTACK_OVERFLOW (ED_GN | 0x32) /* <- Decalage pour + * PCX (old 0x14) */ +#define ED_DSP_CRASHED_EXC_SYSSTACK_OVERFLOW (ED_GN | 0x33) /* ~ */ +#define ED_DSP_CRASHED_EXC_ILLEGAL (ED_GN | 0x34) /* ~ */ +#define ED_DSP_CRASHED_EXC_TIMER_REENTRY (ED_GN | 0x35) /* ~ */ +#define ED_DSP_CRASHED_EXC_FATAL_ERROR (ED_GN | 0x36) /* ~ */ + +#define ED_FLASH_PCCARD_NOT_PRESENT (ED_GN | 0x37) + +#define ED_NO_CURRENT_CLOCK (ED_GN | 0x38) + +/* Complete DRV error code for real time class */ +#define ED_RT (ERROR_VALUE | E_SOURCE_DRV | E_CLASS_REAL_TIME_ERROR) +#define ED_DSP_TIMED_OUT (ED_RT | 0x01) +#define ED_DSP_CHK_TIMED_OUT (ED_RT | 0x02) +#define ED_STREAM_OVERRUN (ED_RT | 0x03) +#define ED_DSP_BUSY (ED_RT | 0x04) +#define ED_DSP_SEMAPHORE_TIME_OUT (ED_RT | 0x05) +#define ED_BOARD_TIME_OUT (ED_RT | 0x06) +#define ED_XILINX_ERROR (ED_RT | 0x07) +#define ED_COBRANET_ITF_NOT_RESPONDING (ED_RT | 0x08) + +/* Complete BOARD error code for the invaid standard object class */ +#define EB_ISO (ERROR_VALUE | E_SOURCE_BOARD | \ + E_CLASS_INVALID_STD_OBJECT) +#define EB_INVALID_EFFECT (EB_ISO | 0x00) +#define EB_INVALID_PIPE (EB_ISO | 0x40) +#define EB_INVALID_STREAM (EB_ISO | 0x80) +#define EB_INVALID_AUDIO (EB_ISO | 0xC0) + +/* Complete BOARD error code for impossible resource allocation class */ +#define EB_RI (ERROR_VALUE | E_SOURCE_BOARD | E_CLASS_RSRC_IMPOSSIBLE) +#define EB_ALLOCATE_ALL_STREAM_TRANSFERT_BUFFERS_IMPOSSIBLE (EB_RI | 0x01) +#define EB_ALLOCATE_PIPE_SAMPLE_BUFFER_IMPOSSIBLE (EB_RI | 0x02) + +#define EB_ALLOCATE_MEM_STREAM_IMPOSSIBLE \ + EB_ALLOCATE_ALL_STREAM_TRANSFERT_BUFFERS_IMPOSSIBLE +#define EB_ALLOCATE_MEM_PIPE_IMPOSSIBLE \ + EB_ALLOCATE_PIPE_SAMPLE_BUFFER_IMPOSSIBLE + +#define EB_ALLOCATE_DIFFERED_CMD_IMPOSSIBLE (EB_RI | 0x03) +#define EB_TOO_MANY_DIFFERED_CMD (EB_RI | 0x04) +#define EB_RBUFFERS_TABLE_OVERFLOW (EB_RI | 0x05) +#define EB_ALLOCATE_EFFECTS_IMPOSSIBLE (EB_RI | 0x08) +#define EB_ALLOCATE_EFFECT_POS_IMPOSSIBLE (EB_RI | 0x09) +#define EB_RBUFFER_NOT_AVAILABLE (EB_RI | 0x0A) +#define EB_ALLOCATE_CONTEXT_LIII_IMPOSSIBLE (EB_RI | 0x0B) +#define EB_STATUS_DIALOG_IMPOSSIBLE (EB_RI | 0x1D) +#define EB_CONTROL_CMD_IMPOSSIBLE (EB_RI | 0x1E) +#define EB_STATUS_SEND_IMPOSSIBLE (EB_RI | 0x1F) +#define EB_ALLOCATE_PIPE_IMPOSSIBLE (EB_RI | 0x40) +#define EB_ALLOCATE_STREAM_IMPOSSIBLE (EB_RI | 0x80) +#define EB_ALLOCATE_AUDIO_IMPOSSIBLE (EB_RI | 0xC0) + +/* Complete BOARD error code for wrong call context class */ +#define EB_WCC (ERROR_VALUE | E_SOURCE_BOARD | E_CLASS_WRONG_CONTEXT) +#define EB_CMD_REFUSED (EB_WCC | 0x00) +#define EB_START_STREAM_REFUSED (EB_WCC | 0xFC) +#define EB_SPC_REFUSED (EB_WCC | 0xFD) +#define EB_CSN_REFUSED (EB_WCC | 0xFE) +#define EB_CSE_REFUSED (EB_WCC | 0xFF) + + + + +#endif /* LX_DEFS_H */ diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c new file mode 100644 index 000000000..dd45ffa17 --- /dev/null +++ b/sound/pci/maestro3.c @@ -0,0 +1,2712 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for ESS Maestro3/Allegro (ES1988) soundcards. + * Copyright (c) 2000 by Zach Brown + * Takashi Iwai + * + * Most of the hardware init stuffs are based on maestro3 driver for + * OSS/Free by Zach Brown. Many thanks to Zach! + * + * ChangeLog: + * Aug. 27, 2001 + * - Fixed deadlock on capture + * - Added Canyon3D-2 support by Rob Riggs + */ + +#define CARD_NAME "ESS Maestro3/Allegro/Canyon3D-2" +#define DRIVER_NAME "Maestro3" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Zach Brown , Takashi Iwai "); +MODULE_DESCRIPTION("ESS Maestro3 PCI"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("ess/maestro3_assp_kernel.fw"); +MODULE_FIRMWARE("ess/maestro3_assp_minisrc.fw"); + +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_PNP; /* all enabled */ +static bool external_amp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +static int amp_gpio[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable this soundcard."); +module_param_array(external_amp, bool, NULL, 0444); +MODULE_PARM_DESC(external_amp, "Enable external amp for " CARD_NAME " soundcard."); +module_param_array(amp_gpio, int, NULL, 0444); +MODULE_PARM_DESC(amp_gpio, "GPIO pin number for external amp. (default = -1)"); + +#define MAX_PLAYBACKS 2 +#define MAX_CAPTURES 1 +#define NR_DSPS (MAX_PLAYBACKS + MAX_CAPTURES) + + +/* + * maestro3 registers + */ + +/* Allegro PCI configuration registers */ +#define PCI_LEGACY_AUDIO_CTRL 0x40 +#define SOUND_BLASTER_ENABLE 0x00000001 +#define FM_SYNTHESIS_ENABLE 0x00000002 +#define GAME_PORT_ENABLE 0x00000004 +#define MPU401_IO_ENABLE 0x00000008 +#define MPU401_IRQ_ENABLE 0x00000010 +#define ALIAS_10BIT_IO 0x00000020 +#define SB_DMA_MASK 0x000000C0 +#define SB_DMA_0 0x00000040 +#define SB_DMA_1 0x00000040 +#define SB_DMA_R 0x00000080 +#define SB_DMA_3 0x000000C0 +#define SB_IRQ_MASK 0x00000700 +#define SB_IRQ_5 0x00000000 +#define SB_IRQ_7 0x00000100 +#define SB_IRQ_9 0x00000200 +#define SB_IRQ_10 0x00000300 +#define MIDI_IRQ_MASK 0x00003800 +#define SERIAL_IRQ_ENABLE 0x00004000 +#define DISABLE_LEGACY 0x00008000 + +#define PCI_ALLEGRO_CONFIG 0x50 +#define SB_ADDR_240 0x00000004 +#define MPU_ADDR_MASK 0x00000018 +#define MPU_ADDR_330 0x00000000 +#define MPU_ADDR_300 0x00000008 +#define MPU_ADDR_320 0x00000010 +#define MPU_ADDR_340 0x00000018 +#define USE_PCI_TIMING 0x00000040 +#define POSTED_WRITE_ENABLE 0x00000080 +#define DMA_POLICY_MASK 0x00000700 +#define DMA_DDMA 0x00000000 +#define DMA_TDMA 0x00000100 +#define DMA_PCPCI 0x00000200 +#define DMA_WBDMA16 0x00000400 +#define DMA_WBDMA4 0x00000500 +#define DMA_WBDMA2 0x00000600 +#define DMA_WBDMA1 0x00000700 +#define DMA_SAFE_GUARD 0x00000800 +#define HI_PERF_GP_ENABLE 0x00001000 +#define PIC_SNOOP_MODE_0 0x00002000 +#define PIC_SNOOP_MODE_1 0x00004000 +#define SOUNDBLASTER_IRQ_MASK 0x00008000 +#define RING_IN_ENABLE 0x00010000 +#define SPDIF_TEST_MODE 0x00020000 +#define CLK_MULT_MODE_SELECT_2 0x00040000 +#define EEPROM_WRITE_ENABLE 0x00080000 +#define CODEC_DIR_IN 0x00100000 +#define HV_BUTTON_FROM_GD 0x00200000 +#define REDUCED_DEBOUNCE 0x00400000 +#define HV_CTRL_ENABLE 0x00800000 +#define SPDIF_ENABLE 0x01000000 +#define CLK_DIV_SELECT 0x06000000 +#define CLK_DIV_BY_48 0x00000000 +#define CLK_DIV_BY_49 0x02000000 +#define CLK_DIV_BY_50 0x04000000 +#define CLK_DIV_RESERVED 0x06000000 +#define PM_CTRL_ENABLE 0x08000000 +#define CLK_MULT_MODE_SELECT 0x30000000 +#define CLK_MULT_MODE_SHIFT 28 +#define CLK_MULT_MODE_0 0x00000000 +#define CLK_MULT_MODE_1 0x10000000 +#define CLK_MULT_MODE_2 0x20000000 +#define CLK_MULT_MODE_3 0x30000000 +#define INT_CLK_SELECT 0x40000000 +#define INT_CLK_MULT_RESET 0x80000000 + +/* M3 */ +#define INT_CLK_SRC_NOT_PCI 0x00100000 +#define INT_CLK_MULT_ENABLE 0x80000000 + +#define PCI_ACPI_CONTROL 0x54 +#define PCI_ACPI_D0 0x00000000 +#define PCI_ACPI_D1 0xB4F70000 +#define PCI_ACPI_D2 0xB4F7B4F7 + +#define PCI_USER_CONFIG 0x58 +#define EXT_PCI_MASTER_ENABLE 0x00000001 +#define SPDIF_OUT_SELECT 0x00000002 +#define TEST_PIN_DIR_CTRL 0x00000004 +#define AC97_CODEC_TEST 0x00000020 +#define TRI_STATE_BUFFER 0x00000080 +#define IN_CLK_12MHZ_SELECT 0x00000100 +#define MULTI_FUNC_DISABLE 0x00000200 +#define EXT_MASTER_PAIR_SEL 0x00000400 +#define PCI_MASTER_SUPPORT 0x00000800 +#define STOP_CLOCK_ENABLE 0x00001000 +#define EAPD_DRIVE_ENABLE 0x00002000 +#define REQ_TRI_STATE_ENABLE 0x00004000 +#define REQ_LOW_ENABLE 0x00008000 +#define MIDI_1_ENABLE 0x00010000 +#define MIDI_2_ENABLE 0x00020000 +#define SB_AUDIO_SYNC 0x00040000 +#define HV_CTRL_TEST 0x00100000 +#define SOUNDBLASTER_TEST 0x00400000 + +#define PCI_USER_CONFIG_C 0x5C + +#define PCI_DDMA_CTRL 0x60 +#define DDMA_ENABLE 0x00000001 + + +/* Allegro registers */ +#define HOST_INT_CTRL 0x18 +#define SB_INT_ENABLE 0x0001 +#define MPU401_INT_ENABLE 0x0002 +#define ASSP_INT_ENABLE 0x0010 +#define RING_INT_ENABLE 0x0020 +#define HV_INT_ENABLE 0x0040 +#define CLKRUN_GEN_ENABLE 0x0100 +#define HV_CTRL_TO_PME 0x0400 +#define SOFTWARE_RESET_ENABLE 0x8000 + +/* + * should be using the above defines, probably. + */ +#define REGB_ENABLE_RESET 0x01 +#define REGB_STOP_CLOCK 0x10 + +#define HOST_INT_STATUS 0x1A +#define SB_INT_PENDING 0x01 +#define MPU401_INT_PENDING 0x02 +#define ASSP_INT_PENDING 0x10 +#define RING_INT_PENDING 0x20 +#define HV_INT_PENDING 0x40 + +#define HARDWARE_VOL_CTRL 0x1B +#define SHADOW_MIX_REG_VOICE 0x1C +#define HW_VOL_COUNTER_VOICE 0x1D +#define SHADOW_MIX_REG_MASTER 0x1E +#define HW_VOL_COUNTER_MASTER 0x1F + +#define CODEC_COMMAND 0x30 +#define CODEC_READ_B 0x80 + +#define CODEC_STATUS 0x30 +#define CODEC_BUSY_B 0x01 + +#define CODEC_DATA 0x32 + +#define RING_BUS_CTRL_A 0x36 +#define RAC_PME_ENABLE 0x0100 +#define RAC_SDFS_ENABLE 0x0200 +#define LAC_PME_ENABLE 0x0400 +#define LAC_SDFS_ENABLE 0x0800 +#define SERIAL_AC_LINK_ENABLE 0x1000 +#define IO_SRAM_ENABLE 0x2000 +#define IIS_INPUT_ENABLE 0x8000 + +#define RING_BUS_CTRL_B 0x38 +#define SECOND_CODEC_ID_MASK 0x0003 +#define SPDIF_FUNC_ENABLE 0x0010 +#define SECOND_AC_ENABLE 0x0020 +#define SB_MODULE_INTF_ENABLE 0x0040 +#define SSPE_ENABLE 0x0040 +#define M3I_DOCK_ENABLE 0x0080 + +#define SDO_OUT_DEST_CTRL 0x3A +#define COMMAND_ADDR_OUT 0x0003 +#define PCM_LR_OUT_LOCAL 0x0000 +#define PCM_LR_OUT_REMOTE 0x0004 +#define PCM_LR_OUT_MUTE 0x0008 +#define PCM_LR_OUT_BOTH 0x000C +#define LINE1_DAC_OUT_LOCAL 0x0000 +#define LINE1_DAC_OUT_REMOTE 0x0010 +#define LINE1_DAC_OUT_MUTE 0x0020 +#define LINE1_DAC_OUT_BOTH 0x0030 +#define PCM_CLS_OUT_LOCAL 0x0000 +#define PCM_CLS_OUT_REMOTE 0x0040 +#define PCM_CLS_OUT_MUTE 0x0080 +#define PCM_CLS_OUT_BOTH 0x00C0 +#define PCM_RLF_OUT_LOCAL 0x0000 +#define PCM_RLF_OUT_REMOTE 0x0100 +#define PCM_RLF_OUT_MUTE 0x0200 +#define PCM_RLF_OUT_BOTH 0x0300 +#define LINE2_DAC_OUT_LOCAL 0x0000 +#define LINE2_DAC_OUT_REMOTE 0x0400 +#define LINE2_DAC_OUT_MUTE 0x0800 +#define LINE2_DAC_OUT_BOTH 0x0C00 +#define HANDSET_OUT_LOCAL 0x0000 +#define HANDSET_OUT_REMOTE 0x1000 +#define HANDSET_OUT_MUTE 0x2000 +#define HANDSET_OUT_BOTH 0x3000 +#define IO_CTRL_OUT_LOCAL 0x0000 +#define IO_CTRL_OUT_REMOTE 0x4000 +#define IO_CTRL_OUT_MUTE 0x8000 +#define IO_CTRL_OUT_BOTH 0xC000 + +#define SDO_IN_DEST_CTRL 0x3C +#define STATUS_ADDR_IN 0x0003 +#define PCM_LR_IN_LOCAL 0x0000 +#define PCM_LR_IN_REMOTE 0x0004 +#define PCM_LR_RESERVED 0x0008 +#define PCM_LR_IN_BOTH 0x000C +#define LINE1_ADC_IN_LOCAL 0x0000 +#define LINE1_ADC_IN_REMOTE 0x0010 +#define LINE1_ADC_IN_MUTE 0x0020 +#define MIC_ADC_IN_LOCAL 0x0000 +#define MIC_ADC_IN_REMOTE 0x0040 +#define MIC_ADC_IN_MUTE 0x0080 +#define LINE2_DAC_IN_LOCAL 0x0000 +#define LINE2_DAC_IN_REMOTE 0x0400 +#define LINE2_DAC_IN_MUTE 0x0800 +#define HANDSET_IN_LOCAL 0x0000 +#define HANDSET_IN_REMOTE 0x1000 +#define HANDSET_IN_MUTE 0x2000 +#define IO_STATUS_IN_LOCAL 0x0000 +#define IO_STATUS_IN_REMOTE 0x4000 + +#define SPDIF_IN_CTRL 0x3E +#define SPDIF_IN_ENABLE 0x0001 + +#define GPIO_DATA 0x60 +#define GPIO_DATA_MASK 0x0FFF +#define GPIO_HV_STATUS 0x3000 +#define GPIO_PME_STATUS 0x4000 + +#define GPIO_MASK 0x64 +#define GPIO_DIRECTION 0x68 +#define GPO_PRIMARY_AC97 0x0001 +#define GPI_LINEOUT_SENSE 0x0004 +#define GPO_SECONDARY_AC97 0x0008 +#define GPI_VOL_DOWN 0x0010 +#define GPI_VOL_UP 0x0020 +#define GPI_IIS_CLK 0x0040 +#define GPI_IIS_LRCLK 0x0080 +#define GPI_IIS_DATA 0x0100 +#define GPI_DOCKING_STATUS 0x0100 +#define GPI_HEADPHONE_SENSE 0x0200 +#define GPO_EXT_AMP_SHUTDOWN 0x1000 + +#define GPO_EXT_AMP_M3 1 /* default m3 amp */ +#define GPO_EXT_AMP_ALLEGRO 8 /* default allegro amp */ + +/* M3 */ +#define GPO_M3_EXT_AMP_SHUTDN 0x0002 + +#define ASSP_INDEX_PORT 0x80 +#define ASSP_MEMORY_PORT 0x82 +#define ASSP_DATA_PORT 0x84 + +#define MPU401_DATA_PORT 0x98 +#define MPU401_STATUS_PORT 0x99 + +#define CLK_MULT_DATA_PORT 0x9C + +#define ASSP_CONTROL_A 0xA2 +#define ASSP_0_WS_ENABLE 0x01 +#define ASSP_CTRL_A_RESERVED1 0x02 +#define ASSP_CTRL_A_RESERVED2 0x04 +#define ASSP_CLK_49MHZ_SELECT 0x08 +#define FAST_PLU_ENABLE 0x10 +#define ASSP_CTRL_A_RESERVED3 0x20 +#define DSP_CLK_36MHZ_SELECT 0x40 + +#define ASSP_CONTROL_B 0xA4 +#define RESET_ASSP 0x00 +#define RUN_ASSP 0x01 +#define ENABLE_ASSP_CLOCK 0x00 +#define STOP_ASSP_CLOCK 0x10 +#define RESET_TOGGLE 0x40 + +#define ASSP_CONTROL_C 0xA6 +#define ASSP_HOST_INT_ENABLE 0x01 +#define FM_ADDR_REMAP_DISABLE 0x02 +#define HOST_WRITE_PORT_ENABLE 0x08 + +#define ASSP_HOST_INT_STATUS 0xAC +#define DSP2HOST_REQ_PIORECORD 0x01 +#define DSP2HOST_REQ_I2SRATE 0x02 +#define DSP2HOST_REQ_TIMER 0x04 + +/* + * ASSP control regs + */ +#define DSP_PORT_TIMER_COUNT 0x06 + +#define DSP_PORT_MEMORY_INDEX 0x80 + +#define DSP_PORT_MEMORY_TYPE 0x82 +#define MEMTYPE_INTERNAL_CODE 0x0002 +#define MEMTYPE_INTERNAL_DATA 0x0003 +#define MEMTYPE_MASK 0x0003 + +#define DSP_PORT_MEMORY_DATA 0x84 + +#define DSP_PORT_CONTROL_REG_A 0xA2 +#define DSP_PORT_CONTROL_REG_B 0xA4 +#define DSP_PORT_CONTROL_REG_C 0xA6 + +#define REV_A_CODE_MEMORY_BEGIN 0x0000 +#define REV_A_CODE_MEMORY_END 0x0FFF +#define REV_A_CODE_MEMORY_UNIT_LENGTH 0x0040 +#define REV_A_CODE_MEMORY_LENGTH (REV_A_CODE_MEMORY_END - REV_A_CODE_MEMORY_BEGIN + 1) + +#define REV_B_CODE_MEMORY_BEGIN 0x0000 +#define REV_B_CODE_MEMORY_END 0x0BFF +#define REV_B_CODE_MEMORY_UNIT_LENGTH 0x0040 +#define REV_B_CODE_MEMORY_LENGTH (REV_B_CODE_MEMORY_END - REV_B_CODE_MEMORY_BEGIN + 1) + +#define REV_A_DATA_MEMORY_BEGIN 0x1000 +#define REV_A_DATA_MEMORY_END 0x2FFF +#define REV_A_DATA_MEMORY_UNIT_LENGTH 0x0080 +#define REV_A_DATA_MEMORY_LENGTH (REV_A_DATA_MEMORY_END - REV_A_DATA_MEMORY_BEGIN + 1) + +#define REV_B_DATA_MEMORY_BEGIN 0x1000 +#define REV_B_DATA_MEMORY_END 0x2BFF +#define REV_B_DATA_MEMORY_UNIT_LENGTH 0x0080 +#define REV_B_DATA_MEMORY_LENGTH (REV_B_DATA_MEMORY_END - REV_B_DATA_MEMORY_BEGIN + 1) + + +#define NUM_UNITS_KERNEL_CODE 16 +#define NUM_UNITS_KERNEL_DATA 2 + +#define NUM_UNITS_KERNEL_CODE_WITH_HSP 16 +#define NUM_UNITS_KERNEL_DATA_WITH_HSP 5 + +/* + * Kernel data layout + */ + +#define DP_SHIFT_COUNT 7 + +#define KDATA_BASE_ADDR 0x1000 +#define KDATA_BASE_ADDR2 0x1080 + +#define KDATA_TASK0 (KDATA_BASE_ADDR + 0x0000) +#define KDATA_TASK1 (KDATA_BASE_ADDR + 0x0001) +#define KDATA_TASK2 (KDATA_BASE_ADDR + 0x0002) +#define KDATA_TASK3 (KDATA_BASE_ADDR + 0x0003) +#define KDATA_TASK4 (KDATA_BASE_ADDR + 0x0004) +#define KDATA_TASK5 (KDATA_BASE_ADDR + 0x0005) +#define KDATA_TASK6 (KDATA_BASE_ADDR + 0x0006) +#define KDATA_TASK7 (KDATA_BASE_ADDR + 0x0007) +#define KDATA_TASK_ENDMARK (KDATA_BASE_ADDR + 0x0008) + +#define KDATA_CURRENT_TASK (KDATA_BASE_ADDR + 0x0009) +#define KDATA_TASK_SWITCH (KDATA_BASE_ADDR + 0x000A) + +#define KDATA_INSTANCE0_POS3D (KDATA_BASE_ADDR + 0x000B) +#define KDATA_INSTANCE1_POS3D (KDATA_BASE_ADDR + 0x000C) +#define KDATA_INSTANCE2_POS3D (KDATA_BASE_ADDR + 0x000D) +#define KDATA_INSTANCE3_POS3D (KDATA_BASE_ADDR + 0x000E) +#define KDATA_INSTANCE4_POS3D (KDATA_BASE_ADDR + 0x000F) +#define KDATA_INSTANCE5_POS3D (KDATA_BASE_ADDR + 0x0010) +#define KDATA_INSTANCE6_POS3D (KDATA_BASE_ADDR + 0x0011) +#define KDATA_INSTANCE7_POS3D (KDATA_BASE_ADDR + 0x0012) +#define KDATA_INSTANCE8_POS3D (KDATA_BASE_ADDR + 0x0013) +#define KDATA_INSTANCE_POS3D_ENDMARK (KDATA_BASE_ADDR + 0x0014) + +#define KDATA_INSTANCE0_SPKVIRT (KDATA_BASE_ADDR + 0x0015) +#define KDATA_INSTANCE_SPKVIRT_ENDMARK (KDATA_BASE_ADDR + 0x0016) + +#define KDATA_INSTANCE0_SPDIF (KDATA_BASE_ADDR + 0x0017) +#define KDATA_INSTANCE_SPDIF_ENDMARK (KDATA_BASE_ADDR + 0x0018) + +#define KDATA_INSTANCE0_MODEM (KDATA_BASE_ADDR + 0x0019) +#define KDATA_INSTANCE_MODEM_ENDMARK (KDATA_BASE_ADDR + 0x001A) + +#define KDATA_INSTANCE0_SRC (KDATA_BASE_ADDR + 0x001B) +#define KDATA_INSTANCE1_SRC (KDATA_BASE_ADDR + 0x001C) +#define KDATA_INSTANCE_SRC_ENDMARK (KDATA_BASE_ADDR + 0x001D) + +#define KDATA_INSTANCE0_MINISRC (KDATA_BASE_ADDR + 0x001E) +#define KDATA_INSTANCE1_MINISRC (KDATA_BASE_ADDR + 0x001F) +#define KDATA_INSTANCE2_MINISRC (KDATA_BASE_ADDR + 0x0020) +#define KDATA_INSTANCE3_MINISRC (KDATA_BASE_ADDR + 0x0021) +#define KDATA_INSTANCE_MINISRC_ENDMARK (KDATA_BASE_ADDR + 0x0022) + +#define KDATA_INSTANCE0_CPYTHRU (KDATA_BASE_ADDR + 0x0023) +#define KDATA_INSTANCE1_CPYTHRU (KDATA_BASE_ADDR + 0x0024) +#define KDATA_INSTANCE_CPYTHRU_ENDMARK (KDATA_BASE_ADDR + 0x0025) + +#define KDATA_CURRENT_DMA (KDATA_BASE_ADDR + 0x0026) +#define KDATA_DMA_SWITCH (KDATA_BASE_ADDR + 0x0027) +#define KDATA_DMA_ACTIVE (KDATA_BASE_ADDR + 0x0028) + +#define KDATA_DMA_XFER0 (KDATA_BASE_ADDR + 0x0029) +#define KDATA_DMA_XFER1 (KDATA_BASE_ADDR + 0x002A) +#define KDATA_DMA_XFER2 (KDATA_BASE_ADDR + 0x002B) +#define KDATA_DMA_XFER3 (KDATA_BASE_ADDR + 0x002C) +#define KDATA_DMA_XFER4 (KDATA_BASE_ADDR + 0x002D) +#define KDATA_DMA_XFER5 (KDATA_BASE_ADDR + 0x002E) +#define KDATA_DMA_XFER6 (KDATA_BASE_ADDR + 0x002F) +#define KDATA_DMA_XFER7 (KDATA_BASE_ADDR + 0x0030) +#define KDATA_DMA_XFER8 (KDATA_BASE_ADDR + 0x0031) +#define KDATA_DMA_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0032) + +#define KDATA_I2S_SAMPLE_COUNT (KDATA_BASE_ADDR + 0x0033) +#define KDATA_I2S_INT_METER (KDATA_BASE_ADDR + 0x0034) +#define KDATA_I2S_ACTIVE (KDATA_BASE_ADDR + 0x0035) + +#define KDATA_TIMER_COUNT_RELOAD (KDATA_BASE_ADDR + 0x0036) +#define KDATA_TIMER_COUNT_CURRENT (KDATA_BASE_ADDR + 0x0037) + +#define KDATA_HALT_SYNCH_CLIENT (KDATA_BASE_ADDR + 0x0038) +#define KDATA_HALT_SYNCH_DMA (KDATA_BASE_ADDR + 0x0039) +#define KDATA_HALT_ACKNOWLEDGE (KDATA_BASE_ADDR + 0x003A) + +#define KDATA_ADC1_XFER0 (KDATA_BASE_ADDR + 0x003B) +#define KDATA_ADC1_XFER_ENDMARK (KDATA_BASE_ADDR + 0x003C) +#define KDATA_ADC1_LEFT_VOLUME (KDATA_BASE_ADDR + 0x003D) +#define KDATA_ADC1_RIGHT_VOLUME (KDATA_BASE_ADDR + 0x003E) +#define KDATA_ADC1_LEFT_SUR_VOL (KDATA_BASE_ADDR + 0x003F) +#define KDATA_ADC1_RIGHT_SUR_VOL (KDATA_BASE_ADDR + 0x0040) + +#define KDATA_ADC2_XFER0 (KDATA_BASE_ADDR + 0x0041) +#define KDATA_ADC2_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0042) +#define KDATA_ADC2_LEFT_VOLUME (KDATA_BASE_ADDR + 0x0043) +#define KDATA_ADC2_RIGHT_VOLUME (KDATA_BASE_ADDR + 0x0044) +#define KDATA_ADC2_LEFT_SUR_VOL (KDATA_BASE_ADDR + 0x0045) +#define KDATA_ADC2_RIGHT_SUR_VOL (KDATA_BASE_ADDR + 0x0046) + +#define KDATA_CD_XFER0 (KDATA_BASE_ADDR + 0x0047) +#define KDATA_CD_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0048) +#define KDATA_CD_LEFT_VOLUME (KDATA_BASE_ADDR + 0x0049) +#define KDATA_CD_RIGHT_VOLUME (KDATA_BASE_ADDR + 0x004A) +#define KDATA_CD_LEFT_SUR_VOL (KDATA_BASE_ADDR + 0x004B) +#define KDATA_CD_RIGHT_SUR_VOL (KDATA_BASE_ADDR + 0x004C) + +#define KDATA_MIC_XFER0 (KDATA_BASE_ADDR + 0x004D) +#define KDATA_MIC_XFER_ENDMARK (KDATA_BASE_ADDR + 0x004E) +#define KDATA_MIC_VOLUME (KDATA_BASE_ADDR + 0x004F) +#define KDATA_MIC_SUR_VOL (KDATA_BASE_ADDR + 0x0050) + +#define KDATA_I2S_XFER0 (KDATA_BASE_ADDR + 0x0051) +#define KDATA_I2S_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0052) + +#define KDATA_CHI_XFER0 (KDATA_BASE_ADDR + 0x0053) +#define KDATA_CHI_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0054) + +#define KDATA_SPDIF_XFER (KDATA_BASE_ADDR + 0x0055) +#define KDATA_SPDIF_CURRENT_FRAME (KDATA_BASE_ADDR + 0x0056) +#define KDATA_SPDIF_FRAME0 (KDATA_BASE_ADDR + 0x0057) +#define KDATA_SPDIF_FRAME1 (KDATA_BASE_ADDR + 0x0058) +#define KDATA_SPDIF_FRAME2 (KDATA_BASE_ADDR + 0x0059) + +#define KDATA_SPDIF_REQUEST (KDATA_BASE_ADDR + 0x005A) +#define KDATA_SPDIF_TEMP (KDATA_BASE_ADDR + 0x005B) + +#define KDATA_SPDIFIN_XFER0 (KDATA_BASE_ADDR + 0x005C) +#define KDATA_SPDIFIN_XFER_ENDMARK (KDATA_BASE_ADDR + 0x005D) +#define KDATA_SPDIFIN_INT_METER (KDATA_BASE_ADDR + 0x005E) + +#define KDATA_DSP_RESET_COUNT (KDATA_BASE_ADDR + 0x005F) +#define KDATA_DEBUG_OUTPUT (KDATA_BASE_ADDR + 0x0060) + +#define KDATA_KERNEL_ISR_LIST (KDATA_BASE_ADDR + 0x0061) + +#define KDATA_KERNEL_ISR_CBSR1 (KDATA_BASE_ADDR + 0x0062) +#define KDATA_KERNEL_ISR_CBER1 (KDATA_BASE_ADDR + 0x0063) +#define KDATA_KERNEL_ISR_CBCR (KDATA_BASE_ADDR + 0x0064) +#define KDATA_KERNEL_ISR_AR0 (KDATA_BASE_ADDR + 0x0065) +#define KDATA_KERNEL_ISR_AR1 (KDATA_BASE_ADDR + 0x0066) +#define KDATA_KERNEL_ISR_AR2 (KDATA_BASE_ADDR + 0x0067) +#define KDATA_KERNEL_ISR_AR3 (KDATA_BASE_ADDR + 0x0068) +#define KDATA_KERNEL_ISR_AR4 (KDATA_BASE_ADDR + 0x0069) +#define KDATA_KERNEL_ISR_AR5 (KDATA_BASE_ADDR + 0x006A) +#define KDATA_KERNEL_ISR_BRCR (KDATA_BASE_ADDR + 0x006B) +#define KDATA_KERNEL_ISR_PASR (KDATA_BASE_ADDR + 0x006C) +#define KDATA_KERNEL_ISR_PAER (KDATA_BASE_ADDR + 0x006D) + +#define KDATA_CLIENT_SCRATCH0 (KDATA_BASE_ADDR + 0x006E) +#define KDATA_CLIENT_SCRATCH1 (KDATA_BASE_ADDR + 0x006F) +#define KDATA_KERNEL_SCRATCH (KDATA_BASE_ADDR + 0x0070) +#define KDATA_KERNEL_ISR_SCRATCH (KDATA_BASE_ADDR + 0x0071) + +#define KDATA_OUEUE_LEFT (KDATA_BASE_ADDR + 0x0072) +#define KDATA_QUEUE_RIGHT (KDATA_BASE_ADDR + 0x0073) + +#define KDATA_ADC1_REQUEST (KDATA_BASE_ADDR + 0x0074) +#define KDATA_ADC2_REQUEST (KDATA_BASE_ADDR + 0x0075) +#define KDATA_CD_REQUEST (KDATA_BASE_ADDR + 0x0076) +#define KDATA_MIC_REQUEST (KDATA_BASE_ADDR + 0x0077) + +#define KDATA_ADC1_MIXER_REQUEST (KDATA_BASE_ADDR + 0x0078) +#define KDATA_ADC2_MIXER_REQUEST (KDATA_BASE_ADDR + 0x0079) +#define KDATA_CD_MIXER_REQUEST (KDATA_BASE_ADDR + 0x007A) +#define KDATA_MIC_MIXER_REQUEST (KDATA_BASE_ADDR + 0x007B) +#define KDATA_MIC_SYNC_COUNTER (KDATA_BASE_ADDR + 0x007C) + +/* + * second 'segment' (?) reserved for mixer + * buffers.. + */ + +#define KDATA_MIXER_WORD0 (KDATA_BASE_ADDR2 + 0x0000) +#define KDATA_MIXER_WORD1 (KDATA_BASE_ADDR2 + 0x0001) +#define KDATA_MIXER_WORD2 (KDATA_BASE_ADDR2 + 0x0002) +#define KDATA_MIXER_WORD3 (KDATA_BASE_ADDR2 + 0x0003) +#define KDATA_MIXER_WORD4 (KDATA_BASE_ADDR2 + 0x0004) +#define KDATA_MIXER_WORD5 (KDATA_BASE_ADDR2 + 0x0005) +#define KDATA_MIXER_WORD6 (KDATA_BASE_ADDR2 + 0x0006) +#define KDATA_MIXER_WORD7 (KDATA_BASE_ADDR2 + 0x0007) +#define KDATA_MIXER_WORD8 (KDATA_BASE_ADDR2 + 0x0008) +#define KDATA_MIXER_WORD9 (KDATA_BASE_ADDR2 + 0x0009) +#define KDATA_MIXER_WORDA (KDATA_BASE_ADDR2 + 0x000A) +#define KDATA_MIXER_WORDB (KDATA_BASE_ADDR2 + 0x000B) +#define KDATA_MIXER_WORDC (KDATA_BASE_ADDR2 + 0x000C) +#define KDATA_MIXER_WORDD (KDATA_BASE_ADDR2 + 0x000D) +#define KDATA_MIXER_WORDE (KDATA_BASE_ADDR2 + 0x000E) +#define KDATA_MIXER_WORDF (KDATA_BASE_ADDR2 + 0x000F) + +#define KDATA_MIXER_XFER0 (KDATA_BASE_ADDR2 + 0x0010) +#define KDATA_MIXER_XFER1 (KDATA_BASE_ADDR2 + 0x0011) +#define KDATA_MIXER_XFER2 (KDATA_BASE_ADDR2 + 0x0012) +#define KDATA_MIXER_XFER3 (KDATA_BASE_ADDR2 + 0x0013) +#define KDATA_MIXER_XFER4 (KDATA_BASE_ADDR2 + 0x0014) +#define KDATA_MIXER_XFER5 (KDATA_BASE_ADDR2 + 0x0015) +#define KDATA_MIXER_XFER6 (KDATA_BASE_ADDR2 + 0x0016) +#define KDATA_MIXER_XFER7 (KDATA_BASE_ADDR2 + 0x0017) +#define KDATA_MIXER_XFER8 (KDATA_BASE_ADDR2 + 0x0018) +#define KDATA_MIXER_XFER9 (KDATA_BASE_ADDR2 + 0x0019) +#define KDATA_MIXER_XFER_ENDMARK (KDATA_BASE_ADDR2 + 0x001A) + +#define KDATA_MIXER_TASK_NUMBER (KDATA_BASE_ADDR2 + 0x001B) +#define KDATA_CURRENT_MIXER (KDATA_BASE_ADDR2 + 0x001C) +#define KDATA_MIXER_ACTIVE (KDATA_BASE_ADDR2 + 0x001D) +#define KDATA_MIXER_BANK_STATUS (KDATA_BASE_ADDR2 + 0x001E) +#define KDATA_DAC_LEFT_VOLUME (KDATA_BASE_ADDR2 + 0x001F) +#define KDATA_DAC_RIGHT_VOLUME (KDATA_BASE_ADDR2 + 0x0020) + +#define MAX_INSTANCE_MINISRC (KDATA_INSTANCE_MINISRC_ENDMARK - KDATA_INSTANCE0_MINISRC) +#define MAX_VIRTUAL_DMA_CHANNELS (KDATA_DMA_XFER_ENDMARK - KDATA_DMA_XFER0) +#define MAX_VIRTUAL_MIXER_CHANNELS (KDATA_MIXER_XFER_ENDMARK - KDATA_MIXER_XFER0) +#define MAX_VIRTUAL_ADC1_CHANNELS (KDATA_ADC1_XFER_ENDMARK - KDATA_ADC1_XFER0) + +/* + * client data area offsets + */ +#define CDATA_INSTANCE_READY 0x00 + +#define CDATA_HOST_SRC_ADDRL 0x01 +#define CDATA_HOST_SRC_ADDRH 0x02 +#define CDATA_HOST_SRC_END_PLUS_1L 0x03 +#define CDATA_HOST_SRC_END_PLUS_1H 0x04 +#define CDATA_HOST_SRC_CURRENTL 0x05 +#define CDATA_HOST_SRC_CURRENTH 0x06 + +#define CDATA_IN_BUF_CONNECT 0x07 +#define CDATA_OUT_BUF_CONNECT 0x08 + +#define CDATA_IN_BUF_BEGIN 0x09 +#define CDATA_IN_BUF_END_PLUS_1 0x0A +#define CDATA_IN_BUF_HEAD 0x0B +#define CDATA_IN_BUF_TAIL 0x0C +#define CDATA_OUT_BUF_BEGIN 0x0D +#define CDATA_OUT_BUF_END_PLUS_1 0x0E +#define CDATA_OUT_BUF_HEAD 0x0F +#define CDATA_OUT_BUF_TAIL 0x10 + +#define CDATA_DMA_CONTROL 0x11 +#define CDATA_RESERVED 0x12 + +#define CDATA_FREQUENCY 0x13 +#define CDATA_LEFT_VOLUME 0x14 +#define CDATA_RIGHT_VOLUME 0x15 +#define CDATA_LEFT_SUR_VOL 0x16 +#define CDATA_RIGHT_SUR_VOL 0x17 + +#define CDATA_HEADER_LEN 0x18 + +#define SRC3_DIRECTION_OFFSET CDATA_HEADER_LEN +#define SRC3_MODE_OFFSET (CDATA_HEADER_LEN + 1) +#define SRC3_WORD_LENGTH_OFFSET (CDATA_HEADER_LEN + 2) +#define SRC3_PARAMETER_OFFSET (CDATA_HEADER_LEN + 3) +#define SRC3_COEFF_ADDR_OFFSET (CDATA_HEADER_LEN + 8) +#define SRC3_FILTAP_ADDR_OFFSET (CDATA_HEADER_LEN + 10) +#define SRC3_TEMP_INBUF_ADDR_OFFSET (CDATA_HEADER_LEN + 16) +#define SRC3_TEMP_OUTBUF_ADDR_OFFSET (CDATA_HEADER_LEN + 17) + +#define MINISRC_IN_BUFFER_SIZE ( 0x50 * 2 ) +#define MINISRC_OUT_BUFFER_SIZE ( 0x50 * 2 * 2) +#define MINISRC_TMP_BUFFER_SIZE ( 112 + ( MINISRC_BIQUAD_STAGE * 3 + 4 ) * 2 * 2 ) +#define MINISRC_BIQUAD_STAGE 2 +#define MINISRC_COEF_LOC 0x175 + +#define DMACONTROL_BLOCK_MASK 0x000F +#define DMAC_BLOCK0_SELECTOR 0x0000 +#define DMAC_BLOCK1_SELECTOR 0x0001 +#define DMAC_BLOCK2_SELECTOR 0x0002 +#define DMAC_BLOCK3_SELECTOR 0x0003 +#define DMAC_BLOCK4_SELECTOR 0x0004 +#define DMAC_BLOCK5_SELECTOR 0x0005 +#define DMAC_BLOCK6_SELECTOR 0x0006 +#define DMAC_BLOCK7_SELECTOR 0x0007 +#define DMAC_BLOCK8_SELECTOR 0x0008 +#define DMAC_BLOCK9_SELECTOR 0x0009 +#define DMAC_BLOCKA_SELECTOR 0x000A +#define DMAC_BLOCKB_SELECTOR 0x000B +#define DMAC_BLOCKC_SELECTOR 0x000C +#define DMAC_BLOCKD_SELECTOR 0x000D +#define DMAC_BLOCKE_SELECTOR 0x000E +#define DMAC_BLOCKF_SELECTOR 0x000F +#define DMACONTROL_PAGE_MASK 0x00F0 +#define DMAC_PAGE0_SELECTOR 0x0030 +#define DMAC_PAGE1_SELECTOR 0x0020 +#define DMAC_PAGE2_SELECTOR 0x0010 +#define DMAC_PAGE3_SELECTOR 0x0000 +#define DMACONTROL_AUTOREPEAT 0x1000 +#define DMACONTROL_STOPPED 0x2000 +#define DMACONTROL_DIRECTION 0x0100 + +/* + * an arbitrary volume we set the internal + * volume settings to so that the ac97 volume + * range is a little less insane. 0x7fff is + * max. + */ +#define ARB_VOLUME ( 0x6800 ) + +/* + */ + +struct m3_list { + int curlen; + int mem_addr; + int max; +}; + +struct m3_dma { + + int number; + struct snd_pcm_substream *substream; + + struct assp_instance { + unsigned short code, data; + } inst; + + int running; + int opened; + + unsigned long buffer_addr; + int dma_size; + int period_size; + unsigned int hwptr; + int count; + + int index[3]; + struct m3_list *index_list[3]; + + int in_lists; + + struct list_head list; + +}; + +struct snd_m3 { + + struct snd_card *card; + + unsigned long iobase; + + int irq; + unsigned int allegro_flag : 1; + + struct snd_ac97 *ac97; + + struct snd_pcm *pcm; + + struct pci_dev *pci; + + int dacs_active; + int timer_users; + + struct m3_list msrc_list; + struct m3_list mixer_list; + struct m3_list adc1_list; + struct m3_list dma_list; + + /* for storing reset state..*/ + u8 reset_state; + + int external_amp; + int amp_gpio; /* gpio pin # for external amp, -1 = default */ + unsigned int hv_config; /* hardware-volume config bits */ + unsigned irda_workaround :1; /* avoid to touch 0x10 on GPIO_DIRECTION + (e.g. for IrDA on Dell Inspirons) */ + unsigned is_omnibook :1; /* Do HP OmniBook GPIO magic? */ + + /* midi */ + struct snd_rawmidi *rmidi; + + /* pcm streams */ + int num_substreams; + struct m3_dma *substreams; + + spinlock_t reg_lock; + +#ifdef CONFIG_SND_MAESTRO3_INPUT + struct input_dev *input_dev; + char phys[64]; /* physical device path */ +#else + struct snd_kcontrol *master_switch; + struct snd_kcontrol *master_volume; +#endif + struct work_struct hwvol_work; + + unsigned int in_suspend; + + u16 *suspend_mem; + + const struct firmware *assp_kernel_image; + const struct firmware *assp_minisrc_image; +}; + +/* + * pci ids + */ +static const struct pci_device_id snd_m3_ids[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ALLEGRO_1), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, { + PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ALLEGRO), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, { + PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_CANYON3D_2LE), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, { + PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_CANYON3D_2), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, { + PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, { + PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3_1), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, { + PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3_HW), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, { + PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3_2), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, + { }, +}; + +MODULE_DEVICE_TABLE(pci, snd_m3_ids); + +static const struct snd_pci_quirk m3_amp_quirk_list[] = { + SND_PCI_QUIRK(0x0E11, 0x0094, "Compaq Evo N600c", 0x0c), + SND_PCI_QUIRK(0x10f7, 0x833e, "Panasonic CF-28", 0x0d), + SND_PCI_QUIRK(0x10f7, 0x833d, "Panasonic CF-72", 0x0d), + SND_PCI_QUIRK(0x1033, 0x80f1, "NEC LM800J/7", 0x03), + SND_PCI_QUIRK(0x1509, 0x1740, "LEGEND ZhaoYang 3100CF", 0x03), + { } /* END */ +}; + +static const struct snd_pci_quirk m3_irda_quirk_list[] = { + SND_PCI_QUIRK(0x1028, 0x00b0, "Dell Inspiron 4000", 1), + SND_PCI_QUIRK(0x1028, 0x00a4, "Dell Inspiron 8000", 1), + SND_PCI_QUIRK(0x1028, 0x00e6, "Dell Inspiron 8100", 1), + { } /* END */ +}; + +/* hardware volume quirks */ +static const struct snd_pci_quirk m3_hv_quirk_list[] = { + /* Allegro chips */ + SND_PCI_QUIRK(0x0E11, 0x002E, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x0E11, 0x0094, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x0E11, 0xB112, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x0E11, 0xB114, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x103C, 0x0012, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x103C, 0x0018, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x103C, 0x001C, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x103C, 0x001D, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x103C, 0x001E, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x107B, 0x3350, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x10F7, 0x8338, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x10F7, 0x833C, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x10F7, 0x833D, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x10F7, 0x833E, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x10F7, 0x833F, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x13BD, 0x1018, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x13BD, 0x1019, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x13BD, 0x101A, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x14FF, 0x0F03, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x14FF, 0x0F04, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x14FF, 0x0F05, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x156D, 0xB400, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x156D, 0xB795, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x156D, 0xB797, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x156D, 0xC700, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD), + SND_PCI_QUIRK(0x1033, 0x80F1, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x103C, 0x001A, NULL, /* HP OmniBook 6100 */ + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x107B, 0x340A, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x107B, 0x3450, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x109F, 0x3134, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x109F, 0x3161, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x144D, 0x3280, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x144D, 0x3281, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x144D, 0xC002, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x144D, 0xC003, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x1509, 0x1740, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x1610, 0x0010, NULL, + HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x1042, 0x1042, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x107B, 0x9500, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x14FF, 0x0F06, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x1558, 0x8586, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x161F, 0x2011, NULL, HV_CTRL_ENABLE), + /* Maestro3 chips */ + SND_PCI_QUIRK(0x103C, 0x000E, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x103C, 0x0010, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x103C, 0x0011, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x103C, 0x001B, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x104D, 0x80A6, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x104D, 0x80AA, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x107B, 0x5300, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x110A, 0x1998, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x13BD, 0x1015, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x13BD, 0x101C, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x13BD, 0x1802, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x1599, 0x0715, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x5643, 0x5643, NULL, HV_CTRL_ENABLE), + SND_PCI_QUIRK(0x144D, 0x3260, NULL, HV_CTRL_ENABLE | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x144D, 0x3261, NULL, HV_CTRL_ENABLE | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x144D, 0xC000, NULL, HV_CTRL_ENABLE | REDUCED_DEBOUNCE), + SND_PCI_QUIRK(0x144D, 0xC001, NULL, HV_CTRL_ENABLE | REDUCED_DEBOUNCE), + { } /* END */ +}; + +/* HP Omnibook quirks */ +static const struct snd_pci_quirk m3_omnibook_quirk_list[] = { + SND_PCI_QUIRK_ID(0x103c, 0x0010), /* HP OmniBook 6000 */ + SND_PCI_QUIRK_ID(0x103c, 0x0011), /* HP OmniBook 500 */ + { } /* END */ +}; + +/* + * lowlevel functions + */ + +static inline void snd_m3_outw(struct snd_m3 *chip, u16 value, unsigned long reg) +{ + outw(value, chip->iobase + reg); +} + +static inline u16 snd_m3_inw(struct snd_m3 *chip, unsigned long reg) +{ + return inw(chip->iobase + reg); +} + +static inline void snd_m3_outb(struct snd_m3 *chip, u8 value, unsigned long reg) +{ + outb(value, chip->iobase + reg); +} + +static inline u8 snd_m3_inb(struct snd_m3 *chip, unsigned long reg) +{ + return inb(chip->iobase + reg); +} + +/* + * access 16bit words to the code or data regions of the dsp's memory. + * index addresses 16bit words. + */ +static u16 snd_m3_assp_read(struct snd_m3 *chip, u16 region, u16 index) +{ + snd_m3_outw(chip, region & MEMTYPE_MASK, DSP_PORT_MEMORY_TYPE); + snd_m3_outw(chip, index, DSP_PORT_MEMORY_INDEX); + return snd_m3_inw(chip, DSP_PORT_MEMORY_DATA); +} + +static void snd_m3_assp_write(struct snd_m3 *chip, u16 region, u16 index, u16 data) +{ + snd_m3_outw(chip, region & MEMTYPE_MASK, DSP_PORT_MEMORY_TYPE); + snd_m3_outw(chip, index, DSP_PORT_MEMORY_INDEX); + snd_m3_outw(chip, data, DSP_PORT_MEMORY_DATA); +} + +static void snd_m3_assp_halt(struct snd_m3 *chip) +{ + chip->reset_state = snd_m3_inb(chip, DSP_PORT_CONTROL_REG_B) & ~REGB_STOP_CLOCK; + msleep(10); + snd_m3_outb(chip, chip->reset_state & ~REGB_ENABLE_RESET, DSP_PORT_CONTROL_REG_B); +} + +static void snd_m3_assp_continue(struct snd_m3 *chip) +{ + snd_m3_outb(chip, chip->reset_state | REGB_ENABLE_RESET, DSP_PORT_CONTROL_REG_B); +} + + +/* + * This makes me sad. the maestro3 has lists + * internally that must be packed.. 0 terminates, + * apparently, or maybe all unused entries have + * to be 0, the lists have static lengths set + * by the binary code images. + */ + +static int snd_m3_add_list(struct snd_m3 *chip, struct m3_list *list, u16 val) +{ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + list->mem_addr + list->curlen, + val); + return list->curlen++; +} + +static void snd_m3_remove_list(struct snd_m3 *chip, struct m3_list *list, int index) +{ + u16 val; + int lastindex = list->curlen - 1; + + if (index != lastindex) { + val = snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA, + list->mem_addr + lastindex); + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + list->mem_addr + index, + val); + } + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + list->mem_addr + lastindex, + 0); + + list->curlen--; +} + +static void snd_m3_inc_timer_users(struct snd_m3 *chip) +{ + chip->timer_users++; + if (chip->timer_users != 1) + return; + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_TIMER_COUNT_RELOAD, + 240); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_TIMER_COUNT_CURRENT, + 240); + + snd_m3_outw(chip, + snd_m3_inw(chip, HOST_INT_CTRL) | CLKRUN_GEN_ENABLE, + HOST_INT_CTRL); +} + +static void snd_m3_dec_timer_users(struct snd_m3 *chip) +{ + chip->timer_users--; + if (chip->timer_users > 0) + return; + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_TIMER_COUNT_RELOAD, + 0); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_TIMER_COUNT_CURRENT, + 0); + + snd_m3_outw(chip, + snd_m3_inw(chip, HOST_INT_CTRL) & ~CLKRUN_GEN_ENABLE, + HOST_INT_CTRL); +} + +/* + * start/stop + */ + +/* spinlock held! */ +static int snd_m3_pcm_start(struct snd_m3 *chip, struct m3_dma *s, + struct snd_pcm_substream *subs) +{ + if (! s || ! subs) + return -EINVAL; + + snd_m3_inc_timer_users(chip); + switch (subs->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + chip->dacs_active++; + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_INSTANCE_READY, 1); + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_MIXER_TASK_NUMBER, + chip->dacs_active); + break; + case SNDRV_PCM_STREAM_CAPTURE: + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_ADC1_REQUEST, 1); + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_INSTANCE_READY, 1); + break; + } + return 0; +} + +/* spinlock held! */ +static int snd_m3_pcm_stop(struct snd_m3 *chip, struct m3_dma *s, + struct snd_pcm_substream *subs) +{ + if (! s || ! subs) + return -EINVAL; + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_INSTANCE_READY, 0); + snd_m3_dec_timer_users(chip); + switch (subs->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + chip->dacs_active--; + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_MIXER_TASK_NUMBER, + chip->dacs_active); + break; + case SNDRV_PCM_STREAM_CAPTURE: + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_ADC1_REQUEST, 0); + break; + } + return 0; +} + +static int +snd_m3_pcm_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct snd_m3 *chip = snd_pcm_substream_chip(subs); + struct m3_dma *s = subs->runtime->private_data; + int err = -EINVAL; + + if (snd_BUG_ON(!s)) + return -ENXIO; + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (s->running) + err = -EBUSY; + else { + s->running = 1; + err = snd_m3_pcm_start(chip, s, subs); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (! s->running) + err = 0; /* should return error? */ + else { + s->running = 0; + err = snd_m3_pcm_stop(chip, s, subs); + } + break; + } + return err; +} + +/* + * setup + */ +static void +snd_m3_pcm_setup1(struct snd_m3 *chip, struct m3_dma *s, struct snd_pcm_substream *subs) +{ + int dsp_in_size, dsp_out_size, dsp_in_buffer, dsp_out_buffer; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (subs->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dsp_in_size = MINISRC_IN_BUFFER_SIZE - (0x20 * 2); + dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x20 * 2); + } else { + dsp_in_size = MINISRC_IN_BUFFER_SIZE - (0x10 * 2); + dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x10 * 2); + } + dsp_in_buffer = s->inst.data + (MINISRC_TMP_BUFFER_SIZE / 2); + dsp_out_buffer = dsp_in_buffer + (dsp_in_size / 2) + 1; + + s->dma_size = frames_to_bytes(runtime, runtime->buffer_size); + s->period_size = frames_to_bytes(runtime, runtime->period_size); + s->hwptr = 0; + s->count = 0; + +#define LO(x) ((x) & 0xffff) +#define HI(x) LO((x) >> 16) + + /* host dma buffer pointers */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_HOST_SRC_ADDRL, + LO(s->buffer_addr)); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_HOST_SRC_ADDRH, + HI(s->buffer_addr)); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_HOST_SRC_END_PLUS_1L, + LO(s->buffer_addr + s->dma_size)); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_HOST_SRC_END_PLUS_1H, + HI(s->buffer_addr + s->dma_size)); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_HOST_SRC_CURRENTL, + LO(s->buffer_addr)); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_HOST_SRC_CURRENTH, + HI(s->buffer_addr)); +#undef LO +#undef HI + + /* dsp buffers */ + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_IN_BUF_BEGIN, + dsp_in_buffer); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_IN_BUF_END_PLUS_1, + dsp_in_buffer + (dsp_in_size / 2)); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_IN_BUF_HEAD, + dsp_in_buffer); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_IN_BUF_TAIL, + dsp_in_buffer); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_OUT_BUF_BEGIN, + dsp_out_buffer); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_OUT_BUF_END_PLUS_1, + dsp_out_buffer + (dsp_out_size / 2)); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_OUT_BUF_HEAD, + dsp_out_buffer); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_OUT_BUF_TAIL, + dsp_out_buffer); +} + +static void snd_m3_pcm_setup2(struct snd_m3 *chip, struct m3_dma *s, + struct snd_pcm_runtime *runtime) +{ + u32 freq; + + /* + * put us in the lists if we're not already there + */ + if (! s->in_lists) { + s->index[0] = snd_m3_add_list(chip, s->index_list[0], + s->inst.data >> DP_SHIFT_COUNT); + s->index[1] = snd_m3_add_list(chip, s->index_list[1], + s->inst.data >> DP_SHIFT_COUNT); + s->index[2] = snd_m3_add_list(chip, s->index_list[2], + s->inst.data >> DP_SHIFT_COUNT); + s->in_lists = 1; + } + + /* write to 'mono' word */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + SRC3_DIRECTION_OFFSET + 1, + runtime->channels == 2 ? 0 : 1); + /* write to '8bit' word */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + SRC3_DIRECTION_OFFSET + 2, + snd_pcm_format_width(runtime->format) == 16 ? 0 : 1); + + /* set up dac/adc rate */ + freq = DIV_ROUND_CLOSEST(runtime->rate << 15, 48000); + if (freq) + freq--; + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_FREQUENCY, + freq); +} + + +static const struct play_vals { + u16 addr, val; +} pv[] = { + {CDATA_LEFT_VOLUME, ARB_VOLUME}, + {CDATA_RIGHT_VOLUME, ARB_VOLUME}, + {SRC3_DIRECTION_OFFSET, 0} , + /* +1, +2 are stereo/16 bit */ + {SRC3_DIRECTION_OFFSET + 3, 0x0000}, /* fraction? */ + {SRC3_DIRECTION_OFFSET + 4, 0}, /* first l */ + {SRC3_DIRECTION_OFFSET + 5, 0}, /* first r */ + {SRC3_DIRECTION_OFFSET + 6, 0}, /* second l */ + {SRC3_DIRECTION_OFFSET + 7, 0}, /* second r */ + {SRC3_DIRECTION_OFFSET + 8, 0}, /* delta l */ + {SRC3_DIRECTION_OFFSET + 9, 0}, /* delta r */ + {SRC3_DIRECTION_OFFSET + 10, 0x8000}, /* round */ + {SRC3_DIRECTION_OFFSET + 11, 0xFF00}, /* higher bute mark */ + {SRC3_DIRECTION_OFFSET + 13, 0}, /* temp0 */ + {SRC3_DIRECTION_OFFSET + 14, 0}, /* c fraction */ + {SRC3_DIRECTION_OFFSET + 15, 0}, /* counter */ + {SRC3_DIRECTION_OFFSET + 16, 8}, /* numin */ + {SRC3_DIRECTION_OFFSET + 17, 50*2}, /* numout */ + {SRC3_DIRECTION_OFFSET + 18, MINISRC_BIQUAD_STAGE - 1}, /* numstage */ + {SRC3_DIRECTION_OFFSET + 20, 0}, /* filtertap */ + {SRC3_DIRECTION_OFFSET + 21, 0} /* booster */ +}; + + +/* the mode passed should be already shifted and masked */ +static void +snd_m3_playback_setup(struct snd_m3 *chip, struct m3_dma *s, + struct snd_pcm_substream *subs) +{ + unsigned int i; + + /* + * some per client initializers + */ + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + SRC3_DIRECTION_OFFSET + 12, + s->inst.data + 40 + 8); + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + SRC3_DIRECTION_OFFSET + 19, + s->inst.code + MINISRC_COEF_LOC); + + /* enable or disable low pass filter? */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + SRC3_DIRECTION_OFFSET + 22, + subs->runtime->rate > 45000 ? 0xff : 0); + + /* tell it which way dma is going? */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_DMA_CONTROL, + DMACONTROL_AUTOREPEAT + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); + + /* + * set an armload of static initializers + */ + for (i = 0; i < ARRAY_SIZE(pv); i++) + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + pv[i].addr, pv[i].val); +} + +/* + * Native record driver + */ +static const struct rec_vals { + u16 addr, val; +} rv[] = { + {CDATA_LEFT_VOLUME, ARB_VOLUME}, + {CDATA_RIGHT_VOLUME, ARB_VOLUME}, + {SRC3_DIRECTION_OFFSET, 1} , + /* +1, +2 are stereo/16 bit */ + {SRC3_DIRECTION_OFFSET + 3, 0x0000}, /* fraction? */ + {SRC3_DIRECTION_OFFSET + 4, 0}, /* first l */ + {SRC3_DIRECTION_OFFSET + 5, 0}, /* first r */ + {SRC3_DIRECTION_OFFSET + 6, 0}, /* second l */ + {SRC3_DIRECTION_OFFSET + 7, 0}, /* second r */ + {SRC3_DIRECTION_OFFSET + 8, 0}, /* delta l */ + {SRC3_DIRECTION_OFFSET + 9, 0}, /* delta r */ + {SRC3_DIRECTION_OFFSET + 10, 0x8000}, /* round */ + {SRC3_DIRECTION_OFFSET + 11, 0xFF00}, /* higher bute mark */ + {SRC3_DIRECTION_OFFSET + 13, 0}, /* temp0 */ + {SRC3_DIRECTION_OFFSET + 14, 0}, /* c fraction */ + {SRC3_DIRECTION_OFFSET + 15, 0}, /* counter */ + {SRC3_DIRECTION_OFFSET + 16, 50},/* numin */ + {SRC3_DIRECTION_OFFSET + 17, 8}, /* numout */ + {SRC3_DIRECTION_OFFSET + 18, 0}, /* numstage */ + {SRC3_DIRECTION_OFFSET + 19, 0}, /* coef */ + {SRC3_DIRECTION_OFFSET + 20, 0}, /* filtertap */ + {SRC3_DIRECTION_OFFSET + 21, 0}, /* booster */ + {SRC3_DIRECTION_OFFSET + 22, 0xff} /* skip lpf */ +}; + +static void +snd_m3_capture_setup(struct snd_m3 *chip, struct m3_dma *s, struct snd_pcm_substream *subs) +{ + unsigned int i; + + /* + * some per client initializers + */ + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + SRC3_DIRECTION_OFFSET + 12, + s->inst.data + 40 + 8); + + /* tell it which way dma is going? */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_DMA_CONTROL, + DMACONTROL_DIRECTION + DMACONTROL_AUTOREPEAT + + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); + + /* + * set an armload of static initializers + */ + for (i = 0; i < ARRAY_SIZE(rv); i++) + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + rv[i].addr, rv[i].val); +} + +static int snd_m3_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct m3_dma *s = substream->runtime->private_data; + + /* set buffer address */ + s->buffer_addr = substream->runtime->dma_addr; + if (s->buffer_addr & 0x3) { + dev_err(substream->pcm->card->dev, "oh my, not aligned\n"); + s->buffer_addr = s->buffer_addr & ~0x3; + } + return 0; +} + +static int snd_m3_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct m3_dma *s; + + if (substream->runtime->private_data == NULL) + return 0; + s = substream->runtime->private_data; + s->buffer_addr = 0; + return 0; +} + +static int +snd_m3_pcm_prepare(struct snd_pcm_substream *subs) +{ + struct snd_m3 *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + struct m3_dma *s = runtime->private_data; + + if (snd_BUG_ON(!s)) + return -ENXIO; + + if (runtime->format != SNDRV_PCM_FORMAT_U8 && + runtime->format != SNDRV_PCM_FORMAT_S16_LE) + return -EINVAL; + if (runtime->rate > 48000 || + runtime->rate < 8000) + return -EINVAL; + + guard(spinlock_irq)(&chip->reg_lock); + + snd_m3_pcm_setup1(chip, s, subs); + + if (subs->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_m3_playback_setup(chip, s, subs); + else + snd_m3_capture_setup(chip, s, subs); + + snd_m3_pcm_setup2(chip, s, runtime); + + return 0; +} + +/* + * get current pointer + */ +static unsigned int +snd_m3_get_pointer(struct snd_m3 *chip, struct m3_dma *s, struct snd_pcm_substream *subs) +{ + u16 hi = 0, lo = 0; + int retry = 10; + u32 addr; + + /* + * try and get a valid answer + */ + while (retry--) { + hi = snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_HOST_SRC_CURRENTH); + + lo = snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_HOST_SRC_CURRENTL); + + if (hi == snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA, + s->inst.data + CDATA_HOST_SRC_CURRENTH)) + break; + } + addr = lo | ((u32)hi<<16); + return (unsigned int)(addr - s->buffer_addr); +} + +static snd_pcm_uframes_t +snd_m3_pcm_pointer(struct snd_pcm_substream *subs) +{ + struct snd_m3 *chip = snd_pcm_substream_chip(subs); + unsigned int ptr; + struct m3_dma *s = subs->runtime->private_data; + + if (snd_BUG_ON(!s)) + return 0; + + guard(spinlock)(&chip->reg_lock); + ptr = snd_m3_get_pointer(chip, s, subs); + return bytes_to_frames(subs->runtime, ptr); +} + + +/* update pointer */ +/* spinlock held! */ +static void snd_m3_update_ptr(struct snd_m3 *chip, struct m3_dma *s) +{ + struct snd_pcm_substream *subs = s->substream; + unsigned int hwptr; + int diff; + + if (! s->running) + return; + + hwptr = snd_m3_get_pointer(chip, s, subs); + + /* try to avoid expensive modulo divisions */ + if (hwptr >= s->dma_size) + hwptr %= s->dma_size; + + diff = s->dma_size + hwptr - s->hwptr; + if (diff >= s->dma_size) + diff %= s->dma_size; + + s->hwptr = hwptr; + s->count += diff; + + if (s->count >= (signed)s->period_size) { + + if (s->count < 2 * (signed)s->period_size) + s->count -= (signed)s->period_size; + else + s->count %= s->period_size; + + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(subs); + spin_lock(&chip->reg_lock); + } +} + +/* The m3's hardware volume works by incrementing / decrementing 2 counters + (without wrap around) in response to volume button presses and then + generating an interrupt. The pair of counters is stored in bits 1-3 and 5-7 + of a byte wide register. The meaning of bits 0 and 4 is unknown. */ +static void snd_m3_update_hw_volume(struct work_struct *work) +{ + struct snd_m3 *chip = container_of(work, struct snd_m3, hwvol_work); + int x, val; + + /* Figure out which volume control button was pushed, + based on differences from the default register + values. */ + x = inb(chip->iobase + SHADOW_MIX_REG_VOICE) & 0xee; + + /* Reset the volume counters to 4. Tests on the allegro integrated + into a Compaq N600C laptop, have revealed that: + 1) Writing any value will result in the 2 counters being reset to + 4 so writing 0x88 is not strictly necessary + 2) Writing to any of the 4 involved registers will reset all 4 + of them (and reading them always returns the same value for all + of them) + It could be that a maestro deviates from this, so leave the code + as is. */ + outb(0x88, chip->iobase + SHADOW_MIX_REG_VOICE); + outb(0x88, chip->iobase + HW_VOL_COUNTER_VOICE); + outb(0x88, chip->iobase + SHADOW_MIX_REG_MASTER); + outb(0x88, chip->iobase + HW_VOL_COUNTER_MASTER); + + /* Ignore spurious HV interrupts during suspend / resume, this avoids + mistaking them for a mute button press. */ + if (chip->in_suspend) + return; + +#ifndef CONFIG_SND_MAESTRO3_INPUT + if (!chip->master_switch || !chip->master_volume) + return; + + val = snd_ac97_read(chip->ac97, AC97_MASTER); + switch (x) { + case 0x88: + /* The counters have not changed, yet we've received a HV + interrupt. According to tests run by various people this + happens when pressing the mute button. */ + val ^= 0x8000; + break; + case 0xaa: + /* counters increased by 1 -> volume up */ + if ((val & 0x7f) > 0) + val--; + if ((val & 0x7f00) > 0) + val -= 0x0100; + break; + case 0x66: + /* counters decreased by 1 -> volume down */ + if ((val & 0x7f) < 0x1f) + val++; + if ((val & 0x7f00) < 0x1f00) + val += 0x0100; + break; + } + if (snd_ac97_update(chip->ac97, AC97_MASTER, val)) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_switch->id); +#else + if (!chip->input_dev) + return; + + val = 0; + switch (x) { + case 0x88: + /* The counters have not changed, yet we've received a HV + interrupt. According to tests run by various people this + happens when pressing the mute button. */ + val = KEY_MUTE; + break; + case 0xaa: + /* counters increased by 1 -> volume up */ + val = KEY_VOLUMEUP; + break; + case 0x66: + /* counters decreased by 1 -> volume down */ + val = KEY_VOLUMEDOWN; + break; + } + + if (val) { + input_report_key(chip->input_dev, val, 1); + input_sync(chip->input_dev); + input_report_key(chip->input_dev, val, 0); + input_sync(chip->input_dev); + } +#endif +} + +static irqreturn_t snd_m3_interrupt(int irq, void *dev_id) +{ + struct snd_m3 *chip = dev_id; + u8 status; + int i; + + status = inb(chip->iobase + HOST_INT_STATUS); + + if (status == 0xff) + return IRQ_NONE; + + if (status & HV_INT_PENDING) + schedule_work(&chip->hwvol_work); + + /* + * ack an assp int if its running + * and has an int pending + */ + if (status & ASSP_INT_PENDING) { + u8 ctl = inb(chip->iobase + ASSP_CONTROL_B); + if (!(ctl & STOP_ASSP_CLOCK)) { + ctl = inb(chip->iobase + ASSP_HOST_INT_STATUS); + if (ctl & DSP2HOST_REQ_TIMER) { + outb(DSP2HOST_REQ_TIMER, chip->iobase + ASSP_HOST_INT_STATUS); + /* update adc/dac info if it was a timer int */ + guard(spinlock)(&chip->reg_lock); + for (i = 0; i < chip->num_substreams; i++) { + struct m3_dma *s = &chip->substreams[i]; + if (s->running) + snd_m3_update_ptr(chip, s); + } + } + } + } + +#if 0 /* TODO: not supported yet */ + if ((status & MPU401_INT_PENDING) && chip->rmidi) + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data, regs); +#endif + + /* ack ints */ + outb(status, chip->iobase + HOST_INT_STATUS); + + return IRQ_HANDLED; +} + + +/* + */ + +static const struct snd_pcm_hardware snd_m3_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + /*SNDRV_PCM_INFO_PAUSE |*/ + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (512*1024), + .period_bytes_min = 64, + .period_bytes_max = (512*1024), + .periods_min = 1, + .periods_max = 1024, +}; + +static const struct snd_pcm_hardware snd_m3_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + /*SNDRV_PCM_INFO_PAUSE |*/ + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (512*1024), + .period_bytes_min = 64, + .period_bytes_max = (512*1024), + .periods_min = 1, + .periods_max = 1024, +}; + + +/* + */ + +static int +snd_m3_substream_open(struct snd_m3 *chip, struct snd_pcm_substream *subs) +{ + int i; + struct m3_dma *s; + + guard(spinlock_irq)(&chip->reg_lock); + for (i = 0; i < chip->num_substreams; i++) { + s = &chip->substreams[i]; + if (! s->opened) + goto __found; + } + return -ENOMEM; +__found: + s->opened = 1; + s->running = 0; + + subs->runtime->private_data = s; + s->substream = subs; + + /* set list owners */ + if (subs->stream == SNDRV_PCM_STREAM_PLAYBACK) { + s->index_list[0] = &chip->mixer_list; + } else + s->index_list[0] = &chip->adc1_list; + s->index_list[1] = &chip->msrc_list; + s->index_list[2] = &chip->dma_list; + + return 0; +} + +static void +snd_m3_substream_close(struct snd_m3 *chip, struct snd_pcm_substream *subs) +{ + struct m3_dma *s = subs->runtime->private_data; + + if (s == NULL) + return; /* not opened properly */ + + guard(spinlock_irq)(&chip->reg_lock); + if (s->substream && s->running) + snd_m3_pcm_stop(chip, s, s->substream); /* does this happen? */ + if (s->in_lists) { + snd_m3_remove_list(chip, s->index_list[0], s->index[0]); + snd_m3_remove_list(chip, s->index_list[1], s->index[1]); + snd_m3_remove_list(chip, s->index_list[2], s->index[2]); + s->in_lists = 0; + } + s->running = 0; + s->opened = 0; +} + +static int +snd_m3_playback_open(struct snd_pcm_substream *subs) +{ + struct snd_m3 *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + int err; + + err = snd_m3_substream_open(chip, subs); + if (err < 0) + return err; + + runtime->hw = snd_m3_playback; + + return 0; +} + +static int +snd_m3_playback_close(struct snd_pcm_substream *subs) +{ + struct snd_m3 *chip = snd_pcm_substream_chip(subs); + + snd_m3_substream_close(chip, subs); + return 0; +} + +static int +snd_m3_capture_open(struct snd_pcm_substream *subs) +{ + struct snd_m3 *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + int err; + + err = snd_m3_substream_open(chip, subs); + if (err < 0) + return err; + + runtime->hw = snd_m3_capture; + + return 0; +} + +static int +snd_m3_capture_close(struct snd_pcm_substream *subs) +{ + struct snd_m3 *chip = snd_pcm_substream_chip(subs); + + snd_m3_substream_close(chip, subs); + return 0; +} + +/* + * create pcm instance + */ + +static const struct snd_pcm_ops snd_m3_playback_ops = { + .open = snd_m3_playback_open, + .close = snd_m3_playback_close, + .hw_params = snd_m3_pcm_hw_params, + .hw_free = snd_m3_pcm_hw_free, + .prepare = snd_m3_pcm_prepare, + .trigger = snd_m3_pcm_trigger, + .pointer = snd_m3_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_m3_capture_ops = { + .open = snd_m3_capture_open, + .close = snd_m3_capture_close, + .hw_params = snd_m3_pcm_hw_params, + .hw_free = snd_m3_pcm_hw_free, + .prepare = snd_m3_pcm_prepare, + .trigger = snd_m3_pcm_trigger, + .pointer = snd_m3_pcm_pointer, +}; + +static int +snd_m3_pcm(struct snd_m3 * chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, chip->card->driver, device, + MAX_PLAYBACKS, MAX_CAPTURES, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_m3_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_m3_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + strscpy(pcm->name, chip->card->driver); + chip->pcm = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 64*1024); + + return 0; +} + + +/* + * ac97 interface + */ + +/* + * Wait for the ac97 serial bus to be free. + * return nonzero if the bus is still busy. + */ +static int snd_m3_ac97_wait(struct snd_m3 *chip) +{ + int i = 10000; + + do { + if (! (snd_m3_inb(chip, 0x30) & 1)) + return 0; + cpu_relax(); + } while (i-- > 0); + + dev_err(chip->card->dev, "ac97 serial bus busy\n"); + return 1; +} + +static unsigned short +snd_m3_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct snd_m3 *chip = ac97->private_data; + unsigned short data = 0xffff; + + if (snd_m3_ac97_wait(chip)) + goto fail; + snd_m3_outb(chip, 0x80 | (reg & 0x7f), CODEC_COMMAND); + if (snd_m3_ac97_wait(chip)) + goto fail; + data = snd_m3_inw(chip, CODEC_DATA); +fail: + return data; +} + +static void +snd_m3_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) +{ + struct snd_m3 *chip = ac97->private_data; + + if (snd_m3_ac97_wait(chip)) + return; + snd_m3_outw(chip, val, CODEC_DATA); + snd_m3_outb(chip, reg & 0x7f, CODEC_COMMAND); + /* + * Workaround for buggy ES1988 integrated AC'97 codec. It remains silent + * until the MASTER volume or mute is touched (alsactl restore does not + * work). + */ + if (ac97->id == 0x45838308 && reg == AC97_MASTER) { + snd_m3_ac97_wait(chip); + snd_m3_outw(chip, val, CODEC_DATA); + snd_m3_outb(chip, reg & 0x7f, CODEC_COMMAND); + } +} + + +static void snd_m3_remote_codec_config(struct snd_m3 *chip, int isremote) +{ + int io = chip->iobase; + u16 tmp; + + isremote = isremote ? 1 : 0; + + tmp = inw(io + RING_BUS_CTRL_B) & ~SECOND_CODEC_ID_MASK; + /* enable dock on Dell Latitude C810 */ + if (chip->pci->subsystem_vendor == 0x1028 && + chip->pci->subsystem_device == 0x00e5) + tmp |= M3I_DOCK_ENABLE; + outw(tmp | isremote, io + RING_BUS_CTRL_B); + outw((inw(io + SDO_OUT_DEST_CTRL) & ~COMMAND_ADDR_OUT) | isremote, + io + SDO_OUT_DEST_CTRL); + outw((inw(io + SDO_IN_DEST_CTRL) & ~STATUS_ADDR_IN) | isremote, + io + SDO_IN_DEST_CTRL); +} + +/* + * hack, returns non zero on err + */ +static int snd_m3_try_read_vendor(struct snd_m3 *chip) +{ + u16 ret; + + if (snd_m3_ac97_wait(chip)) + return 1; + + snd_m3_outb(chip, 0x80 | (AC97_VENDOR_ID1 & 0x7f), 0x30); + + if (snd_m3_ac97_wait(chip)) + return 1; + + ret = snd_m3_inw(chip, 0x32); + + return (ret == 0) || (ret == 0xffff); +} + +static void snd_m3_ac97_reset(struct snd_m3 *chip) +{ + u16 dir; + int delay1 = 0, delay2 = 0, i; + int io = chip->iobase; + + if (chip->allegro_flag) { + /* + * the onboard codec on the allegro seems + * to want to wait a very long time before + * coming back to life + */ + delay1 = 50; + delay2 = 800; + } else { + /* maestro3 */ + delay1 = 20; + delay2 = 500; + } + + for (i = 0; i < 5; i++) { + dir = inw(io + GPIO_DIRECTION); + if (!chip->irda_workaround) + dir |= 0x10; /* assuming pci bus master? */ + + snd_m3_remote_codec_config(chip, 0); + + outw(IO_SRAM_ENABLE, io + RING_BUS_CTRL_A); + udelay(20); + + outw(dir & ~GPO_PRIMARY_AC97 , io + GPIO_DIRECTION); + outw(~GPO_PRIMARY_AC97 , io + GPIO_MASK); + outw(0, io + GPIO_DATA); + outw(dir | GPO_PRIMARY_AC97, io + GPIO_DIRECTION); + + schedule_timeout_uninterruptible(msecs_to_jiffies(delay1)); + + outw(GPO_PRIMARY_AC97, io + GPIO_DATA); + udelay(5); + /* ok, bring back the ac-link */ + outw(IO_SRAM_ENABLE | SERIAL_AC_LINK_ENABLE, io + RING_BUS_CTRL_A); + outw(~0, io + GPIO_MASK); + + schedule_timeout_uninterruptible(msecs_to_jiffies(delay2)); + + if (! snd_m3_try_read_vendor(chip)) + break; + + delay1 += 10; + delay2 += 100; + + dev_dbg(chip->card->dev, + "retrying codec reset with delays of %d and %d ms\n", + delay1, delay2); + } + +#if 0 + /* more gung-ho reset that doesn't + * seem to work anywhere :) + */ + tmp = inw(io + RING_BUS_CTRL_A); + outw(RAC_SDFS_ENABLE|LAC_SDFS_ENABLE, io + RING_BUS_CTRL_A); + msleep(20); + outw(tmp, io + RING_BUS_CTRL_A); + msleep(50); +#endif +} + +static int snd_m3_mixer(struct snd_m3 *chip) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_m3_ac97_write, + .read = snd_m3_ac97_read, + }; + + err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + err = snd_ac97_mixer(pbus, &ac97, &chip->ac97); + if (err < 0) + return err; + + /* seems ac97 PCM needs initialization.. hack hack.. */ + snd_ac97_write(chip->ac97, AC97_PCM, 0x8000 | (15 << 8) | 15); + schedule_timeout_uninterruptible(msecs_to_jiffies(100)); + snd_ac97_write(chip->ac97, AC97_PCM, 0); + +#ifndef CONFIG_SND_MAESTRO3_INPUT + chip->master_switch = snd_ctl_find_id_mixer(chip->card, + "Master Playback Switch"); + chip->master_volume = snd_ctl_find_id_mixer(chip->card, + "Master Playback Volume"); +#endif + + return 0; +} + + +/* + * initialize ASSP + */ + +#define MINISRC_LPF_LEN 10 +static const u16 minisrc_lpf[MINISRC_LPF_LEN] = { + 0X0743, 0X1104, 0X0A4C, 0XF88D, 0X242C, + 0X1023, 0X1AA9, 0X0B60, 0XEFDD, 0X186F +}; + +static void snd_m3_assp_init(struct snd_m3 *chip) +{ + unsigned int i; + const __le16 *data; + + /* zero kernel data */ + for (i = 0; i < (REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA) / 2; i++) + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_BASE_ADDR + i, 0); + + /* zero mixer data? */ + for (i = 0; i < (REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA) / 2; i++) + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_BASE_ADDR2 + i, 0); + + /* init dma pointer */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_CURRENT_DMA, + KDATA_DMA_XFER0); + + /* write kernel into code memory.. */ + data = (const __le16 *)chip->assp_kernel_image->data; + for (i = 0 ; i * 2 < chip->assp_kernel_image->size; i++) { + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE, + REV_B_CODE_MEMORY_BEGIN + i, + le16_to_cpu(data[i])); + } + + /* + * We only have this one client and we know that 0x400 + * is free in our kernel's mem map, so lets just + * drop it there. It seems that the minisrc doesn't + * need vectors, so we won't bother with them.. + */ + data = (const __le16 *)chip->assp_minisrc_image->data; + for (i = 0; i * 2 < chip->assp_minisrc_image->size; i++) { + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE, + 0x400 + i, le16_to_cpu(data[i])); + } + + /* + * write the coefficients for the low pass filter? + */ + for (i = 0; i < MINISRC_LPF_LEN ; i++) { + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE, + 0x400 + MINISRC_COEF_LOC + i, + minisrc_lpf[i]); + } + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE, + 0x400 + MINISRC_COEF_LOC + MINISRC_LPF_LEN, + 0x8000); + + /* + * the minisrc is the only thing on + * our task list.. + */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_TASK0, + 0x400); + + /* + * init the mixer number.. + */ + + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_MIXER_TASK_NUMBER,0); + + /* + * EXTREME KERNEL MASTER VOLUME + */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_DAC_LEFT_VOLUME, ARB_VOLUME); + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_DAC_RIGHT_VOLUME, ARB_VOLUME); + + chip->mixer_list.curlen = 0; + chip->mixer_list.mem_addr = KDATA_MIXER_XFER0; + chip->mixer_list.max = MAX_VIRTUAL_MIXER_CHANNELS; + chip->adc1_list.curlen = 0; + chip->adc1_list.mem_addr = KDATA_ADC1_XFER0; + chip->adc1_list.max = MAX_VIRTUAL_ADC1_CHANNELS; + chip->dma_list.curlen = 0; + chip->dma_list.mem_addr = KDATA_DMA_XFER0; + chip->dma_list.max = MAX_VIRTUAL_DMA_CHANNELS; + chip->msrc_list.curlen = 0; + chip->msrc_list.mem_addr = KDATA_INSTANCE0_MINISRC; + chip->msrc_list.max = MAX_INSTANCE_MINISRC; +} + + +static int snd_m3_assp_client_init(struct snd_m3 *chip, struct m3_dma *s, int index) +{ + int data_bytes = 2 * ( MINISRC_TMP_BUFFER_SIZE / 2 + + MINISRC_IN_BUFFER_SIZE / 2 + + 1 + MINISRC_OUT_BUFFER_SIZE / 2 + 1 ); + int address, i; + + /* + * the revb memory map has 0x1100 through 0x1c00 + * free. + */ + + /* + * align instance address to 256 bytes so that its + * shifted list address is aligned. + * list address = (mem address >> 1) >> 7; + */ + data_bytes = ALIGN(data_bytes, 256); + address = 0x1100 + ((data_bytes/2) * index); + + if ((address + (data_bytes/2)) >= 0x1c00) { + dev_err(chip->card->dev, + "no memory for %d bytes at ind %d (addr 0x%x)\n", + data_bytes, index, address); + return -ENOMEM; + } + + s->number = index; + s->inst.code = 0x400; + s->inst.data = address; + + for (i = data_bytes / 2; i > 0; address++, i--) { + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + address, 0); + } + + return 0; +} + + +/* + * this works for the reference board, have to find + * out about others + * + * this needs more magic for 4 speaker, but.. + */ +static void +snd_m3_amp_enable(struct snd_m3 *chip, int enable) +{ + int io = chip->iobase; + u16 gpo, polarity; + + if (! chip->external_amp) + return; + + polarity = enable ? 0 : 1; + polarity = polarity << chip->amp_gpio; + gpo = 1 << chip->amp_gpio; + + outw(~gpo, io + GPIO_MASK); + + outw(inw(io + GPIO_DIRECTION) | gpo, + io + GPIO_DIRECTION); + + outw((GPO_SECONDARY_AC97 | GPO_PRIMARY_AC97 | polarity), + io + GPIO_DATA); + + outw(0xffff, io + GPIO_MASK); +} + +static void +snd_m3_hv_init(struct snd_m3 *chip) +{ + unsigned long io = chip->iobase; + u16 val = GPI_VOL_DOWN | GPI_VOL_UP; + + if (!chip->is_omnibook) + return; + + /* + * Volume buttons on some HP OmniBook laptops + * require some GPIO magic to work correctly. + */ + outw(0xffff, io + GPIO_MASK); + outw(0x0000, io + GPIO_DATA); + + outw(~val, io + GPIO_MASK); + outw(inw(io + GPIO_DIRECTION) & ~val, io + GPIO_DIRECTION); + outw(val, io + GPIO_MASK); + + outw(0xffff, io + GPIO_MASK); +} + +static int +snd_m3_chip_init(struct snd_m3 *chip) +{ + struct pci_dev *pcidev = chip->pci; + unsigned long io = chip->iobase; + u32 n; + u16 w; + u8 t; /* makes as much sense as 'n', no? */ + + pci_read_config_word(pcidev, PCI_LEGACY_AUDIO_CTRL, &w); + w &= ~(SOUND_BLASTER_ENABLE|FM_SYNTHESIS_ENABLE| + MPU401_IO_ENABLE|MPU401_IRQ_ENABLE|ALIAS_10BIT_IO| + DISABLE_LEGACY); + pci_write_config_word(pcidev, PCI_LEGACY_AUDIO_CTRL, w); + + pci_read_config_dword(pcidev, PCI_ALLEGRO_CONFIG, &n); + n &= ~(HV_CTRL_ENABLE | REDUCED_DEBOUNCE | HV_BUTTON_FROM_GD); + n |= chip->hv_config; + /* For some reason we must always use reduced debounce. */ + n |= REDUCED_DEBOUNCE; + n |= PM_CTRL_ENABLE | CLK_DIV_BY_49 | USE_PCI_TIMING; + pci_write_config_dword(pcidev, PCI_ALLEGRO_CONFIG, n); + + outb(RESET_ASSP, chip->iobase + ASSP_CONTROL_B); + pci_read_config_dword(pcidev, PCI_ALLEGRO_CONFIG, &n); + n &= ~INT_CLK_SELECT; + if (!chip->allegro_flag) { + n &= ~INT_CLK_MULT_ENABLE; + n |= INT_CLK_SRC_NOT_PCI; + } + n &= ~( CLK_MULT_MODE_SELECT | CLK_MULT_MODE_SELECT_2 ); + pci_write_config_dword(pcidev, PCI_ALLEGRO_CONFIG, n); + + if (chip->allegro_flag) { + pci_read_config_dword(pcidev, PCI_USER_CONFIG, &n); + n |= IN_CLK_12MHZ_SELECT; + pci_write_config_dword(pcidev, PCI_USER_CONFIG, n); + } + + t = inb(chip->iobase + ASSP_CONTROL_A); + t &= ~( DSP_CLK_36MHZ_SELECT | ASSP_CLK_49MHZ_SELECT); + t |= ASSP_CLK_49MHZ_SELECT; + t |= ASSP_0_WS_ENABLE; + outb(t, chip->iobase + ASSP_CONTROL_A); + + snd_m3_assp_init(chip); /* download DSP code before starting ASSP below */ + outb(RUN_ASSP, chip->iobase + ASSP_CONTROL_B); + + outb(0x00, io + HARDWARE_VOL_CTRL); + outb(0x88, io + SHADOW_MIX_REG_VOICE); + outb(0x88, io + HW_VOL_COUNTER_VOICE); + outb(0x88, io + SHADOW_MIX_REG_MASTER); + outb(0x88, io + HW_VOL_COUNTER_MASTER); + + return 0; +} + +static void +snd_m3_enable_ints(struct snd_m3 *chip) +{ + unsigned long io = chip->iobase; + unsigned short val; + + /* TODO: MPU401 not supported yet */ + val = ASSP_INT_ENABLE /*| MPU401_INT_ENABLE*/; + if (chip->hv_config & HV_CTRL_ENABLE) + val |= HV_INT_ENABLE; + outb(val, chip->iobase + HOST_INT_STATUS); + outw(val, io + HOST_INT_CTRL); + outb(inb(io + ASSP_CONTROL_C) | ASSP_HOST_INT_ENABLE, + io + ASSP_CONTROL_C); +} + + +/* + */ + +static void snd_m3_free(struct snd_card *card) +{ + struct snd_m3 *chip = card->private_data; + struct m3_dma *s; + int i; + + cancel_work_sync(&chip->hwvol_work); + + if (chip->substreams) { + guard(spinlock_irq)(&chip->reg_lock); + for (i = 0; i < chip->num_substreams; i++) { + s = &chip->substreams[i]; + /* check surviving pcms; this should not happen though.. */ + if (s->substream && s->running) + snd_m3_pcm_stop(chip, s, s->substream); + } + } + if (chip->iobase) { + outw(0, chip->iobase + HOST_INT_CTRL); /* disable ints */ + } + + vfree(chip->suspend_mem); + release_firmware(chip->assp_kernel_image); + release_firmware(chip->assp_minisrc_image); +} + + +/* + * APM support + */ +static int m3_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_m3 *chip = card->private_data; + int i, dsp_index; + + if (chip->suspend_mem == NULL) + return 0; + + chip->in_suspend = 1; + cancel_work_sync(&chip->hwvol_work); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(chip->ac97); + + msleep(10); /* give the assp a chance to idle.. */ + + snd_m3_assp_halt(chip); + + /* save dsp image */ + dsp_index = 0; + for (i = REV_B_CODE_MEMORY_BEGIN; i <= REV_B_CODE_MEMORY_END; i++) + chip->suspend_mem[dsp_index++] = + snd_m3_assp_read(chip, MEMTYPE_INTERNAL_CODE, i); + for (i = REV_B_DATA_MEMORY_BEGIN ; i <= REV_B_DATA_MEMORY_END; i++) + chip->suspend_mem[dsp_index++] = + snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA, i); + return 0; +} + +static int m3_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_m3 *chip = card->private_data; + int i, dsp_index; + + if (chip->suspend_mem == NULL) + return 0; + + /* first lets just bring everything back. .*/ + snd_m3_outw(chip, 0, 0x54); + snd_m3_outw(chip, 0, 0x56); + + snd_m3_chip_init(chip); + snd_m3_assp_halt(chip); + snd_m3_ac97_reset(chip); + + /* restore dsp image */ + dsp_index = 0; + for (i = REV_B_CODE_MEMORY_BEGIN; i <= REV_B_CODE_MEMORY_END; i++) + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE, i, + chip->suspend_mem[dsp_index++]); + for (i = REV_B_DATA_MEMORY_BEGIN ; i <= REV_B_DATA_MEMORY_END; i++) + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, i, + chip->suspend_mem[dsp_index++]); + + /* tell the dma engine to restart itself */ + snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, + KDATA_DMA_ACTIVE, 0); + + /* restore ac97 registers */ + snd_ac97_resume(chip->ac97); + + snd_m3_assp_continue(chip); + snd_m3_enable_ints(chip); + snd_m3_amp_enable(chip, 1); + + snd_m3_hv_init(chip); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + chip->in_suspend = 0; + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(m3_pm, m3_suspend, m3_resume); + +#ifdef CONFIG_SND_MAESTRO3_INPUT +static int snd_m3_input_register(struct snd_m3 *chip) +{ + struct input_dev *input_dev; + int err; + + input_dev = devm_input_allocate_device(&chip->pci->dev); + if (!input_dev) + return -ENOMEM; + + snprintf(chip->phys, sizeof(chip->phys), "pci-%s/input0", + pci_name(chip->pci)); + + input_dev->name = chip->card->driver; + input_dev->phys = chip->phys; + input_dev->id.bustype = BUS_PCI; + input_dev->id.vendor = chip->pci->vendor; + input_dev->id.product = chip->pci->device; + input_dev->dev.parent = &chip->pci->dev; + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(KEY_MUTE, input_dev->keybit); + __set_bit(KEY_VOLUMEDOWN, input_dev->keybit); + __set_bit(KEY_VOLUMEUP, input_dev->keybit); + + err = input_register_device(input_dev); + if (err) + return err; + + chip->input_dev = input_dev; + return 0; +} +#endif /* CONFIG_INPUT */ + +/* + */ + +static int +snd_m3_create(struct snd_card *card, struct pci_dev *pci, + int enable_amp, + int amp_gpio) +{ + struct snd_m3 *chip = card->private_data; + int i, err; + const struct snd_pci_quirk *quirk; + + if (pcim_enable_device(pci)) + return -EIO; + + /* check, if we can restrict PCI DMA transfers to 28 bits */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(28))) { + dev_err(card->dev, + "architecture does not support 28bit PCI busmaster DMA\n"); + return -ENXIO; + } + + spin_lock_init(&chip->reg_lock); + + switch (pci->device) { + case PCI_DEVICE_ID_ESS_ALLEGRO: + case PCI_DEVICE_ID_ESS_ALLEGRO_1: + case PCI_DEVICE_ID_ESS_CANYON3D_2LE: + case PCI_DEVICE_ID_ESS_CANYON3D_2: + chip->allegro_flag = 1; + break; + } + + chip->card = card; + chip->pci = pci; + chip->irq = -1; + INIT_WORK(&chip->hwvol_work, snd_m3_update_hw_volume); + card->private_free = snd_m3_free; + + chip->external_amp = enable_amp; + if (amp_gpio >= 0 && amp_gpio <= 0x0f) + chip->amp_gpio = amp_gpio; + else { + quirk = snd_pci_quirk_lookup(pci, m3_amp_quirk_list); + if (quirk) { + dev_info(card->dev, "set amp-gpio for '%s'\n", + snd_pci_quirk_name(quirk)); + chip->amp_gpio = quirk->value; + } else if (chip->allegro_flag) + chip->amp_gpio = GPO_EXT_AMP_ALLEGRO; + else /* presumably this is for all 'maestro3's.. */ + chip->amp_gpio = GPO_EXT_AMP_M3; + } + + quirk = snd_pci_quirk_lookup(pci, m3_irda_quirk_list); + if (quirk) { + dev_info(card->dev, "enabled irda workaround for '%s'\n", + snd_pci_quirk_name(quirk)); + chip->irda_workaround = 1; + } + quirk = snd_pci_quirk_lookup(pci, m3_hv_quirk_list); + if (quirk) + chip->hv_config = quirk->value; + if (snd_pci_quirk_lookup(pci, m3_omnibook_quirk_list)) + chip->is_omnibook = 1; + + chip->num_substreams = NR_DSPS; + chip->substreams = devm_kcalloc(&pci->dev, chip->num_substreams, + sizeof(struct m3_dma), GFP_KERNEL); + if (!chip->substreams) + return -ENOMEM; + + err = request_firmware(&chip->assp_kernel_image, + "ess/maestro3_assp_kernel.fw", &pci->dev); + if (err < 0) + return err; + + err = request_firmware(&chip->assp_minisrc_image, + "ess/maestro3_assp_minisrc.fw", &pci->dev); + if (err < 0) + return err; + + err = pcim_request_all_regions(pci, card->driver); + if (err < 0) + return err; + + chip->iobase = pci_resource_start(pci, 0); + + /* just to be sure */ + pci_set_master(pci); + + snd_m3_chip_init(chip); + snd_m3_assp_halt(chip); + + snd_m3_ac97_reset(chip); + + snd_m3_amp_enable(chip, 1); + + snd_m3_hv_init(chip); + + if (devm_request_irq(&pci->dev, pci->irq, snd_m3_interrupt, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -ENOMEM; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + + if (IS_ENABLED(CONFIG_PM_SLEEP)) { + chip->suspend_mem = + vmalloc_array(REV_B_CODE_MEMORY_LENGTH + + REV_B_DATA_MEMORY_LENGTH, + sizeof(u16)); + if (!chip->suspend_mem) + dev_warn(card->dev, "can't allocate apm buffer\n"); + } + + err = snd_m3_mixer(chip); + if (err < 0) + return err; + + for (i = 0; i < chip->num_substreams; i++) { + struct m3_dma *s = &chip->substreams[i]; + err = snd_m3_assp_client_init(chip, s, i); + if (err < 0) + return err; + } + + err = snd_m3_pcm(chip, 0); + if (err < 0) + return err; + +#ifdef CONFIG_SND_MAESTRO3_INPUT + if (chip->hv_config & HV_CTRL_ENABLE) { + err = snd_m3_input_register(chip); + if (err) + dev_warn(card->dev, + "Input device registration failed with error %i", + err); + } +#endif + + snd_m3_enable_ints(chip); + snd_m3_assp_continue(chip); + + return 0; +} + +/* + */ +static int +__snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_m3 *chip; + int err; + + /* don't pick up modems */ + if (((pci->class >> 8) & 0xffff) != PCI_CLASS_MULTIMEDIA_AUDIO) + return -ENODEV; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + switch (pci->device) { + case PCI_DEVICE_ID_ESS_ALLEGRO: + case PCI_DEVICE_ID_ESS_ALLEGRO_1: + strscpy(card->driver, "Allegro"); + break; + case PCI_DEVICE_ID_ESS_CANYON3D_2LE: + case PCI_DEVICE_ID_ESS_CANYON3D_2: + strscpy(card->driver, "Canyon3D-2"); + break; + default: + strscpy(card->driver, "Maestro3"); + break; + } + + err = snd_m3_create(card, pci, external_amp[dev], amp_gpio[dev]); + if (err < 0) + return err; + + sprintf(card->shortname, "ESS %s PCI", card->driver); + sprintf(card->longname, "%s at 0x%lx, irq %d", + card->shortname, chip->iobase, chip->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + +#if 0 /* TODO: not supported yet */ + /* TODO enable MIDI IRQ and I/O */ + err = snd_mpu401_uart_new(chip->card, 0, MPU401_HW_MPU401, + chip->iobase + MPU401_DATA_PORT, + MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK, + -1, &chip->rmidi); + if (err < 0) + dev_warn(card->dev, "no MIDI support.\n"); +#endif + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int +snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_m3_probe(pci, pci_id)); +} + +static struct pci_driver m3_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_m3_ids, + .probe = snd_m3_probe, + .driver = { + .pm = &m3_pm, + }, +}; + +module_pci_driver(m3_driver); diff --git a/sound/pci/mixart/Makefile b/sound/pci/mixart/Makefile new file mode 100644 index 000000000..b803e5e72 --- /dev/null +++ b/sound/pci/mixart/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-mixart-y := mixart.o mixart_core.o mixart_hwdep.o mixart_mixer.o + +obj-$(CONFIG_SND_MIXART) += snd-mixart.o diff --git a/sound/pci/mixart/mixart.c b/sound/pci/mixart/mixart.c new file mode 100644 index 000000000..f451554cf --- /dev/null +++ b/sound/pci/mixart/mixart.c @@ -0,0 +1,1390 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram miXart soundcards + * + * main file with alsa callbacks + * + * Copyright (c) 2003 by Digigram + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "mixart.h" +#include "mixart_hwdep.h" +#include "mixart_core.h" +#include "mixart_mixer.h" + +#define CARD_NAME "miXart" + +MODULE_AUTHOR("Digigram "); +MODULE_DESCRIPTION("Digigram " CARD_NAME); +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_PNP; /* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Digigram " CARD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Digigram " CARD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Digigram " CARD_NAME " soundcard."); + +/* + */ + +static const struct pci_device_id snd_mixart_ids[] = { + { PCI_VDEVICE(MOTOROLA, 0x0003) }, /* MC8240 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_mixart_ids); + + +static int mixart_set_pipe_state(struct mixart_mgr *mgr, + struct mixart_pipe *pipe, int start) +{ + struct mixart_group_state_req group_state; + struct mixart_group_state_resp group_state_resp; + struct mixart_msg request; + int err; + u32 system_msg_uid; + + switch(pipe->status) { + case PIPE_RUNNING: + case PIPE_CLOCK_SET: + if(start) return 0; /* already started */ + break; + case PIPE_STOPPED: + if(!start) return 0; /* already stopped */ + break; + default: + dev_err(&mgr->pci->dev, + "error mixart_set_pipe_state called with wrong pipe->status!\n"); + return -EINVAL; /* function called with wrong pipe status */ + } + + system_msg_uid = 0x12345678; /* the event ! (take care: the MSB and two LSB's have to be 0) */ + + /* wait on the last MSG_SYSTEM_SEND_SYNCHRO_CMD command to be really finished */ + + request.message_id = MSG_SYSTEM_WAIT_SYNCHRO_CMD; + request.uid = (struct mixart_uid){0,0}; + request.data = &system_msg_uid; + request.size = sizeof(system_msg_uid); + + err = snd_mixart_send_msg_wait_notif(mgr, &request, system_msg_uid); + if(err) { + dev_err(&mgr->pci->dev, + "error : MSG_SYSTEM_WAIT_SYNCHRO_CMD was not notified !\n"); + return err; + } + + /* start or stop the pipe (1 pipe) */ + + memset(&group_state, 0, sizeof(group_state)); + group_state.pipe_count = 1; + group_state.pipe_uid = pipe->group_uid; + + if(start) + request.message_id = MSG_STREAM_START_STREAM_GRP_PACKET; + else + request.message_id = MSG_STREAM_STOP_STREAM_GRP_PACKET; + + request.uid = pipe->group_uid; /*(struct mixart_uid){0,0};*/ + request.data = &group_state; + request.size = sizeof(group_state); + + err = snd_mixart_send_msg(mgr, &request, sizeof(group_state_resp), &group_state_resp); + if (err < 0 || group_state_resp.txx_status != 0) { + dev_err(&mgr->pci->dev, + "error MSG_STREAM_ST***_STREAM_GRP_PACKET err=%x stat=%x !\n", + err, group_state_resp.txx_status); + return -EINVAL; + } + + if(start) { + u32 stat = 0; + + group_state.pipe_count = 0; /* in case of start same command once again with pipe_count=0 */ + + err = snd_mixart_send_msg(mgr, &request, sizeof(group_state_resp), &group_state_resp); + if (err < 0 || group_state_resp.txx_status != 0) { + dev_err(&mgr->pci->dev, + "error MSG_STREAM_START_STREAM_GRP_PACKET err=%x stat=%x !\n", + err, group_state_resp.txx_status); + return -EINVAL; + } + + /* in case of start send a synchro top */ + + request.message_id = MSG_SYSTEM_SEND_SYNCHRO_CMD; + request.uid = (struct mixart_uid){0,0}; + request.data = NULL; + request.size = 0; + + err = snd_mixart_send_msg(mgr, &request, sizeof(stat), &stat); + if (err < 0 || stat != 0) { + dev_err(&mgr->pci->dev, + "error MSG_SYSTEM_SEND_SYNCHRO_CMD err=%x stat=%x !\n", + err, stat); + return -EINVAL; + } + + pipe->status = PIPE_RUNNING; + } + else /* !start */ + pipe->status = PIPE_STOPPED; + + return 0; +} + + +static int mixart_set_clock(struct mixart_mgr *mgr, + struct mixart_pipe *pipe, unsigned int rate) +{ + struct mixart_msg request; + struct mixart_clock_properties clock_properties; + struct mixart_clock_properties_resp clock_prop_resp; + int err; + + switch(pipe->status) { + case PIPE_CLOCK_SET: + break; + case PIPE_RUNNING: + if(rate != 0) + break; + fallthrough; + default: + if(rate == 0) + return 0; /* nothing to do */ + else { + dev_err(&mgr->pci->dev, + "error mixart_set_clock(%d) called with wrong pipe->status !\n", + rate); + return -EINVAL; + } + } + + memset(&clock_properties, 0, sizeof(clock_properties)); + clock_properties.clock_generic_type = (rate != 0) ? CGT_INTERNAL_CLOCK : CGT_NO_CLOCK; + clock_properties.clock_mode = CM_STANDALONE; + clock_properties.frequency = rate; + clock_properties.nb_callers = 1; /* only one entry in uid_caller ! */ + clock_properties.uid_caller = pipe->group_uid; + + dev_dbg(&mgr->pci->dev, "mixart_set_clock to %d kHz\n", rate); + + request.message_id = MSG_CLOCK_SET_PROPERTIES; + request.uid = mgr->uid_console_manager; + request.data = &clock_properties; + request.size = sizeof(clock_properties); + + err = snd_mixart_send_msg(mgr, &request, sizeof(clock_prop_resp), &clock_prop_resp); + if (err < 0 || clock_prop_resp.status != 0 || clock_prop_resp.clock_mode != CM_STANDALONE) { + dev_err(&mgr->pci->dev, + "error MSG_CLOCK_SET_PROPERTIES err=%x stat=%x mod=%x !\n", + err, clock_prop_resp.status, clock_prop_resp.clock_mode); + return -EINVAL; + } + + if(rate) pipe->status = PIPE_CLOCK_SET; + else pipe->status = PIPE_RUNNING; + + return 0; +} + + +/* + * Allocate or reference output pipe for analog IOs (pcmp0/1) + */ +struct mixart_pipe * +snd_mixart_add_ref_pipe(struct snd_mixart *chip, int pcm_number, int capture, + int monitoring) +{ + int stream_count; + struct mixart_pipe *pipe; + struct mixart_msg request; + + if(capture) { + if (pcm_number == MIXART_PCM_ANALOG) { + pipe = &(chip->pipe_in_ana); /* analog inputs */ + } else { + pipe = &(chip->pipe_in_dig); /* digital inputs */ + } + request.message_id = MSG_STREAM_ADD_OUTPUT_GROUP; + stream_count = MIXART_CAPTURE_STREAMS; + } else { + if (pcm_number == MIXART_PCM_ANALOG) { + pipe = &(chip->pipe_out_ana); /* analog outputs */ + } else { + pipe = &(chip->pipe_out_dig); /* digital outputs */ + } + request.message_id = MSG_STREAM_ADD_INPUT_GROUP; + stream_count = MIXART_PLAYBACK_STREAMS; + } + + /* a new stream is opened and there are already all streams in use */ + if( (monitoring == 0) && (pipe->references >= stream_count) ) { + return NULL; + } + + /* pipe is not yet defined */ + if( pipe->status == PIPE_UNDEFINED ) { + int err, i; + struct { + struct mixart_streaming_group_req sgroup_req; + struct mixart_streaming_group sgroup_resp; + } *buf; + + dev_dbg(chip->card->dev, + "add_ref_pipe audio chip(%d) pcm(%d)\n", + chip->chip_idx, pcm_number); + + buf = kmalloc_obj(*buf); + if (!buf) + return NULL; + + request.uid = (struct mixart_uid){0,0}; /* should be StreamManagerUID, but zero is OK if there is only one ! */ + request.data = &buf->sgroup_req; + request.size = sizeof(buf->sgroup_req); + + memset(&buf->sgroup_req, 0, sizeof(buf->sgroup_req)); + + buf->sgroup_req.stream_count = stream_count; + buf->sgroup_req.channel_count = 2; + buf->sgroup_req.latency = 256; + buf->sgroup_req.connector = pipe->uid_left_connector; /* the left connector */ + + for (i=0; isgroup_req.stream_info[i].size_max_byte_frame = 1024; + buf->sgroup_req.stream_info[i].size_max_sample_frame = 256; + buf->sgroup_req.stream_info[i].nb_bytes_max_per_sample = MIXART_FLOAT_P__4_0_TO_HEX; /* is 4.0f */ + + /* find the right bufferinfo_array */ + j = (chip->chip_idx * MIXART_MAX_STREAM_PER_CARD) + (pcm_number * (MIXART_PLAYBACK_STREAMS + MIXART_CAPTURE_STREAMS)) + i; + if(capture) j += MIXART_PLAYBACK_STREAMS; /* in the array capture is behind playback */ + + buf->sgroup_req.flow_entry[i] = j; + + flowinfo = (struct mixart_flowinfo *)chip->mgr->flowinfo.area; + flowinfo[j].bufferinfo_array_phy_address = (u32)chip->mgr->bufferinfo.addr + (j * sizeof(struct mixart_bufferinfo)); + flowinfo[j].bufferinfo_count = 1; /* 1 will set the miXart to ring-buffer mode ! */ + + bufferinfo = (struct mixart_bufferinfo *)chip->mgr->bufferinfo.area; + bufferinfo[j].buffer_address = 0; /* buffer is not yet allocated */ + bufferinfo[j].available_length = 0; /* buffer is not yet allocated */ + + /* construct the identifier of the stream buffer received in the interrupts ! */ + bufferinfo[j].buffer_id = (chip->chip_idx << MIXART_NOTIFY_CARD_OFFSET) + (pcm_number << MIXART_NOTIFY_PCM_OFFSET ) + i; + if(capture) { + bufferinfo[j].buffer_id |= MIXART_NOTIFY_CAPT_MASK; + } + } + + err = snd_mixart_send_msg(chip->mgr, &request, sizeof(buf->sgroup_resp), &buf->sgroup_resp); + if((err < 0) || (buf->sgroup_resp.status != 0)) { + dev_err(chip->card->dev, + "error MSG_STREAM_ADD_**PUT_GROUP err=%x stat=%x !\n", + err, buf->sgroup_resp.status); + kfree(buf); + return NULL; + } + + pipe->group_uid = buf->sgroup_resp.group; /* id of the pipe, as returned by embedded */ + pipe->stream_count = buf->sgroup_resp.stream_count; + /* pipe->stream_uid[i] = buf->sgroup_resp.stream[i].stream_uid; */ + + pipe->status = PIPE_STOPPED; + kfree(buf); + } + + if(monitoring) pipe->monitoring = 1; + else pipe->references++; + + return pipe; +} + + +int snd_mixart_kill_ref_pipe(struct mixart_mgr *mgr, + struct mixart_pipe *pipe, int monitoring) +{ + int err = 0; + + if(pipe->status == PIPE_UNDEFINED) + return 0; + + if(monitoring) + pipe->monitoring = 0; + else + pipe->references--; + + if((pipe->references <= 0) && (pipe->monitoring == 0)) { + + struct mixart_msg request; + struct mixart_delete_group_resp delete_resp; + + /* release the clock */ + err = mixart_set_clock( mgr, pipe, 0); + if( err < 0 ) { + dev_err(&mgr->pci->dev, + "mixart_set_clock(0) return error!\n"); + } + + /* stop the pipe */ + err = mixart_set_pipe_state(mgr, pipe, 0); + if( err < 0 ) { + dev_err(&mgr->pci->dev, "error stopping pipe!\n"); + } + + request.message_id = MSG_STREAM_DELETE_GROUP; + request.uid = (struct mixart_uid){0,0}; + request.data = &pipe->group_uid; /* the streaming group ! */ + request.size = sizeof(pipe->group_uid); + + /* delete the pipe */ + err = snd_mixart_send_msg(mgr, &request, sizeof(delete_resp), &delete_resp); + if ((err < 0) || (delete_resp.status != 0)) { + dev_err(&mgr->pci->dev, + "error MSG_STREAM_DELETE_GROUP err(%x), status(%x)\n", + err, delete_resp.status); + } + + pipe->group_uid = (struct mixart_uid){0,0}; + pipe->stream_count = 0; + pipe->status = PIPE_UNDEFINED; + } + + return err; +} + +static int mixart_set_stream_state(struct mixart_stream *stream, int start) +{ + struct snd_mixart *chip; + struct mixart_stream_state_req stream_state_req; + struct mixart_msg request; + + if(!stream->substream) + return -EINVAL; + + memset(&stream_state_req, 0, sizeof(stream_state_req)); + stream_state_req.stream_count = 1; + stream_state_req.stream_info.stream_desc.uid_pipe = stream->pipe->group_uid; + stream_state_req.stream_info.stream_desc.stream_idx = stream->substream->number; + + if (stream->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + request.message_id = start ? MSG_STREAM_START_INPUT_STAGE_PACKET : MSG_STREAM_STOP_INPUT_STAGE_PACKET; + else + request.message_id = start ? MSG_STREAM_START_OUTPUT_STAGE_PACKET : MSG_STREAM_STOP_OUTPUT_STAGE_PACKET; + + request.uid = (struct mixart_uid){0,0}; + request.data = &stream_state_req; + request.size = sizeof(stream_state_req); + + stream->abs_period_elapsed = 0; /* reset stream pos */ + stream->buf_periods = 0; + stream->buf_period_frag = 0; + + chip = snd_pcm_substream_chip(stream->substream); + + return snd_mixart_send_msg_nonblock(chip->mgr, &request); +} + +/* + * Trigger callback + */ + +static int snd_mixart_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct mixart_stream *stream = subs->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + + dev_dbg(subs->pcm->card->dev, "SNDRV_PCM_TRIGGER_START\n"); + + /* START_STREAM */ + if( mixart_set_stream_state(stream, 1) ) + return -EINVAL; + + stream->status = MIXART_STREAM_STATUS_RUNNING; + + break; + case SNDRV_PCM_TRIGGER_STOP: + + /* STOP_STREAM */ + if( mixart_set_stream_state(stream, 0) ) + return -EINVAL; + + stream->status = MIXART_STREAM_STATUS_OPEN; + + dev_dbg(subs->pcm->card->dev, "SNDRV_PCM_TRIGGER_STOP\n"); + + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* TODO */ + stream->status = MIXART_STREAM_STATUS_PAUSE; + dev_dbg(subs->pcm->card->dev, "SNDRV_PCM_PAUSE_PUSH\n"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* TODO */ + stream->status = MIXART_STREAM_STATUS_RUNNING; + dev_dbg(subs->pcm->card->dev, "SNDRV_PCM_PAUSE_RELEASE\n"); + break; + default: + return -EINVAL; + } + return 0; +} + +static int mixart_sync_nonblock_events(struct mixart_mgr *mgr) +{ + unsigned long timeout = jiffies + HZ; + while (atomic_read(&mgr->msg_processed) > 0) { + if (time_after(jiffies, timeout)) { + dev_err(&mgr->pci->dev, + "mixart: cannot process nonblock events!\n"); + return -EBUSY; + } + schedule_timeout_uninterruptible(1); + } + return 0; +} + +/* + * prepare callback for all pcms + */ +static int snd_mixart_prepare(struct snd_pcm_substream *subs) +{ + struct snd_mixart *chip = snd_pcm_substream_chip(subs); + struct mixart_stream *stream = subs->runtime->private_data; + + /* TODO de façon non bloquante, réappliquer les hw_params (rate, bits, codec) */ + + dev_dbg(chip->card->dev, "snd_mixart_prepare\n"); + + mixart_sync_nonblock_events(chip->mgr); + + /* only the first stream can choose the sample rate */ + /* the further opened streams will be limited to its frequency (see open) */ + if(chip->mgr->ref_count_rate == 1) + chip->mgr->sample_rate = subs->runtime->rate; + + /* set the clock only once (first stream) on the same pipe */ + if(stream->pipe->references == 1) { + if( mixart_set_clock(chip->mgr, stream->pipe, subs->runtime->rate) ) + return -EINVAL; + } + + return 0; +} + + +static int mixart_set_format(struct mixart_stream *stream, snd_pcm_format_t format) +{ + int err; + struct snd_mixart *chip; + struct mixart_msg request; + struct mixart_stream_param_desc stream_param; + struct mixart_return_uid resp; + + chip = snd_pcm_substream_chip(stream->substream); + + memset(&stream_param, 0, sizeof(stream_param)); + + stream_param.coding_type = CT_LINEAR; + stream_param.number_of_channel = stream->channels; + + stream_param.sampling_freq = chip->mgr->sample_rate; + if(stream_param.sampling_freq == 0) + stream_param.sampling_freq = 44100; /* if frequency not yet defined, use some default */ + + switch(format){ + case SNDRV_PCM_FORMAT_U8: + stream_param.sample_type = ST_INTEGER_8; + stream_param.sample_size = 8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + stream_param.sample_type = ST_INTEGER_16LE; + stream_param.sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S16_BE: + stream_param.sample_type = ST_INTEGER_16BE; + stream_param.sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + stream_param.sample_type = ST_INTEGER_24LE; + stream_param.sample_size = 24; + break; + case SNDRV_PCM_FORMAT_S24_3BE: + stream_param.sample_type = ST_INTEGER_24BE; + stream_param.sample_size = 24; + break; + case SNDRV_PCM_FORMAT_FLOAT_LE: + stream_param.sample_type = ST_FLOATING_POINT_32LE; + stream_param.sample_size = 32; + break; + case SNDRV_PCM_FORMAT_FLOAT_BE: + stream_param.sample_type = ST_FLOATING_POINT_32BE; + stream_param.sample_size = 32; + break; + default: + dev_err(chip->card->dev, + "error mixart_set_format() : unknown format\n"); + return -EINVAL; + } + + dev_dbg(chip->card->dev, + "set SNDRV_PCM_FORMAT sample_type(%d) sample_size(%d) freq(%d) channels(%d)\n", + stream_param.sample_type, stream_param.sample_size, stream_param.sampling_freq, stream->channels); + + /* TODO: what else to configure ? */ + /* stream_param.samples_per_frame = 2; */ + /* stream_param.bytes_per_frame = 4; */ + /* stream_param.bytes_per_sample = 2; */ + + stream_param.pipe_count = 1; /* set to 1 */ + stream_param.stream_count = 1; /* set to 1 */ + stream_param.stream_desc.uid_pipe = stream->pipe->group_uid; + stream_param.stream_desc.stream_idx = stream->substream->number; + + request.message_id = MSG_STREAM_SET_INPUT_STAGE_PARAM; + request.uid = (struct mixart_uid){0,0}; + request.data = &stream_param; + request.size = sizeof(stream_param); + + err = snd_mixart_send_msg(chip->mgr, &request, sizeof(resp), &resp); + if((err < 0) || resp.error_code) { + dev_err(chip->card->dev, + "MSG_STREAM_SET_INPUT_STAGE_PARAM err=%x; resp=%x\n", + err, resp.error_code); + return -EINVAL; + } + return 0; +} + + +/* + * HW_PARAMS callback for all pcms + */ +static int snd_mixart_hw_params(struct snd_pcm_substream *subs, + struct snd_pcm_hw_params *hw) +{ + struct snd_mixart *chip = snd_pcm_substream_chip(subs); + struct mixart_mgr *mgr = chip->mgr; + struct mixart_stream *stream = subs->runtime->private_data; + snd_pcm_format_t format; + int err; + int channels; + + /* set up channels */ + channels = params_channels(hw); + + /* set up format for the stream */ + format = params_format(hw); + + guard(mutex)(&mgr->setup_mutex); + + /* update the stream levels */ + if( stream->pcm_number <= MIXART_PCM_DIGITAL ) { + int is_aes = stream->pcm_number > MIXART_PCM_ANALOG; + if( subs->stream == SNDRV_PCM_STREAM_PLAYBACK ) + mixart_update_playback_stream_level(chip, is_aes, subs->number); + else + mixart_update_capture_stream_level( chip, is_aes); + } + + stream->channels = channels; + + /* set the format to the board */ + err = mixart_set_format(stream, format); + if (err < 0) + return err; + + if (subs->runtime->buffer_changed) { + struct mixart_bufferinfo *bufferinfo; + int i = (chip->chip_idx * MIXART_MAX_STREAM_PER_CARD) + (stream->pcm_number * (MIXART_PLAYBACK_STREAMS+MIXART_CAPTURE_STREAMS)) + subs->number; + if( subs->stream == SNDRV_PCM_STREAM_CAPTURE ) { + i += MIXART_PLAYBACK_STREAMS; /* in array capture is behind playback */ + } + + bufferinfo = (struct mixart_bufferinfo *)chip->mgr->bufferinfo.area; + bufferinfo[i].buffer_address = subs->runtime->dma_addr; + bufferinfo[i].available_length = subs->runtime->dma_bytes; + /* bufferinfo[i].buffer_id is already defined */ + + dev_dbg(chip->card->dev, + "snd_mixart_hw_params(pcm %d) : dma_addr(%x) dma_bytes(%x) subs-number(%d)\n", + i, bufferinfo[i].buffer_address, + bufferinfo[i].available_length, + subs->number); + } + + return 0; +} + +static int snd_mixart_hw_free(struct snd_pcm_substream *subs) +{ + struct snd_mixart *chip = snd_pcm_substream_chip(subs); + mixart_sync_nonblock_events(chip->mgr); + return 0; +} + + + +/* + * TODO CONFIGURATION SPACE for all pcms, mono pcm must update channels_max + */ +static const struct snd_pcm_hardware snd_mixart_analog_caps = +{ + .info = ( SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .formats = ( SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE ), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (32*1024), + .period_bytes_min = 256, /* 256 frames U8 mono*/ + .period_bytes_max = (16*1024), + .periods_min = 2, + .periods_max = (32*1024/256), +}; + +static const struct snd_pcm_hardware snd_mixart_digital_caps = +{ + .info = ( SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .formats = ( SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE ), + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (32*1024), + .period_bytes_min = 256, /* 256 frames U8 mono*/ + .period_bytes_max = (16*1024), + .periods_min = 2, + .periods_max = (32*1024/256), +}; + + +static int snd_mixart_playback_open(struct snd_pcm_substream *subs) +{ + struct snd_mixart *chip = snd_pcm_substream_chip(subs); + struct mixart_mgr *mgr = chip->mgr; + struct snd_pcm_runtime *runtime = subs->runtime; + struct snd_pcm *pcm = subs->pcm; + struct mixart_stream *stream; + struct mixart_pipe *pipe; + int err = 0; + int pcm_number; + + guard(mutex)(&mgr->setup_mutex); + + if ( pcm == chip->pcm ) { + pcm_number = MIXART_PCM_ANALOG; + runtime->hw = snd_mixart_analog_caps; + } else { + snd_BUG_ON(pcm != chip->pcm_dig); + pcm_number = MIXART_PCM_DIGITAL; + runtime->hw = snd_mixart_digital_caps; + } + dev_dbg(chip->card->dev, + "snd_mixart_playback_open C%d/P%d/Sub%d\n", + chip->chip_idx, pcm_number, subs->number); + + /* get stream info */ + stream = &(chip->playback_stream[pcm_number][subs->number]); + + if (stream->status != MIXART_STREAM_STATUS_FREE){ + /* streams in use */ + dev_err(chip->card->dev, + "snd_mixart_playback_open C%d/P%d/Sub%d in use\n", + chip->chip_idx, pcm_number, subs->number); + return -EBUSY; + } + + /* get pipe pointer (out pipe) */ + pipe = snd_mixart_add_ref_pipe(chip, pcm_number, 0, 0); + + if (pipe == NULL) + return -EINVAL; + + /* start the pipe if necessary */ + err = mixart_set_pipe_state(chip->mgr, pipe, 1); + if( err < 0 ) { + dev_err(chip->card->dev, "error starting pipe!\n"); + snd_mixart_kill_ref_pipe(chip->mgr, pipe, 0); + return -EINVAL; + } + + stream->pipe = pipe; + stream->pcm_number = pcm_number; + stream->status = MIXART_STREAM_STATUS_OPEN; + stream->substream = subs; + stream->channels = 0; /* not configured yet */ + + runtime->private_data = stream; + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 64); + + /* if a sample rate is already used, another stream cannot change */ + if(mgr->ref_count_rate++) { + if(mgr->sample_rate) { + runtime->hw.rate_min = runtime->hw.rate_max = mgr->sample_rate; + } + } + + return 0; +} + + +static int snd_mixart_capture_open(struct snd_pcm_substream *subs) +{ + struct snd_mixart *chip = snd_pcm_substream_chip(subs); + struct mixart_mgr *mgr = chip->mgr; + struct snd_pcm_runtime *runtime = subs->runtime; + struct snd_pcm *pcm = subs->pcm; + struct mixart_stream *stream; + struct mixart_pipe *pipe; + int err = 0; + int pcm_number; + + guard(mutex)(&mgr->setup_mutex); + + if ( pcm == chip->pcm ) { + pcm_number = MIXART_PCM_ANALOG; + runtime->hw = snd_mixart_analog_caps; + } else { + snd_BUG_ON(pcm != chip->pcm_dig); + pcm_number = MIXART_PCM_DIGITAL; + runtime->hw = snd_mixart_digital_caps; + } + + runtime->hw.channels_min = 2; /* for instance, no mono */ + + dev_dbg(chip->card->dev, "snd_mixart_capture_open C%d/P%d/Sub%d\n", + chip->chip_idx, pcm_number, subs->number); + + /* get stream info */ + stream = &(chip->capture_stream[pcm_number]); + + if (stream->status != MIXART_STREAM_STATUS_FREE){ + /* streams in use */ + dev_err(chip->card->dev, + "snd_mixart_capture_open C%d/P%d/Sub%d in use\n", + chip->chip_idx, pcm_number, subs->number); + return -EBUSY; + } + + /* get pipe pointer (in pipe) */ + pipe = snd_mixart_add_ref_pipe(chip, pcm_number, 1, 0); + + if (pipe == NULL) + return -EINVAL; + + /* start the pipe if necessary */ + err = mixart_set_pipe_state(chip->mgr, pipe, 1); + if( err < 0 ) { + dev_err(chip->card->dev, "error starting pipe!\n"); + snd_mixart_kill_ref_pipe(chip->mgr, pipe, 0); + return -EINVAL; + } + + stream->pipe = pipe; + stream->pcm_number = pcm_number; + stream->status = MIXART_STREAM_STATUS_OPEN; + stream->substream = subs; + stream->channels = 0; /* not configured yet */ + + runtime->private_data = stream; + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 64); + + /* if a sample rate is already used, another stream cannot change */ + if(mgr->ref_count_rate++) { + if(mgr->sample_rate) { + runtime->hw.rate_min = runtime->hw.rate_max = mgr->sample_rate; + } + } + + return 0; +} + + + +static int snd_mixart_close(struct snd_pcm_substream *subs) +{ + struct snd_mixart *chip = snd_pcm_substream_chip(subs); + struct mixart_mgr *mgr = chip->mgr; + struct mixart_stream *stream = subs->runtime->private_data; + + guard(mutex)(&mgr->setup_mutex); + + dev_dbg(chip->card->dev, "snd_mixart_close C%d/P%d/Sub%d\n", + chip->chip_idx, stream->pcm_number, subs->number); + + /* sample rate released */ + if(--mgr->ref_count_rate == 0) { + mgr->sample_rate = 0; + } + + /* delete pipe */ + if (snd_mixart_kill_ref_pipe(mgr, stream->pipe, 0 ) < 0) { + + dev_err(chip->card->dev, + "error snd_mixart_kill_ref_pipe C%dP%d\n", + chip->chip_idx, stream->pcm_number); + } + + stream->pipe = NULL; + stream->status = MIXART_STREAM_STATUS_FREE; + stream->substream = NULL; + + return 0; +} + + +static snd_pcm_uframes_t snd_mixart_stream_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct mixart_stream *stream = runtime->private_data; + + return (snd_pcm_uframes_t)((stream->buf_periods * runtime->period_size) + stream->buf_period_frag); +} + + + +static const struct snd_pcm_ops snd_mixart_playback_ops = { + .open = snd_mixart_playback_open, + .close = snd_mixart_close, + .prepare = snd_mixart_prepare, + .hw_params = snd_mixart_hw_params, + .hw_free = snd_mixart_hw_free, + .trigger = snd_mixart_trigger, + .pointer = snd_mixart_stream_pointer, +}; + +static const struct snd_pcm_ops snd_mixart_capture_ops = { + .open = snd_mixart_capture_open, + .close = snd_mixart_close, + .prepare = snd_mixart_prepare, + .hw_params = snd_mixart_hw_params, + .hw_free = snd_mixart_hw_free, + .trigger = snd_mixart_trigger, + .pointer = snd_mixart_stream_pointer, +}; + +static void preallocate_buffers(struct snd_mixart *chip, struct snd_pcm *pcm) +{ +#if 0 + struct snd_pcm_substream *subs; + int stream; + + for (stream = 0; stream < 2; stream++) { + int idx = 0; + for (subs = pcm->streams[stream].substream; subs; subs = subs->next, idx++) + /* set up the unique device id with the chip index */ + subs->dma_device.id = subs->pcm->device << 16 | + subs->stream << 8 | (subs->number + 1) | + (chip->chip_idx + 1) << 24; + } +#endif + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->mgr->pci->dev, + 32*1024, 32*1024); +} + +/* + */ +static int snd_mixart_pcm_analog(struct snd_mixart *chip) +{ + int err; + struct snd_pcm *pcm; + char name[32]; + + sprintf(name, "miXart analog %d", chip->chip_idx); + err = snd_pcm_new(chip->card, name, MIXART_PCM_ANALOG, + MIXART_PLAYBACK_STREAMS, + MIXART_CAPTURE_STREAMS, &pcm); + if (err < 0) { + dev_err(chip->card->dev, + "cannot create the analog pcm %d\n", chip->chip_idx); + return err; + } + + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_mixart_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_mixart_capture_ops); + + pcm->info_flags = 0; + pcm->nonatomic = true; + strscpy(pcm->name, name); + + preallocate_buffers(chip, pcm); + + chip->pcm = pcm; + return 0; +} + + +/* + */ +static int snd_mixart_pcm_digital(struct snd_mixart *chip) +{ + int err; + struct snd_pcm *pcm; + char name[32]; + + sprintf(name, "miXart AES/EBU %d", chip->chip_idx); + err = snd_pcm_new(chip->card, name, MIXART_PCM_DIGITAL, + MIXART_PLAYBACK_STREAMS, + MIXART_CAPTURE_STREAMS, &pcm); + if (err < 0) { + dev_err(chip->card->dev, + "cannot create the digital pcm %d\n", chip->chip_idx); + return err; + } + + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_mixart_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_mixart_capture_ops); + + pcm->info_flags = 0; + pcm->nonatomic = true; + strscpy(pcm->name, name); + + preallocate_buffers(chip, pcm); + + chip->pcm_dig = pcm; + return 0; +} + +static int snd_mixart_chip_free(struct snd_mixart *chip) +{ + kfree(chip); + return 0; +} + +static int snd_mixart_chip_dev_free(struct snd_device *device) +{ + struct snd_mixart *chip = device->device_data; + return snd_mixart_chip_free(chip); +} + + +/* + */ +static int snd_mixart_create(struct mixart_mgr *mgr, struct snd_card *card, int idx) +{ + int err; + struct snd_mixart *chip; + static const struct snd_device_ops ops = { + .dev_free = snd_mixart_chip_dev_free, + }; + + chip = kzalloc_obj(*chip); + if (!chip) + return -ENOMEM; + + chip->card = card; + chip->chip_idx = idx; + chip->mgr = mgr; + card->sync_irq = mgr->irq; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + snd_mixart_chip_free(chip); + return err; + } + + mgr->chip[idx] = chip; + return 0; +} + +int snd_mixart_create_pcm(struct snd_mixart* chip) +{ + int err; + + err = snd_mixart_pcm_analog(chip); + if (err < 0) + return err; + + if(chip->mgr->board_type == MIXART_DAUGHTER_TYPE_AES) { + + err = snd_mixart_pcm_digital(chip); + if (err < 0) + return err; + } + return err; +} + + +/* + * release all the cards assigned to a manager instance + */ +static int snd_mixart_free(struct mixart_mgr *mgr) +{ + unsigned int i; + + for (i = 0; i < mgr->num_cards; i++) { + if (mgr->chip[i]) + snd_card_free(mgr->chip[i]->card); + } + + /* stop mailbox */ + snd_mixart_exit_mailbox(mgr); + + /* release irq */ + if (mgr->irq >= 0) + free_irq(mgr->irq, mgr); + + /* reset board if some firmware was loaded */ + if(mgr->dsp_loaded) { + snd_mixart_reset_board(mgr); + dev_dbg(&mgr->pci->dev, "reset miXart !\n"); + } + + /* release the i/o ports */ + for (i = 0; i < 2; ++i) + iounmap(mgr->mem[i].virt); + + pci_release_regions(mgr->pci); + + /* free flowarray */ + if(mgr->flowinfo.area) { + snd_dma_free_pages(&mgr->flowinfo); + mgr->flowinfo.area = NULL; + } + /* free bufferarray */ + if(mgr->bufferinfo.area) { + snd_dma_free_pages(&mgr->bufferinfo); + mgr->bufferinfo.area = NULL; + } + + pci_disable_device(mgr->pci); + kfree(mgr); + return 0; +} + +/* + * proc interface + */ + +/* + mixart_BA0 proc interface for BAR 0 - read callback + */ +static ssize_t snd_mixart_BA0_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) +{ + struct mixart_mgr *mgr = entry->private_data; + + count = count & ~3; /* make sure the read size is a multiple of 4 bytes */ + if (copy_to_user_fromio(buf, MIXART_MEM(mgr, pos), count)) + return -EFAULT; + return count; +} + +/* + mixart_BA1 proc interface for BAR 1 - read callback + */ +static ssize_t snd_mixart_BA1_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) +{ + struct mixart_mgr *mgr = entry->private_data; + + count = count & ~3; /* make sure the read size is a multiple of 4 bytes */ + if (copy_to_user_fromio(buf, MIXART_REG(mgr, pos), count)) + return -EFAULT; + return count; +} + +static const struct snd_info_entry_ops snd_mixart_proc_ops_BA0 = { + .read = snd_mixart_BA0_read, +}; + +static const struct snd_info_entry_ops snd_mixart_proc_ops_BA1 = { + .read = snd_mixart_BA1_read, +}; + + +static void snd_mixart_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_mixart *chip = entry->private_data; + u32 ref; + + snd_iprintf(buffer, "Digigram miXart (alsa card %d)\n\n", chip->chip_idx); + + /* stats available when embedded OS is running */ + if (chip->mgr->dsp_loaded & ( 1 << MIXART_MOTHERBOARD_ELF_INDEX)) { + snd_iprintf(buffer, "- hardware -\n"); + switch (chip->mgr->board_type ) { + case MIXART_DAUGHTER_TYPE_NONE : snd_iprintf(buffer, "\tmiXart8 (no daughter board)\n\n"); break; + case MIXART_DAUGHTER_TYPE_AES : snd_iprintf(buffer, "\tmiXart8 AES/EBU\n\n"); break; + case MIXART_DAUGHTER_TYPE_COBRANET : snd_iprintf(buffer, "\tmiXart8 Cobranet\n\n"); break; + default: snd_iprintf(buffer, "\tUNKNOWN!\n\n"); break; + } + + snd_iprintf(buffer, "- system load -\n"); + + /* get perf reference */ + + ref = readl_be( MIXART_MEM( chip->mgr, MIXART_PSEUDOREG_PERF_SYSTEM_LOAD_OFFSET)); + + if (ref) { + u32 mailbox = 100 * readl_be( MIXART_MEM( chip->mgr, MIXART_PSEUDOREG_PERF_MAILBX_LOAD_OFFSET)) / ref; + u32 streaming = 100 * readl_be( MIXART_MEM( chip->mgr, MIXART_PSEUDOREG_PERF_STREAM_LOAD_OFFSET)) / ref; + u32 interr = 100 * readl_be( MIXART_MEM( chip->mgr, MIXART_PSEUDOREG_PERF_INTERR_LOAD_OFFSET)) / ref; + + snd_iprintf(buffer, "\tstreaming : %d\n", streaming); + snd_iprintf(buffer, "\tmailbox : %d\n", mailbox); + snd_iprintf(buffer, "\tinterrupts handling : %d\n\n", interr); + } + } /* endif elf loaded */ +} + +static void snd_mixart_proc_init(struct snd_mixart *chip) +{ + struct snd_info_entry *entry; + + /* text interface to read perf and temp meters */ + snd_card_ro_proc_new(chip->card, "board_info", chip, + snd_mixart_proc_read); + + if (! snd_card_proc_new(chip->card, "mixart_BA0", &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = chip->mgr; + entry->c.ops = &snd_mixart_proc_ops_BA0; + entry->size = MIXART_BA0_SIZE; + } + if (! snd_card_proc_new(chip->card, "mixart_BA1", &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = chip->mgr; + entry->c.ops = &snd_mixart_proc_ops_BA1; + entry->size = MIXART_BA1_SIZE; + } +} +/* end of proc interface */ + + +/* + * probe function - creates the card manager + */ +static int snd_mixart_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct mixart_mgr *mgr; + unsigned int i; + int err; + size_t size; + + /* + */ + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (! enable[dev]) { + dev++; + return -ENOENT; + } + + /* enable PCI device */ + err = pci_enable_device(pci); + if (err < 0) + return err; + pci_set_master(pci); + + /* check if we can restrict PCI DMA transfers to 32 bits */ + if (dma_set_mask(&pci->dev, DMA_BIT_MASK(32)) < 0) { + dev_err(&pci->dev, + "architecture does not support 32bit PCI busmaster DMA\n"); + pci_disable_device(pci); + return -ENXIO; + } + + /* + */ + mgr = kzalloc_obj(*mgr); + if (! mgr) { + pci_disable_device(pci); + return -ENOMEM; + } + + mgr->pci = pci; + mgr->irq = -1; + + /* resource assignment */ + err = pci_request_regions(pci, CARD_NAME); + if (err < 0) { + kfree(mgr); + pci_disable_device(pci); + return err; + } + for (i = 0; i < 2; i++) { + mgr->mem[i].phys = pci_resource_start(pci, i); + mgr->mem[i].virt = pci_ioremap_bar(pci, i); + if (!mgr->mem[i].virt) { + dev_err(&pci->dev, "unable to remap resource 0x%lx\n", + mgr->mem[i].phys); + snd_mixart_free(mgr); + return -EBUSY; + } + } + + if (request_threaded_irq(pci->irq, snd_mixart_interrupt, + snd_mixart_threaded_irq, IRQF_SHARED, + KBUILD_MODNAME, mgr)) { + dev_err(&pci->dev, "unable to grab IRQ %d\n", pci->irq); + snd_mixart_free(mgr); + return -EBUSY; + } + mgr->irq = pci->irq; + + /* init mailbox */ + mgr->msg_fifo_readptr = 0; + mgr->msg_fifo_writeptr = 0; + + mutex_init(&mgr->lock); + mutex_init(&mgr->msg_lock); + init_waitqueue_head(&mgr->msg_sleep); + atomic_set(&mgr->msg_processed, 0); + + /* init setup mutex*/ + mutex_init(&mgr->setup_mutex); + + /* card assignment */ + mgr->num_cards = MIXART_MAX_CARDS; /* 4 FIXME: configurable? */ + for (i = 0; i < mgr->num_cards; i++) { + struct snd_card *card; + char tmpid[16]; + int idx; + + if (index[dev] < 0) + idx = index[dev]; + else + idx = index[dev] + i; + snprintf(tmpid, sizeof(tmpid), "%s-%d", id[dev] ? id[dev] : "MIXART", i); + err = snd_card_new(&pci->dev, idx, tmpid, THIS_MODULE, + 0, &card); + + if (err < 0) { + dev_err(&pci->dev, "cannot allocate the card %d\n", i); + snd_mixart_free(mgr); + return err; + } + + strscpy(card->driver, CARD_NAME); + snprintf(card->shortname, sizeof(card->shortname), + "Digigram miXart [PCM #%d]", i); + snprintf(card->longname, sizeof(card->longname), + "Digigram miXart at 0x%lx & 0x%lx, irq %i [PCM #%d]", + mgr->mem[0].phys, mgr->mem[1].phys, mgr->irq, i); + + err = snd_mixart_create(mgr, card, i); + if (err < 0) { + snd_card_free(card); + snd_mixart_free(mgr); + return err; + } + + if(i==0) { + /* init proc interface only for chip0 */ + snd_mixart_proc_init(mgr->chip[i]); + } + + err = snd_card_register(card); + if (err < 0) { + snd_mixart_free(mgr); + return err; + } + } + + /* init firmware status (mgr->dsp_loaded reset in hwdep_new) */ + mgr->board_type = MIXART_DAUGHTER_TYPE_NONE; + + /* create array of streaminfo */ + size = PAGE_ALIGN( (MIXART_MAX_STREAM_PER_CARD * MIXART_MAX_CARDS * + sizeof(struct mixart_flowinfo)) ); + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + size, &mgr->flowinfo) < 0) { + snd_mixart_free(mgr); + return -ENOMEM; + } + /* init streaminfo_array */ + memset(mgr->flowinfo.area, 0, size); + + /* create array of bufferinfo */ + size = PAGE_ALIGN( (MIXART_MAX_STREAM_PER_CARD * MIXART_MAX_CARDS * + sizeof(struct mixart_bufferinfo)) ); + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + size, &mgr->bufferinfo) < 0) { + snd_mixart_free(mgr); + return -ENOMEM; + } + /* init bufferinfo_array */ + memset(mgr->bufferinfo.area, 0, size); + + /* set up firmware */ + err = snd_mixart_setup_firmware(mgr); + if (err < 0) { + snd_mixart_free(mgr); + return err; + } + + pci_set_drvdata(pci, mgr); + dev++; + return 0; +} + +static void snd_mixart_remove(struct pci_dev *pci) +{ + snd_mixart_free(pci_get_drvdata(pci)); +} + +static struct pci_driver mixart_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_mixart_ids, + .probe = snd_mixart_probe, + .remove = snd_mixart_remove, +}; + +module_pci_driver(mixart_driver); diff --git a/sound/pci/mixart/mixart.h b/sound/pci/mixart/mixart.h new file mode 100644 index 000000000..3fcc5ca75 --- /dev/null +++ b/sound/pci/mixart/mixart.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram miXart soundcards + * + * main header file + * + * Copyright (c) 2003 by Digigram + */ + +#ifndef __SOUND_MIXART_H +#define __SOUND_MIXART_H + +#include +#include +#include + +#define MIXART_DRIVER_VERSION 0x000100 /* 0.1.0 */ + + +/* + */ + +struct mixart_uid { + u32 object_id; + u32 desc; +}; + +struct mem_area { + unsigned long phys; + void __iomem *virt; + struct resource *res; +}; + + +struct mixart_route { + unsigned char connected; + unsigned char phase_inv; + int volume; +}; + + +/* firmware status codes */ +#define MIXART_MOTHERBOARD_XLX_INDEX 0 +#define MIXART_MOTHERBOARD_ELF_INDEX 1 +#define MIXART_AESEBUBOARD_XLX_INDEX 2 +#define MIXART_HARDW_FILES_MAX_INDEX 3 /* xilinx, elf, AESEBU xilinx */ + +#define MIXART_MAX_CARDS 4 +#define MSG_FIFO_SIZE 16 + +#define MIXART_MAX_PHYS_CONNECTORS (MIXART_MAX_CARDS * 2 * 2) /* 4 * stereo * (analog+digital) */ + +struct mixart_mgr { + unsigned int num_cards; + struct snd_mixart *chip[MIXART_MAX_CARDS]; + + struct pci_dev *pci; + + int irq; + + /* memory-maps */ + struct mem_area mem[2]; + + /* one and only blocking message or notification may be pending */ + u32 pending_event; + wait_queue_head_t msg_sleep; + + /* messages fifo */ + u32 msg_fifo[MSG_FIFO_SIZE]; + int msg_fifo_readptr; + int msg_fifo_writeptr; + atomic_t msg_processed; /* number of messages to be processed in irq thread */ + + struct mutex lock; /* interrupt lock */ + struct mutex msg_lock; /* mailbox lock */ + + struct mutex setup_mutex; /* mutex used in hw_params, open and close */ + + /* hardware interface */ + unsigned int dsp_loaded; /* bit flags of loaded dsp indices */ + unsigned int board_type; /* read from embedded once elf file is loaded, 250 = miXart8, 251 = with AES, 252 = with Cobranet */ + + struct snd_dma_buffer flowinfo; + struct snd_dma_buffer bufferinfo; + + struct mixart_uid uid_console_manager; + int sample_rate; + int ref_count_rate; + + struct mutex mixer_mutex; /* mutex for mixer */ + +}; + + +#define MIXART_STREAM_STATUS_FREE 0 +#define MIXART_STREAM_STATUS_OPEN 1 +#define MIXART_STREAM_STATUS_RUNNING 2 +#define MIXART_STREAM_STATUS_DRAINING 3 +#define MIXART_STREAM_STATUS_PAUSE 4 + +#define MIXART_PLAYBACK_STREAMS 4 +#define MIXART_CAPTURE_STREAMS 1 + +#define MIXART_PCM_ANALOG 0 +#define MIXART_PCM_DIGITAL 1 +#define MIXART_PCM_TOTAL 2 + +#define MIXART_MAX_STREAM_PER_CARD (MIXART_PCM_TOTAL * (MIXART_PLAYBACK_STREAMS + MIXART_CAPTURE_STREAMS) ) + + +#define MIXART_NOTIFY_CARD_MASK 0xF000 +#define MIXART_NOTIFY_CARD_OFFSET 12 +#define MIXART_NOTIFY_PCM_MASK 0x0F00 +#define MIXART_NOTIFY_PCM_OFFSET 8 +#define MIXART_NOTIFY_CAPT_MASK 0x0080 +#define MIXART_NOTIFY_SUBS_MASK 0x007F + + +struct mixart_stream { + struct snd_pcm_substream *substream; + struct mixart_pipe *pipe; + int pcm_number; + + int status; /* nothing, running, draining */ + + u64 abs_period_elapsed; /* last absolute stream position where period_elapsed was called (multiple of runtime->period_size) */ + u32 buf_periods; /* periods counter in the buffer (< runtime->periods) */ + u32 buf_period_frag; /* defines with buf_periods the exact position in the buffer (< runtime->period_size) */ + + int channels; +}; + + +enum mixart_pipe_status { + PIPE_UNDEFINED, + PIPE_STOPPED, + PIPE_RUNNING, + PIPE_CLOCK_SET +}; + +struct mixart_pipe { + struct mixart_uid group_uid; /* id of the pipe, as returned by embedded */ + int stream_count; + struct mixart_uid uid_left_connector; /* UID's for the audio connectors */ + struct mixart_uid uid_right_connector; + enum mixart_pipe_status status; + int references; /* number of subs openned */ + int monitoring; /* pipe used for monitoring issue */ +}; + + +struct snd_mixart { + struct snd_card *card; + struct mixart_mgr *mgr; + int chip_idx; /* zero based */ + struct snd_hwdep *hwdep; /* DSP loader, only for the first card */ + + struct snd_pcm *pcm; /* PCM analog i/o */ + struct snd_pcm *pcm_dig; /* PCM digital i/o */ + + /* allocate stereo pipe for instance */ + struct mixart_pipe pipe_in_ana; + struct mixart_pipe pipe_out_ana; + + /* if AES/EBU daughter board is available, additional pipes possible on pcm_dig */ + struct mixart_pipe pipe_in_dig; + struct mixart_pipe pipe_out_dig; + + struct mixart_stream playback_stream[MIXART_PCM_TOTAL][MIXART_PLAYBACK_STREAMS]; /* 0 = pcm, 1 = pcm_dig */ + struct mixart_stream capture_stream[MIXART_PCM_TOTAL]; /* 0 = pcm, 1 = pcm_dig */ + + /* UID's for the physical io's */ + struct mixart_uid uid_out_analog_physio; + struct mixart_uid uid_in_analog_physio; + + int analog_playback_active[2]; /* Mixer : Master Playback active (!mute) */ + int analog_playback_volume[2]; /* Mixer : Master Playback Volume */ + int analog_capture_volume[2]; /* Mixer : Master Capture Volume */ + int digital_playback_active[2*MIXART_PLAYBACK_STREAMS][2]; /* Mixer : Digital Playback Active [(analog+AES output)*streams][stereo]*/ + int digital_playback_volume[2*MIXART_PLAYBACK_STREAMS][2]; /* Mixer : Digital Playback Volume [(analog+AES output)*streams][stereo]*/ + int digital_capture_volume[2][2]; /* Mixer : Digital Capture Volume [analog+AES output][stereo] */ + int monitoring_active[2]; /* Mixer : Monitoring Active */ + int monitoring_volume[2]; /* Mixer : Monitoring Volume */ +}; + +struct mixart_bufferinfo +{ + u32 buffer_address; + u32 reserved[5]; + u32 available_length; + u32 buffer_id; +}; + +struct mixart_flowinfo +{ + u32 bufferinfo_array_phy_address; + u32 reserved[11]; + u32 bufferinfo_count; + u32 capture; +}; + +/* exported */ +int snd_mixart_create_pcm(struct snd_mixart * chip); +struct mixart_pipe *snd_mixart_add_ref_pipe(struct snd_mixart *chip, int pcm_number, int capture, int monitoring); +int snd_mixart_kill_ref_pipe(struct mixart_mgr *mgr, struct mixart_pipe *pipe, int monitoring); + +#endif /* __SOUND_MIXART_H */ diff --git a/sound/pci/mixart/mixart_core.c b/sound/pci/mixart/mixart_core.c new file mode 100644 index 000000000..f7396ee4f --- /dev/null +++ b/sound/pci/mixart/mixart_core.c @@ -0,0 +1,584 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram miXart soundcards + * + * low level interface with interrupt handling and mail box implementation + * + * Copyright (c) 2003 by Digigram + */ + +#include +#include +#include +#include + +#include +#include "mixart.h" +#include "mixart_hwdep.h" +#include "mixart_core.h" + + +#define MSG_TIMEOUT_JIFFIES (400 * HZ) / 1000 /* 400 ms */ + +#define MSG_DESCRIPTOR_SIZE 0x24 +#define MSG_HEADER_SIZE (MSG_DESCRIPTOR_SIZE + 4) + +#define MSG_TYPE_MASK 0x00000003 /* mask for following types */ +#define MSG_TYPE_NOTIFY 0 /* embedded -> driver (only notification, do not get_msg() !) */ +#define MSG_TYPE_COMMAND 1 /* driver <-> embedded (a command has no answer) */ +#define MSG_TYPE_REQUEST 2 /* driver -> embedded (request will get an answer back) */ +#define MSG_TYPE_ANSWER 3 /* embedded -> driver */ +#define MSG_CANCEL_NOTIFY_MASK 0x80000000 /* this bit is set for a notification that has been canceled */ + + +static int retrieve_msg_frame(struct mixart_mgr *mgr, u32 *msg_frame) +{ + /* read the message frame fifo */ + u32 headptr, tailptr; + + tailptr = readl_be(MIXART_MEM(mgr, MSG_OUTBOUND_POST_TAIL)); + headptr = readl_be(MIXART_MEM(mgr, MSG_OUTBOUND_POST_HEAD)); + + if (tailptr == headptr) + return 0; /* no message posted */ + + if (tailptr < MSG_OUTBOUND_POST_STACK) + return 0; /* error */ + if (tailptr >= MSG_OUTBOUND_POST_STACK + MSG_BOUND_STACK_SIZE) + return 0; /* error */ + + *msg_frame = readl_be(MIXART_MEM(mgr, tailptr)); + + /* increment the tail index */ + tailptr += 4; + if( tailptr >= (MSG_OUTBOUND_POST_STACK+MSG_BOUND_STACK_SIZE) ) + tailptr = MSG_OUTBOUND_POST_STACK; + writel_be(tailptr, MIXART_MEM(mgr, MSG_OUTBOUND_POST_TAIL)); + + return 1; +} + +static int get_msg(struct mixart_mgr *mgr, struct mixart_msg *resp, + u32 msg_frame_address ) +{ + u32 headptr; + u32 size; + int err; +#ifndef __BIG_ENDIAN + unsigned int i; +#endif + + err = 0; + + /* copy message descriptor from miXart to driver */ + size = readl_be(MIXART_MEM(mgr, msg_frame_address)); /* size of descriptor + response */ + resp->message_id = readl_be(MIXART_MEM(mgr, msg_frame_address + 4)); /* dwMessageID */ + resp->uid.object_id = readl_be(MIXART_MEM(mgr, msg_frame_address + 8)); /* uidDest */ + resp->uid.desc = readl_be(MIXART_MEM(mgr, msg_frame_address + 12)); /* */ + + if( (size < MSG_DESCRIPTOR_SIZE) || (resp->size < (size - MSG_DESCRIPTOR_SIZE))) { + err = -EINVAL; + dev_err(&mgr->pci->dev, + "problem with response size = %d\n", size); + goto _clean_exit; + } + size -= MSG_DESCRIPTOR_SIZE; + + memcpy_fromio(resp->data, MIXART_MEM(mgr, msg_frame_address + MSG_HEADER_SIZE ), size); + resp->size = size; + + /* swap if necessary */ +#ifndef __BIG_ENDIAN + size /= 4; /* u32 size */ + for(i=0; i < size; i++) { + ((u32*)resp->data)[i] = be32_to_cpu(((__be32*)resp->data)[i]); + } +#endif + + /* + * free message frame address + */ + headptr = readl_be(MIXART_MEM(mgr, MSG_OUTBOUND_FREE_HEAD)); + + if( (headptr < MSG_OUTBOUND_FREE_STACK) || ( headptr >= (MSG_OUTBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE))) { + err = -EINVAL; + goto _clean_exit; + } + + /* give address back to outbound fifo */ + writel_be(msg_frame_address, MIXART_MEM(mgr, headptr)); + + /* increment the outbound free head */ + headptr += 4; + if( headptr >= (MSG_OUTBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE) ) + headptr = MSG_OUTBOUND_FREE_STACK; + + writel_be(headptr, MIXART_MEM(mgr, MSG_OUTBOUND_FREE_HEAD)); + + _clean_exit: + return err; +} + + +/* + * send a message to miXart. return: the msg_frame used for this message + */ +/* call with mgr->msg_lock held! */ +static int send_msg( struct mixart_mgr *mgr, + struct mixart_msg *msg, + int max_answersize, + int mark_pending, + u32 *msg_event) +{ + u32 headptr, tailptr; + u32 msg_frame_address; + int i; + + if (snd_BUG_ON(msg->size % 4)) + return -EINVAL; + + /* get message frame address */ + tailptr = readl_be(MIXART_MEM(mgr, MSG_INBOUND_FREE_TAIL)); + headptr = readl_be(MIXART_MEM(mgr, MSG_INBOUND_FREE_HEAD)); + + if (tailptr == headptr) { + dev_err(&mgr->pci->dev, "error: no message frame available\n"); + return -EBUSY; + } + + if( (tailptr < MSG_INBOUND_FREE_STACK) || (tailptr >= (MSG_INBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE))) { + return -EINVAL; + } + + msg_frame_address = readl_be(MIXART_MEM(mgr, tailptr)); + writel(0, MIXART_MEM(mgr, tailptr)); /* set address to zero on this fifo position */ + + /* increment the inbound free tail */ + tailptr += 4; + if( tailptr >= (MSG_INBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE) ) + tailptr = MSG_INBOUND_FREE_STACK; + + writel_be(tailptr, MIXART_MEM(mgr, MSG_INBOUND_FREE_TAIL)); + + /* TODO : use memcpy_toio() with intermediate buffer to copy the message */ + + /* copy message descriptor to card memory */ + writel_be( msg->size + MSG_DESCRIPTOR_SIZE, MIXART_MEM(mgr, msg_frame_address) ); /* size of descriptor + request */ + writel_be( msg->message_id , MIXART_MEM(mgr, msg_frame_address + 4) ); /* dwMessageID */ + writel_be( msg->uid.object_id, MIXART_MEM(mgr, msg_frame_address + 8) ); /* uidDest */ + writel_be( msg->uid.desc, MIXART_MEM(mgr, msg_frame_address + 12) ); /* */ + writel_be( MSG_DESCRIPTOR_SIZE, MIXART_MEM(mgr, msg_frame_address + 16) ); /* SizeHeader */ + writel_be( MSG_DESCRIPTOR_SIZE, MIXART_MEM(mgr, msg_frame_address + 20) ); /* OffsetDLL_T16 */ + writel_be( msg->size, MIXART_MEM(mgr, msg_frame_address + 24) ); /* SizeDLL_T16 */ + writel_be( MSG_DESCRIPTOR_SIZE, MIXART_MEM(mgr, msg_frame_address + 28) ); /* OffsetDLL_DRV */ + writel_be( 0, MIXART_MEM(mgr, msg_frame_address + 32) ); /* SizeDLL_DRV */ + writel_be( MSG_DESCRIPTOR_SIZE + max_answersize, MIXART_MEM(mgr, msg_frame_address + 36) ); /* dwExpectedAnswerSize */ + + /* copy message data to card memory */ + for( i=0; i < msg->size; i+=4 ) { + writel_be( *(u32*)(msg->data + i), MIXART_MEM(mgr, MSG_HEADER_SIZE + msg_frame_address + i) ); + } + + if( mark_pending ) { + if( *msg_event ) { + /* the pending event is the notification we wait for ! */ + mgr->pending_event = *msg_event; + } + else { + /* the pending event is the answer we wait for (same address than the request)! */ + mgr->pending_event = msg_frame_address; + + /* copy address back to caller */ + *msg_event = msg_frame_address; + } + } + + /* mark the frame as a request (will have an answer) */ + msg_frame_address |= MSG_TYPE_REQUEST; + + /* post the frame */ + headptr = readl_be(MIXART_MEM(mgr, MSG_INBOUND_POST_HEAD)); + + if( (headptr < MSG_INBOUND_POST_STACK) || (headptr >= (MSG_INBOUND_POST_STACK+MSG_BOUND_STACK_SIZE))) { + return -EINVAL; + } + + writel_be(msg_frame_address, MIXART_MEM(mgr, headptr)); + + /* increment the inbound post head */ + headptr += 4; + if( headptr >= (MSG_INBOUND_POST_STACK+MSG_BOUND_STACK_SIZE) ) + headptr = MSG_INBOUND_POST_STACK; + + writel_be(headptr, MIXART_MEM(mgr, MSG_INBOUND_POST_HEAD)); + + return 0; +} + + +int snd_mixart_send_msg(struct mixart_mgr *mgr, struct mixart_msg *request, int max_resp_size, void *resp_data) +{ + struct mixart_msg resp; + u32 msg_frame = 0; /* set to 0, so it's no notification to wait for, but the answer */ + int err; + wait_queue_entry_t wait; + long timeout; + + init_waitqueue_entry(&wait, current); + + scoped_guard(mutex, &mgr->msg_lock) { + /* send the message */ + err = send_msg(mgr, request, max_resp_size, 1, &msg_frame); /* send and mark the answer pending */ + if (err) + return err; + + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&mgr->msg_sleep, &wait); + } + + timeout = schedule_timeout(MSG_TIMEOUT_JIFFIES); + remove_wait_queue(&mgr->msg_sleep, &wait); + + if (! timeout) { + /* error - no ack */ + dev_err(&mgr->pci->dev, + "error: no response on msg %x\n", msg_frame); + return -EIO; + } + + /* retrieve the answer into the same struct mixart_msg */ + resp.message_id = 0; + resp.uid = (struct mixart_uid){0,0}; + resp.data = resp_data; + resp.size = max_resp_size; + + scoped_guard(mutex, &mgr->msg_lock) { + err = get_msg(mgr, &resp, msg_frame); + } + + if( request->message_id != resp.message_id ) + dev_err(&mgr->pci->dev, "RESPONSE ERROR!\n"); + + return err; +} + + +int snd_mixart_send_msg_wait_notif(struct mixart_mgr *mgr, + struct mixart_msg *request, u32 notif_event) +{ + int err; + wait_queue_entry_t wait; + long timeout; + + if (snd_BUG_ON(!notif_event)) + return -EINVAL; + if (snd_BUG_ON((notif_event & MSG_TYPE_MASK) != MSG_TYPE_NOTIFY)) + return -EINVAL; + if (snd_BUG_ON(notif_event & MSG_CANCEL_NOTIFY_MASK)) + return -EINVAL; + + init_waitqueue_entry(&wait, current); + + scoped_guard(mutex, &mgr->msg_lock) { + /* send the message */ + err = send_msg(mgr, request, MSG_DEFAULT_SIZE, 1, ¬if_event); /* send and mark the notification event pending */ + if (err) + return err; + + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&mgr->msg_sleep, &wait); + } + + timeout = schedule_timeout(MSG_TIMEOUT_JIFFIES); + remove_wait_queue(&mgr->msg_sleep, &wait); + + if (! timeout) { + /* error - no ack */ + dev_err(&mgr->pci->dev, + "error: notification %x not received\n", notif_event); + return -EIO; + } + + return 0; +} + + +int snd_mixart_send_msg_nonblock(struct mixart_mgr *mgr, struct mixart_msg *request) +{ + u32 message_frame; + int err; + + /* just send the message (do not mark it as a pending one) */ + guard(mutex)(&mgr->msg_lock); + err = send_msg(mgr, request, MSG_DEFAULT_SIZE, 0, &message_frame); + + /* the answer will be handled by snd_struct mixart_msgasklet() */ + atomic_inc(&mgr->msg_processed); + + return err; +} + + +/* common buffer of interrupt to send/receive messages */ +static u32 mixart_msg_data[MSG_DEFAULT_SIZE / 4]; + + +static void snd_mixart_process_msg(struct mixart_mgr *mgr) +{ + struct mixart_msg resp; + u32 msg, addr, type; + int err; + + while (mgr->msg_fifo_readptr != mgr->msg_fifo_writeptr) { + msg = mgr->msg_fifo[mgr->msg_fifo_readptr]; + mgr->msg_fifo_readptr++; + mgr->msg_fifo_readptr %= MSG_FIFO_SIZE; + + /* process the message ... */ + addr = msg & ~MSG_TYPE_MASK; + type = msg & MSG_TYPE_MASK; + + switch (type) { + case MSG_TYPE_ANSWER: + /* answer to a message on that we did not wait for (send_msg_nonblock) */ + resp.message_id = 0; + resp.data = mixart_msg_data; + resp.size = sizeof(mixart_msg_data); + err = get_msg(mgr, &resp, addr); + if( err < 0 ) { + dev_err(&mgr->pci->dev, + "error(%d) reading mf %x\n", + err, msg); + break; + } + + switch(resp.message_id) { + case MSG_STREAM_START_INPUT_STAGE_PACKET: + case MSG_STREAM_START_OUTPUT_STAGE_PACKET: + case MSG_STREAM_STOP_INPUT_STAGE_PACKET: + case MSG_STREAM_STOP_OUTPUT_STAGE_PACKET: + if(mixart_msg_data[0]) + dev_err(&mgr->pci->dev, + "error MSG_STREAM_ST***_***PUT_STAGE_PACKET status=%x\n", + mixart_msg_data[0]); + break; + default: + dev_dbg(&mgr->pci->dev, + "received mf(%x) : msg_id(%x) uid(%x, %x) size(%zd)\n", + msg, resp.message_id, resp.uid.object_id, resp.uid.desc, resp.size); + break; + } + break; + case MSG_TYPE_NOTIFY: + /* msg contains no address ! do not get_msg() ! */ + case MSG_TYPE_COMMAND: + /* get_msg() necessary */ + default: + dev_err(&mgr->pci->dev, + "doesn't know what to do with message %x\n", + msg); + } /* switch type */ + + /* decrement counter */ + atomic_dec(&mgr->msg_processed); + + } /* while there is a msg in fifo */ +} + + +irqreturn_t snd_mixart_interrupt(int irq, void *dev_id) +{ + struct mixart_mgr *mgr = dev_id; + u32 it_reg; + + it_reg = readl_le(MIXART_REG(mgr, MIXART_PCI_OMISR_OFFSET)); + if( !(it_reg & MIXART_OIDI) ) { + /* this device did not cause the interrupt */ + return IRQ_NONE; + } + + /* mask all interrupts */ + writel_le(MIXART_HOST_ALL_INTERRUPT_MASKED, MIXART_REG(mgr, MIXART_PCI_OMIMR_OFFSET)); + + /* outdoorbell register clear */ + it_reg = readl(MIXART_REG(mgr, MIXART_PCI_ODBR_OFFSET)); + writel(it_reg, MIXART_REG(mgr, MIXART_PCI_ODBR_OFFSET)); + + /* clear interrupt */ + writel_le( MIXART_OIDI, MIXART_REG(mgr, MIXART_PCI_OMISR_OFFSET) ); + + return IRQ_WAKE_THREAD; +} + +irqreturn_t snd_mixart_threaded_irq(int irq, void *dev_id) +{ + struct mixart_mgr *mgr = dev_id; + int err; + struct mixart_msg resp; + u32 msg; + + guard(mutex)(&mgr->lock); + /* process interrupt */ + while (retrieve_msg_frame(mgr, &msg)) { + + switch (msg & MSG_TYPE_MASK) { + case MSG_TYPE_COMMAND: + resp.message_id = 0; + resp.data = mixart_msg_data; + resp.size = sizeof(mixart_msg_data); + err = get_msg(mgr, &resp, msg & ~MSG_TYPE_MASK); + if( err < 0 ) { + dev_err(&mgr->pci->dev, + "interrupt: error(%d) reading mf %x\n", + err, msg); + break; + } + + if(resp.message_id == MSG_SERVICES_TIMER_NOTIFY) { + int i; + struct mixart_timer_notify *notify; + notify = (struct mixart_timer_notify *)mixart_msg_data; + + BUILD_BUG_ON(sizeof(notify) > sizeof(mixart_msg_data)); + if (snd_BUG_ON(notify->stream_count > ARRAY_SIZE(notify->streams))) + break; + for(i=0; istream_count; i++) { + + u32 buffer_id = notify->streams[i].buffer_id; + unsigned int chip_number = (buffer_id & MIXART_NOTIFY_CARD_MASK) >> MIXART_NOTIFY_CARD_OFFSET; /* card0 to 3 */ + unsigned int pcm_number = (buffer_id & MIXART_NOTIFY_PCM_MASK ) >> MIXART_NOTIFY_PCM_OFFSET; /* pcm0 to 3 */ + unsigned int sub_number = buffer_id & MIXART_NOTIFY_SUBS_MASK; /* 0 to MIXART_PLAYBACK_STREAMS */ + unsigned int is_capture = ((buffer_id & MIXART_NOTIFY_CAPT_MASK) != 0); /* playback == 0 / capture == 1 */ + + struct snd_mixart *chip = mgr->chip[chip_number]; + struct mixart_stream *stream; + + if ((chip_number >= mgr->num_cards) || (pcm_number >= MIXART_PCM_TOTAL) || (sub_number >= MIXART_PLAYBACK_STREAMS)) { + dev_err(&mgr->pci->dev, + "error MSG_SERVICES_TIMER_NOTIFY buffer_id (%x) pos(%d)\n", + buffer_id, notify->streams[i].sample_pos_low_part); + break; + } + + if (is_capture) + stream = &chip->capture_stream[pcm_number]; + else + stream = &chip->playback_stream[pcm_number][sub_number]; + + if (stream->substream && (stream->status == MIXART_STREAM_STATUS_RUNNING)) { + struct snd_pcm_runtime *runtime = stream->substream->runtime; + int elapsed = 0; + u64 sample_count = ((u64)notify->streams[i].sample_pos_high_part) << 32; + sample_count |= notify->streams[i].sample_pos_low_part; + + while (1) { + u64 new_elapse_pos = stream->abs_period_elapsed + runtime->period_size; + + if (new_elapse_pos > sample_count) { + break; /* while */ + } + else { + elapsed = 1; + stream->buf_periods++; + if (stream->buf_periods >= runtime->periods) + stream->buf_periods = 0; + + stream->abs_period_elapsed = new_elapse_pos; + } + } + stream->buf_period_frag = (u32)( sample_count - stream->abs_period_elapsed ); + + if(elapsed) { + mutex_unlock(&mgr->lock); + snd_pcm_period_elapsed(stream->substream); + mutex_lock(&mgr->lock); + } + } + } + break; + } + if(resp.message_id == MSG_SERVICES_REPORT_TRACES) { + if(resp.size > 1) { +#ifndef __BIG_ENDIAN + /* Traces are text: the swapped msg_data has to be swapped back ! */ + int i; + for(i=0; i<(resp.size/4); i++) { + ((__be32*)mixart_msg_data)[i] = cpu_to_be32((mixart_msg_data)[i]); + } +#endif + ((char*)mixart_msg_data)[resp.size - 1] = 0; + dev_dbg(&mgr->pci->dev, + "MIXART TRACE : %s\n", + (char *)mixart_msg_data); + } + break; + } + + dev_dbg(&mgr->pci->dev, "command %x not handled\n", + resp.message_id); + break; + + case MSG_TYPE_NOTIFY: + if(msg & MSG_CANCEL_NOTIFY_MASK) { + msg &= ~MSG_CANCEL_NOTIFY_MASK; + dev_err(&mgr->pci->dev, + "canceled notification %x !\n", msg); + } + fallthrough; + case MSG_TYPE_ANSWER: + /* answer or notification to a message we are waiting for*/ + scoped_guard(mutex, &mgr->msg_lock) { + if ((msg & ~MSG_TYPE_MASK) == mgr->pending_event) { + wake_up(&mgr->msg_sleep); + mgr->pending_event = 0; + } + /* answer to a message we did't want to wait for */ + else { + mgr->msg_fifo[mgr->msg_fifo_writeptr] = msg; + mgr->msg_fifo_writeptr++; + mgr->msg_fifo_writeptr %= MSG_FIFO_SIZE; + snd_mixart_process_msg(mgr); + } + } + break; + case MSG_TYPE_REQUEST: + default: + dev_dbg(&mgr->pci->dev, + "interrupt received request %x\n", msg); + /* TODO : are there things to do here ? */ + break; + } /* switch on msg type */ + } /* while there are msgs */ + + /* allow interrupt again */ + writel_le( MIXART_ALLOW_OUTBOUND_DOORBELL, MIXART_REG( mgr, MIXART_PCI_OMIMR_OFFSET)); + + return IRQ_HANDLED; +} + + +void snd_mixart_init_mailbox(struct mixart_mgr *mgr) +{ + writel( 0, MIXART_MEM( mgr, MSG_HOST_RSC_PROTECTION ) ); + writel( 0, MIXART_MEM( mgr, MSG_AGENT_RSC_PROTECTION ) ); + + /* allow outbound messagebox to generate interrupts */ + if(mgr->irq >= 0) { + writel_le( MIXART_ALLOW_OUTBOUND_DOORBELL, MIXART_REG( mgr, MIXART_PCI_OMIMR_OFFSET)); + } + return; +} + +void snd_mixart_exit_mailbox(struct mixart_mgr *mgr) +{ + /* no more interrupts on outbound messagebox */ + writel_le( MIXART_HOST_ALL_INTERRUPT_MASKED, MIXART_REG( mgr, MIXART_PCI_OMIMR_OFFSET)); + return; +} + +void snd_mixart_reset_board(struct mixart_mgr *mgr) +{ + /* reset miXart */ + writel_be( 1, MIXART_REG(mgr, MIXART_BA1_BRUTAL_RESET_OFFSET) ); + return; +} diff --git a/sound/pci/mixart/mixart_core.h b/sound/pci/mixart/mixart_core.h new file mode 100644 index 000000000..7c9a9d82d --- /dev/null +++ b/sound/pci/mixart/mixart_core.h @@ -0,0 +1,565 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram miXart soundcards + * + * low level interface with interrupt handling and mail box implementation + * + * Copyright (c) 2003 by Digigram + */ + +#ifndef __SOUND_MIXART_CORE_H +#define __SOUND_MIXART_CORE_H + + +enum mixart_message_id { + MSG_CONNECTOR_GET_AUDIO_INFO = 0x050008, + MSG_CONNECTOR_GET_OUT_AUDIO_LEVEL = 0x050009, + MSG_CONNECTOR_SET_OUT_AUDIO_LEVEL = 0x05000A, + + MSG_CONSOLE_MANAGER = 0x070000, + MSG_CONSOLE_GET_CLOCK_UID = 0x070003, + + MSG_PHYSICALIO_SET_LEVEL = 0x0F0008, + + MSG_STREAM_ADD_INPUT_GROUP = 0x130000, + MSG_STREAM_ADD_OUTPUT_GROUP = 0x130001, + MSG_STREAM_DELETE_GROUP = 0x130004, + MSG_STREAM_START_STREAM_GRP_PACKET = 0x130006, + MSG_STREAM_START_INPUT_STAGE_PACKET = 0x130007, + MSG_STREAM_START_OUTPUT_STAGE_PACKET = 0x130008, + MSG_STREAM_STOP_STREAM_GRP_PACKET = 0x130009, + MSG_STREAM_STOP_INPUT_STAGE_PACKET = 0x13000A, + MSG_STREAM_STOP_OUTPUT_STAGE_PACKET = 0x13000B, + MSG_STREAM_SET_INPUT_STAGE_PARAM = 0x13000F, + MSG_STREAM_SET_OUTPUT_STAGE_PARAM = 0x130010, + MSG_STREAM_SET_IN_AUDIO_LEVEL = 0x130015, + MSG_STREAM_SET_OUT_STREAM_LEVEL = 0x130017, + + MSG_SYSTEM_FIRST_ID = 0x160000, + MSG_SYSTEM_ENUM_PHYSICAL_IO = 0x16000E, + MSG_SYSTEM_ENUM_PLAY_CONNECTOR = 0x160017, + MSG_SYSTEM_ENUM_RECORD_CONNECTOR = 0x160018, + MSG_SYSTEM_WAIT_SYNCHRO_CMD = 0x16002C, + MSG_SYSTEM_SEND_SYNCHRO_CMD = 0x16002D, + + MSG_SERVICES_TIMER_NOTIFY = 0x1D0404, + MSG_SERVICES_REPORT_TRACES = 0x1D0700, + + MSG_CLOCK_CHECK_PROPERTIES = 0x200001, + MSG_CLOCK_SET_PROPERTIES = 0x200002, +}; + +#define MSG_DEFAULT_SIZE 512 + +struct mixart_msg +{ + u32 message_id; + struct mixart_uid uid; + void* data; + size_t size; +}; + +/* structs used to communicate with miXart */ + +struct mixart_enum_connector_resp +{ + u32 error_code; + u32 first_uid_offset; + u32 uid_count; + u32 current_uid_index; + struct mixart_uid uid[MIXART_MAX_PHYS_CONNECTORS]; +} __packed; + + +/* used for following struct */ +#define MIXART_FLOAT_P_22_0_TO_HEX 0x41b00000 /* 22.0f */ +#define MIXART_FLOAT_M_20_0_TO_HEX 0xc1a00000 /* -20.0f */ +#define MIXART_FLOAT____0_0_TO_HEX 0x00000000 /* 0.0f */ + +struct mixart_audio_info_req +{ + u32 line_max_level; /* float */ + u32 micro_max_level; /* float */ + u32 cd_max_level; /* float */ +} __packed; + +struct mixart_analog_hw_info +{ + u32 is_present; + u32 hw_connection_type; + u32 max_level; /* float */ + u32 min_var_level; /* float */ + u32 max_var_level; /* float */ + u32 step_var_level; /* float */ + u32 fix_gain; /* float */ + u32 zero_var; /* float */ +} __packed; + +struct mixart_digital_hw_info +{ + u32 hw_connection_type; + u32 presence; + u32 clock; + u32 reserved; +} __packed; + +struct mixart_analog_info +{ + u32 type_mask; + struct mixart_analog_hw_info micro_info; + struct mixart_analog_hw_info line_info; + struct mixart_analog_hw_info cd_info; + u32 analog_level_present; +} __packed; + +struct mixart_digital_info +{ + u32 type_mask; + struct mixart_digital_hw_info aes_info; + struct mixart_digital_hw_info adat_info; +} __packed; + +struct mixart_audio_info +{ + u32 clock_type_mask; + struct mixart_analog_info analog_info; + struct mixart_digital_info digital_info; +} __packed; + +struct mixart_audio_info_resp +{ + u32 txx_status; + struct mixart_audio_info info; +} __packed; + + +/* used for nb_bytes_max_per_sample */ +#define MIXART_FLOAT_P__4_0_TO_HEX 0x40800000 /* +4.0f */ +#define MIXART_FLOAT_P__8_0_TO_HEX 0x41000000 /* +8.0f */ + +struct mixart_stream_info +{ + u32 size_max_byte_frame; + u32 size_max_sample_frame; + u32 nb_bytes_max_per_sample; /* float */ +} __packed; + +/* MSG_STREAM_ADD_INPUT_GROUP */ +/* MSG_STREAM_ADD_OUTPUT_GROUP */ + +struct mixart_streaming_group_req +{ + u32 stream_count; + u32 channel_count; + u32 user_grp_number; + u32 first_phys_audio; + u32 latency; + struct mixart_stream_info stream_info[32]; + struct mixart_uid connector; + u32 flow_entry[32]; +} __packed; + +struct mixart_stream_desc +{ + struct mixart_uid stream_uid; + u32 stream_desc; +} __packed; + +struct mixart_streaming_group +{ + u32 status; + struct mixart_uid group; + u32 pipe_desc; + u32 stream_count; + struct mixart_stream_desc stream[32]; +} __packed; + +/* MSG_STREAM_DELETE_GROUP */ + +/* request : mixart_uid_t group */ + +struct mixart_delete_group_resp +{ + u32 status; + u32 unused[2]; +} __packed; + + +/* MSG_STREAM_START_INPUT_STAGE_PACKET = 0x130000 + 7, + MSG_STREAM_START_OUTPUT_STAGE_PACKET = 0x130000 + 8, + MSG_STREAM_STOP_INPUT_STAGE_PACKET = 0x130000 + 10, + MSG_STREAM_STOP_OUTPUT_STAGE_PACKET = 0x130000 + 11, + */ + +struct mixart_fx_couple_uid +{ + struct mixart_uid uid_fx_code; + struct mixart_uid uid_fx_data; +} __packed; + +struct mixart_txx_stream_desc +{ + struct mixart_uid uid_pipe; + u32 stream_idx; + u32 fx_number; + struct mixart_fx_couple_uid uid_fx[4]; +} __packed; + +struct mixart_flow_info +{ + struct mixart_txx_stream_desc stream_desc; + u32 flow_entry; + u32 flow_phy_addr; +} __packed; + +struct mixart_stream_state_req +{ + u32 delayed; + u64 scheduler; + u32 reserved4np[3]; + u32 stream_count; /* set to 1 for instance */ + struct mixart_flow_info stream_info; /* could be an array[stream_count] */ +} __packed; + +/* MSG_STREAM_START_STREAM_GRP_PACKET = 0x130000 + 6 + MSG_STREAM_STOP_STREAM_GRP_PACKET = 0x130000 + 9 + */ + +struct mixart_group_state_req +{ + u32 delayed; + u64 scheduler; + u32 reserved4np[2]; + u32 pipe_count; /* set to 1 for instance */ + struct mixart_uid pipe_uid; /* could be an array[pipe_count], in theory */ +} __packed; + +struct mixart_group_state_resp +{ + u32 txx_status; + u64 scheduler; +} __packed; + + + +/* Structures used by the MSG_SERVICES_TIMER_NOTIFY command */ + +struct mixart_sample_pos +{ + u32 buffer_id; + u32 validity; + u32 sample_pos_high_part; + u32 sample_pos_low_part; +} __packed; + +/* + * This structure is limited by the size of MSG_DEFAULT_SIZE. Instead of + * having MIXART_MAX_STREAM_PER_CARD * MIXART_MAX_CARDS many streams, + * this is capped to have a total size below MSG_DEFAULT_SIZE. + */ +#define MIXART_MAX_TIMER_NOTIFY_STREAMS \ + ((MSG_DEFAULT_SIZE - sizeof(u32)) / sizeof(struct mixart_sample_pos)) +struct mixart_timer_notify +{ + u32 stream_count; + struct mixart_sample_pos streams[MIXART_MAX_TIMER_NOTIFY_STREAMS]; +} __packed; + + +/* MSG_CONSOLE_GET_CLOCK_UID = 0x070003, + */ + +/* request is a uid with desc = MSG_CONSOLE_MANAGER | cardindex */ + +struct mixart_return_uid +{ + u32 error_code; + struct mixart_uid uid; +} __packed; + +/* MSG_CLOCK_CHECK_PROPERTIES = 0x200001, + MSG_CLOCK_SET_PROPERTIES = 0x200002, +*/ + +enum mixart_clock_generic_type { + CGT_NO_CLOCK, + CGT_INTERNAL_CLOCK, + CGT_PROGRAMMABLE_CLOCK, + CGT_INTERNAL_ENSLAVED_CLOCK, + CGT_EXTERNAL_CLOCK, + CGT_CURRENT_CLOCK +}; + +enum mixart_clock_mode { + CM_UNDEFINED, + CM_MASTER, + CM_SLAVE, + CM_STANDALONE, + CM_NOT_CONCERNED +}; + + +struct mixart_clock_properties +{ + u32 error_code; + u32 validation_mask; + u32 frequency; + u32 reference_frequency; + u32 clock_generic_type; + u32 clock_mode; + struct mixart_uid uid_clock_source; + struct mixart_uid uid_event_source; + u32 event_mode; + u32 synchro_signal_presence; + u32 format; + u32 board_mask; + u32 nb_callers; /* set to 1 (see below) */ + struct mixart_uid uid_caller; +} __packed; + +struct mixart_clock_properties_resp +{ + u32 status; + u32 clock_mode; +} __packed; + + +/* MSG_STREAM_SET_INPUT_STAGE_PARAM = 0x13000F */ +/* MSG_STREAM_SET_OUTPUT_STAGE_PARAM = 0x130010 */ + +enum mixart_coding_type { + CT_NOT_DEFINED, + CT_LINEAR, + CT_MPEG_L1, + CT_MPEG_L2, + CT_MPEG_L3, + CT_MPEG_L3_LSF, + CT_GSM +}; +enum mixart_sample_type { + ST_NOT_DEFINED, + ST_FLOATING_POINT_32BE, + ST_FLOATING_POINT_32LE, + ST_FLOATING_POINT_64BE, + ST_FLOATING_POINT_64LE, + ST_FIXED_POINT_8, + ST_FIXED_POINT_16BE, + ST_FIXED_POINT_16LE, + ST_FIXED_POINT_24BE, + ST_FIXED_POINT_24LE, + ST_FIXED_POINT_32BE, + ST_FIXED_POINT_32LE, + ST_INTEGER_8, + ST_INTEGER_16BE, + ST_INTEGER_16LE, + ST_INTEGER_24BE, + ST_INTEGER_24LE, + ST_INTEGER_32BE, + ST_INTEGER_32LE +}; + +struct mixart_stream_param_desc +{ + u32 coding_type; /* use enum mixart_coding_type */ + u32 sample_type; /* use enum mixart_sample_type */ + + union { + struct { + u32 linear_endian_ness; + u32 linear_bits; + u32 is_signed; + u32 is_float; + } linear_format_info; + + struct { + u32 mpeg_layer; + u32 mpeg_mode; + u32 mpeg_mode_extension; + u32 mpeg_pre_emphasis; + u32 mpeg_has_padding_bit; + u32 mpeg_has_crc; + u32 mpeg_has_extension; + u32 mpeg_is_original; + u32 mpeg_has_copyright; + } mpeg_format_info; + } format_info; + + u32 delayed; + u64 scheduler; + u32 sample_size; + u32 has_header; + u32 has_suffix; + u32 has_bitrate; + u32 samples_per_frame; + u32 bytes_per_frame; + u32 bytes_per_sample; + u32 sampling_freq; + u32 number_of_channel; + u32 stream_number; + u32 buffer_size; + u32 differed_time; + u32 reserved4np[3]; + u32 pipe_count; /* set to 1 (array size !) */ + u32 stream_count; /* set to 1 (array size !) */ + struct mixart_txx_stream_desc stream_desc; /* only one stream per command, but this could be an array, in theory */ +} __packed; + + +/* MSG_CONNECTOR_GET_OUT_AUDIO_LEVEL = 0x050009, + */ + + +struct mixart_get_out_audio_level +{ + u32 txx_status; + u32 digital_level; /* float */ + u32 analog_level; /* float */ + u32 monitor_level; /* float */ + u32 mute; + u32 monitor_mute1; + u32 monitor_mute2; +} __packed; + + +/* MSG_CONNECTOR_SET_OUT_AUDIO_LEVEL = 0x05000A, + */ + +/* used for valid_mask below */ +#define MIXART_AUDIO_LEVEL_ANALOG_MASK 0x01 +#define MIXART_AUDIO_LEVEL_DIGITAL_MASK 0x02 +#define MIXART_AUDIO_LEVEL_MONITOR_MASK 0x04 +#define MIXART_AUDIO_LEVEL_MUTE_MASK 0x08 +#define MIXART_AUDIO_LEVEL_MUTE_M1_MASK 0x10 +#define MIXART_AUDIO_LEVEL_MUTE_M2_MASK 0x20 + +struct mixart_set_out_audio_level +{ + u32 delayed; + u64 scheduler; + u32 valid_mask1; + u32 valid_mask2; + u32 digital_level; /* float */ + u32 analog_level; /* float */ + u32 monitor_level; /* float */ + u32 mute; + u32 monitor_mute1; + u32 monitor_mute2; + u32 reserved4np; +} __packed; + + +/* MSG_SYSTEM_ENUM_PHYSICAL_IO = 0x16000E, + */ + +#define MIXART_MAX_PHYS_IO (MIXART_MAX_CARDS * 2 * 2) /* 4 * (analog+digital) * (playback+capture) */ + +struct mixart_uid_enumeration +{ + u32 error_code; + u32 first_uid_offset; + u32 nb_uid; + u32 current_uid_index; + struct mixart_uid uid[MIXART_MAX_PHYS_IO]; +} __packed; + + +/* MSG_PHYSICALIO_SET_LEVEL = 0x0F0008, + MSG_PHYSICALIO_GET_LEVEL = 0x0F000C, +*/ + +struct mixart_io_channel_level +{ + u32 analog_level; /* float */ + u32 unused[2]; +} __packed; + +struct mixart_io_level +{ + s32 channel; /* 0=left, 1=right, -1=both, -2=both same */ + struct mixart_io_channel_level level[2]; +} __packed; + + +/* MSG_STREAM_SET_IN_AUDIO_LEVEL = 0x130015, + */ + +struct mixart_in_audio_level_info +{ + struct mixart_uid connector; + u32 valid_mask1; + u32 valid_mask2; + u32 digital_level; + u32 analog_level; +} __packed; + +struct mixart_set_in_audio_level_req +{ + u32 delayed; + u64 scheduler; + u32 audio_count; /* set to <= 2 */ + u32 reserved4np; + struct mixart_in_audio_level_info level[2]; +} __packed; + +/* response is a 32 bit status */ + + +/* MSG_STREAM_SET_OUT_STREAM_LEVEL = 0x130017, + */ + +/* defines used for valid_mask1 */ +#define MIXART_OUT_STREAM_SET_LEVEL_LEFT_AUDIO1 0x01 +#define MIXART_OUT_STREAM_SET_LEVEL_LEFT_AUDIO2 0x02 +#define MIXART_OUT_STREAM_SET_LEVEL_RIGHT_AUDIO1 0x04 +#define MIXART_OUT_STREAM_SET_LEVEL_RIGHT_AUDIO2 0x08 +#define MIXART_OUT_STREAM_SET_LEVEL_STREAM_1 0x10 +#define MIXART_OUT_STREAM_SET_LEVEL_STREAM_2 0x20 +#define MIXART_OUT_STREAM_SET_LEVEL_MUTE_1 0x40 +#define MIXART_OUT_STREAM_SET_LEVEL_MUTE_2 0x80 + +struct mixart_out_stream_level_info +{ + u32 valid_mask1; + u32 valid_mask2; + u32 left_to_out1_level; + u32 left_to_out2_level; + u32 right_to_out1_level; + u32 right_to_out2_level; + u32 digital_level1; + u32 digital_level2; + u32 mute1; + u32 mute2; +} __packed; + +struct mixart_set_out_stream_level +{ + struct mixart_txx_stream_desc desc; + struct mixart_out_stream_level_info out_level; +} __packed; + +struct mixart_set_out_stream_level_req +{ + u32 delayed; + u64 scheduler; + u32 reserved4np[2]; + u32 nb_of_stream; /* set to 1 */ + struct mixart_set_out_stream_level stream_level; /* could be an array */ +} __packed; + +/* response to this request is a u32 status value */ + + +/* exported */ +void snd_mixart_init_mailbox(struct mixart_mgr *mgr); +void snd_mixart_exit_mailbox(struct mixart_mgr *mgr); + +int snd_mixart_send_msg(struct mixart_mgr *mgr, struct mixart_msg *request, int max_resp_size, void *resp_data); +int snd_mixart_send_msg_wait_notif(struct mixart_mgr *mgr, struct mixart_msg *request, u32 notif_event); +int snd_mixart_send_msg_nonblock(struct mixart_mgr *mgr, struct mixart_msg *request); + +irqreturn_t snd_mixart_interrupt(int irq, void *dev_id); +irqreturn_t snd_mixart_threaded_irq(int irq, void *dev_id); + +void snd_mixart_reset_board(struct mixart_mgr *mgr); + +#endif /* __SOUND_MIXART_CORE_H */ diff --git a/sound/pci/mixart/mixart_hwdep.c b/sound/pci/mixart/mixart_hwdep.c new file mode 100644 index 000000000..aa8d5bae3 --- /dev/null +++ b/sound/pci/mixart/mixart_hwdep.c @@ -0,0 +1,584 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram miXart soundcards + * + * DSP firmware management + * + * Copyright (c) 2003 by Digigram + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mixart.h" +#include "mixart_mixer.h" +#include "mixart_core.h" +#include "mixart_hwdep.h" + + +/** + * mixart_wait_nice_for_register_value - wait for a value on a peudo register, + * exit with a timeout + * + * @mgr: pointer to miXart manager structure + * @offset: unsigned pseudo_register base + offset of value + * @is_egal: wait for the equal value + * @value: value + * @timeout: timeout in centisenconds + */ +static int mixart_wait_nice_for_register_value(struct mixart_mgr *mgr, + u32 offset, int is_egal, + u32 value, unsigned long timeout) +{ + unsigned long end_time = jiffies + (timeout * HZ / 100); + u32 read; + + do { /* we may take too long time in this loop. + * so give controls back to kernel if needed. + */ + cond_resched(); + + read = readl_be( MIXART_MEM( mgr, offset )); + if(is_egal) { + if(read == value) return 0; + } + else { /* wait for different value */ + if(read != value) return 0; + } + } while ( time_after_eq(end_time, jiffies) ); + + return -EBUSY; +} + + +/* + structures needed to upload elf code packets + */ +struct snd_mixart_elf32_ehdr { + u8 e_ident[16]; + __be16 e_type; + __be16 e_machine; + __be32 e_version; + __be32 e_entry; + __be32 e_phoff; + __be32 e_shoff; + __be32 e_flags; + __be16 e_ehsize; + __be16 e_phentsize; + __be16 e_phnum; + __be16 e_shentsize; + __be16 e_shnum; + __be16 e_shstrndx; +}; + +struct snd_mixart_elf32_phdr { + __be32 p_type; + __be32 p_offset; + __be32 p_vaddr; + __be32 p_paddr; + __be32 p_filesz; + __be32 p_memsz; + __be32 p_flags; + __be32 p_align; +}; + +static int mixart_load_elf(struct mixart_mgr *mgr, const struct firmware *dsp ) +{ + char elf32_magic_number[4] = {0x7f,'E','L','F'}; + struct snd_mixart_elf32_ehdr *elf_header; + int i; + + elf_header = (struct snd_mixart_elf32_ehdr *)dsp->data; + for( i=0; i<4; i++ ) + if ( elf32_magic_number[i] != elf_header->e_ident[i] ) + return -EINVAL; + + if( elf_header->e_phoff != 0 ) { + struct snd_mixart_elf32_phdr elf_programheader; + + for( i=0; i < be16_to_cpu(elf_header->e_phnum); i++ ) { + u32 pos = be32_to_cpu(elf_header->e_phoff) + (u32)(i * be16_to_cpu(elf_header->e_phentsize)); + + memcpy( &elf_programheader, dsp->data + pos, sizeof(elf_programheader) ); + + if(elf_programheader.p_type != 0) { + if( elf_programheader.p_filesz != 0 ) { + memcpy_toio( MIXART_MEM( mgr, be32_to_cpu(elf_programheader.p_vaddr)), + dsp->data + be32_to_cpu( elf_programheader.p_offset ), + be32_to_cpu( elf_programheader.p_filesz )); + } + } + } + } + return 0; +} + +/* + * get basic information and init miXart + */ + +/* audio IDs for request to the board */ +#define MIXART_FIRST_ANA_AUDIO_ID 0 +#define MIXART_FIRST_DIG_AUDIO_ID 8 + +static int mixart_enum_connectors(struct mixart_mgr *mgr) +{ + u32 k; + int err; + struct mixart_msg request; + struct mixart_enum_connector_resp *connector; + struct mixart_audio_info_req *audio_info_req; + struct mixart_audio_info_resp *audio_info; + + connector = kmalloc_obj(*connector); + audio_info_req = kmalloc_obj(*audio_info_req); + audio_info = kmalloc_obj(*audio_info); + if (! connector || ! audio_info_req || ! audio_info) { + err = -ENOMEM; + goto __error; + } + + audio_info_req->line_max_level = MIXART_FLOAT_P_22_0_TO_HEX; + audio_info_req->micro_max_level = MIXART_FLOAT_M_20_0_TO_HEX; + audio_info_req->cd_max_level = MIXART_FLOAT____0_0_TO_HEX; + + request.message_id = MSG_SYSTEM_ENUM_PLAY_CONNECTOR; + request.uid = (struct mixart_uid){0,0}; /* board num = 0 */ + request.data = NULL; + request.size = 0; + + err = snd_mixart_send_msg(mgr, &request, sizeof(*connector), connector); + if((err < 0) || (connector->error_code) || (connector->uid_count > MIXART_MAX_PHYS_CONNECTORS)) { + dev_err(&mgr->pci->dev, + "error MSG_SYSTEM_ENUM_PLAY_CONNECTOR\n"); + err = -EINVAL; + goto __error; + } + + for(k=0; k < connector->uid_count; k++) { + struct mixart_pipe *pipe; + + if(k < MIXART_FIRST_DIG_AUDIO_ID) { + pipe = &mgr->chip[k/2]->pipe_out_ana; + } else { + pipe = &mgr->chip[(k-MIXART_FIRST_DIG_AUDIO_ID)/2]->pipe_out_dig; + } + if(k & 1) { + pipe->uid_right_connector = connector->uid[k]; /* odd */ + } else { + pipe->uid_left_connector = connector->uid[k]; /* even */ + } + + /* dev_dbg(&mgr->pci->dev, "playback connector[%d].object_id = %x\n", k, connector->uid[k].object_id); */ + + /* TODO: really need send_msg MSG_CONNECTOR_GET_AUDIO_INFO for each connector ? perhaps for analog level caps ? */ + request.message_id = MSG_CONNECTOR_GET_AUDIO_INFO; + request.uid = connector->uid[k]; + request.data = audio_info_req; + request.size = sizeof(*audio_info_req); + + err = snd_mixart_send_msg(mgr, &request, sizeof(*audio_info), audio_info); + if( err < 0 ) { + dev_err(&mgr->pci->dev, + "error MSG_CONNECTOR_GET_AUDIO_INFO\n"); + goto __error; + } + /*dev_dbg(&mgr->pci->dev, "play analog_info.analog_level_present = %x\n", audio_info->info.analog_info.analog_level_present);*/ + } + + request.message_id = MSG_SYSTEM_ENUM_RECORD_CONNECTOR; + request.uid = (struct mixart_uid){0,0}; /* board num = 0 */ + request.data = NULL; + request.size = 0; + + err = snd_mixart_send_msg(mgr, &request, sizeof(*connector), connector); + if((err < 0) || (connector->error_code) || (connector->uid_count > MIXART_MAX_PHYS_CONNECTORS)) { + dev_err(&mgr->pci->dev, + "error MSG_SYSTEM_ENUM_RECORD_CONNECTOR\n"); + err = -EINVAL; + goto __error; + } + + for(k=0; k < connector->uid_count; k++) { + struct mixart_pipe *pipe; + + if(k < MIXART_FIRST_DIG_AUDIO_ID) { + pipe = &mgr->chip[k/2]->pipe_in_ana; + } else { + pipe = &mgr->chip[(k-MIXART_FIRST_DIG_AUDIO_ID)/2]->pipe_in_dig; + } + if(k & 1) { + pipe->uid_right_connector = connector->uid[k]; /* odd */ + } else { + pipe->uid_left_connector = connector->uid[k]; /* even */ + } + + /* dev_dbg(&mgr->pci->dev, "capture connector[%d].object_id = %x\n", k, connector->uid[k].object_id); */ + + /* TODO: really need send_msg MSG_CONNECTOR_GET_AUDIO_INFO for each connector ? perhaps for analog level caps ? */ + request.message_id = MSG_CONNECTOR_GET_AUDIO_INFO; + request.uid = connector->uid[k]; + request.data = audio_info_req; + request.size = sizeof(*audio_info_req); + + err = snd_mixart_send_msg(mgr, &request, sizeof(*audio_info), audio_info); + if( err < 0 ) { + dev_err(&mgr->pci->dev, + "error MSG_CONNECTOR_GET_AUDIO_INFO\n"); + goto __error; + } + /*dev_dbg(&mgr->pci->dev, "rec analog_info.analog_level_present = %x\n", audio_info->info.analog_info.analog_level_present);*/ + } + err = 0; + + __error: + kfree(connector); + kfree(audio_info_req); + kfree(audio_info); + + return err; +} + +static int mixart_enum_physio(struct mixart_mgr *mgr) +{ + u32 k; + int err; + struct mixart_msg request; + struct mixart_uid get_console_mgr; + struct mixart_return_uid console_mgr; + struct mixart_uid_enumeration phys_io; + + /* get the uid for the console manager */ + get_console_mgr.object_id = 0; + get_console_mgr.desc = MSG_CONSOLE_MANAGER | 0; /* cardindex = 0 */ + + request.message_id = MSG_CONSOLE_GET_CLOCK_UID; + request.uid = get_console_mgr; + request.data = &get_console_mgr; + request.size = sizeof(get_console_mgr); + + err = snd_mixart_send_msg(mgr, &request, sizeof(console_mgr), &console_mgr); + + if( (err < 0) || (console_mgr.error_code != 0) ) { + dev_dbg(&mgr->pci->dev, + "error MSG_CONSOLE_GET_CLOCK_UID : err=%x\n", + console_mgr.error_code); + return -EINVAL; + } + + /* used later for clock issues ! */ + mgr->uid_console_manager = console_mgr.uid; + + request.message_id = MSG_SYSTEM_ENUM_PHYSICAL_IO; + request.uid = (struct mixart_uid){0,0}; + request.data = &console_mgr.uid; + request.size = sizeof(console_mgr.uid); + + err = snd_mixart_send_msg(mgr, &request, sizeof(phys_io), &phys_io); + if( (err < 0) || ( phys_io.error_code != 0 ) ) { + dev_err(&mgr->pci->dev, + "error MSG_SYSTEM_ENUM_PHYSICAL_IO err(%x) error_code(%x)\n", + err, phys_io.error_code); + return -EINVAL; + } + + /* min 2 phys io per card (analog in + analog out) */ + if (phys_io.nb_uid < MIXART_MAX_CARDS * 2) + return -EINVAL; + + for(k=0; knum_cards; k++) { + mgr->chip[k]->uid_in_analog_physio = phys_io.uid[k]; + mgr->chip[k]->uid_out_analog_physio = phys_io.uid[phys_io.nb_uid/2 + k]; + } + + return 0; +} + + +static int mixart_first_init(struct mixart_mgr *mgr) +{ + u32 k; + int err; + struct mixart_msg request; + + err = mixart_enum_connectors(mgr); + if (err < 0) + return err; + + err = mixart_enum_physio(mgr); + if (err < 0) + return err; + + /* send a synchro command to card (necessary to do this before first MSG_STREAM_START_STREAM_GRP_PACKET) */ + /* though why not here */ + request.message_id = MSG_SYSTEM_SEND_SYNCHRO_CMD; + request.uid = (struct mixart_uid){0,0}; + request.data = NULL; + request.size = 0; + /* this command has no data. response is a 32 bit status */ + err = snd_mixart_send_msg(mgr, &request, sizeof(k), &k); + if( (err < 0) || (k != 0) ) { + dev_err(&mgr->pci->dev, "error MSG_SYSTEM_SEND_SYNCHRO_CMD\n"); + return err == 0 ? -EINVAL : err; + } + + return 0; +} + + +/* firmware base addresses (when hard coded) */ +#define MIXART_MOTHERBOARD_XLX_BASE_ADDRESS 0x00600000 + +static int mixart_dsp_load(struct mixart_mgr* mgr, int index, const struct firmware *dsp) +{ + int err, card_index; + u32 status_xilinx, status_elf, status_daught; + u32 val; + + /* read motherboard xilinx status */ + status_xilinx = readl_be( MIXART_MEM( mgr,MIXART_PSEUDOREG_MXLX_STATUS_OFFSET )); + /* read elf status */ + status_elf = readl_be( MIXART_MEM( mgr,MIXART_PSEUDOREG_ELF_STATUS_OFFSET )); + /* read daughterboard xilinx status */ + status_daught = readl_be( MIXART_MEM( mgr,MIXART_PSEUDOREG_DXLX_STATUS_OFFSET )); + + /* motherboard xilinx status 5 will say that the board is performing a reset */ + if (status_xilinx == 5) { + dev_err(&mgr->pci->dev, "miXart is resetting !\n"); + return -EAGAIN; /* try again later */ + } + + switch (index) { + case MIXART_MOTHERBOARD_XLX_INDEX: + + /* xilinx already loaded ? */ + if (status_xilinx == 4) { + dev_dbg(&mgr->pci->dev, "xilinx is already loaded !\n"); + return 0; + } + /* the status should be 0 == "idle" */ + if (status_xilinx != 0) { + dev_err(&mgr->pci->dev, + "xilinx load error ! status = %d\n", + status_xilinx); + return -EIO; /* modprob -r may help ? */ + } + + /* check xilinx validity */ + if (((u32*)(dsp->data))[0] == 0xffffffff) + return -EINVAL; + if (dsp->size % 4) + return -EINVAL; + + /* set xilinx status to copying */ + writel_be( 1, MIXART_MEM( mgr, MIXART_PSEUDOREG_MXLX_STATUS_OFFSET )); + + /* setup xilinx base address */ + writel_be( MIXART_MOTHERBOARD_XLX_BASE_ADDRESS, MIXART_MEM( mgr,MIXART_PSEUDOREG_MXLX_BASE_ADDR_OFFSET )); + /* setup code size for xilinx file */ + writel_be( dsp->size, MIXART_MEM( mgr, MIXART_PSEUDOREG_MXLX_SIZE_OFFSET )); + + /* copy xilinx code */ + memcpy_toio( MIXART_MEM( mgr, MIXART_MOTHERBOARD_XLX_BASE_ADDRESS), dsp->data, dsp->size); + + /* set xilinx status to copy finished */ + writel_be( 2, MIXART_MEM( mgr, MIXART_PSEUDOREG_MXLX_STATUS_OFFSET )); + + /* return, because no further processing needed */ + return 0; + + case MIXART_MOTHERBOARD_ELF_INDEX: + + if (status_elf == 4) { + dev_dbg(&mgr->pci->dev, "elf file already loaded !\n"); + return 0; + } + + /* the status should be 0 == "idle" */ + if (status_elf != 0) { + dev_err(&mgr->pci->dev, + "elf load error ! status = %d\n", + status_elf); + return -EIO; /* modprob -r may help ? */ + } + + /* wait for xilinx status == 4 */ + err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_MXLX_STATUS_OFFSET, 1, 4, 500); /* 5sec */ + if (err < 0) { + dev_err(&mgr->pci->dev, "xilinx was not loaded or " + "could not be started\n"); + return err; + } + + /* init some data on the card */ + writel_be( 0, MIXART_MEM( mgr, MIXART_PSEUDOREG_BOARDNUMBER ) ); /* set miXart boardnumber to 0 */ + writel_be( 0, MIXART_MEM( mgr, MIXART_FLOWTABLE_PTR ) ); /* reset pointer to flow table on miXart */ + + /* set elf status to copying */ + writel_be( 1, MIXART_MEM( mgr, MIXART_PSEUDOREG_ELF_STATUS_OFFSET )); + + /* process the copying of the elf packets */ + err = mixart_load_elf( mgr, dsp ); + if (err < 0) return err; + + /* set elf status to copy finished */ + writel_be( 2, MIXART_MEM( mgr, MIXART_PSEUDOREG_ELF_STATUS_OFFSET )); + + /* wait for elf status == 4 */ + err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_ELF_STATUS_OFFSET, 1, 4, 300); /* 3sec */ + if (err < 0) { + dev_err(&mgr->pci->dev, "elf could not be started\n"); + return err; + } + + /* miXart waits at this point on the pointer to the flow table */ + writel_be( (u32)mgr->flowinfo.addr, MIXART_MEM( mgr, MIXART_FLOWTABLE_PTR ) ); /* give pointer of flow table to miXart */ + + return 0; /* return, another xilinx file has to be loaded before */ + + case MIXART_AESEBUBOARD_XLX_INDEX: + default: + + /* elf and xilinx should be loaded */ + if (status_elf != 4 || status_xilinx != 4) { + dev_err(&mgr->pci->dev, "xilinx or elf not " + "successfully loaded\n"); + return -EIO; /* modprob -r may help ? */ + } + + /* wait for daughter detection != 0 */ + err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_DBRD_PRESENCE_OFFSET, 0, 0, 30); /* 300msec */ + if (err < 0) { + dev_err(&mgr->pci->dev, "error starting elf file\n"); + return err; + } + + /* the board type can now be retrieved */ + mgr->board_type = (DAUGHTER_TYPE_MASK & readl_be( MIXART_MEM( mgr, MIXART_PSEUDOREG_DBRD_TYPE_OFFSET))); + + if (mgr->board_type == MIXART_DAUGHTER_TYPE_NONE) + break; /* no daughter board; the file does not have to be loaded, continue after the switch */ + + /* only if aesebu daughter board presence (elf code must run) */ + if (mgr->board_type != MIXART_DAUGHTER_TYPE_AES ) + return -EINVAL; + + /* daughter should be idle */ + if (status_daught != 0) { + dev_err(&mgr->pci->dev, + "daughter load error ! status = %d\n", + status_daught); + return -EIO; /* modprob -r may help ? */ + } + + /* check daughterboard xilinx validity */ + if (((u32*)(dsp->data))[0] == 0xffffffff) + return -EINVAL; + if (dsp->size % 4) + return -EINVAL; + + /* inform mixart about the size of the file */ + writel_be( dsp->size, MIXART_MEM( mgr, MIXART_PSEUDOREG_DXLX_SIZE_OFFSET )); + + /* set daughterboard status to 1 */ + writel_be( 1, MIXART_MEM( mgr, MIXART_PSEUDOREG_DXLX_STATUS_OFFSET )); + + /* wait for status == 2 */ + err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_DXLX_STATUS_OFFSET, 1, 2, 30); /* 300msec */ + if (err < 0) { + dev_err(&mgr->pci->dev, "daughter board load error\n"); + return err; + } + + /* get the address where to write the file */ + val = readl_be( MIXART_MEM( mgr, MIXART_PSEUDOREG_DXLX_BASE_ADDR_OFFSET )); + if (!val) + return -EINVAL; + + /* copy daughterboard xilinx code */ + memcpy_toio( MIXART_MEM( mgr, val), dsp->data, dsp->size); + + /* set daughterboard status to 4 */ + writel_be( 4, MIXART_MEM( mgr, MIXART_PSEUDOREG_DXLX_STATUS_OFFSET )); + + /* continue with init */ + break; + } /* end of switch file index*/ + + /* wait for daughter status == 3 */ + err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_DXLX_STATUS_OFFSET, 1, 3, 300); /* 3sec */ + if (err < 0) { + dev_err(&mgr->pci->dev, + "daughter board could not be initialised\n"); + return err; + } + + /* init mailbox (communication with embedded) */ + snd_mixart_init_mailbox(mgr); + + /* first communication with embedded */ + err = mixart_first_init(mgr); + if (err < 0) { + dev_err(&mgr->pci->dev, "miXart could not be set up\n"); + return err; + } + + /* create devices and mixer in accordance with HW options*/ + for (card_index = 0; card_index < mgr->num_cards; card_index++) { + struct snd_mixart *chip = mgr->chip[card_index]; + + err = snd_mixart_create_pcm(chip); + if (err < 0) + return err; + + if (card_index == 0) { + err = snd_mixart_create_mixer(chip->mgr); + if (err < 0) + return err; + } + + err = snd_card_register(chip->card); + if (err < 0) + return err; + } + + dev_dbg(&mgr->pci->dev, + "miXart firmware downloaded and successfully set up\n"); + + return 0; +} + + +int snd_mixart_setup_firmware(struct mixart_mgr *mgr) +{ + static const char * const fw_files[3] = { + "miXart8.xlx", "miXart8.elf", "miXart8AES.xlx" + }; + char path[32]; + int i, err; + + for (i = 0; i < 3; i++) { + sprintf(path, "mixart/%s", fw_files[i]); + const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "miXart: can't load firmware %s\n", path); + return -ENOENT; + } + /* fake hwdep dsp record */ + err = mixart_dsp_load(mgr, i, fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; + } + return 0; +} + +MODULE_FIRMWARE("mixart/miXart8.xlx"); +MODULE_FIRMWARE("mixart/miXart8.elf"); +MODULE_FIRMWARE("mixart/miXart8AES.xlx"); diff --git a/sound/pci/mixart/mixart_hwdep.h b/sound/pci/mixart/mixart_hwdep.h new file mode 100644 index 000000000..69f45bb4d --- /dev/null +++ b/sound/pci/mixart/mixart_hwdep.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram miXart soundcards + * + * definitions and makros for basic card access + * + * Copyright (c) 2003 by Digigram + */ + +#ifndef __SOUND_MIXART_HWDEP_H +#define __SOUND_MIXART_HWDEP_H + +#include + +#ifndef readl_be +#define readl_be(x) be32_to_cpu((__force __be32)__raw_readl(x)) +#endif + +#ifndef writel_be +#define writel_be(data,addr) __raw_writel((__force u32)cpu_to_be32(data),addr) +#endif + +#ifndef readl_le +#define readl_le(x) le32_to_cpu((__force __le32)__raw_readl(x)) +#endif + +#ifndef writel_le +#define writel_le(data,addr) __raw_writel((__force u32)cpu_to_le32(data),addr) +#endif + +#define MIXART_MEM(mgr,x) ((mgr)->mem[0].virt + (x)) +#define MIXART_REG(mgr,x) ((mgr)->mem[1].virt + (x)) + + +/* Daughter board Type */ +#define DAUGHTER_TYPE_MASK 0x0F +#define DAUGHTER_VER_MASK 0xF0 +#define DAUGHTER_TYPEVER_MASK (DAUGHTER_TYPE_MASK|DAUGHTER_VER_MASK) + +#define MIXART_DAUGHTER_TYPE_NONE 0x00 +#define MIXART_DAUGHTER_TYPE_COBRANET 0x08 +#define MIXART_DAUGHTER_TYPE_AES 0x0E + + + +#define MIXART_BA0_SIZE (16 * 1024 * 1024) /* 16M */ +#define MIXART_BA1_SIZE (4 * 1024) /* 4k */ + +/* + * -----------BAR 0 -------------------------------------------------------------------------------------------------------- + */ +#define MIXART_PSEUDOREG 0x2000 /* base address for pseudoregister */ + +#define MIXART_PSEUDOREG_BOARDNUMBER MIXART_PSEUDOREG+0 /* board number */ + +/* perfmeter (available when elf loaded)*/ +#define MIXART_PSEUDOREG_PERF_STREAM_LOAD_OFFSET MIXART_PSEUDOREG+0x70 /* streaming load */ +#define MIXART_PSEUDOREG_PERF_SYSTEM_LOAD_OFFSET MIXART_PSEUDOREG+0x78 /* system load (reference)*/ +#define MIXART_PSEUDOREG_PERF_MAILBX_LOAD_OFFSET MIXART_PSEUDOREG+0x7C /* mailbox load */ +#define MIXART_PSEUDOREG_PERF_INTERR_LOAD_OFFSET MIXART_PSEUDOREG+0x74 /* interrupt handling load */ + +/* motherboard xilinx loader info */ +#define MIXART_PSEUDOREG_MXLX_BASE_ADDR_OFFSET MIXART_PSEUDOREG+0x9C /* 0x00600000 */ +#define MIXART_PSEUDOREG_MXLX_SIZE_OFFSET MIXART_PSEUDOREG+0xA0 /* xilinx size in bytes */ +#define MIXART_PSEUDOREG_MXLX_STATUS_OFFSET MIXART_PSEUDOREG+0xA4 /* status = EMBEBBED_STAT_XXX */ + +/* elf loader info */ +#define MIXART_PSEUDOREG_ELF_STATUS_OFFSET MIXART_PSEUDOREG+0xB0 /* status = EMBEBBED_STAT_XXX */ + +/* +* after the elf code is loaded, and the flowtable info was passed to it, +* the driver polls on this address, until it shows 1 (presence) or 2 (absence) +* once it is non-zero, the daughter board type may be read +*/ +#define MIXART_PSEUDOREG_DBRD_PRESENCE_OFFSET MIXART_PSEUDOREG+0x990 + +/* Global info structure */ +#define MIXART_PSEUDOREG_DBRD_TYPE_OFFSET MIXART_PSEUDOREG+0x994 /* Type and version of daughterboard */ + + +/* daughterboard xilinx loader info */ +#define MIXART_PSEUDOREG_DXLX_BASE_ADDR_OFFSET MIXART_PSEUDOREG+0x998 /* get the address here where to write the file */ +#define MIXART_PSEUDOREG_DXLX_SIZE_OFFSET MIXART_PSEUDOREG+0x99C /* xilinx size in bytes */ +#define MIXART_PSEUDOREG_DXLX_STATUS_OFFSET MIXART_PSEUDOREG+0x9A0 /* status = EMBEBBED_STAT_XXX */ + +/* */ +#define MIXART_FLOWTABLE_PTR 0x3000 /* pointer to flow table */ + +/* mailbox addresses */ + +/* message DRV -> EMB */ +#define MSG_INBOUND_POST_HEAD 0x010008 /* DRV posts MF + increment4 */ +#define MSG_INBOUND_POST_TAIL 0x01000C /* EMB gets MF + increment4 */ +/* message EMB -> DRV */ +#define MSG_OUTBOUND_POST_TAIL 0x01001C /* DRV gets MF + increment4 */ +#define MSG_OUTBOUND_POST_HEAD 0x010018 /* EMB posts MF + increment4 */ +/* Get Free Frames */ +#define MSG_INBOUND_FREE_TAIL 0x010004 /* DRV gets MFA + increment4 */ +#define MSG_OUTBOUND_FREE_TAIL 0x010014 /* EMB gets MFA + increment4 */ +/* Put Free Frames */ +#define MSG_OUTBOUND_FREE_HEAD 0x010010 /* DRV puts MFA + increment4 */ +#define MSG_INBOUND_FREE_HEAD 0x010000 /* EMB puts MFA + increment4 */ + +/* firmware addresses of the message fifos */ +#define MSG_BOUND_STACK_SIZE 0x004000 /* size of each following stack */ +/* posted messages */ +#define MSG_OUTBOUND_POST_STACK 0x108000 /* stack of messages to the DRV */ +#define MSG_INBOUND_POST_STACK 0x104000 /* stack of messages to the EMB */ +/* available empty messages */ +#define MSG_OUTBOUND_FREE_STACK 0x10C000 /* stack of free enveloped for EMB */ +#define MSG_INBOUND_FREE_STACK 0x100000 /* stack of free enveloped for DRV */ + + +/* defines for mailbox message frames */ +#define MSG_FRAME_OFFSET 0x64 +#define MSG_FRAME_SIZE 0x6400 +#define MSG_FRAME_NUMBER 32 +#define MSG_FROM_AGENT_ITMF_OFFSET (MSG_FRAME_OFFSET + (MSG_FRAME_SIZE * MSG_FRAME_NUMBER)) +#define MSG_TO_AGENT_ITMF_OFFSET (MSG_FROM_AGENT_ITMF_OFFSET + MSG_FRAME_SIZE) +#define MSG_HOST_RSC_PROTECTION (MSG_TO_AGENT_ITMF_OFFSET + MSG_FRAME_SIZE) +#define MSG_AGENT_RSC_PROTECTION (MSG_HOST_RSC_PROTECTION + 4) + + +/* + * -----------BAR 1 -------------------------------------------------------------------------------------------------------- + */ + +/* interrupt addresses and constants */ +#define MIXART_PCI_OMIMR_OFFSET 0x34 /* outbound message interrupt mask register */ +#define MIXART_PCI_OMISR_OFFSET 0x30 /* outbound message interrupt status register */ +#define MIXART_PCI_ODBR_OFFSET 0x60 /* outbound doorbell register */ + +#define MIXART_BA1_BRUTAL_RESET_OFFSET 0x68 /* write 1 in LSBit to reset board */ + +#define MIXART_HOST_ALL_INTERRUPT_MASKED 0x02B /* 0000 0010 1011 */ +#define MIXART_ALLOW_OUTBOUND_DOORBELL 0x023 /* 0000 0010 0011 */ +#define MIXART_OIDI 0x008 /* 0000 0000 1000 */ + + +int snd_mixart_setup_firmware(struct mixart_mgr *mgr); + +#endif /* __SOUND_MIXART_HWDEP_H */ diff --git a/sound/pci/mixart/mixart_mixer.c b/sound/pci/mixart/mixart_mixer.c new file mode 100644 index 000000000..f4081d323 --- /dev/null +++ b/sound/pci/mixart/mixart_mixer.c @@ -0,0 +1,1190 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram miXart soundcards + * + * mixer callbacks + * + * Copyright (c) 2003 by Digigram + */ + +#include +#include +#include +#include + +#include +#include "mixart.h" +#include "mixart_core.h" +#include "mixart_hwdep.h" +#include +#include +#include "mixart_mixer.h" + +static const u32 mixart_analog_level[256] = { + 0xc2c00000, /* [000] -96.0 dB */ + 0xc2bf0000, /* [001] -95.5 dB */ + 0xc2be0000, /* [002] -95.0 dB */ + 0xc2bd0000, /* [003] -94.5 dB */ + 0xc2bc0000, /* [004] -94.0 dB */ + 0xc2bb0000, /* [005] -93.5 dB */ + 0xc2ba0000, /* [006] -93.0 dB */ + 0xc2b90000, /* [007] -92.5 dB */ + 0xc2b80000, /* [008] -92.0 dB */ + 0xc2b70000, /* [009] -91.5 dB */ + 0xc2b60000, /* [010] -91.0 dB */ + 0xc2b50000, /* [011] -90.5 dB */ + 0xc2b40000, /* [012] -90.0 dB */ + 0xc2b30000, /* [013] -89.5 dB */ + 0xc2b20000, /* [014] -89.0 dB */ + 0xc2b10000, /* [015] -88.5 dB */ + 0xc2b00000, /* [016] -88.0 dB */ + 0xc2af0000, /* [017] -87.5 dB */ + 0xc2ae0000, /* [018] -87.0 dB */ + 0xc2ad0000, /* [019] -86.5 dB */ + 0xc2ac0000, /* [020] -86.0 dB */ + 0xc2ab0000, /* [021] -85.5 dB */ + 0xc2aa0000, /* [022] -85.0 dB */ + 0xc2a90000, /* [023] -84.5 dB */ + 0xc2a80000, /* [024] -84.0 dB */ + 0xc2a70000, /* [025] -83.5 dB */ + 0xc2a60000, /* [026] -83.0 dB */ + 0xc2a50000, /* [027] -82.5 dB */ + 0xc2a40000, /* [028] -82.0 dB */ + 0xc2a30000, /* [029] -81.5 dB */ + 0xc2a20000, /* [030] -81.0 dB */ + 0xc2a10000, /* [031] -80.5 dB */ + 0xc2a00000, /* [032] -80.0 dB */ + 0xc29f0000, /* [033] -79.5 dB */ + 0xc29e0000, /* [034] -79.0 dB */ + 0xc29d0000, /* [035] -78.5 dB */ + 0xc29c0000, /* [036] -78.0 dB */ + 0xc29b0000, /* [037] -77.5 dB */ + 0xc29a0000, /* [038] -77.0 dB */ + 0xc2990000, /* [039] -76.5 dB */ + 0xc2980000, /* [040] -76.0 dB */ + 0xc2970000, /* [041] -75.5 dB */ + 0xc2960000, /* [042] -75.0 dB */ + 0xc2950000, /* [043] -74.5 dB */ + 0xc2940000, /* [044] -74.0 dB */ + 0xc2930000, /* [045] -73.5 dB */ + 0xc2920000, /* [046] -73.0 dB */ + 0xc2910000, /* [047] -72.5 dB */ + 0xc2900000, /* [048] -72.0 dB */ + 0xc28f0000, /* [049] -71.5 dB */ + 0xc28e0000, /* [050] -71.0 dB */ + 0xc28d0000, /* [051] -70.5 dB */ + 0xc28c0000, /* [052] -70.0 dB */ + 0xc28b0000, /* [053] -69.5 dB */ + 0xc28a0000, /* [054] -69.0 dB */ + 0xc2890000, /* [055] -68.5 dB */ + 0xc2880000, /* [056] -68.0 dB */ + 0xc2870000, /* [057] -67.5 dB */ + 0xc2860000, /* [058] -67.0 dB */ + 0xc2850000, /* [059] -66.5 dB */ + 0xc2840000, /* [060] -66.0 dB */ + 0xc2830000, /* [061] -65.5 dB */ + 0xc2820000, /* [062] -65.0 dB */ + 0xc2810000, /* [063] -64.5 dB */ + 0xc2800000, /* [064] -64.0 dB */ + 0xc27e0000, /* [065] -63.5 dB */ + 0xc27c0000, /* [066] -63.0 dB */ + 0xc27a0000, /* [067] -62.5 dB */ + 0xc2780000, /* [068] -62.0 dB */ + 0xc2760000, /* [069] -61.5 dB */ + 0xc2740000, /* [070] -61.0 dB */ + 0xc2720000, /* [071] -60.5 dB */ + 0xc2700000, /* [072] -60.0 dB */ + 0xc26e0000, /* [073] -59.5 dB */ + 0xc26c0000, /* [074] -59.0 dB */ + 0xc26a0000, /* [075] -58.5 dB */ + 0xc2680000, /* [076] -58.0 dB */ + 0xc2660000, /* [077] -57.5 dB */ + 0xc2640000, /* [078] -57.0 dB */ + 0xc2620000, /* [079] -56.5 dB */ + 0xc2600000, /* [080] -56.0 dB */ + 0xc25e0000, /* [081] -55.5 dB */ + 0xc25c0000, /* [082] -55.0 dB */ + 0xc25a0000, /* [083] -54.5 dB */ + 0xc2580000, /* [084] -54.0 dB */ + 0xc2560000, /* [085] -53.5 dB */ + 0xc2540000, /* [086] -53.0 dB */ + 0xc2520000, /* [087] -52.5 dB */ + 0xc2500000, /* [088] -52.0 dB */ + 0xc24e0000, /* [089] -51.5 dB */ + 0xc24c0000, /* [090] -51.0 dB */ + 0xc24a0000, /* [091] -50.5 dB */ + 0xc2480000, /* [092] -50.0 dB */ + 0xc2460000, /* [093] -49.5 dB */ + 0xc2440000, /* [094] -49.0 dB */ + 0xc2420000, /* [095] -48.5 dB */ + 0xc2400000, /* [096] -48.0 dB */ + 0xc23e0000, /* [097] -47.5 dB */ + 0xc23c0000, /* [098] -47.0 dB */ + 0xc23a0000, /* [099] -46.5 dB */ + 0xc2380000, /* [100] -46.0 dB */ + 0xc2360000, /* [101] -45.5 dB */ + 0xc2340000, /* [102] -45.0 dB */ + 0xc2320000, /* [103] -44.5 dB */ + 0xc2300000, /* [104] -44.0 dB */ + 0xc22e0000, /* [105] -43.5 dB */ + 0xc22c0000, /* [106] -43.0 dB */ + 0xc22a0000, /* [107] -42.5 dB */ + 0xc2280000, /* [108] -42.0 dB */ + 0xc2260000, /* [109] -41.5 dB */ + 0xc2240000, /* [110] -41.0 dB */ + 0xc2220000, /* [111] -40.5 dB */ + 0xc2200000, /* [112] -40.0 dB */ + 0xc21e0000, /* [113] -39.5 dB */ + 0xc21c0000, /* [114] -39.0 dB */ + 0xc21a0000, /* [115] -38.5 dB */ + 0xc2180000, /* [116] -38.0 dB */ + 0xc2160000, /* [117] -37.5 dB */ + 0xc2140000, /* [118] -37.0 dB */ + 0xc2120000, /* [119] -36.5 dB */ + 0xc2100000, /* [120] -36.0 dB */ + 0xc20e0000, /* [121] -35.5 dB */ + 0xc20c0000, /* [122] -35.0 dB */ + 0xc20a0000, /* [123] -34.5 dB */ + 0xc2080000, /* [124] -34.0 dB */ + 0xc2060000, /* [125] -33.5 dB */ + 0xc2040000, /* [126] -33.0 dB */ + 0xc2020000, /* [127] -32.5 dB */ + 0xc2000000, /* [128] -32.0 dB */ + 0xc1fc0000, /* [129] -31.5 dB */ + 0xc1f80000, /* [130] -31.0 dB */ + 0xc1f40000, /* [131] -30.5 dB */ + 0xc1f00000, /* [132] -30.0 dB */ + 0xc1ec0000, /* [133] -29.5 dB */ + 0xc1e80000, /* [134] -29.0 dB */ + 0xc1e40000, /* [135] -28.5 dB */ + 0xc1e00000, /* [136] -28.0 dB */ + 0xc1dc0000, /* [137] -27.5 dB */ + 0xc1d80000, /* [138] -27.0 dB */ + 0xc1d40000, /* [139] -26.5 dB */ + 0xc1d00000, /* [140] -26.0 dB */ + 0xc1cc0000, /* [141] -25.5 dB */ + 0xc1c80000, /* [142] -25.0 dB */ + 0xc1c40000, /* [143] -24.5 dB */ + 0xc1c00000, /* [144] -24.0 dB */ + 0xc1bc0000, /* [145] -23.5 dB */ + 0xc1b80000, /* [146] -23.0 dB */ + 0xc1b40000, /* [147] -22.5 dB */ + 0xc1b00000, /* [148] -22.0 dB */ + 0xc1ac0000, /* [149] -21.5 dB */ + 0xc1a80000, /* [150] -21.0 dB */ + 0xc1a40000, /* [151] -20.5 dB */ + 0xc1a00000, /* [152] -20.0 dB */ + 0xc19c0000, /* [153] -19.5 dB */ + 0xc1980000, /* [154] -19.0 dB */ + 0xc1940000, /* [155] -18.5 dB */ + 0xc1900000, /* [156] -18.0 dB */ + 0xc18c0000, /* [157] -17.5 dB */ + 0xc1880000, /* [158] -17.0 dB */ + 0xc1840000, /* [159] -16.5 dB */ + 0xc1800000, /* [160] -16.0 dB */ + 0xc1780000, /* [161] -15.5 dB */ + 0xc1700000, /* [162] -15.0 dB */ + 0xc1680000, /* [163] -14.5 dB */ + 0xc1600000, /* [164] -14.0 dB */ + 0xc1580000, /* [165] -13.5 dB */ + 0xc1500000, /* [166] -13.0 dB */ + 0xc1480000, /* [167] -12.5 dB */ + 0xc1400000, /* [168] -12.0 dB */ + 0xc1380000, /* [169] -11.5 dB */ + 0xc1300000, /* [170] -11.0 dB */ + 0xc1280000, /* [171] -10.5 dB */ + 0xc1200000, /* [172] -10.0 dB */ + 0xc1180000, /* [173] -9.5 dB */ + 0xc1100000, /* [174] -9.0 dB */ + 0xc1080000, /* [175] -8.5 dB */ + 0xc1000000, /* [176] -8.0 dB */ + 0xc0f00000, /* [177] -7.5 dB */ + 0xc0e00000, /* [178] -7.0 dB */ + 0xc0d00000, /* [179] -6.5 dB */ + 0xc0c00000, /* [180] -6.0 dB */ + 0xc0b00000, /* [181] -5.5 dB */ + 0xc0a00000, /* [182] -5.0 dB */ + 0xc0900000, /* [183] -4.5 dB */ + 0xc0800000, /* [184] -4.0 dB */ + 0xc0600000, /* [185] -3.5 dB */ + 0xc0400000, /* [186] -3.0 dB */ + 0xc0200000, /* [187] -2.5 dB */ + 0xc0000000, /* [188] -2.0 dB */ + 0xbfc00000, /* [189] -1.5 dB */ + 0xbf800000, /* [190] -1.0 dB */ + 0xbf000000, /* [191] -0.5 dB */ + 0x00000000, /* [192] 0.0 dB */ + 0x3f000000, /* [193] 0.5 dB */ + 0x3f800000, /* [194] 1.0 dB */ + 0x3fc00000, /* [195] 1.5 dB */ + 0x40000000, /* [196] 2.0 dB */ + 0x40200000, /* [197] 2.5 dB */ + 0x40400000, /* [198] 3.0 dB */ + 0x40600000, /* [199] 3.5 dB */ + 0x40800000, /* [200] 4.0 dB */ + 0x40900000, /* [201] 4.5 dB */ + 0x40a00000, /* [202] 5.0 dB */ + 0x40b00000, /* [203] 5.5 dB */ + 0x40c00000, /* [204] 6.0 dB */ + 0x40d00000, /* [205] 6.5 dB */ + 0x40e00000, /* [206] 7.0 dB */ + 0x40f00000, /* [207] 7.5 dB */ + 0x41000000, /* [208] 8.0 dB */ + 0x41080000, /* [209] 8.5 dB */ + 0x41100000, /* [210] 9.0 dB */ + 0x41180000, /* [211] 9.5 dB */ + 0x41200000, /* [212] 10.0 dB */ + 0x41280000, /* [213] 10.5 dB */ + 0x41300000, /* [214] 11.0 dB */ + 0x41380000, /* [215] 11.5 dB */ + 0x41400000, /* [216] 12.0 dB */ + 0x41480000, /* [217] 12.5 dB */ + 0x41500000, /* [218] 13.0 dB */ + 0x41580000, /* [219] 13.5 dB */ + 0x41600000, /* [220] 14.0 dB */ + 0x41680000, /* [221] 14.5 dB */ + 0x41700000, /* [222] 15.0 dB */ + 0x41780000, /* [223] 15.5 dB */ + 0x41800000, /* [224] 16.0 dB */ + 0x41840000, /* [225] 16.5 dB */ + 0x41880000, /* [226] 17.0 dB */ + 0x418c0000, /* [227] 17.5 dB */ + 0x41900000, /* [228] 18.0 dB */ + 0x41940000, /* [229] 18.5 dB */ + 0x41980000, /* [230] 19.0 dB */ + 0x419c0000, /* [231] 19.5 dB */ + 0x41a00000, /* [232] 20.0 dB */ + 0x41a40000, /* [233] 20.5 dB */ + 0x41a80000, /* [234] 21.0 dB */ + 0x41ac0000, /* [235] 21.5 dB */ + 0x41b00000, /* [236] 22.0 dB */ + 0x41b40000, /* [237] 22.5 dB */ + 0x41b80000, /* [238] 23.0 dB */ + 0x41bc0000, /* [239] 23.5 dB */ + 0x41c00000, /* [240] 24.0 dB */ + 0x41c40000, /* [241] 24.5 dB */ + 0x41c80000, /* [242] 25.0 dB */ + 0x41cc0000, /* [243] 25.5 dB */ + 0x41d00000, /* [244] 26.0 dB */ + 0x41d40000, /* [245] 26.5 dB */ + 0x41d80000, /* [246] 27.0 dB */ + 0x41dc0000, /* [247] 27.5 dB */ + 0x41e00000, /* [248] 28.0 dB */ + 0x41e40000, /* [249] 28.5 dB */ + 0x41e80000, /* [250] 29.0 dB */ + 0x41ec0000, /* [251] 29.5 dB */ + 0x41f00000, /* [252] 30.0 dB */ + 0x41f40000, /* [253] 30.5 dB */ + 0x41f80000, /* [254] 31.0 dB */ + 0x41fc0000, /* [255] 31.5 dB */ +}; + +#define MIXART_ANALOG_CAPTURE_LEVEL_MIN 0 /* -96.0 dB + 8.0 dB = -88.0 dB */ +#define MIXART_ANALOG_CAPTURE_LEVEL_MAX 255 /* 31.5 dB + 8.0 dB = 39.5 dB */ +#define MIXART_ANALOG_CAPTURE_ZERO_LEVEL 176 /* -8.0 dB + 8.0 dB = 0.0 dB */ + +#define MIXART_ANALOG_PLAYBACK_LEVEL_MIN 0 /* -96.0 dB + 1.5 dB = -94.5 dB (possible is down to (-114.0+1.5)dB) */ +#define MIXART_ANALOG_PLAYBACK_LEVEL_MAX 192 /* 0.0 dB + 1.5 dB = 1.5 dB */ +#define MIXART_ANALOG_PLAYBACK_ZERO_LEVEL 189 /* -1.5 dB + 1.5 dB = 0.0 dB */ + +static int mixart_update_analog_audio_level(struct snd_mixart* chip, int is_capture) +{ + int i, err; + struct mixart_msg request; + struct mixart_io_level io_level; + struct mixart_return_uid resp; + + memset(&io_level, 0, sizeof(io_level)); + io_level.channel = -1; /* left and right */ + + for(i=0; i<2; i++) { + if(is_capture) { + io_level.level[i].analog_level = mixart_analog_level[chip->analog_capture_volume[i]]; + } else { + if(chip->analog_playback_active[i]) + io_level.level[i].analog_level = mixart_analog_level[chip->analog_playback_volume[i]]; + else + io_level.level[i].analog_level = mixart_analog_level[MIXART_ANALOG_PLAYBACK_LEVEL_MIN]; + } + } + + if(is_capture) request.uid = chip->uid_in_analog_physio; + else request.uid = chip->uid_out_analog_physio; + request.message_id = MSG_PHYSICALIO_SET_LEVEL; + request.data = &io_level; + request.size = sizeof(io_level); + + err = snd_mixart_send_msg(chip->mgr, &request, sizeof(resp), &resp); + if((err<0) || (resp.error_code)) { + dev_dbg(chip->card->dev, + "error MSG_PHYSICALIO_SET_LEVEL card(%d) is_capture(%d) error_code(%x)\n", + chip->chip_idx, is_capture, resp.error_code); + return -EINVAL; + } + return 0; +} + +/* + * analog level control + */ +static int mixart_analog_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + if(kcontrol->private_value == 0) { /* playback */ + uinfo->value.integer.min = MIXART_ANALOG_PLAYBACK_LEVEL_MIN; /* -96 dB */ + uinfo->value.integer.max = MIXART_ANALOG_PLAYBACK_LEVEL_MAX; /* 0 dB */ + } else { /* capture */ + uinfo->value.integer.min = MIXART_ANALOG_CAPTURE_LEVEL_MIN; /* -96 dB */ + uinfo->value.integer.max = MIXART_ANALOG_CAPTURE_LEVEL_MAX; /* 31.5 dB */ + } + return 0; +} + +static int mixart_analog_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + if(kcontrol->private_value == 0) { /* playback */ + ucontrol->value.integer.value[0] = chip->analog_playback_volume[0]; + ucontrol->value.integer.value[1] = chip->analog_playback_volume[1]; + } else { /* capture */ + ucontrol->value.integer.value[0] = chip->analog_capture_volume[0]; + ucontrol->value.integer.value[1] = chip->analog_capture_volume[1]; + } + return 0; +} + +static int mixart_analog_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int is_capture, i; + + guard(mutex)(&chip->mgr->mixer_mutex); + is_capture = (kcontrol->private_value != 0); + for (i = 0; i < 2; i++) { + int new_volume = ucontrol->value.integer.value[i]; + int *stored_volume = is_capture ? + &chip->analog_capture_volume[i] : + &chip->analog_playback_volume[i]; + if (is_capture) { + if (new_volume < MIXART_ANALOG_CAPTURE_LEVEL_MIN || + new_volume > MIXART_ANALOG_CAPTURE_LEVEL_MAX) + continue; + } else { + if (new_volume < MIXART_ANALOG_PLAYBACK_LEVEL_MIN || + new_volume > MIXART_ANALOG_PLAYBACK_LEVEL_MAX) + continue; + } + if (*stored_volume != new_volume) { + *stored_volume = new_volume; + changed = 1; + } + } + if (changed) + mixart_update_analog_audio_level(chip, is_capture); + return changed; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_analog, -9600, 50, 0); + +static const struct snd_kcontrol_new mixart_control_analog_level = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + /* name will be filled later */ + .info = mixart_analog_vol_info, + .get = mixart_analog_vol_get, + .put = mixart_analog_vol_put, + .tlv = { .p = db_scale_analog }, +}; + +/* shared */ +#define mixart_sw_info snd_ctl_boolean_stereo_info + +static int mixart_audio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->analog_playback_active[0]; + ucontrol->value.integer.value[1] = chip->analog_playback_active[1]; + return 0; +} + +static int mixart_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + int i, changed = 0; + + guard(mutex)(&chip->mgr->mixer_mutex); + for (i = 0; i < 2; i++) { + if (chip->analog_playback_active[i] != + ucontrol->value.integer.value[i]) { + chip->analog_playback_active[i] = + !!ucontrol->value.integer.value[i]; + changed = 1; + } + } + if (changed) /* update playback levels */ + mixart_update_analog_audio_level(chip, 0); + return changed; +} + +static const struct snd_kcontrol_new mixart_control_output_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = mixart_sw_info, /* shared */ + .get = mixart_audio_sw_get, + .put = mixart_audio_sw_put +}; + +static const u32 mixart_digital_level[256] = { + 0x00000000, /* [000] = 0.00e+000 = mute if <= -109.5dB */ + 0x366e1c7a, /* [001] = 3.55e-006 = pow(10.0, 0.05 * -109.0dB) */ + 0x367c3860, /* [002] = 3.76e-006 = pow(10.0, 0.05 * -108.5dB) */ + 0x36859525, /* [003] = 3.98e-006 = pow(10.0, 0.05 * -108.0dB) */ + 0x368d7f74, /* [004] = 4.22e-006 = pow(10.0, 0.05 * -107.5dB) */ + 0x3695e1d4, /* [005] = 4.47e-006 = pow(10.0, 0.05 * -107.0dB) */ + 0x369ec362, /* [006] = 4.73e-006 = pow(10.0, 0.05 * -106.5dB) */ + 0x36a82ba8, /* [007] = 5.01e-006 = pow(10.0, 0.05 * -106.0dB) */ + 0x36b222a0, /* [008] = 5.31e-006 = pow(10.0, 0.05 * -105.5dB) */ + 0x36bcb0c1, /* [009] = 5.62e-006 = pow(10.0, 0.05 * -105.0dB) */ + 0x36c7defd, /* [010] = 5.96e-006 = pow(10.0, 0.05 * -104.5dB) */ + 0x36d3b6d3, /* [011] = 6.31e-006 = pow(10.0, 0.05 * -104.0dB) */ + 0x36e0424e, /* [012] = 6.68e-006 = pow(10.0, 0.05 * -103.5dB) */ + 0x36ed8c14, /* [013] = 7.08e-006 = pow(10.0, 0.05 * -103.0dB) */ + 0x36fb9f6c, /* [014] = 7.50e-006 = pow(10.0, 0.05 * -102.5dB) */ + 0x37054423, /* [015] = 7.94e-006 = pow(10.0, 0.05 * -102.0dB) */ + 0x370d29a5, /* [016] = 8.41e-006 = pow(10.0, 0.05 * -101.5dB) */ + 0x371586f0, /* [017] = 8.91e-006 = pow(10.0, 0.05 * -101.0dB) */ + 0x371e631b, /* [018] = 9.44e-006 = pow(10.0, 0.05 * -100.5dB) */ + 0x3727c5ac, /* [019] = 1.00e-005 = pow(10.0, 0.05 * -100.0dB) */ + 0x3731b69a, /* [020] = 1.06e-005 = pow(10.0, 0.05 * -99.5dB) */ + 0x373c3e53, /* [021] = 1.12e-005 = pow(10.0, 0.05 * -99.0dB) */ + 0x374765c8, /* [022] = 1.19e-005 = pow(10.0, 0.05 * -98.5dB) */ + 0x3753366f, /* [023] = 1.26e-005 = pow(10.0, 0.05 * -98.0dB) */ + 0x375fba4f, /* [024] = 1.33e-005 = pow(10.0, 0.05 * -97.5dB) */ + 0x376cfc07, /* [025] = 1.41e-005 = pow(10.0, 0.05 * -97.0dB) */ + 0x377b06d5, /* [026] = 1.50e-005 = pow(10.0, 0.05 * -96.5dB) */ + 0x3784f352, /* [027] = 1.58e-005 = pow(10.0, 0.05 * -96.0dB) */ + 0x378cd40b, /* [028] = 1.68e-005 = pow(10.0, 0.05 * -95.5dB) */ + 0x37952c42, /* [029] = 1.78e-005 = pow(10.0, 0.05 * -95.0dB) */ + 0x379e030e, /* [030] = 1.88e-005 = pow(10.0, 0.05 * -94.5dB) */ + 0x37a75fef, /* [031] = 2.00e-005 = pow(10.0, 0.05 * -94.0dB) */ + 0x37b14ad5, /* [032] = 2.11e-005 = pow(10.0, 0.05 * -93.5dB) */ + 0x37bbcc2c, /* [033] = 2.24e-005 = pow(10.0, 0.05 * -93.0dB) */ + 0x37c6ecdd, /* [034] = 2.37e-005 = pow(10.0, 0.05 * -92.5dB) */ + 0x37d2b65a, /* [035] = 2.51e-005 = pow(10.0, 0.05 * -92.0dB) */ + 0x37df32a3, /* [036] = 2.66e-005 = pow(10.0, 0.05 * -91.5dB) */ + 0x37ec6c50, /* [037] = 2.82e-005 = pow(10.0, 0.05 * -91.0dB) */ + 0x37fa6e9b, /* [038] = 2.99e-005 = pow(10.0, 0.05 * -90.5dB) */ + 0x3804a2b3, /* [039] = 3.16e-005 = pow(10.0, 0.05 * -90.0dB) */ + 0x380c7ea4, /* [040] = 3.35e-005 = pow(10.0, 0.05 * -89.5dB) */ + 0x3814d1cc, /* [041] = 3.55e-005 = pow(10.0, 0.05 * -89.0dB) */ + 0x381da33c, /* [042] = 3.76e-005 = pow(10.0, 0.05 * -88.5dB) */ + 0x3826fa6f, /* [043] = 3.98e-005 = pow(10.0, 0.05 * -88.0dB) */ + 0x3830df51, /* [044] = 4.22e-005 = pow(10.0, 0.05 * -87.5dB) */ + 0x383b5a49, /* [045] = 4.47e-005 = pow(10.0, 0.05 * -87.0dB) */ + 0x3846743b, /* [046] = 4.73e-005 = pow(10.0, 0.05 * -86.5dB) */ + 0x38523692, /* [047] = 5.01e-005 = pow(10.0, 0.05 * -86.0dB) */ + 0x385eab48, /* [048] = 5.31e-005 = pow(10.0, 0.05 * -85.5dB) */ + 0x386bdcf1, /* [049] = 5.62e-005 = pow(10.0, 0.05 * -85.0dB) */ + 0x3879d6bc, /* [050] = 5.96e-005 = pow(10.0, 0.05 * -84.5dB) */ + 0x38845244, /* [051] = 6.31e-005 = pow(10.0, 0.05 * -84.0dB) */ + 0x388c2971, /* [052] = 6.68e-005 = pow(10.0, 0.05 * -83.5dB) */ + 0x3894778d, /* [053] = 7.08e-005 = pow(10.0, 0.05 * -83.0dB) */ + 0x389d43a4, /* [054] = 7.50e-005 = pow(10.0, 0.05 * -82.5dB) */ + 0x38a6952c, /* [055] = 7.94e-005 = pow(10.0, 0.05 * -82.0dB) */ + 0x38b0740f, /* [056] = 8.41e-005 = pow(10.0, 0.05 * -81.5dB) */ + 0x38bae8ac, /* [057] = 8.91e-005 = pow(10.0, 0.05 * -81.0dB) */ + 0x38c5fbe2, /* [058] = 9.44e-005 = pow(10.0, 0.05 * -80.5dB) */ + 0x38d1b717, /* [059] = 1.00e-004 = pow(10.0, 0.05 * -80.0dB) */ + 0x38de2440, /* [060] = 1.06e-004 = pow(10.0, 0.05 * -79.5dB) */ + 0x38eb4de8, /* [061] = 1.12e-004 = pow(10.0, 0.05 * -79.0dB) */ + 0x38f93f3a, /* [062] = 1.19e-004 = pow(10.0, 0.05 * -78.5dB) */ + 0x39040206, /* [063] = 1.26e-004 = pow(10.0, 0.05 * -78.0dB) */ + 0x390bd472, /* [064] = 1.33e-004 = pow(10.0, 0.05 * -77.5dB) */ + 0x39141d84, /* [065] = 1.41e-004 = pow(10.0, 0.05 * -77.0dB) */ + 0x391ce445, /* [066] = 1.50e-004 = pow(10.0, 0.05 * -76.5dB) */ + 0x39263027, /* [067] = 1.58e-004 = pow(10.0, 0.05 * -76.0dB) */ + 0x3930090d, /* [068] = 1.68e-004 = pow(10.0, 0.05 * -75.5dB) */ + 0x393a7753, /* [069] = 1.78e-004 = pow(10.0, 0.05 * -75.0dB) */ + 0x394583d2, /* [070] = 1.88e-004 = pow(10.0, 0.05 * -74.5dB) */ + 0x395137ea, /* [071] = 2.00e-004 = pow(10.0, 0.05 * -74.0dB) */ + 0x395d9d8a, /* [072] = 2.11e-004 = pow(10.0, 0.05 * -73.5dB) */ + 0x396abf37, /* [073] = 2.24e-004 = pow(10.0, 0.05 * -73.0dB) */ + 0x3978a814, /* [074] = 2.37e-004 = pow(10.0, 0.05 * -72.5dB) */ + 0x3983b1f8, /* [075] = 2.51e-004 = pow(10.0, 0.05 * -72.0dB) */ + 0x398b7fa6, /* [076] = 2.66e-004 = pow(10.0, 0.05 * -71.5dB) */ + 0x3993c3b2, /* [077] = 2.82e-004 = pow(10.0, 0.05 * -71.0dB) */ + 0x399c8521, /* [078] = 2.99e-004 = pow(10.0, 0.05 * -70.5dB) */ + 0x39a5cb5f, /* [079] = 3.16e-004 = pow(10.0, 0.05 * -70.0dB) */ + 0x39af9e4d, /* [080] = 3.35e-004 = pow(10.0, 0.05 * -69.5dB) */ + 0x39ba063f, /* [081] = 3.55e-004 = pow(10.0, 0.05 * -69.0dB) */ + 0x39c50c0b, /* [082] = 3.76e-004 = pow(10.0, 0.05 * -68.5dB) */ + 0x39d0b90a, /* [083] = 3.98e-004 = pow(10.0, 0.05 * -68.0dB) */ + 0x39dd1726, /* [084] = 4.22e-004 = pow(10.0, 0.05 * -67.5dB) */ + 0x39ea30db, /* [085] = 4.47e-004 = pow(10.0, 0.05 * -67.0dB) */ + 0x39f81149, /* [086] = 4.73e-004 = pow(10.0, 0.05 * -66.5dB) */ + 0x3a03621b, /* [087] = 5.01e-004 = pow(10.0, 0.05 * -66.0dB) */ + 0x3a0b2b0d, /* [088] = 5.31e-004 = pow(10.0, 0.05 * -65.5dB) */ + 0x3a136a16, /* [089] = 5.62e-004 = pow(10.0, 0.05 * -65.0dB) */ + 0x3a1c2636, /* [090] = 5.96e-004 = pow(10.0, 0.05 * -64.5dB) */ + 0x3a2566d5, /* [091] = 6.31e-004 = pow(10.0, 0.05 * -64.0dB) */ + 0x3a2f33cd, /* [092] = 6.68e-004 = pow(10.0, 0.05 * -63.5dB) */ + 0x3a399570, /* [093] = 7.08e-004 = pow(10.0, 0.05 * -63.0dB) */ + 0x3a44948c, /* [094] = 7.50e-004 = pow(10.0, 0.05 * -62.5dB) */ + 0x3a503a77, /* [095] = 7.94e-004 = pow(10.0, 0.05 * -62.0dB) */ + 0x3a5c9112, /* [096] = 8.41e-004 = pow(10.0, 0.05 * -61.5dB) */ + 0x3a69a2d7, /* [097] = 8.91e-004 = pow(10.0, 0.05 * -61.0dB) */ + 0x3a777ada, /* [098] = 9.44e-004 = pow(10.0, 0.05 * -60.5dB) */ + 0x3a83126f, /* [099] = 1.00e-003 = pow(10.0, 0.05 * -60.0dB) */ + 0x3a8ad6a8, /* [100] = 1.06e-003 = pow(10.0, 0.05 * -59.5dB) */ + 0x3a9310b1, /* [101] = 1.12e-003 = pow(10.0, 0.05 * -59.0dB) */ + 0x3a9bc784, /* [102] = 1.19e-003 = pow(10.0, 0.05 * -58.5dB) */ + 0x3aa50287, /* [103] = 1.26e-003 = pow(10.0, 0.05 * -58.0dB) */ + 0x3aaec98e, /* [104] = 1.33e-003 = pow(10.0, 0.05 * -57.5dB) */ + 0x3ab924e5, /* [105] = 1.41e-003 = pow(10.0, 0.05 * -57.0dB) */ + 0x3ac41d56, /* [106] = 1.50e-003 = pow(10.0, 0.05 * -56.5dB) */ + 0x3acfbc31, /* [107] = 1.58e-003 = pow(10.0, 0.05 * -56.0dB) */ + 0x3adc0b51, /* [108] = 1.68e-003 = pow(10.0, 0.05 * -55.5dB) */ + 0x3ae91528, /* [109] = 1.78e-003 = pow(10.0, 0.05 * -55.0dB) */ + 0x3af6e4c6, /* [110] = 1.88e-003 = pow(10.0, 0.05 * -54.5dB) */ + 0x3b02c2f2, /* [111] = 2.00e-003 = pow(10.0, 0.05 * -54.0dB) */ + 0x3b0a8276, /* [112] = 2.11e-003 = pow(10.0, 0.05 * -53.5dB) */ + 0x3b12b782, /* [113] = 2.24e-003 = pow(10.0, 0.05 * -53.0dB) */ + 0x3b1b690d, /* [114] = 2.37e-003 = pow(10.0, 0.05 * -52.5dB) */ + 0x3b249e76, /* [115] = 2.51e-003 = pow(10.0, 0.05 * -52.0dB) */ + 0x3b2e5f8f, /* [116] = 2.66e-003 = pow(10.0, 0.05 * -51.5dB) */ + 0x3b38b49f, /* [117] = 2.82e-003 = pow(10.0, 0.05 * -51.0dB) */ + 0x3b43a669, /* [118] = 2.99e-003 = pow(10.0, 0.05 * -50.5dB) */ + 0x3b4f3e37, /* [119] = 3.16e-003 = pow(10.0, 0.05 * -50.0dB) */ + 0x3b5b85e0, /* [120] = 3.35e-003 = pow(10.0, 0.05 * -49.5dB) */ + 0x3b6887cf, /* [121] = 3.55e-003 = pow(10.0, 0.05 * -49.0dB) */ + 0x3b764f0e, /* [122] = 3.76e-003 = pow(10.0, 0.05 * -48.5dB) */ + 0x3b8273a6, /* [123] = 3.98e-003 = pow(10.0, 0.05 * -48.0dB) */ + 0x3b8a2e77, /* [124] = 4.22e-003 = pow(10.0, 0.05 * -47.5dB) */ + 0x3b925e89, /* [125] = 4.47e-003 = pow(10.0, 0.05 * -47.0dB) */ + 0x3b9b0ace, /* [126] = 4.73e-003 = pow(10.0, 0.05 * -46.5dB) */ + 0x3ba43aa2, /* [127] = 5.01e-003 = pow(10.0, 0.05 * -46.0dB) */ + 0x3badf5d1, /* [128] = 5.31e-003 = pow(10.0, 0.05 * -45.5dB) */ + 0x3bb8449c, /* [129] = 5.62e-003 = pow(10.0, 0.05 * -45.0dB) */ + 0x3bc32fc3, /* [130] = 5.96e-003 = pow(10.0, 0.05 * -44.5dB) */ + 0x3bcec08a, /* [131] = 6.31e-003 = pow(10.0, 0.05 * -44.0dB) */ + 0x3bdb00c0, /* [132] = 6.68e-003 = pow(10.0, 0.05 * -43.5dB) */ + 0x3be7facc, /* [133] = 7.08e-003 = pow(10.0, 0.05 * -43.0dB) */ + 0x3bf5b9b0, /* [134] = 7.50e-003 = pow(10.0, 0.05 * -42.5dB) */ + 0x3c02248a, /* [135] = 7.94e-003 = pow(10.0, 0.05 * -42.0dB) */ + 0x3c09daac, /* [136] = 8.41e-003 = pow(10.0, 0.05 * -41.5dB) */ + 0x3c1205c6, /* [137] = 8.91e-003 = pow(10.0, 0.05 * -41.0dB) */ + 0x3c1aacc8, /* [138] = 9.44e-003 = pow(10.0, 0.05 * -40.5dB) */ + 0x3c23d70a, /* [139] = 1.00e-002 = pow(10.0, 0.05 * -40.0dB) */ + 0x3c2d8c52, /* [140] = 1.06e-002 = pow(10.0, 0.05 * -39.5dB) */ + 0x3c37d4dd, /* [141] = 1.12e-002 = pow(10.0, 0.05 * -39.0dB) */ + 0x3c42b965, /* [142] = 1.19e-002 = pow(10.0, 0.05 * -38.5dB) */ + 0x3c4e4329, /* [143] = 1.26e-002 = pow(10.0, 0.05 * -38.0dB) */ + 0x3c5a7bf1, /* [144] = 1.33e-002 = pow(10.0, 0.05 * -37.5dB) */ + 0x3c676e1e, /* [145] = 1.41e-002 = pow(10.0, 0.05 * -37.0dB) */ + 0x3c7524ac, /* [146] = 1.50e-002 = pow(10.0, 0.05 * -36.5dB) */ + 0x3c81d59f, /* [147] = 1.58e-002 = pow(10.0, 0.05 * -36.0dB) */ + 0x3c898712, /* [148] = 1.68e-002 = pow(10.0, 0.05 * -35.5dB) */ + 0x3c91ad39, /* [149] = 1.78e-002 = pow(10.0, 0.05 * -35.0dB) */ + 0x3c9a4efc, /* [150] = 1.88e-002 = pow(10.0, 0.05 * -34.5dB) */ + 0x3ca373af, /* [151] = 2.00e-002 = pow(10.0, 0.05 * -34.0dB) */ + 0x3cad2314, /* [152] = 2.11e-002 = pow(10.0, 0.05 * -33.5dB) */ + 0x3cb76563, /* [153] = 2.24e-002 = pow(10.0, 0.05 * -33.0dB) */ + 0x3cc24350, /* [154] = 2.37e-002 = pow(10.0, 0.05 * -32.5dB) */ + 0x3ccdc614, /* [155] = 2.51e-002 = pow(10.0, 0.05 * -32.0dB) */ + 0x3cd9f773, /* [156] = 2.66e-002 = pow(10.0, 0.05 * -31.5dB) */ + 0x3ce6e1c6, /* [157] = 2.82e-002 = pow(10.0, 0.05 * -31.0dB) */ + 0x3cf49003, /* [158] = 2.99e-002 = pow(10.0, 0.05 * -30.5dB) */ + 0x3d0186e2, /* [159] = 3.16e-002 = pow(10.0, 0.05 * -30.0dB) */ + 0x3d0933ac, /* [160] = 3.35e-002 = pow(10.0, 0.05 * -29.5dB) */ + 0x3d1154e1, /* [161] = 3.55e-002 = pow(10.0, 0.05 * -29.0dB) */ + 0x3d19f169, /* [162] = 3.76e-002 = pow(10.0, 0.05 * -28.5dB) */ + 0x3d231090, /* [163] = 3.98e-002 = pow(10.0, 0.05 * -28.0dB) */ + 0x3d2cba15, /* [164] = 4.22e-002 = pow(10.0, 0.05 * -27.5dB) */ + 0x3d36f62b, /* [165] = 4.47e-002 = pow(10.0, 0.05 * -27.0dB) */ + 0x3d41cd81, /* [166] = 4.73e-002 = pow(10.0, 0.05 * -26.5dB) */ + 0x3d4d494a, /* [167] = 5.01e-002 = pow(10.0, 0.05 * -26.0dB) */ + 0x3d597345, /* [168] = 5.31e-002 = pow(10.0, 0.05 * -25.5dB) */ + 0x3d6655c3, /* [169] = 5.62e-002 = pow(10.0, 0.05 * -25.0dB) */ + 0x3d73fbb4, /* [170] = 5.96e-002 = pow(10.0, 0.05 * -24.5dB) */ + 0x3d813856, /* [171] = 6.31e-002 = pow(10.0, 0.05 * -24.0dB) */ + 0x3d88e078, /* [172] = 6.68e-002 = pow(10.0, 0.05 * -23.5dB) */ + 0x3d90fcbf, /* [173] = 7.08e-002 = pow(10.0, 0.05 * -23.0dB) */ + 0x3d99940e, /* [174] = 7.50e-002 = pow(10.0, 0.05 * -22.5dB) */ + 0x3da2adad, /* [175] = 7.94e-002 = pow(10.0, 0.05 * -22.0dB) */ + 0x3dac5156, /* [176] = 8.41e-002 = pow(10.0, 0.05 * -21.5dB) */ + 0x3db68738, /* [177] = 8.91e-002 = pow(10.0, 0.05 * -21.0dB) */ + 0x3dc157fb, /* [178] = 9.44e-002 = pow(10.0, 0.05 * -20.5dB) */ + 0x3dcccccd, /* [179] = 1.00e-001 = pow(10.0, 0.05 * -20.0dB) */ + 0x3dd8ef67, /* [180] = 1.06e-001 = pow(10.0, 0.05 * -19.5dB) */ + 0x3de5ca15, /* [181] = 1.12e-001 = pow(10.0, 0.05 * -19.0dB) */ + 0x3df367bf, /* [182] = 1.19e-001 = pow(10.0, 0.05 * -18.5dB) */ + 0x3e00e9f9, /* [183] = 1.26e-001 = pow(10.0, 0.05 * -18.0dB) */ + 0x3e088d77, /* [184] = 1.33e-001 = pow(10.0, 0.05 * -17.5dB) */ + 0x3e10a4d3, /* [185] = 1.41e-001 = pow(10.0, 0.05 * -17.0dB) */ + 0x3e1936ec, /* [186] = 1.50e-001 = pow(10.0, 0.05 * -16.5dB) */ + 0x3e224b06, /* [187] = 1.58e-001 = pow(10.0, 0.05 * -16.0dB) */ + 0x3e2be8d7, /* [188] = 1.68e-001 = pow(10.0, 0.05 * -15.5dB) */ + 0x3e361887, /* [189] = 1.78e-001 = pow(10.0, 0.05 * -15.0dB) */ + 0x3e40e2bb, /* [190] = 1.88e-001 = pow(10.0, 0.05 * -14.5dB) */ + 0x3e4c509b, /* [191] = 2.00e-001 = pow(10.0, 0.05 * -14.0dB) */ + 0x3e586bd9, /* [192] = 2.11e-001 = pow(10.0, 0.05 * -13.5dB) */ + 0x3e653ebb, /* [193] = 2.24e-001 = pow(10.0, 0.05 * -13.0dB) */ + 0x3e72d424, /* [194] = 2.37e-001 = pow(10.0, 0.05 * -12.5dB) */ + 0x3e809bcc, /* [195] = 2.51e-001 = pow(10.0, 0.05 * -12.0dB) */ + 0x3e883aa8, /* [196] = 2.66e-001 = pow(10.0, 0.05 * -11.5dB) */ + 0x3e904d1c, /* [197] = 2.82e-001 = pow(10.0, 0.05 * -11.0dB) */ + 0x3e98da02, /* [198] = 2.99e-001 = pow(10.0, 0.05 * -10.5dB) */ + 0x3ea1e89b, /* [199] = 3.16e-001 = pow(10.0, 0.05 * -10.0dB) */ + 0x3eab8097, /* [200] = 3.35e-001 = pow(10.0, 0.05 * -9.5dB) */ + 0x3eb5aa1a, /* [201] = 3.55e-001 = pow(10.0, 0.05 * -9.0dB) */ + 0x3ec06dc3, /* [202] = 3.76e-001 = pow(10.0, 0.05 * -8.5dB) */ + 0x3ecbd4b4, /* [203] = 3.98e-001 = pow(10.0, 0.05 * -8.0dB) */ + 0x3ed7e89b, /* [204] = 4.22e-001 = pow(10.0, 0.05 * -7.5dB) */ + 0x3ee4b3b6, /* [205] = 4.47e-001 = pow(10.0, 0.05 * -7.0dB) */ + 0x3ef240e2, /* [206] = 4.73e-001 = pow(10.0, 0.05 * -6.5dB) */ + 0x3f004dce, /* [207] = 5.01e-001 = pow(10.0, 0.05 * -6.0dB) */ + 0x3f07e80b, /* [208] = 5.31e-001 = pow(10.0, 0.05 * -5.5dB) */ + 0x3f0ff59a, /* [209] = 5.62e-001 = pow(10.0, 0.05 * -5.0dB) */ + 0x3f187d50, /* [210] = 5.96e-001 = pow(10.0, 0.05 * -4.5dB) */ + 0x3f21866c, /* [211] = 6.31e-001 = pow(10.0, 0.05 * -4.0dB) */ + 0x3f2b1896, /* [212] = 6.68e-001 = pow(10.0, 0.05 * -3.5dB) */ + 0x3f353bef, /* [213] = 7.08e-001 = pow(10.0, 0.05 * -3.0dB) */ + 0x3f3ff911, /* [214] = 7.50e-001 = pow(10.0, 0.05 * -2.5dB) */ + 0x3f4b5918, /* [215] = 7.94e-001 = pow(10.0, 0.05 * -2.0dB) */ + 0x3f5765ac, /* [216] = 8.41e-001 = pow(10.0, 0.05 * -1.5dB) */ + 0x3f642905, /* [217] = 8.91e-001 = pow(10.0, 0.05 * -1.0dB) */ + 0x3f71adf9, /* [218] = 9.44e-001 = pow(10.0, 0.05 * -0.5dB) */ + 0x3f800000, /* [219] = 1.00e+000 = pow(10.0, 0.05 * 0.0dB) */ + 0x3f8795a0, /* [220] = 1.06e+000 = pow(10.0, 0.05 * 0.5dB) */ + 0x3f8f9e4d, /* [221] = 1.12e+000 = pow(10.0, 0.05 * 1.0dB) */ + 0x3f9820d7, /* [222] = 1.19e+000 = pow(10.0, 0.05 * 1.5dB) */ + 0x3fa12478, /* [223] = 1.26e+000 = pow(10.0, 0.05 * 2.0dB) */ + 0x3faab0d5, /* [224] = 1.33e+000 = pow(10.0, 0.05 * 2.5dB) */ + 0x3fb4ce08, /* [225] = 1.41e+000 = pow(10.0, 0.05 * 3.0dB) */ + 0x3fbf84a6, /* [226] = 1.50e+000 = pow(10.0, 0.05 * 3.5dB) */ + 0x3fcaddc8, /* [227] = 1.58e+000 = pow(10.0, 0.05 * 4.0dB) */ + 0x3fd6e30d, /* [228] = 1.68e+000 = pow(10.0, 0.05 * 4.5dB) */ + 0x3fe39ea9, /* [229] = 1.78e+000 = pow(10.0, 0.05 * 5.0dB) */ + 0x3ff11b6a, /* [230] = 1.88e+000 = pow(10.0, 0.05 * 5.5dB) */ + 0x3fff64c1, /* [231] = 2.00e+000 = pow(10.0, 0.05 * 6.0dB) */ + 0x40074368, /* [232] = 2.11e+000 = pow(10.0, 0.05 * 6.5dB) */ + 0x400f4735, /* [233] = 2.24e+000 = pow(10.0, 0.05 * 7.0dB) */ + 0x4017c496, /* [234] = 2.37e+000 = pow(10.0, 0.05 * 7.5dB) */ + 0x4020c2bf, /* [235] = 2.51e+000 = pow(10.0, 0.05 * 8.0dB) */ + 0x402a4952, /* [236] = 2.66e+000 = pow(10.0, 0.05 * 8.5dB) */ + 0x40346063, /* [237] = 2.82e+000 = pow(10.0, 0.05 * 9.0dB) */ + 0x403f1082, /* [238] = 2.99e+000 = pow(10.0, 0.05 * 9.5dB) */ + 0x404a62c2, /* [239] = 3.16e+000 = pow(10.0, 0.05 * 10.0dB) */ + 0x405660bd, /* [240] = 3.35e+000 = pow(10.0, 0.05 * 10.5dB) */ + 0x406314a0, /* [241] = 3.55e+000 = pow(10.0, 0.05 * 11.0dB) */ + 0x40708933, /* [242] = 3.76e+000 = pow(10.0, 0.05 * 11.5dB) */ + 0x407ec9e1, /* [243] = 3.98e+000 = pow(10.0, 0.05 * 12.0dB) */ + 0x4086f161, /* [244] = 4.22e+000 = pow(10.0, 0.05 * 12.5dB) */ + 0x408ef052, /* [245] = 4.47e+000 = pow(10.0, 0.05 * 13.0dB) */ + 0x4097688d, /* [246] = 4.73e+000 = pow(10.0, 0.05 * 13.5dB) */ + 0x40a06142, /* [247] = 5.01e+000 = pow(10.0, 0.05 * 14.0dB) */ + 0x40a9e20e, /* [248] = 5.31e+000 = pow(10.0, 0.05 * 14.5dB) */ + 0x40b3f300, /* [249] = 5.62e+000 = pow(10.0, 0.05 * 15.0dB) */ + 0x40be9ca5, /* [250] = 5.96e+000 = pow(10.0, 0.05 * 15.5dB) */ + 0x40c9e807, /* [251] = 6.31e+000 = pow(10.0, 0.05 * 16.0dB) */ + 0x40d5debc, /* [252] = 6.68e+000 = pow(10.0, 0.05 * 16.5dB) */ + 0x40e28aeb, /* [253] = 7.08e+000 = pow(10.0, 0.05 * 17.0dB) */ + 0x40eff755, /* [254] = 7.50e+000 = pow(10.0, 0.05 * 17.5dB) */ + 0x40fe2f5e, /* [255] = 7.94e+000 = pow(10.0, 0.05 * 18.0dB) */ +}; + +#define MIXART_DIGITAL_LEVEL_MIN 0 /* -109.5 dB */ +#define MIXART_DIGITAL_LEVEL_MAX 255 /* 18.0 dB */ +#define MIXART_DIGITAL_ZERO_LEVEL 219 /* 0.0 dB */ + + +int mixart_update_playback_stream_level(struct snd_mixart* chip, int is_aes, int idx) +{ + int err, i; + int volume[2]; + struct mixart_msg request; + struct mixart_set_out_stream_level_req set_level; + u32 status = 0; + struct mixart_pipe *pipe; + + memset(&set_level, 0, sizeof(set_level)); + set_level.nb_of_stream = 1; + set_level.stream_level.desc.stream_idx = idx; + + if(is_aes) { + pipe = &chip->pipe_out_dig; /* AES playback */ + idx += MIXART_PLAYBACK_STREAMS; + } else { + pipe = &chip->pipe_out_ana; /* analog playback */ + } + + /* only when pipe exists ! */ + if(pipe->status == PIPE_UNDEFINED) + return 0; + + set_level.stream_level.desc.uid_pipe = pipe->group_uid; + + for(i=0; i<2; i++) { + if(chip->digital_playback_active[idx][i]) + volume[i] = chip->digital_playback_volume[idx][i]; + else + volume[i] = MIXART_DIGITAL_LEVEL_MIN; + } + + set_level.stream_level.out_level.valid_mask1 = MIXART_OUT_STREAM_SET_LEVEL_LEFT_AUDIO1 | MIXART_OUT_STREAM_SET_LEVEL_RIGHT_AUDIO2; + set_level.stream_level.out_level.left_to_out1_level = mixart_digital_level[volume[0]]; + set_level.stream_level.out_level.right_to_out2_level = mixart_digital_level[volume[1]]; + + request.message_id = MSG_STREAM_SET_OUT_STREAM_LEVEL; + request.uid = (struct mixart_uid){0,0}; + request.data = &set_level; + request.size = sizeof(set_level); + + err = snd_mixart_send_msg(chip->mgr, &request, sizeof(status), &status); + if((err<0) || status) { + dev_dbg(chip->card->dev, + "error MSG_STREAM_SET_OUT_STREAM_LEVEL card(%d) status(%x)\n", + chip->chip_idx, status); + return -EINVAL; + } + return 0; +} + +int mixart_update_capture_stream_level(struct snd_mixart* chip, int is_aes) +{ + int err, i, idx; + struct mixart_pipe *pipe; + struct mixart_msg request; + struct mixart_set_in_audio_level_req set_level; + u32 status = 0; + + if(is_aes) { + idx = 1; + pipe = &chip->pipe_in_dig; + } else { + idx = 0; + pipe = &chip->pipe_in_ana; + } + + /* only when pipe exists ! */ + if(pipe->status == PIPE_UNDEFINED) + return 0; + + memset(&set_level, 0, sizeof(set_level)); + set_level.audio_count = 2; + set_level.level[0].connector = pipe->uid_left_connector; + set_level.level[1].connector = pipe->uid_right_connector; + + for(i=0; i<2; i++) { + set_level.level[i].valid_mask1 = MIXART_AUDIO_LEVEL_DIGITAL_MASK; + set_level.level[i].digital_level = mixart_digital_level[chip->digital_capture_volume[idx][i]]; + } + + request.message_id = MSG_STREAM_SET_IN_AUDIO_LEVEL; + request.uid = (struct mixart_uid){0,0}; + request.data = &set_level; + request.size = sizeof(set_level); + + err = snd_mixart_send_msg(chip->mgr, &request, sizeof(status), &status); + if((err<0) || status) { + dev_dbg(chip->card->dev, + "error MSG_STREAM_SET_IN_AUDIO_LEVEL card(%d) status(%x)\n", + chip->chip_idx, status); + return -EINVAL; + } + return 0; +} + + +/* shared */ +static int mixart_digital_vol_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 = MIXART_DIGITAL_LEVEL_MIN; /* -109.5 dB */ + uinfo->value.integer.max = MIXART_DIGITAL_LEVEL_MAX; /* 18.0 dB */ + return 0; +} + +#define MIXART_VOL_REC_MASK 1 +#define MIXART_VOL_AES_MASK 2 + +static int mixart_pcm_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + int *stored_volume; + int is_capture = kcontrol->private_value & MIXART_VOL_REC_MASK; + int is_aes = kcontrol->private_value & MIXART_VOL_AES_MASK; + + guard(mutex)(&chip->mgr->mixer_mutex); + if(is_capture) { + if(is_aes) stored_volume = chip->digital_capture_volume[1]; /* AES capture */ + else stored_volume = chip->digital_capture_volume[0]; /* analog capture */ + } else { + snd_BUG_ON(idx >= MIXART_PLAYBACK_STREAMS); + if(is_aes) stored_volume = chip->digital_playback_volume[MIXART_PLAYBACK_STREAMS + idx]; /* AES playback */ + else stored_volume = chip->digital_playback_volume[idx]; /* analog playback */ + } + ucontrol->value.integer.value[0] = stored_volume[0]; + ucontrol->value.integer.value[1] = stored_volume[1]; + return 0; +} + +static int mixart_pcm_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + int changed = 0; + int is_capture = kcontrol->private_value & MIXART_VOL_REC_MASK; + int is_aes = kcontrol->private_value & MIXART_VOL_AES_MASK; + int* stored_volume; + int i; + + guard(mutex)(&chip->mgr->mixer_mutex); + if (is_capture) { + if (is_aes) /* AES capture */ + stored_volume = chip->digital_capture_volume[1]; + else /* analog capture */ + stored_volume = chip->digital_capture_volume[0]; + } else { + snd_BUG_ON(idx >= MIXART_PLAYBACK_STREAMS); + if (is_aes) /* AES playback */ + stored_volume = chip->digital_playback_volume[MIXART_PLAYBACK_STREAMS + idx]; + else /* analog playback */ + stored_volume = chip->digital_playback_volume[idx]; + } + for (i = 0; i < 2; i++) { + int vol = ucontrol->value.integer.value[i]; + if (vol < MIXART_DIGITAL_LEVEL_MIN || + vol > MIXART_DIGITAL_LEVEL_MAX) + continue; + if (stored_volume[i] != vol) { + stored_volume[i] = vol; + changed = 1; + } + } + if (changed) { + if (is_capture) + mixart_update_capture_stream_level(chip, is_aes); + else + mixart_update_playback_stream_level(chip, is_aes, idx); + } + return changed; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_digital, -10950, 50, 0); + +static const struct snd_kcontrol_new snd_mixart_pcm_vol = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + /* name will be filled later */ + /* count will be filled later */ + .info = mixart_digital_vol_info, /* shared */ + .get = mixart_pcm_vol_get, + .put = mixart_pcm_vol_put, + .tlv = { .p = db_scale_digital }, +}; + + +static int mixart_pcm_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + snd_BUG_ON(idx >= MIXART_PLAYBACK_STREAMS); + + guard(mutex)(&chip->mgr->mixer_mutex); + if(kcontrol->private_value & MIXART_VOL_AES_MASK) /* AES playback */ + idx += MIXART_PLAYBACK_STREAMS; + ucontrol->value.integer.value[0] = chip->digital_playback_active[idx][0]; + ucontrol->value.integer.value[1] = chip->digital_playback_active[idx][1]; + return 0; +} + +static int mixart_pcm_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int is_aes = kcontrol->private_value & MIXART_VOL_AES_MASK; + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + int i, j; + snd_BUG_ON(idx >= MIXART_PLAYBACK_STREAMS); + + guard(mutex)(&chip->mgr->mixer_mutex); + j = idx; + if (is_aes) + j += MIXART_PLAYBACK_STREAMS; + for (i = 0; i < 2; i++) { + if (chip->digital_playback_active[j][i] != + ucontrol->value.integer.value[i]) { + chip->digital_playback_active[j][i] = + !!ucontrol->value.integer.value[i]; + changed = 1; + } + } + if (changed) + mixart_update_playback_stream_level(chip, is_aes, idx); + return changed; +} + +static const struct snd_kcontrol_new mixart_control_pcm_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* name will be filled later */ + .count = MIXART_PLAYBACK_STREAMS, + .info = mixart_sw_info, /* shared */ + .get = mixart_pcm_sw_get, + .put = mixart_pcm_sw_put +}; + +static int mixart_update_monitoring(struct snd_mixart* chip, int channel) +{ + int err; + struct mixart_msg request; + struct mixart_set_out_audio_level audio_level; + u32 resp = 0; + + if(chip->pipe_out_ana.status == PIPE_UNDEFINED) + return -EINVAL; /* no pipe defined */ + + if(!channel) request.uid = chip->pipe_out_ana.uid_left_connector; + else request.uid = chip->pipe_out_ana.uid_right_connector; + request.message_id = MSG_CONNECTOR_SET_OUT_AUDIO_LEVEL; + request.data = &audio_level; + request.size = sizeof(audio_level); + + memset(&audio_level, 0, sizeof(audio_level)); + audio_level.valid_mask1 = MIXART_AUDIO_LEVEL_MONITOR_MASK | MIXART_AUDIO_LEVEL_MUTE_M1_MASK; + audio_level.monitor_level = mixart_digital_level[chip->monitoring_volume[channel!=0]]; + audio_level.monitor_mute1 = !chip->monitoring_active[channel!=0]; + + err = snd_mixart_send_msg(chip->mgr, &request, sizeof(resp), &resp); + if((err<0) || resp) { + dev_dbg(chip->card->dev, + "error MSG_CONNECTOR_SET_OUT_AUDIO_LEVEL card(%d) resp(%x)\n", + chip->chip_idx, resp); + return -EINVAL; + } + return 0; +} + +/* + * monitoring level control + */ + +static int mixart_monitor_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->monitoring_volume[0]; + ucontrol->value.integer.value[1] = chip->monitoring_volume[1]; + return 0; +} + +static int mixart_monitor_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int i; + + guard(mutex)(&chip->mgr->mixer_mutex); + for (i = 0; i < 2; i++) { + if (chip->monitoring_volume[i] != + ucontrol->value.integer.value[i]) { + chip->monitoring_volume[i] = + !!ucontrol->value.integer.value[i]; + mixart_update_monitoring(chip, i); + changed = 1; + } + } + return changed; +} + +static const struct snd_kcontrol_new mixart_control_monitor_vol = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Monitoring Volume", + .info = mixart_digital_vol_info, /* shared */ + .get = mixart_monitor_vol_get, + .put = mixart_monitor_vol_put, + .tlv = { .p = db_scale_digital }, +}; + +/* + * monitoring switch control + */ + +static int mixart_monitor_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->monitoring_active[0]; + ucontrol->value.integer.value[1] = chip->monitoring_active[1]; + return 0; +} + +static int mixart_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_mixart *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int i; + + guard(mutex)(&chip->mgr->mixer_mutex); + for (i = 0; i < 2; i++) { + if (chip->monitoring_active[i] != + ucontrol->value.integer.value[i]) { + chip->monitoring_active[i] = + !!ucontrol->value.integer.value[i]; + changed |= (1<monitoring_active[0] || + chip->monitoring_active[1]; + if (allocate) { + /* allocate the playback pipe for monitoring */ + snd_mixart_add_ref_pipe(chip, MIXART_PCM_ANALOG, 0, 1); + /* allocate the capture pipe for monitoring */ + snd_mixart_add_ref_pipe(chip, MIXART_PCM_ANALOG, 1, 1); + } + if (changed & 0x01) + mixart_update_monitoring(chip, 0); + if (changed & 0x02) + mixart_update_monitoring(chip, 1); + if (!allocate) { + /* release the capture pipe for monitoring */ + snd_mixart_kill_ref_pipe(chip->mgr, + &chip->pipe_in_ana, 1); + /* release the playback pipe for monitoring */ + snd_mixart_kill_ref_pipe(chip->mgr, + &chip->pipe_out_ana, 1); + } + } + + return (changed != 0); +} + +static const struct snd_kcontrol_new mixart_control_monitor_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitoring Switch", + .info = mixart_sw_info, /* shared */ + .get = mixart_monitor_sw_get, + .put = mixart_monitor_sw_put +}; + + +static void mixart_reset_audio_levels(struct snd_mixart *chip) +{ + /* analog volumes can be set even if there is no pipe */ + mixart_update_analog_audio_level(chip, 0); + /* analog levels for capture only on the first two chips */ + if(chip->chip_idx < 2) { + mixart_update_analog_audio_level(chip, 1); + } + return; +} + + +int snd_mixart_create_mixer(struct mixart_mgr *mgr) +{ + struct snd_mixart *chip; + int err, i; + + mutex_init(&mgr->mixer_mutex); /* can be in another place */ + + for(i=0; inum_cards; i++) { + struct snd_kcontrol_new temp; + chip = mgr->chip[i]; + + /* analog output level control */ + temp = mixart_control_analog_level; + temp.name = "Master Playback Volume"; + temp.private_value = 0; /* playback */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + /* output mute controls */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&mixart_control_output_switch, chip)); + if (err < 0) + return err; + + /* analog input level control only on first two chips !*/ + if(i<2) { + temp = mixart_control_analog_level; + temp.name = "Master Capture Volume"; + temp.private_value = 1; /* capture */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + + temp = snd_mixart_pcm_vol; + temp.name = "PCM Playback Volume"; + temp.count = MIXART_PLAYBACK_STREAMS; + temp.private_value = 0; /* playback analog */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + + temp.name = "PCM Capture Volume"; + temp.count = 1; + temp.private_value = MIXART_VOL_REC_MASK; /* capture analog */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + + if(mgr->board_type == MIXART_DAUGHTER_TYPE_AES) { + temp.name = "AES Playback Volume"; + temp.count = MIXART_PLAYBACK_STREAMS; + temp.private_value = MIXART_VOL_AES_MASK; /* playback AES/EBU */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + + temp.name = "AES Capture Volume"; + temp.count = 0; + temp.private_value = MIXART_VOL_REC_MASK | MIXART_VOL_AES_MASK; /* capture AES/EBU */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + temp = mixart_control_pcm_switch; + temp.name = "PCM Playback Switch"; + temp.private_value = 0; /* playback analog */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + + if(mgr->board_type == MIXART_DAUGHTER_TYPE_AES) { + temp.name = "AES Playback Switch"; + temp.private_value = MIXART_VOL_AES_MASK; /* playback AES/EBU */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + + /* monitoring */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&mixart_control_monitor_vol, chip)); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, snd_ctl_new1(&mixart_control_monitor_sw, chip)); + if (err < 0) + return err; + + /* init all mixer data and program the master volumes/switches */ + mixart_reset_audio_levels(chip); + } + return 0; +} diff --git a/sound/pci/mixart/mixart_mixer.h b/sound/pci/mixart/mixart_mixer.h new file mode 100644 index 000000000..42e189272 --- /dev/null +++ b/sound/pci/mixart/mixart_mixer.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram miXart soundcards + * + * include file for mixer + * + * Copyright (c) 2003 by Digigram + */ + +#ifndef __SOUND_MIXART_MIXER_H +#define __SOUND_MIXART_MIXER_H + +/* exported */ +int mixart_update_playback_stream_level(struct snd_mixart* chip, int is_aes, int idx); +int mixart_update_capture_stream_level(struct snd_mixart* chip, int is_aes); +int snd_mixart_create_mixer(struct mixart_mgr* mgr); + +#endif /* __SOUND_MIXART_MIXER_H */ diff --git a/sound/pci/nm256/Makefile b/sound/pci/nm256/Makefile new file mode 100644 index 000000000..7d55fe774 --- /dev/null +++ b/sound/pci/nm256/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-nm256-y := nm256.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_NM256) += snd-nm256.o diff --git a/sound/pci/nm256/nm256.c b/sound/pci/nm256/nm256.c new file mode 100644 index 000000000..a7da55d9c --- /dev/null +++ b/sound/pci/nm256/nm256.c @@ -0,0 +1,1653 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for NeoMagic 256AV and 256ZX chipsets. + * Copyright (c) 2000 by Takashi Iwai + * + * Based on nm256_audio.c OSS driver in linux kernel. + * The original author of OSS nm256 driver wishes to remain anonymous, + * so I just put my acknoledgment to him/her here. + * The original author's web page is found at + * http://www.uglx.org/sony.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define CARD_NAME "NeoMagic 256AV/ZX" +#define DRIVER_NAME "NM256" + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("NeoMagic NM256AV/ZX"); +MODULE_LICENSE("GPL"); + +/* + * some compile conditions. + */ + +static int index = SNDRV_DEFAULT_IDX1; /* Index */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static int playback_bufsize = 16; +static int capture_bufsize = 16; +static bool force_ac97; /* disabled as default */ +static int buffer_top; /* not specified */ +static bool use_cache; /* disabled */ +static bool vaio_hack; /* disabled */ +static bool reset_workaround; +static bool reset_workaround_2; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); +module_param(playback_bufsize, int, 0444); +MODULE_PARM_DESC(playback_bufsize, "DAC frame size in kB for " CARD_NAME " soundcard."); +module_param(capture_bufsize, int, 0444); +MODULE_PARM_DESC(capture_bufsize, "ADC frame size in kB for " CARD_NAME " soundcard."); +module_param(force_ac97, bool, 0444); +MODULE_PARM_DESC(force_ac97, "Force to use AC97 codec for " CARD_NAME " soundcard."); +module_param(buffer_top, int, 0444); +MODULE_PARM_DESC(buffer_top, "Set the top address of audio buffer for " CARD_NAME " soundcard."); +module_param(use_cache, bool, 0444); +MODULE_PARM_DESC(use_cache, "Enable the cache for coefficient table access."); +module_param(vaio_hack, bool, 0444); +MODULE_PARM_DESC(vaio_hack, "Enable workaround for Sony VAIO notebooks."); +module_param(reset_workaround, bool, 0444); +MODULE_PARM_DESC(reset_workaround, "Enable AC97 RESET workaround for some laptops."); +module_param(reset_workaround_2, bool, 0444); +MODULE_PARM_DESC(reset_workaround_2, "Enable extended AC97 RESET workaround for some other laptops."); + +/* just for backward compatibility */ +static bool enable; +module_param(enable, bool, 0444); + + + +/* + * hw definitions + */ + +/* The BIOS signature. */ +#define NM_SIGNATURE 0x4e4d0000 +/* Signature mask. */ +#define NM_SIG_MASK 0xffff0000 + +/* Size of the second memory area. */ +#define NM_PORT2_SIZE 4096 + +/* The base offset of the mixer in the second memory area. */ +#define NM_MIXER_OFFSET 0x600 + +/* The maximum size of a coefficient entry. */ +#define NM_MAX_PLAYBACK_COEF_SIZE 0x5000 +#define NM_MAX_RECORD_COEF_SIZE 0x1260 + +/* The interrupt register. */ +#define NM_INT_REG 0xa04 +/* And its bits. */ +#define NM_PLAYBACK_INT 0x40 +#define NM_RECORD_INT 0x100 +#define NM_MISC_INT_1 0x4000 +#define NM_MISC_INT_2 0x1 +#define NM_ACK_INT(chip, X) snd_nm256_writew(chip, NM_INT_REG, (X) << 1) + +/* The AV's "mixer ready" status bit and location. */ +#define NM_MIXER_STATUS_OFFSET 0xa04 +#define NM_MIXER_READY_MASK 0x0800 +#define NM_MIXER_PRESENCE 0xa06 +#define NM_PRESENCE_MASK 0x0050 +#define NM_PRESENCE_VALUE 0x0040 + +/* + * For the ZX. It uses the same interrupt register, but it holds 32 + * bits instead of 16. + */ +#define NM2_PLAYBACK_INT 0x10000 +#define NM2_RECORD_INT 0x80000 +#define NM2_MISC_INT_1 0x8 +#define NM2_MISC_INT_2 0x2 +#define NM2_ACK_INT(chip, X) snd_nm256_writel(chip, NM_INT_REG, (X)) + +/* The ZX's "mixer ready" status bit and location. */ +#define NM2_MIXER_STATUS_OFFSET 0xa06 +#define NM2_MIXER_READY_MASK 0x0800 + +/* The playback registers start from here. */ +#define NM_PLAYBACK_REG_OFFSET 0x0 +/* The record registers start from here. */ +#define NM_RECORD_REG_OFFSET 0x200 + +/* The rate register is located 2 bytes from the start of the register area. */ +#define NM_RATE_REG_OFFSET 2 + +/* Mono/stereo flag, number of bits on playback, and rate mask. */ +#define NM_RATE_STEREO 1 +#define NM_RATE_BITS_16 2 +#define NM_RATE_MASK 0xf0 + +/* Playback enable register. */ +#define NM_PLAYBACK_ENABLE_REG (NM_PLAYBACK_REG_OFFSET + 0x1) +#define NM_PLAYBACK_ENABLE_FLAG 1 +#define NM_PLAYBACK_ONESHOT 2 +#define NM_PLAYBACK_FREERUN 4 + +/* Mutes the audio output. */ +#define NM_AUDIO_MUTE_REG (NM_PLAYBACK_REG_OFFSET + 0x18) +#define NM_AUDIO_MUTE_LEFT 0x8000 +#define NM_AUDIO_MUTE_RIGHT 0x0080 + +/* Recording enable register. */ +#define NM_RECORD_ENABLE_REG (NM_RECORD_REG_OFFSET + 0) +#define NM_RECORD_ENABLE_FLAG 1 +#define NM_RECORD_FREERUN 2 + +/* coefficient buffer pointer */ +#define NM_COEFF_START_OFFSET 0x1c +#define NM_COEFF_END_OFFSET 0x20 + +/* DMA buffer offsets */ +#define NM_RBUFFER_START (NM_RECORD_REG_OFFSET + 0x4) +#define NM_RBUFFER_END (NM_RECORD_REG_OFFSET + 0x10) +#define NM_RBUFFER_WMARK (NM_RECORD_REG_OFFSET + 0xc) +#define NM_RBUFFER_CURRP (NM_RECORD_REG_OFFSET + 0x8) + +#define NM_PBUFFER_START (NM_PLAYBACK_REG_OFFSET + 0x4) +#define NM_PBUFFER_END (NM_PLAYBACK_REG_OFFSET + 0x14) +#define NM_PBUFFER_WMARK (NM_PLAYBACK_REG_OFFSET + 0xc) +#define NM_PBUFFER_CURRP (NM_PLAYBACK_REG_OFFSET + 0x8) + +struct nm256_stream { + + struct nm256 *chip; + struct snd_pcm_substream *substream; + int running; + int suspended; + + u32 buf; /* offset from chip->buffer */ + int bufsize; /* buffer size in bytes */ + void __iomem *bufptr; /* mapped pointer */ + unsigned long bufptr_addr; /* physical address of the mapped pointer */ + + int dma_size; /* buffer size of the substream in bytes */ + int period_size; /* period size in bytes */ + int periods; /* # of periods */ + int shift; /* bit shifts */ + int cur_period; /* current period # */ + +}; + +struct nm256 { + + struct snd_card *card; + + void __iomem *cport; /* control port */ + unsigned long cport_addr; /* physical address */ + + void __iomem *buffer; /* buffer */ + unsigned long buffer_addr; /* buffer phyiscal address */ + + u32 buffer_start; /* start offset from pci resource 0 */ + u32 buffer_end; /* end offset */ + u32 buffer_size; /* total buffer size */ + + u32 all_coeff_buf; /* coefficient buffer */ + u32 coeff_buf[2]; /* coefficient buffer for each stream */ + + unsigned int coeffs_current: 1; /* coeff. table is loaded? */ + unsigned int use_cache: 1; /* use one big coef. table */ + unsigned int reset_workaround: 1; /* Workaround for some laptops to avoid freeze */ + unsigned int reset_workaround_2: 1; /* Extended workaround for some other laptops to avoid freeze */ + unsigned int in_resume: 1; + + int mixer_base; /* register offset of ac97 mixer */ + int mixer_status_offset; /* offset of mixer status reg. */ + int mixer_status_mask; /* bit mask to test the mixer status */ + + int irq; + int irq_acks; + irq_handler_t interrupt; + int badintrcount; /* counter to check bogus interrupts */ + struct mutex irq_mutex; + + struct nm256_stream streams[2]; + + struct snd_ac97 *ac97; + unsigned short *ac97_regs; /* register caches, only for valid regs */ + + struct snd_pcm *pcm; + + struct pci_dev *pci; + + spinlock_t reg_lock; + +}; + + +/* + * include coefficient table + */ +#include "nm256_coef.c" + + +/* + * PCI ids + */ +static const struct pci_device_id snd_nm256_ids[] = { + { PCI_VDEVICE(NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256AV_AUDIO) }, + { PCI_VDEVICE(NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256ZX_AUDIO) }, + { PCI_VDEVICE(NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256XL_PLUS_AUDIO) }, + { }, +}; + +MODULE_DEVICE_TABLE(pci, snd_nm256_ids); + + +/* + * lowlvel stuffs + */ + +static inline u8 +snd_nm256_readb(struct nm256 *chip, int offset) +{ + return readb(chip->cport + offset); +} + +static inline u16 +snd_nm256_readw(struct nm256 *chip, int offset) +{ + return readw(chip->cport + offset); +} + +static inline u32 +snd_nm256_readl(struct nm256 *chip, int offset) +{ + return readl(chip->cport + offset); +} + +static inline void +snd_nm256_writeb(struct nm256 *chip, int offset, u8 val) +{ + writeb(val, chip->cport + offset); +} + +static inline void +snd_nm256_writew(struct nm256 *chip, int offset, u16 val) +{ + writew(val, chip->cport + offset); +} + +static inline void +snd_nm256_writel(struct nm256 *chip, int offset, u32 val) +{ + writel(val, chip->cport + offset); +} + +static inline void +snd_nm256_write_buffer(struct nm256 *chip, const void *src, int offset, int size) +{ + offset -= chip->buffer_start; +#ifdef CONFIG_SND_DEBUG + if (offset < 0 || offset >= chip->buffer_size) { + dev_err(chip->card->dev, + "write_buffer invalid offset = %d size = %d\n", + offset, size); + return; + } +#endif + memcpy_toio(chip->buffer + offset, src, size); +} + +/* + * coefficient handlers -- what a magic! + */ + +static u16 +snd_nm256_get_start_offset(int which) +{ + u16 offset = 0; + while (which-- > 0) + offset += coefficient_sizes[which]; + return offset; +} + +static void +snd_nm256_load_one_coefficient(struct nm256 *chip, int stream, u32 port, int which) +{ + u32 coeff_buf = chip->coeff_buf[stream]; + u16 offset = snd_nm256_get_start_offset(which); + u16 size = coefficient_sizes[which]; + + snd_nm256_write_buffer(chip, coefficients + offset, coeff_buf, size); + snd_nm256_writel(chip, port, coeff_buf); + /* ??? Record seems to behave differently than playback. */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + size--; + snd_nm256_writel(chip, port + 4, coeff_buf + size); +} + +static void +snd_nm256_load_coefficient(struct nm256 *chip, int stream, int number) +{ + /* The enable register for the specified engine. */ + u32 poffset = (stream == SNDRV_PCM_STREAM_CAPTURE ? + NM_RECORD_ENABLE_REG : NM_PLAYBACK_ENABLE_REG); + u32 addr = NM_COEFF_START_OFFSET; + + addr += (stream == SNDRV_PCM_STREAM_CAPTURE ? + NM_RECORD_REG_OFFSET : NM_PLAYBACK_REG_OFFSET); + + if (snd_nm256_readb(chip, poffset) & 1) { + dev_dbg(chip->card->dev, + "NM256: Engine was enabled while loading coefficients!\n"); + return; + } + + /* The recording engine uses coefficient values 8-15. */ + number &= 7; + if (stream == SNDRV_PCM_STREAM_CAPTURE) + number += 8; + + if (! chip->use_cache) { + snd_nm256_load_one_coefficient(chip, stream, addr, number); + return; + } + if (! chip->coeffs_current) { + snd_nm256_write_buffer(chip, coefficients, chip->all_coeff_buf, + NM_TOTAL_COEFF_COUNT * 4); + chip->coeffs_current = 1; + } else { + u32 base = chip->all_coeff_buf; + u32 offset = snd_nm256_get_start_offset(number); + u32 end_offset = offset + coefficient_sizes[number]; + snd_nm256_writel(chip, addr, base + offset); + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + end_offset--; + snd_nm256_writel(chip, addr + 4, base + end_offset); + } +} + + +/* The actual rates supported by the card. */ +static const unsigned int samplerates[8] = { + 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, +}; +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(samplerates), + .list = samplerates, + .mask = 0, +}; + +/* + * return the index of the target rate + */ +static int +snd_nm256_fixed_rate(unsigned int rate) +{ + unsigned int i; + for (i = 0; i < ARRAY_SIZE(samplerates); i++) { + if (rate == samplerates[i]) + return i; + } + snd_BUG(); + return 0; +} + +/* + * set sample rate and format + */ +static void +snd_nm256_set_format(struct nm256 *chip, struct nm256_stream *s, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int rate_index = snd_nm256_fixed_rate(runtime->rate); + unsigned char ratebits = (rate_index << 4) & NM_RATE_MASK; + + s->shift = 0; + if (snd_pcm_format_width(runtime->format) == 16) { + ratebits |= NM_RATE_BITS_16; + s->shift++; + } + if (runtime->channels > 1) { + ratebits |= NM_RATE_STEREO; + s->shift++; + } + + runtime->rate = samplerates[rate_index]; + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + snd_nm256_load_coefficient(chip, 0, rate_index); /* 0 = playback */ + snd_nm256_writeb(chip, + NM_PLAYBACK_REG_OFFSET + NM_RATE_REG_OFFSET, + ratebits); + break; + case SNDRV_PCM_STREAM_CAPTURE: + snd_nm256_load_coefficient(chip, 1, rate_index); /* 1 = record */ + snd_nm256_writeb(chip, + NM_RECORD_REG_OFFSET + NM_RATE_REG_OFFSET, + ratebits); + break; + } +} + +/* acquire interrupt */ +static int snd_nm256_acquire_irq(struct nm256 *chip) +{ + guard(mutex)(&chip->irq_mutex); + if (chip->irq < 0) { + if (request_irq(chip->pci->irq, chip->interrupt, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(chip->card->dev, + "unable to grab IRQ %d\n", chip->pci->irq); + return -EBUSY; + } + chip->irq = chip->pci->irq; + chip->card->sync_irq = chip->irq; + } + chip->irq_acks++; + return 0; +} + +/* release interrupt */ +static void snd_nm256_release_irq(struct nm256 *chip) +{ + guard(mutex)(&chip->irq_mutex); + if (chip->irq_acks > 0) + chip->irq_acks--; + if (chip->irq_acks == 0 && chip->irq >= 0) { + free_irq(chip->irq, chip); + chip->irq = -1; + chip->card->sync_irq = -1; + } +} + +/* + * start / stop + */ + +/* update the watermark (current period) */ +static void snd_nm256_pcm_mark(struct nm256 *chip, struct nm256_stream *s, int reg) +{ + s->cur_period++; + s->cur_period %= s->periods; + snd_nm256_writel(chip, reg, s->buf + s->cur_period * s->period_size); +} + +#define snd_nm256_playback_mark(chip, s) snd_nm256_pcm_mark(chip, s, NM_PBUFFER_WMARK) +#define snd_nm256_capture_mark(chip, s) snd_nm256_pcm_mark(chip, s, NM_RBUFFER_WMARK) + +static void +snd_nm256_playback_start(struct nm256 *chip, struct nm256_stream *s, + struct snd_pcm_substream *substream) +{ + /* program buffer pointers */ + snd_nm256_writel(chip, NM_PBUFFER_START, s->buf); + snd_nm256_writel(chip, NM_PBUFFER_END, s->buf + s->dma_size - (1 << s->shift)); + snd_nm256_writel(chip, NM_PBUFFER_CURRP, s->buf); + snd_nm256_playback_mark(chip, s); + + /* Enable playback engine and interrupts. */ + snd_nm256_writeb(chip, NM_PLAYBACK_ENABLE_REG, + NM_PLAYBACK_ENABLE_FLAG | NM_PLAYBACK_FREERUN); + /* Enable both channels. */ + snd_nm256_writew(chip, NM_AUDIO_MUTE_REG, 0x0); +} + +static void +snd_nm256_capture_start(struct nm256 *chip, struct nm256_stream *s, + struct snd_pcm_substream *substream) +{ + /* program buffer pointers */ + snd_nm256_writel(chip, NM_RBUFFER_START, s->buf); + snd_nm256_writel(chip, NM_RBUFFER_END, s->buf + s->dma_size); + snd_nm256_writel(chip, NM_RBUFFER_CURRP, s->buf); + snd_nm256_capture_mark(chip, s); + + /* Enable playback engine and interrupts. */ + snd_nm256_writeb(chip, NM_RECORD_ENABLE_REG, + NM_RECORD_ENABLE_FLAG | NM_RECORD_FREERUN); +} + +/* Stop the play engine. */ +static void +snd_nm256_playback_stop(struct nm256 *chip) +{ + /* Shut off sound from both channels. */ + snd_nm256_writew(chip, NM_AUDIO_MUTE_REG, + NM_AUDIO_MUTE_LEFT | NM_AUDIO_MUTE_RIGHT); + /* Disable play engine. */ + snd_nm256_writeb(chip, NM_PLAYBACK_ENABLE_REG, 0); +} + +static void +snd_nm256_capture_stop(struct nm256 *chip) +{ + /* Disable recording engine. */ + snd_nm256_writeb(chip, NM_RECORD_ENABLE_REG, 0); +} + +static int +snd_nm256_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct nm256 *chip = snd_pcm_substream_chip(substream); + struct nm256_stream *s = substream->runtime->private_data; + + if (snd_BUG_ON(!s)) + return -ENXIO; + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + s->suspended = 0; + fallthrough; + case SNDRV_PCM_TRIGGER_START: + if (! s->running) { + snd_nm256_playback_start(chip, s, substream); + s->running = 1; + } + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + s->suspended = 1; + fallthrough; + case SNDRV_PCM_TRIGGER_STOP: + if (s->running) { + snd_nm256_playback_stop(chip); + s->running = 0; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int +snd_nm256_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct nm256 *chip = snd_pcm_substream_chip(substream); + struct nm256_stream *s = substream->runtime->private_data; + + if (snd_BUG_ON(!s)) + return -ENXIO; + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (! s->running) { + snd_nm256_capture_start(chip, s, substream); + s->running = 1; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (s->running) { + snd_nm256_capture_stop(chip); + s->running = 0; + } + break; + default: + return -EINVAL; + } + return 0; +} + + +/* + * prepare playback/capture channel + */ +static int snd_nm256_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct nm256 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct nm256_stream *s = runtime->private_data; + + if (snd_BUG_ON(!s)) + return -ENXIO; + s->dma_size = frames_to_bytes(runtime, substream->runtime->buffer_size); + s->period_size = frames_to_bytes(runtime, substream->runtime->period_size); + s->periods = substream->runtime->periods; + s->cur_period = 0; + + guard(spinlock_irq)(&chip->reg_lock); + s->running = 0; + snd_nm256_set_format(chip, s, substream); + + return 0; +} + + +/* + * get the current pointer + */ +static snd_pcm_uframes_t +snd_nm256_playback_pointer(struct snd_pcm_substream *substream) +{ + struct nm256 *chip = snd_pcm_substream_chip(substream); + struct nm256_stream *s = substream->runtime->private_data; + unsigned long curp; + + if (snd_BUG_ON(!s)) + return 0; + curp = snd_nm256_readl(chip, NM_PBUFFER_CURRP) - (unsigned long)s->buf; + curp %= s->dma_size; + return bytes_to_frames(substream->runtime, curp); +} + +static snd_pcm_uframes_t +snd_nm256_capture_pointer(struct snd_pcm_substream *substream) +{ + struct nm256 *chip = snd_pcm_substream_chip(substream); + struct nm256_stream *s = substream->runtime->private_data; + unsigned long curp; + + if (snd_BUG_ON(!s)) + return 0; + curp = snd_nm256_readl(chip, NM_RBUFFER_CURRP) - (unsigned long)s->buf; + curp %= s->dma_size; + return bytes_to_frames(substream->runtime, curp); +} + +/* Remapped I/O space can be accessible as pointer on i386 */ +/* This might be changed in the future */ +#ifndef __i386__ +/* + * silence / copy for playback + */ +static int +snd_nm256_playback_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nm256_stream *s = runtime->private_data; + + memset_io(s->bufptr + pos, 0, count); + return 0; +} + +static int +snd_nm256_playback_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *src, unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nm256_stream *s = runtime->private_data; + + if (copy_from_iter_toio(s->bufptr + pos, count, src) != count) + return -EFAULT; + return 0; +} + +/* + * copy to user + */ +static int +snd_nm256_capture_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *dst, unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nm256_stream *s = runtime->private_data; + + if (copy_to_iter_fromio(s->bufptr + pos, count, dst) != count) + return -EFAULT; + return 0; +} + +#endif /* !__i386__ */ + + +/* + * update playback/capture watermarks + */ + +/* spinlock held! */ +static void +snd_nm256_playback_update(struct nm256 *chip) +{ + struct nm256_stream *s; + + s = &chip->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (s->running && s->substream) { + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(s->substream); + spin_lock(&chip->reg_lock); + snd_nm256_playback_mark(chip, s); + } +} + +/* spinlock held! */ +static void +snd_nm256_capture_update(struct nm256 *chip) +{ + struct nm256_stream *s; + + s = &chip->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (s->running && s->substream) { + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(s->substream); + spin_lock(&chip->reg_lock); + snd_nm256_capture_mark(chip, s); + } +} + +/* + * hardware info + */ +static const struct snd_pcm_hardware snd_nm256_playback = +{ + .info = SNDRV_PCM_INFO_MMAP_IOMEM |SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + /*SNDRV_PCM_INFO_PAUSE |*/ + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_KNOT/*24k*/ | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 128 * 1024, +}; + +static const struct snd_pcm_hardware snd_nm256_capture = +{ + .info = SNDRV_PCM_INFO_MMAP_IOMEM | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + /*SNDRV_PCM_INFO_PAUSE |*/ + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_KNOT/*24k*/ | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 128 * 1024, +}; + + +/* set dma transfer size */ +static int snd_nm256_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + /* area and addr are already set and unchanged */ + substream->runtime->dma_bytes = params_buffer_bytes(hw_params); + return 0; +} + +/* + * open + */ +static void snd_nm256_setup_stream(struct nm256 *chip, struct nm256_stream *s, + struct snd_pcm_substream *substream, + const struct snd_pcm_hardware *hw_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + s->running = 0; + runtime->hw = *hw_ptr; + runtime->hw.buffer_bytes_max = s->bufsize; + runtime->hw.period_bytes_max = s->bufsize / 2; + runtime->dma_area = (void __force *) s->bufptr; + runtime->dma_addr = s->bufptr_addr; + runtime->dma_bytes = s->bufsize; + runtime->private_data = s; + s->substream = substream; + + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); +} + +static int +snd_nm256_playback_open(struct snd_pcm_substream *substream) +{ + struct nm256 *chip = snd_pcm_substream_chip(substream); + + if (snd_nm256_acquire_irq(chip) < 0) + return -EBUSY; + snd_nm256_setup_stream(chip, &chip->streams[SNDRV_PCM_STREAM_PLAYBACK], + substream, &snd_nm256_playback); + return 0; +} + +static int +snd_nm256_capture_open(struct snd_pcm_substream *substream) +{ + struct nm256 *chip = snd_pcm_substream_chip(substream); + + if (snd_nm256_acquire_irq(chip) < 0) + return -EBUSY; + snd_nm256_setup_stream(chip, &chip->streams[SNDRV_PCM_STREAM_CAPTURE], + substream, &snd_nm256_capture); + return 0; +} + +/* + * close - we don't have to do special.. + */ +static int +snd_nm256_playback_close(struct snd_pcm_substream *substream) +{ + struct nm256 *chip = snd_pcm_substream_chip(substream); + + snd_nm256_release_irq(chip); + return 0; +} + + +static int +snd_nm256_capture_close(struct snd_pcm_substream *substream) +{ + struct nm256 *chip = snd_pcm_substream_chip(substream); + + snd_nm256_release_irq(chip); + return 0; +} + +/* + * create a pcm instance + */ +static const struct snd_pcm_ops snd_nm256_playback_ops = { + .open = snd_nm256_playback_open, + .close = snd_nm256_playback_close, + .hw_params = snd_nm256_pcm_hw_params, + .prepare = snd_nm256_pcm_prepare, + .trigger = snd_nm256_playback_trigger, + .pointer = snd_nm256_playback_pointer, +#ifndef __i386__ + .copy = snd_nm256_playback_copy, + .fill_silence = snd_nm256_playback_silence, +#endif + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static const struct snd_pcm_ops snd_nm256_capture_ops = { + .open = snd_nm256_capture_open, + .close = snd_nm256_capture_close, + .hw_params = snd_nm256_pcm_hw_params, + .prepare = snd_nm256_pcm_prepare, + .trigger = snd_nm256_capture_trigger, + .pointer = snd_nm256_capture_pointer, +#ifndef __i386__ + .copy = snd_nm256_capture_copy, +#endif + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static int +snd_nm256_pcm(struct nm256 *chip, int device) +{ + struct snd_pcm *pcm; + int i, err; + + for (i = 0; i < 2; i++) { + struct nm256_stream *s = &chip->streams[i]; + s->bufptr = chip->buffer + (s->buf - chip->buffer_start); + s->bufptr_addr = chip->buffer_addr + (s->buf - chip->buffer_start); + } + + err = snd_pcm_new(chip->card, chip->card->driver, device, + 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_nm256_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_nm256_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + chip->pcm = pcm; + + return 0; +} + + +/* + * Initialize the hardware. + */ +static void +snd_nm256_init_chip(struct nm256 *chip) +{ + /* Reset everything. */ + snd_nm256_writeb(chip, 0x0, 0x11); + snd_nm256_writew(chip, 0x214, 0); + /* stop sounds.. */ + //snd_nm256_playback_stop(chip); + //snd_nm256_capture_stop(chip); +} + + +static irqreturn_t +snd_nm256_intr_check(struct nm256 *chip) +{ + if (chip->badintrcount++ > 1000) { + /* + * I'm not sure if the best thing is to stop the card from + * playing or just release the interrupt (after all, we're in + * a bad situation, so doing fancy stuff may not be such a good + * idea). + * + * I worry about the card engine continuing to play noise + * over and over, however--that could become a very + * obnoxious problem. And we know that when this usually + * happens things are fairly safe, it just means the user's + * inserted a PCMCIA card and someone's spamming us with IRQ 9s. + */ + if (chip->streams[SNDRV_PCM_STREAM_PLAYBACK].running) + snd_nm256_playback_stop(chip); + if (chip->streams[SNDRV_PCM_STREAM_CAPTURE].running) + snd_nm256_capture_stop(chip); + chip->badintrcount = 0; + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +/* + * Handle a potential interrupt for the device referred to by DEV_ID. + * + * I don't like the cut-n-paste job here either between the two routines, + * but there are sufficient differences between the two interrupt handlers + * that parameterizing it isn't all that great either. (Could use a macro, + * I suppose...yucky bleah.) + */ + +static irqreturn_t +snd_nm256_interrupt(int irq, void *dev_id) +{ + struct nm256 *chip = dev_id; + u16 status; + u8 cbyte; + + status = snd_nm256_readw(chip, NM_INT_REG); + + /* Not ours. */ + if (status == 0) + return snd_nm256_intr_check(chip); + + chip->badintrcount = 0; + + /* Rather boring; check for individual interrupts and process them. */ + + guard(spinlock)(&chip->reg_lock); + if (status & NM_PLAYBACK_INT) { + status &= ~NM_PLAYBACK_INT; + NM_ACK_INT(chip, NM_PLAYBACK_INT); + snd_nm256_playback_update(chip); + } + + if (status & NM_RECORD_INT) { + status &= ~NM_RECORD_INT; + NM_ACK_INT(chip, NM_RECORD_INT); + snd_nm256_capture_update(chip); + } + + if (status & NM_MISC_INT_1) { + status &= ~NM_MISC_INT_1; + NM_ACK_INT(chip, NM_MISC_INT_1); + dev_dbg(chip->card->dev, "NM256: Got misc interrupt #1\n"); + snd_nm256_writew(chip, NM_INT_REG, 0x8000); + cbyte = snd_nm256_readb(chip, 0x400); + snd_nm256_writeb(chip, 0x400, cbyte | 2); + } + + if (status & NM_MISC_INT_2) { + status &= ~NM_MISC_INT_2; + NM_ACK_INT(chip, NM_MISC_INT_2); + dev_dbg(chip->card->dev, "NM256: Got misc interrupt #2\n"); + cbyte = snd_nm256_readb(chip, 0x400); + snd_nm256_writeb(chip, 0x400, cbyte & ~2); + } + + /* Unknown interrupt. */ + if (status) { + dev_dbg(chip->card->dev, + "NM256: Fire in the hole! Unknown status 0x%x\n", + status); + /* Pray. */ + NM_ACK_INT(chip, status); + } + + return IRQ_HANDLED; +} + +/* + * Handle a potential interrupt for the device referred to by DEV_ID. + * This handler is for the 256ZX, and is very similar to the non-ZX + * routine. + */ + +static irqreturn_t +snd_nm256_interrupt_zx(int irq, void *dev_id) +{ + struct nm256 *chip = dev_id; + u32 status; + u8 cbyte; + + status = snd_nm256_readl(chip, NM_INT_REG); + + /* Not ours. */ + if (status == 0) + return snd_nm256_intr_check(chip); + + chip->badintrcount = 0; + + /* Rather boring; check for individual interrupts and process them. */ + + guard(spinlock)(&chip->reg_lock); + if (status & NM2_PLAYBACK_INT) { + status &= ~NM2_PLAYBACK_INT; + NM2_ACK_INT(chip, NM2_PLAYBACK_INT); + snd_nm256_playback_update(chip); + } + + if (status & NM2_RECORD_INT) { + status &= ~NM2_RECORD_INT; + NM2_ACK_INT(chip, NM2_RECORD_INT); + snd_nm256_capture_update(chip); + } + + if (status & NM2_MISC_INT_1) { + status &= ~NM2_MISC_INT_1; + NM2_ACK_INT(chip, NM2_MISC_INT_1); + dev_dbg(chip->card->dev, "NM256: Got misc interrupt #1\n"); + cbyte = snd_nm256_readb(chip, 0x400); + snd_nm256_writeb(chip, 0x400, cbyte | 2); + } + + if (status & NM2_MISC_INT_2) { + status &= ~NM2_MISC_INT_2; + NM2_ACK_INT(chip, NM2_MISC_INT_2); + dev_dbg(chip->card->dev, "NM256: Got misc interrupt #2\n"); + cbyte = snd_nm256_readb(chip, 0x400); + snd_nm256_writeb(chip, 0x400, cbyte & ~2); + } + + /* Unknown interrupt. */ + if (status) { + dev_dbg(chip->card->dev, + "NM256: Fire in the hole! Unknown status 0x%x\n", + status); + /* Pray. */ + NM2_ACK_INT(chip, status); + } + + return IRQ_HANDLED; +} + +/* + * AC97 interface + */ + +/* + * Waits for the mixer to become ready to be written; returns a zero value + * if it timed out. + */ +static int +snd_nm256_ac97_ready(struct nm256 *chip) +{ + int timeout = 10; + u32 testaddr; + u16 testb; + + testaddr = chip->mixer_status_offset; + testb = chip->mixer_status_mask; + + /* + * Loop around waiting for the mixer to become ready. + */ + while (timeout-- > 0) { + if ((snd_nm256_readw(chip, testaddr) & testb) == 0) + return 1; + udelay(100); + } + return 0; +} + +/* + * Initial register values to be written to the AC97 mixer. + * While most of these are identical to the reset values, we do this + * so that we have most of the register contents cached--this avoids + * reading from the mixer directly (which seems to be problematic, + * probably due to ignorance). + */ + +struct initialValues { + unsigned short reg; + unsigned short value; +}; + +static const struct initialValues nm256_ac97_init_val[] = +{ + { AC97_MASTER, 0x8000 }, + { AC97_HEADPHONE, 0x8000 }, + { AC97_MASTER_MONO, 0x8000 }, + { AC97_PC_BEEP, 0x8000 }, + { AC97_PHONE, 0x8008 }, + { AC97_MIC, 0x8000 }, + { AC97_LINE, 0x8808 }, + { AC97_CD, 0x8808 }, + { AC97_VIDEO, 0x8808 }, + { AC97_AUX, 0x8808 }, + { AC97_PCM, 0x8808 }, + { AC97_REC_SEL, 0x0000 }, + { AC97_REC_GAIN, 0x0B0B }, + { AC97_GENERAL_PURPOSE, 0x0000 }, + { AC97_3D_CONTROL, 0x8000 }, + { AC97_VENDOR_ID1, 0x8384 }, + { AC97_VENDOR_ID2, 0x7609 }, +}; + +static int nm256_ac97_idx(unsigned short reg) +{ + int i; + for (i = 0; i < ARRAY_SIZE(nm256_ac97_init_val); i++) + if (nm256_ac97_init_val[i].reg == reg) + return i; + return -1; +} + +/* + * some nm256 easily crash when reading from mixer registers + * thus we're treating it as a write-only mixer and cache the + * written values + */ +static unsigned short +snd_nm256_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct nm256 *chip = ac97->private_data; + int idx = nm256_ac97_idx(reg); + + if (idx < 0) + return 0; + return chip->ac97_regs[idx]; +} + +/* + */ +static void +snd_nm256_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + struct nm256 *chip = ac97->private_data; + int tries = 2; + int idx = nm256_ac97_idx(reg); + u32 base; + + if (idx < 0) + return; + + base = chip->mixer_base; + + snd_nm256_ac97_ready(chip); + + /* Wait for the write to take, too. */ + while (tries-- > 0) { + snd_nm256_writew(chip, base + reg, val); + msleep(1); /* a little delay here seems better.. */ + if (snd_nm256_ac97_ready(chip)) { + /* successful write: set cache */ + chip->ac97_regs[idx] = val; + return; + } + } + dev_dbg(chip->card->dev, "nm256: ac97 codec not ready..\n"); +} + +/* static resolution table */ +static const struct snd_ac97_res_table nm256_res_table[] = { + { AC97_MASTER, 0x1f1f }, + { AC97_HEADPHONE, 0x1f1f }, + { AC97_MASTER_MONO, 0x001f }, + { AC97_PC_BEEP, 0x001f }, + { AC97_PHONE, 0x001f }, + { AC97_MIC, 0x001f }, + { AC97_LINE, 0x1f1f }, + { AC97_CD, 0x1f1f }, + { AC97_VIDEO, 0x1f1f }, + { AC97_AUX, 0x1f1f }, + { AC97_PCM, 0x1f1f }, + { AC97_REC_GAIN, 0x0f0f }, + { } /* terminator */ +}; + +/* initialize the ac97 into a known state */ +static void +snd_nm256_ac97_reset(struct snd_ac97 *ac97) +{ + struct nm256 *chip = ac97->private_data; + + /* Reset the mixer. 'Tis magic! */ + snd_nm256_writeb(chip, 0x6c0, 1); + if (! chip->reset_workaround) { + /* Dell latitude LS will lock up by this */ + snd_nm256_writeb(chip, 0x6cc, 0x87); + } + if (! chip->reset_workaround_2) { + /* Dell latitude CSx will lock up by this */ + snd_nm256_writeb(chip, 0x6cc, 0x80); + snd_nm256_writeb(chip, 0x6cc, 0x0); + } + if (! chip->in_resume) { + int i; + for (i = 0; i < ARRAY_SIZE(nm256_ac97_init_val); i++) { + /* preload the cache, so as to avoid even a single + * read of the mixer regs + */ + snd_nm256_ac97_write(ac97, nm256_ac97_init_val[i].reg, + nm256_ac97_init_val[i].value); + } + } +} + +/* create an ac97 mixer interface */ +static int +snd_nm256_mixer(struct nm256 *chip) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .reset = snd_nm256_ac97_reset, + .write = snd_nm256_ac97_write, + .read = snd_nm256_ac97_read, + }; + + chip->ac97_regs = devm_kcalloc(chip->card->dev, + ARRAY_SIZE(nm256_ac97_init_val), + sizeof(short), GFP_KERNEL); + if (! chip->ac97_regs) + return -ENOMEM; + + err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ac97.scaps = AC97_SCAP_AUDIO; /* we support audio! */ + ac97.private_data = chip; + ac97.res_table = nm256_res_table; + pbus->no_vra = 1; + err = snd_ac97_mixer(pbus, &ac97, &chip->ac97); + if (err < 0) + return err; + if (! (chip->ac97->id & (0xf0000000))) { + /* looks like an invalid id */ + sprintf(chip->card->mixername, "%s AC97", chip->card->driver); + } + return 0; +} + +/* + * See if the signature left by the NM256 BIOS is intact; if so, we use + * the associated address as the end of our audio buffer in the video + * RAM. + */ + +static int +snd_nm256_peek_for_sig(struct nm256 *chip) +{ + /* The signature is located 1K below the end of video RAM. */ + void __iomem *temp; + /* Default buffer end is 5120 bytes below the top of RAM. */ + unsigned long pointer_found = chip->buffer_end - 0x1400; + u32 sig; + + temp = ioremap(chip->buffer_addr + chip->buffer_end - 0x400, 16); + if (temp == NULL) { + dev_err(chip->card->dev, + "Unable to scan for card signature in video RAM\n"); + return -EBUSY; + } + + sig = readl(temp); + if ((sig & NM_SIG_MASK) == NM_SIGNATURE) { + u32 pointer = readl(temp + 4); + + /* + * If it's obviously invalid, don't use it + */ + if (pointer == 0xffffffff || + pointer < chip->buffer_size || + pointer > chip->buffer_end) { + dev_err(chip->card->dev, + "invalid signature found: 0x%x\n", pointer); + iounmap(temp); + return -ENODEV; + } else { + pointer_found = pointer; + dev_info(chip->card->dev, + "found card signature in video RAM: 0x%x\n", + pointer); + } + } + + iounmap(temp); + chip->buffer_end = pointer_found; + + return 0; +} + +/* + * APM event handler, so the card is properly reinitialized after a power + * event. + */ +static int nm256_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct nm256 *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(chip->ac97); + chip->coeffs_current = 0; + return 0; +} + +static int nm256_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct nm256 *chip = card->private_data; + int i; + + /* Perform a full reset on the hardware */ + chip->in_resume = 1; + + snd_nm256_init_chip(chip); + + /* restore ac97 */ + snd_ac97_resume(chip->ac97); + + for (i = 0; i < 2; i++) { + struct nm256_stream *s = &chip->streams[i]; + if (s->substream && s->suspended) { + guard(spinlock_irq)(&chip->reg_lock); + snd_nm256_set_format(chip, s, s->substream); + } + } + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + chip->in_resume = 0; + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(nm256_pm, nm256_suspend, nm256_resume); + +static void snd_nm256_free(struct snd_card *card) +{ + struct nm256 *chip = card->private_data; + + if (chip->streams[SNDRV_PCM_STREAM_PLAYBACK].running) + snd_nm256_playback_stop(chip); + if (chip->streams[SNDRV_PCM_STREAM_CAPTURE].running) + snd_nm256_capture_stop(chip); +} + +static int +snd_nm256_create(struct snd_card *card, struct pci_dev *pci) +{ + struct nm256 *chip = card->private_data; + int err, pval; + u32 addr; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + chip->card = card; + chip->pci = pci; + chip->use_cache = use_cache; + spin_lock_init(&chip->reg_lock); + chip->irq = -1; + mutex_init(&chip->irq_mutex); + + /* store buffer sizes in bytes */ + chip->streams[SNDRV_PCM_STREAM_PLAYBACK].bufsize = playback_bufsize * 1024; + chip->streams[SNDRV_PCM_STREAM_CAPTURE].bufsize = capture_bufsize * 1024; + + /* + * The NM256 has two memory ports. The first port is nothing + * more than a chunk of video RAM, which is used as the I/O ring + * buffer. The second port has the actual juicy stuff (like the + * mixer and the playback engine control registers). + */ + + chip->buffer_addr = pci_resource_start(pci, 0); + chip->cport_addr = pci_resource_start(pci, 1); + + err = pcim_request_all_regions(pci, card->driver); + if (err < 0) + return err; + + /* Init the memory port info. */ + /* remap control port (#2) */ + chip->cport = devm_ioremap(&pci->dev, chip->cport_addr, NM_PORT2_SIZE); + if (!chip->cport) { + dev_err(card->dev, "unable to map control port %lx\n", + chip->cport_addr); + return -ENOMEM; + } + + if (!strcmp(card->driver, "NM256AV")) { + /* Ok, try to see if this is a non-AC97 version of the hardware. */ + pval = snd_nm256_readw(chip, NM_MIXER_PRESENCE); + if ((pval & NM_PRESENCE_MASK) != NM_PRESENCE_VALUE) { + if (! force_ac97) { + dev_err(card->dev, + "no ac97 is found!\n"); + dev_err(card->dev, + "force the driver to load by passing in the module parameter\n"); + dev_err(card->dev, + " force_ac97=1\n"); + dev_err(card->dev, + "or try sb16, opl3sa2, or cs423x drivers instead.\n"); + return -ENXIO; + } + } + chip->buffer_end = 2560 * 1024; + chip->interrupt = snd_nm256_interrupt; + chip->mixer_status_offset = NM_MIXER_STATUS_OFFSET; + chip->mixer_status_mask = NM_MIXER_READY_MASK; + } else { + /* Not sure if there is any relevant detect for the ZX or not. */ + if (snd_nm256_readb(chip, 0xa0b) != 0) + chip->buffer_end = 6144 * 1024; + else + chip->buffer_end = 4096 * 1024; + + chip->interrupt = snd_nm256_interrupt_zx; + chip->mixer_status_offset = NM2_MIXER_STATUS_OFFSET; + chip->mixer_status_mask = NM2_MIXER_READY_MASK; + } + + chip->buffer_size = chip->streams[SNDRV_PCM_STREAM_PLAYBACK].bufsize + + chip->streams[SNDRV_PCM_STREAM_CAPTURE].bufsize; + if (chip->use_cache) + chip->buffer_size += NM_TOTAL_COEFF_COUNT * 4; + else + chip->buffer_size += NM_MAX_PLAYBACK_COEF_SIZE + NM_MAX_RECORD_COEF_SIZE; + + if (buffer_top >= chip->buffer_size && buffer_top < chip->buffer_end) + chip->buffer_end = buffer_top; + else { + /* get buffer end pointer from signature */ + err = snd_nm256_peek_for_sig(chip); + if (err < 0) + return err; + } + + chip->buffer_start = chip->buffer_end - chip->buffer_size; + chip->buffer_addr += chip->buffer_start; + + dev_info(card->dev, "Mapping port 1 from 0x%x - 0x%x\n", + chip->buffer_start, chip->buffer_end); + + chip->buffer = devm_ioremap(&pci->dev, chip->buffer_addr, + chip->buffer_size); + if (!chip->buffer) { + dev_err(card->dev, "unable to map ring buffer at %lx\n", + chip->buffer_addr); + return -ENOMEM; + } + + /* set offsets */ + addr = chip->buffer_start; + chip->streams[SNDRV_PCM_STREAM_PLAYBACK].buf = addr; + addr += chip->streams[SNDRV_PCM_STREAM_PLAYBACK].bufsize; + chip->streams[SNDRV_PCM_STREAM_CAPTURE].buf = addr; + addr += chip->streams[SNDRV_PCM_STREAM_CAPTURE].bufsize; + if (chip->use_cache) { + chip->all_coeff_buf = addr; + } else { + chip->coeff_buf[SNDRV_PCM_STREAM_PLAYBACK] = addr; + addr += NM_MAX_PLAYBACK_COEF_SIZE; + chip->coeff_buf[SNDRV_PCM_STREAM_CAPTURE] = addr; + } + + /* Fixed setting. */ + chip->mixer_base = NM_MIXER_OFFSET; + + chip->coeffs_current = 0; + + snd_nm256_init_chip(chip); + + // pci_set_master(pci); /* needed? */ + return 0; +} + + +enum { NM_IGNORED, NM_RESET_WORKAROUND, NM_RESET_WORKAROUND_2 }; + +static const struct snd_pci_quirk nm256_quirks[] = { + /* HP omnibook 4150 has cs4232 codec internally */ + SND_PCI_QUIRK(0x103c, 0x0007, "HP omnibook 4150", NM_IGNORED), + /* Reset workarounds to avoid lock-ups */ + SND_PCI_QUIRK(0x104d, 0x8041, "Sony PCG-F305", NM_RESET_WORKAROUND), + SND_PCI_QUIRK(0x1028, 0x0080, "Dell Latitude LS", NM_RESET_WORKAROUND), + SND_PCI_QUIRK(0x1028, 0x0091, "Dell Latitude CSx", NM_RESET_WORKAROUND_2), + { } /* terminator */ +}; + + +static int snd_nm256_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct nm256 *chip; + int err; + const struct snd_pci_quirk *q; + + q = snd_pci_quirk_lookup(pci, nm256_quirks); + if (q) { + dev_dbg(&pci->dev, "Enabled quirk for %s.\n", + snd_pci_quirk_name(q)); + switch (q->value) { + case NM_IGNORED: + dev_info(&pci->dev, + "The device is on the denylist. Loading stopped\n"); + return -ENODEV; + case NM_RESET_WORKAROUND_2: + reset_workaround_2 = 1; + fallthrough; + case NM_RESET_WORKAROUND: + reset_workaround = 1; + break; + } + } + + err = snd_devm_card_new(&pci->dev, index, id, THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + switch (pci->device) { + case PCI_DEVICE_ID_NEOMAGIC_NM256AV_AUDIO: + strscpy(card->driver, "NM256AV"); + break; + case PCI_DEVICE_ID_NEOMAGIC_NM256ZX_AUDIO: + strscpy(card->driver, "NM256ZX"); + break; + case PCI_DEVICE_ID_NEOMAGIC_NM256XL_PLUS_AUDIO: + strscpy(card->driver, "NM256XL+"); + break; + default: + dev_err(&pci->dev, "invalid device id 0x%x\n", pci->device); + return -EINVAL; + } + + if (vaio_hack) + buffer_top = 0x25a800; /* this avoids conflicts with XFree86 server */ + + if (playback_bufsize < 4) + playback_bufsize = 4; + if (playback_bufsize > 128) + playback_bufsize = 128; + if (capture_bufsize < 4) + capture_bufsize = 4; + if (capture_bufsize > 128) + capture_bufsize = 128; + err = snd_nm256_create(card, pci); + if (err < 0) + return err; + + if (reset_workaround) { + dev_dbg(&pci->dev, "reset_workaround activated\n"); + chip->reset_workaround = 1; + } + + if (reset_workaround_2) { + dev_dbg(&pci->dev, "reset_workaround_2 activated\n"); + chip->reset_workaround_2 = 1; + } + + err = snd_nm256_pcm(chip, 0); + if (err < 0) + return err; + err = snd_nm256_mixer(chip); + if (err < 0) + return err; + + sprintf(card->shortname, "NeoMagic %s", card->driver); + sprintf(card->longname, "%s at 0x%lx & 0x%lx, irq %d", + card->shortname, + chip->buffer_addr, chip->cport_addr, chip->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + card->private_free = snd_nm256_free; + + pci_set_drvdata(pci, card); + return 0; +} + +static struct pci_driver nm256_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_nm256_ids, + .probe = snd_nm256_probe, + .driver = { + .pm = &nm256_pm, + }, +}; + +module_pci_driver(nm256_driver); diff --git a/sound/pci/nm256/nm256_coef.c b/sound/pci/nm256/nm256_coef.c new file mode 100644 index 000000000..f0599dbe8 --- /dev/null +++ b/sound/pci/nm256/nm256_coef.c @@ -0,0 +1,4608 @@ +// SPDX-License-Identifier: GPL-2.0 +#define NM_TOTAL_COEFF_COUNT 0x3158 + +static const char coefficients[NM_TOTAL_COEFF_COUNT * 4] = { + 0xFF, 0xFF, 0x2F, 0x00, 0x4B, 0xFF, 0xA5, 0x01, 0xEF, 0xFC, 0x21, + 0x05, 0x87, 0xF7, 0x62, 0x11, 0xE9, 0x45, 0x5E, 0xF9, 0xB5, 0x01, + 0xDE, 0xFF, 0xA4, 0xFF, 0x60, 0x00, 0xCA, 0xFF, 0x0D, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD6, 0x06, + 0x4C, 0xF3, 0xED, 0x20, 0x3D, 0x3D, 0x4A, 0xF3, 0x4E, 0x05, 0xB1, + 0xFD, 0xE1, 0x00, 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFD, 0xFF, + 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, + 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, + 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x02, 0x00, 0x05, + 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, 0xFD, 0x4E, 0x05, 0x4A, 0xF3, + 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, 0xD6, 0x06, 0x3D, 0xFC, 0xE6, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCA, 0xFF, + 0x60, 0x00, 0xA4, 0xFF, 0xDE, 0xFF, 0xB5, 0x01, 0x5E, 0xF9, 0xE9, + 0x45, 0x62, 0x11, 0x87, 0xF7, 0x21, 0x05, 0xEF, 0xFC, 0xA5, 0x01, + 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, + 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, + 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, + 0x01, 0x84, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, + 0xCA, 0x01, 0x95, 0xFC, 0xEA, 0x05, 0xBB, 0xF5, 0x25, 0x17, 0x3C, + 0x43, 0x8D, 0xF6, 0x43, 0x03, 0xF5, 0xFE, 0x26, 0x00, 0x20, 0x00, + 0xE2, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4D, 0xFF, 0xC5, + 0x01, 0x4C, 0xFC, 0x26, 0x07, 0xA3, 0xF1, 0xAB, 0x2C, 0xBB, 0x33, + 0x8F, 0xF1, 0xCA, 0x06, 0xA6, 0xFC, 0x85, 0x01, 0x6F, 0xFF, 0x24, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFE, 0xFF, 0xD5, 0xFF, 0xBC, 0x00, + 0xF0, 0xFD, 0xEC, 0x04, 0xD9, 0xF3, 0xB1, 0x3E, 0xCD, 0x1E, 0xC1, + 0xF3, 0xAF, 0x06, 0x49, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x16, 0x00, 0xA6, 0xFF, 0xBB, 0x00, 0xE9, 0xFE, 0x38, + 0x01, 0x4B, 0xFF, 0x28, 0xFE, 0x3A, 0x48, 0x04, 0x0A, 0x2E, 0xFA, + 0xDF, 0x03, 0x8A, 0xFD, 0x60, 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0x98, 0x01, 0x0D, 0xFD, + 0xE0, 0x04, 0x14, 0xF8, 0xC3, 0x0F, 0x89, 0x46, 0x4C, 0xFA, 0x38, + 0x01, 0x25, 0x00, 0x7D, 0xFF, 0x73, 0x00, 0xC2, 0xFF, 0x0F, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x0F, + 0x07, 0x84, 0xF2, 0x29, 0x25, 0x1A, 0x3A, 0x67, 0xF2, 0xF6, 0x05, + 0x41, 0xFD, 0x24, 0x01, 0xA1, 0xFF, 0x12, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x15, 0x00, 0x97, 0xFF, 0x37, 0x01, 0x22, 0xFD, 0x23, 0x06, + 0x2F, 0xF2, 0x11, 0x39, 0x7B, 0x26, 0x50, 0xF2, 0x1B, 0x07, 0x32, + 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, + 0xC8, 0xFF, 0x64, 0x00, 0x9B, 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, + 0xF9, 0x10, 0x46, 0x03, 0x11, 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, + 0xA2, 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, + 0x00, 0x6A, 0xFF, 0x53, 0x01, 0xA6, 0xFD, 0xA6, 0x03, 0xA1, 0xFA, + 0xDE, 0x08, 0x76, 0x48, 0x0C, 0xFF, 0xDE, 0xFE, 0x73, 0x01, 0xC9, + 0xFE, 0xCA, 0x00, 0xA0, 0xFF, 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE1, 0x01, 0x52, 0xFC, 0x93, 0x06, 0x10, 0xF4, 0x78, + 0x1D, 0x90, 0x3F, 0x3E, 0xF4, 0xAA, 0x04, 0x19, 0xFE, 0xA4, 0x00, + 0xE2, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x68, + 0xFF, 0x93, 0x01, 0x92, 0xFC, 0xE2, 0x06, 0x83, 0xF1, 0x8C, 0x32, + 0xED, 0x2D, 0x90, 0xF1, 0x1E, 0x07, 0x57, 0xFC, 0xBD, 0x01, 0x51, + 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE8, 0xFF, 0x12, 0x00, + 0x42, 0x00, 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, 0x76, + 0x18, 0x5C, 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, 0x03, 0x01, 0x53, + 0xFE, 0x53, 0x02, 0x39, 0xFD, 0xA9, 0x02, 0xF2, 0x48, 0xB9, 0x04, + 0x54, 0xFC, 0xCA, 0x02, 0x16, 0xFE, 0x20, 0x01, 0x7F, 0xFF, 0x20, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, 0xC3, 0x01, + 0xA7, 0xFC, 0xC0, 0x05, 0x1E, 0xF6, 0xD8, 0x15, 0xE7, 0x43, 0x20, + 0xF7, 0xEF, 0x02, 0x27, 0xFF, 0x0A, 0x00, 0x2E, 0x00, 0xDD, 0xFF, + 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCD, 0x01, 0x43, + 0xFC, 0x2A, 0x07, 0xBC, 0xF1, 0x64, 0x2B, 0xE3, 0x34, 0xA3, 0xF1, + 0xAE, 0x06, 0xBD, 0xFC, 0x77, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, + 0xFF, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, 0x00, 0xC8, 0xFD, + 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, 0x76, 0xF3, 0xC8, + 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x14, 0x00, 0xAC, 0xFF, 0xAC, 0x00, 0x08, 0xFF, 0xFD, 0x00, 0xB5, + 0xFF, 0x4B, 0xFD, 0xF4, 0x47, 0x30, 0x0B, 0xBC, 0xF9, 0x17, 0x04, + 0x6E, 0xFD, 0x6D, 0x01, 0x60, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x2C, 0x00, 0x54, 0xFF, 0x8D, 0x01, 0x26, 0xFD, 0xAD, 0x04, + 0x82, 0xF8, 0x87, 0x0E, 0xF9, 0x46, 0x0C, 0xFB, 0xD4, 0x00, 0x5D, + 0x00, 0x5E, 0xFF, 0x82, 0x00, 0xBD, 0xFF, 0x10, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0x01, 0x07, 0xBE, + 0xF2, 0xD6, 0x23, 0x1F, 0x3B, 0xA5, 0xF2, 0xC5, 0x05, 0x62, 0xFD, + 0x10, 0x01, 0xAB, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x19, + 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, 0xFD, 0x4D, 0x06, 0x00, 0xF2, + 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, 0x23, 0x07, 0x34, 0xFC, 0xDD, + 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xCE, 0xFF, + 0x56, 0x00, 0xB9, 0xFF, 0xB8, 0xFF, 0xF7, 0x01, 0xE2, 0xF8, 0x8D, + 0x45, 0x46, 0x12, 0x3C, 0xF7, 0x43, 0x05, 0xDF, 0xFC, 0xAC, 0x01, + 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, + 0xFF, 0x46, 0x01, 0xC3, 0xFD, 0x6D, 0x03, 0x14, 0xFB, 0xBE, 0x07, + 0xA6, 0x48, 0xF8, 0xFF, 0x70, 0xFE, 0xAE, 0x01, 0xAA, 0xFE, 0xD9, + 0x00, 0x9A, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xDE, 0x01, 0x5D, 0xFC, 0x74, 0x06, 0x63, 0xF4, 0x23, 0x1C, 0x66, + 0x40, 0xAA, 0xF4, 0x65, 0x04, 0x44, 0xFE, 0x8B, 0x00, 0xEE, 0xFF, + 0xF5, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, + 0x01, 0x80, 0xFC, 0xF7, 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, + 0x83, 0xF1, 0x13, 0x07, 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, + 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xED, 0xFF, 0x05, 0x00, 0x5D, 0x00, + 0x95, 0xFE, 0xE2, 0x03, 0x7F, 0xF5, 0xCC, 0x41, 0xC7, 0x19, 0xFF, + 0xF4, 0x37, 0x06, 0x75, 0xFC, 0xD6, 0x01, 0x39, 0xFF, 0x35, 0x00, + 0xFE, 0xFF, 0x1B, 0x00, 0x90, 0xFF, 0xF4, 0x00, 0x72, 0xFE, 0x18, + 0x02, 0xAA, 0xFD, 0xAB, 0x01, 0xDF, 0x48, 0xCA, 0x05, 0xE1, 0xFB, + 0x05, 0x03, 0xF7, 0xFD, 0x2E, 0x01, 0x79, 0xFF, 0x21, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x43, 0xFF, 0xBB, 0x01, 0xBA, 0xFC, + 0x95, 0x05, 0x83, 0xF6, 0x8C, 0x14, 0x87, 0x44, 0xBB, 0xF7, 0x98, + 0x02, 0x5A, 0xFF, 0xEE, 0xFF, 0x3C, 0x00, 0xD8, 0xFF, 0x0A, 0x00, + 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, + 0x07, 0xDC, 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, + 0xD5, 0xFC, 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x01, + 0x00, 0x07, 0x00, 0xBE, 0xFF, 0xEA, 0x00, 0xA2, 0xFD, 0x65, 0x05, + 0x28, 0xF3, 0xDB, 0x3C, 0x78, 0x21, 0x30, 0xF3, 0xDF, 0x06, 0x3A, + 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, + 0xB2, 0xFF, 0x9D, 0x00, 0x27, 0xFF, 0xC3, 0x00, 0x1F, 0x00, 0x76, + 0xFC, 0xA3, 0x47, 0x60, 0x0C, 0x4A, 0xF9, 0x4E, 0x04, 0x53, 0xFD, + 0x79, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, + 0x00, 0x58, 0xFF, 0x82, 0x01, 0x3F, 0xFD, 0x78, 0x04, 0xF2, 0xF8, + 0x50, 0x0D, 0x5E, 0x47, 0xD5, 0xFB, 0x6F, 0x00, 0x96, 0x00, 0x40, + 0xFF, 0x91, 0x00, 0xB7, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, 0xF2, 0x81, + 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, 0xFB, 0x00, + 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x85, + 0xFF, 0x5B, 0x01, 0xE9, 0xFC, 0x73, 0x06, 0xD8, 0xF1, 0xE5, 0x36, + 0x19, 0x29, 0xF8, 0xF1, 0x29, 0x07, 0x37, 0xFC, 0xD8, 0x01, 0x42, + 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD3, 0xFF, 0x47, 0x00, + 0xD7, 0xFF, 0x82, 0xFF, 0x53, 0x02, 0x39, 0xF8, 0xFD, 0x44, 0x8D, + 0x13, 0xD3, 0xF6, 0x72, 0x05, 0xCA, 0xFC, 0xB5, 0x01, 0x45, 0xFF, + 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x75, 0xFF, 0x39, + 0x01, 0xE0, 0xFD, 0x33, 0x03, 0x87, 0xFB, 0xA2, 0x06, 0xCB, 0x48, + 0xEA, 0x00, 0x01, 0xFE, 0xE9, 0x01, 0x8A, 0xFE, 0xE8, 0x00, 0x95, + 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x38, 0xFF, 0xDA, 0x01, + 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, 0xCE, 0x1A, 0x32, 0x41, 0x1F, + 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, 0x00, 0xFB, 0xFF, 0xF0, 0xFF, + 0x05, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5B, 0xFF, 0xAB, 0x01, 0x6F, + 0xFC, 0x08, 0x07, 0x7E, 0xF1, 0x21, 0x30, 0x67, 0x30, 0x7D, 0xF1, + 0x05, 0x07, 0x73, 0xFC, 0xA8, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, + 0xFF, 0x05, 0x00, 0xF2, 0xFF, 0xF8, 0xFF, 0x77, 0x00, 0x67, 0xFE, + 0x2D, 0x04, 0x04, 0xF5, 0x07, 0x41, 0x1B, 0x1B, 0xA6, 0xF4, 0x5A, + 0x06, 0x67, 0xFC, 0xDB, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, + 0x1A, 0x00, 0x96, 0xFF, 0xE5, 0x00, 0x91, 0xFE, 0xDC, 0x01, 0x1A, + 0xFE, 0xB3, 0x00, 0xC3, 0x48, 0xE1, 0x06, 0x6E, 0xFB, 0x40, 0x03, + 0xDA, 0xFD, 0x3C, 0x01, 0x74, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB3, 0x01, 0xCF, 0xFC, 0x67, 0x05, + 0xEA, 0xF6, 0x44, 0x13, 0x1E, 0x45, 0x5E, 0xF8, 0x3F, 0x02, 0x8E, + 0xFF, 0xD0, 0xFF, 0x4A, 0x00, 0xD2, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, + 0x33, 0x00, 0x41, 0xFF, 0xD9, 0x01, 0x36, 0xFC, 0x28, 0x07, 0x01, + 0xF2, 0xCE, 0x28, 0x23, 0x37, 0xE0, 0xF1, 0x6B, 0x06, 0xEF, 0xFC, + 0x57, 0x01, 0x87, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0B, + 0x00, 0xB4, 0xFF, 0x00, 0x01, 0x7E, 0xFD, 0x9C, 0x05, 0xDC, 0xF2, + 0xE4, 0x3B, 0xCD, 0x22, 0xEE, 0xF2, 0xF3, 0x06, 0x35, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, 0x00, 0xB8, 0xFF, + 0x8E, 0x00, 0x46, 0xFF, 0x8A, 0x00, 0x86, 0x00, 0xA7, 0xFB, 0x48, + 0x47, 0x95, 0x0D, 0xD9, 0xF8, 0x84, 0x04, 0x39, 0xFD, 0x85, 0x01, + 0x57, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, + 0xFF, 0x76, 0x01, 0x59, 0xFD, 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, + 0xB6, 0x47, 0xA4, 0xFC, 0x07, 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, + 0x00, 0xB1, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE6, 0x01, 0x3B, 0xFC, 0xDA, 0x06, 0x3F, 0xF3, 0x2C, 0x21, 0x11, + 0x3D, 0x3A, 0xF3, 0x58, 0x05, 0xAA, 0xFD, 0xE5, 0x00, 0xC1, 0xFF, + 0x06, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1F, 0x00, 0x7D, 0xFF, 0x6B, + 0x01, 0xCF, 0xFC, 0x96, 0x06, 0xB7, 0xF1, 0xC6, 0x35, 0x64, 0x2A, + 0xD4, 0xF1, 0x2B, 0x07, 0x3D, 0xFC, 0xD2, 0x01, 0x45, 0xFF, 0x32, + 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD9, 0xFF, 0x39, 0x00, 0xF4, 0xFF, + 0x4E, 0xFF, 0xAC, 0x02, 0x98, 0xF7, 0x65, 0x44, 0xD6, 0x14, 0x6C, + 0xF6, 0x9F, 0x05, 0xB6, 0xFC, 0xBD, 0x01, 0x42, 0xFF, 0x32, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, 0x2B, 0x01, 0xFE, + 0xFD, 0xF8, 0x02, 0xFB, 0xFB, 0x8D, 0x05, 0xE5, 0x48, 0xE3, 0x01, + 0x91, 0xFD, 0x25, 0x02, 0x6B, 0xFE, 0xF7, 0x00, 0x8F, 0xFF, 0x1C, + 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD5, 0x01, 0x78, 0xFC, + 0x2F, 0x06, 0x13, 0xF5, 0x7C, 0x19, 0xF7, 0x41, 0x9B, 0xF5, 0xD1, + 0x03, 0x9F, 0xFE, 0x57, 0x00, 0x08, 0x00, 0xEC, 0xFF, 0x06, 0x00, + 0xFD, 0xFF, 0x2D, 0x00, 0x55, 0xFF, 0xB5, 0x01, 0x61, 0xFC, 0x16, + 0x07, 0x85, 0xF1, 0xE6, 0x2E, 0x9E, 0x31, 0x7D, 0xF1, 0xF3, 0x06, + 0x84, 0xFC, 0x9D, 0x01, 0x63, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x04, + 0x00, 0xF6, 0xFF, 0xEB, 0xFF, 0x91, 0x00, 0x3B, 0xFE, 0x75, 0x04, + 0x92, 0xF4, 0x36, 0x40, 0x6E, 0x1C, 0x50, 0xF4, 0x7B, 0x06, 0x5B, + 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, + 0x9C, 0xFF, 0xD6, 0x00, 0xB1, 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, + 0xFF, 0x9C, 0x48, 0xFD, 0x07, 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, + 0x49, 0x01, 0x6E, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, + 0x00, 0x49, 0xFF, 0xAA, 0x01, 0xE4, 0xFC, 0x38, 0x05, 0x54, 0xF7, + 0xFE, 0x11, 0xAA, 0x45, 0x09, 0xF9, 0xE2, 0x01, 0xC4, 0xFF, 0xB3, + 0xFF, 0x59, 0x00, 0xCD, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x3E, 0xFF, 0xDE, 0x01, 0x33, 0xFC, 0x22, 0x07, 0x2B, 0xF2, 0x80, + 0x27, 0x3B, 0x38, 0x0A, 0xF2, 0x44, 0x06, 0x0B, 0xFD, 0x45, 0x01, + 0x90, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA9, + 0xFF, 0x15, 0x01, 0x5B, 0xFD, 0xD0, 0x05, 0x97, 0xF2, 0xE6, 0x3A, + 0x21, 0x24, 0xB1, 0xF2, 0x04, 0x07, 0x33, 0xFC, 0xE5, 0x01, 0x39, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBE, 0xFF, 0x7F, 0x00, + 0x65, 0xFF, 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, 0xCD, + 0x0E, 0x6A, 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, 0xFF, + 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x62, 0xFF, 0x6A, + 0x01, 0x74, 0xFD, 0x0A, 0x04, 0xD5, 0xF9, 0xED, 0x0A, 0x03, 0x48, + 0x7C, 0xFD, 0x9E, 0xFF, 0x0A, 0x01, 0x01, 0xFF, 0xAF, 0x00, 0xAB, + 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, + 0x42, 0xFC, 0xC3, 0x06, 0x87, 0xF3, 0xD7, 0x1F, 0xFE, 0x3D, 0x91, + 0xF3, 0x1D, 0x05, 0xD1, 0xFD, 0xCE, 0x00, 0xCC, 0xFF, 0x02, 0x00, + 0x02, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x75, 0xFF, 0x7A, 0x01, 0xB8, + 0xFC, 0xB4, 0x06, 0x9E, 0xF1, 0xA2, 0x34, 0xAD, 0x2B, 0xB6, 0xF1, + 0x29, 0x07, 0x45, 0xFC, 0xCB, 0x01, 0x49, 0xFF, 0x31, 0x00, 0xFD, + 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, 0x00, 0x1B, 0xFF, + 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, 0x07, 0xF6, 0xCA, + 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x20, 0x00, 0x80, 0xFF, 0x1C, 0x01, 0x1C, 0xFE, 0xBD, + 0x02, 0x6E, 0xFC, 0x7D, 0x04, 0xF3, 0x48, 0xE2, 0x02, 0x1F, 0xFD, + 0x60, 0x02, 0x4C, 0xFE, 0x06, 0x01, 0x89, 0xFF, 0x1D, 0x00, 0xFE, + 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCF, 0x01, 0x88, 0xFC, 0x09, 0x06, + 0x71, 0xF5, 0x2B, 0x18, 0xB2, 0x42, 0x20, 0xF6, 0x83, 0x03, 0xCF, + 0xFE, 0x3C, 0x00, 0x15, 0x00, 0xE6, 0xFF, 0x07, 0x00, 0xFD, 0xFF, + 0x2E, 0x00, 0x50, 0xFF, 0xBF, 0x01, 0x54, 0xFC, 0x20, 0x07, 0x94, + 0xF1, 0xA6, 0x2D, 0xD0, 0x32, 0x85, 0xF1, 0xDD, 0x06, 0x96, 0xFC, + 0x90, 0x01, 0x69, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFB, + 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, 0xFE, 0xB9, 0x04, 0x27, 0xF4, + 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, 0x99, 0x06, 0x50, 0xFC, 0xE2, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0xA2, 0xFF, + 0xC7, 0x00, 0xD0, 0xFE, 0x65, 0x01, 0xF6, 0xFE, 0xD9, 0xFE, 0x6A, + 0x48, 0x1F, 0x09, 0x87, 0xFA, 0xB3, 0x03, 0xA0, 0xFD, 0x56, 0x01, + 0x69, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4D, + 0xFF, 0xA0, 0x01, 0xFB, 0xFC, 0x07, 0x05, 0xBF, 0xF7, 0xBB, 0x10, + 0x2B, 0x46, 0xBB, 0xF9, 0x83, 0x01, 0xFA, 0xFF, 0x95, 0xFF, 0x68, + 0x00, 0xC7, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, + 0xE1, 0x01, 0x31, 0xFC, 0x19, 0x07, 0x5B, 0xF2, 0x30, 0x26, 0x4B, + 0x39, 0x3B, 0xF2, 0x1A, 0x06, 0x29, 0xFD, 0x33, 0x01, 0x99, 0xFF, + 0x15, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, + 0x01, 0x3A, 0xFD, 0x00, 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, + 0x79, 0xF2, 0x12, 0x07, 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, + 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC4, 0xFF, 0x70, 0x00, 0x84, 0xFF, + 0x19, 0x00, 0x4D, 0x01, 0x22, 0xFA, 0x70, 0x46, 0x0A, 0x10, 0xFC, + 0xF7, 0xEB, 0x04, 0x08, 0xFD, 0x9A, 0x01, 0x4F, 0xFF, 0x2E, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x66, 0xFF, 0x5E, 0x01, 0x90, + 0xFD, 0xD2, 0x03, 0x47, 0xFA, 0xC3, 0x09, 0x48, 0x48, 0x5A, 0xFE, + 0x33, 0xFF, 0x45, 0x01, 0xE2, 0xFE, 0xBE, 0x00, 0xA5, 0xFF, 0x16, + 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4B, 0xFC, + 0xA9, 0x06, 0xD2, 0xF3, 0x81, 0x1E, 0xE4, 0x3E, 0xEF, 0xF3, 0xDE, + 0x04, 0xF9, 0xFD, 0xB7, 0x00, 0xD8, 0xFF, 0xFD, 0xFF, 0x03, 0x00, + 0xFD, 0xFF, 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, + 0x06, 0x8C, 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, + 0x4E, 0xFC, 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x08, + 0x00, 0xE4, 0xFF, 0x1D, 0x00, 0x2D, 0x00, 0xEA, 0xFE, 0x56, 0x03, + 0x6D, 0xF6, 0x17, 0x43, 0x70, 0x17, 0xA6, 0xF5, 0xF3, 0x05, 0x91, + 0xFC, 0xCC, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1E, 0x00, + 0x86, 0xFF, 0x0E, 0x01, 0x3B, 0xFE, 0x82, 0x02, 0xE0, 0xFC, 0x73, + 0x03, 0xF6, 0x48, 0xE9, 0x03, 0xAD, 0xFC, 0x9C, 0x02, 0x2D, 0xFE, + 0x14, 0x01, 0x83, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, + 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x99, 0xFC, 0xE1, 0x05, 0xD1, 0xF5, + 0xDC, 0x16, 0x65, 0x43, 0xAD, 0xF6, 0x31, 0x03, 0x00, 0xFF, 0x20, + 0x00, 0x23, 0x00, 0xE1, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, + 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, 0xF1, 0x62, + 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, 0x82, 0x01, + 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0xFF, 0xFF, 0xD3, + 0xFF, 0xC1, 0x00, 0xE7, 0xFD, 0xFA, 0x04, 0xC4, 0xF3, 0x7E, 0x3E, + 0x19, 0x1F, 0xB0, 0xF3, 0xB5, 0x06, 0x47, 0xFC, 0xE4, 0x01, 0x36, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xA8, 0xFF, 0xB8, 0x00, + 0xF0, 0xFE, 0x2B, 0x01, 0x63, 0xFF, 0xF6, 0xFD, 0x2C, 0x48, 0x47, + 0x0A, 0x14, 0xFA, 0xEB, 0x03, 0x84, 0xFD, 0x63, 0x01, 0x64, 0xFF, + 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x96, + 0x01, 0x13, 0xFD, 0xD5, 0x04, 0x2C, 0xF8, 0x7D, 0x0F, 0xA3, 0x46, + 0x76, 0xFA, 0x22, 0x01, 0x32, 0x00, 0x76, 0xFF, 0x76, 0x00, 0xC1, + 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, 0xFF, 0xE4, 0x01, + 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, 0xDD, 0x24, 0x54, 0x3A, 0x74, + 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, 0x01, 0xA3, 0xFF, 0x11, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x95, 0xFF, 0x3B, 0x01, 0x1B, + 0xFD, 0x2D, 0x06, 0x24, 0xF2, 0xD3, 0x38, 0xC6, 0x26, 0x45, 0xF2, + 0x1D, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x0D, 0x00, 0xC9, 0xFF, 0x61, 0x00, 0xA2, 0xFF, 0xE2, 0xFF, + 0xAE, 0x01, 0x6B, 0xF9, 0xF2, 0x45, 0x4A, 0x11, 0x8F, 0xF7, 0x1D, + 0x05, 0xF1, 0xFC, 0xA4, 0x01, 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x25, 0x00, 0x6C, 0xFF, 0x51, 0x01, 0xAC, 0xFD, 0x9A, + 0x03, 0xBA, 0xFA, 0x9E, 0x08, 0x81, 0x48, 0x40, 0xFF, 0xC6, 0xFE, + 0x80, 0x01, 0xC2, 0xFE, 0xCE, 0x00, 0x9F, 0xFF, 0x17, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE1, 0x01, 0x55, 0xFC, 0x8C, 0x06, + 0x22, 0xF4, 0x2C, 0x1D, 0xC0, 0x3F, 0x55, 0xF4, 0x9B, 0x04, 0x23, + 0xFE, 0x9F, 0x00, 0xE4, 0xFF, 0xF9, 0xFF, 0x04, 0x00, 0xFD, 0xFF, + 0x27, 0x00, 0x66, 0xFF, 0x96, 0x01, 0x8E, 0xFC, 0xE7, 0x06, 0x81, + 0xF1, 0x48, 0x32, 0x34, 0x2E, 0x8D, 0xF1, 0x1C, 0x07, 0x5A, 0xFC, + 0xBB, 0x01, 0x53, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE9, + 0xFF, 0x0F, 0x00, 0x48, 0x00, 0xB9, 0xFE, 0xA6, 0x03, 0xE4, 0xF5, + 0x60, 0x42, 0xC1, 0x18, 0x47, 0xF5, 0x1A, 0x06, 0x81, 0xFC, 0xD2, + 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8B, 0xFF, + 0xFF, 0x00, 0x5A, 0xFE, 0x46, 0x02, 0x52, 0xFD, 0x70, 0x02, 0xED, + 0x48, 0xF5, 0x04, 0x3B, 0xFC, 0xD7, 0x02, 0x0F, 0xFE, 0x23, 0x01, + 0x7E, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, + 0xFF, 0xC1, 0x01, 0xAB, 0xFC, 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, + 0x0B, 0x44, 0x42, 0xF7, 0xDC, 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, + 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x47, 0xFF, + 0xCE, 0x01, 0x41, 0xFC, 0x2A, 0x07, 0xC2, 0xF1, 0x1B, 0x2B, 0x25, + 0x35, 0xA8, 0xF1, 0xA7, 0x06, 0xC2, 0xFC, 0x74, 0x01, 0x78, 0xFF, + 0x20, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x04, 0x00, 0xC7, 0xFF, 0xD9, + 0x00, 0xBF, 0xFD, 0x38, 0x05, 0x69, 0xF3, 0x96, 0x3D, 0x6F, 0x20, + 0x66, 0xF3, 0xCE, 0x06, 0x3F, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAE, 0xFF, 0xA9, 0x00, 0x0F, 0xFF, + 0xF0, 0x00, 0xCD, 0xFF, 0x1B, 0xFD, 0xE4, 0x47, 0x73, 0x0B, 0xA2, + 0xF9, 0x23, 0x04, 0x68, 0xFD, 0x70, 0x01, 0x5F, 0xFF, 0x29, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x55, 0xFF, 0x8B, 0x01, 0x2B, + 0xFD, 0xA1, 0x04, 0x9B, 0xF8, 0x42, 0x0E, 0x0F, 0x47, 0x38, 0xFB, + 0xBE, 0x00, 0x6A, 0x00, 0x58, 0xFF, 0x85, 0x00, 0xBB, 0xFF, 0x10, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, + 0xFD, 0x06, 0xCB, 0xF2, 0x8A, 0x23, 0x58, 0x3B, 0xB4, 0xF2, 0xBA, + 0x05, 0x6A, 0xFD, 0x0B, 0x01, 0xAE, 0xFF, 0x0D, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x19, 0x00, 0x8C, 0xFF, 0x4D, 0x01, 0xFE, 0xFC, 0x56, + 0x06, 0xF7, 0xF1, 0xBF, 0x37, 0x15, 0x28, 0x18, 0xF2, 0x25, 0x07, + 0x34, 0xFC, 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, + 0x00, 0xCF, 0xFF, 0x52, 0x00, 0xC0, 0xFF, 0xAC, 0xFF, 0x0C, 0x02, + 0xBC, 0xF8, 0x6D, 0x45, 0x8E, 0x12, 0x24, 0xF7, 0x4D, 0x05, 0xDB, + 0xFC, 0xAE, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x24, 0x00, 0x71, 0xFF, 0x43, 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, + 0xFB, 0x7E, 0x07, 0xAF, 0x48, 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, + 0xA3, 0xFE, 0xDD, 0x00, 0x99, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xDD, 0x01, 0x60, 0xFC, 0x6D, 0x06, 0x76, 0xF4, + 0xD8, 0x1B, 0x95, 0x40, 0xC3, 0xF4, 0x56, 0x04, 0x4E, 0xFE, 0x85, + 0x00, 0xF1, 0xFF, 0xF4, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x29, 0x00, + 0x60, 0xFF, 0xA2, 0x01, 0x7C, 0xFC, 0xFB, 0x06, 0x7C, 0xF1, 0x15, + 0x31, 0x73, 0x2F, 0x81, 0xF1, 0x10, 0x07, 0x67, 0xFC, 0xB1, 0x01, + 0x58, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x02, + 0x00, 0x63, 0x00, 0x8A, 0xFE, 0xF3, 0x03, 0x63, 0xF5, 0xA1, 0x41, + 0x12, 0x1A, 0xEB, 0xF4, 0x3F, 0x06, 0x72, 0xFC, 0xD7, 0x01, 0x39, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x91, 0xFF, 0xF1, 0x00, + 0x79, 0xFE, 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, 0x07, + 0x06, 0xC7, 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, 0xFF, + 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x43, 0xFF, 0xBA, + 0x01, 0xBF, 0xFC, 0x8B, 0x05, 0x99, 0xF6, 0x43, 0x14, 0xA9, 0x44, + 0xDE, 0xF7, 0x85, 0x02, 0x65, 0xFF, 0xE7, 0xFF, 0x3F, 0x00, 0xD6, + 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD5, 0x01, + 0x3A, 0xFC, 0x2A, 0x07, 0xE3, 0xF1, 0xD1, 0x29, 0x46, 0x36, 0xC5, + 0xF1, 0x87, 0x06, 0xDA, 0xFC, 0x64, 0x01, 0x80, 0xFF, 0x1E, 0x00, + 0xFE, 0xFF, 0x01, 0x00, 0x08, 0x00, 0xBC, 0xFF, 0xEF, 0x00, 0x9A, + 0xFD, 0x72, 0x05, 0x16, 0xF3, 0xA5, 0x3C, 0xC4, 0x21, 0x21, 0xF3, + 0xE4, 0x06, 0x39, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, 0xFF, 0xB6, 0x00, + 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, 0x31, 0xF9, 0x5A, + 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, 0x80, 0x01, 0x45, 0xFD, 0x6C, + 0x04, 0x0B, 0xF9, 0x0B, 0x0D, 0x73, 0x47, 0x02, 0xFC, 0x58, 0x00, + 0xA3, 0x00, 0x39, 0xFF, 0x94, 0x00, 0xB5, 0xFF, 0x12, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x37, 0xFC, 0xEB, 0x06, + 0x0B, 0xF3, 0x35, 0x22, 0x52, 0x3C, 0xFD, 0xF2, 0x84, 0x05, 0x8D, + 0xFD, 0xF6, 0x00, 0xB8, 0xFF, 0x09, 0x00, 0x01, 0x00, 0xFE, 0xFF, + 0x1D, 0x00, 0x83, 0xFF, 0x5E, 0x01, 0xE3, 0xFC, 0x7B, 0x06, 0xD0, + 0xF1, 0xA5, 0x36, 0x62, 0x29, 0xEF, 0xF1, 0x29, 0x07, 0x39, 0xFC, + 0xD7, 0x01, 0x42, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD5, + 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, 0xFF, 0x67, 0x02, 0x14, 0xF8, + 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, 0x7C, 0x05, 0xC5, 0xFC, 0xB7, + 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, + 0x76, 0xFF, 0x35, 0x01, 0xE7, 0xFD, 0x26, 0x03, 0xA1, 0xFB, 0x64, + 0x06, 0xD2, 0x48, 0x21, 0x01, 0xE8, 0xFD, 0xF7, 0x01, 0x83, 0xFE, + 0xEC, 0x00, 0x93, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, + 0xFF, 0xD9, 0x01, 0x6D, 0xFC, 0x4B, 0x06, 0xCD, 0xF4, 0x83, 0x1A, + 0x5F, 0x41, 0x3A, 0xF5, 0x0C, 0x04, 0x7B, 0xFE, 0x6C, 0x00, 0xFE, + 0xFF, 0xEF, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5A, 0xFF, + 0xAD, 0x01, 0x6C, 0xFC, 0x0C, 0x07, 0x7F, 0xF1, 0xDC, 0x2F, 0xAD, + 0x30, 0x7D, 0xF1, 0x01, 0x07, 0x76, 0xFC, 0xA6, 0x01, 0x5E, 0xFF, + 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, + 0x00, 0x5D, 0xFE, 0x3E, 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, + 0x93, 0xF4, 0x62, 0x06, 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x97, 0xFF, 0xE2, 0x00, 0x98, 0xFE, + 0xCF, 0x01, 0x33, 0xFE, 0x7D, 0x00, 0xBB, 0x48, 0x1F, 0x07, 0x54, + 0xFB, 0x4C, 0x03, 0xD3, 0xFD, 0x3F, 0x01, 0x73, 0xFF, 0x23, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB1, 0x01, 0xD3, + 0xFC, 0x5D, 0x05, 0x01, 0xF7, 0xFB, 0x12, 0x3F, 0x45, 0x83, 0xF8, + 0x2A, 0x02, 0x9A, 0xFF, 0xCA, 0xFF, 0x4E, 0x00, 0xD1, 0xFF, 0x0C, + 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x40, 0xFF, 0xDA, 0x01, 0x35, 0xFC, + 0x27, 0x07, 0x09, 0xF2, 0x85, 0x28, 0x63, 0x37, 0xE9, 0xF1, 0x63, + 0x06, 0xF5, 0xFC, 0x53, 0x01, 0x89, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, + 0x00, 0x00, 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, + 0x05, 0xCC, 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, + 0x35, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, + 0x00, 0xB9, 0xFF, 0x8A, 0x00, 0x4D, 0xFF, 0x7D, 0x00, 0x9C, 0x00, + 0x7B, 0xFB, 0x31, 0x47, 0xD9, 0x0D, 0xC0, 0xF8, 0x8F, 0x04, 0x34, + 0xFD, 0x87, 0x01, 0x56, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x5E, 0xFF, 0x74, 0x01, 0x5F, 0xFD, 0x35, 0x04, 0x7C, + 0xF9, 0xD8, 0x0B, 0xC9, 0x47, 0xD4, 0xFC, 0xF0, 0xFF, 0xDD, 0x00, + 0x19, 0xFF, 0xA4, 0x00, 0xAF, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD5, 0x06, 0x4F, 0xF3, + 0xE0, 0x20, 0x45, 0x3D, 0x4D, 0xF3, 0x4B, 0x05, 0xB3, 0xFD, 0xE0, + 0x00, 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, + 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, 0xF1, 0x86, + 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, 0xD1, 0x01, + 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xDA, 0xFF, 0x36, + 0x00, 0xFA, 0xFF, 0x43, 0xFF, 0xBF, 0x02, 0x75, 0xF7, 0x42, 0x44, + 0x20, 0x15, 0x55, 0xF6, 0xA9, 0x05, 0xB2, 0xFC, 0xBF, 0x01, 0x41, + 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7C, 0xFF, + 0x27, 0x01, 0x05, 0xFE, 0xEB, 0x02, 0x14, 0xFC, 0x50, 0x05, 0xEA, + 0x48, 0x1B, 0x02, 0x78, 0xFD, 0x32, 0x02, 0x64, 0xFE, 0xFA, 0x00, + 0x8D, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD4, + 0x01, 0x7C, 0xFC, 0x27, 0x06, 0x28, 0xF5, 0x31, 0x19, 0x21, 0x42, + 0xB8, 0xF5, 0xC0, 0x03, 0xAA, 0xFE, 0x51, 0x00, 0x0B, 0x00, 0xEA, + 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x54, 0xFF, 0xB7, 0x01, + 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, 0x9F, 0x2E, 0xE3, 0x31, 0x7E, + 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, 0x01, 0x64, 0xFF, 0x28, 0x00, + 0xFD, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xE8, 0xFF, 0x96, 0x00, 0x31, + 0xFE, 0x84, 0x04, 0x79, 0xF4, 0x07, 0x40, 0xBA, 0x1C, 0x3E, 0xF4, + 0x82, 0x06, 0x58, 0xFC, 0xE0, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0x18, 0x00, 0x9D, 0xFF, 0xD3, 0x00, 0xB8, 0xFE, 0x93, 0x01, + 0xA1, 0xFE, 0x8E, 0xFF, 0x92, 0x48, 0x3D, 0x08, 0xE1, 0xFA, 0x86, + 0x03, 0xB6, 0xFD, 0x4C, 0x01, 0x6D, 0xFF, 0x25, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xA8, 0x01, 0xE9, 0xFC, 0x2D, + 0x05, 0x6B, 0xF7, 0xB6, 0x11, 0xC8, 0x45, 0x30, 0xF9, 0xCD, 0x01, + 0xD0, 0xFF, 0xAC, 0xFF, 0x5C, 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDF, 0x01, 0x33, 0xFC, 0x20, 0x07, + 0x35, 0xF2, 0x36, 0x27, 0x78, 0x38, 0x14, 0xF2, 0x3B, 0x06, 0x11, + 0xFD, 0x41, 0x01, 0x92, 0xFF, 0x17, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x10, 0x00, 0xA7, 0xFF, 0x19, 0x01, 0x53, 0xFD, 0xDB, 0x05, 0x88, + 0xF2, 0xAD, 0x3A, 0x6D, 0x24, 0xA4, 0xF2, 0x08, 0x07, 0x32, 0xFC, + 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBF, + 0xFF, 0x7B, 0x00, 0x6C, 0xFF, 0x44, 0x00, 0x01, 0x01, 0xB6, 0xFA, + 0xC8, 0x46, 0x13, 0x0F, 0x51, 0xF8, 0xC4, 0x04, 0x1B, 0xFD, 0x92, + 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, + 0x63, 0xFF, 0x67, 0x01, 0x7A, 0xFD, 0xFE, 0x03, 0xEE, 0xF9, 0xAA, + 0x0A, 0x16, 0x48, 0xAC, 0xFD, 0x86, 0xFF, 0x17, 0x01, 0xFA, 0xFE, + 0xB3, 0x00, 0xAA, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE5, 0x01, 0x44, 0xFC, 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, + 0x31, 0x3E, 0xA5, 0xF3, 0x0F, 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, + 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x73, 0xFF, + 0x7D, 0x01, 0xB3, 0xFC, 0xBB, 0x06, 0x9A, 0xF1, 0x60, 0x34, 0xF5, + 0x2B, 0xB0, 0xF1, 0x28, 0x07, 0x47, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, + 0x30, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDF, 0xFF, 0x28, 0x00, 0x17, + 0x00, 0x10, 0xFF, 0x15, 0x03, 0xDD, 0xF6, 0x9E, 0x43, 0x6C, 0x16, + 0xF1, 0xF5, 0xD3, 0x05, 0x9F, 0xFC, 0xC6, 0x01, 0x3F, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, 0xFF, 0x19, 0x01, + 0x23, 0xFE, 0xB0, 0x02, 0x87, 0xFC, 0x41, 0x04, 0xF4, 0x48, 0x1C, + 0x03, 0x06, 0xFD, 0x6E, 0x02, 0x45, 0xFE, 0x09, 0x01, 0x88, 0xFF, + 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCE, 0x01, 0x8C, + 0xFC, 0x00, 0x06, 0x86, 0xF5, 0xE0, 0x17, 0xDB, 0x42, 0x3F, 0xF6, + 0x71, 0x03, 0xD9, 0xFE, 0x36, 0x00, 0x18, 0x00, 0xE5, 0xFF, 0x07, + 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4F, 0xFF, 0xC1, 0x01, 0x52, 0xFC, + 0x22, 0x07, 0x98, 0xF1, 0x5E, 0x2D, 0x13, 0x33, 0x87, 0xF1, 0xD8, + 0x06, 0x9B, 0xFC, 0x8D, 0x01, 0x6B, 0xFF, 0x25, 0x00, 0xFD, 0xFF, + 0x03, 0x00, 0xFC, 0xFF, 0xDC, 0xFF, 0xAF, 0x00, 0x07, 0xFE, 0xC8, + 0x04, 0x10, 0xF4, 0x2D, 0x3F, 0x0F, 0x1E, 0xED, 0xF3, 0xA0, 0x06, + 0x4E, 0xFC, 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, + 0x00, 0xA3, 0xFF, 0xC3, 0x00, 0xD7, 0xFE, 0x58, 0x01, 0x0F, 0xFF, + 0xA6, 0xFE, 0x5D, 0x48, 0x61, 0x09, 0x6E, 0xFA, 0xC0, 0x03, 0x99, + 0xFD, 0x59, 0x01, 0x68, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x2E, 0x00, 0x4E, 0xFF, 0x9E, 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, + 0xF7, 0x75, 0x10, 0x48, 0x46, 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, + 0x8E, 0xFF, 0x6B, 0x00, 0xC6, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, + 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x16, 0x07, 0x67, 0xF2, + 0xE5, 0x25, 0x87, 0x39, 0x47, 0xF2, 0x10, 0x06, 0x30, 0xFD, 0x2F, + 0x01, 0x9C, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x13, 0x00, + 0x9D, 0xFF, 0x2D, 0x01, 0x33, 0xFD, 0x0B, 0x06, 0x4D, 0xF2, 0xA5, + 0x39, 0xBF, 0x25, 0x6D, 0xF2, 0x15, 0x07, 0x31, 0xFC, 0xE2, 0x01, + 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, 0xC5, 0xFF, 0x6D, + 0x00, 0x8B, 0xFF, 0x0D, 0x00, 0x63, 0x01, 0xF9, 0xF9, 0x55, 0x46, + 0x51, 0x10, 0xE3, 0xF7, 0xF7, 0x04, 0x03, 0xFD, 0x9D, 0x01, 0x4E, + 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, + 0x5B, 0x01, 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, 0x57, + 0x48, 0x8D, 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, 0x00, + 0xA4, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, + 0x01, 0x4D, 0xFC, 0xA3, 0x06, 0xE4, 0xF3, 0x36, 0x1E, 0x16, 0x3F, + 0x05, 0xF4, 0xCF, 0x04, 0x02, 0xFE, 0xB2, 0x00, 0xDB, 0xFF, 0xFC, + 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6C, 0xFF, 0x8B, 0x01, + 0x9D, 0xFC, 0xD5, 0x06, 0x89, 0xF1, 0x35, 0x33, 0x3A, 0x2D, 0x9A, + 0xF1, 0x23, 0x07, 0x51, 0xFC, 0xC2, 0x01, 0x4F, 0xFF, 0x2F, 0x00, + 0xFD, 0xFF, 0x07, 0x00, 0xE5, 0xFF, 0x1A, 0x00, 0x33, 0x00, 0xDF, + 0xFE, 0x68, 0x03, 0x4E, 0xF6, 0xEE, 0x42, 0xBB, 0x17, 0x90, 0xF5, + 0xFC, 0x05, 0x8E, 0xFC, 0xCD, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, 0xFE, 0x74, 0x02, + 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, 0x94, 0xFC, 0xA9, + 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, 0xC7, 0x01, 0x9D, 0xFC, 0xD8, + 0x05, 0xE7, 0xF5, 0x91, 0x16, 0x89, 0x43, 0xCD, 0xF6, 0x1E, 0x03, + 0x0B, 0xFF, 0x1A, 0x00, 0x26, 0x00, 0xE0, 0xFF, 0x08, 0x00, 0xFD, + 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC9, 0x01, 0x48, 0xFC, 0x28, 0x07, + 0xAD, 0xF1, 0x19, 0x2C, 0x3F, 0x34, 0x97, 0xF1, 0xBE, 0x06, 0xB0, + 0xFC, 0x7F, 0x01, 0x72, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x02, 0x00, + 0x00, 0x00, 0xD0, 0xFF, 0xC7, 0x00, 0xDE, 0xFD, 0x08, 0x05, 0xB0, + 0xF3, 0x4A, 0x3E, 0x64, 0x1F, 0xA0, 0xF3, 0xBB, 0x06, 0x45, 0xFC, + 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xA9, + 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, 0x01, 0x7A, 0xFF, 0xC5, 0xFD, + 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, 0xF8, 0x03, 0x7D, 0xFD, 0x66, + 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, + 0x52, 0xFF, 0x93, 0x01, 0x18, 0xFD, 0xC9, 0x04, 0x45, 0xF8, 0x36, + 0x0F, 0xBB, 0x46, 0xA1, 0xFA, 0x0C, 0x01, 0x3E, 0x00, 0x70, 0xFF, + 0x7A, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, + 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x09, 0x07, 0x9D, 0xF2, 0x92, 0x24, + 0x8F, 0x3A, 0x82, 0xF2, 0xE1, 0x05, 0x50, 0xFD, 0x1B, 0x01, 0xA6, + 0xFF, 0x10, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x17, 0x00, 0x93, 0xFF, + 0x3F, 0x01, 0x15, 0xFD, 0x36, 0x06, 0x19, 0xF2, 0x97, 0x38, 0x11, + 0x27, 0x3B, 0xF2, 0x1F, 0x07, 0x32, 0xFC, 0xDF, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, + 0xFF, 0xD6, 0xFF, 0xC3, 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, + 0x77, 0xF7, 0x28, 0x05, 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6D, 0xFF, 0x4E, 0x01, + 0xB3, 0xFD, 0x8D, 0x03, 0xD4, 0xFA, 0x5D, 0x08, 0x8D, 0x48, 0x74, + 0xFF, 0xAE, 0xFE, 0x8D, 0x01, 0xBB, 0xFE, 0xD1, 0x00, 0x9E, 0xFF, + 0x18, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x57, + 0xFC, 0x85, 0x06, 0x34, 0xF4, 0xE0, 0x1C, 0xF0, 0x3F, 0x6D, 0xF4, + 0x8C, 0x04, 0x2C, 0xFE, 0x99, 0x00, 0xE7, 0xFF, 0xF8, 0xFF, 0x04, + 0x00, 0xFD, 0xFF, 0x27, 0x00, 0x65, 0xFF, 0x98, 0x01, 0x8A, 0xFC, + 0xEC, 0x06, 0x7F, 0xF1, 0x04, 0x32, 0x7B, 0x2E, 0x8A, 0xF1, 0x1A, + 0x07, 0x5D, 0xFC, 0xB8, 0x01, 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, + 0x06, 0x00, 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, + 0x03, 0xC7, 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, + 0x7D, 0xFC, 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, + 0x00, 0x8D, 0xFF, 0xFC, 0x00, 0x61, 0xFE, 0x39, 0x02, 0x6B, 0xFD, + 0x37, 0x02, 0xEB, 0x48, 0x31, 0x05, 0x21, 0xFC, 0xE4, 0x02, 0x08, + 0xFE, 0x26, 0x01, 0x7C, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x32, 0x00, 0x41, 0xFF, 0xC0, 0x01, 0xAF, 0xFC, 0xAD, 0x05, 0x4A, + 0xF6, 0x44, 0x15, 0x2F, 0x44, 0x64, 0xF7, 0xC9, 0x02, 0x3D, 0xFF, + 0xFE, 0xFF, 0x34, 0x00, 0xDB, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x32, + 0x00, 0x47, 0xFF, 0xD0, 0x01, 0x40, 0xFC, 0x2A, 0x07, 0xCA, 0xF1, + 0xD1, 0x2A, 0x65, 0x35, 0xAE, 0xF1, 0xA0, 0x06, 0xC7, 0xFC, 0x70, + 0x01, 0x7A, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x05, 0x00, + 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, 0xF3, 0x61, + 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, 0xE6, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA5, + 0x00, 0x16, 0xFF, 0xE3, 0x00, 0xE4, 0xFF, 0xEB, 0xFC, 0xD2, 0x47, + 0xB6, 0x0B, 0x89, 0xF9, 0x2F, 0x04, 0x62, 0xFD, 0x72, 0x01, 0x5E, + 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x56, 0xFF, + 0x88, 0x01, 0x31, 0xFD, 0x95, 0x04, 0xB4, 0xF8, 0xFC, 0x0D, 0x26, + 0x47, 0x64, 0xFB, 0xA7, 0x00, 0x77, 0x00, 0x51, 0xFF, 0x89, 0x00, + 0xBA, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, + 0x01, 0x34, 0xFC, 0xF9, 0x06, 0xD9, 0xF2, 0x3F, 0x23, 0x90, 0x3B, + 0xC4, 0xF2, 0xAE, 0x05, 0x72, 0xFD, 0x07, 0x01, 0xB0, 0xFF, 0x0C, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8A, 0xFF, 0x51, 0x01, + 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, 0x82, 0x37, 0x60, 0x28, 0x0E, + 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, 0xFF, 0x34, 0x00, + 0xFD, 0xFF, 0x0C, 0x00, 0xD0, 0xFF, 0x4F, 0x00, 0xC7, 0xFF, 0xA0, + 0xFF, 0x20, 0x02, 0x96, 0xF8, 0x4E, 0x45, 0xD7, 0x12, 0x0D, 0xF7, + 0x58, 0x05, 0xD6, 0xFC, 0xB0, 0x01, 0x47, 0xFF, 0x30, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x23, 0x00, 0x72, 0xFF, 0x40, 0x01, 0xD0, 0xFD, + 0x53, 0x03, 0x47, 0xFB, 0x3F, 0x07, 0xB8, 0x48, 0x62, 0x00, 0x3F, + 0xFE, 0xC8, 0x01, 0x9C, 0xFE, 0xE0, 0x00, 0x98, 0xFF, 0x19, 0x00, + 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDC, 0x01, 0x63, 0xFC, 0x66, + 0x06, 0x89, 0xF4, 0x8C, 0x1B, 0xC3, 0x40, 0xDD, 0xF4, 0x46, 0x04, + 0x58, 0xFE, 0x80, 0x00, 0xF4, 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFD, + 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA5, 0x01, 0x78, 0xFC, 0xFF, 0x06, + 0x7D, 0xF1, 0xCF, 0x30, 0xB8, 0x2F, 0x80, 0xF1, 0x0D, 0x07, 0x6A, + 0xFC, 0xAE, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, + 0xEF, 0xFF, 0xFF, 0xFF, 0x69, 0x00, 0x80, 0xFE, 0x04, 0x04, 0x48, + 0xF5, 0x74, 0x41, 0x5D, 0x1A, 0xD7, 0xF4, 0x47, 0x06, 0x6F, 0xFC, + 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x93, + 0xFF, 0xED, 0x00, 0x80, 0xFE, 0xFD, 0x01, 0xDC, 0xFD, 0x3C, 0x01, + 0xD5, 0x48, 0x45, 0x06, 0xAE, 0xFB, 0x1F, 0x03, 0xEA, 0xFD, 0x34, + 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, + 0x44, 0xFF, 0xB8, 0x01, 0xC3, 0xFC, 0x81, 0x05, 0xB0, 0xF6, 0xFA, + 0x13, 0xCC, 0x44, 0x02, 0xF8, 0x71, 0x02, 0x71, 0xFF, 0xE1, 0xFF, + 0x42, 0x00, 0xD5, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x43, + 0xFF, 0xD6, 0x01, 0x39, 0xFC, 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, + 0x85, 0x36, 0xCC, 0xF1, 0x7F, 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, + 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x09, 0x00, 0xBA, 0xFF, + 0xF4, 0x00, 0x91, 0xFD, 0x7E, 0x05, 0x05, 0xF3, 0x6E, 0x3C, 0x10, + 0x22, 0x12, 0xF3, 0xE9, 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB5, 0xFF, 0x96, 0x00, 0x35, + 0xFF, 0xA9, 0x00, 0x4D, 0x00, 0x19, 0xFC, 0x7C, 0x47, 0xE8, 0x0C, + 0x18, 0xF9, 0x66, 0x04, 0x48, 0xFD, 0x7E, 0x01, 0x5A, 0xFF, 0x2B, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5A, 0xFF, 0x7D, 0x01, + 0x4B, 0xFD, 0x60, 0x04, 0x24, 0xF9, 0xC6, 0x0C, 0x86, 0x47, 0x30, + 0xFC, 0x41, 0x00, 0xB0, 0x00, 0x32, 0xFF, 0x98, 0x00, 0xB4, 0xFF, + 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x38, + 0xFC, 0xE6, 0x06, 0x19, 0xF3, 0xEA, 0x21, 0x8A, 0x3C, 0x0E, 0xF3, + 0x78, 0x05, 0x96, 0xFD, 0xF1, 0x00, 0xBB, 0xFF, 0x08, 0x00, 0x01, + 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x81, 0xFF, 0x62, 0x01, 0xDD, 0xFC, + 0x83, 0x06, 0xC9, 0xF1, 0x66, 0x36, 0xAC, 0x29, 0xE7, 0xF1, 0x2A, + 0x07, 0x3A, 0xFC, 0xD5, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x0B, 0x00, 0xD6, 0xFF, 0x41, 0x00, 0xE4, 0xFF, 0x6B, 0xFF, 0x7B, + 0x02, 0xF0, 0xF7, 0xBA, 0x44, 0x1E, 0x14, 0xA5, 0xF6, 0x86, 0x05, + 0xC1, 0xFC, 0xB9, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x22, 0x00, 0x77, 0xFF, 0x32, 0x01, 0xED, 0xFD, 0x19, 0x03, + 0xBB, 0xFB, 0x26, 0x06, 0xD7, 0x48, 0x58, 0x01, 0xCF, 0xFD, 0x04, + 0x02, 0x7D, 0xFE, 0xEF, 0x00, 0x92, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, + 0x35, 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, + 0xF4, 0x38, 0x1A, 0x8C, 0x41, 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, + 0x66, 0x00, 0x01, 0x00, 0xEE, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2B, + 0x00, 0x59, 0xFF, 0xB0, 0x01, 0x69, 0xFC, 0x0F, 0x07, 0x80, 0xF1, + 0x96, 0x2F, 0xF2, 0x30, 0x7C, 0xF1, 0xFD, 0x06, 0x7A, 0xFC, 0xA3, + 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF4, 0xFF, + 0xF2, 0xFF, 0x83, 0x00, 0x53, 0xFE, 0x4E, 0x04, 0xD0, 0xF4, 0xAB, + 0x40, 0xB2, 0x1B, 0x7F, 0xF4, 0x69, 0x06, 0x62, 0xFC, 0xDD, 0x01, + 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x98, 0xFF, 0xDE, + 0x00, 0x9F, 0xFE, 0xC2, 0x01, 0x4B, 0xFE, 0x48, 0x00, 0xB3, 0x48, + 0x5E, 0x07, 0x3B, 0xFB, 0x59, 0x03, 0xCD, 0xFD, 0x42, 0x01, 0x71, + 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x47, 0xFF, + 0xAF, 0x01, 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, 0x5C, + 0x45, 0xA9, 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, 0x00, + 0xD0, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x40, 0xFF, 0xDB, + 0x01, 0x35, 0xFC, 0x25, 0x07, 0x13, 0xF2, 0x3A, 0x28, 0xA0, 0x37, + 0xF2, 0xF1, 0x5A, 0x06, 0xFB, 0xFC, 0x4F, 0x01, 0x8B, 0xFF, 0x1A, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0D, 0x00, 0xAF, 0xFF, 0x09, 0x01, + 0x6E, 0xFD, 0xB4, 0x05, 0xBC, 0xF2, 0x73, 0x3B, 0x64, 0x23, 0xD2, + 0xF2, 0xFB, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x11, 0x00, 0xBB, 0xFF, 0x87, 0x00, 0x54, 0xFF, 0x70, + 0x00, 0xB3, 0x00, 0x4E, 0xFB, 0x1A, 0x47, 0x1F, 0x0E, 0xA8, 0xF8, + 0x9B, 0x04, 0x2E, 0xFD, 0x8A, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, 0x01, 0x65, 0xFD, + 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, 0x03, 0xFD, 0xD9, + 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, 0xFF, 0x14, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3E, 0xFC, 0xD0, + 0x06, 0x5E, 0xF3, 0x94, 0x20, 0x7B, 0x3D, 0x60, 0xF3, 0x3E, 0x05, + 0xBB, 0xFD, 0xDB, 0x00, 0xC6, 0xFF, 0x04, 0x00, 0x02, 0x00, 0xFE, + 0xFF, 0x20, 0x00, 0x79, 0xFF, 0x72, 0x01, 0xC4, 0xFC, 0xA4, 0x06, + 0xAB, 0xF1, 0x46, 0x35, 0xF7, 0x2A, 0xC6, 0xF1, 0x2A, 0x07, 0x40, + 0xFC, 0xCF, 0x01, 0x47, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, + 0xDB, 0xFF, 0x33, 0x00, 0x01, 0x00, 0x38, 0xFF, 0xD3, 0x02, 0x53, + 0xF7, 0x1F, 0x44, 0x69, 0x15, 0x3F, 0xF6, 0xB2, 0x05, 0xAD, 0xFC, + 0xC1, 0x01, 0x41, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, + 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, 0xFE, 0xDE, 0x02, 0x2E, 0xFC, + 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, 0x5E, 0xFD, 0x3F, 0x02, 0x5D, + 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x3B, 0xFF, 0xD3, 0x01, 0x7F, 0xFC, 0x1F, 0x06, 0x3C, 0xF5, 0xE6, + 0x18, 0x4D, 0x42, 0xD5, 0xF5, 0xAF, 0x03, 0xB4, 0xFE, 0x4B, 0x00, + 0x0E, 0x00, 0xE9, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x53, + 0xFF, 0xBA, 0x01, 0x5B, 0xFC, 0x1B, 0x07, 0x8B, 0xF1, 0x58, 0x2E, + 0x26, 0x32, 0x80, 0xF1, 0xEA, 0x06, 0x8C, 0xFC, 0x97, 0x01, 0x66, + 0xFF, 0x27, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF8, 0xFF, 0xE6, 0xFF, + 0x9C, 0x00, 0x27, 0xFE, 0x94, 0x04, 0x61, 0xF4, 0xD7, 0x3F, 0x06, + 0x1D, 0x2B, 0xF4, 0x89, 0x06, 0x56, 0xFC, 0xE0, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, + 0xFE, 0x86, 0x01, 0xBA, 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, + 0xC7, 0xFA, 0x93, 0x03, 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4B, 0xFF, 0xA6, 0x01, + 0xEE, 0xFC, 0x23, 0x05, 0x83, 0xF7, 0x6E, 0x11, 0xE5, 0x45, 0x57, + 0xF9, 0xB8, 0x01, 0xDC, 0xFF, 0xA5, 0xFF, 0x5F, 0x00, 0xCA, 0xFF, + 0x0D, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3D, 0xFF, 0xDF, 0x01, 0x32, + 0xFC, 0x1E, 0x07, 0x40, 0xF2, 0xEB, 0x26, 0xB5, 0x38, 0x1F, 0xF2, + 0x32, 0x06, 0x18, 0xFD, 0x3D, 0x01, 0x94, 0xFF, 0x16, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x11, 0x00, 0xA4, 0xFF, 0x1D, 0x01, 0x4C, 0xFD, + 0xE6, 0x05, 0x7B, 0xF2, 0x71, 0x3A, 0xB8, 0x24, 0x97, 0xF2, 0x0B, + 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x0F, 0x00, 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, + 0x01, 0x8B, 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, + 0x15, 0xFD, 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x28, 0x00, 0x64, 0xFF, 0x65, 0x01, 0x81, 0xFD, 0xF2, 0x03, + 0x08, 0xFA, 0x68, 0x0A, 0x25, 0x48, 0xDE, 0xFD, 0x6E, 0xFF, 0x24, + 0x01, 0xF3, 0xFE, 0xB6, 0x00, 0xA8, 0xFF, 0x15, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x46, 0xFC, 0xB8, 0x06, 0xA8, + 0xF3, 0x3F, 0x1F, 0x64, 0x3E, 0xBA, 0xF3, 0x01, 0x05, 0xE2, 0xFD, + 0xC4, 0x00, 0xD2, 0xFF, 0x00, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x23, + 0x00, 0x71, 0xFF, 0x81, 0x01, 0xAE, 0xFC, 0xC1, 0x06, 0x95, 0xF1, + 0x1E, 0x34, 0x3E, 0x2C, 0xAB, 0xF1, 0x27, 0x07, 0x49, 0xFC, 0xC8, + 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE1, 0xFF, + 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, 0xF6, 0x77, + 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, 0xC8, 0x01, + 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, + 0xFF, 0x16, 0x01, 0x2A, 0xFE, 0xA3, 0x02, 0xA1, 0xFC, 0x06, 0x04, + 0xF5, 0x48, 0x56, 0x03, 0xED, 0xFC, 0x7B, 0x02, 0x3E, 0xFE, 0x0C, + 0x01, 0x86, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, + 0xCC, 0x01, 0x8F, 0xFC, 0xF8, 0x05, 0x9B, 0xF5, 0x96, 0x17, 0x02, + 0x43, 0x5E, 0xF6, 0x5F, 0x03, 0xE4, 0xFE, 0x30, 0x00, 0x1B, 0x00, + 0xE4, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, + 0x01, 0x4F, 0xFC, 0x24, 0x07, 0x9C, 0xF1, 0x17, 0x2D, 0x57, 0x33, + 0x8A, 0xF1, 0xD3, 0x06, 0x9F, 0xFC, 0x8A, 0x01, 0x6D, 0xFF, 0x25, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0xD9, 0xFF, 0xB4, 0x00, + 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, 0xFC, 0x3E, 0x5B, 0x1E, 0xDB, + 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC0, 0x00, 0xDE, 0xFE, 0x4B, + 0x01, 0x27, 0xFF, 0x73, 0xFE, 0x4F, 0x48, 0xA2, 0x09, 0x54, 0xFA, + 0xCC, 0x03, 0x93, 0xFD, 0x5C, 0x01, 0x67, 0xFF, 0x27, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9C, 0x01, 0x05, 0xFD, + 0xF1, 0x04, 0xF0, 0xF7, 0x2D, 0x10, 0x61, 0x46, 0x0D, 0xFA, 0x58, + 0x01, 0x13, 0x00, 0x87, 0xFF, 0x6E, 0x00, 0xC4, 0xFF, 0x0E, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x14, + 0x07, 0x73, 0xF2, 0x99, 0x25, 0xC2, 0x39, 0x54, 0xF2, 0x05, 0x06, + 0x37, 0xFD, 0x2B, 0x01, 0x9E, 0xFF, 0x13, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0x14, 0x00, 0x9B, 0xFF, 0x31, 0x01, 0x2C, 0xFD, 0x15, 0x06, + 0x41, 0xF2, 0x6A, 0x39, 0x0A, 0x26, 0x61, 0xF2, 0x17, 0x07, 0x31, + 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, + 0xC6, 0xFF, 0x69, 0x00, 0x91, 0xFF, 0x00, 0x00, 0x78, 0x01, 0xD0, + 0xF9, 0x39, 0x46, 0x98, 0x10, 0xCB, 0xF7, 0x02, 0x05, 0xFE, 0xFC, + 0x9F, 0x01, 0x4D, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, + 0x00, 0x69, 0xFF, 0x58, 0x01, 0x9D, 0xFD, 0xB9, 0x03, 0x7B, 0xFA, + 0x40, 0x09, 0x63, 0x48, 0xBF, 0xFE, 0x03, 0xFF, 0x5F, 0x01, 0xD4, + 0xFE, 0xC5, 0x00, 0xA2, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE2, 0x01, 0x4F, 0xFC, 0x9C, 0x06, 0xF5, 0xF3, 0xEA, + 0x1D, 0x47, 0x3F, 0x1B, 0xF4, 0xC1, 0x04, 0x0B, 0xFE, 0xAC, 0x00, + 0xDE, 0xFF, 0xFB, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6A, + 0xFF, 0x8E, 0x01, 0x99, 0xFC, 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, + 0x82, 0x2D, 0x96, 0xF1, 0x21, 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, + 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE6, 0xFF, 0x17, 0x00, + 0x39, 0x00, 0xD4, 0xFE, 0x7A, 0x03, 0x2F, 0xF6, 0xC7, 0x42, 0x06, + 0x18, 0x7B, 0xF5, 0x05, 0x06, 0x8A, 0xFC, 0xCF, 0x01, 0x3C, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x88, 0xFF, 0x07, 0x01, 0x49, + 0xFE, 0x67, 0x02, 0x13, 0xFD, 0xFF, 0x02, 0xF4, 0x48, 0x5F, 0x04, + 0x7A, 0xFC, 0xB6, 0x02, 0x20, 0xFE, 0x1B, 0x01, 0x81, 0xFF, 0x1F, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC6, 0x01, + 0xA1, 0xFC, 0xCF, 0x05, 0xFC, 0xF5, 0x47, 0x16, 0xB0, 0x43, 0xEE, + 0xF6, 0x0C, 0x03, 0x16, 0xFF, 0x14, 0x00, 0x29, 0x00, 0xDF, 0xFF, + 0x09, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xCA, 0x01, 0x46, + 0xFC, 0x29, 0x07, 0xB3, 0xF1, 0xD1, 0x2B, 0x81, 0x34, 0x9C, 0xF1, + 0xB8, 0x06, 0xB5, 0xFC, 0x7C, 0x01, 0x74, 0xFF, 0x22, 0x00, 0xFE, + 0xFF, 0x02, 0x00, 0x01, 0x00, 0xCE, 0xFF, 0xCC, 0x00, 0xD5, 0xFD, + 0x16, 0x05, 0x9B, 0xF3, 0x18, 0x3E, 0xB1, 0x1F, 0x8F, 0xF3, 0xC0, + 0x06, 0x43, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x15, 0x00, 0xAA, 0xFF, 0xB1, 0x00, 0xFE, 0xFE, 0x10, 0x01, 0x92, + 0xFF, 0x94, 0xFD, 0x0D, 0x48, 0xCB, 0x0A, 0xE2, 0xF9, 0x04, 0x04, + 0x77, 0xFD, 0x69, 0x01, 0x62, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x91, 0x01, 0x1E, 0xFD, 0xBE, 0x04, + 0x5E, 0xF8, 0xF0, 0x0E, 0xD3, 0x46, 0xCB, 0xFA, 0xF6, 0x00, 0x4B, + 0x00, 0x69, 0xFF, 0x7D, 0x00, 0xBE, 0xFF, 0x10, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, + 0xF2, 0x46, 0x24, 0xC8, 0x3A, 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, + 0x17, 0x01, 0xA8, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, + 0x00, 0x91, 0xFF, 0x43, 0x01, 0x0E, 0xFD, 0x40, 0x06, 0x0F, 0xF2, + 0x5B, 0x38, 0x5C, 0x27, 0x30, 0xF2, 0x21, 0x07, 0x33, 0xFC, 0xDE, + 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCC, 0xFF, + 0x5A, 0x00, 0xAF, 0xFF, 0xCA, 0xFF, 0xD8, 0x01, 0x1C, 0xF9, 0xB8, + 0x45, 0xDA, 0x11, 0x60, 0xF7, 0x33, 0x05, 0xE7, 0xFC, 0xA9, 0x01, + 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6E, + 0xFF, 0x4B, 0x01, 0xB9, 0xFD, 0x80, 0x03, 0xEE, 0xFA, 0x1D, 0x08, + 0x98, 0x48, 0xA8, 0xFF, 0x95, 0xFE, 0x9A, 0x01, 0xB4, 0xFE, 0xD4, + 0x00, 0x9C, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xDF, 0x01, 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, 0x1F, + 0x40, 0x85, 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, 0xFF, + 0xF7, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9B, + 0x01, 0x86, 0xFC, 0xF1, 0x06, 0x7E, 0xF1, 0xC0, 0x31, 0xC2, 0x2E, + 0x87, 0xF1, 0x17, 0x07, 0x5F, 0xFC, 0xB6, 0x01, 0x55, 0xFF, 0x2D, + 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEB, 0xFF, 0x09, 0x00, 0x54, 0x00, + 0xA4, 0xFE, 0xC9, 0x03, 0xAA, 0xF5, 0x0C, 0x42, 0x56, 0x19, 0x1E, + 0xF5, 0x2B, 0x06, 0x7A, 0xFC, 0xD4, 0x01, 0x3A, 0xFF, 0x35, 0x00, + 0xFE, 0xFF, 0x1C, 0x00, 0x8E, 0xFF, 0xF9, 0x00, 0x68, 0xFE, 0x2C, + 0x02, 0x84, 0xFD, 0xFF, 0x01, 0xE6, 0x48, 0x6E, 0x05, 0x07, 0xFC, + 0xF1, 0x02, 0x01, 0xFE, 0x29, 0x01, 0x7B, 0xFF, 0x21, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, 0x01, 0xB4, 0xFC, + 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, 0x86, 0xF7, 0xB6, + 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, 0xFF, 0x0A, 0x00, + 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, 0xD1, 0x01, 0x3E, 0xFC, 0x2B, + 0x07, 0xD0, 0xF1, 0x89, 0x2A, 0xA6, 0x35, 0xB4, 0xF1, 0x99, 0x06, + 0xCD, 0xFC, 0x6D, 0x01, 0x7C, 0xFF, 0x1F, 0x00, 0xFE, 0xFF, 0x01, + 0x00, 0x06, 0x00, 0xC2, 0xFF, 0xE3, 0x00, 0xAE, 0xFD, 0x52, 0x05, + 0x44, 0xF3, 0x2A, 0x3D, 0x06, 0x21, 0x47, 0xF3, 0xD8, 0x06, 0x3C, + 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, + 0xB0, 0xFF, 0xA2, 0x00, 0x1D, 0xFF, 0xD6, 0x00, 0xFC, 0xFF, 0xBC, + 0xFC, 0xC0, 0x47, 0xFA, 0x0B, 0x70, 0xF9, 0x3C, 0x04, 0x5C, 0xFD, + 0x75, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, + 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, 0xFD, 0x89, 0x04, 0xCD, 0xF8, + 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, 0x91, 0x00, 0x83, 0x00, 0x4A, + 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF5, 0x06, 0xE7, 0xF2, 0xF2, + 0x22, 0xC7, 0x3B, 0xD4, 0xF2, 0xA2, 0x05, 0x7A, 0xFD, 0x02, 0x01, + 0xB2, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x88, + 0xFF, 0x55, 0x01, 0xF2, 0xFC, 0x67, 0x06, 0xE4, 0xF1, 0x44, 0x37, + 0xAA, 0x28, 0x05, 0xF2, 0x27, 0x07, 0x36, 0xFC, 0xDA, 0x01, 0x41, + 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD2, 0xFF, 0x4C, 0x00, + 0xCD, 0xFF, 0x94, 0xFF, 0x34, 0x02, 0x70, 0xF8, 0x2E, 0x45, 0x20, + 0x13, 0xF6, 0xF6, 0x62, 0x05, 0xD1, 0xFC, 0xB2, 0x01, 0x46, 0xFF, + 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, + 0x01, 0xD6, 0xFD, 0x46, 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, + 0x98, 0x00, 0x26, 0xFE, 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, + 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDB, 0x01, + 0x66, 0xFC, 0x5E, 0x06, 0x9C, 0xF4, 0x40, 0x1B, 0xEF, 0x40, 0xF7, + 0xF4, 0x35, 0x04, 0x62, 0xFE, 0x7A, 0x00, 0xF7, 0xFF, 0xF2, 0xFF, + 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5D, 0xFF, 0xA7, 0x01, 0x75, + 0xFC, 0x03, 0x07, 0x7D, 0xF1, 0x8A, 0x30, 0xFF, 0x2F, 0x7E, 0xF1, + 0x0A, 0x07, 0x6E, 0xFC, 0xAC, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0xFD, + 0xFF, 0x05, 0x00, 0xF0, 0xFF, 0xFC, 0xFF, 0x6E, 0x00, 0x76, 0xFE, + 0x15, 0x04, 0x2C, 0xF5, 0x49, 0x41, 0xA9, 0x1A, 0xC3, 0xF4, 0x4F, + 0x06, 0x6C, 0xFC, 0xD9, 0x01, 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, + 0x1A, 0x00, 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, + 0xFD, 0x05, 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, + 0xE4, 0xFD, 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x31, 0x00, 0x45, 0xFF, 0xB6, 0x01, 0xC8, 0xFC, 0x77, 0x05, + 0xC7, 0xF6, 0xB1, 0x13, 0xED, 0x44, 0x26, 0xF8, 0x5D, 0x02, 0x7D, + 0xFF, 0xDA, 0xFF, 0x46, 0x00, 0xD4, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, + 0x33, 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x38, 0xFC, 0x29, 0x07, 0xF3, + 0xF1, 0x3E, 0x29, 0xC6, 0x36, 0xD4, 0xF1, 0x77, 0x06, 0xE6, 0xFC, + 0x5C, 0x01, 0x84, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, + 0x00, 0xB7, 0xFF, 0xF9, 0x00, 0x89, 0xFD, 0x8A, 0x05, 0xF4, 0xF2, + 0x37, 0x3C, 0x5B, 0x22, 0x03, 0xF3, 0xED, 0x06, 0x37, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB6, 0xFF, + 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, 0xFB, 0x69, + 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, 0x81, 0x01, + 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5B, + 0xFF, 0x7A, 0x01, 0x50, 0xFD, 0x54, 0x04, 0x3D, 0xF9, 0x82, 0x0C, + 0x9A, 0x47, 0x5E, 0xFC, 0x2A, 0x00, 0xBD, 0x00, 0x2B, 0xFF, 0x9B, + 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE6, 0x01, 0x3A, 0xFC, 0xE2, 0x06, 0x28, 0xF3, 0x9E, 0x21, 0xC0, + 0x3C, 0x1F, 0xF3, 0x6C, 0x05, 0x9E, 0xFD, 0xED, 0x00, 0xBD, 0xFF, + 0x07, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x80, 0xFF, 0x66, + 0x01, 0xD8, 0xFC, 0x8B, 0x06, 0xC1, 0xF1, 0x27, 0x36, 0xF6, 0x29, + 0xDF, 0xF1, 0x2A, 0x07, 0x3B, 0xFC, 0xD4, 0x01, 0x44, 0xFF, 0x32, + 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD7, 0xFF, 0x3E, 0x00, 0xEA, 0xFF, + 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, 0x99, 0x44, 0x68, 0x14, 0x8E, + 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, 0x01, 0x43, 0xFF, 0x32, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x79, 0xFF, 0x2F, 0x01, 0xF4, + 0xFD, 0x0C, 0x03, 0xD4, 0xFB, 0xE9, 0x05, 0xDE, 0x48, 0x8F, 0x01, + 0xB6, 0xFD, 0x11, 0x02, 0x76, 0xFE, 0xF2, 0x00, 0x91, 0xFF, 0x1B, + 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, 0x01, 0x73, 0xFC, + 0x3B, 0x06, 0xF5, 0xF4, 0xED, 0x19, 0xB7, 0x41, 0x71, 0xF5, 0xEB, + 0x03, 0x90, 0xFE, 0x60, 0x00, 0x04, 0x00, 0xED, 0xFF, 0x06, 0x00, + 0xFD, 0xFF, 0x2C, 0x00, 0x57, 0xFF, 0xB2, 0x01, 0x65, 0xFC, 0x12, + 0x07, 0x82, 0xF1, 0x50, 0x2F, 0x38, 0x31, 0x7C, 0xF1, 0xF9, 0x06, + 0x7E, 0xFC, 0xA1, 0x01, 0x61, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x04, + 0x00, 0xF5, 0xFF, 0xEF, 0xFF, 0x88, 0x00, 0x49, 0xFE, 0x5D, 0x04, + 0xB7, 0xF4, 0x7D, 0x40, 0xFD, 0x1B, 0x6C, 0xF4, 0x70, 0x06, 0x5F, + 0xFC, 0xDE, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, + 0x9A, 0xFF, 0xDB, 0x00, 0xA6, 0xFE, 0xB4, 0x01, 0x64, 0xFE, 0x12, + 0x00, 0xAA, 0x48, 0x9E, 0x07, 0x21, 0xFB, 0x66, 0x03, 0xC6, 0xFD, + 0x45, 0x01, 0x70, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, + 0x00, 0x48, 0xFF, 0xAD, 0x01, 0xDD, 0xFC, 0x48, 0x05, 0x30, 0xF7, + 0x6B, 0x12, 0x7D, 0x45, 0xCF, 0xF8, 0x01, 0x02, 0xB2, 0xFF, 0xBD, + 0xFF, 0x54, 0x00, 0xCE, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x3F, 0xFF, 0xDC, 0x01, 0x34, 0xFC, 0x24, 0x07, 0x1C, 0xF2, 0xF0, + 0x27, 0xDF, 0x37, 0xFB, 0xF1, 0x51, 0x06, 0x01, 0xFD, 0x4B, 0x01, + 0x8D, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAC, + 0xFF, 0x0E, 0x01, 0x66, 0xFD, 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, + 0xB0, 0x23, 0xC4, 0xF2, 0xFF, 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBC, 0xFF, 0x84, 0x00, + 0x5B, 0xFF, 0x64, 0x00, 0xC9, 0x00, 0x22, 0xFB, 0x02, 0x47, 0x64, + 0x0E, 0x8F, 0xF8, 0xA7, 0x04, 0x29, 0xFD, 0x8C, 0x01, 0x54, 0xFF, + 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6E, + 0x01, 0x6B, 0xFD, 0x1D, 0x04, 0xAF, 0xF9, 0x51, 0x0B, 0xEC, 0x47, + 0x33, 0xFD, 0xC1, 0xFF, 0xF7, 0x00, 0x0C, 0xFF, 0xAA, 0x00, 0xAD, + 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, + 0x40, 0xFC, 0xCB, 0x06, 0x6E, 0xF3, 0x49, 0x20, 0xB0, 0x3D, 0x73, + 0xF3, 0x31, 0x05, 0xC4, 0xFD, 0xD6, 0x00, 0xC8, 0xFF, 0x03, 0x00, + 0x02, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x77, 0xFF, 0x75, 0x01, 0xBF, + 0xFC, 0xAB, 0x06, 0xA6, 0xF1, 0x05, 0x35, 0x40, 0x2B, 0xBF, 0xF1, + 0x2A, 0x07, 0x42, 0xFC, 0xCE, 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, + 0xFF, 0x09, 0x00, 0xDC, 0xFF, 0x2F, 0x00, 0x07, 0x00, 0x2C, 0xFF, + 0xE6, 0x02, 0x31, 0xF7, 0xFA, 0x43, 0xB3, 0x15, 0x29, 0xF6, 0xBC, + 0x05, 0xA9, 0xFC, 0xC2, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x20, 0x00, 0x7E, 0xFF, 0x21, 0x01, 0x12, 0xFE, 0xD1, + 0x02, 0x47, 0xFC, 0xD7, 0x04, 0xF0, 0x48, 0x8D, 0x02, 0x45, 0xFD, + 0x4D, 0x02, 0x56, 0xFE, 0x01, 0x01, 0x8B, 0xFF, 0x1D, 0x00, 0xFE, + 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD1, 0x01, 0x83, 0xFC, 0x16, 0x06, + 0x51, 0xF5, 0x9B, 0x18, 0x75, 0x42, 0xF3, 0xF5, 0x9D, 0x03, 0xBF, + 0xFE, 0x45, 0x00, 0x11, 0x00, 0xE8, 0xFF, 0x07, 0x00, 0xFD, 0xFF, + 0x2E, 0x00, 0x52, 0xFF, 0xBC, 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, + 0xF1, 0x11, 0x2E, 0x6B, 0x32, 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, + 0x94, 0x01, 0x67, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF9, + 0xFF, 0xE3, 0xFF, 0xA1, 0x00, 0x1E, 0xFE, 0xA3, 0x04, 0x49, 0xF4, + 0xA8, 0x3F, 0x52, 0x1D, 0x19, 0xF4, 0x90, 0x06, 0x53, 0xFC, 0xE1, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0xA0, 0xFF, + 0xCC, 0x00, 0xC6, 0xFE, 0x79, 0x01, 0xD2, 0xFE, 0x26, 0xFF, 0x7C, + 0x48, 0xBE, 0x08, 0xAE, 0xFA, 0xA0, 0x03, 0xA9, 0xFD, 0x52, 0x01, + 0x6B, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, + 0xFF, 0xA3, 0x01, 0xF3, 0xFC, 0x18, 0x05, 0x9B, 0xF7, 0x27, 0x11, + 0x02, 0x46, 0x7F, 0xF9, 0xA3, 0x01, 0xE8, 0xFF, 0x9F, 0xFF, 0x63, + 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, + 0xE0, 0x01, 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, 0xF2, + 0x38, 0x2A, 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, 0xFF, + 0x16, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x11, 0x00, 0xA2, 0xFF, 0x22, + 0x01, 0x45, 0xFD, 0xF1, 0x05, 0x6D, 0xF2, 0x38, 0x3A, 0x03, 0x25, + 0x8B, 0xF2, 0x0E, 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x3A, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC2, 0xFF, 0x75, 0x00, 0x7A, 0xFF, + 0x2B, 0x00, 0x2D, 0x01, 0x61, 0xFA, 0x97, 0x46, 0xA0, 0x0F, 0x20, + 0xF8, 0xDA, 0x04, 0x10, 0xFD, 0x97, 0x01, 0x50, 0xFF, 0x2E, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x62, 0x01, 0x87, + 0xFD, 0xE5, 0x03, 0x21, 0xFA, 0x25, 0x0A, 0x33, 0x48, 0x0F, 0xFE, + 0x57, 0xFF, 0x31, 0x01, 0xEC, 0xFE, 0xB9, 0x00, 0xA7, 0xFF, 0x15, + 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x48, 0xFC, + 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, 0xCF, 0xF3, 0xF3, + 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, 0xFF, 0x03, 0x00, + 0xFE, 0xFF, 0x23, 0x00, 0x70, 0xFF, 0x84, 0x01, 0xA9, 0xFC, 0xC7, + 0x06, 0x91, 0xF1, 0xDC, 0x33, 0x87, 0x2C, 0xA5, 0xF1, 0x26, 0x07, + 0x4B, 0xFC, 0xC6, 0x01, 0x4C, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, + 0x00, 0xE2, 0xFF, 0x21, 0x00, 0x23, 0x00, 0xFA, 0xFE, 0x3A, 0x03, + 0x9D, 0xF6, 0x50, 0x43, 0x00, 0x17, 0xC6, 0xF5, 0xE6, 0x05, 0x97, + 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x00, 0x00, + 0x1E, 0x00, 0x84, 0xFF, 0x13, 0x01, 0x31, 0xFE, 0x95, 0x02, 0xBA, + 0xFC, 0xCB, 0x03, 0xF7, 0x48, 0x91, 0x03, 0xD3, 0xFC, 0x88, 0x02, + 0x38, 0xFE, 0x10, 0x01, 0x85, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, + 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, 0xFC, 0xEF, 0x05, 0xB0, 0xF5, + 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, 0x4D, 0x03, 0xEF, 0xFE, 0x2A, + 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x2F, 0x00, + 0x4D, 0xFF, 0xC4, 0x01, 0x4D, 0xFC, 0x25, 0x07, 0xA1, 0xF1, 0xCE, + 0x2C, 0x99, 0x33, 0x8E, 0xF1, 0xCD, 0x06, 0xA4, 0xFC, 0x87, 0x01, + 0x6E, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFE, 0xFF, 0xD7, + 0xFF, 0xBA, 0x00, 0xF4, 0xFD, 0xE5, 0x04, 0xE4, 0xF3, 0xCA, 0x3E, + 0xA7, 0x1E, 0xCA, 0xF3, 0xAC, 0x06, 0x4A, 0xFC, 0xE4, 0x01, 0x36, + 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA6, 0xFF, 0xBD, 0x00, + 0xE5, 0xFE, 0x3E, 0x01, 0x3F, 0xFF, 0x41, 0xFE, 0x41, 0x48, 0xE4, + 0x09, 0x3B, 0xFA, 0xD9, 0x03, 0x8D, 0xFD, 0x5F, 0x01, 0x66, 0xFF, + 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, + 0x01, 0x0B, 0xFD, 0xE6, 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, + 0x37, 0xFA, 0x42, 0x01, 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, + 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, + 0x31, 0xFC, 0x11, 0x07, 0x7F, 0xF2, 0x4E, 0x25, 0xFD, 0x39, 0x60, + 0xF2, 0xFB, 0x05, 0x3E, 0xFD, 0x26, 0x01, 0xA0, 0xFF, 0x12, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x15, 0x00, 0x98, 0xFF, 0x35, 0x01, 0x25, + 0xFD, 0x1E, 0x06, 0x35, 0xF2, 0x2E, 0x39, 0x55, 0x26, 0x56, 0xF2, + 0x1A, 0x07, 0x31, 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x0E, 0x00, 0xC7, 0xFF, 0x66, 0x00, 0x98, 0xFF, 0xF4, 0xFF, + 0x8E, 0x01, 0xA7, 0xF9, 0x1D, 0x46, 0xDF, 0x10, 0xB3, 0xF7, 0x0D, + 0x05, 0xF8, 0xFC, 0xA1, 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, + 0x03, 0x94, 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, + 0x6C, 0x01, 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x51, 0xFC, 0x96, 0x06, + 0x07, 0xF4, 0x9E, 0x1D, 0x77, 0x3F, 0x32, 0xF4, 0xB2, 0x04, 0x15, + 0xFE, 0xA7, 0x00, 0xE0, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0xFD, 0xFF, + 0x26, 0x00, 0x69, 0xFF, 0x91, 0x01, 0x94, 0xFC, 0xE0, 0x06, 0x84, + 0xF1, 0xAF, 0x32, 0xCA, 0x2D, 0x92, 0xF1, 0x1F, 0x07, 0x56, 0xFC, + 0xBE, 0x01, 0x51, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE7, + 0xFF, 0x14, 0x00, 0x3F, 0x00, 0xC9, 0xFE, 0x8C, 0x03, 0x11, 0xF6, + 0x9E, 0x42, 0x50, 0x18, 0x66, 0xF5, 0x0D, 0x06, 0x86, 0xFC, 0xD0, + 0x01, 0x3B, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, + 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, 0x02, 0xF2, + 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, 0x1E, 0x01, + 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, + 0xFF, 0xC4, 0x01, 0xA5, 0xFC, 0xC5, 0x05, 0x13, 0xF6, 0xFD, 0x15, + 0xD4, 0x43, 0x0F, 0xF7, 0xF9, 0x02, 0x21, 0xFF, 0x0D, 0x00, 0x2C, + 0x00, 0xDE, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x49, 0xFF, + 0xCC, 0x01, 0x44, 0xFC, 0x29, 0x07, 0xB9, 0xF1, 0x89, 0x2B, 0xC3, + 0x34, 0xA0, 0xF1, 0xB1, 0x06, 0xBA, 0xFC, 0x79, 0x01, 0x76, 0xFF, + 0x21, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x02, 0x00, 0xCB, 0xFF, 0xD1, + 0x00, 0xCC, 0xFD, 0x24, 0x05, 0x87, 0xF3, 0xE4, 0x3D, 0xFD, 0x1F, + 0x7F, 0xF3, 0xC6, 0x06, 0x41, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAC, 0xFF, 0xAE, 0x00, 0x05, 0xFF, + 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, 0xFD, 0x47, 0x0E, 0x0B, 0xC8, + 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, 0x01, 0x61, 0xFF, 0x28, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0x8F, 0x01, 0x23, + 0xFD, 0xB2, 0x04, 0x76, 0xF8, 0xAA, 0x0E, 0xED, 0x46, 0xF7, 0xFA, + 0xDF, 0x00, 0x57, 0x00, 0x62, 0xFF, 0x80, 0x00, 0xBD, 0xFF, 0x10, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x33, 0xFC, + 0x03, 0x07, 0xB7, 0xF2, 0xFC, 0x23, 0x03, 0x3B, 0x9E, 0xF2, 0xCB, + 0x05, 0x5F, 0xFD, 0x12, 0x01, 0xAA, 0xFF, 0x0E, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x18, 0x00, 0x8F, 0xFF, 0x47, 0x01, 0x08, 0xFD, 0x49, + 0x06, 0x05, 0xF2, 0x1D, 0x38, 0xA6, 0x27, 0x26, 0xF2, 0x23, 0x07, + 0x33, 0xFC, 0xDD, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, + 0x00, 0xCD, 0xFF, 0x57, 0x00, 0xB6, 0xFF, 0xBE, 0xFF, 0xED, 0x01, + 0xF5, 0xF8, 0x9B, 0x45, 0x22, 0x12, 0x48, 0xF7, 0x3D, 0x05, 0xE2, + 0xFC, 0xAB, 0x01, 0x49, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x24, 0x00, 0x6F, 0xFF, 0x48, 0x01, 0xC0, 0xFD, 0x73, 0x03, 0x07, + 0xFB, 0xDD, 0x07, 0xA1, 0x48, 0xDD, 0xFF, 0x7D, 0xFE, 0xA7, 0x01, + 0xAD, 0xFE, 0xD8, 0x00, 0x9B, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5C, 0xFC, 0x78, 0x06, 0x5A, 0xF4, + 0x49, 0x1C, 0x4E, 0x40, 0x9E, 0xF4, 0x6D, 0x04, 0x3F, 0xFE, 0x8E, + 0x00, 0xED, 0xFF, 0xF6, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, + 0x62, 0xFF, 0x9E, 0x01, 0x82, 0xFC, 0xF5, 0x06, 0x7D, 0xF1, 0x7B, + 0x31, 0x09, 0x2F, 0x84, 0xF1, 0x15, 0x07, 0x62, 0xFC, 0xB4, 0x01, + 0x56, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEC, 0xFF, 0x06, + 0x00, 0x5A, 0x00, 0x9A, 0xFE, 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, + 0xA1, 0x19, 0x09, 0xF5, 0x33, 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x8F, 0xFF, 0xF5, 0x00, + 0x6F, 0xFE, 0x1E, 0x02, 0x9D, 0xFD, 0xC7, 0x01, 0xE1, 0x48, 0xAB, + 0x05, 0xEE, 0xFB, 0xFE, 0x02, 0xFB, 0xFD, 0x2C, 0x01, 0x7A, 0xFF, + 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBC, + 0x01, 0xB8, 0xFC, 0x9A, 0x05, 0x77, 0xF6, 0xB1, 0x14, 0x77, 0x44, + 0xA9, 0xF7, 0xA2, 0x02, 0x54, 0xFF, 0xF1, 0xFF, 0x3A, 0x00, 0xD8, + 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, 0xFF, 0xD3, 0x01, + 0x3C, 0xFC, 0x2A, 0x07, 0xD8, 0xF1, 0x3F, 0x2A, 0xE6, 0x35, 0xBB, + 0xF1, 0x92, 0x06, 0xD2, 0xFC, 0x69, 0x01, 0x7E, 0xFF, 0x1F, 0x00, + 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xE8, 0x00, 0xA6, + 0xFD, 0x5F, 0x05, 0x31, 0xF3, 0xF6, 0x3C, 0x52, 0x21, 0x37, 0xF3, + 0xDD, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x13, 0x00, 0xB1, 0xFF, 0x9F, 0x00, 0x24, 0xFF, 0xC9, 0x00, + 0x13, 0x00, 0x8D, 0xFC, 0xAE, 0x47, 0x3E, 0x0C, 0x56, 0xF9, 0x48, + 0x04, 0x56, 0xFD, 0x78, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2B, 0x00, 0x58, 0xFF, 0x83, 0x01, 0x3C, 0xFD, 0x7E, + 0x04, 0xE6, 0xF8, 0x72, 0x0D, 0x52, 0x47, 0xBE, 0xFB, 0x7A, 0x00, + 0x90, 0x00, 0x43, 0xFF, 0x8F, 0x00, 0xB7, 0xFF, 0x11, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xF1, 0x06, + 0xF5, 0xF2, 0xA7, 0x22, 0xFF, 0x3B, 0xE4, 0xF2, 0x96, 0x05, 0x81, + 0xFD, 0xFD, 0x00, 0xB5, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0xFE, 0xFF, + 0x1C, 0x00, 0x86, 0xFF, 0x59, 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, + 0xF1, 0x04, 0x37, 0xF3, 0x28, 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, + 0xD8, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD3, + 0xFF, 0x49, 0x00, 0xD4, 0xFF, 0x88, 0xFF, 0x49, 0x02, 0x4B, 0xF8, + 0x0D, 0x45, 0x68, 0x13, 0xDF, 0xF6, 0x6C, 0x05, 0xCC, 0xFC, 0xB4, + 0x01, 0x45, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, + 0x74, 0xFF, 0x3A, 0x01, 0xDD, 0xFD, 0x39, 0x03, 0x7B, 0xFB, 0xC1, + 0x06, 0xC7, 0x48, 0xCF, 0x00, 0x0D, 0xFE, 0xE3, 0x01, 0x8E, 0xFE, + 0xE7, 0x00, 0x95, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, + 0xFF, 0xDA, 0x01, 0x69, 0xFC, 0x57, 0x06, 0xAF, 0xF4, 0xF5, 0x1A, + 0x1D, 0x41, 0x11, 0xF5, 0x25, 0x04, 0x6C, 0xFE, 0x74, 0x00, 0xF9, + 0xFF, 0xF1, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, 0xFF, + 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, 0x44, + 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, 0xFF, + 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF1, 0xFF, 0xF9, 0xFF, 0x74, + 0x00, 0x6C, 0xFE, 0x25, 0x04, 0x11, 0xF5, 0x1D, 0x41, 0xF5, 0x1A, + 0xAF, 0xF4, 0x57, 0x06, 0x69, 0xFC, 0xDA, 0x01, 0x38, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE7, 0x00, 0x8E, 0xFE, + 0xE3, 0x01, 0x0D, 0xFE, 0xCF, 0x00, 0xC7, 0x48, 0xC1, 0x06, 0x7B, + 0xFB, 0x39, 0x03, 0xDD, 0xFD, 0x3A, 0x01, 0x74, 0xFF, 0x23, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x45, 0xFF, 0xB4, 0x01, 0xCC, + 0xFC, 0x6C, 0x05, 0xDF, 0xF6, 0x68, 0x13, 0x0D, 0x45, 0x4B, 0xF8, + 0x49, 0x02, 0x88, 0xFF, 0xD4, 0xFF, 0x49, 0x00, 0xD3, 0xFF, 0x0B, + 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, 0x01, 0x37, 0xFC, + 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, 0xDC, 0xF1, 0x6F, + 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, + 0x01, 0x00, 0x0B, 0x00, 0xB5, 0xFF, 0xFD, 0x00, 0x81, 0xFD, 0x96, + 0x05, 0xE4, 0xF2, 0xFF, 0x3B, 0xA7, 0x22, 0xF5, 0xF2, 0xF1, 0x06, + 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, + 0x00, 0xB7, 0xFF, 0x8F, 0x00, 0x43, 0xFF, 0x90, 0x00, 0x7A, 0x00, + 0xBE, 0xFB, 0x52, 0x47, 0x72, 0x0D, 0xE6, 0xF8, 0x7E, 0x04, 0x3C, + 0xFD, 0x83, 0x01, 0x58, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2A, 0x00, 0x5C, 0xFF, 0x78, 0x01, 0x56, 0xFD, 0x48, 0x04, 0x56, + 0xF9, 0x3E, 0x0C, 0xAE, 0x47, 0x8D, 0xFC, 0x13, 0x00, 0xC9, 0x00, + 0x24, 0xFF, 0x9F, 0x00, 0xB1, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, 0xFC, 0xDD, 0x06, 0x37, 0xF3, + 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, 0x5F, 0x05, 0xA6, 0xFD, 0xE8, + 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1F, 0x00, + 0x7E, 0xFF, 0x69, 0x01, 0xD2, 0xFC, 0x92, 0x06, 0xBB, 0xF1, 0xE6, + 0x35, 0x3F, 0x2A, 0xD8, 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, + 0x45, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD8, 0xFF, 0x3A, + 0x00, 0xF1, 0xFF, 0x54, 0xFF, 0xA2, 0x02, 0xA9, 0xF7, 0x77, 0x44, + 0xB1, 0x14, 0x77, 0xF6, 0x9A, 0x05, 0xB8, 0xFC, 0xBC, 0x01, 0x42, + 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, + 0x2C, 0x01, 0xFB, 0xFD, 0xFE, 0x02, 0xEE, 0xFB, 0xAB, 0x05, 0xE1, + 0x48, 0xC7, 0x01, 0x9D, 0xFD, 0x1E, 0x02, 0x6F, 0xFE, 0xF5, 0x00, + 0x8F, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, + 0x01, 0x77, 0xFC, 0x33, 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, + 0x8D, 0xF5, 0xDA, 0x03, 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, + 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x56, 0xFF, 0xB4, 0x01, + 0x62, 0xFC, 0x15, 0x07, 0x84, 0xF1, 0x09, 0x2F, 0x7B, 0x31, 0x7D, + 0xF1, 0xF5, 0x06, 0x82, 0xFC, 0x9E, 0x01, 0x62, 0xFF, 0x28, 0x00, + 0xFD, 0xFF, 0x04, 0x00, 0xF6, 0xFF, 0xED, 0xFF, 0x8E, 0x00, 0x3F, + 0xFE, 0x6D, 0x04, 0x9E, 0xF4, 0x4E, 0x40, 0x49, 0x1C, 0x5A, 0xF4, + 0x78, 0x06, 0x5C, 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0x18, 0x00, 0x9B, 0xFF, 0xD8, 0x00, 0xAD, 0xFE, 0xA7, 0x01, + 0x7D, 0xFE, 0xDD, 0xFF, 0xA1, 0x48, 0xDD, 0x07, 0x07, 0xFB, 0x73, + 0x03, 0xC0, 0xFD, 0x48, 0x01, 0x6F, 0xFF, 0x24, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, + 0x05, 0x48, 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, + 0xBE, 0xFF, 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDD, 0x01, 0x33, 0xFC, 0x23, 0x07, + 0x26, 0xF2, 0xA6, 0x27, 0x1D, 0x38, 0x05, 0xF2, 0x49, 0x06, 0x08, + 0xFD, 0x47, 0x01, 0x8F, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x0E, 0x00, 0xAA, 0xFF, 0x12, 0x01, 0x5F, 0xFD, 0xCB, 0x05, 0x9E, + 0xF2, 0x03, 0x3B, 0xFC, 0x23, 0xB7, 0xF2, 0x03, 0x07, 0x33, 0xFC, + 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBD, + 0xFF, 0x80, 0x00, 0x62, 0xFF, 0x57, 0x00, 0xDF, 0x00, 0xF7, 0xFA, + 0xED, 0x46, 0xAA, 0x0E, 0x76, 0xF8, 0xB2, 0x04, 0x23, 0xFD, 0x8F, + 0x01, 0x53, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, + 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, 0xF9, 0x0E, + 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, 0x05, 0xFF, + 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE5, 0x01, 0x41, 0xFC, 0xC6, 0x06, 0x7F, 0xF3, 0xFD, 0x1F, + 0xE4, 0x3D, 0x87, 0xF3, 0x24, 0x05, 0xCC, 0xFD, 0xD1, 0x00, 0xCB, + 0xFF, 0x02, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x76, 0xFF, + 0x79, 0x01, 0xBA, 0xFC, 0xB1, 0x06, 0xA0, 0xF1, 0xC3, 0x34, 0x89, + 0x2B, 0xB9, 0xF1, 0x29, 0x07, 0x44, 0xFC, 0xCC, 0x01, 0x49, 0xFF, + 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2C, 0x00, 0x0D, + 0x00, 0x21, 0xFF, 0xF9, 0x02, 0x0F, 0xF7, 0xD4, 0x43, 0xFD, 0x15, + 0x13, 0xF6, 0xC5, 0x05, 0xA5, 0xFC, 0xC4, 0x01, 0x40, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, 0xFF, 0x1E, 0x01, + 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, 0x9B, 0x04, 0xF2, 0x48, 0xC6, + 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, 0xFE, 0x04, 0x01, 0x8A, 0xFF, + 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD0, 0x01, 0x86, + 0xFC, 0x0D, 0x06, 0x66, 0xF5, 0x50, 0x18, 0x9E, 0x42, 0x11, 0xF6, + 0x8C, 0x03, 0xC9, 0xFE, 0x3F, 0x00, 0x14, 0x00, 0xE7, 0xFF, 0x07, + 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBE, 0x01, 0x56, 0xFC, + 0x1F, 0x07, 0x92, 0xF1, 0xCA, 0x2D, 0xAF, 0x32, 0x84, 0xF1, 0xE0, + 0x06, 0x94, 0xFC, 0x91, 0x01, 0x69, 0xFF, 0x26, 0x00, 0xFD, 0xFF, + 0x03, 0x00, 0xFA, 0xFF, 0xE0, 0xFF, 0xA7, 0x00, 0x15, 0xFE, 0xB2, + 0x04, 0x32, 0xF4, 0x77, 0x3F, 0x9E, 0x1D, 0x07, 0xF4, 0x96, 0x06, + 0x51, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, + 0x00, 0xA1, 0xFF, 0xC9, 0x00, 0xCD, 0xFE, 0x6C, 0x01, 0xEA, 0xFE, + 0xF3, 0xFE, 0x70, 0x48, 0xFF, 0x08, 0x94, 0xFA, 0xAD, 0x03, 0xA3, + 0xFD, 0x55, 0x01, 0x6A, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x2F, 0x00, 0x4C, 0xFF, 0xA1, 0x01, 0xF8, 0xFC, 0x0D, 0x05, 0xB3, + 0xF7, 0xDF, 0x10, 0x1D, 0x46, 0xA7, 0xF9, 0x8E, 0x01, 0xF4, 0xFF, + 0x98, 0xFF, 0x66, 0x00, 0xC7, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, + 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x31, 0xFC, 0x1A, 0x07, 0x56, 0xF2, + 0x55, 0x26, 0x2E, 0x39, 0x35, 0xF2, 0x1E, 0x06, 0x25, 0xFD, 0x35, + 0x01, 0x98, 0xFF, 0x15, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x12, 0x00, + 0xA0, 0xFF, 0x26, 0x01, 0x3E, 0xFD, 0xFB, 0x05, 0x60, 0xF2, 0xFD, + 0x39, 0x4E, 0x25, 0x7F, 0xF2, 0x11, 0x07, 0x31, 0xFC, 0xE3, 0x01, + 0x3A, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC3, 0xFF, 0x71, + 0x00, 0x81, 0xFF, 0x1F, 0x00, 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, + 0xE7, 0x0F, 0x08, 0xF8, 0xE6, 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, + 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x66, 0xFF, + 0x5F, 0x01, 0x8D, 0xFD, 0xD9, 0x03, 0x3B, 0xFA, 0xE4, 0x09, 0x41, + 0x48, 0x41, 0xFE, 0x3F, 0xFF, 0x3E, 0x01, 0xE5, 0xFE, 0xBD, 0x00, + 0xA6, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, + 0x01, 0x4A, 0xFC, 0xAC, 0x06, 0xCA, 0xF3, 0xA7, 0x1E, 0xCA, 0x3E, + 0xE4, 0xF3, 0xE5, 0x04, 0xF4, 0xFD, 0xBA, 0x00, 0xD7, 0xFF, 0xFE, + 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6E, 0xFF, 0x87, 0x01, + 0xA4, 0xFC, 0xCD, 0x06, 0x8E, 0xF1, 0x99, 0x33, 0xCE, 0x2C, 0xA1, + 0xF1, 0x25, 0x07, 0x4D, 0xFC, 0xC4, 0x01, 0x4D, 0xFF, 0x2F, 0x00, + 0xFD, 0xFF, 0x08, 0x00, 0xE3, 0xFF, 0x1E, 0x00, 0x2A, 0x00, 0xEF, + 0xFE, 0x4D, 0x03, 0x7D, 0xF6, 0x2A, 0x43, 0x4B, 0x17, 0xB0, 0xF5, + 0xEF, 0x05, 0x93, 0xFC, 0xCB, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0x1E, 0x00, 0x85, 0xFF, 0x10, 0x01, 0x38, 0xFE, 0x88, 0x02, + 0xD3, 0xFC, 0x91, 0x03, 0xF7, 0x48, 0xCB, 0x03, 0xBA, 0xFC, 0x95, + 0x02, 0x31, 0xFE, 0x13, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x97, 0xFC, 0xE6, + 0x05, 0xC6, 0xF5, 0x00, 0x17, 0x50, 0x43, 0x9D, 0xF6, 0x3A, 0x03, + 0xFA, 0xFE, 0x23, 0x00, 0x21, 0x00, 0xE2, 0xFF, 0x08, 0x00, 0xFD, + 0xFF, 0x30, 0x00, 0x4C, 0xFF, 0xC6, 0x01, 0x4B, 0xFC, 0x26, 0x07, + 0xA5, 0xF1, 0x87, 0x2C, 0xDC, 0x33, 0x91, 0xF1, 0xC7, 0x06, 0xA9, + 0xFC, 0x84, 0x01, 0x70, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x03, 0x00, + 0xFF, 0xFF, 0xD4, 0xFF, 0xBF, 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, + 0xF3, 0x98, 0x3E, 0xF3, 0x1E, 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, + 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x15, 0x00, 0xA7, + 0xFF, 0xB9, 0x00, 0xEC, 0xFE, 0x31, 0x01, 0x57, 0xFF, 0x0F, 0xFE, + 0x33, 0x48, 0x25, 0x0A, 0x21, 0xFA, 0xE5, 0x03, 0x87, 0xFD, 0x62, + 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, + 0x50, 0xFF, 0x97, 0x01, 0x10, 0xFD, 0xDA, 0x04, 0x20, 0xF8, 0xA0, + 0x0F, 0x97, 0x46, 0x61, 0xFA, 0x2D, 0x01, 0x2B, 0x00, 0x7A, 0xFF, + 0x75, 0x00, 0xC2, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, + 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0E, 0x07, 0x8B, 0xF2, 0x03, 0x25, + 0x38, 0x3A, 0x6D, 0xF2, 0xF1, 0x05, 0x45, 0xFD, 0x22, 0x01, 0xA2, + 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x96, 0xFF, + 0x39, 0x01, 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, 0xA0, + 0x26, 0x4B, 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, 0xFF, + 0x35, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xC9, 0xFF, 0x63, 0x00, 0x9F, + 0xFF, 0xE8, 0xFF, 0xA3, 0x01, 0x7F, 0xF9, 0x02, 0x46, 0x27, 0x11, + 0x9B, 0xF7, 0x18, 0x05, 0xF3, 0xFC, 0xA3, 0x01, 0x4C, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6B, 0xFF, 0x52, 0x01, + 0xA9, 0xFD, 0xA0, 0x03, 0xAE, 0xFA, 0xBE, 0x08, 0x7C, 0x48, 0x26, + 0xFF, 0xD2, 0xFE, 0x79, 0x01, 0xC6, 0xFE, 0xCC, 0x00, 0xA0, 0xFF, + 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE1, 0x01, 0x53, + 0xFC, 0x90, 0x06, 0x19, 0xF4, 0x52, 0x1D, 0xA8, 0x3F, 0x49, 0xF4, + 0xA3, 0x04, 0x1E, 0xFE, 0xA1, 0x00, 0xE3, 0xFF, 0xF9, 0xFF, 0x04, + 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, 0x01, 0x90, 0xFC, + 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, 0x8E, 0xF1, 0x1D, + 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, + 0x07, 0x00, 0xE8, 0xFF, 0x11, 0x00, 0x45, 0x00, 0xBF, 0xFE, 0x9D, + 0x03, 0xF3, 0xF5, 0x75, 0x42, 0x9B, 0x18, 0x51, 0xF5, 0x16, 0x06, + 0x83, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, + 0x00, 0x8B, 0xFF, 0x01, 0x01, 0x56, 0xFE, 0x4D, 0x02, 0x45, 0xFD, + 0x8D, 0x02, 0xF0, 0x48, 0xD7, 0x04, 0x47, 0xFC, 0xD1, 0x02, 0x12, + 0xFE, 0x21, 0x01, 0x7E, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x33, 0x00, 0x40, 0xFF, 0xC2, 0x01, 0xA9, 0xFC, 0xBC, 0x05, 0x29, + 0xF6, 0xB3, 0x15, 0xFA, 0x43, 0x31, 0xF7, 0xE6, 0x02, 0x2C, 0xFF, + 0x07, 0x00, 0x2F, 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, + 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, 0xFC, 0x2A, 0x07, 0xBF, 0xF1, + 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, 0xAB, 0x06, 0xBF, 0xFC, 0x75, + 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x03, 0x00, + 0xC8, 0xFF, 0xD6, 0x00, 0xC4, 0xFD, 0x31, 0x05, 0x73, 0xF3, 0xB0, + 0x3D, 0x49, 0x20, 0x6E, 0xF3, 0xCB, 0x06, 0x40, 0xFC, 0xE6, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAD, 0xFF, 0xAA, + 0x00, 0x0C, 0xFF, 0xF7, 0x00, 0xC1, 0xFF, 0x33, 0xFD, 0xEC, 0x47, + 0x51, 0x0B, 0xAF, 0xF9, 0x1D, 0x04, 0x6B, 0xFD, 0x6E, 0x01, 0x60, + 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x54, 0xFF, + 0x8C, 0x01, 0x29, 0xFD, 0xA7, 0x04, 0x8F, 0xF8, 0x64, 0x0E, 0x02, + 0x47, 0x22, 0xFB, 0xC9, 0x00, 0x64, 0x00, 0x5B, 0xFF, 0x84, 0x00, + 0xBC, 0xFF, 0x10, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, + 0x01, 0x33, 0xFC, 0xFF, 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, + 0xAD, 0xF2, 0xBF, 0x05, 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x19, 0x00, 0x8D, 0xFF, 0x4B, 0x01, + 0x01, 0xFD, 0x51, 0x06, 0xFB, 0xF1, 0xDF, 0x37, 0xF0, 0x27, 0x1C, + 0xF2, 0x24, 0x07, 0x34, 0xFC, 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, + 0xFD, 0xFF, 0x0C, 0x00, 0xCE, 0xFF, 0x54, 0x00, 0xBD, 0xFF, 0xB2, + 0xFF, 0x01, 0x02, 0xCF, 0xF8, 0x7D, 0x45, 0x6B, 0x12, 0x30, 0xF7, + 0x48, 0x05, 0xDD, 0xFC, 0xAD, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, 0xFF, 0x45, 0x01, 0xC6, 0xFD, + 0x66, 0x03, 0x21, 0xFB, 0x9E, 0x07, 0xAA, 0x48, 0x12, 0x00, 0x64, + 0xFE, 0xB4, 0x01, 0xA6, 0xFE, 0xDB, 0x00, 0x9A, 0xFF, 0x19, 0x00, + 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, + 0x06, 0x6C, 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, + 0x49, 0xFE, 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0xFD, + 0xFF, 0x29, 0x00, 0x61, 0xFF, 0xA1, 0x01, 0x7E, 0xFC, 0xF9, 0x06, + 0x7C, 0xF1, 0x38, 0x31, 0x50, 0x2F, 0x82, 0xF1, 0x12, 0x07, 0x65, + 0xFC, 0xB2, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, + 0xED, 0xFF, 0x04, 0x00, 0x60, 0x00, 0x90, 0xFE, 0xEB, 0x03, 0x71, + 0xF5, 0xB7, 0x41, 0xED, 0x19, 0xF5, 0xF4, 0x3B, 0x06, 0x73, 0xFC, + 0xD7, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x91, + 0xFF, 0xF2, 0x00, 0x76, 0xFE, 0x11, 0x02, 0xB6, 0xFD, 0x8F, 0x01, + 0xDE, 0x48, 0xE9, 0x05, 0xD4, 0xFB, 0x0C, 0x03, 0xF4, 0xFD, 0x2F, + 0x01, 0x79, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, + 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, 0xF6, 0x68, + 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, 0xEA, 0xFF, + 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, + 0xFF, 0xD4, 0x01, 0x3B, 0xFC, 0x2A, 0x07, 0xDF, 0xF1, 0xF6, 0x29, + 0x27, 0x36, 0xC1, 0xF1, 0x8B, 0x06, 0xD8, 0xFC, 0x66, 0x01, 0x80, + 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xBD, 0xFF, + 0xED, 0x00, 0x9E, 0xFD, 0x6C, 0x05, 0x1F, 0xF3, 0xC0, 0x3C, 0x9E, + 0x21, 0x28, 0xF3, 0xE2, 0x06, 0x3A, 0xFC, 0xE6, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x9B, 0x00, 0x2B, + 0xFF, 0xBD, 0x00, 0x2A, 0x00, 0x5E, 0xFC, 0x9A, 0x47, 0x82, 0x0C, + 0x3D, 0xF9, 0x54, 0x04, 0x50, 0xFD, 0x7A, 0x01, 0x5B, 0xFF, 0x2A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, 0x81, 0x01, + 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, 0x2D, 0x0D, 0x69, 0x47, 0xEB, + 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, 0xFF, 0x93, 0x00, 0xB6, 0xFF, + 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x37, + 0xFC, 0xED, 0x06, 0x03, 0xF3, 0x5B, 0x22, 0x37, 0x3C, 0xF4, 0xF2, + 0x8A, 0x05, 0x89, 0xFD, 0xF9, 0x00, 0xB7, 0xFF, 0x0A, 0x00, 0x01, + 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x84, 0xFF, 0x5C, 0x01, 0xE6, 0xFC, + 0x77, 0x06, 0xD4, 0xF1, 0xC6, 0x36, 0x3E, 0x29, 0xF3, 0xF1, 0x29, + 0x07, 0x38, 0xFC, 0xD7, 0x01, 0x42, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x0B, 0x00, 0xD4, 0xFF, 0x46, 0x00, 0xDA, 0xFF, 0x7D, 0xFF, 0x5D, + 0x02, 0x26, 0xF8, 0xED, 0x44, 0xB1, 0x13, 0xC7, 0xF6, 0x77, 0x05, + 0xC8, 0xFC, 0xB6, 0x01, 0x45, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x22, 0x00, 0x76, 0xFF, 0x37, 0x01, 0xE4, 0xFD, 0x2C, 0x03, + 0x94, 0xFB, 0x83, 0x06, 0xCE, 0x48, 0x05, 0x01, 0xF5, 0xFD, 0xF0, + 0x01, 0x87, 0xFE, 0xEA, 0x00, 0x94, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, + 0x35, 0x00, 0x38, 0xFF, 0xD9, 0x01, 0x6C, 0xFC, 0x4F, 0x06, 0xC3, + 0xF4, 0xA9, 0x1A, 0x49, 0x41, 0x2C, 0xF5, 0x15, 0x04, 0x76, 0xFE, + 0x6E, 0x00, 0xFC, 0xFF, 0xF0, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2B, + 0x00, 0x5A, 0xFF, 0xAC, 0x01, 0x6E, 0xFC, 0x0A, 0x07, 0x7E, 0xF1, + 0xFF, 0x2F, 0x8A, 0x30, 0x7D, 0xF1, 0x03, 0x07, 0x75, 0xFC, 0xA7, + 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF2, 0xFF, + 0xF7, 0xFF, 0x7A, 0x00, 0x62, 0xFE, 0x35, 0x04, 0xF7, 0xF4, 0xEF, + 0x40, 0x40, 0x1B, 0x9C, 0xF4, 0x5E, 0x06, 0x66, 0xFC, 0xDB, 0x01, + 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x96, 0xFF, 0xE3, + 0x00, 0x95, 0xFE, 0xD5, 0x01, 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, + 0x00, 0x07, 0x61, 0xFB, 0x46, 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, + 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, + 0xB2, 0x01, 0xD1, 0xFC, 0x62, 0x05, 0xF6, 0xF6, 0x20, 0x13, 0x2E, + 0x45, 0x70, 0xF8, 0x34, 0x02, 0x94, 0xFF, 0xCD, 0xFF, 0x4C, 0x00, + 0xD2, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xDA, + 0x01, 0x36, 0xFC, 0x27, 0x07, 0x05, 0xF2, 0xAA, 0x28, 0x44, 0x37, + 0xE4, 0xF1, 0x67, 0x06, 0xF2, 0xFC, 0x55, 0x01, 0x88, 0xFF, 0x1B, + 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0B, 0x00, 0xB2, 0xFF, 0x02, 0x01, + 0x7A, 0xFD, 0xA2, 0x05, 0xD4, 0xF2, 0xC7, 0x3B, 0xF2, 0x22, 0xE7, + 0xF2, 0xF5, 0x06, 0x35, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x11, 0x00, 0xB9, 0xFF, 0x8C, 0x00, 0x4A, 0xFF, 0x83, + 0x00, 0x91, 0x00, 0x91, 0xFB, 0x3D, 0x47, 0xB7, 0x0D, 0xCD, 0xF8, + 0x89, 0x04, 0x36, 0xFD, 0x86, 0x01, 0x57, 0xFF, 0x2B, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, 0xFF, 0x75, 0x01, 0x5C, 0xFD, + 0x3C, 0x04, 0x70, 0xF9, 0xFA, 0x0B, 0xC0, 0x47, 0xBC, 0xFC, 0xFC, + 0xFF, 0xD6, 0x00, 0x1D, 0xFF, 0xA2, 0x00, 0xB0, 0xFF, 0x13, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3C, 0xFC, 0xD8, + 0x06, 0x47, 0xF3, 0x06, 0x21, 0x2A, 0x3D, 0x44, 0xF3, 0x52, 0x05, + 0xAE, 0xFD, 0xE3, 0x00, 0xC2, 0xFF, 0x06, 0x00, 0x01, 0x00, 0xFE, + 0xFF, 0x1F, 0x00, 0x7C, 0xFF, 0x6D, 0x01, 0xCD, 0xFC, 0x99, 0x06, + 0xB4, 0xF1, 0xA6, 0x35, 0x89, 0x2A, 0xD0, 0xF1, 0x2B, 0x07, 0x3E, + 0xFC, 0xD1, 0x01, 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, + 0xD9, 0xFF, 0x37, 0x00, 0xF7, 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, + 0xF7, 0x53, 0x44, 0xFB, 0x14, 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, + 0xBE, 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, + 0x00, 0x7B, 0xFF, 0x29, 0x01, 0x01, 0xFE, 0xF1, 0x02, 0x07, 0xFC, + 0x6E, 0x05, 0xE6, 0x48, 0xFF, 0x01, 0x84, 0xFD, 0x2C, 0x02, 0x68, + 0xFE, 0xF9, 0x00, 0x8E, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x3A, 0xFF, 0xD4, 0x01, 0x7A, 0xFC, 0x2B, 0x06, 0x1E, 0xF5, 0x56, + 0x19, 0x0C, 0x42, 0xAA, 0xF5, 0xC9, 0x03, 0xA4, 0xFE, 0x54, 0x00, + 0x09, 0x00, 0xEB, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x55, + 0xFF, 0xB6, 0x01, 0x5F, 0xFC, 0x17, 0x07, 0x87, 0xF1, 0xC2, 0x2E, + 0xC0, 0x31, 0x7E, 0xF1, 0xF1, 0x06, 0x86, 0xFC, 0x9B, 0x01, 0x63, + 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xEA, 0xFF, + 0x93, 0x00, 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, 0x94, + 0x1C, 0x47, 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, 0x9C, 0xFF, 0xD4, 0x00, 0xB4, + 0xFE, 0x9A, 0x01, 0x95, 0xFE, 0xA8, 0xFF, 0x98, 0x48, 0x1D, 0x08, + 0xEE, 0xFA, 0x80, 0x03, 0xB9, 0xFD, 0x4B, 0x01, 0x6E, 0xFF, 0x25, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xA9, 0x01, + 0xE7, 0xFC, 0x33, 0x05, 0x60, 0xF7, 0xDA, 0x11, 0xB8, 0x45, 0x1C, + 0xF9, 0xD8, 0x01, 0xCA, 0xFF, 0xAF, 0xFF, 0x5A, 0x00, 0xCC, 0xFF, + 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDE, 0x01, 0x33, + 0xFC, 0x21, 0x07, 0x30, 0xF2, 0x5C, 0x27, 0x5B, 0x38, 0x0F, 0xF2, + 0x40, 0x06, 0x0E, 0xFD, 0x43, 0x01, 0x91, 0xFF, 0x18, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, 0x01, 0x57, 0xFD, + 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, 0xAA, 0xF2, 0x06, + 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x10, 0x00, 0xBE, 0xFF, 0x7D, 0x00, 0x69, 0xFF, 0x4B, 0x00, 0xF6, + 0x00, 0xCB, 0xFA, 0xD3, 0x46, 0xF0, 0x0E, 0x5E, 0xF8, 0xBE, 0x04, + 0x1E, 0xFD, 0x91, 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x28, 0x00, 0x62, 0xFF, 0x69, 0x01, 0x77, 0xFD, 0x04, 0x04, + 0xE2, 0xF9, 0xCB, 0x0A, 0x0D, 0x48, 0x94, 0xFD, 0x92, 0xFF, 0x10, + 0x01, 0xFE, 0xFE, 0xB1, 0x00, 0xAA, 0xFF, 0x15, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x43, 0xFC, 0xC0, 0x06, 0x8F, + 0xF3, 0xB1, 0x1F, 0x18, 0x3E, 0x9B, 0xF3, 0x16, 0x05, 0xD5, 0xFD, + 0xCC, 0x00, 0xCE, 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x22, + 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, 0xFC, 0xB8, 0x06, 0x9C, 0xF1, + 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, 0x29, 0x07, 0x46, 0xFC, 0xCA, + 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDF, 0xFF, + 0x29, 0x00, 0x14, 0x00, 0x16, 0xFF, 0x0C, 0x03, 0xEE, 0xF6, 0xB0, + 0x43, 0x47, 0x16, 0xFC, 0xF5, 0xCF, 0x05, 0xA1, 0xFC, 0xC6, 0x01, + 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, + 0xFF, 0x1B, 0x01, 0x20, 0xFE, 0xB6, 0x02, 0x7A, 0xFC, 0x5F, 0x04, + 0xF4, 0x48, 0xFF, 0x02, 0x13, 0xFD, 0x67, 0x02, 0x49, 0xFE, 0x07, + 0x01, 0x88, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, + 0xCF, 0x01, 0x8A, 0xFC, 0x05, 0x06, 0x7B, 0xF5, 0x06, 0x18, 0xC7, + 0x42, 0x2F, 0xF6, 0x7A, 0x03, 0xD4, 0xFE, 0x39, 0x00, 0x17, 0x00, + 0xE6, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, + 0x01, 0x53, 0xFC, 0x21, 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, + 0x86, 0xF1, 0xDB, 0x06, 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFB, 0xFF, 0xDE, 0xFF, 0xAC, 0x00, + 0x0B, 0xFE, 0xC1, 0x04, 0x1B, 0xF4, 0x47, 0x3F, 0xEA, 0x1D, 0xF5, + 0xF3, 0x9C, 0x06, 0x4F, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x16, 0x00, 0xA2, 0xFF, 0xC5, 0x00, 0xD4, 0xFE, 0x5F, + 0x01, 0x03, 0xFF, 0xBF, 0xFE, 0x63, 0x48, 0x40, 0x09, 0x7B, 0xFA, + 0xB9, 0x03, 0x9D, 0xFD, 0x58, 0x01, 0x69, 0xFF, 0x26, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4D, 0xFF, 0x9F, 0x01, 0xFE, 0xFC, + 0x02, 0x05, 0xCB, 0xF7, 0x98, 0x10, 0x39, 0x46, 0xD0, 0xF9, 0x78, + 0x01, 0x00, 0x00, 0x91, 0xFF, 0x69, 0x00, 0xC6, 0xFF, 0x0E, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, + 0x07, 0x61, 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, + 0x2C, 0xFD, 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0x13, 0x00, 0x9E, 0xFF, 0x2B, 0x01, 0x37, 0xFD, 0x05, 0x06, + 0x54, 0xF2, 0xC2, 0x39, 0x99, 0x25, 0x73, 0xF2, 0x14, 0x07, 0x31, + 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, + 0xC4, 0xFF, 0x6E, 0x00, 0x87, 0xFF, 0x13, 0x00, 0x58, 0x01, 0x0D, + 0xFA, 0x61, 0x46, 0x2D, 0x10, 0xF0, 0xF7, 0xF1, 0x04, 0x05, 0xFD, + 0x9C, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, + 0x00, 0x67, 0xFF, 0x5C, 0x01, 0x93, 0xFD, 0xCC, 0x03, 0x54, 0xFA, + 0xA2, 0x09, 0x4F, 0x48, 0x73, 0xFE, 0x27, 0xFF, 0x4B, 0x01, 0xDE, + 0xFE, 0xC0, 0x00, 0xA4, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, 0xF3, 0x5B, + 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, 0xB4, 0x00, + 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6D, + 0xFF, 0x8A, 0x01, 0x9F, 0xFC, 0xD3, 0x06, 0x8A, 0xF1, 0x57, 0x33, + 0x17, 0x2D, 0x9C, 0xF1, 0x24, 0x07, 0x4F, 0xFC, 0xC3, 0x01, 0x4E, + 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE4, 0xFF, 0x1B, 0x00, + 0x30, 0x00, 0xE4, 0xFE, 0x5F, 0x03, 0x5E, 0xF6, 0x02, 0x43, 0x96, + 0x17, 0x9B, 0xF5, 0xF8, 0x05, 0x8F, 0xFC, 0xCC, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x86, 0xFF, 0x0C, 0x01, 0x3E, + 0xFE, 0x7B, 0x02, 0xED, 0xFC, 0x56, 0x03, 0xF5, 0x48, 0x06, 0x04, + 0xA1, 0xFC, 0xA3, 0x02, 0x2A, 0xFE, 0x16, 0x01, 0x83, 0xFF, 0x1F, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, 0xC8, 0x01, + 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, 0xB6, 0x16, 0x77, 0x43, 0xBD, + 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, 0x00, 0x25, 0x00, 0xE1, 0xFF, + 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC8, 0x01, 0x49, + 0xFC, 0x27, 0x07, 0xAB, 0xF1, 0x3E, 0x2C, 0x1E, 0x34, 0x95, 0xF1, + 0xC1, 0x06, 0xAE, 0xFC, 0x81, 0x01, 0x71, 0xFF, 0x23, 0x00, 0xFE, + 0xFF, 0x02, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xC4, 0x00, 0xE2, 0xFD, + 0x01, 0x05, 0xBA, 0xF3, 0x64, 0x3E, 0x3F, 0x1F, 0xA8, 0xF3, 0xB8, + 0x06, 0x46, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x15, 0x00, 0xA8, 0xFF, 0xB6, 0x00, 0xF3, 0xFE, 0x24, 0x01, 0x6E, + 0xFF, 0xDE, 0xFD, 0x25, 0x48, 0x68, 0x0A, 0x08, 0xFA, 0xF2, 0x03, + 0x81, 0xFD, 0x65, 0x01, 0x64, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x95, 0x01, 0x15, 0xFD, 0xCF, 0x04, + 0x39, 0xF8, 0x59, 0x0F, 0xAF, 0x46, 0x8B, 0xFA, 0x17, 0x01, 0x38, + 0x00, 0x73, 0xFF, 0x78, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x39, 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0B, 0x07, 0x97, + 0xF2, 0xB8, 0x24, 0x71, 0x3A, 0x7B, 0xF2, 0xE6, 0x05, 0x4C, 0xFD, + 0x1D, 0x01, 0xA4, 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x16, + 0x00, 0x94, 0xFF, 0x3D, 0x01, 0x18, 0xFD, 0x32, 0x06, 0x1F, 0xF2, + 0xB5, 0x38, 0xEB, 0x26, 0x40, 0xF2, 0x1E, 0x07, 0x32, 0xFC, 0xDF, + 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCA, 0xFF, + 0x5F, 0x00, 0xA5, 0xFF, 0xDC, 0xFF, 0xB8, 0x01, 0x57, 0xF9, 0xE5, + 0x45, 0x6E, 0x11, 0x83, 0xF7, 0x23, 0x05, 0xEE, 0xFC, 0xA6, 0x01, + 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6C, + 0xFF, 0x4F, 0x01, 0xB0, 0xFD, 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, + 0x86, 0x48, 0x5A, 0xFF, 0xBA, 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, + 0x00, 0x9E, 0xFF, 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE0, 0x01, 0x56, 0xFC, 0x89, 0x06, 0x2B, 0xF4, 0x06, 0x1D, 0xD7, + 0x3F, 0x61, 0xF4, 0x94, 0x04, 0x27, 0xFE, 0x9C, 0x00, 0xE6, 0xFF, + 0xF8, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x27, 0x00, 0x66, 0xFF, 0x97, + 0x01, 0x8C, 0xFC, 0xEA, 0x06, 0x80, 0xF1, 0x26, 0x32, 0x58, 0x2E, + 0x8B, 0xF1, 0x1B, 0x07, 0x5B, 0xFC, 0xBA, 0x01, 0x53, 0xFF, 0x2D, + 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE9, 0xFF, 0x0E, 0x00, 0x4B, 0x00, + 0xB4, 0xFE, 0xAF, 0x03, 0xD5, 0xF5, 0x4D, 0x42, 0xE6, 0x18, 0x3C, + 0xF5, 0x1F, 0x06, 0x7F, 0xFC, 0xD3, 0x01, 0x3B, 0xFF, 0x35, 0x00, + 0xFE, 0xFF, 0x1C, 0x00, 0x8C, 0xFF, 0xFE, 0x00, 0x5D, 0xFE, 0x3F, + 0x02, 0x5E, 0xFD, 0x54, 0x02, 0xEC, 0x48, 0x13, 0x05, 0x2E, 0xFC, + 0xDE, 0x02, 0x0C, 0xFE, 0x24, 0x01, 0x7D, 0xFF, 0x20, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x41, 0xFF, 0xC1, 0x01, 0xAD, 0xFC, + 0xB2, 0x05, 0x3F, 0xF6, 0x69, 0x15, 0x1F, 0x44, 0x53, 0xF7, 0xD3, + 0x02, 0x38, 0xFF, 0x01, 0x00, 0x33, 0x00, 0xDB, 0xFF, 0x09, 0x00, + 0xFD, 0xFF, 0x31, 0x00, 0x47, 0xFF, 0xCF, 0x01, 0x40, 0xFC, 0x2A, + 0x07, 0xC6, 0xF1, 0xF7, 0x2A, 0x46, 0x35, 0xAB, 0xF1, 0xA4, 0x06, + 0xC4, 0xFC, 0x72, 0x01, 0x79, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x02, + 0x00, 0x04, 0x00, 0xC6, 0xFF, 0xDB, 0x00, 0xBB, 0xFD, 0x3E, 0x05, + 0x60, 0xF3, 0x7B, 0x3D, 0x94, 0x20, 0x5E, 0xF3, 0xD0, 0x06, 0x3E, + 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, + 0xAE, 0xFF, 0xA7, 0x00, 0x12, 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, + 0xFD, 0xDC, 0x47, 0x95, 0x0B, 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, + 0x71, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, + 0x00, 0x55, 0xFF, 0x8A, 0x01, 0x2E, 0xFD, 0x9B, 0x04, 0xA8, 0xF8, + 0x1F, 0x0E, 0x1A, 0x47, 0x4E, 0xFB, 0xB3, 0x00, 0x70, 0x00, 0x54, + 0xFF, 0x87, 0x00, 0xBB, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, 0xFB, 0x06, 0xD2, 0xF2, 0x64, + 0x23, 0x73, 0x3B, 0xBC, 0xF2, 0xB4, 0x05, 0x6E, 0xFD, 0x09, 0x01, + 0xAF, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8B, + 0xFF, 0x4F, 0x01, 0xFB, 0xFC, 0x5A, 0x06, 0xF2, 0xF1, 0xA0, 0x37, + 0x3A, 0x28, 0x13, 0xF2, 0x25, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, + 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xD0, 0xFF, 0x51, 0x00, + 0xC3, 0xFF, 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, 0xB2, + 0x12, 0x19, 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, 0xFF, + 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x71, 0xFF, 0x42, + 0x01, 0xCD, 0xFD, 0x59, 0x03, 0x3B, 0xFB, 0x5E, 0x07, 0xB3, 0x48, + 0x48, 0x00, 0x4B, 0xFE, 0xC2, 0x01, 0x9F, 0xFE, 0xDE, 0x00, 0x98, + 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDD, 0x01, + 0x62, 0xFC, 0x69, 0x06, 0x7F, 0xF4, 0xB2, 0x1B, 0xAB, 0x40, 0xD0, + 0xF4, 0x4E, 0x04, 0x53, 0xFE, 0x83, 0x00, 0xF2, 0xFF, 0xF4, 0xFF, + 0x05, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA3, 0x01, 0x7A, + 0xFC, 0xFD, 0x06, 0x7C, 0xF1, 0xF2, 0x30, 0x96, 0x2F, 0x80, 0xF1, + 0x0F, 0x07, 0x69, 0xFC, 0xB0, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0xFD, + 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, 0x00, 0x85, 0xFE, + 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, 0xE1, 0xF4, 0x43, + 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, + 0x1B, 0x00, 0x92, 0xFF, 0xEF, 0x00, 0x7D, 0xFE, 0x04, 0x02, 0xCF, + 0xFD, 0x58, 0x01, 0xD7, 0x48, 0x26, 0x06, 0xBB, 0xFB, 0x19, 0x03, + 0xED, 0xFD, 0x32, 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xB9, 0x01, 0xC1, 0xFC, 0x86, 0x05, + 0xA5, 0xF6, 0x1E, 0x14, 0xBA, 0x44, 0xF0, 0xF7, 0x7B, 0x02, 0x6B, + 0xFF, 0xE4, 0xFF, 0x41, 0x00, 0xD6, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, + 0x33, 0x00, 0x43, 0xFF, 0xD5, 0x01, 0x3A, 0xFC, 0x2A, 0x07, 0xE7, + 0xF1, 0xAC, 0x29, 0x66, 0x36, 0xC9, 0xF1, 0x83, 0x06, 0xDD, 0xFC, + 0x62, 0x01, 0x81, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x08, + 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, 0xFD, 0x78, 0x05, 0x0E, 0xF3, + 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, 0xE6, 0x06, 0x38, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB4, 0xFF, + 0x98, 0x00, 0x32, 0xFF, 0xB0, 0x00, 0x41, 0x00, 0x30, 0xFC, 0x86, + 0x47, 0xC6, 0x0C, 0x24, 0xF9, 0x60, 0x04, 0x4B, 0xFD, 0x7D, 0x01, + 0x5A, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x5A, + 0xFF, 0x7E, 0x01, 0x48, 0xFD, 0x66, 0x04, 0x18, 0xF9, 0xE8, 0x0C, + 0x7C, 0x47, 0x19, 0xFC, 0x4D, 0x00, 0xA9, 0x00, 0x35, 0xFF, 0x96, + 0x00, 0xB5, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE6, 0x01, 0x38, 0xFC, 0xE9, 0x06, 0x12, 0xF3, 0x10, 0x22, 0x6E, + 0x3C, 0x05, 0xF3, 0x7E, 0x05, 0x91, 0xFD, 0xF4, 0x00, 0xBA, 0xFF, + 0x09, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, + 0x01, 0xE0, 0xFC, 0x7F, 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, + 0xEB, 0xF1, 0x2A, 0x07, 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, + 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD5, 0xFF, 0x42, 0x00, 0xE1, 0xFF, + 0x71, 0xFF, 0x71, 0x02, 0x02, 0xF8, 0xCC, 0x44, 0xFA, 0x13, 0xB0, + 0xF6, 0x81, 0x05, 0xC3, 0xFC, 0xB8, 0x01, 0x44, 0xFF, 0x31, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x77, 0xFF, 0x34, 0x01, 0xEA, + 0xFD, 0x1F, 0x03, 0xAE, 0xFB, 0x45, 0x06, 0xD5, 0x48, 0x3C, 0x01, + 0xDC, 0xFD, 0xFD, 0x01, 0x80, 0xFE, 0xED, 0x00, 0x93, 0xFF, 0x1B, + 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x6F, 0xFC, + 0x47, 0x06, 0xD7, 0xF4, 0x5D, 0x1A, 0x74, 0x41, 0x48, 0xF5, 0x04, + 0x04, 0x80, 0xFE, 0x69, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0x05, 0x00, + 0xFD, 0xFF, 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, + 0x07, 0x80, 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, + 0x78, 0xFC, 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x05, + 0x00, 0xF3, 0xFF, 0xF4, 0xFF, 0x80, 0x00, 0x58, 0xFE, 0x46, 0x04, + 0xDD, 0xF4, 0xC3, 0x40, 0x8C, 0x1B, 0x89, 0xF4, 0x66, 0x06, 0x63, + 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, + 0x98, 0xFF, 0xE0, 0x00, 0x9C, 0xFE, 0xC8, 0x01, 0x3F, 0xFE, 0x62, + 0x00, 0xB8, 0x48, 0x3F, 0x07, 0x47, 0xFB, 0x53, 0x03, 0xD0, 0xFD, + 0x40, 0x01, 0x72, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, + 0x00, 0x47, 0xFF, 0xB0, 0x01, 0xD6, 0xFC, 0x58, 0x05, 0x0D, 0xF7, + 0xD7, 0x12, 0x4E, 0x45, 0x96, 0xF8, 0x20, 0x02, 0xA0, 0xFF, 0xC7, + 0xFF, 0x4F, 0x00, 0xD0, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, 0xF2, 0x60, + 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, 0x51, 0x01, + 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00, 0xB0, + 0xFF, 0x07, 0x01, 0x72, 0xFD, 0xAE, 0x05, 0xC4, 0xF2, 0x90, 0x3B, + 0x3F, 0x23, 0xD9, 0xF2, 0xF9, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, 0x00, 0xBA, 0xFF, 0x89, 0x00, + 0x51, 0xFF, 0x77, 0x00, 0xA7, 0x00, 0x64, 0xFB, 0x26, 0x47, 0xFC, + 0x0D, 0xB4, 0xF8, 0x95, 0x04, 0x31, 0xFD, 0x88, 0x01, 0x56, 0xFF, + 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5E, 0xFF, 0x72, + 0x01, 0x62, 0xFD, 0x2F, 0x04, 0x89, 0xF9, 0xB6, 0x0B, 0xD2, 0x47, + 0xEB, 0xFC, 0xE4, 0xFF, 0xE3, 0x00, 0x16, 0xFF, 0xA5, 0x00, 0xAF, + 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, + 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, 0xBA, 0x20, 0x61, 0x3D, 0x56, + 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, 0x00, 0xC5, 0xFF, 0x05, 0x00, + 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x7A, 0xFF, 0x70, 0x01, 0xC7, + 0xFC, 0xA0, 0x06, 0xAE, 0xF1, 0x65, 0x35, 0xD1, 0x2A, 0xCA, 0xF1, + 0x2A, 0x07, 0x40, 0xFC, 0xD0, 0x01, 0x47, 0xFF, 0x32, 0x00, 0xFD, + 0xFF, 0x09, 0x00, 0xDB, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x3D, 0xFF, + 0xC9, 0x02, 0x64, 0xF7, 0x2F, 0x44, 0x44, 0x15, 0x4A, 0xF6, 0xAD, + 0x05, 0xAF, 0xFC, 0xC0, 0x01, 0x41, 0xFF, 0x32, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x21, 0x00, 0x7C, 0xFF, 0x26, 0x01, 0x08, 0xFE, 0xE4, + 0x02, 0x21, 0xFC, 0x31, 0x05, 0xEB, 0x48, 0x37, 0x02, 0x6B, 0xFD, + 0x39, 0x02, 0x61, 0xFE, 0xFC, 0x00, 0x8D, 0xFF, 0x1C, 0x00, 0xFE, + 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD3, 0x01, 0x7D, 0xFC, 0x23, 0x06, + 0x32, 0xF5, 0x0C, 0x19, 0x38, 0x42, 0xC7, 0xF5, 0xB8, 0x03, 0xAF, + 0xFE, 0x4E, 0x00, 0x0C, 0x00, 0xEA, 0xFF, 0x06, 0x00, 0xFD, 0xFF, + 0x2D, 0x00, 0x54, 0xFF, 0xB8, 0x01, 0x5D, 0xFC, 0x1A, 0x07, 0x8A, + 0xF1, 0x7B, 0x2E, 0x04, 0x32, 0x7F, 0xF1, 0xEC, 0x06, 0x8A, 0xFC, + 0x98, 0x01, 0x65, 0xFF, 0x27, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF8, + 0xFF, 0xE7, 0xFF, 0x99, 0x00, 0x2C, 0xFE, 0x8C, 0x04, 0x6D, 0xF4, + 0xF0, 0x3F, 0xE0, 0x1C, 0x34, 0xF4, 0x85, 0x06, 0x57, 0xFC, 0xE0, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, 0x9E, 0xFF, + 0xD1, 0x00, 0xBB, 0xFE, 0x8D, 0x01, 0xAE, 0xFE, 0x74, 0xFF, 0x8D, + 0x48, 0x5D, 0x08, 0xD4, 0xFA, 0x8D, 0x03, 0xB3, 0xFD, 0x4E, 0x01, + 0x6D, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, + 0xFF, 0xA7, 0x01, 0xEC, 0xFC, 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, + 0xD7, 0x45, 0x43, 0xF9, 0xC3, 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, + 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3D, 0xFF, + 0xDF, 0x01, 0x32, 0xFC, 0x1F, 0x07, 0x3B, 0xF2, 0x11, 0x27, 0x97, + 0x38, 0x19, 0xF2, 0x36, 0x06, 0x15, 0xFD, 0x3F, 0x01, 0x93, 0xFF, + 0x17, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x10, 0x00, 0xA6, 0xFF, 0x1B, + 0x01, 0x50, 0xFD, 0xE1, 0x05, 0x82, 0xF2, 0x8F, 0x3A, 0x92, 0x24, + 0x9D, 0xF2, 0x09, 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0x7A, 0x00, 0x70, 0xFF, + 0x3E, 0x00, 0x0C, 0x01, 0xA1, 0xFA, 0xBB, 0x46, 0x36, 0x0F, 0x45, + 0xF8, 0xC9, 0x04, 0x18, 0xFD, 0x93, 0x01, 0x52, 0xFF, 0x2D, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x63, 0xFF, 0x66, 0x01, 0x7D, + 0xFD, 0xF8, 0x03, 0xFB, 0xF9, 0x89, 0x0A, 0x1D, 0x48, 0xC5, 0xFD, + 0x7A, 0xFF, 0x1D, 0x01, 0xF7, 0xFE, 0xB4, 0x00, 0xA9, 0xFF, 0x15, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x45, 0xFC, + 0xBB, 0x06, 0xA0, 0xF3, 0x64, 0x1F, 0x4A, 0x3E, 0xB0, 0xF3, 0x08, + 0x05, 0xDE, 0xFD, 0xC7, 0x00, 0xD0, 0xFF, 0x00, 0x00, 0x02, 0x00, + 0xFE, 0xFF, 0x23, 0x00, 0x72, 0xFF, 0x7F, 0x01, 0xB0, 0xFC, 0xBE, + 0x06, 0x97, 0xF1, 0x3F, 0x34, 0x19, 0x2C, 0xAD, 0xF1, 0x28, 0x07, + 0x48, 0xFC, 0xC9, 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, + 0x00, 0xE0, 0xFF, 0x26, 0x00, 0x1A, 0x00, 0x0B, 0xFF, 0x1E, 0x03, + 0xCD, 0xF6, 0x89, 0x43, 0x91, 0x16, 0xE7, 0xF5, 0xD8, 0x05, 0x9D, + 0xFC, 0xC7, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x1F, 0x00, 0x82, 0xFF, 0x18, 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, + 0xFC, 0x24, 0x04, 0xF5, 0x48, 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, + 0x42, 0xFE, 0x0B, 0x01, 0x87, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, + 0x00, 0x3C, 0xFF, 0xCD, 0x01, 0x8E, 0xFC, 0xFC, 0x05, 0x90, 0xF5, + 0xBB, 0x17, 0xEE, 0x42, 0x4E, 0xF6, 0x68, 0x03, 0xDF, 0xFE, 0x33, + 0x00, 0x1A, 0x00, 0xE5, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2F, 0x00, + 0x4F, 0xFF, 0xC2, 0x01, 0x51, 0xFC, 0x23, 0x07, 0x9A, 0xF1, 0x3A, + 0x2D, 0x35, 0x33, 0x89, 0xF1, 0xD5, 0x06, 0x9D, 0xFC, 0x8B, 0x01, + 0x6C, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFC, 0xFF, 0xDB, + 0xFF, 0xB2, 0x00, 0x02, 0xFE, 0xCF, 0x04, 0x05, 0xF4, 0x16, 0x3F, + 0x36, 0x1E, 0xE4, 0xF3, 0xA3, 0x06, 0x4D, 0xFC, 0xE3, 0x01, 0x36, + 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC2, 0x00, + 0xDB, 0xFE, 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, 0x81, + 0x09, 0x61, 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, 0xFF, + 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9D, + 0x01, 0x03, 0xFD, 0xF7, 0x04, 0xE3, 0xF7, 0x51, 0x10, 0x55, 0x46, + 0xF9, 0xF9, 0x63, 0x01, 0x0D, 0x00, 0x8B, 0xFF, 0x6D, 0x00, 0xC5, + 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, + 0x31, 0xFC, 0x15, 0x07, 0x6D, 0xF2, 0xBF, 0x25, 0xA5, 0x39, 0x4D, + 0xF2, 0x0B, 0x06, 0x33, 0xFD, 0x2D, 0x01, 0x9D, 0xFF, 0x13, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x14, 0x00, 0x9C, 0xFF, 0x2F, 0x01, 0x30, + 0xFD, 0x10, 0x06, 0x47, 0xF2, 0x87, 0x39, 0xE5, 0x25, 0x67, 0xF2, + 0x16, 0x07, 0x31, 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, 0xFF, 0x06, 0x00, + 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, 0xD7, 0xF7, 0xFC, + 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, 0x59, 0x01, 0x99, 0xFD, 0xC0, + 0x03, 0x6E, 0xFA, 0x61, 0x09, 0x5D, 0x48, 0xA6, 0xFE, 0x0F, 0xFF, + 0x58, 0x01, 0xD7, 0xFE, 0xC3, 0x00, 0xA3, 0xFF, 0x16, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4E, 0xFC, 0xA0, 0x06, + 0xED, 0xF3, 0x0F, 0x1E, 0x2D, 0x3F, 0x10, 0xF4, 0xC8, 0x04, 0x07, + 0xFE, 0xAF, 0x00, 0xDC, 0xFF, 0xFC, 0xFF, 0x03, 0x00, 0xFD, 0xFF, + 0x25, 0x00, 0x6B, 0xFF, 0x8D, 0x01, 0x9B, 0xFC, 0xD8, 0x06, 0x87, + 0xF1, 0x13, 0x33, 0x5E, 0x2D, 0x98, 0xF1, 0x22, 0x07, 0x52, 0xFC, + 0xC1, 0x01, 0x4F, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE5, + 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, 0xFE, 0x71, 0x03, 0x3F, 0xF6, + 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, 0x00, 0x06, 0x8C, 0xFC, 0xCE, + 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x88, 0xFF, + 0x09, 0x01, 0x45, 0xFE, 0x6E, 0x02, 0x06, 0xFD, 0x1C, 0x03, 0xF4, + 0x48, 0x41, 0x04, 0x87, 0xFC, 0xB0, 0x02, 0x23, 0xFE, 0x19, 0x01, + 0x81, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, + 0xFF, 0xC6, 0x01, 0x9F, 0xFC, 0xD3, 0x05, 0xF1, 0xF5, 0x6C, 0x16, + 0x9E, 0x43, 0xDD, 0xF6, 0x15, 0x03, 0x10, 0xFF, 0x17, 0x00, 0x28, + 0x00, 0xDF, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, + 0xCA, 0x01, 0x47, 0xFC, 0x28, 0x07, 0xB0, 0xF1, 0xF5, 0x2B, 0x60, + 0x34, 0x9A, 0xF1, 0xBB, 0x06, 0xB3, 0xFC, 0x7D, 0x01, 0x73, 0xFF, + 0x22, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, + 0x00, 0xDA, 0xFD, 0x0F, 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, + 0x97, 0xF3, 0xBD, 0x06, 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xAA, 0xFF, 0xB3, 0x00, 0xFA, 0xFE, + 0x17, 0x01, 0x86, 0xFF, 0xAC, 0xFD, 0x16, 0x48, 0xAA, 0x0A, 0xEE, + 0xF9, 0xFE, 0x03, 0x7A, 0xFD, 0x67, 0x01, 0x63, 0xFF, 0x28, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x92, 0x01, 0x1B, + 0xFD, 0xC4, 0x04, 0x51, 0xF8, 0x13, 0x0F, 0xC8, 0x46, 0xB6, 0xFA, + 0x01, 0x01, 0x44, 0x00, 0x6C, 0xFF, 0x7B, 0x00, 0xBF, 0xFF, 0x10, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, + 0x08, 0x07, 0xA4, 0xF2, 0x6D, 0x24, 0xAD, 0x3A, 0x88, 0xF2, 0xDB, + 0x05, 0x53, 0xFD, 0x19, 0x01, 0xA7, 0xFF, 0x10, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, + 0x06, 0x14, 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, + 0x33, 0xFC, 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0D, + 0x00, 0xCB, 0xFF, 0x5C, 0x00, 0xAC, 0xFF, 0xD0, 0xFF, 0xCD, 0x01, + 0x30, 0xF9, 0xC8, 0x45, 0xB6, 0x11, 0x6B, 0xF7, 0x2D, 0x05, 0xE9, + 0xFC, 0xA8, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x25, 0x00, 0x6D, 0xFF, 0x4C, 0x01, 0xB6, 0xFD, 0x86, 0x03, 0xE1, + 0xFA, 0x3D, 0x08, 0x92, 0x48, 0x8E, 0xFF, 0xA1, 0xFE, 0x93, 0x01, + 0xB8, 0xFE, 0xD3, 0x00, 0x9D, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x58, 0xFC, 0x82, 0x06, 0x3E, 0xF4, + 0xBA, 0x1C, 0x07, 0x40, 0x79, 0xF4, 0x84, 0x04, 0x31, 0xFE, 0x96, + 0x00, 0xE8, 0xFF, 0xF7, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, + 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, 0xF1, 0xE3, + 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, 0xB7, 0x01, + 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEA, 0xFF, 0x0B, + 0x00, 0x51, 0x00, 0xAA, 0xFE, 0xC0, 0x03, 0xB8, 0xF5, 0x21, 0x42, + 0x31, 0x19, 0x28, 0xF5, 0x27, 0x06, 0x7C, 0xFC, 0xD4, 0x01, 0x3A, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8D, 0xFF, 0xFA, 0x00, + 0x64, 0xFE, 0x32, 0x02, 0x78, 0xFD, 0x1B, 0x02, 0xEA, 0x48, 0x50, + 0x05, 0x14, 0xFC, 0xEB, 0x02, 0x05, 0xFE, 0x27, 0x01, 0x7C, 0xFF, + 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x41, 0xFF, 0xBF, + 0x01, 0xB2, 0xFC, 0xA9, 0x05, 0x55, 0xF6, 0x20, 0x15, 0x42, 0x44, + 0x75, 0xF7, 0xBF, 0x02, 0x43, 0xFF, 0xFA, 0xFF, 0x36, 0x00, 0xDA, + 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, 0xD1, 0x01, + 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, 0xAE, 0x2A, 0x86, 0x35, 0xB1, + 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, 0x01, 0x7B, 0xFF, 0x20, 0x00, + 0xFE, 0xFF, 0x02, 0x00, 0x05, 0x00, 0xC3, 0xFF, 0xE0, 0x00, 0xB3, + 0xFD, 0x4B, 0x05, 0x4D, 0xF3, 0x45, 0x3D, 0xE0, 0x20, 0x4F, 0xF3, + 0xD5, 0x06, 0x3D, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA4, 0x00, 0x19, 0xFF, 0xDD, 0x00, + 0xF0, 0xFF, 0xD4, 0xFC, 0xC9, 0x47, 0xD8, 0x0B, 0x7C, 0xF9, 0x35, + 0x04, 0x5F, 0xFD, 0x74, 0x01, 0x5E, 0xFF, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2C, 0x00, 0x56, 0xFF, 0x87, 0x01, 0x34, 0xFD, 0x8F, + 0x04, 0xC0, 0xF8, 0xD9, 0x0D, 0x31, 0x47, 0x7B, 0xFB, 0x9C, 0x00, + 0x7D, 0x00, 0x4D, 0xFF, 0x8A, 0x00, 0xB9, 0xFF, 0x11, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF7, 0x06, + 0xE0, 0xF2, 0x18, 0x23, 0xAB, 0x3B, 0xCC, 0xF2, 0xA8, 0x05, 0x76, + 0xFD, 0x04, 0x01, 0xB1, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0xFE, 0xFF, + 0x1A, 0x00, 0x89, 0xFF, 0x53, 0x01, 0xF5, 0xFC, 0x63, 0x06, 0xE9, + 0xF1, 0x63, 0x37, 0x85, 0x28, 0x09, 0xF2, 0x27, 0x07, 0x35, 0xFC, + 0xDA, 0x01, 0x40, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xD1, + 0xFF, 0x4E, 0x00, 0xCA, 0xFF, 0x9A, 0xFF, 0x2A, 0x02, 0x83, 0xF8, + 0x3F, 0x45, 0xFB, 0x12, 0x01, 0xF7, 0x5D, 0x05, 0xD3, 0xFC, 0xB1, + 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, + 0x73, 0xFF, 0x3F, 0x01, 0xD3, 0xFD, 0x4C, 0x03, 0x54, 0xFB, 0x1F, + 0x07, 0xBB, 0x48, 0x7D, 0x00, 0x33, 0xFE, 0xCF, 0x01, 0x98, 0xFE, + 0xE2, 0x00, 0x97, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, + 0xFF, 0xDC, 0x01, 0x64, 0xFC, 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, + 0xD9, 0x40, 0xEA, 0xF4, 0x3E, 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, + 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5E, 0xFF, + 0xA6, 0x01, 0x76, 0xFC, 0x01, 0x07, 0x7D, 0xF1, 0xAD, 0x30, 0xDC, + 0x2F, 0x7F, 0xF1, 0x0C, 0x07, 0x6C, 0xFC, 0xAD, 0x01, 0x5A, 0xFF, + 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xEF, 0xFF, 0xFE, 0xFF, 0x6C, + 0x00, 0x7B, 0xFE, 0x0C, 0x04, 0x3A, 0xF5, 0x5F, 0x41, 0x83, 0x1A, + 0xCD, 0xF4, 0x4B, 0x06, 0x6D, 0xFC, 0xD9, 0x01, 0x39, 0xFF, 0x35, + 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x93, 0xFF, 0xEC, 0x00, 0x83, 0xFE, + 0xF7, 0x01, 0xE8, 0xFD, 0x21, 0x01, 0xD2, 0x48, 0x64, 0x06, 0xA1, + 0xFB, 0x26, 0x03, 0xE7, 0xFD, 0x35, 0x01, 0x76, 0xFF, 0x22, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x44, 0xFF, 0xB7, 0x01, 0xC5, + 0xFC, 0x7C, 0x05, 0xBC, 0xF6, 0xD5, 0x13, 0xDC, 0x44, 0x14, 0xF8, + 0x67, 0x02, 0x77, 0xFF, 0xDD, 0xFF, 0x44, 0x00, 0xD5, 0xFF, 0x0B, + 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x39, 0xFC, + 0x29, 0x07, 0xEF, 0xF1, 0x62, 0x29, 0xA5, 0x36, 0xD0, 0xF1, 0x7B, + 0x06, 0xE3, 0xFC, 0x5E, 0x01, 0x83, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, + 0x01, 0x00, 0x09, 0x00, 0xB8, 0xFF, 0xF6, 0x00, 0x8D, 0xFD, 0x84, + 0x05, 0xFD, 0xF2, 0x52, 0x3C, 0x35, 0x22, 0x0B, 0xF3, 0xEB, 0x06, + 0x37, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, + 0x00, 0xB5, 0xFF, 0x94, 0x00, 0x39, 0xFF, 0xA3, 0x00, 0x58, 0x00, + 0x02, 0xFC, 0x73, 0x47, 0x0B, 0x0D, 0x0B, 0xF9, 0x6C, 0x04, 0x45, + 0xFD, 0x80, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2A, 0x00, 0x5B, 0xFF, 0x7C, 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, + 0xF9, 0xA4, 0x0C, 0x90, 0x47, 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, + 0x2E, 0xFF, 0x99, 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x39, 0xFC, 0xE4, 0x06, 0x21, 0xF3, + 0xC4, 0x21, 0xA5, 0x3C, 0x16, 0xF3, 0x72, 0x05, 0x9A, 0xFD, 0xEF, + 0x00, 0xBC, 0xFF, 0x08, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1E, 0x00, + 0x80, 0xFF, 0x64, 0x01, 0xDA, 0xFC, 0x87, 0x06, 0xC5, 0xF1, 0x46, + 0x36, 0xD1, 0x29, 0xE3, 0xF1, 0x2A, 0x07, 0x3A, 0xFC, 0xD5, 0x01, + 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD6, 0xFF, 0x3F, + 0x00, 0xE7, 0xFF, 0x65, 0xFF, 0x85, 0x02, 0xDE, 0xF7, 0xA9, 0x44, + 0x43, 0x14, 0x99, 0xF6, 0x8B, 0x05, 0xBF, 0xFC, 0xBA, 0x01, 0x43, + 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x78, 0xFF, + 0x31, 0x01, 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, 0xDB, + 0x48, 0x73, 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, 0x00, + 0x91, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, + 0x01, 0x72, 0xFC, 0x3F, 0x06, 0xEB, 0xF4, 0x12, 0x1A, 0xA1, 0x41, + 0x63, 0xF5, 0xF3, 0x03, 0x8A, 0xFE, 0x63, 0x00, 0x02, 0x00, 0xEE, + 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x58, 0xFF, 0xB1, 0x01, + 0x67, 0xFC, 0x10, 0x07, 0x81, 0xF1, 0x73, 0x2F, 0x15, 0x31, 0x7C, + 0xF1, 0xFB, 0x06, 0x7C, 0xFC, 0xA2, 0x01, 0x60, 0xFF, 0x29, 0x00, + 0xFD, 0xFF, 0x04, 0x00, 0xF4, 0xFF, 0xF1, 0xFF, 0x85, 0x00, 0x4E, + 0xFE, 0x56, 0x04, 0xC3, 0xF4, 0x95, 0x40, 0xD8, 0x1B, 0x76, 0xF4, + 0x6D, 0x06, 0x60, 0xFC, 0xDD, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, 0xFE, 0xBB, 0x01, + 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, 0x2E, 0xFB, 0x60, + 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAE, 0x01, 0xDB, 0xFC, 0x4D, + 0x05, 0x24, 0xF7, 0x8E, 0x12, 0x6D, 0x45, 0xBC, 0xF8, 0x0C, 0x02, + 0xAC, 0xFF, 0xC0, 0xFF, 0x52, 0x00, 0xCF, 0xFF, 0x0C, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDC, 0x01, 0x34, 0xFC, 0x25, 0x07, + 0x18, 0xF2, 0x15, 0x28, 0xBF, 0x37, 0xF7, 0xF1, 0x56, 0x06, 0xFE, + 0xFC, 0x4D, 0x01, 0x8C, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x0D, 0x00, 0xAE, 0xFF, 0x0B, 0x01, 0x6A, 0xFD, 0xBA, 0x05, 0xB4, + 0xF2, 0x58, 0x3B, 0x8A, 0x23, 0xCB, 0xF2, 0xFD, 0x06, 0x34, 0xFC, + 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBB, + 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, 0x00, 0xBE, 0x00, 0x38, 0xFB, + 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, 0xA1, 0x04, 0x2B, 0xFD, 0x8B, + 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, + 0x5F, 0xFF, 0x70, 0x01, 0x68, 0xFD, 0x23, 0x04, 0xA2, 0xF9, 0x73, + 0x0B, 0xE4, 0x47, 0x1B, 0xFD, 0xCD, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, + 0xA9, 0x00, 0xAE, 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE6, 0x01, 0x3F, 0xFC, 0xCE, 0x06, 0x66, 0xF3, 0x6F, 0x20, + 0x96, 0x3D, 0x69, 0xF3, 0x38, 0x05, 0xBF, 0xFD, 0xD9, 0x00, 0xC7, + 0xFF, 0x04, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x78, 0xFF, + 0x74, 0x01, 0xC2, 0xFC, 0xA7, 0x06, 0xA8, 0xF1, 0x25, 0x35, 0x1B, + 0x2B, 0xC2, 0xF1, 0x2A, 0x07, 0x41, 0xFC, 0xCE, 0x01, 0x47, 0xFF, + 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, + 0x00, 0x32, 0xFF, 0xDC, 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, + 0x34, 0xF6, 0xB7, 0x05, 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7E, 0xFF, 0x23, 0x01, + 0x0F, 0xFE, 0xD7, 0x02, 0x3B, 0xFC, 0xF5, 0x04, 0xED, 0x48, 0x70, + 0x02, 0x52, 0xFD, 0x46, 0x02, 0x5A, 0xFE, 0xFF, 0x00, 0x8B, 0xFF, + 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xD2, 0x01, 0x81, + 0xFC, 0x1A, 0x06, 0x47, 0xF5, 0xC1, 0x18, 0x60, 0x42, 0xE4, 0xF5, + 0xA6, 0x03, 0xB9, 0xFE, 0x48, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0x07, + 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x53, 0xFF, 0xBB, 0x01, 0x5A, 0xFC, + 0x1C, 0x07, 0x8D, 0xF1, 0x34, 0x2E, 0x48, 0x32, 0x81, 0xF1, 0xE7, + 0x06, 0x8E, 0xFC, 0x96, 0x01, 0x66, 0xFF, 0x27, 0x00, 0xFD, 0xFF, + 0x04, 0x00, 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, + 0x04, 0x55, 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, + 0x55, 0xFC, 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, + 0x00, 0x9F, 0xFF, 0xCE, 0x00, 0xC2, 0xFE, 0x80, 0x01, 0xC6, 0xFE, + 0x40, 0xFF, 0x81, 0x48, 0x9E, 0x08, 0xBA, 0xFA, 0x9A, 0x03, 0xAC, + 0xFD, 0x51, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x2F, 0x00, 0x4B, 0xFF, 0xA4, 0x01, 0xF1, 0xFC, 0x1D, 0x05, 0x8F, + 0xF7, 0x4A, 0x11, 0xF2, 0x45, 0x6B, 0xF9, 0xAE, 0x01, 0xE2, 0xFF, + 0xA2, 0xFF, 0x61, 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x35, + 0x00, 0x3D, 0xFF, 0xE0, 0x01, 0x32, 0xFC, 0x1D, 0x07, 0x45, 0xF2, + 0xC6, 0x26, 0xD3, 0x38, 0x24, 0xF2, 0x2D, 0x06, 0x1B, 0xFD, 0x3B, + 0x01, 0x95, 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x11, 0x00, + 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, 0xF2, 0x54, + 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, 0xE4, 0x01, + 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC1, 0xFF, 0x76, + 0x00, 0x76, 0xFF, 0x32, 0x00, 0x22, 0x01, 0x76, 0xFA, 0xA3, 0x46, + 0x7D, 0x0F, 0x2C, 0xF8, 0xD5, 0x04, 0x13, 0xFD, 0x96, 0x01, 0x51, + 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x64, 0xFF, + 0x63, 0x01, 0x84, 0xFD, 0xEB, 0x03, 0x14, 0xFA, 0x47, 0x0A, 0x2C, + 0x48, 0xF6, 0xFD, 0x63, 0xFF, 0x2B, 0x01, 0xF0, 0xFE, 0xB8, 0x00, + 0xA8, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, + 0x01, 0x47, 0xFC, 0xB5, 0x06, 0xB0, 0xF3, 0x19, 0x1F, 0x7E, 0x3E, + 0xC4, 0xF3, 0xFA, 0x04, 0xE7, 0xFD, 0xC1, 0x00, 0xD3, 0xFF, 0xFF, + 0xFF, 0x02, 0x00, 0xFE, 0xFF, 0x23, 0x00, 0x71, 0xFF, 0x82, 0x01, + 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, 0xFD, 0x33, 0x62, 0x2C, 0xA8, + 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, 0x01, 0x4C, 0xFF, 0x30, 0x00, + 0xFD, 0xFF, 0x08, 0x00, 0xE1, 0xFF, 0x23, 0x00, 0x20, 0x00, 0x00, + 0xFF, 0x31, 0x03, 0xAD, 0xF6, 0x65, 0x43, 0xDC, 0x16, 0xD1, 0xF5, + 0xE1, 0x05, 0x99, 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, 0xFF, 0x14, 0x01, 0x2D, 0xFE, + 0x9C, 0x02, 0xAD, 0xFC, 0xE9, 0x03, 0xF6, 0x48, 0x73, 0x03, 0xE0, + 0xFC, 0x82, 0x02, 0x3B, 0xFE, 0x0E, 0x01, 0x86, 0xFF, 0x1E, 0x00, + 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCC, 0x01, 0x91, 0xFC, 0xF3, + 0x05, 0xA6, 0xF5, 0x70, 0x17, 0x17, 0x43, 0x6D, 0xF6, 0x56, 0x03, + 0xEA, 0xFE, 0x2D, 0x00, 0x1D, 0x00, 0xE4, 0xFF, 0x08, 0x00, 0xFD, + 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, 0x01, 0x4E, 0xFC, 0x24, 0x07, + 0x9E, 0xF1, 0xF2, 0x2C, 0x78, 0x33, 0x8C, 0xF1, 0xD0, 0x06, 0xA2, + 0xFC, 0x88, 0x01, 0x6D, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x03, 0x00, + 0xFD, 0xFF, 0xD8, 0xFF, 0xB7, 0x00, 0xF9, 0xFD, 0xDE, 0x04, 0xEF, + 0xF3, 0xE4, 0x3E, 0x81, 0x1E, 0xD2, 0xF3, 0xA9, 0x06, 0x4B, 0xFC, + 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA5, + 0xFF, 0xBE, 0x00, 0xE2, 0xFE, 0x45, 0x01, 0x33, 0xFF, 0x5A, 0xFE, + 0x48, 0x48, 0xC3, 0x09, 0x47, 0xFA, 0xD2, 0x03, 0x90, 0xFD, 0x5E, + 0x01, 0x66, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, + 0x4F, 0xFF, 0x9A, 0x01, 0x08, 0xFD, 0xEB, 0x04, 0xFC, 0xF7, 0x0A, + 0x10, 0x70, 0x46, 0x22, 0xFA, 0x4D, 0x01, 0x19, 0x00, 0x84, 0xFF, + 0x70, 0x00, 0xC4, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, + 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, + 0xDF, 0x39, 0x5A, 0xF2, 0x00, 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, + 0xFF, 0x13, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x15, 0x00, 0x99, 0xFF, + 0x33, 0x01, 0x29, 0xFD, 0x1A, 0x06, 0x3B, 0xF2, 0x4B, 0x39, 0x30, + 0x26, 0x5B, 0xF2, 0x19, 0x07, 0x31, 0xFC, 0xE1, 0x01, 0x3C, 0xFF, + 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, 0xC7, 0xFF, 0x68, 0x00, 0x95, + 0xFF, 0xFA, 0xFF, 0x83, 0x01, 0xBB, 0xF9, 0x2B, 0x46, 0xBB, 0x10, + 0xBF, 0xF7, 0x07, 0x05, 0xFB, 0xFC, 0xA0, 0x01, 0x4D, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x69, 0xFF, 0x56, 0x01, + 0xA0, 0xFD, 0xB3, 0x03, 0x87, 0xFA, 0x1F, 0x09, 0x6A, 0x48, 0xD9, + 0xFE, 0xF6, 0xFE, 0x65, 0x01, 0xD0, 0xFE, 0xC7, 0x00, 0xA2, 0xFF, + 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x50, + 0xFC, 0x99, 0x06, 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, 0x3F, 0x27, 0xF4, + 0xB9, 0x04, 0x10, 0xFE, 0xA9, 0x00, 0xDF, 0xFF, 0xFB, 0xFF, 0x03, + 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x69, 0xFF, 0x90, 0x01, 0x96, 0xFC, + 0xDD, 0x06, 0x85, 0xF1, 0xD0, 0x32, 0xA6, 0x2D, 0x94, 0xF1, 0x20, + 0x07, 0x54, 0xFC, 0xBF, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, + 0x07, 0x00, 0xE6, 0xFF, 0x15, 0x00, 0x3C, 0x00, 0xCF, 0xFE, 0x83, + 0x03, 0x20, 0xF6, 0xB2, 0x42, 0x2B, 0x18, 0x71, 0xF5, 0x09, 0x06, + 0x88, 0xFC, 0xCF, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, + 0x00, 0x89, 0xFF, 0x06, 0x01, 0x4C, 0xFE, 0x60, 0x02, 0x1F, 0xFD, + 0xE2, 0x02, 0xF3, 0x48, 0x7D, 0x04, 0x6E, 0xFC, 0xBD, 0x02, 0x1C, + 0xFE, 0x1C, 0x01, 0x80, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x33, 0x00, 0x3F, 0xFF, 0xC5, 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, + 0xF6, 0x22, 0x16, 0xC3, 0x43, 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, + 0x11, 0x00, 0x2B, 0x00, 0xDE, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, + 0x00, 0x49, 0xFF, 0xCB, 0x01, 0x45, 0xFC, 0x29, 0x07, 0xB6, 0xF1, + 0xAD, 0x2B, 0xA2, 0x34, 0x9E, 0xF1, 0xB4, 0x06, 0xB8, 0xFC, 0x7A, + 0x01, 0x75, 0xFF, 0x22, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x02, 0x00, + 0xCC, 0xFF, 0xCE, 0x00, 0xD1, 0xFD, 0x1D, 0x05, 0x91, 0xF3, 0xFE, + 0x3D, 0xD7, 0x1F, 0x87, 0xF3, 0xC3, 0x06, 0x42, 0xFC, 0xE5, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAB, 0xFF, 0xAF, + 0x00, 0x01, 0xFF, 0x0A, 0x01, 0x9E, 0xFF, 0x7C, 0xFD, 0x03, 0x48, + 0xED, 0x0A, 0xD5, 0xF9, 0x0A, 0x04, 0x74, 0xFD, 0x6A, 0x01, 0x62, + 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, + 0x90, 0x01, 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, 0xE1, + 0x46, 0xE1, 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, 0x00, + 0xBE, 0xFF, 0x10, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, + 0x01, 0x33, 0xFC, 0x04, 0x07, 0xB1, 0xF2, 0x21, 0x24, 0xE6, 0x3A, + 0x97, 0xF2, 0xD0, 0x05, 0x5B, 0xFD, 0x15, 0x01, 0xA9, 0xFF, 0x0F, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x00, 0x90, 0xFF, 0x45, 0x01, + 0x0B, 0xFD, 0x44, 0x06, 0x0A, 0xF2, 0x3B, 0x38, 0x80, 0x27, 0x2B, + 0xF2, 0x22, 0x07, 0x33, 0xFC, 0xDE, 0x01, 0x3E, 0xFF, 0x34, 0x00, + 0xFD, 0xFF, 0x0D, 0x00, 0xCD, 0xFF, 0x59, 0x00, 0xB3, 0xFF, 0xC4, + 0xFF, 0xE2, 0x01, 0x09, 0xF9, 0xAA, 0x45, 0xFE, 0x11, 0x54, 0xF7, + 0x38, 0x05, 0xE4, 0xFC, 0xAA, 0x01, 0x49, 0xFF, 0x30, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, 0x01, 0xBC, 0xFD, + 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, 0xC3, 0xFF, 0x89, + 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, 0xFF, 0x18, 0x00, + 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5B, 0xFC, 0x7B, + 0x06, 0x50, 0xF4, 0x6E, 0x1C, 0x36, 0x40, 0x92, 0xF4, 0x75, 0x04, + 0x3B, 0xFE, 0x91, 0x00, 0xEB, 0xFF, 0xF6, 0xFF, 0x04, 0x00, 0xFD, + 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9D, 0x01, 0x84, 0xFC, 0xF3, 0x06, + 0x7D, 0xF1, 0x9E, 0x31, 0xE6, 0x2E, 0x85, 0xF1, 0x16, 0x07, 0x61, + 0xFC, 0xB5, 0x01, 0x55, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x06, 0x00, + 0xEC, 0xFF, 0x08, 0x00, 0x57, 0x00, 0x9F, 0xFE, 0xD1, 0x03, 0x9B, + 0xF5, 0xF7, 0x41, 0x7C, 0x19, 0x13, 0xF5, 0x2F, 0x06, 0x78, 0xFC, + 0xD5, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8F, + 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, 0x02, 0x91, 0xFD, 0xE3, 0x01, + 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, 0xF8, 0x02, 0xFE, 0xFD, 0x2B, + 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, + 0x42, 0xFF, 0xBD, 0x01, 0xB6, 0xFC, 0x9F, 0x05, 0x6C, 0xF6, 0xD6, + 0x14, 0x65, 0x44, 0x98, 0xF7, 0xAC, 0x02, 0x4E, 0xFF, 0xF4, 0xFF, + 0x39, 0x00, 0xD9, 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, + 0xFF, 0xD2, 0x01, 0x3D, 0xFC, 0x2B, 0x07, 0xD4, 0xF1, 0x64, 0x2A, + 0xC6, 0x35, 0xB7, 0xF1, 0x96, 0x06, 0xCF, 0xFC, 0x6B, 0x01, 0x7D, + 0xFF, 0x1F, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x06, 0x00, 0xC1, 0xFF, + 0xE5, 0x00, 0xAA, 0xFD, 0x58, 0x05, 0x3A, 0xF3, 0x11, 0x3D, 0x2C, + 0x21, 0x3F, 0xF3, 0xDA, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, + 0xFF, 0xD0, 0x00, 0x07, 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, + 0x63, 0xF9, 0x42, 0x04, 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x85, 0x01, + 0x39, 0xFD, 0x84, 0x04, 0xD9, 0xF8, 0x95, 0x0D, 0x48, 0x47, 0xA7, + 0xFB, 0x86, 0x00, 0x8A, 0x00, 0x46, 0xFF, 0x8E, 0x00, 0xB8, 0xFF, + 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x35, + 0xFC, 0xF3, 0x06, 0xEE, 0xF2, 0xCD, 0x22, 0xE4, 0x3B, 0xDC, 0xF2, + 0x9C, 0x05, 0x7E, 0xFD, 0x00, 0x01, 0xB4, 0xFF, 0x0B, 0x00, 0x01, + 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x87, 0xFF, 0x57, 0x01, 0xEF, 0xFC, + 0x6B, 0x06, 0xE0, 0xF1, 0x23, 0x37, 0xCE, 0x28, 0x01, 0xF2, 0x28, + 0x07, 0x36, 0xFC, 0xD9, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x0B, 0x00, 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, + 0x02, 0x5E, 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, + 0xCF, 0xFC, 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x23, 0x00, 0x74, 0xFF, 0x3C, 0x01, 0xDA, 0xFD, 0x40, 0x03, + 0x6E, 0xFB, 0xE1, 0x06, 0xC3, 0x48, 0xB3, 0x00, 0x1A, 0xFE, 0xDC, + 0x01, 0x91, 0xFE, 0xE5, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, + 0x36, 0x00, 0x38, 0xFF, 0xDB, 0x01, 0x67, 0xFC, 0x5A, 0x06, 0xA6, + 0xF4, 0x1B, 0x1B, 0x07, 0x41, 0x04, 0xF5, 0x2D, 0x04, 0x67, 0xFE, + 0x77, 0x00, 0xF8, 0xFF, 0xF2, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, + 0x00, 0x5C, 0xFF, 0xA8, 0x01, 0x73, 0xFC, 0x05, 0x07, 0x7D, 0xF1, + 0x67, 0x30, 0x21, 0x30, 0x7E, 0xF1, 0x08, 0x07, 0x6F, 0xFC, 0xAB, + 0x01, 0x5B, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF0, 0xFF, + 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, 0xF5, 0x32, + 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, 0xDA, 0x01, + 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE8, + 0x00, 0x8A, 0xFE, 0xE9, 0x01, 0x01, 0xFE, 0xEA, 0x00, 0xCB, 0x48, + 0xA2, 0x06, 0x87, 0xFB, 0x33, 0x03, 0xE0, 0xFD, 0x39, 0x01, 0x75, + 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x45, 0xFF, + 0xB5, 0x01, 0xCA, 0xFC, 0x72, 0x05, 0xD3, 0xF6, 0x8D, 0x13, 0xFD, + 0x44, 0x39, 0xF8, 0x53, 0x02, 0x82, 0xFF, 0xD7, 0xFF, 0x47, 0x00, + 0xD3, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x42, 0xFF, 0xD8, + 0x01, 0x37, 0xFC, 0x29, 0x07, 0xF8, 0xF1, 0x19, 0x29, 0xE5, 0x36, + 0xD8, 0xF1, 0x73, 0x06, 0xE9, 0xFC, 0x5B, 0x01, 0x85, 0xFF, 0x1C, + 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, 0x00, 0xB6, 0xFF, 0xFB, 0x00, + 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, 0x1C, 0x3C, 0x81, 0x22, 0xFC, + 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x12, 0x00, 0xB7, 0xFF, 0x91, 0x00, 0x40, 0xFF, 0x96, + 0x00, 0x6F, 0x00, 0xD5, 0xFB, 0x5E, 0x47, 0x50, 0x0D, 0xF2, 0xF8, + 0x78, 0x04, 0x3F, 0xFD, 0x82, 0x01, 0x58, 0xFF, 0x2B, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5C, 0xFF, 0x79, 0x01, 0x53, 0xFD, + 0x4E, 0x04, 0x4A, 0xF9, 0x60, 0x0C, 0xA3, 0x47, 0x76, 0xFC, 0x1F, + 0x00, 0xC3, 0x00, 0x27, 0xFF, 0x9D, 0x00, 0xB2, 0xFF, 0x13, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x3A, 0xFC, 0xDF, + 0x06, 0x30, 0xF3, 0x78, 0x21, 0xDB, 0x3C, 0x28, 0xF3, 0x65, 0x05, + 0xA2, 0xFD, 0xEA, 0x00, 0xBE, 0xFF, 0x07, 0x00, 0x01, 0x00, 0xFE, + 0xFF, 0x1E, 0x00, 0x7F, 0xFF, 0x67, 0x01, 0xD5, 0xFC, 0x8E, 0x06, + 0xBE, 0xF1, 0x06, 0x36, 0x1A, 0x2A, 0xDC, 0xF1, 0x2A, 0x07, 0x3C, + 0xFC, 0xD3, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, + 0xD8, 0xFF, 0x3C, 0x00, 0xEE, 0xFF, 0x5A, 0xFF, 0x98, 0x02, 0xBB, + 0xF7, 0x87, 0x44, 0x8C, 0x14, 0x83, 0xF6, 0x95, 0x05, 0xBA, 0xFC, + 0xBB, 0x01, 0x43, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, + 0x00, 0x79, 0xFF, 0x2E, 0x01, 0xF7, 0xFD, 0x05, 0x03, 0xE1, 0xFB, + 0xCA, 0x05, 0xDF, 0x48, 0xAB, 0x01, 0xAA, 0xFD, 0x18, 0x02, 0x72, + 0xFE, 0xF4, 0x00, 0x90, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x39, 0xFF, 0xD6, 0x01, 0x75, 0xFC, 0x37, 0x06, 0xFF, 0xF4, 0xC7, + 0x19, 0xCC, 0x41, 0x7F, 0xF5, 0xE2, 0x03, 0x95, 0xFE, 0x5D, 0x00, + 0x05, 0x00, 0xED, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x57, + 0xFF, 0xB3, 0x01, 0x64, 0xFC, 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, + 0x5A, 0x31, 0x7D, 0xF1, 0xF7, 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, + 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF5, 0xFF, 0xEE, 0xFF, + 0x8B, 0x00, 0x44, 0xFE, 0x65, 0x04, 0xAA, 0xF4, 0x66, 0x40, 0x23, + 0x1C, 0x63, 0xF4, 0x74, 0x06, 0x5D, 0xFC, 0xDE, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x9A, 0xFF, 0xD9, 0x00, 0xAA, + 0xFE, 0xAE, 0x01, 0x70, 0xFE, 0xF8, 0xFF, 0xA6, 0x48, 0xBE, 0x07, + 0x14, 0xFB, 0x6D, 0x03, 0xC3, 0xFD, 0x46, 0x01, 0x70, 0xFF, 0x24, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAC, 0x01, + 0xDF, 0xFC, 0x43, 0x05, 0x3C, 0xF7, 0x46, 0x12, 0x8D, 0x45, 0xE2, + 0xF8, 0xF7, 0x01, 0xB8, 0xFF, 0xB9, 0xFF, 0x56, 0x00, 0xCE, 0xFF, + 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDD, 0x01, 0x34, + 0xFC, 0x23, 0x07, 0x21, 0xF2, 0xCB, 0x27, 0xFE, 0x37, 0x00, 0xF2, + 0x4D, 0x06, 0x04, 0xFD, 0x49, 0x01, 0x8E, 0xFF, 0x19, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAB, 0xFF, 0x10, 0x01, 0x62, 0xFD, + 0xC5, 0x05, 0xA5, 0xF2, 0x1F, 0x3B, 0xD6, 0x23, 0xBE, 0xF2, 0x01, + 0x07, 0x33, 0xFC, 0xE5, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x10, 0x00, 0xBD, 0xFF, 0x82, 0x00, 0x5E, 0xFF, 0x5D, 0x00, 0xD4, + 0x00, 0x0C, 0xFB, 0xF9, 0x46, 0x87, 0x0E, 0x82, 0xF8, 0xAD, 0x04, + 0x26, 0xFD, 0x8D, 0x01, 0x54, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6D, 0x01, 0x6E, 0xFD, 0x17, 0x04, + 0xBC, 0xF9, 0x30, 0x0B, 0xF4, 0x47, 0x4B, 0xFD, 0xB5, 0xFF, 0xFD, + 0x00, 0x08, 0xFF, 0xAC, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, + 0xF3, 0x22, 0x20, 0xCA, 0x3D, 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, + 0xD4, 0x00, 0xCA, 0xFF, 0x03, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x21, + 0x00, 0x77, 0xFF, 0x77, 0x01, 0xBD, 0xFC, 0xAE, 0x06, 0xA3, 0xF1, + 0xE3, 0x34, 0x64, 0x2B, 0xBC, 0xF1, 0x2A, 0x07, 0x43, 0xFC, 0xCD, + 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDD, 0xFF, + 0x2E, 0x00, 0x0A, 0x00, 0x27, 0xFF, 0xEF, 0x02, 0x20, 0xF7, 0xE7, + 0x43, 0xD8, 0x15, 0x1E, 0xF6, 0xC0, 0x05, 0xA7, 0xFC, 0xC3, 0x01, + 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, + 0xFF, 0x20, 0x01, 0x16, 0xFE, 0xCA, 0x02, 0x54, 0xFC, 0xB9, 0x04, + 0xF2, 0x48, 0xA9, 0x02, 0x39, 0xFD, 0x53, 0x02, 0x53, 0xFE, 0x03, + 0x01, 0x8A, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, + 0xD1, 0x01, 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, 0x89, + 0x42, 0x02, 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, 0x00, + 0xE8, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBD, + 0x01, 0x57, 0xFC, 0x1E, 0x07, 0x90, 0xF1, 0xED, 0x2D, 0x8C, 0x32, + 0x83, 0xF1, 0xE2, 0x06, 0x92, 0xFC, 0x93, 0x01, 0x68, 0xFF, 0x26, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFA, 0xFF, 0xE2, 0xFF, 0xA4, 0x00, + 0x19, 0xFE, 0xAA, 0x04, 0x3E, 0xF4, 0x90, 0x3F, 0x78, 0x1D, 0x10, + 0xF4, 0x93, 0x06, 0x52, 0xFC, 0xE1, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x17, 0x00, 0xA0, 0xFF, 0xCA, 0x00, 0xC9, 0xFE, 0x73, + 0x01, 0xDE, 0xFE, 0x0C, 0xFF, 0x76, 0x48, 0xDE, 0x08, 0xA1, 0xFA, + 0xA6, 0x03, 0xA6, 0xFD, 0x53, 0x01, 0x6A, 0xFF, 0x26, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, 0x01, 0xF6, 0xFC, + 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, 0x93, 0xF9, 0x98, + 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, 0xFF, 0x0E, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x32, 0xFC, 0x1B, + 0x07, 0x50, 0xF2, 0x7B, 0x26, 0x11, 0x39, 0x2F, 0xF2, 0x23, 0x06, + 0x22, 0xFD, 0x37, 0x01, 0x97, 0xFF, 0x15, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x12, 0x00, 0xA1, 0xFF, 0x24, 0x01, 0x41, 0xFD, 0xF6, 0x05, + 0x67, 0xF2, 0x1A, 0x3A, 0x29, 0x25, 0x84, 0xF2, 0x0F, 0x07, 0x31, + 0xFC, 0xE3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0F, 0x00, + 0xC2, 0xFF, 0x73, 0x00, 0x7D, 0xFF, 0x25, 0x00, 0x38, 0x01, 0x4C, + 0xFA, 0x89, 0x46, 0xC3, 0x0F, 0x14, 0xF8, 0xE0, 0x04, 0x0D, 0xFD, + 0x98, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, + 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, 0xFD, 0xDF, 0x03, 0x2E, 0xFA, + 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, 0x4B, 0xFF, 0x38, 0x01, 0xE9, + 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE4, 0x01, 0x49, 0xFC, 0xAF, 0x06, 0xC1, 0xF3, 0xCD, + 0x1E, 0xB1, 0x3E, 0xD9, 0xF3, 0xEC, 0x04, 0xF0, 0xFD, 0xBC, 0x00, + 0xD5, 0xFF, 0xFE, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6F, + 0xFF, 0x85, 0x01, 0xA6, 0xFC, 0xCA, 0x06, 0x8F, 0xF1, 0xBB, 0x33, + 0xAB, 0x2C, 0xA3, 0xF1, 0x26, 0x07, 0x4C, 0xFC, 0xC5, 0x01, 0x4D, + 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE2, 0xFF, 0x20, 0x00, + 0x26, 0x00, 0xF5, 0xFE, 0x43, 0x03, 0x8D, 0xF6, 0x3C, 0x43, 0x25, + 0x17, 0xBB, 0xF5, 0xEA, 0x05, 0x95, 0xFC, 0xCA, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, + 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, + 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, + 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, + 0x3D, 0xFC, 0xD6, 0x06, 0x4C, 0xF3, 0xED, 0x20, 0x3D, 0x3D, 0x4A, + 0xF3, 0x4E, 0x05, 0xB1, 0xFD, 0xE1, 0x00, 0xC3, 0xFF, 0x05, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, + 0xFD, 0x4E, 0x05, 0x4A, 0xF3, 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, + 0xD6, 0x06, 0x3D, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, + 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, + 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, + 0xFD, 0xFF, 0x30, 0x00, 0x4D, 0xFF, 0xC5, 0x01, 0x4C, 0xFC, 0x26, + 0x07, 0xA3, 0xF1, 0xAB, 0x2C, 0xBB, 0x33, 0x8F, 0xF1, 0xCA, 0x06, + 0xA6, 0xFC, 0x85, 0x01, 0x6F, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x16, + 0x00, 0xA6, 0xFF, 0xBB, 0x00, 0xE9, 0xFE, 0x38, 0x01, 0x4B, 0xFF, + 0x28, 0xFE, 0x3A, 0x48, 0x04, 0x0A, 0x2E, 0xFA, 0xDF, 0x03, 0x8A, + 0xFD, 0x60, 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x0F, 0x07, 0x84, + 0xF2, 0x29, 0x25, 0x1A, 0x3A, 0x67, 0xF2, 0xF6, 0x05, 0x41, 0xFD, + 0x24, 0x01, 0xA1, 0xFF, 0x12, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xC8, + 0xFF, 0x64, 0x00, 0x9B, 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, 0xF9, + 0x10, 0x46, 0x03, 0x11, 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, 0xA2, + 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE1, 0x01, 0x52, 0xFC, 0x93, 0x06, 0x10, 0xF4, 0x78, + 0x1D, 0x90, 0x3F, 0x3E, 0xF4, 0xAA, 0x04, 0x19, 0xFE, 0xA4, 0x00, + 0xE2, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0x07, 0x00, 0xE8, 0xFF, 0x12, + 0x00, 0x42, 0x00, 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, + 0x76, 0x18, 0x5C, 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, + 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, + 0xC3, 0x01, 0xA7, 0xFC, 0xC0, 0x05, 0x1E, 0xF6, 0xD8, 0x15, 0xE7, + 0x43, 0x20, 0xF7, 0xEF, 0x02, 0x27, 0xFF, 0x0A, 0x00, 0x2E, 0x00, + 0xDD, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, + 0x00, 0xC8, 0xFD, 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, + 0x76, 0xF3, 0xC8, 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x54, 0xFF, 0x8D, 0x01, + 0x26, 0xFD, 0xAD, 0x04, 0x82, 0xF8, 0x87, 0x0E, 0xF9, 0x46, 0x0C, + 0xFB, 0xD4, 0x00, 0x5D, 0x00, 0x5E, 0xFF, 0x82, 0x00, 0xBD, 0xFF, + 0x10, 0x00, 0xFF, 0xFF, 0x19, 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, + 0xFD, 0x4D, 0x06, 0x00, 0xF2, 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, + 0x23, 0x07, 0x34, 0xFC, 0xDD, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, 0xFF, 0x46, 0x01, 0xC3, 0xFD, + 0x6D, 0x03, 0x14, 0xFB, 0xBE, 0x07, 0xA6, 0x48, 0xF8, 0xFF, 0x70, + 0xFE, 0xAE, 0x01, 0xAA, 0xFE, 0xD9, 0x00, 0x9A, 0xFF, 0x19, 0x00, + 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, 0x01, 0x80, 0xFC, 0xF7, + 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, 0x83, 0xF1, 0x13, 0x07, + 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x1B, + 0x00, 0x90, 0xFF, 0xF4, 0x00, 0x72, 0xFE, 0x18, 0x02, 0xAA, 0xFD, + 0xAB, 0x01, 0xDF, 0x48, 0xCA, 0x05, 0xE1, 0xFB, 0x05, 0x03, 0xF7, + 0xFD, 0x2E, 0x01, 0x79, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, 0x07, 0xDC, + 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, 0xD5, 0xFC, + 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x13, 0x00, 0xB2, + 0xFF, 0x9D, 0x00, 0x27, 0xFF, 0xC3, 0x00, 0x1F, 0x00, 0x76, 0xFC, + 0xA3, 0x47, 0x60, 0x0C, 0x4A, 0xF9, 0x4E, 0x04, 0x53, 0xFD, 0x79, + 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, 0xF2, 0x81, + 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, 0xFB, 0x00, + 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x0B, 0x00, 0xD3, 0xFF, 0x47, + 0x00, 0xD7, 0xFF, 0x82, 0xFF, 0x53, 0x02, 0x39, 0xF8, 0xFD, 0x44, + 0x8D, 0x13, 0xD3, 0xF6, 0x72, 0x05, 0xCA, 0xFC, 0xB5, 0x01, 0x45, + 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x38, 0xFF, + 0xDA, 0x01, 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, 0xCE, 0x1A, 0x32, + 0x41, 0x1F, 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, 0x00, 0xFB, 0xFF, + 0xF0, 0xFF, 0x05, 0x00, 0x05, 0x00, 0xF2, 0xFF, 0xF8, 0xFF, 0x77, + 0x00, 0x67, 0xFE, 0x2D, 0x04, 0x04, 0xF5, 0x07, 0x41, 0x1B, 0x1B, + 0xA6, 0xF4, 0x5A, 0x06, 0x67, 0xFC, 0xDB, 0x01, 0x38, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB3, 0x01, + 0xCF, 0xFC, 0x67, 0x05, 0xEA, 0xF6, 0x44, 0x13, 0x1E, 0x45, 0x5E, + 0xF8, 0x3F, 0x02, 0x8E, 0xFF, 0xD0, 0xFF, 0x4A, 0x00, 0xD2, 0xFF, + 0x0B, 0x00, 0x01, 0x00, 0x0B, 0x00, 0xB4, 0xFF, 0x00, 0x01, 0x7E, + 0xFD, 0x9C, 0x05, 0xDC, 0xF2, 0xE4, 0x3B, 0xCD, 0x22, 0xEE, 0xF2, + 0xF3, 0x06, 0x35, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x2A, 0x00, 0x5D, 0xFF, 0x76, 0x01, 0x59, 0xFD, + 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, 0xB6, 0x47, 0xA4, 0xFC, 0x07, + 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, 0x00, 0xB1, 0xFF, 0x13, 0x00, + 0xFE, 0xFF, 0x1F, 0x00, 0x7D, 0xFF, 0x6B, 0x01, 0xCF, 0xFC, 0x96, + 0x06, 0xB7, 0xF1, 0xC6, 0x35, 0x64, 0x2A, 0xD4, 0xF1, 0x2B, 0x07, + 0x3D, 0xFC, 0xD2, 0x01, 0x45, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x21, 0x00, 0x7A, 0xFF, 0x2B, 0x01, 0xFE, 0xFD, 0xF8, 0x02, + 0xFB, 0xFB, 0x8D, 0x05, 0xE5, 0x48, 0xE3, 0x01, 0x91, 0xFD, 0x25, + 0x02, 0x6B, 0xFE, 0xF7, 0x00, 0x8F, 0xFF, 0x1C, 0x00, 0xFD, 0xFF, + 0x2D, 0x00, 0x55, 0xFF, 0xB5, 0x01, 0x61, 0xFC, 0x16, 0x07, 0x85, + 0xF1, 0xE6, 0x2E, 0x9E, 0x31, 0x7D, 0xF1, 0xF3, 0x06, 0x84, 0xFC, + 0x9D, 0x01, 0x63, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x18, 0x00, 0x9C, + 0xFF, 0xD6, 0x00, 0xB1, 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, 0xFF, + 0x9C, 0x48, 0xFD, 0x07, 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, 0x49, + 0x01, 0x6E, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x3E, 0xFF, 0xDE, 0x01, 0x33, 0xFC, 0x22, 0x07, 0x2B, 0xF2, 0x80, + 0x27, 0x3B, 0x38, 0x0A, 0xF2, 0x44, 0x06, 0x0B, 0xFD, 0x45, 0x01, + 0x90, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x10, 0x00, 0xBE, 0xFF, 0x7F, + 0x00, 0x65, 0xFF, 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, + 0xCD, 0x0E, 0x6A, 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, + 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE5, 0x01, 0x42, 0xFC, 0xC3, 0x06, 0x87, 0xF3, 0xD7, 0x1F, 0xFE, + 0x3D, 0x91, 0xF3, 0x1D, 0x05, 0xD1, 0xFD, 0xCE, 0x00, 0xCC, 0xFF, + 0x02, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, + 0x00, 0x1B, 0xFF, 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, + 0x07, 0xF6, 0xCA, 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCF, 0x01, + 0x88, 0xFC, 0x09, 0x06, 0x71, 0xF5, 0x2B, 0x18, 0xB2, 0x42, 0x20, + 0xF6, 0x83, 0x03, 0xCF, 0xFE, 0x3C, 0x00, 0x15, 0x00, 0xE6, 0xFF, + 0x07, 0x00, 0x03, 0x00, 0xFB, 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, + 0xFE, 0xB9, 0x04, 0x27, 0xF4, 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, + 0x99, 0x06, 0x50, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4D, 0xFF, 0xA0, 0x01, 0xFB, 0xFC, + 0x07, 0x05, 0xBF, 0xF7, 0xBB, 0x10, 0x2B, 0x46, 0xBB, 0xF9, 0x83, + 0x01, 0xFA, 0xFF, 0x95, 0xFF, 0x68, 0x00, 0xC7, 0xFF, 0x0E, 0x00, + 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, 0x01, 0x3A, 0xFD, 0x00, + 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, 0x79, 0xF2, 0x12, 0x07, + 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x27, 0x00, 0x66, 0xFF, 0x5E, 0x01, 0x90, 0xFD, 0xD2, 0x03, + 0x47, 0xFA, 0xC3, 0x09, 0x48, 0x48, 0x5A, 0xFE, 0x33, 0xFF, 0x45, + 0x01, 0xE2, 0xFE, 0xBE, 0x00, 0xA5, 0xFF, 0x16, 0x00, 0xFD, 0xFF, + 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, 0x06, 0x8C, + 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, 0x4E, 0xFC, + 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x1E, 0x00, 0x86, + 0xFF, 0x0E, 0x01, 0x3B, 0xFE, 0x82, 0x02, 0xE0, 0xFC, 0x73, 0x03, + 0xF6, 0x48, 0xE9, 0x03, 0xAD, 0xFC, 0x9C, 0x02, 0x2D, 0xFE, 0x14, + 0x01, 0x83, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x30, 0x00, + 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, 0xF1, 0x62, + 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, 0x82, 0x01, + 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x15, 0x00, 0xA8, 0xFF, 0xB8, + 0x00, 0xF0, 0xFE, 0x2B, 0x01, 0x63, 0xFF, 0xF6, 0xFD, 0x2C, 0x48, + 0x47, 0x0A, 0x14, 0xFA, 0xEB, 0x03, 0x84, 0xFD, 0x63, 0x01, 0x64, + 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, 0xFF, + 0xE4, 0x01, 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, 0xDD, 0x24, 0x54, + 0x3A, 0x74, 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, 0x01, 0xA3, 0xFF, + 0x11, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xC9, 0xFF, 0x61, 0x00, 0xA2, + 0xFF, 0xE2, 0xFF, 0xAE, 0x01, 0x6B, 0xF9, 0xF2, 0x45, 0x4A, 0x11, + 0x8F, 0xF7, 0x1D, 0x05, 0xF1, 0xFC, 0xA4, 0x01, 0x4B, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE1, 0x01, + 0x55, 0xFC, 0x8C, 0x06, 0x22, 0xF4, 0x2C, 0x1D, 0xC0, 0x3F, 0x55, + 0xF4, 0x9B, 0x04, 0x23, 0xFE, 0x9F, 0x00, 0xE4, 0xFF, 0xF9, 0xFF, + 0x04, 0x00, 0x07, 0x00, 0xE9, 0xFF, 0x0F, 0x00, 0x48, 0x00, 0xB9, + 0xFE, 0xA6, 0x03, 0xE4, 0xF5, 0x60, 0x42, 0xC1, 0x18, 0x47, 0xF5, + 0x1A, 0x06, 0x81, 0xFC, 0xD2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, 0xC1, 0x01, 0xAB, 0xFC, + 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, 0x0B, 0x44, 0x42, 0xF7, 0xDC, + 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, 0x00, 0xDC, 0xFF, 0x09, 0x00, + 0x02, 0x00, 0x04, 0x00, 0xC7, 0xFF, 0xD9, 0x00, 0xBF, 0xFD, 0x38, + 0x05, 0x69, 0xF3, 0x96, 0x3D, 0x6F, 0x20, 0x66, 0xF3, 0xCE, 0x06, + 0x3F, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, + 0xFF, 0x2C, 0x00, 0x55, 0xFF, 0x8B, 0x01, 0x2B, 0xFD, 0xA1, 0x04, + 0x9B, 0xF8, 0x42, 0x0E, 0x0F, 0x47, 0x38, 0xFB, 0xBE, 0x00, 0x6A, + 0x00, 0x58, 0xFF, 0x85, 0x00, 0xBB, 0xFF, 0x10, 0x00, 0xFF, 0xFF, + 0x19, 0x00, 0x8C, 0xFF, 0x4D, 0x01, 0xFE, 0xFC, 0x56, 0x06, 0xF7, + 0xF1, 0xBF, 0x37, 0x15, 0x28, 0x18, 0xF2, 0x25, 0x07, 0x34, 0xFC, + 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x24, + 0x00, 0x71, 0xFF, 0x43, 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, 0xFB, + 0x7E, 0x07, 0xAF, 0x48, 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, 0xA3, + 0xFE, 0xDD, 0x00, 0x99, 0xFF, 0x19, 0x00, 0xFD, 0xFF, 0x29, 0x00, + 0x60, 0xFF, 0xA2, 0x01, 0x7C, 0xFC, 0xFB, 0x06, 0x7C, 0xF1, 0x15, + 0x31, 0x73, 0x2F, 0x81, 0xF1, 0x10, 0x07, 0x67, 0xFC, 0xB1, 0x01, + 0x58, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x1B, 0x00, 0x91, 0xFF, 0xF1, + 0x00, 0x79, 0xFE, 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, + 0x07, 0x06, 0xC7, 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, + 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, + 0xD5, 0x01, 0x3A, 0xFC, 0x2A, 0x07, 0xE3, 0xF1, 0xD1, 0x29, 0x46, + 0x36, 0xC5, 0xF1, 0x87, 0x06, 0xDA, 0xFC, 0x64, 0x01, 0x80, 0xFF, + 0x1E, 0x00, 0xFE, 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, + 0xFF, 0xB6, 0x00, 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, + 0x31, 0xF9, 0x5A, 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, + 0x37, 0xFC, 0xEB, 0x06, 0x0B, 0xF3, 0x35, 0x22, 0x52, 0x3C, 0xFD, + 0xF2, 0x84, 0x05, 0x8D, 0xFD, 0xF6, 0x00, 0xB8, 0xFF, 0x09, 0x00, + 0x01, 0x00, 0x0B, 0x00, 0xD5, 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, + 0xFF, 0x67, 0x02, 0x14, 0xF8, 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, + 0x7C, 0x05, 0xC5, 0xFC, 0xB7, 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, + 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD9, 0x01, 0x6D, 0xFC, + 0x4B, 0x06, 0xCD, 0xF4, 0x83, 0x1A, 0x5F, 0x41, 0x3A, 0xF5, 0x0C, + 0x04, 0x7B, 0xFE, 0x6C, 0x00, 0xFE, 0xFF, 0xEF, 0xFF, 0x05, 0x00, + 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, 0x00, 0x5D, 0xFE, 0x3E, + 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, 0x93, 0xF4, 0x62, 0x06, + 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, + 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB1, 0x01, 0xD3, 0xFC, 0x5D, 0x05, + 0x01, 0xF7, 0xFB, 0x12, 0x3F, 0x45, 0x83, 0xF8, 0x2A, 0x02, 0x9A, + 0xFF, 0xCA, 0xFF, 0x4E, 0x00, 0xD1, 0xFF, 0x0C, 0x00, 0x00, 0x00, + 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, 0x05, 0xCC, + 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, 0x35, 0xFC, + 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x29, + 0x00, 0x5E, 0xFF, 0x74, 0x01, 0x5F, 0xFD, 0x35, 0x04, 0x7C, 0xF9, + 0xD8, 0x0B, 0xC9, 0x47, 0xD4, 0xFC, 0xF0, 0xFF, 0xDD, 0x00, 0x19, + 0xFF, 0xA4, 0x00, 0xAF, 0xFF, 0x13, 0x00, 0xFE, 0xFF, 0x20, 0x00, + 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, 0xF1, 0x86, + 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, 0xD1, 0x01, + 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7C, + 0xFF, 0x27, 0x01, 0x05, 0xFE, 0xEB, 0x02, 0x14, 0xFC, 0x50, 0x05, + 0xEA, 0x48, 0x1B, 0x02, 0x78, 0xFD, 0x32, 0x02, 0x64, 0xFE, 0xFA, + 0x00, 0x8D, 0xFF, 0x1C, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x54, 0xFF, + 0xB7, 0x01, 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, 0x9F, 0x2E, 0xE3, + 0x31, 0x7E, 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, 0x01, 0x64, 0xFF, + 0x28, 0x00, 0xFD, 0xFF, 0x18, 0x00, 0x9D, 0xFF, 0xD3, 0x00, 0xB8, + 0xFE, 0x93, 0x01, 0xA1, 0xFE, 0x8E, 0xFF, 0x92, 0x48, 0x3D, 0x08, + 0xE1, 0xFA, 0x86, 0x03, 0xB6, 0xFD, 0x4C, 0x01, 0x6D, 0xFF, 0x25, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDF, 0x01, + 0x33, 0xFC, 0x20, 0x07, 0x35, 0xF2, 0x36, 0x27, 0x78, 0x38, 0x14, + 0xF2, 0x3B, 0x06, 0x11, 0xFD, 0x41, 0x01, 0x92, 0xFF, 0x17, 0x00, + 0xFF, 0xFF, 0x10, 0x00, 0xBF, 0xFF, 0x7B, 0x00, 0x6C, 0xFF, 0x44, + 0x00, 0x01, 0x01, 0xB6, 0xFA, 0xC8, 0x46, 0x13, 0x0F, 0x51, 0xF8, + 0xC4, 0x04, 0x1B, 0xFD, 0x92, 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, + 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x44, 0xFC, + 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, 0x31, 0x3E, 0xA5, 0xF3, 0x0F, + 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, 0xFF, 0x01, 0x00, 0x02, 0x00, + 0x09, 0x00, 0xDF, 0xFF, 0x28, 0x00, 0x17, 0x00, 0x10, 0xFF, 0x15, + 0x03, 0xDD, 0xF6, 0x9E, 0x43, 0x6C, 0x16, 0xF1, 0xF5, 0xD3, 0x05, + 0x9F, 0xFC, 0xC6, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCE, 0x01, 0x8C, 0xFC, 0x00, 0x06, + 0x86, 0xF5, 0xE0, 0x17, 0xDB, 0x42, 0x3F, 0xF6, 0x71, 0x03, 0xD9, + 0xFE, 0x36, 0x00, 0x18, 0x00, 0xE5, 0xFF, 0x07, 0x00, 0x03, 0x00, + 0xFC, 0xFF, 0xDC, 0xFF, 0xAF, 0x00, 0x07, 0xFE, 0xC8, 0x04, 0x10, + 0xF4, 0x2D, 0x3F, 0x0F, 0x1E, 0xED, 0xF3, 0xA0, 0x06, 0x4E, 0xFC, + 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, + 0x00, 0x4E, 0xFF, 0x9E, 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, 0xF7, + 0x75, 0x10, 0x48, 0x46, 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, 0x8E, + 0xFF, 0x6B, 0x00, 0xC6, 0xFF, 0x0E, 0x00, 0xFF, 0xFF, 0x13, 0x00, + 0x9D, 0xFF, 0x2D, 0x01, 0x33, 0xFD, 0x0B, 0x06, 0x4D, 0xF2, 0xA5, + 0x39, 0xBF, 0x25, 0x6D, 0xF2, 0x15, 0x07, 0x31, 0xFC, 0xE2, 0x01, + 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x68, + 0xFF, 0x5B, 0x01, 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, + 0x57, 0x48, 0x8D, 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, + 0x00, 0xA4, 0xFF, 0x16, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6C, 0xFF, + 0x8B, 0x01, 0x9D, 0xFC, 0xD5, 0x06, 0x89, 0xF1, 0x35, 0x33, 0x3A, + 0x2D, 0x9A, 0xF1, 0x23, 0x07, 0x51, 0xFC, 0xC2, 0x01, 0x4F, 0xFF, + 0x2F, 0x00, 0xFD, 0xFF, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, + 0xFE, 0x74, 0x02, 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, + 0x94, 0xFC, 0xA9, 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC9, 0x01, + 0x48, 0xFC, 0x28, 0x07, 0xAD, 0xF1, 0x19, 0x2C, 0x3F, 0x34, 0x97, + 0xF1, 0xBE, 0x06, 0xB0, 0xFC, 0x7F, 0x01, 0x72, 0xFF, 0x23, 0x00, + 0xFE, 0xFF, 0x15, 0x00, 0xA9, 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, + 0x01, 0x7A, 0xFF, 0xC5, 0xFD, 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, + 0xF8, 0x03, 0x7D, 0xFD, 0x66, 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE4, 0x01, 0x32, 0xFC, + 0x09, 0x07, 0x9D, 0xF2, 0x92, 0x24, 0x8F, 0x3A, 0x82, 0xF2, 0xE1, + 0x05, 0x50, 0xFD, 0x1B, 0x01, 0xA6, 0xFF, 0x10, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, 0xFF, 0xD6, 0xFF, 0xC3, + 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, 0x77, 0xF7, 0x28, 0x05, + 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x57, 0xFC, 0x85, 0x06, + 0x34, 0xF4, 0xE0, 0x1C, 0xF0, 0x3F, 0x6D, 0xF4, 0x8C, 0x04, 0x2C, + 0xFE, 0x99, 0x00, 0xE7, 0xFF, 0xF8, 0xFF, 0x04, 0x00, 0x06, 0x00, + 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, 0x03, 0xC7, + 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, 0x7D, 0xFC, + 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, + 0x00, 0x41, 0xFF, 0xC0, 0x01, 0xAF, 0xFC, 0xAD, 0x05, 0x4A, 0xF6, + 0x44, 0x15, 0x2F, 0x44, 0x64, 0xF7, 0xC9, 0x02, 0x3D, 0xFF, 0xFE, + 0xFF, 0x34, 0x00, 0xDB, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x05, 0x00, + 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, 0xF3, 0x61, + 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, 0xE6, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x56, + 0xFF, 0x88, 0x01, 0x31, 0xFD, 0x95, 0x04, 0xB4, 0xF8, 0xFC, 0x0D, + 0x26, 0x47, 0x64, 0xFB, 0xA7, 0x00, 0x77, 0x00, 0x51, 0xFF, 0x89, + 0x00, 0xBA, 0xFF, 0x11, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8A, 0xFF, + 0x51, 0x01, 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, 0x82, 0x37, 0x60, + 0x28, 0x0E, 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, 0xFF, + 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x72, 0xFF, 0x40, + 0x01, 0xD0, 0xFD, 0x53, 0x03, 0x47, 0xFB, 0x3F, 0x07, 0xB8, 0x48, + 0x62, 0x00, 0x3F, 0xFE, 0xC8, 0x01, 0x9C, 0xFE, 0xE0, 0x00, 0x98, + 0xFF, 0x19, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA5, 0x01, + 0x78, 0xFC, 0xFF, 0x06, 0x7D, 0xF1, 0xCF, 0x30, 0xB8, 0x2F, 0x80, + 0xF1, 0x0D, 0x07, 0x6A, 0xFC, 0xAE, 0x01, 0x59, 0xFF, 0x2B, 0x00, + 0xFD, 0xFF, 0x1B, 0x00, 0x93, 0xFF, 0xED, 0x00, 0x80, 0xFE, 0xFD, + 0x01, 0xDC, 0xFD, 0x3C, 0x01, 0xD5, 0x48, 0x45, 0x06, 0xAE, 0xFB, + 0x1F, 0x03, 0xEA, 0xFD, 0x34, 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x43, 0xFF, 0xD6, 0x01, 0x39, 0xFC, + 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, 0x85, 0x36, 0xCC, 0xF1, 0x7F, + 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, + 0x12, 0x00, 0xB5, 0xFF, 0x96, 0x00, 0x35, 0xFF, 0xA9, 0x00, 0x4D, + 0x00, 0x19, 0xFC, 0x7C, 0x47, 0xE8, 0x0C, 0x18, 0xF9, 0x66, 0x04, + 0x48, 0xFD, 0x7E, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x38, 0xFC, 0xE6, 0x06, + 0x19, 0xF3, 0xEA, 0x21, 0x8A, 0x3C, 0x0E, 0xF3, 0x78, 0x05, 0x96, + 0xFD, 0xF1, 0x00, 0xBB, 0xFF, 0x08, 0x00, 0x01, 0x00, 0x0B, 0x00, + 0xD6, 0xFF, 0x41, 0x00, 0xE4, 0xFF, 0x6B, 0xFF, 0x7B, 0x02, 0xF0, + 0xF7, 0xBA, 0x44, 0x1E, 0x14, 0xA5, 0xF6, 0x86, 0x05, 0xC1, 0xFC, + 0xB9, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, + 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, 0xF4, + 0x38, 0x1A, 0x8C, 0x41, 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, 0x66, + 0x00, 0x01, 0x00, 0xEE, 0xFF, 0x06, 0x00, 0x05, 0x00, 0xF4, 0xFF, + 0xF2, 0xFF, 0x83, 0x00, 0x53, 0xFE, 0x4E, 0x04, 0xD0, 0xF4, 0xAB, + 0x40, 0xB2, 0x1B, 0x7F, 0xF4, 0x69, 0x06, 0x62, 0xFC, 0xDD, 0x01, + 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x47, + 0xFF, 0xAF, 0x01, 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, + 0x5C, 0x45, 0xA9, 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, + 0x00, 0xD0, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xAF, 0xFF, + 0x09, 0x01, 0x6E, 0xFD, 0xB4, 0x05, 0xBC, 0xF2, 0x73, 0x3B, 0x64, + 0x23, 0xD2, 0xF2, 0xFB, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, + 0x01, 0x65, 0xFD, 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, + 0x03, 0xFD, 0xD9, 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, + 0xFF, 0x14, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x79, 0xFF, 0x72, 0x01, + 0xC4, 0xFC, 0xA4, 0x06, 0xAB, 0xF1, 0x46, 0x35, 0xF7, 0x2A, 0xC6, + 0xF1, 0x2A, 0x07, 0x40, 0xFC, 0xCF, 0x01, 0x47, 0xFF, 0x31, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, + 0xFE, 0xDE, 0x02, 0x2E, 0xFC, 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, + 0x5E, 0xFD, 0x3F, 0x02, 0x5D, 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, + 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0xBA, 0x01, 0x5B, 0xFC, + 0x1B, 0x07, 0x8B, 0xF1, 0x58, 0x2E, 0x26, 0x32, 0x80, 0xF1, 0xEA, + 0x06, 0x8C, 0xFC, 0x97, 0x01, 0x66, 0xFF, 0x27, 0x00, 0xFD, 0xFF, + 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, 0xFE, 0x86, 0x01, 0xBA, + 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, 0xC7, 0xFA, 0x93, 0x03, + 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x35, 0x00, 0x3D, 0xFF, 0xDF, 0x01, 0x32, 0xFC, 0x1E, 0x07, + 0x40, 0xF2, 0xEB, 0x26, 0xB5, 0x38, 0x1F, 0xF2, 0x32, 0x06, 0x18, + 0xFD, 0x3D, 0x01, 0x94, 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x0F, 0x00, + 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, 0x01, 0x8B, + 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, 0x15, 0xFD, + 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x46, 0xFC, 0xB8, 0x06, 0xA8, 0xF3, + 0x3F, 0x1F, 0x64, 0x3E, 0xBA, 0xF3, 0x01, 0x05, 0xE2, 0xFD, 0xC4, + 0x00, 0xD2, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x08, 0x00, 0xE1, 0xFF, + 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, 0xF6, 0x77, + 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, 0xC8, 0x01, + 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3D, + 0xFF, 0xCC, 0x01, 0x8F, 0xFC, 0xF8, 0x05, 0x9B, 0xF5, 0x96, 0x17, + 0x02, 0x43, 0x5E, 0xF6, 0x5F, 0x03, 0xE4, 0xFE, 0x30, 0x00, 0x1B, + 0x00, 0xE4, 0xFF, 0x08, 0x00, 0x03, 0x00, 0xFD, 0xFF, 0xD9, 0xFF, + 0xB4, 0x00, 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, 0xFC, 0x3E, 0x5B, + 0x1E, 0xDB, 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9C, + 0x01, 0x05, 0xFD, 0xF1, 0x04, 0xF0, 0xF7, 0x2D, 0x10, 0x61, 0x46, + 0x0D, 0xFA, 0x58, 0x01, 0x13, 0x00, 0x87, 0xFF, 0x6E, 0x00, 0xC4, + 0xFF, 0x0E, 0x00, 0xFF, 0xFF, 0x14, 0x00, 0x9B, 0xFF, 0x31, 0x01, + 0x2C, 0xFD, 0x15, 0x06, 0x41, 0xF2, 0x6A, 0x39, 0x0A, 0x26, 0x61, + 0xF2, 0x17, 0x07, 0x31, 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x69, 0xFF, 0x58, 0x01, 0x9D, + 0xFD, 0xB9, 0x03, 0x7B, 0xFA, 0x40, 0x09, 0x63, 0x48, 0xBF, 0xFE, + 0x03, 0xFF, 0x5F, 0x01, 0xD4, 0xFE, 0xC5, 0x00, 0xA2, 0xFF, 0x16, + 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6A, 0xFF, 0x8E, 0x01, 0x99, 0xFC, + 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, 0x82, 0x2D, 0x96, 0xF1, 0x21, + 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, + 0x1D, 0x00, 0x88, 0xFF, 0x07, 0x01, 0x49, 0xFE, 0x67, 0x02, 0x13, + 0xFD, 0xFF, 0x02, 0xF4, 0x48, 0x5F, 0x04, 0x7A, 0xFC, 0xB6, 0x02, + 0x20, 0xFE, 0x1B, 0x01, 0x81, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xCA, 0x01, 0x46, 0xFC, 0x29, 0x07, + 0xB3, 0xF1, 0xD1, 0x2B, 0x81, 0x34, 0x9C, 0xF1, 0xB8, 0x06, 0xB5, + 0xFC, 0x7C, 0x01, 0x74, 0xFF, 0x22, 0x00, 0xFE, 0xFF, 0x15, 0x00, + 0xAA, 0xFF, 0xB1, 0x00, 0xFE, 0xFE, 0x10, 0x01, 0x92, 0xFF, 0x94, + 0xFD, 0x0D, 0x48, 0xCB, 0x0A, 0xE2, 0xF9, 0x04, 0x04, 0x77, 0xFD, + 0x69, 0x01, 0x62, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, 0xF2, + 0x46, 0x24, 0xC8, 0x3A, 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, 0x17, + 0x01, 0xA8, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xCC, 0xFF, + 0x5A, 0x00, 0xAF, 0xFF, 0xCA, 0xFF, 0xD8, 0x01, 0x1C, 0xF9, 0xB8, + 0x45, 0xDA, 0x11, 0x60, 0xF7, 0x33, 0x05, 0xE7, 0xFC, 0xA9, 0x01, + 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, + 0xFF, 0xDF, 0x01, 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, + 0x1F, 0x40, 0x85, 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, + 0xFF, 0xF7, 0xFF, 0x04, 0x00, 0x06, 0x00, 0xEB, 0xFF, 0x09, 0x00, + 0x54, 0x00, 0xA4, 0xFE, 0xC9, 0x03, 0xAA, 0xF5, 0x0C, 0x42, 0x56, + 0x19, 0x1E, 0xF5, 0x2B, 0x06, 0x7A, 0xFC, 0xD4, 0x01, 0x3A, 0xFF, + 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, + 0x01, 0xB4, 0xFC, 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, + 0x86, 0xF7, 0xB6, 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, + 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x06, 0x00, 0xC2, 0xFF, 0xE3, 0x00, + 0xAE, 0xFD, 0x52, 0x05, 0x44, 0xF3, 0x2A, 0x3D, 0x06, 0x21, 0x47, + 0xF3, 0xD8, 0x06, 0x3C, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, + 0xFD, 0x89, 0x04, 0xCD, 0xF8, 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, + 0x91, 0x00, 0x83, 0x00, 0x4A, 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, + 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x88, 0xFF, 0x55, 0x01, 0xF2, 0xFC, + 0x67, 0x06, 0xE4, 0xF1, 0x44, 0x37, 0xAA, 0x28, 0x05, 0xF2, 0x27, + 0x07, 0x36, 0xFC, 0xDA, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, 0x01, 0xD6, 0xFD, 0x46, + 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, 0x98, 0x00, 0x26, 0xFE, + 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0xFD, + 0xFF, 0x2A, 0x00, 0x5D, 0xFF, 0xA7, 0x01, 0x75, 0xFC, 0x03, 0x07, + 0x7D, 0xF1, 0x8A, 0x30, 0xFF, 0x2F, 0x7E, 0xF1, 0x0A, 0x07, 0x6E, + 0xFC, 0xAC, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x1A, 0x00, + 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, 0xFD, 0x05, + 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, 0xE4, 0xFD, + 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x33, + 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x38, 0xFC, 0x29, 0x07, 0xF3, 0xF1, + 0x3E, 0x29, 0xC6, 0x36, 0xD4, 0xF1, 0x77, 0x06, 0xE6, 0xFC, 0x5C, + 0x01, 0x84, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x12, 0x00, 0xB6, 0xFF, + 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, 0xFB, 0x69, + 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, 0x81, 0x01, + 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, + 0xFF, 0xE6, 0x01, 0x3A, 0xFC, 0xE2, 0x06, 0x28, 0xF3, 0x9E, 0x21, + 0xC0, 0x3C, 0x1F, 0xF3, 0x6C, 0x05, 0x9E, 0xFD, 0xED, 0x00, 0xBD, + 0xFF, 0x07, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xD7, 0xFF, 0x3E, 0x00, + 0xEA, 0xFF, 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, 0x99, 0x44, 0x68, + 0x14, 0x8E, 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, 0x01, 0x43, 0xFF, + 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, + 0x01, 0x73, 0xFC, 0x3B, 0x06, 0xF5, 0xF4, 0xED, 0x19, 0xB7, 0x41, + 0x71, 0xF5, 0xEB, 0x03, 0x90, 0xFE, 0x60, 0x00, 0x04, 0x00, 0xED, + 0xFF, 0x06, 0x00, 0x04, 0x00, 0xF5, 0xFF, 0xEF, 0xFF, 0x88, 0x00, + 0x49, 0xFE, 0x5D, 0x04, 0xB7, 0xF4, 0x7D, 0x40, 0xFD, 0x1B, 0x6C, + 0xF4, 0x70, 0x06, 0x5F, 0xFC, 0xDE, 0x01, 0x37, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAD, 0x01, 0xDD, + 0xFC, 0x48, 0x05, 0x30, 0xF7, 0x6B, 0x12, 0x7D, 0x45, 0xCF, 0xF8, + 0x01, 0x02, 0xB2, 0xFF, 0xBD, 0xFF, 0x54, 0x00, 0xCE, 0xFF, 0x0C, + 0x00, 0x00, 0x00, 0x0E, 0x00, 0xAC, 0xFF, 0x0E, 0x01, 0x66, 0xFD, + 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, 0xB0, 0x23, 0xC4, 0xF2, 0xFF, + 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6E, 0x01, 0x6B, 0xFD, 0x1D, + 0x04, 0xAF, 0xF9, 0x51, 0x0B, 0xEC, 0x47, 0x33, 0xFD, 0xC1, 0xFF, + 0xF7, 0x00, 0x0C, 0xFF, 0xAA, 0x00, 0xAD, 0xFF, 0x14, 0x00, 0xFE, + 0xFF, 0x21, 0x00, 0x77, 0xFF, 0x75, 0x01, 0xBF, 0xFC, 0xAB, 0x06, + 0xA6, 0xF1, 0x05, 0x35, 0x40, 0x2B, 0xBF, 0xF1, 0x2A, 0x07, 0x42, + 0xFC, 0xCE, 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x20, 0x00, 0x7E, 0xFF, 0x21, 0x01, 0x12, 0xFE, 0xD1, 0x02, 0x47, + 0xFC, 0xD7, 0x04, 0xF0, 0x48, 0x8D, 0x02, 0x45, 0xFD, 0x4D, 0x02, + 0x56, 0xFE, 0x01, 0x01, 0x8B, 0xFF, 0x1D, 0x00, 0xFD, 0xFF, 0x2E, + 0x00, 0x52, 0xFF, 0xBC, 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, 0xF1, + 0x11, 0x2E, 0x6B, 0x32, 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, 0x94, + 0x01, 0x67, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x17, 0x00, 0xA0, 0xFF, + 0xCC, 0x00, 0xC6, 0xFE, 0x79, 0x01, 0xD2, 0xFE, 0x26, 0xFF, 0x7C, + 0x48, 0xBE, 0x08, 0xAE, 0xFA, 0xA0, 0x03, 0xA9, 0xFD, 0x52, 0x01, + 0x6B, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, + 0xFF, 0xE0, 0x01, 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, + 0xF2, 0x38, 0x2A, 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, + 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x0F, 0x00, 0xC2, 0xFF, 0x75, 0x00, + 0x7A, 0xFF, 0x2B, 0x00, 0x2D, 0x01, 0x61, 0xFA, 0x97, 0x46, 0xA0, + 0x0F, 0x20, 0xF8, 0xDA, 0x04, 0x10, 0xFD, 0x97, 0x01, 0x50, 0xFF, + 0x2E, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, + 0x01, 0x48, 0xFC, 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, + 0xCF, 0xF3, 0xF3, 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, + 0xFF, 0x03, 0x00, 0x08, 0x00, 0xE2, 0xFF, 0x21, 0x00, 0x23, 0x00, + 0xFA, 0xFE, 0x3A, 0x03, 0x9D, 0xF6, 0x50, 0x43, 0x00, 0x17, 0xC6, + 0xF5, 0xE6, 0x05, 0x97, 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x34, 0x00, + 0xFE, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, + 0xFC, 0xEF, 0x05, 0xB0, 0xF5, 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, + 0x4D, 0x03, 0xEF, 0xFE, 0x2A, 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, + 0x00, 0x03, 0x00, 0xFE, 0xFF, 0xD7, 0xFF, 0xBA, 0x00, 0xF4, 0xFD, + 0xE5, 0x04, 0xE4, 0xF3, 0xCA, 0x3E, 0xA7, 0x1E, 0xCA, 0xF3, 0xAC, + 0x06, 0x4A, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, 0x01, 0x0B, 0xFD, 0xE6, + 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, 0x37, 0xFA, 0x42, 0x01, + 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, 0xFF, 0x0F, 0x00, 0xFF, + 0xFF, 0x15, 0x00, 0x98, 0xFF, 0x35, 0x01, 0x25, 0xFD, 0x1E, 0x06, + 0x35, 0xF2, 0x2E, 0x39, 0x55, 0x26, 0x56, 0xF2, 0x1A, 0x07, 0x31, + 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, 0x03, 0x94, + 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, 0x6C, 0x01, + 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0xFD, 0xFF, 0x26, + 0x00, 0x69, 0xFF, 0x91, 0x01, 0x94, 0xFC, 0xE0, 0x06, 0x84, 0xF1, + 0xAF, 0x32, 0xCA, 0x2D, 0x92, 0xF1, 0x1F, 0x07, 0x56, 0xFC, 0xBE, + 0x01, 0x51, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, + 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, 0x02, 0xF2, + 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, 0x1E, 0x01, + 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x49, + 0xFF, 0xCC, 0x01, 0x44, 0xFC, 0x29, 0x07, 0xB9, 0xF1, 0x89, 0x2B, + 0xC3, 0x34, 0xA0, 0xF1, 0xB1, 0x06, 0xBA, 0xFC, 0x79, 0x01, 0x76, + 0xFF, 0x21, 0x00, 0xFE, 0xFF, 0x14, 0x00, 0xAC, 0xFF, 0xAE, 0x00, + 0x05, 0xFF, 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, 0xFD, 0x47, 0x0E, + 0x0B, 0xC8, 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, 0x01, 0x61, 0xFF, + 0x28, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, + 0x01, 0x33, 0xFC, 0x03, 0x07, 0xB7, 0xF2, 0xFC, 0x23, 0x03, 0x3B, + 0x9E, 0xF2, 0xCB, 0x05, 0x5F, 0xFD, 0x12, 0x01, 0xAA, 0xFF, 0x0E, + 0x00, 0x00, 0x00, 0x0C, 0x00, 0xCD, 0xFF, 0x57, 0x00, 0xB6, 0xFF, + 0xBE, 0xFF, 0xED, 0x01, 0xF5, 0xF8, 0x9B, 0x45, 0x22, 0x12, 0x48, + 0xF7, 0x3D, 0x05, 0xE2, 0xFC, 0xAB, 0x01, 0x49, 0xFF, 0x30, 0x00, + 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5C, + 0xFC, 0x78, 0x06, 0x5A, 0xF4, 0x49, 0x1C, 0x4E, 0x40, 0x9E, 0xF4, + 0x6D, 0x04, 0x3F, 0xFE, 0x8E, 0x00, 0xED, 0xFF, 0xF6, 0xFF, 0x04, + 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0x5A, 0x00, 0x9A, 0xFE, + 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, 0xA1, 0x19, 0x09, 0xF5, 0x33, + 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, + 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBC, 0x01, 0xB8, 0xFC, 0x9A, + 0x05, 0x77, 0xF6, 0xB1, 0x14, 0x77, 0x44, 0xA9, 0xF7, 0xA2, 0x02, + 0x54, 0xFF, 0xF1, 0xFF, 0x3A, 0x00, 0xD8, 0xFF, 0x0A, 0x00, 0x01, + 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xE8, 0x00, 0xA6, 0xFD, 0x5F, 0x05, + 0x31, 0xF3, 0xF6, 0x3C, 0x52, 0x21, 0x37, 0xF3, 0xDD, 0x06, 0x3B, + 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x2B, 0x00, 0x58, 0xFF, 0x83, 0x01, 0x3C, 0xFD, 0x7E, 0x04, 0xE6, + 0xF8, 0x72, 0x0D, 0x52, 0x47, 0xBE, 0xFB, 0x7A, 0x00, 0x90, 0x00, + 0x43, 0xFF, 0x8F, 0x00, 0xB7, 0xFF, 0x11, 0x00, 0xFE, 0xFF, 0x1C, + 0x00, 0x86, 0xFF, 0x59, 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, 0xF1, + 0x04, 0x37, 0xF3, 0x28, 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, 0xD8, + 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x23, 0x00, + 0x74, 0xFF, 0x3A, 0x01, 0xDD, 0xFD, 0x39, 0x03, 0x7B, 0xFB, 0xC1, + 0x06, 0xC7, 0x48, 0xCF, 0x00, 0x0D, 0xFE, 0xE3, 0x01, 0x8E, 0xFE, + 0xE7, 0x00, 0x95, 0xFF, 0x1A, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, + 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, + 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, + 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE7, 0x00, + 0x8E, 0xFE, 0xE3, 0x01, 0x0D, 0xFE, 0xCF, 0x00, 0xC7, 0x48, 0xC1, + 0x06, 0x7B, 0xFB, 0x39, 0x03, 0xDD, 0xFD, 0x3A, 0x01, 0x74, 0xFF, + 0x23, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, + 0x01, 0x37, 0xFC, 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, + 0xDC, 0xF1, 0x6F, 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, + 0x00, 0xFE, 0xFF, 0x11, 0x00, 0xB7, 0xFF, 0x8F, 0x00, 0x43, 0xFF, + 0x90, 0x00, 0x7A, 0x00, 0xBE, 0xFB, 0x52, 0x47, 0x72, 0x0D, 0xE6, + 0xF8, 0x7E, 0x04, 0x3C, 0xFD, 0x83, 0x01, 0x58, 0xFF, 0x2B, 0x00, + 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, + 0xFC, 0xDD, 0x06, 0x37, 0xF3, 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, + 0x5F, 0x05, 0xA6, 0xFD, 0xE8, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, + 0x00, 0x0A, 0x00, 0xD8, 0xFF, 0x3A, 0x00, 0xF1, 0xFF, 0x54, 0xFF, + 0xA2, 0x02, 0xA9, 0xF7, 0x77, 0x44, 0xB1, 0x14, 0x77, 0xF6, 0x9A, + 0x05, 0xB8, 0xFC, 0xBC, 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, + 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, 0x01, 0x77, 0xFC, 0x33, + 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, 0x8D, 0xF5, 0xDA, 0x03, + 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0x04, + 0x00, 0xF6, 0xFF, 0xED, 0xFF, 0x8E, 0x00, 0x3F, 0xFE, 0x6D, 0x04, + 0x9E, 0xF4, 0x4E, 0x40, 0x49, 0x1C, 0x5A, 0xF4, 0x78, 0x06, 0x5C, + 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, + 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, 0x05, 0x48, + 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, 0xBE, 0xFF, + 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0E, + 0x00, 0xAA, 0xFF, 0x12, 0x01, 0x5F, 0xFD, 0xCB, 0x05, 0x9E, 0xF2, + 0x03, 0x3B, 0xFC, 0x23, 0xB7, 0xF2, 0x03, 0x07, 0x33, 0xFC, 0xE5, + 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x28, 0x00, + 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, 0xF9, 0x0E, + 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, 0x05, 0xFF, + 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x76, + 0xFF, 0x79, 0x01, 0xBA, 0xFC, 0xB1, 0x06, 0xA0, 0xF1, 0xC3, 0x34, + 0x89, 0x2B, 0xB9, 0xF1, 0x29, 0x07, 0x44, 0xFC, 0xCC, 0x01, 0x49, + 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, 0xFF, + 0x1E, 0x01, 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, 0x9B, 0x04, 0xF2, + 0x48, 0xC6, 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, 0xFE, 0x04, 0x01, + 0x8A, 0xFF, 0x1D, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBE, + 0x01, 0x56, 0xFC, 0x1F, 0x07, 0x92, 0xF1, 0xCA, 0x2D, 0xAF, 0x32, + 0x84, 0xF1, 0xE0, 0x06, 0x94, 0xFC, 0x91, 0x01, 0x69, 0xFF, 0x26, + 0x00, 0xFD, 0xFF, 0x17, 0x00, 0xA1, 0xFF, 0xC9, 0x00, 0xCD, 0xFE, + 0x6C, 0x01, 0xEA, 0xFE, 0xF3, 0xFE, 0x70, 0x48, 0xFF, 0x08, 0x94, + 0xFA, 0xAD, 0x03, 0xA3, 0xFD, 0x55, 0x01, 0x6A, 0xFF, 0x26, 0x00, + 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x31, + 0xFC, 0x1A, 0x07, 0x56, 0xF2, 0x55, 0x26, 0x2E, 0x39, 0x35, 0xF2, + 0x1E, 0x06, 0x25, 0xFD, 0x35, 0x01, 0x98, 0xFF, 0x15, 0x00, 0xFF, + 0xFF, 0x0F, 0x00, 0xC3, 0xFF, 0x71, 0x00, 0x81, 0xFF, 0x1F, 0x00, + 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, 0xE7, 0x0F, 0x08, 0xF8, 0xE6, + 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, + 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x4A, 0xFC, 0xAC, + 0x06, 0xCA, 0xF3, 0xA7, 0x1E, 0xCA, 0x3E, 0xE4, 0xF3, 0xE5, 0x04, + 0xF4, 0xFD, 0xBA, 0x00, 0xD7, 0xFF, 0xFE, 0xFF, 0x03, 0x00, 0x08, + 0x00, 0xE3, 0xFF, 0x1E, 0x00, 0x2A, 0x00, 0xEF, 0xFE, 0x4D, 0x03, + 0x7D, 0xF6, 0x2A, 0x43, 0x4B, 0x17, 0xB0, 0xF5, 0xEF, 0x05, 0x93, + 0xFC, 0xCB, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFE, 0xFF, + 0x34, 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x97, 0xFC, 0xE6, 0x05, 0xC6, + 0xF5, 0x00, 0x17, 0x50, 0x43, 0x9D, 0xF6, 0x3A, 0x03, 0xFA, 0xFE, + 0x23, 0x00, 0x21, 0x00, 0xE2, 0xFF, 0x08, 0x00, 0x03, 0x00, 0xFF, + 0xFF, 0xD4, 0xFF, 0xBF, 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, 0xF3, + 0x98, 0x3E, 0xF3, 0x1E, 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, 0xE4, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, 0x00, + 0x50, 0xFF, 0x97, 0x01, 0x10, 0xFD, 0xDA, 0x04, 0x20, 0xF8, 0xA0, + 0x0F, 0x97, 0x46, 0x61, 0xFA, 0x2D, 0x01, 0x2B, 0x00, 0x7A, 0xFF, + 0x75, 0x00, 0xC2, 0xFF, 0x0F, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x96, + 0xFF, 0x39, 0x01, 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, + 0xA0, 0x26, 0x4B, 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, + 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6B, 0xFF, + 0x52, 0x01, 0xA9, 0xFD, 0xA0, 0x03, 0xAE, 0xFA, 0xBE, 0x08, 0x7C, + 0x48, 0x26, 0xFF, 0xD2, 0xFE, 0x79, 0x01, 0xC6, 0xFE, 0xCC, 0x00, + 0xA0, 0xFF, 0x17, 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, + 0x01, 0x90, 0xFC, 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, + 0x8E, 0xF1, 0x1D, 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, + 0x00, 0xFD, 0xFF, 0x1D, 0x00, 0x8B, 0xFF, 0x01, 0x01, 0x56, 0xFE, + 0x4D, 0x02, 0x45, 0xFD, 0x8D, 0x02, 0xF0, 0x48, 0xD7, 0x04, 0x47, + 0xFC, 0xD1, 0x02, 0x12, 0xFE, 0x21, 0x01, 0x7E, 0xFF, 0x20, 0x00, + 0x00, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, + 0xFC, 0x2A, 0x07, 0xBF, 0xF1, 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, + 0xAB, 0x06, 0xBF, 0xFC, 0x75, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, + 0xFF, 0x14, 0x00, 0xAD, 0xFF, 0xAA, 0x00, 0x0C, 0xFF, 0xF7, 0x00, + 0xC1, 0xFF, 0x33, 0xFD, 0xEC, 0x47, 0x51, 0x0B, 0xAF, 0xF9, 0x1D, + 0x04, 0x6B, 0xFD, 0x6E, 0x01, 0x60, 0xFF, 0x29, 0x00, 0x00, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0xFF, + 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, 0xAD, 0xF2, 0xBF, 0x05, + 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x0C, + 0x00, 0xCE, 0xFF, 0x54, 0x00, 0xBD, 0xFF, 0xB2, 0xFF, 0x01, 0x02, + 0xCF, 0xF8, 0x7D, 0x45, 0x6B, 0x12, 0x30, 0xF7, 0x48, 0x05, 0xDD, + 0xFC, 0xAD, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, + 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, 0x06, 0x6C, + 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, 0x49, 0xFE, + 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0x06, 0x00, 0xED, + 0xFF, 0x04, 0x00, 0x60, 0x00, 0x90, 0xFE, 0xEB, 0x03, 0x71, 0xF5, + 0xB7, 0x41, 0xED, 0x19, 0xF5, 0xF4, 0x3B, 0x06, 0x73, 0xFC, 0xD7, + 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, + 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, 0xF6, 0x68, + 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, 0xEA, 0xFF, + 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x07, 0x00, 0xBD, + 0xFF, 0xED, 0x00, 0x9E, 0xFD, 0x6C, 0x05, 0x1F, 0xF3, 0xC0, 0x3C, + 0x9E, 0x21, 0x28, 0xF3, 0xE2, 0x06, 0x3A, 0xFC, 0xE6, 0x01, 0x37, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, + 0x81, 0x01, 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, 0x2D, 0x0D, 0x69, + 0x47, 0xEB, 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, 0xFF, 0x93, 0x00, + 0xB6, 0xFF, 0x12, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x84, 0xFF, 0x5C, + 0x01, 0xE6, 0xFC, 0x77, 0x06, 0xD4, 0xF1, 0xC6, 0x36, 0x3E, 0x29, + 0xF3, 0xF1, 0x29, 0x07, 0x38, 0xFC, 0xD7, 0x01, 0x42, 0xFF, 0x33, + 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x76, 0xFF, 0x37, 0x01, + 0xE4, 0xFD, 0x2C, 0x03, 0x94, 0xFB, 0x83, 0x06, 0xCE, 0x48, 0x05, + 0x01, 0xF5, 0xFD, 0xF0, 0x01, 0x87, 0xFE, 0xEA, 0x00, 0x94, 0xFF, + 0x1A, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5A, 0xFF, 0xAC, 0x01, 0x6E, + 0xFC, 0x0A, 0x07, 0x7E, 0xF1, 0xFF, 0x2F, 0x8A, 0x30, 0x7D, 0xF1, + 0x03, 0x07, 0x75, 0xFC, 0xA7, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0xFD, + 0xFF, 0x1A, 0x00, 0x96, 0xFF, 0xE3, 0x00, 0x95, 0xFE, 0xD5, 0x01, + 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, 0x00, 0x07, 0x61, 0xFB, 0x46, + 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, 0xFF, 0x23, 0x00, 0x00, 0x00, + 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xDA, 0x01, 0x36, 0xFC, 0x27, + 0x07, 0x05, 0xF2, 0xAA, 0x28, 0x44, 0x37, 0xE4, 0xF1, 0x67, 0x06, + 0xF2, 0xFC, 0x55, 0x01, 0x88, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x11, + 0x00, 0xB9, 0xFF, 0x8C, 0x00, 0x4A, 0xFF, 0x83, 0x00, 0x91, 0x00, + 0x91, 0xFB, 0x3D, 0x47, 0xB7, 0x0D, 0xCD, 0xF8, 0x89, 0x04, 0x36, + 0xFD, 0x86, 0x01, 0x57, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3C, 0xFC, 0xD8, 0x06, 0x47, + 0xF3, 0x06, 0x21, 0x2A, 0x3D, 0x44, 0xF3, 0x52, 0x05, 0xAE, 0xFD, + 0xE3, 0x00, 0xC2, 0xFF, 0x06, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xD9, + 0xFF, 0x37, 0x00, 0xF7, 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, 0xF7, + 0x53, 0x44, 0xFB, 0x14, 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, 0xBE, + 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, + 0x3A, 0xFF, 0xD4, 0x01, 0x7A, 0xFC, 0x2B, 0x06, 0x1E, 0xF5, 0x56, + 0x19, 0x0C, 0x42, 0xAA, 0xF5, 0xC9, 0x03, 0xA4, 0xFE, 0x54, 0x00, + 0x09, 0x00, 0xEB, 0xFF, 0x06, 0x00, 0x04, 0x00, 0xF7, 0xFF, 0xEA, + 0xFF, 0x93, 0x00, 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, + 0x94, 0x1C, 0x47, 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, + 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, + 0xA9, 0x01, 0xE7, 0xFC, 0x33, 0x05, 0x60, 0xF7, 0xDA, 0x11, 0xB8, + 0x45, 0x1C, 0xF9, 0xD8, 0x01, 0xCA, 0xFF, 0xAF, 0xFF, 0x5A, 0x00, + 0xCC, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, + 0x01, 0x57, 0xFD, 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, + 0xAA, 0xF2, 0x06, 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x62, 0xFF, 0x69, 0x01, + 0x77, 0xFD, 0x04, 0x04, 0xE2, 0xF9, 0xCB, 0x0A, 0x0D, 0x48, 0x94, + 0xFD, 0x92, 0xFF, 0x10, 0x01, 0xFE, 0xFE, 0xB1, 0x00, 0xAA, 0xFF, + 0x15, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, + 0xFC, 0xB8, 0x06, 0x9C, 0xF1, 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, + 0x29, 0x07, 0x46, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, 0xFF, 0x1B, 0x01, 0x20, 0xFE, + 0xB6, 0x02, 0x7A, 0xFC, 0x5F, 0x04, 0xF4, 0x48, 0xFF, 0x02, 0x13, + 0xFD, 0x67, 0x02, 0x49, 0xFE, 0x07, 0x01, 0x88, 0xFF, 0x1D, 0x00, + 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, 0x01, 0x53, 0xFC, 0x21, + 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, 0x86, 0xF1, 0xDB, 0x06, + 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x16, + 0x00, 0xA2, 0xFF, 0xC5, 0x00, 0xD4, 0xFE, 0x5F, 0x01, 0x03, 0xFF, + 0xBF, 0xFE, 0x63, 0x48, 0x40, 0x09, 0x7B, 0xFA, 0xB9, 0x03, 0x9D, + 0xFD, 0x58, 0x01, 0x69, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, 0x07, 0x61, + 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, 0x2C, 0xFD, + 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0x0E, 0x00, 0xC4, + 0xFF, 0x6E, 0x00, 0x87, 0xFF, 0x13, 0x00, 0x58, 0x01, 0x0D, 0xFA, + 0x61, 0x46, 0x2D, 0x10, 0xF0, 0xF7, 0xF1, 0x04, 0x05, 0xFD, 0x9C, + 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, 0xF3, 0x5B, + 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, 0xB4, 0x00, + 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0x08, 0x00, 0xE4, 0xFF, 0x1B, + 0x00, 0x30, 0x00, 0xE4, 0xFE, 0x5F, 0x03, 0x5E, 0xF6, 0x02, 0x43, + 0x96, 0x17, 0x9B, 0xF5, 0xF8, 0x05, 0x8F, 0xFC, 0xCC, 0x01, 0x3D, + 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, + 0xC8, 0x01, 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, 0xB6, 0x16, 0x77, + 0x43, 0xBD, 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, 0x00, 0x25, 0x00, + 0xE1, 0xFF, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xC4, + 0x00, 0xE2, 0xFD, 0x01, 0x05, 0xBA, 0xF3, 0x64, 0x3E, 0x3F, 0x1F, + 0xA8, 0xF3, 0xB8, 0x06, 0x46, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x95, 0x01, + 0x15, 0xFD, 0xCF, 0x04, 0x39, 0xF8, 0x59, 0x0F, 0xAF, 0x46, 0x8B, + 0xFA, 0x17, 0x01, 0x38, 0x00, 0x73, 0xFF, 0x78, 0x00, 0xC0, 0xFF, + 0x0F, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x94, 0xFF, 0x3D, 0x01, 0x18, + 0xFD, 0x32, 0x06, 0x1F, 0xF2, 0xB5, 0x38, 0xEB, 0x26, 0x40, 0xF2, + 0x1E, 0x07, 0x32, 0xFC, 0xDF, 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6C, 0xFF, 0x4F, 0x01, 0xB0, 0xFD, + 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, 0x86, 0x48, 0x5A, 0xFF, 0xBA, + 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, 0x00, 0x9E, 0xFF, 0x17, 0x00, + 0xFD, 0xFF, 0x27, 0x00, 0x66, 0xFF, 0x97, 0x01, 0x8C, 0xFC, 0xEA, + 0x06, 0x80, 0xF1, 0x26, 0x32, 0x58, 0x2E, 0x8B, 0xF1, 0x1B, 0x07, + 0x5B, 0xFC, 0xBA, 0x01, 0x53, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x1C, + 0x00, 0x8C, 0xFF, 0xFE, 0x00, 0x5D, 0xFE, 0x3F, 0x02, 0x5E, 0xFD, + 0x54, 0x02, 0xEC, 0x48, 0x13, 0x05, 0x2E, 0xFC, 0xDE, 0x02, 0x0C, + 0xFE, 0x24, 0x01, 0x7D, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x31, 0x00, 0x47, 0xFF, 0xCF, 0x01, 0x40, 0xFC, 0x2A, 0x07, 0xC6, + 0xF1, 0xF7, 0x2A, 0x46, 0x35, 0xAB, 0xF1, 0xA4, 0x06, 0xC4, 0xFC, + 0x72, 0x01, 0x79, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x14, 0x00, 0xAE, + 0xFF, 0xA7, 0x00, 0x12, 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, 0xFD, + 0xDC, 0x47, 0x95, 0x0B, 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, 0x71, + 0x01, 0x5F, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, 0xFB, 0x06, 0xD2, 0xF2, 0x64, + 0x23, 0x73, 0x3B, 0xBC, 0xF2, 0xB4, 0x05, 0x6E, 0xFD, 0x09, 0x01, + 0xAF, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xD0, 0xFF, 0x51, + 0x00, 0xC3, 0xFF, 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, + 0xB2, 0x12, 0x19, 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, + 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, + 0xDD, 0x01, 0x62, 0xFC, 0x69, 0x06, 0x7F, 0xF4, 0xB2, 0x1B, 0xAB, + 0x40, 0xD0, 0xF4, 0x4E, 0x04, 0x53, 0xFE, 0x83, 0x00, 0xF2, 0xFF, + 0xF4, 0xFF, 0x05, 0x00, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, + 0x00, 0x85, 0xFE, 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, + 0xE1, 0xF4, 0x43, 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, + 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xB9, 0x01, + 0xC1, 0xFC, 0x86, 0x05, 0xA5, 0xF6, 0x1E, 0x14, 0xBA, 0x44, 0xF0, + 0xF7, 0x7B, 0x02, 0x6B, 0xFF, 0xE4, 0xFF, 0x41, 0x00, 0xD6, 0xFF, + 0x0B, 0x00, 0x01, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, + 0xFD, 0x78, 0x05, 0x0E, 0xF3, 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, + 0xE6, 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x5A, 0xFF, 0x7E, 0x01, 0x48, 0xFD, + 0x66, 0x04, 0x18, 0xF9, 0xE8, 0x0C, 0x7C, 0x47, 0x19, 0xFC, 0x4D, + 0x00, 0xA9, 0x00, 0x35, 0xFF, 0x96, 0x00, 0xB5, 0xFF, 0x12, 0x00, + 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, 0x01, 0xE0, 0xFC, 0x7F, + 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, 0xEB, 0xF1, 0x2A, 0x07, + 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x22, 0x00, 0x77, 0xFF, 0x34, 0x01, 0xEA, 0xFD, 0x1F, 0x03, + 0xAE, 0xFB, 0x45, 0x06, 0xD5, 0x48, 0x3C, 0x01, 0xDC, 0xFD, 0xFD, + 0x01, 0x80, 0xFE, 0xED, 0x00, 0x93, 0xFF, 0x1B, 0x00, 0xFD, 0xFF, + 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, 0x07, 0x80, + 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, 0x78, 0xFC, + 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x19, 0x00, 0x98, + 0xFF, 0xE0, 0x00, 0x9C, 0xFE, 0xC8, 0x01, 0x3F, 0xFE, 0x62, 0x00, + 0xB8, 0x48, 0x3F, 0x07, 0x47, 0xFB, 0x53, 0x03, 0xD0, 0xFD, 0x40, + 0x01, 0x72, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, 0xF2, 0x60, + 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, 0x51, 0x01, + 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0x11, 0x00, 0xBA, 0xFF, 0x89, + 0x00, 0x51, 0xFF, 0x77, 0x00, 0xA7, 0x00, 0x64, 0xFB, 0x26, 0x47, + 0xFC, 0x0D, 0xB4, 0xF8, 0x95, 0x04, 0x31, 0xFD, 0x88, 0x01, 0x56, + 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE6, 0x01, 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, 0xBA, 0x20, 0x61, + 0x3D, 0x56, 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, 0x00, 0xC5, 0xFF, + 0x05, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDB, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0x3D, 0xFF, 0xC9, 0x02, 0x64, 0xF7, 0x2F, 0x44, 0x44, 0x15, + 0x4A, 0xF6, 0xAD, 0x05, 0xAF, 0xFC, 0xC0, 0x01, 0x41, 0xFF, 0x32, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD3, 0x01, + 0x7D, 0xFC, 0x23, 0x06, 0x32, 0xF5, 0x0C, 0x19, 0x38, 0x42, 0xC7, + 0xF5, 0xB8, 0x03, 0xAF, 0xFE, 0x4E, 0x00, 0x0C, 0x00, 0xEA, 0xFF, + 0x06, 0x00, 0x04, 0x00, 0xF8, 0xFF, 0xE7, 0xFF, 0x99, 0x00, 0x2C, + 0xFE, 0x8C, 0x04, 0x6D, 0xF4, 0xF0, 0x3F, 0xE0, 0x1C, 0x34, 0xF4, + 0x85, 0x06, 0x57, 0xFC, 0xE0, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, 0xFF, 0xA7, 0x01, 0xEC, 0xFC, + 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, 0xD7, 0x45, 0x43, 0xF9, 0xC3, + 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, 0x00, 0xCB, 0xFF, 0x0D, 0x00, + 0x00, 0x00, 0x10, 0x00, 0xA6, 0xFF, 0x1B, 0x01, 0x50, 0xFD, 0xE1, + 0x05, 0x82, 0xF2, 0x8F, 0x3A, 0x92, 0x24, 0x9D, 0xF2, 0x09, 0x07, + 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x28, 0x00, 0x63, 0xFF, 0x66, 0x01, 0x7D, 0xFD, 0xF8, 0x03, + 0xFB, 0xF9, 0x89, 0x0A, 0x1D, 0x48, 0xC5, 0xFD, 0x7A, 0xFF, 0x1D, + 0x01, 0xF7, 0xFE, 0xB4, 0x00, 0xA9, 0xFF, 0x15, 0x00, 0xFE, 0xFF, + 0x23, 0x00, 0x72, 0xFF, 0x7F, 0x01, 0xB0, 0xFC, 0xBE, 0x06, 0x97, + 0xF1, 0x3F, 0x34, 0x19, 0x2C, 0xAD, 0xF1, 0x28, 0x07, 0x48, 0xFC, + 0xC9, 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x1F, + 0x00, 0x82, 0xFF, 0x18, 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, 0xFC, + 0x24, 0x04, 0xF5, 0x48, 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, 0x42, + 0xFE, 0x0B, 0x01, 0x87, 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x2F, 0x00, + 0x4F, 0xFF, 0xC2, 0x01, 0x51, 0xFC, 0x23, 0x07, 0x9A, 0xF1, 0x3A, + 0x2D, 0x35, 0x33, 0x89, 0xF1, 0xD5, 0x06, 0x9D, 0xFC, 0x8B, 0x01, + 0x6C, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC2, + 0x00, 0xDB, 0xFE, 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, + 0x81, 0x09, 0x61, 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, + 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, + 0xE2, 0x01, 0x31, 0xFC, 0x15, 0x07, 0x6D, 0xF2, 0xBF, 0x25, 0xA5, + 0x39, 0x4D, 0xF2, 0x0B, 0x06, 0x33, 0xFD, 0x2D, 0x01, 0x9D, 0xFF, + 0x13, 0x00, 0xFF, 0xFF, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, + 0xFF, 0x06, 0x00, 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, + 0xD7, 0xF7, 0xFC, 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, + 0x4E, 0xFC, 0xA0, 0x06, 0xED, 0xF3, 0x0F, 0x1E, 0x2D, 0x3F, 0x10, + 0xF4, 0xC8, 0x04, 0x07, 0xFE, 0xAF, 0x00, 0xDC, 0xFF, 0xFC, 0xFF, + 0x03, 0x00, 0x07, 0x00, 0xE5, 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, + 0xFE, 0x71, 0x03, 0x3F, 0xF6, 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, + 0x00, 0x06, 0x8C, 0xFC, 0xCE, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC6, 0x01, 0x9F, 0xFC, + 0xD3, 0x05, 0xF1, 0xF5, 0x6C, 0x16, 0x9E, 0x43, 0xDD, 0xF6, 0x15, + 0x03, 0x10, 0xFF, 0x17, 0x00, 0x28, 0x00, 0xDF, 0xFF, 0x09, 0x00, + 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, 0x00, 0xDA, 0xFD, 0x0F, + 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, 0x97, 0xF3, 0xBD, 0x06, + 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, + 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x92, 0x01, 0x1B, 0xFD, 0xC4, 0x04, + 0x51, 0xF8, 0x13, 0x0F, 0xC8, 0x46, 0xB6, 0xFA, 0x01, 0x01, 0x44, + 0x00, 0x6C, 0xFF, 0x7B, 0x00, 0xBF, 0xFF, 0x10, 0x00, 0xFF, 0xFF, + 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, 0x06, 0x14, + 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, 0x33, 0xFC, + 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x25, + 0x00, 0x6D, 0xFF, 0x4C, 0x01, 0xB6, 0xFD, 0x86, 0x03, 0xE1, 0xFA, + 0x3D, 0x08, 0x92, 0x48, 0x8E, 0xFF, 0xA1, 0xFE, 0x93, 0x01, 0xB8, + 0xFE, 0xD3, 0x00, 0x9D, 0xFF, 0x18, 0x00, 0xFD, 0xFF, 0x28, 0x00, + 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, 0xF1, 0xE3, + 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, 0xB7, 0x01, + 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x1C, 0x00, 0x8D, 0xFF, 0xFA, + 0x00, 0x64, 0xFE, 0x32, 0x02, 0x78, 0xFD, 0x1B, 0x02, 0xEA, 0x48, + 0x50, 0x05, 0x14, 0xFC, 0xEB, 0x02, 0x05, 0xFE, 0x27, 0x01, 0x7C, + 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, + 0xD1, 0x01, 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, 0xAE, 0x2A, 0x86, + 0x35, 0xB1, 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, 0x01, 0x7B, 0xFF, + 0x20, 0x00, 0xFE, 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA4, 0x00, 0x19, + 0xFF, 0xDD, 0x00, 0xF0, 0xFF, 0xD4, 0xFC, 0xC9, 0x47, 0xD8, 0x0B, + 0x7C, 0xF9, 0x35, 0x04, 0x5F, 0xFD, 0x74, 0x01, 0x5E, 0xFF, 0x29, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, + 0x35, 0xFC, 0xF7, 0x06, 0xE0, 0xF2, 0x18, 0x23, 0xAB, 0x3B, 0xCC, + 0xF2, 0xA8, 0x05, 0x76, 0xFD, 0x04, 0x01, 0xB1, 0xFF, 0x0C, 0x00, + 0x00, 0x00, 0x0C, 0x00, 0xD1, 0xFF, 0x4E, 0x00, 0xCA, 0xFF, 0x9A, + 0xFF, 0x2A, 0x02, 0x83, 0xF8, 0x3F, 0x45, 0xFB, 0x12, 0x01, 0xF7, + 0x5D, 0x05, 0xD3, 0xFC, 0xB1, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, + 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDC, 0x01, 0x64, 0xFC, + 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, 0xD9, 0x40, 0xEA, 0xF4, 0x3E, + 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, 0xFF, 0xF3, 0xFF, 0x05, 0x00, + 0x05, 0x00, 0xEF, 0xFF, 0xFE, 0xFF, 0x6C, 0x00, 0x7B, 0xFE, 0x0C, + 0x04, 0x3A, 0xF5, 0x5F, 0x41, 0x83, 0x1A, 0xCD, 0xF4, 0x4B, 0x06, + 0x6D, 0xFC, 0xD9, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, + 0xFF, 0x31, 0x00, 0x44, 0xFF, 0xB7, 0x01, 0xC5, 0xFC, 0x7C, 0x05, + 0xBC, 0xF6, 0xD5, 0x13, 0xDC, 0x44, 0x14, 0xF8, 0x67, 0x02, 0x77, + 0xFF, 0xDD, 0xFF, 0x44, 0x00, 0xD5, 0xFF, 0x0B, 0x00, 0x01, 0x00, + 0x09, 0x00, 0xB8, 0xFF, 0xF6, 0x00, 0x8D, 0xFD, 0x84, 0x05, 0xFD, + 0xF2, 0x52, 0x3C, 0x35, 0x22, 0x0B, 0xF3, 0xEB, 0x06, 0x37, 0xFC, + 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2A, + 0x00, 0x5B, 0xFF, 0x7C, 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, 0xF9, + 0xA4, 0x0C, 0x90, 0x47, 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, 0x2E, + 0xFF, 0x99, 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFE, 0xFF, 0x1E, 0x00, + 0x80, 0xFF, 0x64, 0x01, 0xDA, 0xFC, 0x87, 0x06, 0xC5, 0xF1, 0x46, + 0x36, 0xD1, 0x29, 0xE3, 0xF1, 0x2A, 0x07, 0x3A, 0xFC, 0xD5, 0x01, + 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x78, + 0xFF, 0x31, 0x01, 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, + 0xDB, 0x48, 0x73, 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, + 0x00, 0x91, 0xFF, 0x1B, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x58, 0xFF, + 0xB1, 0x01, 0x67, 0xFC, 0x10, 0x07, 0x81, 0xF1, 0x73, 0x2F, 0x15, + 0x31, 0x7C, 0xF1, 0xFB, 0x06, 0x7C, 0xFC, 0xA2, 0x01, 0x60, 0xFF, + 0x29, 0x00, 0xFD, 0xFF, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, + 0xFE, 0xBB, 0x01, 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, + 0x2E, 0xFB, 0x60, 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDC, 0x01, + 0x34, 0xFC, 0x25, 0x07, 0x18, 0xF2, 0x15, 0x28, 0xBF, 0x37, 0xF7, + 0xF1, 0x56, 0x06, 0xFE, 0xFC, 0x4D, 0x01, 0x8C, 0xFF, 0x19, 0x00, + 0xFF, 0xFF, 0x10, 0x00, 0xBB, 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, + 0x00, 0xBE, 0x00, 0x38, 0xFB, 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, + 0xA1, 0x04, 0x2B, 0xFD, 0x8B, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, + 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3F, 0xFC, + 0xCE, 0x06, 0x66, 0xF3, 0x6F, 0x20, 0x96, 0x3D, 0x69, 0xF3, 0x38, + 0x05, 0xBF, 0xFD, 0xD9, 0x00, 0xC7, 0xFF, 0x04, 0x00, 0x02, 0x00, + 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, 0x00, 0x32, 0xFF, 0xDC, + 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, 0x34, 0xF6, 0xB7, 0x05, + 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xD2, 0x01, 0x81, 0xFC, 0x1A, 0x06, + 0x47, 0xF5, 0xC1, 0x18, 0x60, 0x42, 0xE4, 0xF5, 0xA6, 0x03, 0xB9, + 0xFE, 0x48, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0x07, 0x00, 0x04, 0x00, + 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, 0x04, 0x55, + 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, 0x55, 0xFC, + 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2F, + 0x00, 0x4B, 0xFF, 0xA4, 0x01, 0xF1, 0xFC, 0x1D, 0x05, 0x8F, 0xF7, + 0x4A, 0x11, 0xF2, 0x45, 0x6B, 0xF9, 0xAE, 0x01, 0xE2, 0xFF, 0xA2, + 0xFF, 0x61, 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x11, 0x00, + 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, 0xF2, 0x54, + 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, 0xE4, 0x01, + 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x64, + 0xFF, 0x63, 0x01, 0x84, 0xFD, 0xEB, 0x03, 0x14, 0xFA, 0x47, 0x0A, + 0x2C, 0x48, 0xF6, 0xFD, 0x63, 0xFF, 0x2B, 0x01, 0xF0, 0xFE, 0xB8, + 0x00, 0xA8, 0xFF, 0x15, 0x00, 0xFE, 0xFF, 0x23, 0x00, 0x71, 0xFF, + 0x82, 0x01, 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, 0xFD, 0x33, 0x62, + 0x2C, 0xA8, 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, 0x01, 0x4C, 0xFF, + 0x30, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, 0xFF, 0x14, + 0x01, 0x2D, 0xFE, 0x9C, 0x02, 0xAD, 0xFC, 0xE9, 0x03, 0xF6, 0x48, + 0x73, 0x03, 0xE0, 0xFC, 0x82, 0x02, 0x3B, 0xFE, 0x0E, 0x01, 0x86, + 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, 0x01, + 0x4E, 0xFC, 0x24, 0x07, 0x9E, 0xF1, 0xF2, 0x2C, 0x78, 0x33, 0x8C, + 0xF1, 0xD0, 0x06, 0xA2, 0xFC, 0x88, 0x01, 0x6D, 0xFF, 0x24, 0x00, + 0xFD, 0xFF, 0x16, 0x00, 0xA5, 0xFF, 0xBE, 0x00, 0xE2, 0xFE, 0x45, + 0x01, 0x33, 0xFF, 0x5A, 0xFE, 0x48, 0x48, 0xC3, 0x09, 0x47, 0xFA, + 0xD2, 0x03, 0x90, 0xFD, 0x5E, 0x01, 0x66, 0xFF, 0x27, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE3, 0x01, 0x31, 0xFC, + 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, 0xDF, 0x39, 0x5A, 0xF2, 0x00, + 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, 0xFF, 0x13, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0xC7, 0xFF, 0x68, 0x00, 0x95, 0xFF, 0xFA, 0xFF, 0x83, + 0x01, 0xBB, 0xF9, 0x2B, 0x46, 0xBB, 0x10, 0xBF, 0xF7, 0x07, 0x05, + 0xFB, 0xFC, 0xA0, 0x01, 0x4D, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x50, 0xFC, 0x99, 0x06, + 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, 0x3F, 0x27, 0xF4, 0xB9, 0x04, 0x10, + 0xFE, 0xA9, 0x00, 0xDF, 0xFF, 0xFB, 0xFF, 0x03, 0x00, 0x07, 0x00, + 0xE6, 0xFF, 0x15, 0x00, 0x3C, 0x00, 0xCF, 0xFE, 0x83, 0x03, 0x20, + 0xF6, 0xB2, 0x42, 0x2B, 0x18, 0x71, 0xF5, 0x09, 0x06, 0x88, 0xFC, + 0xCF, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, + 0x00, 0x3F, 0xFF, 0xC5, 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, 0xF6, + 0x22, 0x16, 0xC3, 0x43, 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, 0x11, + 0x00, 0x2B, 0x00, 0xDE, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x02, 0x00, + 0xCC, 0xFF, 0xCE, 0x00, 0xD1, 0xFD, 0x1D, 0x05, 0x91, 0xF3, 0xFE, + 0x3D, 0xD7, 0x1F, 0x87, 0xF3, 0xC3, 0x06, 0x42, 0xFC, 0xE5, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2D, 0x00, 0x53, + 0xFF, 0x90, 0x01, 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, + 0xE1, 0x46, 0xE1, 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, + 0x00, 0xBE, 0xFF, 0x10, 0x00, 0xFF, 0xFF, 0x18, 0x00, 0x90, 0xFF, + 0x45, 0x01, 0x0B, 0xFD, 0x44, 0x06, 0x0A, 0xF2, 0x3B, 0x38, 0x80, + 0x27, 0x2B, 0xF2, 0x22, 0x07, 0x33, 0xFC, 0xDE, 0x01, 0x3E, 0xFF, + 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, + 0x01, 0xBC, 0xFD, 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, + 0xC3, 0xFF, 0x89, 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, + 0xFF, 0x18, 0x00, 0xFD, 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9D, 0x01, + 0x84, 0xFC, 0xF3, 0x06, 0x7D, 0xF1, 0x9E, 0x31, 0xE6, 0x2E, 0x85, + 0xF1, 0x16, 0x07, 0x61, 0xFC, 0xB5, 0x01, 0x55, 0xFF, 0x2D, 0x00, + 0xFD, 0xFF, 0x1C, 0x00, 0x8F, 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, + 0x02, 0x91, 0xFD, 0xE3, 0x01, 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, + 0xF8, 0x02, 0xFE, 0xFD, 0x2B, 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, 0xFF, 0xD2, 0x01, 0x3D, 0xFC, + 0x2B, 0x07, 0xD4, 0xF1, 0x64, 0x2A, 0xC6, 0x35, 0xB7, 0xF1, 0x96, + 0x06, 0xCF, 0xFC, 0x6B, 0x01, 0x7D, 0xFF, 0x1F, 0x00, 0xFE, 0xFF, + 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, 0xFF, 0xD0, 0x00, 0x07, + 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, 0x63, 0xF9, 0x42, 0x04, + 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF3, 0x06, + 0xEE, 0xF2, 0xCD, 0x22, 0xE4, 0x3B, 0xDC, 0xF2, 0x9C, 0x05, 0x7E, + 0xFD, 0x00, 0x01, 0xB4, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0B, 0x00, + 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, 0x02, 0x5E, + 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, 0xCF, 0xFC, + 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, + 0x00, 0x38, 0xFF, 0xDB, 0x01, 0x67, 0xFC, 0x5A, 0x06, 0xA6, 0xF4, + 0x1B, 0x1B, 0x07, 0x41, 0x04, 0xF5, 0x2D, 0x04, 0x67, 0xFE, 0x77, + 0x00, 0xF8, 0xFF, 0xF2, 0xFF, 0x05, 0x00, 0x05, 0x00, 0xF0, 0xFF, + 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, 0xF5, 0x32, + 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, 0xDA, 0x01, + 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x45, + 0xFF, 0xB5, 0x01, 0xCA, 0xFC, 0x72, 0x05, 0xD3, 0xF6, 0x8D, 0x13, + 0xFD, 0x44, 0x39, 0xF8, 0x53, 0x02, 0x82, 0xFF, 0xD7, 0xFF, 0x47, + 0x00, 0xD3, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xB6, 0xFF, + 0xFB, 0x00, 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, 0x1C, 0x3C, 0x81, + 0x22, 0xFC, 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2A, 0x00, 0x5C, 0xFF, 0x79, + 0x01, 0x53, 0xFD, 0x4E, 0x04, 0x4A, 0xF9, 0x60, 0x0C, 0xA3, 0x47, + 0x76, 0xFC, 0x1F, 0x00, 0xC3, 0x00, 0x27, 0xFF, 0x9D, 0x00, 0xB2, + 0xFF, 0x13, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x7F, 0xFF, 0x67, 0x01, + 0xD5, 0xFC, 0x8E, 0x06, 0xBE, 0xF1, 0x06, 0x36, 0x1A, 0x2A, 0xDC, + 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, 0x44, 0xFF, 0x32, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x79, 0xFF, 0x2E, 0x01, 0xF7, + 0xFD, 0x05, 0x03, 0xE1, 0xFB, 0xCA, 0x05, 0xDF, 0x48, 0xAB, 0x01, + 0xAA, 0xFD, 0x18, 0x02, 0x72, 0xFE, 0xF4, 0x00, 0x90, 0xFF, 0x1B, + 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x57, 0xFF, 0xB3, 0x01, 0x64, 0xFC, + 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, 0x5A, 0x31, 0x7D, 0xF1, 0xF7, + 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, 0xFF, 0x29, 0x00, 0xFD, 0xFF, + 0x19, 0x00, 0x9A, 0xFF, 0xD9, 0x00, 0xAA, 0xFE, 0xAE, 0x01, 0x70, + 0xFE, 0xF8, 0xFF, 0xA6, 0x48, 0xBE, 0x07, 0x14, 0xFB, 0x6D, 0x03, + 0xC3, 0xFD, 0x46, 0x01, 0x70, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDD, 0x01, 0x34, 0xFC, 0x23, 0x07, + 0x21, 0xF2, 0xCB, 0x27, 0xFE, 0x37, 0x00, 0xF2, 0x4D, 0x06, 0x04, + 0xFD, 0x49, 0x01, 0x8E, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x10, 0x00, + 0xBD, 0xFF, 0x82, 0x00, 0x5E, 0xFF, 0x5D, 0x00, 0xD4, 0x00, 0x0C, + 0xFB, 0xF9, 0x46, 0x87, 0x0E, 0x82, 0xF8, 0xAD, 0x04, 0x26, 0xFD, + 0x8D, 0x01, 0x54, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, 0xF3, + 0x22, 0x20, 0xCA, 0x3D, 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, 0xD4, + 0x00, 0xCA, 0xFF, 0x03, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDD, 0xFF, + 0x2E, 0x00, 0x0A, 0x00, 0x27, 0xFF, 0xEF, 0x02, 0x20, 0xF7, 0xE7, + 0x43, 0xD8, 0x15, 0x1E, 0xF6, 0xC0, 0x05, 0xA7, 0xFC, 0xC3, 0x01, + 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3B, + 0xFF, 0xD1, 0x01, 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, + 0x89, 0x42, 0x02, 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, + 0x00, 0xE8, 0xFF, 0x07, 0x00, 0x03, 0x00, 0xFA, 0xFF, 0xE2, 0xFF, + 0xA4, 0x00, 0x19, 0xFE, 0xAA, 0x04, 0x3E, 0xF4, 0x90, 0x3F, 0x78, + 0x1D, 0x10, 0xF4, 0x93, 0x06, 0x52, 0xFC, 0xE1, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, + 0x01, 0xF6, 0xFC, 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, + 0x93, 0xF9, 0x98, 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, + 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0xA1, 0xFF, 0x24, 0x01, + 0x41, 0xFD, 0xF6, 0x05, 0x67, 0xF2, 0x1A, 0x3A, 0x29, 0x25, 0x84, + 0xF2, 0x0F, 0x07, 0x31, 0xFC, 0xE3, 0x01, 0x3A, 0xFF, 0x35, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, + 0xFD, 0xDF, 0x03, 0x2E, 0xFA, 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, + 0x4B, 0xFF, 0x38, 0x01, 0xE9, 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, + 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6F, 0xFF, 0x85, 0x01, 0xA6, 0xFC, + 0xCA, 0x06, 0x8F, 0xF1, 0xBB, 0x33, 0xAB, 0x2C, 0xA3, 0xF1, 0x26, + 0x07, 0x4C, 0xFC, 0xC5, 0x01, 0x4D, 0xFF, 0x30, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, + 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, + 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0xFD, + 0xFF, 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, + 0x7E, 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, + 0xFC, 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, + 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, + 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x02, 0x00, 0x05, + 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, 0xFD, 0x4E, 0x05, 0x4A, 0xF3, + 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, 0xD6, 0x06, 0x3D, 0xFC, 0xE6, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD6, 0x06, 0x4C, 0xF3, 0xED, + 0x20, 0x3D, 0x3D, 0x4A, 0xF3, 0x4E, 0x05, 0xB1, 0xFD, 0xE1, 0x00, + 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x84, + 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, + 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, + 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x16, 0x00, 0xA6, 0xFF, 0xBB, 0x00, + 0xE9, 0xFE, 0x38, 0x01, 0x4B, 0xFF, 0x28, 0xFE, 0x3A, 0x48, 0x04, + 0x0A, 0x2E, 0xFA, 0xDF, 0x03, 0x8A, 0xFD, 0x60, 0x01, 0x65, 0xFF, + 0x27, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xC8, 0xFF, 0x64, 0x00, 0x9B, + 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, 0xF9, 0x10, 0x46, 0x03, 0x11, + 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, 0xA2, 0x01, 0x4C, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x07, 0x00, 0xE8, 0xFF, 0x12, 0x00, 0x42, 0x00, + 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, 0x76, 0x18, 0x5C, + 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, 0x34, 0x00, + 0xFE, 0xFF, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, 0x00, 0xC8, + 0xFD, 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, 0x76, 0xF3, + 0xC8, 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0xFF, 0xFF, 0x19, 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, 0xFD, + 0x4D, 0x06, 0x00, 0xF2, 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, 0x23, + 0x07, 0x34, 0xFC, 0xDD, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, + 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, 0x01, 0x80, 0xFC, 0xF7, + 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, 0x83, 0xF1, 0x13, 0x07, + 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, 0x07, + 0xDC, 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, 0xD5, + 0xFC, 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, + 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, + 0xF2, 0x81, 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, + 0xFB, 0x00, 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x35, + 0x00, 0x38, 0xFF, 0xDA, 0x01, 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, + 0xCE, 0x1A, 0x32, 0x41, 0x1F, 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, + 0x00, 0xFB, 0xFF, 0xF0, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0x31, 0x00, + 0x46, 0xFF, 0xB3, 0x01, 0xCF, 0xFC, 0x67, 0x05, 0xEA, 0xF6, 0x44, + 0x13, 0x1E, 0x45, 0x5E, 0xF8, 0x3F, 0x02, 0x8E, 0xFF, 0xD0, 0xFF, + 0x4A, 0x00, 0xD2, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, + 0xFF, 0x76, 0x01, 0x59, 0xFD, 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, + 0xB6, 0x47, 0xA4, 0xFC, 0x07, 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, + 0x00, 0xB1, 0xFF, 0x13, 0x00, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, + 0x2B, 0x01, 0xFE, 0xFD, 0xF8, 0x02, 0xFB, 0xFB, 0x8D, 0x05, 0xE5, + 0x48, 0xE3, 0x01, 0x91, 0xFD, 0x25, 0x02, 0x6B, 0xFE, 0xF7, 0x00, + 0x8F, 0xFF, 0x1C, 0x00, 0x18, 0x00, 0x9C, 0xFF, 0xD6, 0x00, 0xB1, + 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, 0xFF, 0x9C, 0x48, 0xFD, 0x07, + 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, 0x49, 0x01, 0x6E, 0xFF, 0x24, + 0x00, 0x00, 0x00, 0x10, 0x00, 0xBE, 0xFF, 0x7F, 0x00, 0x65, 0xFF, + 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, 0xCD, 0x0E, 0x6A, + 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, 0xFF, 0x2D, 0x00, + 0xFF, 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, 0x00, 0x1B, + 0xFF, 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, 0x07, 0xF6, + 0xCA, 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, + 0xFF, 0x03, 0x00, 0xFB, 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, 0xFE, + 0xB9, 0x04, 0x27, 0xF4, 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, 0x99, + 0x06, 0x50, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, + 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, 0x01, 0x3A, 0xFD, 0x00, + 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, 0x79, 0xF2, 0x12, 0x07, + 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, 0x06, + 0x8C, 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, 0x4E, + 0xFC, 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, + 0x30, 0x00, 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, + 0xF1, 0x62, 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, + 0x82, 0x01, 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x3A, 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, + 0xDD, 0x24, 0x54, 0x3A, 0x74, 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, + 0x01, 0xA3, 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x37, 0xFF, 0xE1, 0x01, 0x55, 0xFC, 0x8C, 0x06, 0x22, 0xF4, 0x2C, + 0x1D, 0xC0, 0x3F, 0x55, 0xF4, 0x9B, 0x04, 0x23, 0xFE, 0x9F, 0x00, + 0xE4, 0xFF, 0xF9, 0xFF, 0x04, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, + 0xFF, 0xC1, 0x01, 0xAB, 0xFC, 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, + 0x0B, 0x44, 0x42, 0xF7, 0xDC, 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, + 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x55, 0xFF, + 0x8B, 0x01, 0x2B, 0xFD, 0xA1, 0x04, 0x9B, 0xF8, 0x42, 0x0E, 0x0F, + 0x47, 0x38, 0xFB, 0xBE, 0x00, 0x6A, 0x00, 0x58, 0xFF, 0x85, 0x00, + 0xBB, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x71, 0xFF, 0x43, + 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, 0xFB, 0x7E, 0x07, 0xAF, 0x48, + 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, 0xA3, 0xFE, 0xDD, 0x00, 0x99, + 0xFF, 0x19, 0x00, 0x1B, 0x00, 0x91, 0xFF, 0xF1, 0x00, 0x79, 0xFE, + 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, 0x07, 0x06, 0xC7, + 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, 0xFF, 0x22, 0x00, + 0x00, 0x00, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, 0xFF, 0xB6, + 0x00, 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, 0x31, 0xF9, + 0x5A, 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, 0x00, 0x00, + 0x00, 0x0B, 0x00, 0xD5, 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, 0xFF, + 0x67, 0x02, 0x14, 0xF8, 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, 0x7C, + 0x05, 0xC5, 0xFC, 0xB7, 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, 0xFF, + 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, 0x00, 0x5D, 0xFE, 0x3E, + 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, 0x93, 0xF4, 0x62, 0x06, + 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, + 0x00, 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, 0x05, + 0xCC, 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, 0x35, + 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, + 0x20, 0x00, 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, + 0xF1, 0x86, 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, + 0xD1, 0x01, 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2D, + 0x00, 0x54, 0xFF, 0xB7, 0x01, 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, + 0x9F, 0x2E, 0xE3, 0x31, 0x7E, 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, + 0x01, 0x64, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x34, 0x00, + 0x3E, 0xFF, 0xDF, 0x01, 0x33, 0xFC, 0x20, 0x07, 0x35, 0xF2, 0x36, + 0x27, 0x78, 0x38, 0x14, 0xF2, 0x3B, 0x06, 0x11, 0xFD, 0x41, 0x01, + 0x92, 0xFF, 0x17, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE5, 0x01, 0x44, 0xFC, 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, + 0x31, 0x3E, 0xA5, 0xF3, 0x0F, 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, + 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, + 0xCE, 0x01, 0x8C, 0xFC, 0x00, 0x06, 0x86, 0xF5, 0xE0, 0x17, 0xDB, + 0x42, 0x3F, 0xF6, 0x71, 0x03, 0xD9, 0xFE, 0x36, 0x00, 0x18, 0x00, + 0xE5, 0xFF, 0x07, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9E, + 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, 0xF7, 0x75, 0x10, 0x48, 0x46, + 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, 0x8E, 0xFF, 0x6B, 0x00, 0xC6, + 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, 0x5B, 0x01, + 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, 0x57, 0x48, 0x8D, + 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, 0x00, 0xA4, 0xFF, + 0x16, 0x00, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, 0xFE, 0x74, + 0x02, 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, 0x94, 0xFC, + 0xA9, 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, 0x00, 0x00, + 0x00, 0x15, 0x00, 0xA9, 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, 0x01, + 0x7A, 0xFF, 0xC5, 0xFD, 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, 0xF8, + 0x03, 0x7D, 0xFD, 0x66, 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, 0xFF, 0xD6, 0xFF, 0xC3, + 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, 0x77, 0xF7, 0x28, 0x05, + 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x06, + 0x00, 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, 0x03, + 0xC7, 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, 0x7D, + 0xFC, 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x02, 0x00, + 0x05, 0x00, 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, + 0xF3, 0x61, 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, + 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x1A, + 0x00, 0x8A, 0xFF, 0x51, 0x01, 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, + 0x82, 0x37, 0x60, 0x28, 0x0E, 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, + 0x01, 0x40, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x29, 0x00, + 0x5F, 0xFF, 0xA5, 0x01, 0x78, 0xFC, 0xFF, 0x06, 0x7D, 0xF1, 0xCF, + 0x30, 0xB8, 0x2F, 0x80, 0xF1, 0x0D, 0x07, 0x6A, 0xFC, 0xAE, 0x01, + 0x59, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x33, 0x00, 0x43, + 0xFF, 0xD6, 0x01, 0x39, 0xFC, 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, + 0x85, 0x36, 0xCC, 0xF1, 0x7F, 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, + 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE6, 0x01, 0x38, 0xFC, 0xE6, 0x06, 0x19, 0xF3, 0xEA, 0x21, 0x8A, + 0x3C, 0x0E, 0xF3, 0x78, 0x05, 0x96, 0xFD, 0xF1, 0x00, 0xBB, 0xFF, + 0x08, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD8, + 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, 0xF4, 0x38, 0x1A, 0x8C, 0x41, + 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, 0x66, 0x00, 0x01, 0x00, 0xEE, + 0xFF, 0x06, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x47, 0xFF, 0xAF, 0x01, + 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, 0x5C, 0x45, 0xA9, + 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, 0x00, 0xD0, 0xFF, + 0x0C, 0x00, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, 0x01, 0x65, + 0xFD, 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, 0x03, 0xFD, + 0xD9, 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, 0xFF, 0x14, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, 0xFE, + 0xDE, 0x02, 0x2E, 0xFC, 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, 0x5E, + 0xFD, 0x3F, 0x02, 0x5D, 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, 0x00, + 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, 0xFE, 0x86, 0x01, 0xBA, + 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, 0xC7, 0xFA, 0x93, 0x03, + 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0x0F, + 0x00, 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, 0x01, + 0x8B, 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, 0x15, + 0xFD, 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x08, 0x00, + 0xE1, 0xFF, 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, + 0xF6, 0x77, 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, + 0xC8, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0xFD, + 0xFF, 0xD9, 0xFF, 0xB4, 0x00, 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, + 0xFC, 0x3E, 0x5B, 0x1E, 0xDB, 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x14, 0x00, + 0x9B, 0xFF, 0x31, 0x01, 0x2C, 0xFD, 0x15, 0x06, 0x41, 0xF2, 0x6A, + 0x39, 0x0A, 0x26, 0x61, 0xF2, 0x17, 0x07, 0x31, 0xFC, 0xE2, 0x01, + 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x25, 0x00, 0x6A, + 0xFF, 0x8E, 0x01, 0x99, 0xFC, 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, + 0x82, 0x2D, 0x96, 0xF1, 0x21, 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, + 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, + 0xCA, 0x01, 0x46, 0xFC, 0x29, 0x07, 0xB3, 0xF1, 0xD1, 0x2B, 0x81, + 0x34, 0x9C, 0xF1, 0xB8, 0x06, 0xB5, 0xFC, 0x7C, 0x01, 0x74, 0xFF, + 0x22, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, + 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, 0xF2, 0x46, 0x24, 0xC8, 0x3A, + 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, 0x17, 0x01, 0xA8, 0xFF, 0x0F, + 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, + 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, 0x1F, 0x40, 0x85, + 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, 0xFF, 0xF7, 0xFF, + 0x04, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, 0x01, 0xB4, + 0xFC, 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, 0x86, 0xF7, + 0xB6, 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, 0xFF, 0x0A, + 0x00, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, 0xFD, + 0x89, 0x04, 0xCD, 0xF8, 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, 0x91, + 0x00, 0x83, 0x00, 0x4A, 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, 0x00, + 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, 0x01, 0xD6, 0xFD, 0x46, + 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, 0x98, 0x00, 0x26, 0xFE, + 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0x1A, + 0x00, 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, 0xFD, + 0x05, 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, 0xE4, + 0xFD, 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0x12, 0x00, + 0xB6, 0xFF, 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, + 0xFB, 0x69, 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, + 0x81, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xD7, + 0xFF, 0x3E, 0x00, 0xEA, 0xFF, 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, + 0x99, 0x44, 0x68, 0x14, 0x8E, 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, + 0x01, 0x43, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x04, 0x00, 0xF5, 0xFF, + 0xEF, 0xFF, 0x88, 0x00, 0x49, 0xFE, 0x5D, 0x04, 0xB7, 0xF4, 0x7D, + 0x40, 0xFD, 0x1B, 0x6C, 0xF4, 0x70, 0x06, 0x5F, 0xFC, 0xDE, 0x01, + 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAC, + 0xFF, 0x0E, 0x01, 0x66, 0xFD, 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, + 0xB0, 0x23, 0xC4, 0xF2, 0xFF, 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x21, 0x00, 0x77, 0xFF, + 0x75, 0x01, 0xBF, 0xFC, 0xAB, 0x06, 0xA6, 0xF1, 0x05, 0x35, 0x40, + 0x2B, 0xBF, 0xF1, 0x2A, 0x07, 0x42, 0xFC, 0xCE, 0x01, 0x48, 0xFF, + 0x31, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2E, 0x00, 0x52, 0xFF, 0xBC, + 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, 0xF1, 0x11, 0x2E, 0x6B, 0x32, + 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, 0x94, 0x01, 0x67, 0xFF, 0x26, + 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE0, 0x01, + 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, 0xF2, 0x38, 0x2A, + 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, 0xFF, 0x16, 0x00, + 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x48, + 0xFC, 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, 0xCF, 0xF3, + 0xF3, 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, 0xFF, 0x03, + 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, 0xFC, + 0xEF, 0x05, 0xB0, 0xF5, 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, 0x4D, + 0x03, 0xEF, 0xFE, 0x2A, 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, 0x00, + 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, 0x01, 0x0B, 0xFD, 0xE6, + 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, 0x37, 0xFA, 0x42, 0x01, + 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, 0xFF, 0x0F, 0x00, 0x00, + 0x00, 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, 0x03, + 0x94, 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, 0x6C, + 0x01, 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0x1D, 0x00, + 0x8A, 0xFF, 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, + 0x02, 0xF2, 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, + 0x1E, 0x01, 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0xAC, + 0xFF, 0xAE, 0x00, 0x05, 0xFF, 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, + 0xFD, 0x47, 0x0E, 0x0B, 0xC8, 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, + 0x01, 0x61, 0xFF, 0x28, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xCD, 0xFF, + 0x57, 0x00, 0xB6, 0xFF, 0xBE, 0xFF, 0xED, 0x01, 0xF5, 0xF8, 0x9B, + 0x45, 0x22, 0x12, 0x48, 0xF7, 0x3D, 0x05, 0xE2, 0xFC, 0xAB, 0x01, + 0x49, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x06, 0x00, 0xEC, 0xFF, 0x06, + 0x00, 0x5A, 0x00, 0x9A, 0xFE, 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, + 0xA1, 0x19, 0x09, 0xF5, 0x33, 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, + 0xE8, 0x00, 0xA6, 0xFD, 0x5F, 0x05, 0x31, 0xF3, 0xF6, 0x3C, 0x52, + 0x21, 0x37, 0xF3, 0xDD, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x1C, 0x00, 0x86, 0xFF, 0x59, + 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, 0xF1, 0x04, 0x37, 0xF3, 0x28, + 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, 0xD8, 0x01, 0x41, 0xFF, 0x33, + 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, + 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, + 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, + 0xFD, 0xFF, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, 0x01, 0x37, + 0xFC, 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, 0xDC, 0xF1, + 0x6F, 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, 0x00, 0xFE, + 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, 0xFC, + 0xDD, 0x06, 0x37, 0xF3, 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, 0x5F, + 0x05, 0xA6, 0xFD, 0xE8, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, 0x00, + 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, 0x01, 0x77, 0xFC, 0x33, + 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, 0x8D, 0xF5, 0xDA, 0x03, + 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0xFF, + 0xFF, 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, 0x05, + 0x48, 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, 0xBE, + 0xFF, 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, + 0xF9, 0x0E, 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, + 0x05, 0xFF, 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x7F, 0xFF, 0x1E, 0x01, 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, + 0x9B, 0x04, 0xF2, 0x48, 0xC6, 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, + 0xFE, 0x04, 0x01, 0x8A, 0xFF, 0x1D, 0x00, 0x17, 0x00, 0xA1, 0xFF, + 0xC9, 0x00, 0xCD, 0xFE, 0x6C, 0x01, 0xEA, 0xFE, 0xF3, 0xFE, 0x70, + 0x48, 0xFF, 0x08, 0x94, 0xFA, 0xAD, 0x03, 0xA3, 0xFD, 0x55, 0x01, + 0x6A, 0xFF, 0x26, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xC3, 0xFF, 0x71, + 0x00, 0x81, 0xFF, 0x1F, 0x00, 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, + 0xE7, 0x0F, 0x08, 0xF8, 0xE6, 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, + 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x08, 0x00, 0xE3, 0xFF, 0x1E, 0x00, + 0x2A, 0x00, 0xEF, 0xFE, 0x4D, 0x03, 0x7D, 0xF6, 0x2A, 0x43, 0x4B, + 0x17, 0xB0, 0xF5, 0xEF, 0x05, 0x93, 0xFC, 0xCB, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0xD4, 0xFF, 0xBF, + 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, 0xF3, 0x98, 0x3E, 0xF3, 0x1E, + 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x16, 0x00, 0x96, 0xFF, 0x39, 0x01, + 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, 0xA0, 0x26, 0x4B, + 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, 0xFF, 0x35, 0x00, + 0xFD, 0xFF, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, 0x01, 0x90, + 0xFC, 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, 0x8E, 0xF1, + 0x1D, 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, 0x00, 0xFD, + 0xFF, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, 0xFC, + 0x2A, 0x07, 0xBF, 0xF1, 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, 0xAB, + 0x06, 0xBF, 0xFC, 0x75, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, 0xFF, + 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0xFF, + 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, 0xAD, 0xF2, 0xBF, 0x05, + 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, 0x06, + 0x6C, 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, 0x49, + 0xFE, 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0xFF, 0xFF, + 0x32, 0x00, 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, + 0xF6, 0x68, 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, + 0xEA, 0xFF, 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x2B, + 0x00, 0x59, 0xFF, 0x81, 0x01, 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, + 0x2D, 0x0D, 0x69, 0x47, 0xEB, 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, + 0xFF, 0x93, 0x00, 0xB6, 0xFF, 0x12, 0x00, 0x00, 0x00, 0x22, 0x00, + 0x76, 0xFF, 0x37, 0x01, 0xE4, 0xFD, 0x2C, 0x03, 0x94, 0xFB, 0x83, + 0x06, 0xCE, 0x48, 0x05, 0x01, 0xF5, 0xFD, 0xF0, 0x01, 0x87, 0xFE, + 0xEA, 0x00, 0x94, 0xFF, 0x1A, 0x00, 0x1A, 0x00, 0x96, 0xFF, 0xE3, + 0x00, 0x95, 0xFE, 0xD5, 0x01, 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, + 0x00, 0x07, 0x61, 0xFB, 0x46, 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, + 0xFF, 0x23, 0x00, 0x00, 0x00, 0x11, 0x00, 0xB9, 0xFF, 0x8C, 0x00, + 0x4A, 0xFF, 0x83, 0x00, 0x91, 0x00, 0x91, 0xFB, 0x3D, 0x47, 0xB7, + 0x0D, 0xCD, 0xF8, 0x89, 0x04, 0x36, 0xFD, 0x86, 0x01, 0x57, 0xFF, + 0x2B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xD9, 0xFF, 0x37, 0x00, 0xF7, + 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, 0xF7, 0x53, 0x44, 0xFB, 0x14, + 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, 0xBE, 0x01, 0x42, 0xFF, 0x32, + 0x00, 0xFF, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xEA, 0xFF, 0x93, 0x00, + 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, 0x94, 0x1C, 0x47, + 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, 0x01, 0x57, + 0xFD, 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, 0xAA, 0xF2, + 0x06, 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0xFE, 0xFF, 0x22, 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, 0xFC, + 0xB8, 0x06, 0x9C, 0xF1, 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, 0x29, + 0x07, 0x46, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, 0xFF, + 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, 0x01, 0x53, 0xFC, 0x21, + 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, 0x86, 0xF1, 0xDB, 0x06, + 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, 0x07, + 0x61, 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, 0x2C, + 0xFD, 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, + 0xF3, 0x5B, 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, + 0xB4, 0x00, 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0x33, + 0x00, 0x3E, 0xFF, 0xC8, 0x01, 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, + 0xB6, 0x16, 0x77, 0x43, 0xBD, 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, + 0x00, 0x25, 0x00, 0xE1, 0xFF, 0x08, 0x00, 0xFF, 0xFF, 0x2D, 0x00, + 0x51, 0xFF, 0x95, 0x01, 0x15, 0xFD, 0xCF, 0x04, 0x39, 0xF8, 0x59, + 0x0F, 0xAF, 0x46, 0x8B, 0xFA, 0x17, 0x01, 0x38, 0x00, 0x73, 0xFF, + 0x78, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x25, 0x00, 0x6C, + 0xFF, 0x4F, 0x01, 0xB0, 0xFD, 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, + 0x86, 0x48, 0x5A, 0xFF, 0xBA, 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, + 0x00, 0x9E, 0xFF, 0x17, 0x00, 0x1C, 0x00, 0x8C, 0xFF, 0xFE, 0x00, + 0x5D, 0xFE, 0x3F, 0x02, 0x5E, 0xFD, 0x54, 0x02, 0xEC, 0x48, 0x13, + 0x05, 0x2E, 0xFC, 0xDE, 0x02, 0x0C, 0xFE, 0x24, 0x01, 0x7D, 0xFF, + 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0xAE, 0xFF, 0xA7, 0x00, 0x12, + 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, 0xFD, 0xDC, 0x47, 0x95, 0x0B, + 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, 0x71, 0x01, 0x5F, 0xFF, 0x29, + 0x00, 0x00, 0x00, 0x0C, 0x00, 0xD0, 0xFF, 0x51, 0x00, 0xC3, 0xFF, + 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, 0xB2, 0x12, 0x19, + 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, 0xFF, 0x30, 0x00, + 0xFF, 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, 0x00, 0x85, + 0xFE, 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, 0xE1, 0xF4, + 0x43, 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, + 0xFF, 0x01, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, 0xFD, + 0x78, 0x05, 0x0E, 0xF3, 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, 0xE6, + 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, 0x01, 0xE0, 0xFC, 0x7F, + 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, 0xEB, 0xF1, 0x2A, 0x07, + 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, 0x07, + 0x80, 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, 0x78, + 0xFC, 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, + 0x34, 0x00, 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, + 0xF2, 0x60, 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, + 0x51, 0x01, 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, + 0xBA, 0x20, 0x61, 0x3D, 0x56, 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, + 0x00, 0xC5, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x3A, 0xFF, 0xD3, 0x01, 0x7D, 0xFC, 0x23, 0x06, 0x32, 0xF5, 0x0C, + 0x19, 0x38, 0x42, 0xC7, 0xF5, 0xB8, 0x03, 0xAF, 0xFE, 0x4E, 0x00, + 0x0C, 0x00, 0xEA, 0xFF, 0x06, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, + 0xFF, 0xA7, 0x01, 0xEC, 0xFC, 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, + 0xD7, 0x45, 0x43, 0xF9, 0xC3, 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, + 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x28, 0x00, 0x63, 0xFF, + 0x66, 0x01, 0x7D, 0xFD, 0xF8, 0x03, 0xFB, 0xF9, 0x89, 0x0A, 0x1D, + 0x48, 0xC5, 0xFD, 0x7A, 0xFF, 0x1D, 0x01, 0xF7, 0xFE, 0xB4, 0x00, + 0xA9, 0xFF, 0x15, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x82, 0xFF, 0x18, + 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, 0xFC, 0x24, 0x04, 0xF5, 0x48, + 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, 0x42, 0xFE, 0x0B, 0x01, 0x87, + 0xFF, 0x1E, 0x00, 0x16, 0x00, 0xA4, 0xFF, 0xC2, 0x00, 0xDB, 0xFE, + 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, 0x81, 0x09, 0x61, + 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, 0xFF, 0x26, 0x00, + 0x00, 0x00, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, 0xFF, 0x06, + 0x00, 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, 0xD7, 0xF7, + 0xFC, 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, + 0xFF, 0x07, 0x00, 0xE5, 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, 0xFE, + 0x71, 0x03, 0x3F, 0xF6, 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, 0x00, + 0x06, 0x8C, 0xFC, 0xCE, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, + 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, 0x00, 0xDA, 0xFD, 0x0F, + 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, 0x97, 0xF3, 0xBD, 0x06, + 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, + 0xFF, 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, 0x06, + 0x14, 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, 0x33, + 0xFC, 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, + 0x28, 0x00, 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, + 0xF1, 0xE3, 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, + 0xB7, 0x01, 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x32, + 0x00, 0x46, 0xFF, 0xD1, 0x01, 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, + 0xAE, 0x2A, 0x86, 0x35, 0xB1, 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, + 0x01, 0x7B, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF7, 0x06, 0xE0, 0xF2, 0x18, + 0x23, 0xAB, 0x3B, 0xCC, 0xF2, 0xA8, 0x05, 0x76, 0xFD, 0x04, 0x01, + 0xB1, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, + 0xFF, 0xDC, 0x01, 0x64, 0xFC, 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, + 0xD9, 0x40, 0xEA, 0xF4, 0x3E, 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, + 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x44, 0xFF, + 0xB7, 0x01, 0xC5, 0xFC, 0x7C, 0x05, 0xBC, 0xF6, 0xD5, 0x13, 0xDC, + 0x44, 0x14, 0xF8, 0x67, 0x02, 0x77, 0xFF, 0xDD, 0xFF, 0x44, 0x00, + 0xD5, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5B, 0xFF, 0x7C, + 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, 0xF9, 0xA4, 0x0C, 0x90, 0x47, + 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, 0x2E, 0xFF, 0x99, 0x00, 0xB3, + 0xFF, 0x12, 0x00, 0x00, 0x00, 0x22, 0x00, 0x78, 0xFF, 0x31, 0x01, + 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, 0xDB, 0x48, 0x73, + 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, 0x00, 0x91, 0xFF, + 0x1B, 0x00, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, 0xFE, 0xBB, + 0x01, 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, 0x2E, 0xFB, + 0x60, 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, 0x00, 0x00, + 0x00, 0x10, 0x00, 0xBB, 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, 0x00, + 0xBE, 0x00, 0x38, 0xFB, 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, 0xA1, + 0x04, 0x2B, 0xFD, 0x8B, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, + 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, 0x00, 0x32, 0xFF, 0xDC, + 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, 0x34, 0xF6, 0xB7, 0x05, + 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x04, + 0x00, 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, 0x04, + 0x55, 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, 0x55, + 0xFC, 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, 0x00, + 0x11, 0x00, 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, + 0xF2, 0x54, 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, + 0xE4, 0x01, 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x23, + 0x00, 0x71, 0xFF, 0x82, 0x01, 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, + 0xFD, 0x33, 0x62, 0x2C, 0xA8, 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, + 0x01, 0x4C, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2F, 0x00, + 0x4E, 0xFF, 0xC3, 0x01, 0x4E, 0xFC, 0x24, 0x07, 0x9E, 0xF1, 0xF2, + 0x2C, 0x78, 0x33, 0x8C, 0xF1, 0xD0, 0x06, 0xA2, 0xFC, 0x88, 0x01, + 0x6D, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x35, 0x00, 0x3B, + 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, + 0xDF, 0x39, 0x5A, 0xF2, 0x00, 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, + 0xFF, 0x13, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE2, 0x01, 0x50, 0xFC, 0x99, 0x06, 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, + 0x3F, 0x27, 0xF4, 0xB9, 0x04, 0x10, 0xFE, 0xA9, 0x00, 0xDF, 0xFF, + 0xFB, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC5, + 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, 0xF6, 0x22, 0x16, 0xC3, 0x43, + 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, 0x11, 0x00, 0x2B, 0x00, 0xDE, + 0xFF, 0x09, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0x90, 0x01, + 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, 0xE1, 0x46, 0xE1, + 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, 0x00, 0xBE, 0xFF, + 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, 0x01, 0xBC, + 0xFD, 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, 0xC3, 0xFF, + 0x89, 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, 0xFF, 0x18, + 0x00, 0x1C, 0x00, 0x8F, 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, 0x02, + 0x91, 0xFD, 0xE3, 0x01, 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, 0xF8, + 0x02, 0xFE, 0xFD, 0x2B, 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, 0x00, + 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, 0xFF, 0xD0, 0x00, 0x07, + 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, 0x63, 0xF9, 0x42, 0x04, + 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x0B, + 0x00, 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, 0x02, + 0x5E, 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, 0xCF, + 0xFC, 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x05, 0x00, + 0xF0, 0xFF, 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, + 0xF5, 0x32, 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, + 0xDA, 0x01, 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, + 0x00, 0xB6, 0xFF, 0xFB, 0x00, 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, + 0x1C, 0x3C, 0x81, 0x22, 0xFC, 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x1E, 0x00, + 0x7F, 0xFF, 0x67, 0x01, 0xD5, 0xFC, 0x8E, 0x06, 0xBE, 0xF1, 0x06, + 0x36, 0x1A, 0x2A, 0xDC, 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, + 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2C, 0x00, 0x57, + 0xFF, 0xB3, 0x01, 0x64, 0xFC, 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, + 0x5A, 0x31, 0x7D, 0xF1, 0xF7, 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, + 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, + 0xDD, 0x01, 0x34, 0xFC, 0x23, 0x07, 0x21, 0xF2, 0xCB, 0x27, 0xFE, + 0x37, 0x00, 0xF2, 0x4D, 0x06, 0x04, 0xFD, 0x49, 0x01, 0x8E, 0xFF, + 0x19, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, + 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, 0xF3, 0x22, 0x20, 0xCA, 0x3D, + 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, 0xD4, 0x00, 0xCA, 0xFF, 0x03, + 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD1, 0x01, + 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, 0x89, 0x42, 0x02, + 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, 0x00, 0xE8, 0xFF, + 0x07, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, 0x01, 0xF6, + 0xFC, 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, 0x93, 0xF9, + 0x98, 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, 0xFF, 0x0E, + 0x00, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, 0xFD, + 0xDF, 0x03, 0x2E, 0xFA, 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, 0x4B, + 0xFF, 0x38, 0x01, 0xE9, 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, 0x00, + 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, + 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, + 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x00, + 0x00, 0xF4, 0xFF, 0x1A, 0x00, 0xFF, 0x00, 0x07, 0x03, 0x16, 0x06, + 0x7C, 0x09, 0x2A, 0x0C, 0x2E, 0x0D, 0x2A, 0x0C, 0x7C, 0x09, 0x16, + 0x06, 0x07, 0x03, 0xFF, 0x00, 0x1A, 0x00, 0xF4, 0xFF, 0xF2, 0xFF, + 0xA0, 0xFF, 0x71, 0xFF, 0x71, 0x00, 0x86, 0x03, 0x73, 0x08, 0x88, + 0x0D, 0x78, 0x10, 0xC9, 0x0F, 0xD5, 0x0B, 0x8B, 0x06, 0x28, 0x02, + 0xDF, 0xFF, 0x6F, 0xFF, 0xC3, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDC, + 0xFF, 0x80, 0xFF, 0x9A, 0xFF, 0x46, 0x01, 0x1E, 0x05, 0x5A, 0x0A, + 0xED, 0x0E, 0xAA, 0x10, 0xAF, 0x0E, 0xFD, 0x09, 0xCB, 0x04, 0x18, + 0x01, 0x8E, 0xFF, 0x85, 0xFF, 0xE1, 0xFF, 0xFC, 0xFF, 0xBD, 0xFF, + 0x6D, 0xFF, 0xF6, 0xFF, 0x65, 0x02, 0xE5, 0x06, 0x2B, 0x0C, 0xF3, + 0x0F, 0x60, 0x10, 0x3B, 0x0D, 0x16, 0x08, 0x3F, 0x03, 0x50, 0x00, + 0x6E, 0xFF, 0xA7, 0xFF, 0xF5, 0xFF, 0xEF, 0xFF, 0x9A, 0xFF, 0x75, + 0xFF, 0x91, 0x00, 0xC9, 0x03, 0xC8, 0x08, 0xCC, 0x0D, 0x89, 0x10, + 0x9F, 0x0F, 0x85, 0x0B, 0x3B, 0x06, 0xF4, 0x01, 0xCD, 0xFF, 0x72, + 0xFF, 0xC9, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD7, 0xFF, 0x7B, 0xFF, + 0xA5, 0xFF, 0x73, 0x01, 0x6A, 0x05, 0xAD, 0x0A, 0x21, 0x0F, 0xA6, + 0x10, 0x74, 0x0E, 0xA9, 0x09, 0x83, 0x04, 0xF0, 0x00, 0x85, 0xFF, + 0x8B, 0xFF, 0xE5, 0xFF, 0xFA, 0xFF, 0xB7, 0xFF, 0x6C, 0xFF, 0x0C, + 0x00, 0x9D, 0x02, 0x37, 0x07, 0x78, 0x0C, 0x15, 0x10, 0x47, 0x10, + 0xF3, 0x0C, 0xC2, 0x07, 0x01, 0x03, 0x35, 0x00, 0x6D, 0xFF, 0xAD, + 0xFF, 0xF7, 0xFF, 0xEB, 0xFF, 0x94, 0xFF, 0x7A, 0xFF, 0xB3, 0x00, + 0x0D, 0x04, 0x1C, 0x09, 0x0D, 0x0E, 0x97, 0x10, 0x73, 0x0F, 0x35, + 0x0B, 0xEB, 0x05, 0xC1, 0x01, 0xBD, 0xFF, 0x75, 0xFF, 0xCE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0x77, 0xFF, 0xB3, 0xFF, 0xA1, + 0x01, 0xB7, 0x05, 0xFF, 0x0A, 0x53, 0x0F, 0x9E, 0x10, 0x37, 0x0E, + 0x55, 0x09, 0x3B, 0x04, 0xCB, 0x00, 0x7E, 0xFF, 0x90, 0xFF, 0xE9, + 0xFF, 0xF8, 0xFF, 0xB1, 0xFF, 0x6C, 0xFF, 0x24, 0x00, 0xD8, 0x02, + 0x8A, 0x07, 0xC2, 0x0C, 0x34, 0x10, 0x2A, 0x10, 0xAA, 0x0C, 0x6F, + 0x07, 0xC4, 0x02, 0x1C, 0x00, 0x6C, 0xFF, 0xB3, 0xFF, 0xF9, 0xFF, + 0xE8, 0xFF, 0x8E, 0xFF, 0x80, 0xFF, 0xD7, 0x00, 0x53, 0x04, 0x71, + 0x09, 0x4C, 0x0E, 0xA1, 0x10, 0x43, 0x0F, 0xE3, 0x0A, 0x9D, 0x05, + 0x91, 0x01, 0xAE, 0xFF, 0x79, 0xFF, 0xD4, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0xCD, 0xFF, 0x74, 0xFF, 0xC2, 0xFF, 0xD2, 0x01, 0x06, 0x06, + 0x50, 0x0B, 0x82, 0x0F, 0x93, 0x10, 0xF8, 0x0D, 0x00, 0x09, 0xF6, + 0x03, 0xA7, 0x00, 0x78, 0xFF, 0x96, 0xFF, 0xEC, 0xFF, 0xF6, 0xFF, + 0xAB, 0xFF, 0x6D, 0xFF, 0x3E, 0x00, 0x15, 0x03, 0xDE, 0x07, 0x0B, + 0x0D, 0x50, 0x10, 0x0A, 0x10, 0x5E, 0x0C, 0x1C, 0x07, 0x8A, 0x02, + 0x04, 0x00, 0x6C, 0xFF, 0xB9, 0xFF, 0xFB, 0xFF, 0xE4, 0xFF, 0x89, + 0xFF, 0x88, 0xFF, 0xFD, 0x00, 0x9B, 0x04, 0xC5, 0x09, 0x88, 0x0E, + 0xA8, 0x10, 0x10, 0x0F, 0x91, 0x0A, 0x50, 0x05, 0x64, 0x01, 0xA1, + 0xFF, 0x7D, 0xFF, 0xD9, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC7, 0xFF, + 0x71, 0xFF, 0xD3, 0xFF, 0x05, 0x02, 0x55, 0x06, 0xA0, 0x0B, 0xAD, + 0x0F, 0x84, 0x10, 0xB6, 0x0D, 0xAC, 0x08, 0xB3, 0x03, 0x86, 0x00, + 0x74, 0xFF, 0x9C, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xA5, 0xFF, 0x6F, + 0xFF, 0x5A, 0x00, 0x54, 0x03, 0x32, 0x08, 0x52, 0x0D, 0x68, 0x10, + 0xE6, 0x0F, 0x11, 0x0C, 0xCA, 0x06, 0x52, 0x02, 0xEF, 0xFF, 0x6E, + 0xFF, 0xBF, 0xFF, 0xFC, 0xFF, 0xDF, 0xFF, 0x84, 0xFF, 0x91, 0xFF, + 0x25, 0x01, 0xE4, 0x04, 0x19, 0x0A, 0xC2, 0x0E, 0xAA, 0x10, 0xDA, + 0x0E, 0x3E, 0x0A, 0x05, 0x05, 0x38, 0x01, 0x96, 0xFF, 0x81, 0xFF, + 0xDD, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC1, 0xFF, 0x6E, 0xFF, 0xE6, + 0xFF, 0x3A, 0x02, 0xA6, 0x06, 0xEF, 0x0B, 0xD6, 0x0F, 0x71, 0x10, + 0x71, 0x0D, 0x57, 0x08, 0x71, 0x03, 0x67, 0x00, 0x70, 0xFF, 0xA2, + 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0x9F, 0xFF, 0x72, 0xFF, 0x78, 0x00, + 0x95, 0x03, 0x86, 0x08, 0x98, 0x0D, 0x7C, 0x10, 0xC0, 0x0F, 0xC3, + 0x0B, 0x79, 0x06, 0x1C, 0x02, 0xDB, 0xFF, 0x70, 0xFF, 0xC5, 0xFF, + 0xFE, 0xFF, 0x00, 0x00, 0xDB, 0xFF, 0x7F, 0xFF, 0x9C, 0xFF, 0x50, + 0x01, 0x2F, 0x05, 0x6C, 0x0A, 0xF9, 0x0E, 0xA9, 0x10, 0xA2, 0x0E, + 0xEA, 0x09, 0xBB, 0x04, 0x0F, 0x01, 0x8C, 0xFF, 0x87, 0xFF, 0xE2, + 0xFF, 0xFC, 0xFF, 0xBC, 0xFF, 0x6D, 0xFF, 0xFA, 0xFF, 0x71, 0x02, + 0xF7, 0x06, 0x3C, 0x0C, 0xFB, 0x0F, 0x5B, 0x10, 0x2B, 0x0D, 0x03, + 0x08, 0x31, 0x03, 0x4A, 0x00, 0x6E, 0xFF, 0xA8, 0xFF, 0xF5, 0xFF, + 0xEE, 0xFF, 0x99, 0xFF, 0x76, 0xFF, 0x98, 0x00, 0xD8, 0x03, 0xDB, + 0x08, 0xDB, 0x0D, 0x8D, 0x10, 0x96, 0x0F, 0x73, 0x0B, 0x29, 0x06, + 0xE8, 0x01, 0xC9, 0xFF, 0x72, 0xFF, 0xCA, 0xFF, 0xFE, 0xFF, 0x00, + 0x00, 0xD6, 0xFF, 0x7A, 0xFF, 0xA8, 0xFF, 0x7D, 0x01, 0x7B, 0x05, + 0xBF, 0x0A, 0x2D, 0x0F, 0xA5, 0x10, 0x67, 0x0E, 0x96, 0x09, 0x73, + 0x04, 0xE7, 0x00, 0x84, 0xFF, 0x8C, 0xFF, 0xE6, 0xFF, 0xFA, 0xFF, + 0xB6, 0xFF, 0x6C, 0xFF, 0x11, 0x00, 0xAA, 0x02, 0x4A, 0x07, 0x88, + 0x0C, 0x1C, 0x10, 0x41, 0x10, 0xE3, 0x0C, 0xAF, 0x07, 0xF3, 0x02, + 0x2F, 0x00, 0x6C, 0xFF, 0xAE, 0xFF, 0xF7, 0xFF, 0xEA, 0xFF, 0x93, + 0xFF, 0x7B, 0xFF, 0xBB, 0x00, 0x1C, 0x04, 0x2F, 0x09, 0x1B, 0x0E, + 0x9A, 0x10, 0x68, 0x0F, 0x23, 0x0B, 0xDA, 0x05, 0xB7, 0x01, 0xB9, + 0xFF, 0x76, 0xFF, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0xFF, + 0x76, 0xFF, 0xB6, 0xFF, 0xAC, 0x01, 0xC8, 0x05, 0x11, 0x0B, 0x5E, + 0x0F, 0x9C, 0x10, 0x29, 0x0E, 0x42, 0x09, 0x2C, 0x04, 0xC2, 0x00, + 0x7D, 0xFF, 0x92, 0xFF, 0xEA, 0xFF, 0xF8, 0xFF, 0xB0, 0xFF, 0x6C, + 0xFF, 0x29, 0x00, 0xE6, 0x02, 0x9D, 0x07, 0xD3, 0x0C, 0x3B, 0x10, + 0x23, 0x10, 0x99, 0x0C, 0x5C, 0x07, 0xB7, 0x02, 0x16, 0x00, 0x6C, + 0xFF, 0xB4, 0xFF, 0xF9, 0xFF, 0xE7, 0xFF, 0x8D, 0xFF, 0x82, 0xFF, + 0xDF, 0x00, 0x63, 0x04, 0x84, 0x09, 0x59, 0x0E, 0xA3, 0x10, 0x38, + 0x0F, 0xD1, 0x0A, 0x8C, 0x05, 0x87, 0x01, 0xAB, 0xFF, 0x79, 0xFF, + 0xD5, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCB, 0xFF, 0x73, 0xFF, 0xC6, + 0xFF, 0xDD, 0x01, 0x17, 0x06, 0x62, 0x0B, 0x8C, 0x0F, 0x90, 0x10, + 0xE9, 0x0D, 0xED, 0x08, 0xE7, 0x03, 0xA0, 0x00, 0x77, 0xFF, 0x97, + 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xA9, 0xFF, 0x6D, 0xFF, 0x44, 0x00, + 0x23, 0x03, 0xF1, 0x07, 0x1B, 0x0D, 0x55, 0x10, 0x02, 0x10, 0x4D, + 0x0C, 0x0A, 0x07, 0x7E, 0x02, 0xFF, 0xFF, 0x6D, 0xFF, 0xBA, 0xFF, + 0xFB, 0xFF, 0xE3, 0xFF, 0x88, 0xFF, 0x8A, 0xFF, 0x06, 0x01, 0xAB, + 0x04, 0xD8, 0x09, 0x95, 0x0E, 0xA9, 0x10, 0x05, 0x0F, 0x7F, 0x0A, + 0x40, 0x05, 0x5A, 0x01, 0x9F, 0xFF, 0x7E, 0xFF, 0xDA, 0xFF, 0x00, + 0x00, 0xFE, 0xFF, 0xC6, 0xFF, 0x70, 0xFF, 0xD7, 0xFF, 0x10, 0x02, + 0x67, 0x06, 0xB1, 0x0B, 0xB7, 0x0F, 0x80, 0x10, 0xA7, 0x0D, 0x99, + 0x08, 0xA4, 0x03, 0x7F, 0x00, 0x73, 0xFF, 0x9D, 0xFF, 0xF0, 0xFF, + 0xF3, 0xFF, 0xA3, 0xFF, 0x70, 0xFF, 0x60, 0x00, 0x62, 0x03, 0x45, + 0x08, 0x62, 0x0D, 0x6C, 0x10, 0xDE, 0x0F, 0x00, 0x0C, 0xB8, 0x06, + 0x46, 0x02, 0xEA, 0xFF, 0x6E, 0xFF, 0xC0, 0xFF, 0xFD, 0xFF, 0x00, + 0x00, 0xDE, 0xFF, 0x83, 0xFF, 0x94, 0xFF, 0x2F, 0x01, 0xF4, 0x04, + 0x2B, 0x0A, 0xCE, 0x0E, 0xAA, 0x10, 0xCE, 0x0E, 0x2B, 0x0A, 0xF4, + 0x04, 0x2F, 0x01, 0x94, 0xFF, 0x83, 0xFF, 0xDE, 0xFF, 0xFD, 0xFF, + 0xC0, 0xFF, 0x6E, 0xFF, 0xEA, 0xFF, 0x46, 0x02, 0xB8, 0x06, 0x00, + 0x0C, 0xDE, 0x0F, 0x6C, 0x10, 0x62, 0x0D, 0x45, 0x08, 0x62, 0x03, + 0x60, 0x00, 0x70, 0xFF, 0xA3, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0x9D, + 0xFF, 0x73, 0xFF, 0x7F, 0x00, 0xA4, 0x03, 0x99, 0x08, 0xA7, 0x0D, + 0x80, 0x10, 0xB7, 0x0F, 0xB1, 0x0B, 0x67, 0x06, 0x10, 0x02, 0xD7, + 0xFF, 0x70, 0xFF, 0xC6, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xDA, 0xFF, + 0x7E, 0xFF, 0x9F, 0xFF, 0x5A, 0x01, 0x40, 0x05, 0x7F, 0x0A, 0x05, + 0x0F, 0xA9, 0x10, 0x95, 0x0E, 0xD8, 0x09, 0xAB, 0x04, 0x06, 0x01, + 0x8A, 0xFF, 0x88, 0xFF, 0xE3, 0xFF, 0xFB, 0xFF, 0xBA, 0xFF, 0x6D, + 0xFF, 0xFF, 0xFF, 0x7E, 0x02, 0x0A, 0x07, 0x4D, 0x0C, 0x02, 0x10, + 0x55, 0x10, 0x1B, 0x0D, 0xF1, 0x07, 0x23, 0x03, 0x44, 0x00, 0x6D, + 0xFF, 0xA9, 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0x97, 0xFF, 0x77, 0xFF, + 0xA0, 0x00, 0xE7, 0x03, 0xED, 0x08, 0xE9, 0x0D, 0x90, 0x10, 0x8C, + 0x0F, 0x62, 0x0B, 0x17, 0x06, 0xDD, 0x01, 0xC6, 0xFF, 0x73, 0xFF, + 0xCB, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD5, 0xFF, 0x79, 0xFF, 0xAB, + 0xFF, 0x87, 0x01, 0x8C, 0x05, 0xD1, 0x0A, 0x38, 0x0F, 0xA3, 0x10, + 0x59, 0x0E, 0x84, 0x09, 0x63, 0x04, 0xDF, 0x00, 0x82, 0xFF, 0x8D, + 0xFF, 0xE7, 0xFF, 0xF9, 0xFF, 0xB4, 0xFF, 0x6C, 0xFF, 0x16, 0x00, + 0xB7, 0x02, 0x5C, 0x07, 0x99, 0x0C, 0x23, 0x10, 0x3B, 0x10, 0xD3, + 0x0C, 0x9D, 0x07, 0xE6, 0x02, 0x29, 0x00, 0x6C, 0xFF, 0xB0, 0xFF, + 0xF8, 0xFF, 0xEA, 0xFF, 0x92, 0xFF, 0x7D, 0xFF, 0xC2, 0x00, 0x2C, + 0x04, 0x42, 0x09, 0x29, 0x0E, 0x9C, 0x10, 0x5E, 0x0F, 0x11, 0x0B, + 0xC8, 0x05, 0xAC, 0x01, 0xB6, 0xFF, 0x76, 0xFF, 0xD1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0x76, 0xFF, 0xB9, 0xFF, 0xB7, 0x01, + 0xDA, 0x05, 0x23, 0x0B, 0x68, 0x0F, 0x9A, 0x10, 0x1B, 0x0E, 0x2F, + 0x09, 0x1C, 0x04, 0xBB, 0x00, 0x7B, 0xFF, 0x93, 0xFF, 0xEA, 0xFF, + 0xF7, 0xFF, 0xAE, 0xFF, 0x6C, 0xFF, 0x2F, 0x00, 0xF3, 0x02, 0xAF, + 0x07, 0xE3, 0x0C, 0x41, 0x10, 0x1C, 0x10, 0x88, 0x0C, 0x4A, 0x07, + 0xAA, 0x02, 0x11, 0x00, 0x6C, 0xFF, 0xB6, 0xFF, 0xFA, 0xFF, 0xE6, + 0xFF, 0x8C, 0xFF, 0x84, 0xFF, 0xE7, 0x00, 0x73, 0x04, 0x96, 0x09, + 0x67, 0x0E, 0xA5, 0x10, 0x2D, 0x0F, 0xBF, 0x0A, 0x7B, 0x05, 0x7D, + 0x01, 0xA8, 0xFF, 0x7A, 0xFF, 0xD6, 0xFF, 0x00, 0x00, 0xFE, 0xFF, + 0xCA, 0xFF, 0x72, 0xFF, 0xC9, 0xFF, 0xE8, 0x01, 0x29, 0x06, 0x73, + 0x0B, 0x96, 0x0F, 0x8D, 0x10, 0xDB, 0x0D, 0xDB, 0x08, 0xD8, 0x03, + 0x98, 0x00, 0x76, 0xFF, 0x99, 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xA8, + 0xFF, 0x6E, 0xFF, 0x4A, 0x00, 0x31, 0x03, 0x03, 0x08, 0x2B, 0x0D, + 0x5B, 0x10, 0xFB, 0x0F, 0x3C, 0x0C, 0xF7, 0x06, 0x71, 0x02, 0xFA, + 0xFF, 0x6D, 0xFF, 0xBC, 0xFF, 0xFC, 0xFF, 0xE2, 0xFF, 0x87, 0xFF, + 0x8C, 0xFF, 0x0F, 0x01, 0xBB, 0x04, 0xEA, 0x09, 0xA2, 0x0E, 0xA9, + 0x10, 0xF9, 0x0E, 0x6C, 0x0A, 0x2F, 0x05, 0x50, 0x01, 0x9C, 0xFF, + 0x7F, 0xFF, 0xDB, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC5, 0xFF, 0x70, + 0xFF, 0xDB, 0xFF, 0x1C, 0x02, 0x79, 0x06, 0xC3, 0x0B, 0xC0, 0x0F, + 0x7C, 0x10, 0x98, 0x0D, 0x86, 0x08, 0x95, 0x03, 0x78, 0x00, 0x72, + 0xFF, 0x9F, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xA2, 0xFF, 0x70, 0xFF, + 0x67, 0x00, 0x71, 0x03, 0x57, 0x08, 0x71, 0x0D, 0x71, 0x10, 0xD6, + 0x0F, 0xEF, 0x0B, 0xA6, 0x06, 0x3A, 0x02, 0xE6, 0xFF, 0x6E, 0xFF, + 0xC1, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDD, 0xFF, 0x81, 0xFF, 0x96, + 0xFF, 0x38, 0x01, 0x05, 0x05, 0x3E, 0x0A, 0xDA, 0x0E, 0xAA, 0x10, + 0xC2, 0x0E, 0x19, 0x0A, 0xE4, 0x04, 0x25, 0x01, 0x91, 0xFF, 0x84, + 0xFF, 0xDF, 0xFF, 0xFC, 0xFF, 0xBF, 0xFF, 0x6E, 0xFF, 0xEF, 0xFF, + 0x52, 0x02, 0xCA, 0x06, 0x11, 0x0C, 0xE6, 0x0F, 0x68, 0x10, 0x52, + 0x0D, 0x32, 0x08, 0x54, 0x03, 0x5A, 0x00, 0x6F, 0xFF, 0xA5, 0xFF, + 0xF4, 0xFF, 0xF0, 0xFF, 0x9C, 0xFF, 0x74, 0xFF, 0x86, 0x00, 0xB3, + 0x03, 0xAC, 0x08, 0xB6, 0x0D, 0x84, 0x10, 0xAD, 0x0F, 0xA0, 0x0B, + 0x55, 0x06, 0x05, 0x02, 0xD3, 0xFF, 0x71, 0xFF, 0xC7, 0xFF, 0xFE, + 0xFF, 0x00, 0x00, 0xD9, 0xFF, 0x7D, 0xFF, 0xA1, 0xFF, 0x64, 0x01, + 0x50, 0x05, 0x91, 0x0A, 0x10, 0x0F, 0xA8, 0x10, 0x88, 0x0E, 0xC5, + 0x09, 0x9B, 0x04, 0xFD, 0x00, 0x88, 0xFF, 0x89, 0xFF, 0xE4, 0xFF, + 0xFB, 0xFF, 0xB9, 0xFF, 0x6C, 0xFF, 0x04, 0x00, 0x8A, 0x02, 0x1C, + 0x07, 0x5E, 0x0C, 0x0A, 0x10, 0x50, 0x10, 0x0B, 0x0D, 0xDE, 0x07, + 0x15, 0x03, 0x3E, 0x00, 0x6D, 0xFF, 0xAB, 0xFF, 0xF6, 0xFF, 0xEC, + 0xFF, 0x96, 0xFF, 0x78, 0xFF, 0xA7, 0x00, 0xF6, 0x03, 0x00, 0x09, + 0xF8, 0x0D, 0x93, 0x10, 0x82, 0x0F, 0x50, 0x0B, 0x06, 0x06, 0xD2, + 0x01, 0xC2, 0xFF, 0x74, 0xFF, 0xCD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xD4, 0xFF, 0x79, 0xFF, 0xAE, 0xFF, 0x91, 0x01, 0x9D, 0x05, 0xE3, + 0x0A, 0x43, 0x0F, 0xA1, 0x10, 0x4C, 0x0E, 0x71, 0x09, 0x53, 0x04, + 0xD7, 0x00, 0x80, 0xFF, 0x8E, 0xFF, 0xE8, 0xFF, 0xF9, 0xFF, 0xB3, + 0xFF, 0x6C, 0xFF, 0x1C, 0x00, 0xC4, 0x02, 0x6F, 0x07, 0xAA, 0x0C, + 0x2A, 0x10, 0x34, 0x10, 0xC2, 0x0C, 0x8A, 0x07, 0xD8, 0x02, 0x24, + 0x00, 0x6C, 0xFF, 0xB1, 0xFF, 0xF8, 0xFF, 0xE9, 0xFF, 0x90, 0xFF, + 0x7E, 0xFF, 0xCB, 0x00, 0x3B, 0x04, 0x55, 0x09, 0x37, 0x0E, 0x9E, + 0x10, 0x53, 0x0F, 0xFF, 0x0A, 0xB7, 0x05, 0xA1, 0x01, 0xB3, 0xFF, + 0x77, 0xFF, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0x75, + 0xFF, 0xBD, 0xFF, 0xC1, 0x01, 0xEB, 0x05, 0x35, 0x0B, 0x73, 0x0F, + 0x97, 0x10, 0x0D, 0x0E, 0x1C, 0x09, 0x0D, 0x04, 0xB3, 0x00, 0x7A, + 0xFF, 0x94, 0xFF, 0xEB, 0xFF, 0xF7, 0xFF, 0xAD, 0xFF, 0x6D, 0xFF, + 0x35, 0x00, 0x01, 0x03, 0xC2, 0x07, 0xF3, 0x0C, 0x47, 0x10, 0x15, + 0x10, 0x78, 0x0C, 0x37, 0x07, 0x9D, 0x02, 0x0C, 0x00, 0x6C, 0xFF, + 0xB7, 0xFF, 0xFA, 0xFF, 0xE5, 0xFF, 0x8B, 0xFF, 0x85, 0xFF, 0xF0, + 0x00, 0x83, 0x04, 0xA9, 0x09, 0x74, 0x0E, 0xA6, 0x10, 0x21, 0x0F, + 0xAD, 0x0A, 0x6A, 0x05, 0x73, 0x01, 0xA5, 0xFF, 0x7B, 0xFF, 0xD7, + 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC9, 0xFF, 0x72, 0xFF, 0xCD, 0xFF, + 0xF4, 0x01, 0x3B, 0x06, 0x85, 0x0B, 0x9F, 0x0F, 0x89, 0x10, 0xCC, + 0x0D, 0xC8, 0x08, 0xC9, 0x03, 0x91, 0x00, 0x75, 0xFF, 0x9A, 0xFF, + 0xEF, 0xFF, 0xF5, 0xFF, 0xA7, 0xFF, 0x6E, 0xFF, 0x50, 0x00, 0x3F, + 0x03, 0x16, 0x08, 0x3B, 0x0D, 0x60, 0x10, 0xF3, 0x0F, 0x2B, 0x0C, + 0xE5, 0x06, 0x65, 0x02, 0xF6, 0xFF, 0x6D, 0xFF, 0xBD, 0xFF, 0xFC, + 0xFF, 0xE1, 0xFF, 0x85, 0xFF, 0x8E, 0xFF, 0x18, 0x01, 0xCB, 0x04, + 0xFD, 0x09, 0xAF, 0x0E, 0xAA, 0x10, 0xED, 0x0E, 0x5A, 0x0A, 0x1E, + 0x05, 0x46, 0x01, 0x9A, 0xFF, 0x80, 0xFF, 0xDC, 0xFF, 0x00, 0x00, + 0xFD, 0xFF, 0xC3, 0xFF, 0x6F, 0xFF, 0xDF, 0xFF, 0x28, 0x02, 0x8B, + 0x06, 0xD5, 0x0B, 0xC9, 0x0F, 0x78, 0x10, 0x88, 0x0D, 0x73, 0x08, + 0x86, 0x03, 0x71, 0x00, 0x71, 0xFF, 0xA0, 0xFF, 0xF2, 0xFF, 0xF2, + 0xFF, 0xA1, 0xFF, 0x71, 0xFF, 0x6E, 0x00, 0x7F, 0x03, 0x6A, 0x08, + 0x81, 0x0D, 0x76, 0x10, 0xCD, 0x0F, 0xDD, 0x0B, 0x94, 0x06, 0x2E, + 0x02, 0xE1, 0xFF, 0x6F, 0xFF, 0xC3, 0xFF, 0xFD, 0xFF, 0x00, 0x00, + 0xDC, 0xFF, 0x80, 0xFF, 0x98, 0xFF, 0x42, 0x01, 0x16, 0x05, 0x50, + 0x0A, 0xE7, 0x0E, 0xAA, 0x10, 0xB5, 0x0E, 0x06, 0x0A, 0xD3, 0x04, + 0x1C, 0x01, 0x8F, 0xFF, 0x85, 0xFF, 0xE0, 0xFF, 0xFC, 0xFF, 0xBE, + 0xFF, 0x6D, 0xFF, 0xF3, 0xFF, 0x5E, 0x02, 0xDC, 0x06, 0x23, 0x0C, + 0xEF, 0x0F, 0x63, 0x10, 0x43, 0x0D, 0x1F, 0x08, 0x46, 0x03, 0x53, + 0x00, 0x6E, 0xFF, 0xA6, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0x9B, 0xFF, + 0x75, 0xFF, 0x8D, 0x00, 0xC1, 0x03, 0xBE, 0x08, 0xC4, 0x0D, 0x88, + 0x10, 0xA4, 0x0F, 0x8E, 0x0B, 0x43, 0x06, 0xF9, 0x01, 0xCF, 0xFF, + 0x71, 0xFF, 0xC8, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD8, 0xFF, 0x7C, + 0xFF, 0xA4, 0xFF, 0x6E, 0x01, 0x61, 0x05, 0xA3, 0x0A, 0x1C, 0x0F, + 0xA7, 0x10, 0x7B, 0x0E, 0xB2, 0x09, 0x8B, 0x04, 0xF4, 0x00, 0x86, + 0xFF, 0x8A, 0xFF, 0xE4, 0xFF, 0xFA, 0xFF, 0xB8, 0xFF, 0x6C, 0xFF, + 0x09, 0x00, 0x97, 0x02, 0x2E, 0x07, 0x6F, 0x0C, 0x11, 0x10, 0x4A, + 0x10, 0xFB, 0x0C, 0xCB, 0x07, 0x07, 0x03, 0x38, 0x00, 0x6D, 0xFF, + 0xAC, 0xFF, 0xF7, 0xFF, 0xEC, 0xFF, 0x95, 0xFF, 0x79, 0xFF, 0xAF, + 0x00, 0x05, 0x04, 0x13, 0x09, 0x06, 0x0E, 0x96, 0x10, 0x78, 0x0F, + 0x3E, 0x0B, 0xF4, 0x05, 0xC7, 0x01, 0xBF, 0xFF, 0x74, 0xFF, 0xCE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0x78, 0xFF, 0xB1, 0xFF, + 0x9C, 0x01, 0xAE, 0x05, 0xF6, 0x0A, 0x4E, 0x0F, 0x9F, 0x10, 0x3E, + 0x0E, 0x5E, 0x09, 0x43, 0x04, 0xCF, 0x00, 0x7F, 0xFF, 0x90, 0xFF, + 0xE8, 0xFF, 0xF9, 0xFF, 0xB2, 0xFF, 0x6C, 0xFF, 0x21, 0x00, 0xD2, + 0x02, 0x81, 0x07, 0xBA, 0x0C, 0x31, 0x10, 0x2E, 0x10, 0xB2, 0x0C, + 0x78, 0x07, 0xCB, 0x02, 0x1E, 0x00, 0x6C, 0xFF, 0xB2, 0xFF, 0xF9, + 0xFF, 0xE8, 0xFF, 0x8F, 0xFF, 0x80, 0xFF, 0xD3, 0x00, 0x4B, 0x04, + 0x67, 0x09, 0x45, 0x0E, 0xA0, 0x10, 0x48, 0x0F, 0xEC, 0x0A, 0xA6, + 0x05, 0x97, 0x01, 0xB0, 0xFF, 0x78, 0xFF, 0xD3, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xCD, 0xFF, 0x74, 0xFF, 0xC0, 0xFF, 0xCC, 0x01, 0xFD, + 0x05, 0x47, 0x0B, 0x7D, 0x0F, 0x94, 0x10, 0xFF, 0x0D, 0x0A, 0x09, + 0xFE, 0x03, 0xAB, 0x00, 0x79, 0xFF, 0x95, 0xFF, 0xEC, 0xFF, 0xF7, + 0xFF, 0xAC, 0xFF, 0x6D, 0xFF, 0x3B, 0x00, 0x0E, 0x03, 0xD5, 0x07, + 0x03, 0x0D, 0x4D, 0x10, 0x0E, 0x10, 0x67, 0x0C, 0x25, 0x07, 0x91, + 0x02, 0x07, 0x00, 0x6C, 0xFF, 0xB8, 0xFF, 0xFB, 0xFF, 0xE4, 0xFF, + 0x89, 0xFF, 0x87, 0xFF, 0xF9, 0x00, 0x93, 0x04, 0xBC, 0x09, 0x82, + 0x0E, 0xA7, 0x10, 0x16, 0x0F, 0x9A, 0x0A, 0x59, 0x05, 0x69, 0x01, + 0xA3, 0xFF, 0x7C, 0xFF, 0xD8, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC8, + 0xFF, 0x71, 0xFF, 0xD1, 0xFF, 0xFF, 0x01, 0x4C, 0x06, 0x97, 0x0B, + 0xA9, 0x0F, 0x86, 0x10, 0xBD, 0x0D, 0xB5, 0x08, 0xBA, 0x03, 0x8A, + 0x00, 0x74, 0xFF, 0x9B, 0xFF, 0xEF, 0xFF, 0xF4, 0xFF, 0xA5, 0xFF, + 0x6F, 0xFF, 0x57, 0x00, 0x4D, 0x03, 0x29, 0x08, 0x4B, 0x0D, 0x65, + 0x10, 0xEB, 0x0F, 0x1A, 0x0C, 0xD3, 0x06, 0x58, 0x02, 0xF1, 0xFF, + 0x6D, 0xFF, 0xBE, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF, 0x84, 0xFF, 0x90, + 0xFF, 0x21, 0x01, 0xDC, 0x04, 0x10, 0x0A, 0xBB, 0x0E, 0xAA, 0x10, + 0xE1, 0x0E, 0x47, 0x0A, 0x0D, 0x05, 0x3D, 0x01, 0x97, 0xFF, 0x81, + 0xFF, 0xDD, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC2, 0xFF, 0x6F, 0xFF, + 0xE4, 0xFF, 0x34, 0x02, 0x9D, 0x06, 0xE6, 0x0B, 0xD1, 0x0F, 0x73, + 0x10, 0x79, 0x0D, 0x61, 0x08, 0x78, 0x03, 0x6A, 0x00, 0x70, 0xFF, + 0xA1, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0x9F, 0xFF, 0x72, 0xFF, 0x74, + 0x00, 0x8E, 0x03, 0x7D, 0x08, 0x90, 0x0D, 0x7A, 0x10, 0xC4, 0x0F, + 0xCC, 0x0B, 0x82, 0x06, 0x22, 0x02, 0xDD, 0xFF, 0x6F, 0xFF, 0xC4, + 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDB, 0xFF, 0x7F, 0xFF, 0x9B, 0xFF, + 0x4B, 0x01, 0x26, 0x05, 0x63, 0x0A, 0xF3, 0x0E, 0xAA, 0x10, 0xA8, + 0x0E, 0xF4, 0x09, 0xC3, 0x04, 0x13, 0x01, 0x8D, 0xFF, 0x86, 0xFF, + 0xE1, 0xFF, 0xFC, 0xFF, 0xBC, 0xFF, 0x6D, 0xFF, 0xF8, 0xFF, 0x6B, + 0x02, 0xEE, 0x06, 0x34, 0x0C, 0xF7, 0x0F, 0x5D, 0x10, 0x33, 0x0D, + 0x0D, 0x08, 0x38, 0x03, 0x4D, 0x00, 0x6E, 0xFF, 0xA7, 0xFF, 0xF5, + 0xFF, 0xEE, 0xFF, 0x99, 0xFF, 0x76, 0xFF, 0x94, 0x00, 0xD0, 0x03, + 0xD1, 0x08, 0xD3, 0x0D, 0x8B, 0x10, 0x9A, 0x0F, 0x7C, 0x0B, 0x32, + 0x06, 0xEE, 0x01, 0xCB, 0xFF, 0x72, 0xFF, 0xCA, 0xFF, 0xFE, 0xFF, + 0x00, 0x00, 0xD6, 0xFF, 0x7B, 0xFF, 0xA7, 0xFF, 0x78, 0x01, 0x72, + 0x05, 0xB6, 0x0A, 0x27, 0x0F, 0xA5, 0x10, 0x6E, 0x0E, 0xA0, 0x09, + 0x7B, 0x04, 0xEC, 0x00, 0x85, 0xFF, 0x8B, 0xFF, 0xE5, 0xFF, 0xFA, + 0xFF, 0xB6, 0xFF, 0x6C, 0xFF, 0x0E, 0x00, 0xA4, 0x02, 0x41, 0x07, + 0x80, 0x0C, 0x19, 0x10, 0x44, 0x10, 0xEB, 0x0C, 0xB9, 0x07, 0xFA, + 0x02, 0x32, 0x00, 0x6D, 0xFF, 0xAE, 0xFF, 0xF7, 0xFF, 0xEB, 0xFF, + 0x93, 0xFF, 0x7B, 0xFF, 0xB7, 0x00, 0x15, 0x04, 0x26, 0x09, 0x14, + 0x0E, 0x98, 0x10, 0x6D, 0x0F, 0x2C, 0x0B, 0xE3, 0x05, 0xBC, 0x01, + 0xBB, 0xFF, 0x75, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, + 0xFF, 0x77, 0xFF, 0xB5, 0xFF, 0xA6, 0x01, 0xC0, 0x05, 0x08, 0x0B, + 0x58, 0x0F, 0x9D, 0x10, 0x30, 0x0E, 0x4B, 0x09, 0x34, 0x04, 0xC6, + 0x00, 0x7D, 0xFF, 0x91, 0xFF, 0xE9, 0xFF, 0xF8, 0xFF, 0xB0, 0xFF, + 0x6C, 0xFF, 0x27, 0x00, 0xDF, 0x02, 0x94, 0x07, 0xCA, 0x0C, 0x37, + 0x10, 0x27, 0x10, 0xA1, 0x0C, 0x65, 0x07, 0xBE, 0x02, 0x19, 0x00, + 0x6C, 0xFF, 0xB4, 0xFF, 0xF9, 0xFF, 0xE7, 0xFF, 0x8E, 0xFF, 0x81, + 0xFF, 0xDB, 0x00, 0x5B, 0x04, 0x7A, 0x09, 0x53, 0x0E, 0xA2, 0x10, + 0x3D, 0x0F, 0xDA, 0x0A, 0x95, 0x05, 0x8C, 0x01, 0xAD, 0xFF, 0x79, + 0xFF, 0xD4, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCC, 0xFF, 0x73, 0xFF, + 0xC4, 0xFF, 0xD7, 0x01, 0x0E, 0x06, 0x59, 0x0B, 0x87, 0x0F, 0x91, + 0x10, 0xF0, 0x0D, 0xF7, 0x08, 0xEF, 0x03, 0xA3, 0x00, 0x78, 0xFF, + 0x97, 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xAA, 0xFF, 0x6D, 0xFF, 0x41, + 0x00, 0x1C, 0x03, 0xE7, 0x07, 0x13, 0x0D, 0x52, 0x10, 0x06, 0x10, + 0x56, 0x0C, 0x13, 0x07, 0x84, 0x02, 0x02, 0x00, 0x6D, 0xFF, 0xBA, + 0xFF, 0xFB, 0xFF, 0xE3, 0xFF, 0x88, 0xFF, 0x89, 0xFF, 0x01, 0x01, + 0xA3, 0x04, 0xCE, 0x09, 0x8F, 0x0E, 0xA8, 0x10, 0x0A, 0x0F, 0x88, + 0x0A, 0x48, 0x05, 0x5F, 0x01, 0xA0, 0xFF, 0x7D, 0xFF, 0xD9, 0xFF, + 0x00, 0x00, 0xFE, 0xFF, 0xC7, 0xFF, 0x70, 0xFF, 0xD5, 0xFF, 0x0B, + 0x02, 0x5E, 0x06, 0xA9, 0x0B, 0xB2, 0x0F, 0x82, 0x10, 0xAE, 0x0D, + 0xA2, 0x08, 0xAB, 0x03, 0x82, 0x00, 0x73, 0xFF, 0x9D, 0xFF, 0xF0, + 0xFF, 0xF3, 0xFF, 0xA4, 0xFF, 0x6F, 0xFF, 0x5D, 0x00, 0x5B, 0x03, + 0x3B, 0x08, 0x5A, 0x0D, 0x6A, 0x10, 0xE2, 0x0F, 0x09, 0x0C, 0xC1, + 0x06, 0x4C, 0x02, 0xEC, 0xFF, 0x6E, 0xFF, 0xC0, 0xFF, 0xFC, 0xFF, + 0xDF, 0xFF, 0x83, 0xFF, 0x93, 0xFF, 0x2A, 0x01, 0xEC, 0x04, 0x22, + 0x0A, 0xC8, 0x0E, 0xAB, 0x10, 0xD4, 0x0E, 0x35, 0x0A, 0xFD, 0x04, + 0x33, 0x01, 0x95, 0xFF, 0x82, 0xFF, 0xDE, 0xFF, 0x00, 0x00, 0xFD, + 0xFF, 0xC1, 0xFF, 0x6E, 0xFF, 0xE8, 0xFF, 0x40, 0x02, 0xAF, 0x06, + 0xF7, 0x0B, 0xDA, 0x0F, 0x6F, 0x10, 0x6A, 0x0D, 0x4E, 0x08, 0x6A, + 0x03, 0x64, 0x00, 0x70, 0xFF, 0xA3, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, + 0x9E, 0xFF, 0x72, 0xFF, 0x7B, 0x00, 0x9C, 0x03, 0x90, 0x08, 0x9F, + 0x0D, 0x7E, 0x10, 0xBB, 0x0F, 0xBA, 0x0B, 0x70, 0x06, 0x16, 0x02, + 0xD9, 0xFF, 0x70, 0xFF, 0xC5, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xDA, + 0xFF, 0x7E, 0xFF, 0x9D, 0xFF, 0x55, 0x01, 0x37, 0x05, 0x75, 0x0A, + 0xFF, 0x0E, 0xA9, 0x10, 0x9C, 0x0E, 0xE1, 0x09, 0xB3, 0x04, 0x0A, + 0x01, 0x8B, 0xFF, 0x87, 0xFF, 0xE2, 0xFF, 0xFB, 0xFF, 0xBB, 0xFF, + 0x6D, 0xFF, 0xFD, 0xFF, 0x77, 0x02, 0x01, 0x07, 0x45, 0x0C, 0xFF, + 0x0F, 0x58, 0x10, 0x23, 0x0D, 0xFA, 0x07, 0x2A, 0x03, 0x47, 0x00, + 0x6E, 0xFF, 0xA9, 0xFF, 0xF5, 0xFF, 0xED, 0xFF, 0x98, 0xFF, 0x77, + 0xFF, 0x9C, 0x00, 0xDF, 0x03, 0xE4, 0x08, 0xE2, 0x0D, 0x8E, 0x10, + 0x91, 0x0F, 0x6B, 0x0B, 0x20, 0x06, 0xE3, 0x01, 0xC8, 0xFF, 0x73, + 0xFF, 0xCB, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD5, 0xFF, 0x7A, 0xFF, + 0xAA, 0xFF, 0x82, 0x01, 0x83, 0x05, 0xC8, 0x0A, 0x32, 0x0F, 0xA4, + 0x10, 0x60, 0x0E, 0x8D, 0x09, 0x6B, 0x04, 0xE3, 0x00, 0x83, 0xFF, + 0x8D, 0xFF, 0xE6, 0xFF, 0xFA, 0xFF, 0xB5, 0xFF, 0x6C, 0xFF, 0x14, + 0x00, 0xB1, 0x02, 0x53, 0x07, 0x91, 0x0C, 0x20, 0x10, 0x3E, 0x10, + 0xDB, 0x0C, 0xA6, 0x07, 0xEC, 0x02, 0x2C, 0x00, 0x6C, 0xFF, 0xAF, + 0xFF, 0xF8, 0xFF, 0xEA, 0xFF, 0x92, 0xFF, 0x7C, 0xFF, 0xBE, 0x00, + 0x24, 0x04, 0x38, 0x09, 0x22, 0x0E, 0x9B, 0x10, 0x63, 0x0F, 0x1A, + 0x0B, 0xD1, 0x05, 0xB1, 0x01, 0xB8, 0xFF, 0x76, 0xFF, 0xD0, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0x76, 0xFF, 0xB8, 0xFF, 0xB1, + 0x01, 0xD1, 0x05, 0x1A, 0x0B, 0x63, 0x0F, 0x9B, 0x10, 0x22, 0x0E, + 0x38, 0x09, 0x24, 0x04, 0xBE, 0x00, 0x7C, 0xFF, 0x92, 0xFF, 0xEA, + 0xFF, 0xF8, 0xFF, 0xAF, 0xFF, 0x6C, 0xFF, 0x2C, 0x00, 0xEC, 0x02, + 0xA6, 0x07, 0xDB, 0x0C, 0x3E, 0x10, 0x20, 0x10, 0x91, 0x0C, 0x53, + 0x07, 0xB1, 0x02, 0x14, 0x00, 0x6C, 0xFF, 0xB5, 0xFF, 0xFA, 0xFF, + 0xE6, 0xFF, 0x8D, 0xFF, 0x83, 0xFF, 0xE3, 0x00, 0x6B, 0x04, 0x8D, + 0x09, 0x60, 0x0E, 0xA4, 0x10, 0x32, 0x0F, 0xC8, 0x0A, 0x83, 0x05, + 0x82, 0x01, 0xAA, 0xFF, 0x7A, 0xFF, 0xD5, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0xCB, 0xFF, 0x73, 0xFF, 0xC8, 0xFF, 0xE3, 0x01, 0x20, 0x06, + 0x6B, 0x0B, 0x91, 0x0F, 0x8E, 0x10, 0xE2, 0x0D, 0xE4, 0x08, 0xDF, + 0x03, 0x9C, 0x00, 0x77, 0xFF, 0x98, 0xFF, 0xED, 0xFF, 0xF5, 0xFF, + 0xA9, 0xFF, 0x6E, 0xFF, 0x47, 0x00, 0x2A, 0x03, 0xFA, 0x07, 0x23, + 0x0D, 0x58, 0x10, 0xFF, 0x0F, 0x45, 0x0C, 0x01, 0x07, 0x77, 0x02, + 0xFD, 0xFF, 0x6D, 0xFF, 0xBB, 0xFF, 0xFB, 0xFF, 0xE2, 0xFF, 0x87, + 0xFF, 0x8B, 0xFF, 0x0A, 0x01, 0xB3, 0x04, 0xE1, 0x09, 0x9C, 0x0E, + 0xA9, 0x10, 0xFF, 0x0E, 0x75, 0x0A, 0x37, 0x05, 0x55, 0x01, 0x9D, + 0xFF, 0x7E, 0xFF, 0xDA, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC5, 0xFF, + 0x70, 0xFF, 0xD9, 0xFF, 0x16, 0x02, 0x70, 0x06, 0xBA, 0x0B, 0xBB, + 0x0F, 0x7E, 0x10, 0x9F, 0x0D, 0x90, 0x08, 0x9C, 0x03, 0x7B, 0x00, + 0x72, 0xFF, 0x9E, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xA3, 0xFF, 0x70, + 0xFF, 0x64, 0x00, 0x6A, 0x03, 0x4E, 0x08, 0x6A, 0x0D, 0x6F, 0x10, + 0xDA, 0x0F, 0xF7, 0x0B, 0xAF, 0x06, 0x40, 0x02, 0xE8, 0xFF, 0x6E, + 0xFF, 0xC1, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDE, 0xFF, 0x82, 0xFF, + 0x95, 0xFF, 0x33, 0x01, 0xFD, 0x04, 0x35, 0x0A, 0xD4, 0x0E, 0xAB, + 0x10, 0xC8, 0x0E, 0x22, 0x0A, 0xEC, 0x04, 0x2A, 0x01, 0x93, 0xFF, + 0x83, 0xFF, 0xDF, 0xFF, 0xFC, 0xFF, 0xC0, 0xFF, 0x6E, 0xFF, 0xEC, + 0xFF, 0x4C, 0x02, 0xC1, 0x06, 0x09, 0x0C, 0xE2, 0x0F, 0x6A, 0x10, + 0x5A, 0x0D, 0x3B, 0x08, 0x5B, 0x03, 0x5D, 0x00, 0x6F, 0xFF, 0xA4, + 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0x9D, 0xFF, 0x73, 0xFF, 0x82, 0x00, + 0xAB, 0x03, 0xA2, 0x08, 0xAE, 0x0D, 0x82, 0x10, 0xB2, 0x0F, 0xA9, + 0x0B, 0x5E, 0x06, 0x0B, 0x02, 0xD5, 0xFF, 0x70, 0xFF, 0xC7, 0xFF, + 0xFE, 0xFF, 0x00, 0x00, 0xD9, 0xFF, 0x7D, 0xFF, 0xA0, 0xFF, 0x5F, + 0x01, 0x48, 0x05, 0x88, 0x0A, 0x0A, 0x0F, 0xA8, 0x10, 0x8F, 0x0E, + 0xCE, 0x09, 0xA3, 0x04, 0x01, 0x01, 0x89, 0xFF, 0x88, 0xFF, 0xE3, + 0xFF, 0xFB, 0xFF, 0xBA, 0xFF, 0x6D, 0xFF, 0x02, 0x00, 0x84, 0x02, + 0x13, 0x07, 0x56, 0x0C, 0x06, 0x10, 0x52, 0x10, 0x13, 0x0D, 0xE7, + 0x07, 0x1C, 0x03, 0x41, 0x00, 0x6D, 0xFF, 0xAA, 0xFF, 0xF6, 0xFF, + 0xED, 0xFF, 0x97, 0xFF, 0x78, 0xFF, 0xA3, 0x00, 0xEF, 0x03, 0xF7, + 0x08, 0xF0, 0x0D, 0x91, 0x10, 0x87, 0x0F, 0x59, 0x0B, 0x0E, 0x06, + 0xD7, 0x01, 0xC4, 0xFF, 0x73, 0xFF, 0xCC, 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0xD4, 0xFF, 0x79, 0xFF, 0xAD, 0xFF, 0x8C, 0x01, 0x95, 0x05, + 0xDA, 0x0A, 0x3D, 0x0F, 0xA2, 0x10, 0x53, 0x0E, 0x7A, 0x09, 0x5B, + 0x04, 0xDB, 0x00, 0x81, 0xFF, 0x8E, 0xFF, 0xE7, 0xFF, 0xF9, 0xFF, + 0xB4, 0xFF, 0x6C, 0xFF, 0x19, 0x00, 0xBE, 0x02, 0x65, 0x07, 0xA1, + 0x0C, 0x27, 0x10, 0x37, 0x10, 0xCA, 0x0C, 0x94, 0x07, 0xDF, 0x02, + 0x27, 0x00, 0x6C, 0xFF, 0xB0, 0xFF, 0xF8, 0xFF, 0xE9, 0xFF, 0x91, + 0xFF, 0x7D, 0xFF, 0xC6, 0x00, 0x34, 0x04, 0x4B, 0x09, 0x30, 0x0E, + 0x9D, 0x10, 0x58, 0x0F, 0x08, 0x0B, 0xC0, 0x05, 0xA6, 0x01, 0xB5, + 0xFF, 0x77, 0xFF, 0xD1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, + 0x75, 0xFF, 0xBB, 0xFF, 0xBC, 0x01, 0xE3, 0x05, 0x2C, 0x0B, 0x6D, + 0x0F, 0x98, 0x10, 0x14, 0x0E, 0x26, 0x09, 0x15, 0x04, 0xB7, 0x00, + 0x7B, 0xFF, 0x93, 0xFF, 0xEB, 0xFF, 0xF7, 0xFF, 0xAE, 0xFF, 0x6D, + 0xFF, 0x32, 0x00, 0xFA, 0x02, 0xB9, 0x07, 0xEB, 0x0C, 0x44, 0x10, + 0x19, 0x10, 0x80, 0x0C, 0x41, 0x07, 0xA4, 0x02, 0x0E, 0x00, 0x6C, + 0xFF, 0xB6, 0xFF, 0xFA, 0xFF, 0xE5, 0xFF, 0x8B, 0xFF, 0x85, 0xFF, + 0xEC, 0x00, 0x7B, 0x04, 0xA0, 0x09, 0x6E, 0x0E, 0xA5, 0x10, 0x27, + 0x0F, 0xB6, 0x0A, 0x72, 0x05, 0x78, 0x01, 0xA7, 0xFF, 0x7B, 0xFF, + 0xD6, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xCA, 0xFF, 0x72, 0xFF, 0xCB, + 0xFF, 0xEE, 0x01, 0x32, 0x06, 0x7C, 0x0B, 0x9A, 0x0F, 0x8B, 0x10, + 0xD3, 0x0D, 0xD1, 0x08, 0xD0, 0x03, 0x94, 0x00, 0x76, 0xFF, 0x99, + 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xA7, 0xFF, 0x6E, 0xFF, 0x4D, 0x00, + 0x38, 0x03, 0x0D, 0x08, 0x33, 0x0D, 0x5D, 0x10, 0xF7, 0x0F, 0x34, + 0x0C, 0xEE, 0x06, 0x6B, 0x02, 0xF8, 0xFF, 0x6D, 0xFF, 0xBC, 0xFF, + 0xFC, 0xFF, 0xE1, 0xFF, 0x86, 0xFF, 0x8D, 0xFF, 0x13, 0x01, 0xC3, + 0x04, 0xF4, 0x09, 0xA8, 0x0E, 0xAA, 0x10, 0xF3, 0x0E, 0x63, 0x0A, + 0x26, 0x05, 0x4B, 0x01, 0x9B, 0xFF, 0x7F, 0xFF, 0xDB, 0xFF, 0x00, + 0x00, 0xFD, 0xFF, 0xC4, 0xFF, 0x6F, 0xFF, 0xDD, 0xFF, 0x22, 0x02, + 0x82, 0x06, 0xCC, 0x0B, 0xC4, 0x0F, 0x7A, 0x10, 0x90, 0x0D, 0x7D, + 0x08, 0x8E, 0x03, 0x74, 0x00, 0x72, 0xFF, 0x9F, 0xFF, 0xF1, 0xFF, + 0xF2, 0xFF, 0xA1, 0xFF, 0x70, 0xFF, 0x6A, 0x00, 0x78, 0x03, 0x61, + 0x08, 0x79, 0x0D, 0x73, 0x10, 0xD1, 0x0F, 0xE6, 0x0B, 0x9D, 0x06, + 0x34, 0x02, 0xE4, 0xFF, 0x6F, 0xFF, 0xC2, 0xFF, 0xFD, 0xFF, 0x00, + 0x00, 0xDD, 0xFF, 0x81, 0xFF, 0x97, 0xFF, 0x3D, 0x01, 0x0D, 0x05, + 0x47, 0x0A, 0xE1, 0x0E, 0xAA, 0x10, 0xBB, 0x0E, 0x10, 0x0A, 0xDC, + 0x04, 0x21, 0x01, 0x90, 0xFF, 0x84, 0xFF, 0xE0, 0xFF, 0xFC, 0xFF, + 0xBE, 0xFF, 0x6D, 0xFF, 0xF1, 0xFF, 0x58, 0x02, 0xD3, 0x06, 0x1A, + 0x0C, 0xEB, 0x0F, 0x65, 0x10, 0x4B, 0x0D, 0x29, 0x08, 0x4D, 0x03, + 0x57, 0x00, 0x6F, 0xFF, 0xA5, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0x9B, + 0xFF, 0x74, 0xFF, 0x8A, 0x00, 0xBA, 0x03, 0xB5, 0x08, 0xBD, 0x0D, + 0x86, 0x10, 0xA9, 0x0F, 0x97, 0x0B, 0x4C, 0x06, 0xFF, 0x01, 0xD1, + 0xFF, 0x71, 0xFF, 0xC8, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD8, 0xFF, + 0x7C, 0xFF, 0xA3, 0xFF, 0x69, 0x01, 0x59, 0x05, 0x9A, 0x0A, 0x16, + 0x0F, 0xA7, 0x10, 0x82, 0x0E, 0xBC, 0x09, 0x93, 0x04, 0xF9, 0x00, + 0x87, 0xFF, 0x89, 0xFF, 0xE4, 0xFF, 0xFB, 0xFF, 0xB8, 0xFF, 0x6C, + 0xFF, 0x07, 0x00, 0x91, 0x02, 0x25, 0x07, 0x67, 0x0C, 0x0E, 0x10, + 0x4D, 0x10, 0x03, 0x0D, 0xD5, 0x07, 0x0E, 0x03, 0x3B, 0x00, 0x6D, + 0xFF, 0xAC, 0xFF, 0xF7, 0xFF, 0xEC, 0xFF, 0x95, 0xFF, 0x79, 0xFF, + 0xAB, 0x00, 0xFE, 0x03, 0x0A, 0x09, 0xFF, 0x0D, 0x94, 0x10, 0x7D, + 0x0F, 0x47, 0x0B, 0xFD, 0x05, 0xCC, 0x01, 0xC0, 0xFF, 0x74, 0xFF, + 0xCD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD3, 0xFF, 0x78, 0xFF, 0xB0, + 0xFF, 0x97, 0x01, 0xA6, 0x05, 0xEC, 0x0A, 0x48, 0x0F, 0xA0, 0x10, + 0x45, 0x0E, 0x67, 0x09, 0x4B, 0x04, 0xD3, 0x00, 0x80, 0xFF, 0x8F, + 0xFF, 0xE8, 0xFF, 0xF9, 0xFF, 0xB2, 0xFF, 0x6C, 0xFF, 0x1E, 0x00, + 0xCB, 0x02, 0x78, 0x07, 0xB2, 0x0C, 0x2E, 0x10, 0x31, 0x10, 0xBA, + 0x0C, 0x81, 0x07, 0xD2, 0x02, 0x21, 0x00, 0x6C, 0xFF, 0xB2, 0xFF, + 0xF9, 0xFF, 0xE8, 0xFF, 0x90, 0xFF, 0x7F, 0xFF, 0xCF, 0x00, 0x43, + 0x04, 0x5E, 0x09, 0x3E, 0x0E, 0x9F, 0x10, 0x4E, 0x0F, 0xF6, 0x0A, + 0xAE, 0x05, 0x9C, 0x01, 0xB1, 0xFF, 0x78, 0xFF, 0xD2, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0x74, 0xFF, 0xBF, 0xFF, 0xC7, 0x01, + 0xF4, 0x05, 0x3E, 0x0B, 0x78, 0x0F, 0x96, 0x10, 0x06, 0x0E, 0x13, + 0x09, 0x05, 0x04, 0xAF, 0x00, 0x79, 0xFF, 0x95, 0xFF, 0xEC, 0xFF, + 0xF7, 0xFF, 0xAC, 0xFF, 0x6D, 0xFF, 0x38, 0x00, 0x07, 0x03, 0xCB, + 0x07, 0xFB, 0x0C, 0x4A, 0x10, 0x11, 0x10, 0x6F, 0x0C, 0x2E, 0x07, + 0x97, 0x02, 0x09, 0x00, 0x6C, 0xFF, 0xB8, 0xFF, 0xFA, 0xFF, 0xE4, + 0xFF, 0x8A, 0xFF, 0x86, 0xFF, 0xF4, 0x00, 0x8B, 0x04, 0xB2, 0x09, + 0x7B, 0x0E, 0xA7, 0x10, 0x1C, 0x0F, 0xA3, 0x0A, 0x61, 0x05, 0x6E, + 0x01, 0xA4, 0xFF, 0x7C, 0xFF, 0xD8, 0xFF, 0x00, 0x00, 0xFE, 0xFF, + 0xC8, 0xFF, 0x71, 0xFF, 0xCF, 0xFF, 0xF9, 0x01, 0x43, 0x06, 0x8E, + 0x0B, 0xA4, 0x0F, 0x88, 0x10, 0xC4, 0x0D, 0xBE, 0x08, 0xC1, 0x03, + 0x8D, 0x00, 0x75, 0xFF, 0x9B, 0xFF, 0xEF, 0xFF, 0xF4, 0xFF, 0xA6, + 0xFF, 0x6E, 0xFF, 0x53, 0x00, 0x46, 0x03, 0x1F, 0x08, 0x43, 0x0D, + 0x63, 0x10, 0xEF, 0x0F, 0x23, 0x0C, 0xDC, 0x06, 0x5E, 0x02, 0xF3, + 0xFF, 0x6D, 0xFF, 0xBE, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF, 0x85, 0xFF, + 0x8F, 0xFF, 0x1C, 0x01, 0xD3, 0x04, 0x06, 0x0A, 0xB5, 0x0E, 0xAA, + 0x10, 0xE7, 0x0E, 0x50, 0x0A, 0x16, 0x05, 0x42, 0x01, 0x98, 0xFF, + 0x80, 0xFF, 0xDC, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC3, 0xFF, 0x6F, + 0xFF, 0xE1, 0xFF, 0x2E, 0x02, 0x94, 0x06, 0xDD, 0x0B, 0xCD, 0x0F, + 0x76, 0x10, 0x81, 0x0D, 0x6A, 0x08, 0x7F, 0x03, 0x6E, 0x00, 0x71, + 0xFF, 0xA1, 0xFF, 0xF2, 0xFF, 0x00, 0x00, 0x15, 0x00, 0xD1, 0xFF, + 0x8B, 0xFE, 0xBC, 0xFD, 0xE1, 0x00, 0x84, 0x09, 0xB0, 0x13, 0x47, + 0x18, 0xB0, 0x13, 0x84, 0x09, 0xE1, 0x00, 0xBC, 0xFD, 0x8B, 0xFE, + 0xD1, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xDA, 0x00, 0x30, + 0x00, 0x5D, 0xFC, 0xB3, 0xFC, 0x35, 0x0A, 0xC2, 0x1C, 0x24, 0x20, + 0x48, 0x10, 0x5D, 0xFF, 0x74, 0xFB, 0x3A, 0xFF, 0xFB, 0x00, 0x42, + 0x00, 0xF8, 0xFF, 0xFA, 0xFF, 0x2C, 0x00, 0xF3, 0x00, 0xAD, 0xFF, + 0xC5, 0xFB, 0x11, 0xFE, 0xAF, 0x0D, 0xEF, 0x1E, 0x68, 0x1E, 0xBC, + 0x0C, 0xA7, 0xFD, 0xEA, 0xFB, 0xD3, 0xFF, 0xEE, 0x00, 0x24, 0x00, + 0xFA, 0xFF, 0xF7, 0xFF, 0x4C, 0x00, 0xFB, 0x00, 0x0C, 0xFF, 0x5F, + 0xFB, 0xE8, 0xFF, 0x3D, 0x11, 0x7E, 0x20, 0x13, 0x1C, 0x4C, 0x09, + 0x6A, 0xFC, 0x8C, 0xFC, 0x4E, 0x00, 0xD1, 0x00, 0x0E, 0x00, 0xFD, + 0xFF, 0xF7, 0xFF, 0x72, 0x00, 0xEC, 0x00, 0x55, 0xFE, 0x3D, 0xFB, + 0x37, 0x02, 0xBE, 0x14, 0x5D, 0x21, 0x40, 0x19, 0x18, 0x06, 0xA2, + 0xFB, 0x47, 0xFD, 0xA7, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0xFF, 0x9B, 0x00, 0xC0, 0x00, 0x92, 0xFD, 0x73, + 0xFB, 0xF2, 0x04, 0x0E, 0x18, 0x81, 0x21, 0x0C, 0x16, 0x37, 0x03, + 0x47, 0xFB, 0x0B, 0xFE, 0xDF, 0x00, 0x82, 0x00, 0xF9, 0xFF, 0xFE, + 0xFF, 0x08, 0x00, 0xC3, 0x00, 0x74, 0x00, 0xD2, 0xFC, 0x10, 0xFC, + 0x08, 0x08, 0x0A, 0x1B, 0xE9, 0x20, 0x9A, 0x12, 0xBE, 0x00, 0x49, + 0xFB, 0xC8, 0xFE, 0xF9, 0x00, 0x5A, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, + 0x1B, 0x00, 0xE4, 0x00, 0x06, 0x00, 0x24, 0xFC, 0x1E, 0xFD, 0x65, + 0x0B, 0x94, 0x1D, 0x9D, 0x1F, 0x0D, 0x0F, 0xB8, 0xFE, 0x96, 0xFB, + 0x72, 0xFF, 0xF9, 0x00, 0x37, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x36, + 0x00, 0xF8, 0x00, 0x78, 0xFF, 0x9B, 0xFB, 0xA6, 0xFE, 0xE9, 0x0E, + 0x8D, 0x1F, 0xAA, 0x1D, 0x87, 0x0B, 0x2B, 0xFD, 0x1E, 0xFC, 0x02, + 0x00, 0xE5, 0x00, 0x1C, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x58, 0x00, + 0xF9, 0x00, 0xCF, 0xFE, 0x4A, 0xFB, 0xA7, 0x00, 0x77, 0x12, 0xE0, + 0x20, 0x26, 0x1B, 0x28, 0x08, 0x18, 0xFC, 0xCB, 0xFC, 0x71, 0x00, + 0xC5, 0x00, 0x08, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x80, 0x00, 0xE1, + 0x00, 0x13, 0xFE, 0x45, 0xFB, 0x1D, 0x03, 0xEB, 0x15, 0x7F, 0x21, + 0x2D, 0x18, 0x0E, 0x05, 0x77, 0xFB, 0x8B, 0xFD, 0xBE, 0x00, 0x9D, + 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA9, 0x00, + 0xAA, 0x00, 0x4F, 0xFD, 0x9D, 0xFB, 0xFA, 0x05, 0x22, 0x19, 0x62, + 0x21, 0xE0, 0x14, 0x50, 0x02, 0x3E, 0xFB, 0x4E, 0xFE, 0xEB, 0x00, + 0x73, 0x00, 0xF7, 0xFF, 0xFE, 0xFF, 0x0D, 0x00, 0xD0, 0x00, 0x52, + 0x00, 0x93, 0xFC, 0x60, 0xFC, 0x2C, 0x09, 0xFA, 0x1B, 0x8A, 0x20, + 0x60, 0x11, 0xFD, 0xFF, 0x5C, 0xFB, 0x06, 0xFF, 0xFB, 0x00, 0x4D, + 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x23, 0x00, 0xED, 0x00, 0xD9, 0xFF, + 0xEF, 0xFB, 0x98, 0xFD, 0x99, 0x0C, 0x54, 0x1E, 0x02, 0x1F, 0xD2, + 0x0D, 0x20, 0xFE, 0xC0, 0xFB, 0xA7, 0xFF, 0xF4, 0x00, 0x2D, 0x00, + 0xF9, 0xFF, 0xF8, 0xFF, 0x41, 0x00, 0xFB, 0x00, 0x41, 0xFF, 0x78, + 0xFB, 0x4A, 0xFF, 0x25, 0x10, 0x16, 0x20, 0xDA, 0x1C, 0x56, 0x0A, + 0xBE, 0xFC, 0x56, 0xFC, 0x2C, 0x00, 0xDB, 0x00, 0x14, 0x00, 0xFD, + 0xFF, 0xF7, 0xFF, 0x66, 0x00, 0xF4, 0x00, 0x8F, 0xFE, 0x3F, 0xFB, + 0x75, 0x01, 0xAE, 0x13, 0x2C, 0x21, 0x2A, 0x1A, 0x0D, 0x07, 0xD4, + 0xFB, 0x0C, 0xFD, 0x8F, 0x00, 0xB7, 0x00, 0x03, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0xFA, 0xFF, 0x8E, 0x00, 0xD1, 0x00, 0xCF, 0xFD, 0x58, + 0xFB, 0x10, 0x04, 0x10, 0x17, 0x8A, 0x21, 0x10, 0x17, 0x10, 0x04, + 0x58, 0xFB, 0xCF, 0xFD, 0xD1, 0x00, 0x8E, 0x00, 0xFA, 0xFF, 0xFF, + 0xFF, 0x03, 0x00, 0xB7, 0x00, 0x8F, 0x00, 0x0C, 0xFD, 0xD4, 0xFB, + 0x0D, 0x07, 0x2A, 0x1A, 0x2C, 0x21, 0xAE, 0x13, 0x75, 0x01, 0x3F, + 0xFB, 0x8F, 0xFE, 0xF4, 0x00, 0x66, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, + 0x14, 0x00, 0xDB, 0x00, 0x2C, 0x00, 0x56, 0xFC, 0xBE, 0xFC, 0x56, + 0x0A, 0xDA, 0x1C, 0x16, 0x20, 0x25, 0x10, 0x4A, 0xFF, 0x78, 0xFB, + 0x41, 0xFF, 0xFB, 0x00, 0x41, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x2D, + 0x00, 0xF4, 0x00, 0xA7, 0xFF, 0xC0, 0xFB, 0x20, 0xFE, 0xD2, 0x0D, + 0x02, 0x1F, 0x54, 0x1E, 0x99, 0x0C, 0x98, 0xFD, 0xEF, 0xFB, 0xD9, + 0xFF, 0xED, 0x00, 0x23, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x4D, 0x00, + 0xFB, 0x00, 0x06, 0xFF, 0x5C, 0xFB, 0xFD, 0xFF, 0x60, 0x11, 0x8A, + 0x20, 0xFA, 0x1B, 0x2C, 0x09, 0x60, 0xFC, 0x93, 0xFC, 0x52, 0x00, + 0xD0, 0x00, 0x0D, 0x00, 0xFE, 0xFF, 0xF7, 0xFF, 0x73, 0x00, 0xEB, + 0x00, 0x4E, 0xFE, 0x3E, 0xFB, 0x50, 0x02, 0xE0, 0x14, 0x62, 0x21, + 0x22, 0x19, 0xFA, 0x05, 0x9D, 0xFB, 0x4F, 0xFD, 0xAA, 0x00, 0xA9, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x9D, 0x00, + 0xBE, 0x00, 0x8B, 0xFD, 0x77, 0xFB, 0x0E, 0x05, 0x2D, 0x18, 0x7F, + 0x21, 0xEB, 0x15, 0x1D, 0x03, 0x45, 0xFB, 0x13, 0xFE, 0xE1, 0x00, + 0x80, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x08, 0x00, 0xC5, 0x00, 0x71, + 0x00, 0xCB, 0xFC, 0x18, 0xFC, 0x28, 0x08, 0x26, 0x1B, 0xE0, 0x20, + 0x77, 0x12, 0xA7, 0x00, 0x4A, 0xFB, 0xCF, 0xFE, 0xF9, 0x00, 0x58, + 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1C, 0x00, 0xE5, 0x00, 0x02, 0x00, + 0x1E, 0xFC, 0x2B, 0xFD, 0x87, 0x0B, 0xAA, 0x1D, 0x8D, 0x1F, 0xE9, + 0x0E, 0xA6, 0xFE, 0x9B, 0xFB, 0x78, 0xFF, 0xF8, 0x00, 0x36, 0x00, + 0xF9, 0xFF, 0xF8, 0xFF, 0x37, 0x00, 0xF9, 0x00, 0x72, 0xFF, 0x96, + 0xFB, 0xB8, 0xFE, 0x0D, 0x0F, 0x9D, 0x1F, 0x94, 0x1D, 0x65, 0x0B, + 0x1E, 0xFD, 0x24, 0xFC, 0x06, 0x00, 0xE4, 0x00, 0x1B, 0x00, 0xFC, + 0xFF, 0xF7, 0xFF, 0x5A, 0x00, 0xF9, 0x00, 0xC8, 0xFE, 0x49, 0xFB, + 0xBE, 0x00, 0x9A, 0x12, 0xE9, 0x20, 0x0A, 0x1B, 0x08, 0x08, 0x10, + 0xFC, 0xD2, 0xFC, 0x74, 0x00, 0xC3, 0x00, 0x08, 0x00, 0xFE, 0xFF, + 0xF9, 0xFF, 0x82, 0x00, 0xDF, 0x00, 0x0B, 0xFE, 0x47, 0xFB, 0x37, + 0x03, 0x0C, 0x16, 0x81, 0x21, 0x0E, 0x18, 0xF2, 0x04, 0x73, 0xFB, + 0x92, 0xFD, 0xC0, 0x00, 0x9B, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xAB, 0x00, 0xA7, 0x00, 0x47, 0xFD, 0xA2, 0xFB, + 0x18, 0x06, 0x40, 0x19, 0x5D, 0x21, 0xBE, 0x14, 0x37, 0x02, 0x3D, + 0xFB, 0x55, 0xFE, 0xEC, 0x00, 0x72, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, + 0x0E, 0x00, 0xD1, 0x00, 0x4E, 0x00, 0x8C, 0xFC, 0x6A, 0xFC, 0x4C, + 0x09, 0x13, 0x1C, 0x7E, 0x20, 0x3D, 0x11, 0xE8, 0xFF, 0x5F, 0xFB, + 0x0C, 0xFF, 0xFB, 0x00, 0x4C, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x24, + 0x00, 0xEE, 0x00, 0xD3, 0xFF, 0xEA, 0xFB, 0xA7, 0xFD, 0xBC, 0x0C, + 0x68, 0x1E, 0xEF, 0x1E, 0xAF, 0x0D, 0x11, 0xFE, 0xC5, 0xFB, 0xAD, + 0xFF, 0xF3, 0x00, 0x2C, 0x00, 0xFA, 0xFF, 0xF8, 0xFF, 0x42, 0x00, + 0xFB, 0x00, 0x3A, 0xFF, 0x74, 0xFB, 0x5D, 0xFF, 0x48, 0x10, 0x24, + 0x20, 0xC2, 0x1C, 0x35, 0x0A, 0xB3, 0xFC, 0x5D, 0xFC, 0x30, 0x00, + 0xDA, 0x00, 0x13, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x67, 0x00, 0xF3, + 0x00, 0x88, 0xFE, 0x3E, 0xFB, 0x8C, 0x01, 0xD0, 0x13, 0x33, 0x21, + 0x0D, 0x1A, 0xEE, 0x06, 0xCD, 0xFB, 0x13, 0xFD, 0x92, 0x00, 0xB6, + 0x00, 0x03, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFA, 0xFF, 0x90, 0x00, + 0xCF, 0x00, 0xC7, 0xFD, 0x5B, 0xFB, 0x2B, 0x04, 0x31, 0x17, 0x8A, + 0x21, 0xF0, 0x16, 0xF4, 0x03, 0x56, 0xFB, 0xD6, 0xFD, 0xD3, 0x00, + 0x8D, 0x00, 0xFA, 0xFF, 0xFF, 0xFF, 0x04, 0x00, 0xB9, 0x00, 0x8C, + 0x00, 0x05, 0xFD, 0xDB, 0xFB, 0x2C, 0x07, 0x47, 0x1A, 0x25, 0x21, + 0x8B, 0x13, 0x5D, 0x01, 0x40, 0xFB, 0x97, 0xFE, 0xF5, 0x00, 0x64, + 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x15, 0x00, 0xDC, 0x00, 0x27, 0x00, + 0x50, 0xFC, 0xCA, 0xFC, 0x78, 0x0A, 0xF2, 0x1C, 0x07, 0x20, 0x02, + 0x10, 0x37, 0xFF, 0x7B, 0xFB, 0x47, 0xFF, 0xFB, 0x00, 0x40, 0x00, + 0xF8, 0xFF, 0xF9, 0xFF, 0x2E, 0x00, 0xF5, 0x00, 0xA2, 0xFF, 0xBB, + 0xFB, 0x31, 0xFE, 0xF5, 0x0D, 0x14, 0x1F, 0x3F, 0x1E, 0x77, 0x0C, + 0x8A, 0xFD, 0xF5, 0xFB, 0xDE, 0xFF, 0xEC, 0x00, 0x22, 0x00, 0xFB, + 0xFF, 0xF7, 0xFF, 0x4E, 0x00, 0xFB, 0x00, 0xFF, 0xFE, 0x59, 0xFB, + 0x11, 0x00, 0x83, 0x11, 0x96, 0x20, 0xE0, 0x1B, 0x0B, 0x09, 0x56, + 0xFC, 0x99, 0xFC, 0x56, 0x00, 0xCE, 0x00, 0x0D, 0x00, 0xFE, 0xFF, + 0xF8, 0xFF, 0x75, 0x00, 0xEA, 0x00, 0x47, 0xFE, 0x3E, 0xFB, 0x69, + 0x02, 0x02, 0x15, 0x66, 0x21, 0x04, 0x19, 0xDC, 0x05, 0x98, 0xFB, + 0x56, 0xFD, 0xAD, 0x00, 0xA8, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x9E, 0x00, 0xBC, 0x00, 0x83, 0xFD, 0x7B, 0xFB, + 0x2B, 0x05, 0x4C, 0x18, 0x7C, 0x21, 0xCA, 0x15, 0x03, 0x03, 0x44, + 0xFB, 0x1A, 0xFE, 0xE2, 0x00, 0x7E, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, + 0x09, 0x00, 0xC6, 0x00, 0x6D, 0x00, 0xC3, 0xFC, 0x20, 0xFC, 0x49, + 0x08, 0x41, 0x1B, 0xD6, 0x20, 0x54, 0x12, 0x92, 0x00, 0x4C, 0xFB, + 0xD6, 0xFE, 0xFA, 0x00, 0x57, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1D, + 0x00, 0xE6, 0x00, 0xFD, 0xFF, 0x18, 0xFC, 0x38, 0xFD, 0xA9, 0x0B, + 0xC0, 0x1D, 0x7C, 0x1F, 0xC6, 0x0E, 0x95, 0xFE, 0x9F, 0xFB, 0x7E, + 0xFF, 0xF8, 0x00, 0x35, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x38, 0x00, + 0xF9, 0x00, 0x6C, 0xFF, 0x92, 0xFB, 0xC9, 0xFE, 0x2F, 0x0F, 0xAD, + 0x1F, 0x7D, 0x1D, 0x42, 0x0B, 0x12, 0xFD, 0x2A, 0xFC, 0x0B, 0x00, + 0xE3, 0x00, 0x1A, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x5B, 0x00, 0xF8, + 0x00, 0xC1, 0xFE, 0x47, 0xFB, 0xD4, 0x00, 0xBC, 0x12, 0xF3, 0x20, + 0xEF, 0x1A, 0xE9, 0x07, 0x08, 0xFC, 0xD9, 0xFC, 0x78, 0x00, 0xC2, + 0x00, 0x07, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0x83, 0x00, 0xDD, 0x00, + 0x04, 0xFE, 0x49, 0xFB, 0x52, 0x03, 0x2D, 0x16, 0x83, 0x21, 0xEF, + 0x17, 0xD5, 0x04, 0x6F, 0xFB, 0x9A, 0xFD, 0xC3, 0x00, 0x9A, 0x00, + 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xAD, 0x00, 0xA4, + 0x00, 0x40, 0xFD, 0xA8, 0xFB, 0x36, 0x06, 0x5E, 0x19, 0x58, 0x21, + 0x9C, 0x14, 0x1E, 0x02, 0x3D, 0xFB, 0x5D, 0xFE, 0xED, 0x00, 0x70, + 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x0F, 0x00, 0xD2, 0x00, 0x4A, 0x00, + 0x85, 0xFC, 0x74, 0xFC, 0x6D, 0x09, 0x2D, 0x1C, 0x72, 0x20, 0x1A, + 0x11, 0xD4, 0xFF, 0x61, 0xFB, 0x13, 0xFF, 0xFC, 0x00, 0x4A, 0x00, + 0xF7, 0xFF, 0xFA, 0xFF, 0x25, 0x00, 0xEF, 0x00, 0xCE, 0xFF, 0xE4, + 0xFB, 0xB5, 0xFD, 0xDE, 0x0C, 0x7C, 0x1E, 0xDD, 0x1E, 0x8C, 0x0D, + 0x01, 0xFE, 0xCA, 0xFB, 0xB3, 0xFF, 0xF3, 0x00, 0x2B, 0x00, 0xFA, + 0xFF, 0xF8, 0xFF, 0x44, 0x00, 0xFB, 0x00, 0x34, 0xFF, 0x71, 0xFB, + 0x71, 0xFF, 0x6B, 0x10, 0x32, 0x20, 0xA9, 0x1C, 0x13, 0x0A, 0xA8, + 0xFC, 0x63, 0xFC, 0x35, 0x00, 0xD9, 0x00, 0x12, 0x00, 0xFD, 0xFF, + 0xF7, 0xFF, 0x69, 0x00, 0xF2, 0x00, 0x81, 0xFE, 0x3E, 0xFB, 0xA4, + 0x01, 0xF2, 0x13, 0x3A, 0x21, 0xF0, 0x19, 0xCF, 0x06, 0xC7, 0xFB, + 0x1B, 0xFD, 0x96, 0x00, 0xB4, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0xFB, 0xFF, 0x92, 0x00, 0xCD, 0x00, 0xC0, 0xFD, 0x5E, 0xFB, + 0x47, 0x04, 0x51, 0x17, 0x8A, 0x21, 0xD0, 0x16, 0xD9, 0x03, 0x53, + 0xFB, 0xDE, 0xFD, 0xD5, 0x00, 0x8B, 0x00, 0xFA, 0xFF, 0xFF, 0xFF, + 0x04, 0x00, 0xBA, 0x00, 0x89, 0x00, 0xFD, 0xFC, 0xE2, 0xFB, 0x4B, + 0x07, 0x63, 0x1A, 0x1D, 0x21, 0x69, 0x13, 0x46, 0x01, 0x41, 0xFB, + 0x9E, 0xFE, 0xF5, 0x00, 0x63, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x16, + 0x00, 0xDD, 0x00, 0x23, 0x00, 0x49, 0xFC, 0xD5, 0xFC, 0x99, 0x0A, + 0x09, 0x1D, 0xF9, 0x1F, 0xDF, 0x0F, 0x24, 0xFF, 0x7F, 0xFB, 0x4D, + 0xFF, 0xFB, 0x00, 0x3F, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x2F, 0x00, + 0xF5, 0x00, 0x9C, 0xFF, 0xB6, 0xFB, 0x41, 0xFE, 0x17, 0x0E, 0x26, + 0x1F, 0x2B, 0x1E, 0x54, 0x0C, 0x7C, 0xFD, 0xFA, 0xFB, 0xE3, 0xFF, + 0xEB, 0x00, 0x21, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x50, 0x00, 0xFB, + 0x00, 0xF8, 0xFE, 0x57, 0xFB, 0x26, 0x00, 0xA6, 0x11, 0xA1, 0x20, + 0xC6, 0x1B, 0xEA, 0x08, 0x4D, 0xFC, 0xA0, 0xFC, 0x5A, 0x00, 0xCD, + 0x00, 0x0C, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x77, 0x00, 0xE9, 0x00, + 0x3F, 0xFE, 0x3F, 0xFB, 0x82, 0x02, 0x23, 0x15, 0x6B, 0x21, 0xE5, + 0x18, 0xBE, 0x05, 0x93, 0xFB, 0x5E, 0xFD, 0xAF, 0x00, 0xA6, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0xA0, 0x00, 0xB9, + 0x00, 0x7C, 0xFD, 0x80, 0xFB, 0x48, 0x05, 0x6B, 0x18, 0x79, 0x21, + 0xA9, 0x15, 0xE9, 0x02, 0x43, 0xFB, 0x21, 0xFE, 0xE3, 0x00, 0x7D, + 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x09, 0x00, 0xC7, 0x00, 0x69, 0x00, + 0xBC, 0xFC, 0x29, 0xFC, 0x69, 0x08, 0x5C, 0x1B, 0xCC, 0x20, 0x32, + 0x12, 0x7C, 0x00, 0x4E, 0xFB, 0xDD, 0xFE, 0xFA, 0x00, 0x56, 0x00, + 0xF7, 0xFF, 0xFB, 0xFF, 0x1D, 0x00, 0xE7, 0x00, 0xF8, 0xFF, 0x12, + 0xFC, 0x45, 0xFD, 0xCB, 0x0B, 0xD6, 0x1D, 0x6C, 0x1F, 0xA3, 0x0E, + 0x84, 0xFE, 0xA4, 0xFB, 0x84, 0xFF, 0xF7, 0x00, 0x34, 0x00, 0xF9, + 0xFF, 0xF8, 0xFF, 0x3A, 0x00, 0xFA, 0x00, 0x66, 0xFF, 0x8E, 0xFB, + 0xDB, 0xFE, 0x53, 0x0F, 0xBD, 0x1F, 0x66, 0x1D, 0x21, 0x0B, 0x05, + 0xFD, 0x30, 0xFC, 0x10, 0x00, 0xE2, 0x00, 0x19, 0x00, 0xFC, 0xFF, + 0xF7, 0xFF, 0x5D, 0x00, 0xF8, 0x00, 0xBA, 0xFE, 0x46, 0xFB, 0xEA, + 0x00, 0xDF, 0x12, 0xFC, 0x20, 0xD3, 0x1A, 0xC9, 0x07, 0x00, 0xFC, + 0xE0, 0xFC, 0x7B, 0x00, 0xC0, 0x00, 0x07, 0x00, 0xFF, 0xFF, 0xF9, + 0xFF, 0x85, 0x00, 0xDC, 0x00, 0xFC, 0xFD, 0x4A, 0xFB, 0x6C, 0x03, + 0x4E, 0x16, 0x85, 0x21, 0xCF, 0x17, 0xB8, 0x04, 0x6C, 0xFB, 0xA2, + 0xFD, 0xC5, 0x00, 0x98, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0x01, 0x00, 0xAE, 0x00, 0xA1, 0x00, 0x38, 0xFD, 0xAE, 0xFB, 0x54, + 0x06, 0x7C, 0x19, 0x53, 0x21, 0x7B, 0x14, 0x05, 0x02, 0x3D, 0xFB, + 0x64, 0xFE, 0xEE, 0x00, 0x6F, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x0F, + 0x00, 0xD4, 0x00, 0x46, 0x00, 0x7E, 0xFC, 0x7E, 0xFC, 0x8E, 0x09, + 0x46, 0x1C, 0x66, 0x20, 0xF7, 0x10, 0xC0, 0xFF, 0x64, 0xFB, 0x1A, + 0xFF, 0xFC, 0x00, 0x49, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x26, 0x00, + 0xF0, 0x00, 0xC9, 0xFF, 0xDF, 0xFB, 0xC4, 0xFD, 0x01, 0x0D, 0x90, + 0x1E, 0xCA, 0x1E, 0x69, 0x0D, 0xF1, 0xFD, 0xCF, 0xFB, 0xB8, 0xFF, + 0xF2, 0x00, 0x29, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x45, 0x00, 0xFC, + 0x00, 0x2D, 0xFF, 0x6D, 0xFB, 0x84, 0xFF, 0x8E, 0x10, 0x3F, 0x20, + 0x91, 0x1C, 0xF2, 0x09, 0x9D, 0xFC, 0x6A, 0xFC, 0x39, 0x00, 0xD7, + 0x00, 0x12, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x6A, 0x00, 0xF1, 0x00, + 0x7A, 0xFE, 0x3D, 0xFB, 0xBC, 0x01, 0x14, 0x14, 0x41, 0x21, 0xD4, + 0x19, 0xB0, 0x06, 0xC0, 0xFB, 0x22, 0xFD, 0x99, 0x00, 0xB3, 0x00, + 0x02, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFB, 0xFF, 0x93, 0x00, 0xCB, + 0x00, 0xB8, 0xFD, 0x61, 0xFB, 0x63, 0x04, 0x71, 0x17, 0x89, 0x21, + 0xB0, 0x16, 0xBD, 0x03, 0x51, 0xFB, 0xE6, 0xFD, 0xD7, 0x00, 0x8A, + 0x00, 0xFA, 0xFF, 0xFF, 0xFF, 0x05, 0x00, 0xBC, 0x00, 0x86, 0x00, + 0xF6, 0xFC, 0xE9, 0xFB, 0x6A, 0x07, 0x80, 0x1A, 0x15, 0x21, 0x47, + 0x13, 0x2F, 0x01, 0x42, 0xFB, 0xA5, 0xFE, 0xF6, 0x00, 0x61, 0x00, + 0xF7, 0xFF, 0xFC, 0xFF, 0x16, 0x00, 0xDF, 0x00, 0x1E, 0x00, 0x43, + 0xFC, 0xE1, 0xFC, 0xBB, 0x0A, 0x21, 0x1D, 0xEA, 0x1F, 0xBC, 0x0F, + 0x12, 0xFF, 0x82, 0xFB, 0x54, 0xFF, 0xFA, 0x00, 0x3D, 0x00, 0xF8, + 0xFF, 0xF9, 0xFF, 0x30, 0x00, 0xF6, 0x00, 0x96, 0xFF, 0xB1, 0xFB, + 0x51, 0xFE, 0x3A, 0x0E, 0x38, 0x1F, 0x16, 0x1E, 0x32, 0x0C, 0x6E, + 0xFD, 0x00, 0xFC, 0xE8, 0xFF, 0xEA, 0x00, 0x20, 0x00, 0xFB, 0xFF, + 0xF7, 0xFF, 0x51, 0x00, 0xFB, 0x00, 0xF1, 0xFE, 0x54, 0xFB, 0x3B, + 0x00, 0xC9, 0x11, 0xAD, 0x20, 0xAC, 0x1B, 0xCA, 0x08, 0x44, 0xFC, + 0xA7, 0xFC, 0x5E, 0x00, 0xCC, 0x00, 0x0B, 0x00, 0xFE, 0xFF, 0xF8, + 0xFF, 0x78, 0x00, 0xE7, 0x00, 0x38, 0xFE, 0x40, 0xFB, 0x9B, 0x02, + 0x45, 0x15, 0x6F, 0x21, 0xC7, 0x18, 0xA1, 0x05, 0x8E, 0xFB, 0x65, + 0xFD, 0xB2, 0x00, 0xA5, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0xA2, 0x00, 0xB7, 0x00, 0x74, 0xFD, 0x84, 0xFB, 0x66, + 0x05, 0x8A, 0x18, 0x76, 0x21, 0x87, 0x15, 0xCF, 0x02, 0x41, 0xFB, + 0x29, 0xFE, 0xE5, 0x00, 0x7B, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0A, + 0x00, 0xC9, 0x00, 0x66, 0x00, 0xB5, 0xFC, 0x32, 0xFC, 0x89, 0x08, + 0x77, 0x1B, 0xC2, 0x20, 0x0F, 0x12, 0x66, 0x00, 0x50, 0xFB, 0xE4, + 0xFE, 0xFA, 0x00, 0x54, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1E, 0x00, + 0xE8, 0x00, 0xF3, 0xFF, 0x0C, 0xFC, 0x53, 0xFD, 0xED, 0x0B, 0xEB, + 0x1D, 0x5A, 0x1F, 0x80, 0x0E, 0x73, 0xFE, 0xA8, 0xFB, 0x8A, 0xFF, + 0xF7, 0x00, 0x32, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x3B, 0x00, 0xFA, + 0x00, 0x60, 0xFF, 0x8A, 0xFB, 0xED, 0xFE, 0x76, 0x0F, 0xCC, 0x1F, + 0x4F, 0x1D, 0xFF, 0x0A, 0xF9, 0xFC, 0x36, 0xFC, 0x15, 0x00, 0xE1, + 0x00, 0x18, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x5E, 0x00, 0xF7, 0x00, + 0xB3, 0xFE, 0x44, 0xFB, 0x01, 0x01, 0x02, 0x13, 0x04, 0x21, 0xB8, + 0x1A, 0xA9, 0x07, 0xF8, 0xFB, 0xE7, 0xFC, 0x7F, 0x00, 0xBF, 0x00, + 0x06, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0x86, 0x00, 0xDA, 0x00, 0xF5, + 0xFD, 0x4C, 0xFB, 0x87, 0x03, 0x6E, 0x16, 0x86, 0x21, 0xB0, 0x17, + 0x9C, 0x04, 0x68, 0xFB, 0xA9, 0xFD, 0xC7, 0x00, 0x96, 0x00, 0xFB, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xB0, 0x00, 0x9F, 0x00, + 0x31, 0xFD, 0xB4, 0xFB, 0x73, 0x06, 0x99, 0x19, 0x4D, 0x21, 0x59, + 0x14, 0xED, 0x01, 0x3D, 0xFB, 0x6B, 0xFE, 0xEF, 0x00, 0x6D, 0x00, + 0xF7, 0xFF, 0xFD, 0xFF, 0x10, 0x00, 0xD5, 0x00, 0x42, 0x00, 0x77, + 0xFC, 0x88, 0xFC, 0xAF, 0x09, 0x5F, 0x1C, 0x59, 0x20, 0xD4, 0x10, + 0xAC, 0xFF, 0x67, 0xFB, 0x20, 0xFF, 0xFC, 0x00, 0x48, 0x00, 0xF7, + 0xFF, 0xFA, 0xFF, 0x27, 0x00, 0xF0, 0x00, 0xC3, 0xFF, 0xD9, 0xFB, + 0xD3, 0xFD, 0x24, 0x0D, 0xA3, 0x1E, 0xB7, 0x1E, 0x46, 0x0D, 0xE2, + 0xFD, 0xD4, 0xFB, 0xBE, 0xFF, 0xF1, 0x00, 0x28, 0x00, 0xFA, 0xFF, + 0xF7, 0xFF, 0x46, 0x00, 0xFC, 0x00, 0x27, 0xFF, 0x6A, 0xFB, 0x98, + 0xFF, 0xB1, 0x10, 0x4C, 0x20, 0x78, 0x1C, 0xD1, 0x09, 0x93, 0xFC, + 0x71, 0xFC, 0x3D, 0x00, 0xD6, 0x00, 0x11, 0x00, 0xFD, 0xFF, 0xF7, + 0xFF, 0x6C, 0x00, 0xF0, 0x00, 0x72, 0xFE, 0x3D, 0xFB, 0xD4, 0x01, + 0x36, 0x14, 0x47, 0x21, 0xB6, 0x19, 0x91, 0x06, 0xBA, 0xFB, 0x29, + 0xFD, 0x9C, 0x00, 0xB1, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0xFB, 0xFF, 0x95, 0x00, 0xC9, 0x00, 0xB1, 0xFD, 0x65, 0xFB, 0x80, + 0x04, 0x90, 0x17, 0x88, 0x21, 0x8F, 0x16, 0xA2, 0x03, 0x4E, 0xFB, + 0xED, 0xFD, 0xD9, 0x00, 0x88, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0x05, + 0x00, 0xBD, 0x00, 0x82, 0x00, 0xEF, 0xFC, 0xF0, 0xFB, 0x8A, 0x07, + 0x9C, 0x1A, 0x0D, 0x21, 0x24, 0x13, 0x18, 0x01, 0x43, 0xFB, 0xAC, + 0xFE, 0xF7, 0x00, 0x60, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x17, 0x00, + 0xE0, 0x00, 0x1A, 0x00, 0x3D, 0xFC, 0xED, 0xFC, 0xDD, 0x0A, 0x38, + 0x1D, 0xDB, 0x1F, 0x99, 0x0F, 0xFF, 0xFE, 0x86, 0xFB, 0x5A, 0xFF, + 0xFA, 0x00, 0x3C, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x31, 0x00, 0xF6, + 0x00, 0x90, 0xFF, 0xAD, 0xFB, 0x62, 0xFE, 0x5D, 0x0E, 0x49, 0x1F, + 0x01, 0x1E, 0x10, 0x0C, 0x60, 0xFD, 0x06, 0xFC, 0xEE, 0xFF, 0xE9, + 0x00, 0x1F, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x53, 0x00, 0xFB, 0x00, + 0xEB, 0xFE, 0x52, 0xFB, 0x51, 0x00, 0xEC, 0x11, 0xB7, 0x20, 0x91, + 0x1B, 0xA9, 0x08, 0x3B, 0xFC, 0xAE, 0xFC, 0x62, 0x00, 0xCA, 0x00, + 0x0B, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7A, 0x00, 0xE6, 0x00, 0x30, + 0xFE, 0x40, 0xFB, 0xB5, 0x02, 0x66, 0x15, 0x73, 0x21, 0xA9, 0x18, + 0x83, 0x05, 0x89, 0xFB, 0x6D, 0xFD, 0xB4, 0x00, 0xA3, 0x00, 0xFE, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xA3, 0x00, 0xB4, 0x00, + 0x6D, 0xFD, 0x89, 0xFB, 0x83, 0x05, 0xA9, 0x18, 0x73, 0x21, 0x66, + 0x15, 0xB5, 0x02, 0x40, 0xFB, 0x30, 0xFE, 0xE6, 0x00, 0x7A, 0x00, + 0xF8, 0xFF, 0xFE, 0xFF, 0x0B, 0x00, 0xCA, 0x00, 0x62, 0x00, 0xAE, + 0xFC, 0x3B, 0xFC, 0xA9, 0x08, 0x91, 0x1B, 0xB7, 0x20, 0xEC, 0x11, + 0x51, 0x00, 0x52, 0xFB, 0xEB, 0xFE, 0xFB, 0x00, 0x53, 0x00, 0xF7, + 0xFF, 0xFB, 0xFF, 0x1F, 0x00, 0xE9, 0x00, 0xEE, 0xFF, 0x06, 0xFC, + 0x60, 0xFD, 0x10, 0x0C, 0x01, 0x1E, 0x49, 0x1F, 0x5D, 0x0E, 0x62, + 0xFE, 0xAD, 0xFB, 0x90, 0xFF, 0xF6, 0x00, 0x31, 0x00, 0xF9, 0xFF, + 0xF8, 0xFF, 0x3C, 0x00, 0xFA, 0x00, 0x5A, 0xFF, 0x86, 0xFB, 0xFF, + 0xFE, 0x99, 0x0F, 0xDB, 0x1F, 0x38, 0x1D, 0xDD, 0x0A, 0xED, 0xFC, + 0x3D, 0xFC, 0x1A, 0x00, 0xE0, 0x00, 0x17, 0x00, 0xFC, 0xFF, 0xF7, + 0xFF, 0x60, 0x00, 0xF7, 0x00, 0xAC, 0xFE, 0x43, 0xFB, 0x18, 0x01, + 0x24, 0x13, 0x0D, 0x21, 0x9C, 0x1A, 0x8A, 0x07, 0xF0, 0xFB, 0xEF, + 0xFC, 0x82, 0x00, 0xBD, 0x00, 0x05, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, + 0x88, 0x00, 0xD9, 0x00, 0xED, 0xFD, 0x4E, 0xFB, 0xA2, 0x03, 0x8F, + 0x16, 0x88, 0x21, 0x90, 0x17, 0x80, 0x04, 0x65, 0xFB, 0xB1, 0xFD, + 0xC9, 0x00, 0x95, 0x00, 0xFB, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x02, + 0x00, 0xB1, 0x00, 0x9C, 0x00, 0x29, 0xFD, 0xBA, 0xFB, 0x91, 0x06, + 0xB6, 0x19, 0x47, 0x21, 0x36, 0x14, 0xD4, 0x01, 0x3D, 0xFB, 0x72, + 0xFE, 0xF0, 0x00, 0x6C, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x11, 0x00, + 0xD6, 0x00, 0x3D, 0x00, 0x71, 0xFC, 0x93, 0xFC, 0xD1, 0x09, 0x78, + 0x1C, 0x4C, 0x20, 0xB1, 0x10, 0x98, 0xFF, 0x6A, 0xFB, 0x27, 0xFF, + 0xFC, 0x00, 0x46, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x28, 0x00, 0xF1, + 0x00, 0xBE, 0xFF, 0xD4, 0xFB, 0xE2, 0xFD, 0x46, 0x0D, 0xB7, 0x1E, + 0xA3, 0x1E, 0x24, 0x0D, 0xD3, 0xFD, 0xD9, 0xFB, 0xC3, 0xFF, 0xF0, + 0x00, 0x27, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x48, 0x00, 0xFC, 0x00, + 0x20, 0xFF, 0x67, 0xFB, 0xAC, 0xFF, 0xD4, 0x10, 0x59, 0x20, 0x5F, + 0x1C, 0xAF, 0x09, 0x88, 0xFC, 0x77, 0xFC, 0x42, 0x00, 0xD5, 0x00, + 0x10, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x6D, 0x00, 0xEF, 0x00, 0x6B, + 0xFE, 0x3D, 0xFB, 0xED, 0x01, 0x59, 0x14, 0x4D, 0x21, 0x99, 0x19, + 0x73, 0x06, 0xB4, 0xFB, 0x31, 0xFD, 0x9F, 0x00, 0xB0, 0x00, 0x01, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFB, 0xFF, 0x96, 0x00, 0xC7, 0x00, + 0xA9, 0xFD, 0x68, 0xFB, 0x9C, 0x04, 0xB0, 0x17, 0x86, 0x21, 0x6E, + 0x16, 0x87, 0x03, 0x4C, 0xFB, 0xF5, 0xFD, 0xDA, 0x00, 0x86, 0x00, + 0xF9, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0xBF, 0x00, 0x7F, 0x00, 0xE7, + 0xFC, 0xF8, 0xFB, 0xA9, 0x07, 0xB8, 0x1A, 0x04, 0x21, 0x02, 0x13, + 0x01, 0x01, 0x44, 0xFB, 0xB3, 0xFE, 0xF7, 0x00, 0x5E, 0x00, 0xF7, + 0xFF, 0xFC, 0xFF, 0x18, 0x00, 0xE1, 0x00, 0x15, 0x00, 0x36, 0xFC, + 0xF9, 0xFC, 0xFF, 0x0A, 0x4F, 0x1D, 0xCC, 0x1F, 0x76, 0x0F, 0xED, + 0xFE, 0x8A, 0xFB, 0x60, 0xFF, 0xFA, 0x00, 0x3B, 0x00, 0xF8, 0xFF, + 0xF9, 0xFF, 0x32, 0x00, 0xF7, 0x00, 0x8A, 0xFF, 0xA8, 0xFB, 0x73, + 0xFE, 0x80, 0x0E, 0x5A, 0x1F, 0xEB, 0x1D, 0xED, 0x0B, 0x53, 0xFD, + 0x0C, 0xFC, 0xF3, 0xFF, 0xE8, 0x00, 0x1E, 0x00, 0xFB, 0xFF, 0xF7, + 0xFF, 0x54, 0x00, 0xFA, 0x00, 0xE4, 0xFE, 0x50, 0xFB, 0x66, 0x00, + 0x0F, 0x12, 0xC2, 0x20, 0x77, 0x1B, 0x89, 0x08, 0x32, 0xFC, 0xB5, + 0xFC, 0x66, 0x00, 0xC9, 0x00, 0x0A, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, + 0x7B, 0x00, 0xE5, 0x00, 0x29, 0xFE, 0x41, 0xFB, 0xCF, 0x02, 0x87, + 0x15, 0x76, 0x21, 0x8A, 0x18, 0x66, 0x05, 0x84, 0xFB, 0x74, 0xFD, + 0xB7, 0x00, 0xA2, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0xFF, 0xA5, 0x00, 0xB2, 0x00, 0x65, 0xFD, 0x8E, 0xFB, 0xA1, 0x05, + 0xC7, 0x18, 0x6F, 0x21, 0x45, 0x15, 0x9B, 0x02, 0x40, 0xFB, 0x38, + 0xFE, 0xE7, 0x00, 0x78, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0B, 0x00, + 0xCC, 0x00, 0x5E, 0x00, 0xA7, 0xFC, 0x44, 0xFC, 0xCA, 0x08, 0xAC, + 0x1B, 0xAD, 0x20, 0xC9, 0x11, 0x3B, 0x00, 0x54, 0xFB, 0xF1, 0xFE, + 0xFB, 0x00, 0x51, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x20, 0x00, 0xEA, + 0x00, 0xE8, 0xFF, 0x00, 0xFC, 0x6E, 0xFD, 0x32, 0x0C, 0x16, 0x1E, + 0x38, 0x1F, 0x3A, 0x0E, 0x51, 0xFE, 0xB1, 0xFB, 0x96, 0xFF, 0xF6, + 0x00, 0x30, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x3D, 0x00, 0xFA, 0x00, + 0x54, 0xFF, 0x82, 0xFB, 0x12, 0xFF, 0xBC, 0x0F, 0xEA, 0x1F, 0x21, + 0x1D, 0xBB, 0x0A, 0xE1, 0xFC, 0x43, 0xFC, 0x1E, 0x00, 0xDF, 0x00, + 0x16, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x61, 0x00, 0xF6, 0x00, 0xA5, + 0xFE, 0x42, 0xFB, 0x2F, 0x01, 0x47, 0x13, 0x15, 0x21, 0x80, 0x1A, + 0x6A, 0x07, 0xE9, 0xFB, 0xF6, 0xFC, 0x86, 0x00, 0xBC, 0x00, 0x05, + 0x00, 0xFF, 0xFF, 0xFA, 0xFF, 0x8A, 0x00, 0xD7, 0x00, 0xE6, 0xFD, + 0x51, 0xFB, 0xBD, 0x03, 0xB0, 0x16, 0x89, 0x21, 0x71, 0x17, 0x63, + 0x04, 0x61, 0xFB, 0xB8, 0xFD, 0xCB, 0x00, 0x93, 0x00, 0xFB, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, 0xB3, 0x00, 0x99, 0x00, 0x22, + 0xFD, 0xC0, 0xFB, 0xB0, 0x06, 0xD4, 0x19, 0x41, 0x21, 0x14, 0x14, + 0xBC, 0x01, 0x3D, 0xFB, 0x7A, 0xFE, 0xF1, 0x00, 0x6A, 0x00, 0xF7, + 0xFF, 0xFD, 0xFF, 0x12, 0x00, 0xD7, 0x00, 0x39, 0x00, 0x6A, 0xFC, + 0x9D, 0xFC, 0xF2, 0x09, 0x91, 0x1C, 0x3F, 0x20, 0x8E, 0x10, 0x84, + 0xFF, 0x6D, 0xFB, 0x2D, 0xFF, 0xFC, 0x00, 0x45, 0x00, 0xF7, 0xFF, + 0xFA, 0xFF, 0x29, 0x00, 0xF2, 0x00, 0xB8, 0xFF, 0xCF, 0xFB, 0xF1, + 0xFD, 0x69, 0x0D, 0xCA, 0x1E, 0x90, 0x1E, 0x01, 0x0D, 0xC4, 0xFD, + 0xDF, 0xFB, 0xC9, 0xFF, 0xF0, 0x00, 0x26, 0x00, 0xFA, 0xFF, 0xF7, + 0xFF, 0x49, 0x00, 0xFC, 0x00, 0x1A, 0xFF, 0x64, 0xFB, 0xC0, 0xFF, + 0xF7, 0x10, 0x66, 0x20, 0x46, 0x1C, 0x8E, 0x09, 0x7E, 0xFC, 0x7E, + 0xFC, 0x46, 0x00, 0xD4, 0x00, 0x0F, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, + 0x6F, 0x00, 0xEE, 0x00, 0x64, 0xFE, 0x3D, 0xFB, 0x05, 0x02, 0x7B, + 0x14, 0x53, 0x21, 0x7C, 0x19, 0x54, 0x06, 0xAE, 0xFB, 0x38, 0xFD, + 0xA1, 0x00, 0xAE, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFC, + 0xFF, 0x98, 0x00, 0xC5, 0x00, 0xA2, 0xFD, 0x6C, 0xFB, 0xB8, 0x04, + 0xCF, 0x17, 0x85, 0x21, 0x4E, 0x16, 0x6C, 0x03, 0x4A, 0xFB, 0xFC, + 0xFD, 0xDC, 0x00, 0x85, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0x07, 0x00, + 0xC0, 0x00, 0x7B, 0x00, 0xE0, 0xFC, 0x00, 0xFC, 0xC9, 0x07, 0xD3, + 0x1A, 0xFC, 0x20, 0xDF, 0x12, 0xEA, 0x00, 0x46, 0xFB, 0xBA, 0xFE, + 0xF8, 0x00, 0x5D, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x19, 0x00, 0xE2, + 0x00, 0x10, 0x00, 0x30, 0xFC, 0x05, 0xFD, 0x21, 0x0B, 0x66, 0x1D, + 0xBD, 0x1F, 0x53, 0x0F, 0xDB, 0xFE, 0x8E, 0xFB, 0x66, 0xFF, 0xFA, + 0x00, 0x3A, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x34, 0x00, 0xF7, 0x00, + 0x84, 0xFF, 0xA4, 0xFB, 0x84, 0xFE, 0xA3, 0x0E, 0x6C, 0x1F, 0xD6, + 0x1D, 0xCB, 0x0B, 0x45, 0xFD, 0x12, 0xFC, 0xF8, 0xFF, 0xE7, 0x00, + 0x1D, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x56, 0x00, 0xFA, 0x00, 0xDD, + 0xFE, 0x4E, 0xFB, 0x7C, 0x00, 0x32, 0x12, 0xCC, 0x20, 0x5C, 0x1B, + 0x69, 0x08, 0x29, 0xFC, 0xBC, 0xFC, 0x69, 0x00, 0xC7, 0x00, 0x09, + 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7D, 0x00, 0xE3, 0x00, 0x21, 0xFE, + 0x43, 0xFB, 0xE9, 0x02, 0xA9, 0x15, 0x79, 0x21, 0x6B, 0x18, 0x48, + 0x05, 0x80, 0xFB, 0x7C, 0xFD, 0xB9, 0x00, 0xA0, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA6, 0x00, 0xAF, 0x00, 0x5E, + 0xFD, 0x93, 0xFB, 0xBE, 0x05, 0xE5, 0x18, 0x6B, 0x21, 0x23, 0x15, + 0x82, 0x02, 0x3F, 0xFB, 0x3F, 0xFE, 0xE9, 0x00, 0x77, 0x00, 0xF8, + 0xFF, 0xFE, 0xFF, 0x0C, 0x00, 0xCD, 0x00, 0x5A, 0x00, 0xA0, 0xFC, + 0x4D, 0xFC, 0xEA, 0x08, 0xC6, 0x1B, 0xA1, 0x20, 0xA6, 0x11, 0x26, + 0x00, 0x57, 0xFB, 0xF8, 0xFE, 0xFB, 0x00, 0x50, 0x00, 0xF7, 0xFF, + 0xFB, 0xFF, 0x21, 0x00, 0xEB, 0x00, 0xE3, 0xFF, 0xFA, 0xFB, 0x7C, + 0xFD, 0x54, 0x0C, 0x2B, 0x1E, 0x26, 0x1F, 0x17, 0x0E, 0x41, 0xFE, + 0xB6, 0xFB, 0x9C, 0xFF, 0xF5, 0x00, 0x2F, 0x00, 0xF9, 0xFF, 0xF8, + 0xFF, 0x3F, 0x00, 0xFB, 0x00, 0x4D, 0xFF, 0x7F, 0xFB, 0x24, 0xFF, + 0xDF, 0x0F, 0xF9, 0x1F, 0x09, 0x1D, 0x99, 0x0A, 0xD5, 0xFC, 0x49, + 0xFC, 0x23, 0x00, 0xDD, 0x00, 0x16, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, + 0x63, 0x00, 0xF5, 0x00, 0x9E, 0xFE, 0x41, 0xFB, 0x46, 0x01, 0x69, + 0x13, 0x1D, 0x21, 0x63, 0x1A, 0x4B, 0x07, 0xE2, 0xFB, 0xFD, 0xFC, + 0x89, 0x00, 0xBA, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0xFA, 0xFF, 0x8B, + 0x00, 0xD5, 0x00, 0xDE, 0xFD, 0x53, 0xFB, 0xD9, 0x03, 0xD0, 0x16, + 0x8A, 0x21, 0x51, 0x17, 0x47, 0x04, 0x5E, 0xFB, 0xC0, 0xFD, 0xCD, + 0x00, 0x92, 0x00, 0xFB, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, + 0xB4, 0x00, 0x96, 0x00, 0x1B, 0xFD, 0xC7, 0xFB, 0xCF, 0x06, 0xF0, + 0x19, 0x3A, 0x21, 0xF2, 0x13, 0xA4, 0x01, 0x3E, 0xFB, 0x81, 0xFE, + 0xF2, 0x00, 0x69, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x12, 0x00, 0xD9, + 0x00, 0x35, 0x00, 0x63, 0xFC, 0xA8, 0xFC, 0x13, 0x0A, 0xA9, 0x1C, + 0x32, 0x20, 0x6B, 0x10, 0x71, 0xFF, 0x71, 0xFB, 0x34, 0xFF, 0xFB, + 0x00, 0x44, 0x00, 0xF8, 0xFF, 0xFA, 0xFF, 0x2B, 0x00, 0xF3, 0x00, + 0xB3, 0xFF, 0xCA, 0xFB, 0x01, 0xFE, 0x8C, 0x0D, 0xDD, 0x1E, 0x7C, + 0x1E, 0xDE, 0x0C, 0xB5, 0xFD, 0xE4, 0xFB, 0xCE, 0xFF, 0xEF, 0x00, + 0x25, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x4A, 0x00, 0xFC, 0x00, 0x13, + 0xFF, 0x61, 0xFB, 0xD4, 0xFF, 0x1A, 0x11, 0x72, 0x20, 0x2D, 0x1C, + 0x6D, 0x09, 0x74, 0xFC, 0x85, 0xFC, 0x4A, 0x00, 0xD2, 0x00, 0x0F, + 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x70, 0x00, 0xED, 0x00, 0x5D, 0xFE, + 0x3D, 0xFB, 0x1E, 0x02, 0x9C, 0x14, 0x58, 0x21, 0x5E, 0x19, 0x36, + 0x06, 0xA8, 0xFB, 0x40, 0xFD, 0xA4, 0x00, 0xAD, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0xFC, 0xFF, 0x9A, 0x00, 0xC3, 0x00, 0x9A, + 0xFD, 0x6F, 0xFB, 0xD5, 0x04, 0xEF, 0x17, 0x83, 0x21, 0x2D, 0x16, + 0x52, 0x03, 0x49, 0xFB, 0x04, 0xFE, 0xDD, 0x00, 0x83, 0x00, 0xF9, + 0xFF, 0xFF, 0xFF, 0x07, 0x00, 0xC2, 0x00, 0x78, 0x00, 0xD9, 0xFC, + 0x08, 0xFC, 0xE9, 0x07, 0xEF, 0x1A, 0xF3, 0x20, 0xBC, 0x12, 0xD4, + 0x00, 0x47, 0xFB, 0xC1, 0xFE, 0xF8, 0x00, 0x5B, 0x00, 0xF7, 0xFF, + 0xFC, 0xFF, 0x1A, 0x00, 0xE3, 0x00, 0x0B, 0x00, 0x2A, 0xFC, 0x12, + 0xFD, 0x42, 0x0B, 0x7D, 0x1D, 0xAD, 0x1F, 0x2F, 0x0F, 0xC9, 0xFE, + 0x92, 0xFB, 0x6C, 0xFF, 0xF9, 0x00, 0x38, 0x00, 0xF8, 0xFF, 0xF9, + 0xFF, 0x35, 0x00, 0xF8, 0x00, 0x7E, 0xFF, 0x9F, 0xFB, 0x95, 0xFE, + 0xC6, 0x0E, 0x7C, 0x1F, 0xC0, 0x1D, 0xA9, 0x0B, 0x38, 0xFD, 0x18, + 0xFC, 0xFD, 0xFF, 0xE6, 0x00, 0x1D, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, + 0x57, 0x00, 0xFA, 0x00, 0xD6, 0xFE, 0x4C, 0xFB, 0x92, 0x00, 0x54, + 0x12, 0xD6, 0x20, 0x41, 0x1B, 0x49, 0x08, 0x20, 0xFC, 0xC3, 0xFC, + 0x6D, 0x00, 0xC6, 0x00, 0x09, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7E, + 0x00, 0xE2, 0x00, 0x1A, 0xFE, 0x44, 0xFB, 0x03, 0x03, 0xCA, 0x15, + 0x7C, 0x21, 0x4C, 0x18, 0x2B, 0x05, 0x7B, 0xFB, 0x83, 0xFD, 0xBC, + 0x00, 0x9E, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xA8, 0x00, 0xAD, 0x00, 0x56, 0xFD, 0x98, 0xFB, 0xDC, 0x05, 0x04, + 0x19, 0x66, 0x21, 0x02, 0x15, 0x69, 0x02, 0x3E, 0xFB, 0x47, 0xFE, + 0xEA, 0x00, 0x75, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0D, 0x00, 0xCE, + 0x00, 0x56, 0x00, 0x99, 0xFC, 0x56, 0xFC, 0x0B, 0x09, 0xE0, 0x1B, + 0x96, 0x20, 0x83, 0x11, 0x11, 0x00, 0x59, 0xFB, 0xFF, 0xFE, 0xFB, + 0x00, 0x4E, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x22, 0x00, 0xEC, 0x00, + 0xDE, 0xFF, 0xF5, 0xFB, 0x8A, 0xFD, 0x77, 0x0C, 0x3F, 0x1E, 0x14, + 0x1F, 0xF5, 0x0D, 0x31, 0xFE, 0xBB, 0xFB, 0xA2, 0xFF, 0xF5, 0x00, + 0x2E, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x40, 0x00, 0xFB, 0x00, 0x47, + 0xFF, 0x7B, 0xFB, 0x37, 0xFF, 0x02, 0x10, 0x07, 0x20, 0xF2, 0x1C, + 0x78, 0x0A, 0xCA, 0xFC, 0x50, 0xFC, 0x27, 0x00, 0xDC, 0x00, 0x15, + 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x64, 0x00, 0xF5, 0x00, 0x97, 0xFE, + 0x40, 0xFB, 0x5D, 0x01, 0x8B, 0x13, 0x25, 0x21, 0x47, 0x1A, 0x2C, + 0x07, 0xDB, 0xFB, 0x05, 0xFD, 0x8C, 0x00, 0xB9, 0x00, 0x04, 0x00, + 0xFF, 0xFF, 0xFA, 0xFF, 0x8D, 0x00, 0xD3, 0x00, 0xD6, 0xFD, 0x56, + 0xFB, 0xF4, 0x03, 0xF0, 0x16, 0x8A, 0x21, 0x31, 0x17, 0x2B, 0x04, + 0x5B, 0xFB, 0xC7, 0xFD, 0xCF, 0x00, 0x90, 0x00, 0xFA, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0x03, 0x00, 0xB6, 0x00, 0x92, 0x00, 0x13, 0xFD, + 0xCD, 0xFB, 0xEE, 0x06, 0x0D, 0x1A, 0x33, 0x21, 0xD0, 0x13, 0x8C, + 0x01, 0x3E, 0xFB, 0x88, 0xFE, 0xF3, 0x00, 0x67, 0x00, 0xF7, 0xFF, + 0x06, 0x00, 0x1D, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0xA1, 0x02, 0xA6, + 0xF8, 0x56, 0x02, 0xA5, 0x28, 0xA5, 0x28, 0x56, 0x02, 0xA6, 0xF8, + 0xA1, 0x02, 0xFE, 0x00, 0x03, 0xFF, 0x1D, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x21, 0x00, 0xA6, 0xFF, 0x3F, 0xFF, 0x0B, 0x03, 0x42, 0xFE, + 0x3E, 0xF8, 0x7F, 0x15, 0xAC, 0x30, 0x7F, 0x15, 0x3E, 0xF8, 0x42, + 0xFE, 0x0B, 0x03, 0x3F, 0xFF, 0xA6, 0xFF, 0x21, 0x00, 0x00, 0x00, + 0xFA, 0xFF, 0xCE, 0xFF, 0x14, 0x01, 0x00, 0xFD, 0x35, 0x06, 0xD5, + 0xF4, 0xDA, 0x15, 0x92, 0x40, 0xAE, 0xFE, 0xF3, 0xFC, 0x68, 0x03, + 0x86, 0xFD, 0x51, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xEC, + 0xFF, 0xF9, 0xFF, 0xC6, 0x00, 0x55, 0xFD, 0x35, 0x06, 0x90, 0xF3, + 0xE5, 0x1C, 0x6B, 0x3D, 0x71, 0xFA, 0x34, 0xFF, 0x46, 0x02, 0xFF, + 0xFD, 0x2D, 0x01, 0x90, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDB, 0xFF, + 0x2D, 0x00, 0x60, 0x00, 0xE1, 0xFD, 0xCE, 0x05, 0xED, 0xF2, 0xF3, + 0x23, 0x20, 0x39, 0x22, 0xF7, 0x44, 0x01, 0x1F, 0x01, 0x89, 0xFE, + 0xFB, 0x00, 0x9C, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC9, 0xFF, 0x68, + 0x00, 0xE5, 0xFF, 0xA0, 0xFE, 0xFB, 0x04, 0x0C, 0xF3, 0xC5, 0x2A, + 0xD8, 0x33, 0xC9, 0xF4, 0x0B, 0x03, 0x05, 0x00, 0x1A, 0xFF, 0xC1, + 0x00, 0xAD, 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB5, 0xFF, 0xA5, 0x00, + 0x5C, 0xFF, 0x8C, 0xFF, 0xBF, 0x03, 0x06, 0xF4, 0x22, 0x31, 0xC8, + 0x2D, 0x63, 0xF3, 0x76, 0x04, 0x08, 0xFF, 0xA7, 0xFF, 0x84, 0x00, + 0xC0, 0xFF, 0x07, 0x00, 0x0C, 0x00, 0xA4, 0xFF, 0xE1, 0x00, 0xCB, + 0xFE, 0x9B, 0x00, 0x21, 0x02, 0xEE, 0xF5, 0xCD, 0x36, 0x24, 0x27, + 0xE1, 0xF2, 0x7A, 0x05, 0x33, 0xFE, 0x2A, 0x00, 0x47, 0x00, 0xD3, + 0xFF, 0x04, 0x00, 0x0F, 0x00, 0x95, 0xFF, 0x17, 0x01, 0x3D, 0xFE, + 0xBD, 0x01, 0x30, 0x00, 0xCC, 0xF8, 0x92, 0x3B, 0x2A, 0x20, 0x2E, + 0xF3, 0x12, 0x06, 0x8F, 0xFD, 0x9A, 0x00, 0x10, 0x00, 0xE5, 0xFF, + 0x02, 0x00, 0x10, 0x00, 0x8C, 0xFF, 0x42, 0x01, 0xBB, 0xFD, 0xE4, + 0x02, 0x01, 0xFE, 0x9C, 0xFC, 0x45, 0x3F, 0x16, 0x19, 0x2D, 0xF4, + 0x41, 0x06, 0x21, 0xFD, 0xF3, 0x00, 0xE0, 0xFF, 0xF4, 0xFF, 0x01, + 0x00, 0x10, 0x00, 0x8B, 0xFF, 0x5D, 0x01, 0x4F, 0xFD, 0xFB, 0x03, + 0xB2, 0xFB, 0x53, 0x01, 0xC2, 0x41, 0x24, 0x12, 0xBA, 0xF5, 0x0F, + 0x06, 0xE9, 0xFC, 0x33, 0x01, 0xBB, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0x93, 0xFF, 0x63, 0x01, 0x04, 0xFD, 0xEF, 0x04, 0x62, + 0xF9, 0xD7, 0x06, 0xF2, 0x42, 0x8D, 0x0B, 0xB0, 0xF7, 0x87, 0x05, + 0xE6, 0xFC, 0x58, 0x01, 0xA0, 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x00, 0xA5, 0xFF, 0x52, 0x01, 0xE2, 0xFC, 0xAD, 0x05, + 0x35, 0xF7, 0x08, 0x0D, 0xCB, 0x42, 0x81, 0x05, 0xE8, 0xF9, 0xBB, + 0x04, 0x12, 0xFD, 0x64, 0x01, 0x90, 0xFF, 0x0E, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0xC2, 0xFF, 0x27, 0x01, 0xF1, 0xFC, 0x22, 0x06, 0x54, + 0xF5, 0xB8, 0x13, 0x4A, 0x41, 0x29, 0x00, 0x3C, 0xFC, 0xBD, 0x03, + 0x66, 0xFD, 0x58, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xF1, + 0xFF, 0xEB, 0xFF, 0xE1, 0x00, 0x35, 0xFD, 0x40, 0x06, 0xE4, 0xF3, + 0xB7, 0x1A, 0x85, 0x3E, 0xA6, 0xFB, 0x86, 0xFE, 0xA0, 0x02, 0xD7, + 0xFD, 0x39, 0x01, 0x8E, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xE1, 0xFF, + 0x1C, 0x00, 0x82, 0x00, 0xB0, 0xFD, 0xF9, 0x05, 0x0C, 0xF3, 0xCB, + 0x21, 0x8F, 0x3A, 0x0D, 0xF8, 0xA9, 0x00, 0x79, 0x01, 0x5D, 0xFE, + 0x0B, 0x01, 0x98, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCE, 0xFF, 0x55, + 0x00, 0x0D, 0x00, 0x60, 0xFE, 0x48, 0x05, 0xEC, 0xF2, 0xB6, 0x28, + 0x91, 0x35, 0x68, 0xF5, 0x88, 0x02, 0x5A, 0x00, 0xED, 0xFE, 0xD4, + 0x00, 0xA8, 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0x92, 0x00, + 0x87, 0xFF, 0x3F, 0xFF, 0x2B, 0x04, 0xA1, 0xF3, 0x3D, 0x2F, 0xB8, + 0x2F, 0xB8, 0xF3, 0x11, 0x04, 0x52, 0xFF, 0x7C, 0xFF, 0x97, 0x00, + 0xBA, 0xFF, 0x08, 0x00, 0x0B, 0x00, 0xA9, 0xFF, 0xCF, 0x00, 0xF8, + 0xFE, 0x44, 0x00, 0xAA, 0x02, 0x3E, 0xF5, 0x24, 0x35, 0x3B, 0x29, + 0xF2, 0xF2, 0x35, 0x05, 0x70, 0xFE, 0x03, 0x00, 0x5A, 0x00, 0xCD, + 0xFF, 0x05, 0x00, 0x0E, 0x00, 0x99, 0xFF, 0x07, 0x01, 0x68, 0xFE, + 0x63, 0x01, 0xD0, 0x00, 0xD0, 0xF7, 0x35, 0x3A, 0x55, 0x22, 0x02, + 0xF3, 0xEF, 0x05, 0xBC, 0xFD, 0x7A, 0x00, 0x20, 0x00, 0xDF, 0xFF, + 0x03, 0x00, 0x10, 0x00, 0x8E, 0xFF, 0x36, 0x01, 0xE1, 0xFD, 0x8A, + 0x02, 0xB2, 0xFE, 0x56, 0xFB, 0x40, 0x3E, 0x42, 0x1B, 0xCE, 0xF3, + 0x3E, 0x06, 0x3D, 0xFD, 0xDB, 0x00, 0xEE, 0xFF, 0xF0, 0xFF, 0x01, + 0x00, 0x11, 0x00, 0x8A, 0xFF, 0x57, 0x01, 0x6D, 0xFD, 0xA8, 0x03, + 0x69, 0xFC, 0xC8, 0xFF, 0x20, 0x41, 0x40, 0x14, 0x33, 0xF5, 0x28, + 0x06, 0xF5, 0xFC, 0x22, 0x01, 0xC5, 0xFF, 0xFD, 0xFF, 0x00, 0x00, + 0x0F, 0x00, 0x8F, 0xFF, 0x64, 0x01, 0x17, 0xFD, 0xA9, 0x04, 0x16, + 0xFA, 0x10, 0x05, 0xB8, 0x42, 0x87, 0x0D, 0x0D, 0xF7, 0xB9, 0x05, + 0xE2, 0xFC, 0x50, 0x01, 0xA7, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x00, 0x9E, 0xFF, 0x5A, 0x01, 0xE8, 0xFC, 0x7A, 0x05, + 0xDA, 0xF7, 0x10, 0x0B, 0xFB, 0x42, 0x4B, 0x07, 0x35, 0xF9, 0x00, + 0x05, 0x00, 0xFD, 0x63, 0x01, 0x94, 0xFF, 0x0D, 0x00, 0x00, 0x00, + 0x01, 0x00, 0xB8, 0xFF, 0x37, 0x01, 0xE7, 0xFC, 0x07, 0x06, 0xDE, + 0xF5, 0x9F, 0x11, 0xE4, 0x41, 0xB8, 0x01, 0x84, 0xFB, 0x0F, 0x04, + 0x48, 0xFD, 0x5E, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF5, + 0xFF, 0xDD, 0xFF, 0xF9, 0x00, 0x1B, 0xFD, 0x41, 0x06, 0x47, 0xF4, + 0x8B, 0x18, 0x81, 0x3F, 0xF1, 0xFC, 0xD5, 0xFD, 0xFA, 0x02, 0xB2, + 0xFD, 0x45, 0x01, 0x8C, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE6, 0xFF, + 0x0C, 0x00, 0xA2, 0x00, 0x85, 0xFD, 0x1A, 0x06, 0x3C, 0xF3, 0x9F, + 0x1F, 0xE6, 0x3B, 0x0E, 0xF9, 0x07, 0x00, 0xD4, 0x01, 0x33, 0xFE, + 0x1B, 0x01, 0x94, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD4, 0xFF, 0x43, + 0x00, 0x33, 0x00, 0x25, 0xFE, 0x89, 0x05, 0xE0, 0xF2, 0x9C, 0x26, + 0x33, 0x37, 0x1E, 0xF6, 0xFD, 0x01, 0xB0, 0x00, 0xC0, 0xFE, 0xE6, + 0x00, 0xA2, 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xC1, 0xFF, 0x7F, 0x00, + 0xB2, 0xFF, 0xF6, 0xFE, 0x8E, 0x04, 0x51, 0xF3, 0x49, 0x2D, 0x98, + 0x31, 0x23, 0xF4, 0xA2, 0x03, 0xA0, 0xFF, 0x51, 0xFF, 0xAA, 0x00, + 0xB4, 0xFF, 0x09, 0x00, 0x0A, 0x00, 0xAE, 0xFF, 0xBD, 0x00, 0x25, + 0xFF, 0xF1, 0xFF, 0x2B, 0x03, 0xA5, 0xF4, 0x68, 0x33, 0x48, 0x2B, + 0x17, 0xF3, 0xE7, 0x04, 0xB1, 0xFE, 0xDB, 0xFF, 0x6C, 0x00, 0xC7, + 0xFF, 0x06, 0x00, 0x0D, 0x00, 0x9E, 0xFF, 0xF7, 0x00, 0x94, 0xFE, + 0x09, 0x01, 0x6A, 0x01, 0xEB, 0xF6, 0xC1, 0x38, 0x7D, 0x24, 0xE8, + 0xF2, 0xC1, 0x05, 0xEE, 0xFD, 0x57, 0x00, 0x31, 0x00, 0xDA, 0xFF, + 0x03, 0x00, 0x10, 0x00, 0x91, 0xFF, 0x29, 0x01, 0x09, 0xFE, 0x2F, + 0x02, 0x5F, 0xFF, 0x27, 0xFA, 0x20, 0x3D, 0x70, 0x1D, 0x7D, 0xF3, + 0x31, 0x06, 0x5E, 0xFD, 0xBF, 0x00, 0xFD, 0xFF, 0xEB, 0xFF, 0x02, + 0x00, 0x11, 0x00, 0x8B, 0xFF, 0x4E, 0x01, 0x8E, 0xFD, 0x52, 0x03, + 0x20, 0xFD, 0x52, 0xFE, 0x60, 0x40, 0x63, 0x16, 0xB7, 0xF4, 0x39, + 0x06, 0x05, 0xFD, 0x0F, 0x01, 0xD1, 0xFF, 0xF9, 0xFF, 0x00, 0x00, + 0x10, 0x00, 0x8D, 0xFF, 0x62, 0x01, 0x2E, 0xFD, 0x5E, 0x04, 0xCC, + 0xFA, 0x5B, 0x03, 0x5E, 0x42, 0x8E, 0x0F, 0x71, 0xF6, 0xE4, 0x05, + 0xE2, 0xFC, 0x45, 0x01, 0xAF, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0B, 0x00, 0x99, 0xFF, 0x60, 0x01, 0xF2, 0xFC, 0x40, 0x05, + 0x85, 0xF8, 0x26, 0x09, 0x0C, 0x43, 0x26, 0x09, 0x85, 0xF8, 0x40, + 0x05, 0xF2, 0xFC, 0x60, 0x01, 0x99, 0xFF, 0x0B, 0x00, 0x00, 0x00, + 0x04, 0x00, 0xAF, 0xFF, 0x45, 0x01, 0xE2, 0xFC, 0xE4, 0x05, 0x71, + 0xF6, 0x8E, 0x0F, 0x5E, 0x42, 0x5B, 0x03, 0xCC, 0xFA, 0x5E, 0x04, + 0x2E, 0xFD, 0x62, 0x01, 0x8D, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xF9, + 0xFF, 0xD1, 0xFF, 0x0F, 0x01, 0x05, 0xFD, 0x39, 0x06, 0xB7, 0xF4, + 0x63, 0x16, 0x60, 0x40, 0x52, 0xFE, 0x20, 0xFD, 0x52, 0x03, 0x8E, + 0xFD, 0x4E, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xEB, 0xFF, + 0xFD, 0xFF, 0xBF, 0x00, 0x5E, 0xFD, 0x31, 0x06, 0x7D, 0xF3, 0x70, + 0x1D, 0x20, 0x3D, 0x27, 0xFA, 0x5F, 0xFF, 0x2F, 0x02, 0x09, 0xFE, + 0x29, 0x01, 0x91, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDA, 0xFF, 0x31, + 0x00, 0x57, 0x00, 0xEE, 0xFD, 0xC1, 0x05, 0xE8, 0xF2, 0x7D, 0x24, + 0xC1, 0x38, 0xEB, 0xF6, 0x6A, 0x01, 0x09, 0x01, 0x94, 0xFE, 0xF7, + 0x00, 0x9E, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC7, 0xFF, 0x6C, 0x00, + 0xDB, 0xFF, 0xB1, 0xFE, 0xE7, 0x04, 0x17, 0xF3, 0x48, 0x2B, 0x68, + 0x33, 0xA5, 0xF4, 0x2B, 0x03, 0xF1, 0xFF, 0x25, 0xFF, 0xBD, 0x00, + 0xAE, 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB4, 0xFF, 0xAA, 0x00, 0x51, + 0xFF, 0xA0, 0xFF, 0xA2, 0x03, 0x23, 0xF4, 0x98, 0x31, 0x49, 0x2D, + 0x51, 0xF3, 0x8E, 0x04, 0xF6, 0xFE, 0xB2, 0xFF, 0x7F, 0x00, 0xC1, + 0xFF, 0x07, 0x00, 0x0C, 0x00, 0xA2, 0xFF, 0xE6, 0x00, 0xC0, 0xFE, + 0xB0, 0x00, 0xFD, 0x01, 0x1E, 0xF6, 0x33, 0x37, 0x9C, 0x26, 0xE0, + 0xF2, 0x89, 0x05, 0x25, 0xFE, 0x33, 0x00, 0x43, 0x00, 0xD4, 0xFF, + 0x04, 0x00, 0x0F, 0x00, 0x94, 0xFF, 0x1B, 0x01, 0x33, 0xFE, 0xD4, + 0x01, 0x07, 0x00, 0x0E, 0xF9, 0xE6, 0x3B, 0x9F, 0x1F, 0x3C, 0xF3, + 0x1A, 0x06, 0x85, 0xFD, 0xA2, 0x00, 0x0C, 0x00, 0xE6, 0xFF, 0x02, + 0x00, 0x11, 0x00, 0x8C, 0xFF, 0x45, 0x01, 0xB2, 0xFD, 0xFA, 0x02, + 0xD5, 0xFD, 0xF1, 0xFC, 0x81, 0x3F, 0x8B, 0x18, 0x47, 0xF4, 0x41, + 0x06, 0x1B, 0xFD, 0xF9, 0x00, 0xDD, 0xFF, 0xF5, 0xFF, 0x01, 0x00, + 0x10, 0x00, 0x8B, 0xFF, 0x5E, 0x01, 0x48, 0xFD, 0x0F, 0x04, 0x84, + 0xFB, 0xB8, 0x01, 0xE4, 0x41, 0x9F, 0x11, 0xDE, 0xF5, 0x07, 0x06, + 0xE7, 0xFC, 0x37, 0x01, 0xB8, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x0D, + 0x00, 0x94, 0xFF, 0x63, 0x01, 0x00, 0xFD, 0x00, 0x05, 0x35, 0xF9, + 0x4B, 0x07, 0xFB, 0x42, 0x10, 0x0B, 0xDA, 0xF7, 0x7A, 0x05, 0xE8, + 0xFC, 0x5A, 0x01, 0x9E, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0xA7, 0xFF, 0x50, 0x01, 0xE2, 0xFC, 0xB9, 0x05, 0x0D, + 0xF7, 0x87, 0x0D, 0xB8, 0x42, 0x10, 0x05, 0x16, 0xFA, 0xA9, 0x04, + 0x17, 0xFD, 0x64, 0x01, 0x8F, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0xC5, 0xFF, 0x22, 0x01, 0xF5, 0xFC, 0x28, 0x06, 0x33, 0xF5, + 0x40, 0x14, 0x20, 0x41, 0xC8, 0xFF, 0x69, 0xFC, 0xA8, 0x03, 0x6D, + 0xFD, 0x57, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xF0, 0xFF, + 0xEE, 0xFF, 0xDB, 0x00, 0x3D, 0xFD, 0x3E, 0x06, 0xCE, 0xF3, 0x42, + 0x1B, 0x40, 0x3E, 0x56, 0xFB, 0xB2, 0xFE, 0x8A, 0x02, 0xE1, 0xFD, + 0x36, 0x01, 0x8E, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDF, 0xFF, 0x20, + 0x00, 0x7A, 0x00, 0xBC, 0xFD, 0xEF, 0x05, 0x02, 0xF3, 0x55, 0x22, + 0x35, 0x3A, 0xD0, 0xF7, 0xD0, 0x00, 0x63, 0x01, 0x68, 0xFE, 0x07, + 0x01, 0x99, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCD, 0xFF, 0x5A, 0x00, + 0x03, 0x00, 0x70, 0xFE, 0x35, 0x05, 0xF2, 0xF2, 0x3B, 0x29, 0x24, + 0x35, 0x3E, 0xF5, 0xAA, 0x02, 0x44, 0x00, 0xF8, 0xFE, 0xCF, 0x00, + 0xA9, 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xBA, 0xFF, 0x97, 0x00, 0x7C, + 0xFF, 0x52, 0xFF, 0x11, 0x04, 0xB8, 0xF3, 0xB8, 0x2F, 0x3D, 0x2F, + 0xA1, 0xF3, 0x2B, 0x04, 0x3F, 0xFF, 0x87, 0xFF, 0x92, 0x00, 0xBB, + 0xFF, 0x08, 0x00, 0x0B, 0x00, 0xA8, 0xFF, 0xD4, 0x00, 0xED, 0xFE, + 0x5A, 0x00, 0x88, 0x02, 0x68, 0xF5, 0x91, 0x35, 0xB6, 0x28, 0xEC, + 0xF2, 0x48, 0x05, 0x60, 0xFE, 0x0D, 0x00, 0x55, 0x00, 0xCE, 0xFF, + 0x05, 0x00, 0x0E, 0x00, 0x98, 0xFF, 0x0B, 0x01, 0x5D, 0xFE, 0x79, + 0x01, 0xA9, 0x00, 0x0D, 0xF8, 0x8F, 0x3A, 0xCB, 0x21, 0x0C, 0xF3, + 0xF9, 0x05, 0xB0, 0xFD, 0x82, 0x00, 0x1C, 0x00, 0xE1, 0xFF, 0x03, + 0x00, 0x10, 0x00, 0x8E, 0xFF, 0x39, 0x01, 0xD7, 0xFD, 0xA0, 0x02, + 0x86, 0xFE, 0xA6, 0xFB, 0x85, 0x3E, 0xB7, 0x1A, 0xE4, 0xF3, 0x40, + 0x06, 0x35, 0xFD, 0xE1, 0x00, 0xEB, 0xFF, 0xF1, 0xFF, 0x01, 0x00, + 0x11, 0x00, 0x8A, 0xFF, 0x58, 0x01, 0x66, 0xFD, 0xBD, 0x03, 0x3C, + 0xFC, 0x29, 0x00, 0x4A, 0x41, 0xB8, 0x13, 0x54, 0xF5, 0x22, 0x06, + 0xF1, 0xFC, 0x27, 0x01, 0xC2, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0x0E, + 0x00, 0x90, 0xFF, 0x64, 0x01, 0x12, 0xFD, 0xBB, 0x04, 0xE8, 0xF9, + 0x81, 0x05, 0xCB, 0x42, 0x08, 0x0D, 0x35, 0xF7, 0xAD, 0x05, 0xE2, + 0xFC, 0x52, 0x01, 0xA5, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0xA0, 0xFF, 0x58, 0x01, 0xE6, 0xFC, 0x87, 0x05, 0xB0, + 0xF7, 0x8D, 0x0B, 0xF2, 0x42, 0xD7, 0x06, 0x62, 0xF9, 0xEF, 0x04, + 0x04, 0xFD, 0x63, 0x01, 0x93, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xBB, 0xFF, 0x33, 0x01, 0xE9, 0xFC, 0x0F, 0x06, 0xBA, 0xF5, + 0x24, 0x12, 0xC2, 0x41, 0x53, 0x01, 0xB2, 0xFB, 0xFB, 0x03, 0x4F, + 0xFD, 0x5D, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF4, 0xFF, + 0xE0, 0xFF, 0xF3, 0x00, 0x21, 0xFD, 0x41, 0x06, 0x2D, 0xF4, 0x16, + 0x19, 0x45, 0x3F, 0x9C, 0xFC, 0x01, 0xFE, 0xE4, 0x02, 0xBB, 0xFD, + 0x42, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE5, 0xFF, 0x10, + 0x00, 0x9A, 0x00, 0x8F, 0xFD, 0x12, 0x06, 0x2E, 0xF3, 0x2A, 0x20, + 0x92, 0x3B, 0xCC, 0xF8, 0x30, 0x00, 0xBD, 0x01, 0x3D, 0xFE, 0x17, + 0x01, 0x95, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD3, 0xFF, 0x47, 0x00, + 0x2A, 0x00, 0x33, 0xFE, 0x7A, 0x05, 0xE1, 0xF2, 0x24, 0x27, 0xCD, + 0x36, 0xEE, 0xF5, 0x21, 0x02, 0x9B, 0x00, 0xCB, 0xFE, 0xE1, 0x00, + 0xA4, 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0x84, 0x00, 0xA7, + 0xFF, 0x08, 0xFF, 0x76, 0x04, 0x63, 0xF3, 0xC8, 0x2D, 0x22, 0x31, + 0x06, 0xF4, 0xBF, 0x03, 0x8C, 0xFF, 0x5C, 0xFF, 0xA5, 0x00, 0xB5, + 0xFF, 0x09, 0x00, 0x0A, 0x00, 0xAD, 0xFF, 0xC1, 0x00, 0x1A, 0xFF, + 0x05, 0x00, 0x0B, 0x03, 0xC9, 0xF4, 0xD8, 0x33, 0xC5, 0x2A, 0x0C, + 0xF3, 0xFB, 0x04, 0xA0, 0xFE, 0xE5, 0xFF, 0x68, 0x00, 0xC9, 0xFF, + 0x06, 0x00, 0x0D, 0x00, 0x9C, 0xFF, 0xFB, 0x00, 0x89, 0xFE, 0x1F, + 0x01, 0x44, 0x01, 0x22, 0xF7, 0x20, 0x39, 0xF3, 0x23, 0xED, 0xF2, + 0xCE, 0x05, 0xE1, 0xFD, 0x60, 0x00, 0x2D, 0x00, 0xDB, 0xFF, 0x03, + 0x00, 0x10, 0x00, 0x90, 0xFF, 0x2D, 0x01, 0xFF, 0xFD, 0x46, 0x02, + 0x34, 0xFF, 0x71, 0xFA, 0x6B, 0x3D, 0xE5, 0x1C, 0x90, 0xF3, 0x35, + 0x06, 0x55, 0xFD, 0xC6, 0x00, 0xF9, 0xFF, 0xEC, 0xFF, 0x01, 0x00, + 0x11, 0x00, 0x8B, 0xFF, 0x51, 0x01, 0x86, 0xFD, 0x68, 0x03, 0xF3, + 0xFC, 0xAE, 0xFE, 0x92, 0x40, 0xDA, 0x15, 0xD5, 0xF4, 0x35, 0x06, + 0x00, 0xFD, 0x14, 0x01, 0xCE, 0xFF, 0xFA, 0xFF, 0x00, 0x00, 0x0F, + 0x00, 0x8D, 0xFF, 0x63, 0x01, 0x28, 0xFD, 0x71, 0x04, 0x9E, 0xFA, + 0xC7, 0x03, 0x79, 0x42, 0x0B, 0x0F, 0x97, 0xF6, 0xDA, 0x05, 0xE2, + 0xFC, 0x48, 0x01, 0xAD, 0xFF, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0B, 0x00, 0x9A, 0xFF, 0x5F, 0x01, 0xEF, 0xFC, 0x4F, 0x05, 0x5A, + 0xF8, 0x9F, 0x09, 0x0A, 0x43, 0xAE, 0x08, 0xB1, 0xF8, 0x30, 0x05, + 0xF5, 0xFC, 0x61, 0x01, 0x97, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x03, + 0x00, 0xB1, 0xFF, 0x41, 0x01, 0xE3, 0xFC, 0xED, 0x05, 0x4C, 0xF6, + 0x11, 0x10, 0x42, 0x42, 0xF1, 0x02, 0xFA, 0xFA, 0x4B, 0x04, 0x34, + 0xFD, 0x61, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF8, 0xFF, + 0xD4, 0xFF, 0x0A, 0x01, 0x0A, 0xFD, 0x3C, 0x06, 0x9A, 0xF4, 0xED, + 0x16, 0x2A, 0x40, 0xF8, 0xFD, 0x4D, 0xFD, 0x3C, 0x03, 0x97, 0xFD, + 0x4C, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xEA, 0xFF, 0x00, + 0x00, 0xB8, 0x00, 0x67, 0xFD, 0x2C, 0x06, 0x6B, 0xF3, 0xFC, 0x1D, + 0xD3, 0x3C, 0xDF, 0xF9, 0x89, 0xFF, 0x18, 0x02, 0x13, 0xFE, 0x26, + 0x01, 0x92, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD9, 0xFF, 0x36, 0x00, + 0x4E, 0x00, 0xFB, 0xFD, 0xB4, 0x05, 0xE4, 0xF2, 0x04, 0x25, 0x5F, + 0x38, 0xB6, 0xF6, 0x90, 0x01, 0xF3, 0x00, 0x9F, 0xFE, 0xF3, 0x00, + 0x9F, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC6, 0xFF, 0x71, 0x00, 0xD1, + 0xFF, 0xC2, 0xFE, 0xD1, 0x04, 0x23, 0xF3, 0xC9, 0x2B, 0xF5, 0x32, + 0x83, 0xF4, 0x49, 0x03, 0xDC, 0xFF, 0x30, 0xFF, 0xB8, 0x00, 0xB0, + 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB3, 0xFF, 0xAE, 0x00, 0x46, 0xFF, + 0xB4, 0xFF, 0x85, 0x03, 0x42, 0xF4, 0x0E, 0x32, 0xCA, 0x2C, 0x41, + 0xF3, 0xA5, 0x04, 0xE4, 0xFE, 0xBC, 0xFF, 0x7A, 0x00, 0xC3, 0xFF, + 0x07, 0x00, 0x0D, 0x00, 0xA1, 0xFF, 0xEA, 0x00, 0xB5, 0xFE, 0xC6, + 0x00, 0xD9, 0x01, 0x4F, 0xF6, 0x99, 0x37, 0x16, 0x26, 0xE0, 0xF2, + 0x98, 0x05, 0x16, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0xD6, 0xFF, 0x04, + 0x00, 0x0F, 0x00, 0x93, 0xFF, 0x1F, 0x01, 0x28, 0xFE, 0xEB, 0x01, + 0xDD, 0xFF, 0x52, 0xF9, 0x36, 0x3C, 0x13, 0x1F, 0x4B, 0xF3, 0x20, + 0x06, 0x7B, 0xFD, 0xA9, 0x00, 0x08, 0x00, 0xE7, 0xFF, 0x02, 0x00, + 0x11, 0x00, 0x8C, 0xFF, 0x47, 0x01, 0xA9, 0xFD, 0x10, 0x03, 0xA8, + 0xFD, 0x47, 0xFD, 0xBB, 0x3F, 0x01, 0x18, 0x62, 0xF4, 0x40, 0x06, + 0x15, 0xFD, 0xFF, 0x00, 0xDA, 0xFF, 0xF6, 0xFF, 0x01, 0x00, 0x10, + 0x00, 0x8B, 0xFF, 0x5F, 0x01, 0x41, 0xFD, 0x23, 0x04, 0x56, 0xFB, + 0x1F, 0x02, 0x06, 0x42, 0x19, 0x11, 0x02, 0xF6, 0xFF, 0x05, 0xE5, + 0xFC, 0x3B, 0x01, 0xB6, 0xFF, 0x02, 0x00, 0x00, 0x00, 0x0D, 0x00, + 0x95, 0xFF, 0x62, 0x01, 0xFC, 0xFC, 0x10, 0x05, 0x09, 0xF9, 0xC1, + 0x07, 0x03, 0x43, 0x94, 0x0A, 0x05, 0xF8, 0x6C, 0x05, 0xEA, 0xFC, + 0x5C, 0x01, 0x9D, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x00, 0xA9, 0xFF, 0x4D, 0x01, 0xE1, 0xFC, 0xC4, 0x05, 0xE6, 0xF6, + 0x08, 0x0E, 0xA5, 0x42, 0xA1, 0x04, 0x43, 0xFA, 0x97, 0x04, 0x1D, + 0xFD, 0x64, 0x01, 0x8F, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFC, 0xFF, + 0xC8, 0xFF, 0x1E, 0x01, 0xF8, 0xFC, 0x2D, 0x06, 0x13, 0xF5, 0xC8, + 0x14, 0xF2, 0x40, 0x69, 0xFF, 0x97, 0xFC, 0x92, 0x03, 0x75, 0xFD, + 0x55, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xEF, 0xFF, 0xF2, + 0xFF, 0xD4, 0x00, 0x45, 0xFD, 0x3B, 0x06, 0xB8, 0xF3, 0xCE, 0x1B, + 0xFB, 0x3D, 0x08, 0xFB, 0xDE, 0xFE, 0x73, 0x02, 0xEB, 0xFD, 0x33, + 0x01, 0x8F, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDE, 0xFF, 0x25, 0x00, + 0x71, 0x00, 0xC8, 0xFD, 0xE5, 0x05, 0xFA, 0xF2, 0xDF, 0x22, 0xDB, + 0x39, 0x94, 0xF7, 0xF7, 0x00, 0x4C, 0x01, 0x73, 0xFE, 0x03, 0x01, + 0x9A, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCC, 0xFF, 0x5E, 0x00, 0xF9, + 0xFF, 0x80, 0xFE, 0x23, 0x05, 0xF9, 0xF2, 0xC0, 0x29, 0xB8, 0x34, + 0x16, 0xF5, 0xCB, 0x02, 0x2F, 0x00, 0x03, 0xFF, 0xCA, 0x00, 0xAA, + 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xB8, 0xFF, 0x9B, 0x00, 0x72, 0xFF, + 0x65, 0xFF, 0xF6, 0x03, 0xD1, 0xF3, 0x31, 0x30, 0xC1, 0x2E, 0x8B, + 0xF3, 0x45, 0x04, 0x2D, 0xFF, 0x92, 0xFF, 0x8D, 0x00, 0xBD, 0xFF, + 0x08, 0x00, 0x0C, 0x00, 0xA6, 0xFF, 0xD8, 0x00, 0xE2, 0xFE, 0x6F, + 0x00, 0x66, 0x02, 0x93, 0xF5, 0xFB, 0x35, 0x31, 0x28, 0xE7, 0xF2, + 0x59, 0x05, 0x51, 0xFE, 0x17, 0x00, 0x50, 0x00, 0xD0, 0xFF, 0x05, + 0x00, 0x0E, 0x00, 0x97, 0xFF, 0x0F, 0x01, 0x53, 0xFE, 0x90, 0x01, + 0x81, 0x00, 0x4B, 0xF8, 0xE6, 0x3A, 0x3F, 0x21, 0x16, 0xF3, 0x02, + 0x06, 0xA5, 0xFD, 0x8A, 0x00, 0x18, 0x00, 0xE2, 0xFF, 0x02, 0x00, + 0x10, 0x00, 0x8D, 0xFF, 0x3C, 0x01, 0xCE, 0xFD, 0xB7, 0x02, 0x5A, + 0xFE, 0xF7, 0xFB, 0xC6, 0x3E, 0x2C, 0x1A, 0xFC, 0xF3, 0x41, 0x06, + 0x2E, 0xFD, 0xE7, 0x00, 0xE7, 0xFF, 0xF2, 0xFF, 0x01, 0x00, 0x10, + 0x00, 0x8B, 0xFF, 0x5A, 0x01, 0x5E, 0xFD, 0xD2, 0x03, 0x0E, 0xFC, + 0x8B, 0x00, 0x75, 0x41, 0x32, 0x13, 0x75, 0xF5, 0x1C, 0x06, 0xEE, + 0xFC, 0x2B, 0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0E, 0x00, + 0x91, 0xFF, 0x64, 0x01, 0x0D, 0xFD, 0xCD, 0x04, 0xBB, 0xF9, 0xF2, + 0x05, 0xD9, 0x42, 0x88, 0x0C, 0x5E, 0xF7, 0xA1, 0x05, 0xE3, 0xFC, + 0x54, 0x01, 0xA3, 0xFF, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x00, 0xA2, 0xFF, 0x56, 0x01, 0xE5, 0xFC, 0x94, 0x05, 0x87, 0xF7, + 0x0A, 0x0C, 0xE6, 0x42, 0x64, 0x06, 0x8E, 0xF9, 0xDE, 0x04, 0x09, + 0xFD, 0x64, 0x01, 0x92, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xBD, 0xFF, 0x2F, 0x01, 0xEC, 0xFC, 0x16, 0x06, 0x98, 0xF5, 0xAB, + 0x12, 0x9C, 0x41, 0xEE, 0x00, 0xE0, 0xFB, 0xE6, 0x03, 0x57, 0xFD, + 0x5B, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF3, 0xFF, 0xE4, + 0xFF, 0xED, 0x00, 0x27, 0xFD, 0x41, 0x06, 0x14, 0xF4, 0xA1, 0x19, + 0x06, 0x3F, 0x49, 0xFC, 0x2E, 0xFE, 0xCD, 0x02, 0xC4, 0xFD, 0x3F, + 0x01, 0x8D, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE3, 0xFF, 0x14, 0x00, + 0x92, 0x00, 0x9A, 0xFD, 0x0A, 0x06, 0x22, 0xF3, 0xB4, 0x20, 0x3C, + 0x3B, 0x8B, 0xF8, 0x58, 0x00, 0xA7, 0x01, 0x48, 0xFE, 0x13, 0x01, + 0x96, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD1, 0xFF, 0x4C, 0x00, 0x20, + 0x00, 0x42, 0xFE, 0x6A, 0x05, 0xE3, 0xF2, 0xAB, 0x27, 0x66, 0x36, + 0xC0, 0xF5, 0x44, 0x02, 0x85, 0x00, 0xD7, 0xFE, 0xDD, 0x00, 0xA5, + 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xBE, 0xFF, 0x89, 0x00, 0x9D, 0xFF, + 0x1A, 0xFF, 0x5E, 0x04, 0x76, 0xF3, 0x45, 0x2E, 0xAA, 0x30, 0xEB, + 0xF3, 0xDB, 0x03, 0x79, 0xFF, 0x67, 0xFF, 0xA0, 0x00, 0xB7, 0xFF, + 0x09, 0x00, 0x0B, 0x00, 0xAC, 0xFF, 0xC6, 0x00, 0x0E, 0xFF, 0x1A, + 0x00, 0xEB, 0x02, 0xEF, 0xF4, 0x49, 0x34, 0x43, 0x2A, 0x02, 0xF3, + 0x0F, 0x05, 0x90, 0xFE, 0xEF, 0xFF, 0x63, 0x00, 0xCA, 0xFF, 0x06, + 0x00, 0x0E, 0x00, 0x9B, 0xFF, 0xFF, 0x00, 0x7E, 0xFE, 0x36, 0x01, + 0x1E, 0x01, 0x5B, 0xF7, 0x7E, 0x39, 0x69, 0x23, 0xF3, 0xF2, 0xD9, + 0x05, 0xD4, 0xFD, 0x69, 0x00, 0x29, 0x00, 0xDD, 0xFF, 0x03, 0x00, + 0x10, 0x00, 0x90, 0xFF, 0x30, 0x01, 0xF5, 0xFD, 0x5C, 0x02, 0x09, + 0xFF, 0xBC, 0xFA, 0xB5, 0x3D, 0x5A, 0x1C, 0xA3, 0xF3, 0x38, 0x06, + 0x4D, 0xFD, 0xCD, 0x00, 0xF5, 0xFF, 0xED, 0xFF, 0x01, 0x00, 0x11, + 0x00, 0x8B, 0xFF, 0x53, 0x01, 0x7E, 0xFD, 0x7D, 0x03, 0xC5, 0xFC, + 0x0B, 0xFF, 0xC3, 0x40, 0x51, 0x15, 0xF4, 0xF4, 0x31, 0x06, 0xFC, + 0xFC, 0x19, 0x01, 0xCB, 0xFF, 0xFB, 0xFF, 0x00, 0x00, 0x0F, 0x00, + 0x8E, 0xFF, 0x63, 0x01, 0x22, 0xFD, 0x84, 0x04, 0x71, 0xFA, 0x34, + 0x04, 0x90, 0x42, 0x89, 0x0E, 0xBE, 0xF6, 0xCF, 0x05, 0xE1, 0xFC, + 0x4A, 0x01, 0xAB, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, + 0x00, 0x9B, 0xFF, 0x5D, 0x01, 0xEC, 0xFC, 0x5D, 0x05, 0x2F, 0xF8, + 0x19, 0x0A, 0x07, 0x43, 0x37, 0x08, 0xDD, 0xF8, 0x21, 0x05, 0xF8, + 0xFC, 0x62, 0x01, 0x96, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, + 0xB4, 0xFF, 0x3E, 0x01, 0xE4, 0xFC, 0xF6, 0x05, 0x26, 0xF6, 0x95, + 0x10, 0x26, 0x42, 0x87, 0x02, 0x28, 0xFB, 0x37, 0x04, 0x3B, 0xFD, + 0x60, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF7, 0xFF, 0xD7, + 0xFF, 0x04, 0x01, 0x0F, 0xFD, 0x3E, 0x06, 0x7D, 0xF4, 0x76, 0x17, + 0xF4, 0x3F, 0x9F, 0xFD, 0x7B, 0xFD, 0x26, 0x03, 0xA0, 0xFD, 0x4A, + 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE9, 0xFF, 0x04, 0x00, + 0xB1, 0x00, 0x71, 0xFD, 0x26, 0x06, 0x5A, 0xF3, 0x88, 0x1E, 0x87, + 0x3C, 0x98, 0xF9, 0xB3, 0xFF, 0x02, 0x02, 0x1E, 0xFE, 0x22, 0x01, + 0x93, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD7, 0xFF, 0x3A, 0x00, 0x45, + 0x00, 0x09, 0xFE, 0xA7, 0x05, 0xE1, 0xF2, 0x8D, 0x25, 0xFD, 0x37, + 0x82, 0xF6, 0xB5, 0x01, 0xDC, 0x00, 0xAA, 0xFE, 0xEE, 0x00, 0xA0, + 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC4, 0xFF, 0x76, 0x00, 0xC7, 0xFF, + 0xD3, 0xFE, 0xBC, 0x04, 0x31, 0xF3, 0x4A, 0x2C, 0x83, 0x32, 0x61, + 0xF4, 0x68, 0x03, 0xC8, 0xFF, 0x3B, 0xFF, 0xB3, 0x00, 0xB1, 0xFF, + 0x0A, 0x00, 0x0A, 0x00, 0xB1, 0xFF, 0xB3, 0x00, 0x3B, 0xFF, 0xC8, + 0xFF, 0x68, 0x03, 0x61, 0xF4, 0x83, 0x32, 0x4A, 0x2C, 0x31, 0xF3, + 0xBC, 0x04, 0xD3, 0xFE, 0xC7, 0xFF, 0x76, 0x00, 0xC4, 0xFF, 0x06, + 0x00, 0x0D, 0x00, 0xA0, 0xFF, 0xEE, 0x00, 0xAA, 0xFE, 0xDC, 0x00, + 0xB5, 0x01, 0x82, 0xF6, 0xFD, 0x37, 0x8D, 0x25, 0xE1, 0xF2, 0xA7, + 0x05, 0x09, 0xFE, 0x45, 0x00, 0x3A, 0x00, 0xD7, 0xFF, 0x04, 0x00, + 0x0F, 0x00, 0x93, 0xFF, 0x22, 0x01, 0x1E, 0xFE, 0x02, 0x02, 0xB3, + 0xFF, 0x98, 0xF9, 0x87, 0x3C, 0x88, 0x1E, 0x5A, 0xF3, 0x26, 0x06, + 0x71, 0xFD, 0xB1, 0x00, 0x04, 0x00, 0xE9, 0xFF, 0x02, 0x00, 0x11, + 0x00, 0x8B, 0xFF, 0x4A, 0x01, 0xA0, 0xFD, 0x26, 0x03, 0x7B, 0xFD, + 0x9F, 0xFD, 0xF4, 0x3F, 0x76, 0x17, 0x7D, 0xF4, 0x3E, 0x06, 0x0F, + 0xFD, 0x04, 0x01, 0xD7, 0xFF, 0xF7, 0xFF, 0x01, 0x00, 0x10, 0x00, + 0x8C, 0xFF, 0x60, 0x01, 0x3B, 0xFD, 0x37, 0x04, 0x28, 0xFB, 0x87, + 0x02, 0x26, 0x42, 0x95, 0x10, 0x26, 0xF6, 0xF6, 0x05, 0xE4, 0xFC, + 0x3E, 0x01, 0xB4, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x96, + 0xFF, 0x62, 0x01, 0xF8, 0xFC, 0x21, 0x05, 0xDD, 0xF8, 0x37, 0x08, + 0x07, 0x43, 0x19, 0x0A, 0x2F, 0xF8, 0x5D, 0x05, 0xEC, 0xFC, 0x5D, + 0x01, 0x9B, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0xAB, 0xFF, 0x4A, 0x01, 0xE1, 0xFC, 0xCF, 0x05, 0xBE, 0xF6, 0x89, + 0x0E, 0x90, 0x42, 0x34, 0x04, 0x71, 0xFA, 0x84, 0x04, 0x22, 0xFD, + 0x63, 0x01, 0x8E, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFB, 0xFF, 0xCB, + 0xFF, 0x19, 0x01, 0xFC, 0xFC, 0x31, 0x06, 0xF4, 0xF4, 0x51, 0x15, + 0xC3, 0x40, 0x0B, 0xFF, 0xC5, 0xFC, 0x7D, 0x03, 0x7E, 0xFD, 0x53, + 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xED, 0xFF, 0xF5, 0xFF, + 0xCD, 0x00, 0x4D, 0xFD, 0x38, 0x06, 0xA3, 0xF3, 0x5A, 0x1C, 0xB5, + 0x3D, 0xBC, 0xFA, 0x09, 0xFF, 0x5C, 0x02, 0xF5, 0xFD, 0x30, 0x01, + 0x90, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDD, 0xFF, 0x29, 0x00, 0x69, + 0x00, 0xD4, 0xFD, 0xD9, 0x05, 0xF3, 0xF2, 0x69, 0x23, 0x7E, 0x39, + 0x5B, 0xF7, 0x1E, 0x01, 0x36, 0x01, 0x7E, 0xFE, 0xFF, 0x00, 0x9B, + 0xFF, 0x0E, 0x00, 0x06, 0x00, 0xCA, 0xFF, 0x63, 0x00, 0xEF, 0xFF, + 0x90, 0xFE, 0x0F, 0x05, 0x02, 0xF3, 0x43, 0x2A, 0x49, 0x34, 0xEF, + 0xF4, 0xEB, 0x02, 0x1A, 0x00, 0x0E, 0xFF, 0xC6, 0x00, 0xAC, 0xFF, + 0x0B, 0x00, 0x09, 0x00, 0xB7, 0xFF, 0xA0, 0x00, 0x67, 0xFF, 0x79, + 0xFF, 0xDB, 0x03, 0xEB, 0xF3, 0xAA, 0x30, 0x45, 0x2E, 0x76, 0xF3, + 0x5E, 0x04, 0x1A, 0xFF, 0x9D, 0xFF, 0x89, 0x00, 0xBE, 0xFF, 0x07, + 0x00, 0x0C, 0x00, 0xA5, 0xFF, 0xDD, 0x00, 0xD7, 0xFE, 0x85, 0x00, + 0x44, 0x02, 0xC0, 0xF5, 0x66, 0x36, 0xAB, 0x27, 0xE3, 0xF2, 0x6A, + 0x05, 0x42, 0xFE, 0x20, 0x00, 0x4C, 0x00, 0xD1, 0xFF, 0x04, 0x00, + 0x0F, 0x00, 0x96, 0xFF, 0x13, 0x01, 0x48, 0xFE, 0xA7, 0x01, 0x58, + 0x00, 0x8B, 0xF8, 0x3C, 0x3B, 0xB4, 0x20, 0x22, 0xF3, 0x0A, 0x06, + 0x9A, 0xFD, 0x92, 0x00, 0x14, 0x00, 0xE3, 0xFF, 0x02, 0x00, 0x10, + 0x00, 0x8D, 0xFF, 0x3F, 0x01, 0xC4, 0xFD, 0xCD, 0x02, 0x2E, 0xFE, + 0x49, 0xFC, 0x06, 0x3F, 0xA1, 0x19, 0x14, 0xF4, 0x41, 0x06, 0x27, + 0xFD, 0xED, 0x00, 0xE4, 0xFF, 0xF3, 0xFF, 0x01, 0x00, 0x10, 0x00, + 0x8B, 0xFF, 0x5B, 0x01, 0x57, 0xFD, 0xE6, 0x03, 0xE0, 0xFB, 0xEE, + 0x00, 0x9C, 0x41, 0xAB, 0x12, 0x98, 0xF5, 0x16, 0x06, 0xEC, 0xFC, + 0x2F, 0x01, 0xBD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x92, + 0xFF, 0x64, 0x01, 0x09, 0xFD, 0xDE, 0x04, 0x8E, 0xF9, 0x64, 0x06, + 0xE6, 0x42, 0x0A, 0x0C, 0x87, 0xF7, 0x94, 0x05, 0xE5, 0xFC, 0x56, + 0x01, 0xA2, 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0xA3, 0xFF, 0x54, 0x01, 0xE3, 0xFC, 0xA1, 0x05, 0x5E, 0xF7, 0x88, + 0x0C, 0xD9, 0x42, 0xF2, 0x05, 0xBB, 0xF9, 0xCD, 0x04, 0x0D, 0xFD, + 0x64, 0x01, 0x91, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, + 0xFF, 0x2B, 0x01, 0xEE, 0xFC, 0x1C, 0x06, 0x75, 0xF5, 0x32, 0x13, + 0x75, 0x41, 0x8B, 0x00, 0x0E, 0xFC, 0xD2, 0x03, 0x5E, 0xFD, 0x5A, + 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF2, 0xFF, 0xE7, 0xFF, + 0xE7, 0x00, 0x2E, 0xFD, 0x41, 0x06, 0xFC, 0xF3, 0x2C, 0x1A, 0xC6, + 0x3E, 0xF7, 0xFB, 0x5A, 0xFE, 0xB7, 0x02, 0xCE, 0xFD, 0x3C, 0x01, + 0x8D, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE2, 0xFF, 0x18, 0x00, 0x8A, + 0x00, 0xA5, 0xFD, 0x02, 0x06, 0x16, 0xF3, 0x3F, 0x21, 0xE6, 0x3A, + 0x4B, 0xF8, 0x81, 0x00, 0x90, 0x01, 0x53, 0xFE, 0x0F, 0x01, 0x97, + 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xD0, 0xFF, 0x50, 0x00, 0x17, 0x00, + 0x51, 0xFE, 0x59, 0x05, 0xE7, 0xF2, 0x31, 0x28, 0xFB, 0x35, 0x93, + 0xF5, 0x66, 0x02, 0x6F, 0x00, 0xE2, 0xFE, 0xD8, 0x00, 0xA6, 0xFF, + 0x0C, 0x00, 0x08, 0x00, 0xBD, 0xFF, 0x8D, 0x00, 0x92, 0xFF, 0x2D, + 0xFF, 0x45, 0x04, 0x8B, 0xF3, 0xC1, 0x2E, 0x31, 0x30, 0xD1, 0xF3, + 0xF6, 0x03, 0x65, 0xFF, 0x72, 0xFF, 0x9B, 0x00, 0xB8, 0xFF, 0x08, + 0x00, 0x0B, 0x00, 0xAA, 0xFF, 0xCA, 0x00, 0x03, 0xFF, 0x2F, 0x00, + 0xCB, 0x02, 0x16, 0xF5, 0xB8, 0x34, 0xC0, 0x29, 0xF9, 0xF2, 0x23, + 0x05, 0x80, 0xFE, 0xF9, 0xFF, 0x5E, 0x00, 0xCC, 0xFF, 0x05, 0x00, + 0x0E, 0x00, 0x9A, 0xFF, 0x03, 0x01, 0x73, 0xFE, 0x4C, 0x01, 0xF7, + 0x00, 0x94, 0xF7, 0xDB, 0x39, 0xDF, 0x22, 0xFA, 0xF2, 0xE5, 0x05, + 0xC8, 0xFD, 0x71, 0x00, 0x25, 0x00, 0xDE, 0xFF, 0x03, 0x00, 0x10, + 0x00, 0x8F, 0xFF, 0x33, 0x01, 0xEB, 0xFD, 0x73, 0x02, 0xDE, 0xFE, + 0x08, 0xFB, 0xFB, 0x3D, 0xCE, 0x1B, 0xB8, 0xF3, 0x3B, 0x06, 0x45, + 0xFD, 0xD4, 0x00, 0xF2, 0xFF, 0xEF, 0xFF, 0x01, 0x00, 0x11, 0x00, + 0x8A, 0xFF, 0x55, 0x01, 0x75, 0xFD, 0x92, 0x03, 0x97, 0xFC, 0x69, + 0xFF, 0xF2, 0x40, 0xC8, 0x14, 0x13, 0xF5, 0x2D, 0x06, 0xF8, 0xFC, + 0x1E, 0x01, 0xC8, 0xFF, 0xFC, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0x8F, + 0xFF, 0x64, 0x01, 0x1D, 0xFD, 0x97, 0x04, 0x43, 0xFA, 0xA1, 0x04, + 0xA5, 0x42, 0x08, 0x0E, 0xE6, 0xF6, 0xC4, 0x05, 0xE1, 0xFC, 0x4D, + 0x01, 0xA9, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, + 0x9D, 0xFF, 0x5C, 0x01, 0xEA, 0xFC, 0x6C, 0x05, 0x05, 0xF8, 0x94, + 0x0A, 0x03, 0x43, 0xC1, 0x07, 0x09, 0xF9, 0x10, 0x05, 0xFC, 0xFC, + 0x62, 0x01, 0x95, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x02, 0x00, 0xB6, + 0xFF, 0x3B, 0x01, 0xE5, 0xFC, 0xFF, 0x05, 0x02, 0xF6, 0x19, 0x11, + 0x06, 0x42, 0x1F, 0x02, 0x56, 0xFB, 0x23, 0x04, 0x41, 0xFD, 0x5F, + 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF6, 0xFF, 0xDA, 0xFF, + 0xFF, 0x00, 0x15, 0xFD, 0x40, 0x06, 0x62, 0xF4, 0x01, 0x18, 0xBB, + 0x3F, 0x47, 0xFD, 0xA8, 0xFD, 0x10, 0x03, 0xA9, 0xFD, 0x47, 0x01, + 0x8C, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE7, 0xFF, 0x08, 0x00, 0xA9, + 0x00, 0x7B, 0xFD, 0x20, 0x06, 0x4B, 0xF3, 0x13, 0x1F, 0x36, 0x3C, + 0x52, 0xF9, 0xDD, 0xFF, 0xEB, 0x01, 0x28, 0xFE, 0x1F, 0x01, 0x93, + 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD6, 0xFF, 0x3F, 0x00, 0x3C, 0x00, + 0x16, 0xFE, 0x98, 0x05, 0xE0, 0xF2, 0x16, 0x26, 0x99, 0x37, 0x4F, + 0xF6, 0xD9, 0x01, 0xC6, 0x00, 0xB5, 0xFE, 0xEA, 0x00, 0xA1, 0xFF, + 0x0D, 0x00, 0x07, 0x00, 0xC3, 0xFF, 0x7A, 0x00, 0xBC, 0xFF, 0xE4, + 0xFE, 0xA5, 0x04, 0x41, 0xF3, 0xCA, 0x2C, 0x0E, 0x32, 0x42, 0xF4, + 0x85, 0x03, 0xB4, 0xFF, 0x46, 0xFF, 0xAE, 0x00, 0xB3, 0xFF, 0x09, + 0x00, 0x0A, 0x00, 0xB0, 0xFF, 0xB8, 0x00, 0x30, 0xFF, 0xDC, 0xFF, + 0x49, 0x03, 0x83, 0xF4, 0xF5, 0x32, 0xC9, 0x2B, 0x23, 0xF3, 0xD1, + 0x04, 0xC2, 0xFE, 0xD1, 0xFF, 0x71, 0x00, 0xC6, 0xFF, 0x06, 0x00, + 0x0D, 0x00, 0x9F, 0xFF, 0xF3, 0x00, 0x9F, 0xFE, 0xF3, 0x00, 0x90, + 0x01, 0xB6, 0xF6, 0x5F, 0x38, 0x04, 0x25, 0xE4, 0xF2, 0xB4, 0x05, + 0xFB, 0xFD, 0x4E, 0x00, 0x36, 0x00, 0xD9, 0xFF, 0x04, 0x00, 0x0F, + 0x00, 0x92, 0xFF, 0x26, 0x01, 0x13, 0xFE, 0x18, 0x02, 0x89, 0xFF, + 0xDF, 0xF9, 0xD3, 0x3C, 0xFC, 0x1D, 0x6B, 0xF3, 0x2C, 0x06, 0x67, + 0xFD, 0xB8, 0x00, 0x00, 0x00, 0xEA, 0xFF, 0x02, 0x00, 0x11, 0x00, + 0x8B, 0xFF, 0x4C, 0x01, 0x97, 0xFD, 0x3C, 0x03, 0x4D, 0xFD, 0xF8, + 0xFD, 0x2A, 0x40, 0xED, 0x16, 0x9A, 0xF4, 0x3C, 0x06, 0x0A, 0xFD, + 0x0A, 0x01, 0xD4, 0xFF, 0xF8, 0xFF, 0x01, 0x00, 0x10, 0x00, 0x8C, + 0xFF, 0x61, 0x01, 0x34, 0xFD, 0x4B, 0x04, 0xFA, 0xFA, 0xF1, 0x02, + 0x42, 0x42, 0x11, 0x10, 0x4C, 0xF6, 0xED, 0x05, 0xE3, 0xFC, 0x41, + 0x01, 0xB1, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x97, 0xFF, + 0x61, 0x01, 0xF5, 0xFC, 0x30, 0x05, 0xB1, 0xF8, 0xAE, 0x08, 0x0A, + 0x43, 0x9F, 0x09, 0x5A, 0xF8, 0x4F, 0x05, 0xEF, 0xFC, 0x5F, 0x01, + 0x9A, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xAD, + 0xFF, 0x48, 0x01, 0xE2, 0xFC, 0xDA, 0x05, 0x97, 0xF6, 0x0B, 0x0F, + 0x79, 0x42, 0xC7, 0x03, 0x9E, 0xFA, 0x71, 0x04, 0x28, 0xFD, 0x63, + 0x01, 0x8D, 0xFF, 0x0F, 0x00 +}; + +static const u16 +coefficient_sizes[8 * 2] = { + /* Playback */ + 0x00C0, 0x5000, 0x0060, 0x2800, 0x0040, 0x0060, 0x1400, 0x0000, + /* capture */ + 0x0020, 0x1260, 0x0020, 0x1260, 0x0000, 0x0040, 0x1260, 0x0000, +}; + diff --git a/sound/pci/oxygen/Makefile b/sound/pci/oxygen/Makefile new file mode 100644 index 000000000..cc0c24694 --- /dev/null +++ b/sound/pci/oxygen/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-oxygen-lib-y := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o +snd-oxygen-y := oxygen.o xonar_dg_mixer.o xonar_dg.o +snd-se6x-y := se6x.o +snd-virtuoso-y := virtuoso.o xonar_lib.o \ + xonar_pcm179x.o xonar_cs43xx.o xonar_wm87x6.o xonar_hdmi.o + +obj-$(CONFIG_SND_OXYGEN_LIB) += snd-oxygen-lib.o +obj-$(CONFIG_SND_OXYGEN) += snd-oxygen.o +obj-$(CONFIG_SND_SE6X) += snd-se6x.o +obj-$(CONFIG_SND_VIRTUOSO) += snd-virtuoso.o diff --git a/sound/pci/oxygen/ak4396.h b/sound/pci/oxygen/ak4396.h new file mode 100644 index 000000000..a51223461 --- /dev/null +++ b/sound/pci/oxygen/ak4396.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef AK4396_H_INCLUDED +#define AK4396_H_INCLUDED + +#define AK4396_WRITE 0x2000 + +#define AK4396_CONTROL_1 0 +#define AK4396_CONTROL_2 1 +#define AK4396_CONTROL_3 2 +#define AK4396_LCH_ATT 3 +#define AK4396_RCH_ATT 4 + +/* control 1 */ +#define AK4396_RSTN 0x01 +#define AK4396_DIF_MASK 0x0e +#define AK4396_DIF_16_LSB 0x00 +#define AK4396_DIF_20_LSB 0x02 +#define AK4396_DIF_24_MSB 0x04 +#define AK4396_DIF_24_I2S 0x06 +#define AK4396_DIF_24_LSB 0x08 +#define AK4396_ACKS 0x80 +/* control 2 */ +#define AK4396_SMUTE 0x01 +#define AK4396_DEM_MASK 0x06 +#define AK4396_DEM_441 0x00 +#define AK4396_DEM_OFF 0x02 +#define AK4396_DEM_48 0x04 +#define AK4396_DEM_32 0x06 +#define AK4396_DFS_MASK 0x18 +#define AK4396_DFS_NORMAL 0x00 +#define AK4396_DFS_DOUBLE 0x08 +#define AK4396_DFS_QUAD 0x10 +#define AK4396_SLOW 0x20 +#define AK4396_DZFM 0x40 +#define AK4396_DZFE 0x80 +/* control 3 */ +#define AK4396_DZFB 0x04 +#define AK4396_DCKB 0x10 +#define AK4396_DCKS 0x20 +#define AK4396_DSDM 0x40 +#define AK4396_D_P_MASK 0x80 +#define AK4396_PCM 0x00 +#define AK4396_DSD 0x80 + +#endif diff --git a/sound/pci/oxygen/cm9780.h b/sound/pci/oxygen/cm9780.h new file mode 100644 index 000000000..7efb119d1 --- /dev/null +++ b/sound/pci/oxygen/cm9780.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CM9780_H_INCLUDED +#define CM9780_H_INCLUDED + +#define CM9780_JACK 0x62 +#define CM9780_MIXER 0x64 +#define CM9780_GPIO_SETUP 0x70 +#define CM9780_GPIO_STATUS 0x72 + +/* jack control */ +#define CM9780_RSOE 0x0001 +#define CM9780_CBOE 0x0002 +#define CM9780_SSOE 0x0004 +#define CM9780_FROE 0x0008 +#define CM9780_HP2FMICOE 0x0010 +#define CM9780_CB2MICOE 0x0020 +#define CM9780_FMIC2LI 0x0040 +#define CM9780_FMIC2MIC 0x0080 +#define CM9780_HP2LI 0x0100 +#define CM9780_HP2MIC 0x0200 +#define CM9780_MIC2LI 0x0400 +#define CM9780_MIC2MIC 0x0800 +#define CM9780_LI2LI 0x1000 +#define CM9780_LI2MIC 0x2000 +#define CM9780_LO2LI 0x4000 +#define CM9780_LO2MIC 0x8000 + +/* mixer control */ +#define CM9780_BSTSEL 0x0001 +#define CM9780_STRO_MIC 0x0002 +#define CM9780_SPDI_FREX 0x0004 +#define CM9780_SPDI_SSEX 0x0008 +#define CM9780_SPDI_CBEX 0x0010 +#define CM9780_SPDI_RSEX 0x0020 +#define CM9780_MIX2FR 0x0040 +#define CM9780_MIX2SS 0x0080 +#define CM9780_MIX2CB 0x0100 +#define CM9780_MIX2RS 0x0200 +#define CM9780_MIX2FR_EX 0x0400 +#define CM9780_MIX2SS_EX 0x0800 +#define CM9780_MIX2CB_EX 0x1000 +#define CM9780_MIX2RS_EX 0x2000 +#define CM9780_P47_IO 0x4000 +#define CM9780_PCBSW 0x8000 + +/* GPIO setup */ +#define CM9780_GPI0EN 0x0001 +#define CM9780_GPI1EN 0x0002 +#define CM9780_SENSE_P 0x0004 +#define CM9780_LOCK_P 0x0008 +#define CM9780_GPIO0P 0x0010 +#define CM9780_GPIO1P 0x0020 +#define CM9780_GPIO0IO 0x0100 +#define CM9780_GPIO1IO 0x0200 + +/* GPIO status */ +#define CM9780_GPO0 0x0001 +#define CM9780_GPO1 0x0002 +#define CM9780_GPIO0S 0x0010 +#define CM9780_GPIO1S 0x0020 +#define CM9780_GPII0S 0x0100 +#define CM9780_GPII1S 0x0200 + +#endif diff --git a/sound/pci/oxygen/cs2000.h b/sound/pci/oxygen/cs2000.h new file mode 100644 index 000000000..aca04794c --- /dev/null +++ b/sound/pci/oxygen/cs2000.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CS2000_H_INCLUDED +#define CS2000_H_INCLUDED + +#define CS2000_DEV_ID 0x01 +#define CS2000_DEV_CTRL 0x02 +#define CS2000_DEV_CFG_1 0x03 +#define CS2000_DEV_CFG_2 0x04 +#define CS2000_GLOBAL_CFG 0x05 +#define CS2000_RATIO_0 0x06 /* 32 bits, big endian */ +#define CS2000_RATIO_1 0x0a +#define CS2000_RATIO_2 0x0e +#define CS2000_RATIO_3 0x12 +#define CS2000_FUN_CFG_1 0x16 +#define CS2000_FUN_CFG_2 0x17 +#define CS2000_FUN_CFG_3 0x1e + +/* DEV_ID */ +#define CS2000_DEVICE_MASK 0xf8 +#define CS2000_REVISION_MASK 0x07 + +/* DEV_CTRL */ +#define CS2000_UNLOCK 0x80 +#define CS2000_AUX_OUT_DIS 0x02 +#define CS2000_CLK_OUT_DIS 0x01 + +/* DEV_CFG_1 */ +#define CS2000_R_MOD_SEL_MASK 0xe0 +#define CS2000_R_MOD_SEL_1 0x00 +#define CS2000_R_MOD_SEL_2 0x20 +#define CS2000_R_MOD_SEL_4 0x40 +#define CS2000_R_MOD_SEL_8 0x60 +#define CS2000_R_MOD_SEL_1_2 0x80 +#define CS2000_R_MOD_SEL_1_4 0xa0 +#define CS2000_R_MOD_SEL_1_8 0xc0 +#define CS2000_R_MOD_SEL_1_16 0xe0 +#define CS2000_R_SEL_MASK 0x18 +#define CS2000_R_SEL_SHIFT 3 +#define CS2000_AUX_OUT_SRC_MASK 0x06 +#define CS2000_AUX_OUT_SRC_REF_CLK 0x00 +#define CS2000_AUX_OUT_SRC_CLK_IN 0x02 +#define CS2000_AUX_OUT_SRC_CLK_OUT 0x04 +#define CS2000_AUX_OUT_SRC_PLL_LOCK 0x06 +#define CS2000_EN_DEV_CFG_1 0x01 + +/* DEV_CFG_2 */ +#define CS2000_LOCK_CLK_MASK 0x06 +#define CS2000_LOCK_CLK_SHIFT 1 +#define CS2000_FRAC_N_SRC_MASK 0x01 +#define CS2000_FRAC_N_SRC_STATIC 0x00 +#define CS2000_FRAC_N_SRC_DYNAMIC 0x01 + +/* GLOBAL_CFG */ +#define CS2000_FREEZE 0x08 +#define CS2000_EN_DEV_CFG_2 0x01 + +/* FUN_CFG_1 */ +#define CS2000_CLK_SKIP_EN 0x80 +#define CS2000_AUX_LOCK_CFG_MASK 0x40 +#define CS2000_AUX_LOCK_CFG_PP_HIGH 0x00 +#define CS2000_AUX_LOCK_CFG_OD_LOW 0x40 +#define CS2000_REF_CLK_DIV_MASK 0x18 +#define CS2000_REF_CLK_DIV_4 0x00 +#define CS2000_REF_CLK_DIV_2 0x08 +#define CS2000_REF_CLK_DIV_1 0x10 + +/* FUN_CFG_2 */ +#define CS2000_CLK_OUT_UNL 0x10 +#define CS2000_L_F_RATIO_CFG_MASK 0x08 +#define CS2000_L_F_RATIO_CFG_20_12 0x00 +#define CS2000_L_F_RATIO_CFG_12_20 0x08 + +/* FUN_CFG_3 */ +#define CS2000_CLK_IN_BW_MASK 0x70 +#define CS2000_CLK_IN_BW_1 0x00 +#define CS2000_CLK_IN_BW_2 0x10 +#define CS2000_CLK_IN_BW_4 0x20 +#define CS2000_CLK_IN_BW_8 0x30 +#define CS2000_CLK_IN_BW_16 0x40 +#define CS2000_CLK_IN_BW_32 0x50 +#define CS2000_CLK_IN_BW_64 0x60 +#define CS2000_CLK_IN_BW_128 0x70 + +#endif diff --git a/sound/pci/oxygen/cs4245.h b/sound/pci/oxygen/cs4245.h new file mode 100644 index 000000000..bb9f2c5b5 --- /dev/null +++ b/sound/pci/oxygen/cs4245.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define CS4245_CHIP_ID 0x01 +#define CS4245_POWER_CTRL 0x02 +#define CS4245_DAC_CTRL_1 0x03 +#define CS4245_ADC_CTRL 0x04 +#define CS4245_MCLK_FREQ 0x05 +#define CS4245_SIGNAL_SEL 0x06 +#define CS4245_PGA_B_CTRL 0x07 +#define CS4245_PGA_A_CTRL 0x08 +#define CS4245_ANALOG_IN 0x09 +#define CS4245_DAC_A_CTRL 0x0a +#define CS4245_DAC_B_CTRL 0x0b +#define CS4245_DAC_CTRL_2 0x0c +#define CS4245_INT_STATUS 0x0d +#define CS4245_INT_MASK 0x0e +#define CS4245_INT_MODE_MSB 0x0f +#define CS4245_INT_MODE_LSB 0x10 + +/* Chip ID */ +#define CS4245_CHIP_PART_MASK 0xf0 +#define CS4245_CHIP_REV_MASK 0x0f + +/* Power Control */ +#define CS4245_FREEZE 0x80 +#define CS4245_PDN_MIC 0x08 +#define CS4245_PDN_ADC 0x04 +#define CS4245_PDN_DAC 0x02 +#define CS4245_PDN 0x01 + +/* DAC Control */ +#define CS4245_DAC_FM_MASK 0xc0 +#define CS4245_DAC_FM_SINGLE 0x00 +#define CS4245_DAC_FM_DOUBLE 0x40 +#define CS4245_DAC_FM_QUAD 0x80 +#define CS4245_DAC_DIF_MASK 0x30 +#define CS4245_DAC_DIF_LJUST 0x00 +#define CS4245_DAC_DIF_I2S 0x10 +#define CS4245_DAC_DIF_RJUST_16 0x20 +#define CS4245_DAC_DIF_RJUST_24 0x30 +#define CS4245_RESERVED_1 0x08 +#define CS4245_MUTE_DAC 0x04 +#define CS4245_DEEMPH 0x02 +#define CS4245_DAC_MASTER 0x01 + +/* ADC Control */ +#define CS4245_ADC_FM_MASK 0xc0 +#define CS4245_ADC_FM_SINGLE 0x00 +#define CS4245_ADC_FM_DOUBLE 0x40 +#define CS4245_ADC_FM_QUAD 0x80 +#define CS4245_ADC_DIF_MASK 0x10 +#define CS4245_ADC_DIF_LJUST 0x00 +#define CS4245_ADC_DIF_I2S 0x10 +#define CS4245_MUTE_ADC 0x04 +#define CS4245_HPF_FREEZE 0x02 +#define CS4245_ADC_MASTER 0x01 + +/* MCLK Frequency */ +#define CS4245_MCLK1_MASK 0x70 +#define CS4245_MCLK1_SHIFT 4 +#define CS4245_MCLK2_MASK 0x07 +#define CS4245_MCLK2_SHIFT 0 +#define CS4245_MCLK_1 0 +#define CS4245_MCLK_1_5 1 +#define CS4245_MCLK_2 2 +#define CS4245_MCLK_3 3 +#define CS4245_MCLK_4 4 + +/* Signal Selection */ +#define CS4245_A_OUT_SEL_MASK 0x60 +#define CS4245_A_OUT_SEL_HIZ 0x00 +#define CS4245_A_OUT_SEL_DAC 0x20 +#define CS4245_A_OUT_SEL_PGA 0x40 +#define CS4245_LOOP 0x02 +#define CS4245_ASYNCH 0x01 + +/* Channel B/A PGA Control */ +#define CS4245_PGA_GAIN_MASK 0x3f + +/* ADC Input Control */ +#define CS4245_PGA_SOFT 0x10 +#define CS4245_PGA_ZERO 0x08 +#define CS4245_SEL_MASK 0x07 +#define CS4245_SEL_MIC 0x00 +#define CS4245_SEL_INPUT_1 0x01 +#define CS4245_SEL_INPUT_2 0x02 +#define CS4245_SEL_INPUT_3 0x03 +#define CS4245_SEL_INPUT_4 0x04 +#define CS4245_SEL_INPUT_5 0x05 +#define CS4245_SEL_INPUT_6 0x06 + +/* DAC Channel A/B Volume Control */ +#define CS4245_VOL_MASK 0xff + +/* DAC Control 2 */ +#define CS4245_DAC_SOFT 0x80 +#define CS4245_DAC_ZERO 0x40 +#define CS4245_INVERT_DAC 0x20 +#define CS4245_INT_ACTIVE_HIGH 0x01 + +/* Interrupt Status/Mask/Mode */ +#define CS4245_ADC_CLK_ERR 0x08 +#define CS4245_DAC_CLK_ERR 0x04 +#define CS4245_ADC_OVFL 0x02 +#define CS4245_ADC_UNDRFL 0x01 + +#define CS4245_SPI_ADDRESS_S (0x9e << 16) +#define CS4245_SPI_WRITE_S (0 << 16) + +#define CS4245_SPI_ADDRESS 0x9e +#define CS4245_SPI_WRITE 0 +#define CS4245_SPI_READ 1 diff --git a/sound/pci/oxygen/cs4362a.h b/sound/pci/oxygen/cs4362a.h new file mode 100644 index 000000000..1aef15e04 --- /dev/null +++ b/sound/pci/oxygen/cs4362a.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* register 01h */ +#define CS4362A_PDN 0x01 +#define CS4362A_DAC1_DIS 0x02 +#define CS4362A_DAC2_DIS 0x04 +#define CS4362A_DAC3_DIS 0x08 +#define CS4362A_MCLKDIV 0x20 +#define CS4362A_FREEZE 0x40 +#define CS4362A_CPEN 0x80 +/* register 02h */ +#define CS4362A_DIF_MASK 0x70 +#define CS4362A_DIF_LJUST 0x00 +#define CS4362A_DIF_I2S 0x10 +#define CS4362A_DIF_RJUST_16 0x20 +#define CS4362A_DIF_RJUST_24 0x30 +#define CS4362A_DIF_RJUST_20 0x40 +#define CS4362A_DIF_RJUST_18 0x50 +/* register 03h */ +#define CS4362A_MUTEC_MASK 0x03 +#define CS4362A_MUTEC_6 0x00 +#define CS4362A_MUTEC_1 0x01 +#define CS4362A_MUTEC_3 0x03 +#define CS4362A_AMUTE 0x04 +#define CS4362A_MUTEC_POL 0x08 +#define CS4362A_RMP_UP 0x10 +#define CS4362A_SNGLVOL 0x20 +#define CS4362A_ZERO_CROSS 0x40 +#define CS4362A_SOFT_RAMP 0x80 +/* register 04h */ +#define CS4362A_RMP_DN 0x01 +#define CS4362A_DEM_MASK 0x06 +#define CS4362A_DEM_NONE 0x00 +#define CS4362A_DEM_44100 0x02 +#define CS4362A_DEM_48000 0x04 +#define CS4362A_DEM_32000 0x06 +#define CS4362A_FILT_SEL 0x10 +/* register 05h */ +#define CS4362A_INV_A1 0x01 +#define CS4362A_INV_B1 0x02 +#define CS4362A_INV_A2 0x04 +#define CS4362A_INV_B2 0x08 +#define CS4362A_INV_A3 0x10 +#define CS4362A_INV_B3 0x20 +/* register 06h */ +#define CS4362A_FM_MASK 0x03 +#define CS4362A_FM_SINGLE 0x00 +#define CS4362A_FM_DOUBLE 0x01 +#define CS4362A_FM_QUAD 0x02 +#define CS4362A_FM_DSD 0x03 +#define CS4362A_ATAPI_MASK 0x7c +#define CS4362A_ATAPI_B_MUTE 0x00 +#define CS4362A_ATAPI_B_R 0x04 +#define CS4362A_ATAPI_B_L 0x08 +#define CS4362A_ATAPI_B_LR 0x0c +#define CS4362A_ATAPI_A_MUTE 0x00 +#define CS4362A_ATAPI_A_R 0x10 +#define CS4362A_ATAPI_A_L 0x20 +#define CS4362A_ATAPI_A_LR 0x30 +#define CS4362A_ATAPI_MIX_LR_VOL 0x40 +#define CS4362A_A_EQ_B 0x80 +/* register 07h */ +#define CS4362A_VOL_MASK 0x7f +#define CS4362A_MUTE 0x80 +/* register 08h: like 07h */ +/* registers 09h..0Bh: like 06h..08h */ +/* registers 0Ch..0Eh: like 06h..08h */ +/* register 12h */ +#define CS4362A_REV_MASK 0x07 +#define CS4362A_PART_MASK 0xf8 +#define CS4362A_PART_CS4362A 0x50 diff --git a/sound/pci/oxygen/cs4398.h b/sound/pci/oxygen/cs4398.h new file mode 100644 index 000000000..76cb9d7af --- /dev/null +++ b/sound/pci/oxygen/cs4398.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* register 1 */ +#define CS4398_REV_MASK 0x07 +#define CS4398_PART_MASK 0xf8 +#define CS4398_PART_CS4398 0x70 +/* register 2 */ +#define CS4398_FM_MASK 0x03 +#define CS4398_FM_SINGLE 0x00 +#define CS4398_FM_DOUBLE 0x01 +#define CS4398_FM_QUAD 0x02 +#define CS4398_FM_DSD 0x03 +#define CS4398_DEM_MASK 0x0c +#define CS4398_DEM_NONE 0x00 +#define CS4398_DEM_44100 0x04 +#define CS4398_DEM_48000 0x08 +#define CS4398_DEM_32000 0x0c +#define CS4398_DIF_MASK 0x70 +#define CS4398_DIF_LJUST 0x00 +#define CS4398_DIF_I2S 0x10 +#define CS4398_DIF_RJUST_16 0x20 +#define CS4398_DIF_RJUST_24 0x30 +#define CS4398_DIF_RJUST_20 0x40 +#define CS4398_DIF_RJUST_18 0x50 +#define CS4398_DSD_SRC 0x80 +/* register 3 */ +#define CS4398_ATAPI_MASK 0x1f +#define CS4398_ATAPI_B_MUTE 0x00 +#define CS4398_ATAPI_B_R 0x01 +#define CS4398_ATAPI_B_L 0x02 +#define CS4398_ATAPI_B_LR 0x03 +#define CS4398_ATAPI_A_MUTE 0x00 +#define CS4398_ATAPI_A_R 0x04 +#define CS4398_ATAPI_A_L 0x08 +#define CS4398_ATAPI_A_LR 0x0c +#define CS4398_ATAPI_MIX_LR_VOL 0x10 +#define CS4398_INVERT_B 0x20 +#define CS4398_INVERT_A 0x40 +#define CS4398_VOL_B_EQ_A 0x80 +/* register 4 */ +#define CS4398_MUTEP_MASK 0x03 +#define CS4398_MUTEP_AUTO 0x00 +#define CS4398_MUTEP_LOW 0x02 +#define CS4398_MUTEP_HIGH 0x03 +#define CS4398_MUTE_B 0x08 +#define CS4398_MUTE_A 0x10 +#define CS4398_MUTEC_A_EQ_B 0x20 +#define CS4398_DAMUTE 0x40 +#define CS4398_PAMUTE 0x80 +/* register 5 */ +#define CS4398_VOL_A_MASK 0xff +/* register 6 */ +#define CS4398_VOL_B_MASK 0xff +/* register 7 */ +#define CS4398_DIR_DSD 0x01 +#define CS4398_FILT_SEL 0x04 +#define CS4398_RMP_DN 0x10 +#define CS4398_RMP_UP 0x20 +#define CS4398_ZERO_CROSS 0x40 +#define CS4398_SOFT_RAMP 0x80 +/* register 8 */ +#define CS4398_MCLKDIV3 0x08 +#define CS4398_MCLKDIV2 0x10 +#define CS4398_FREEZE 0x20 +#define CS4398_CPEN 0x40 +#define CS4398_PDN 0x80 +/* register 9 */ +#define CS4398_DSD_PM_EN 0x01 +#define CS4398_DSD_PM_MODE 0x02 +#define CS4398_INVALID_DSD 0x04 +#define CS4398_STATIC_DSD 0x08 diff --git a/sound/pci/oxygen/oxygen.c b/sound/pci/oxygen/oxygen.c new file mode 100644 index 000000000..a4212b255 --- /dev/null +++ b/sound/pci/oxygen/oxygen.c @@ -0,0 +1,1116 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * C-Media CMI8788 driver for C-Media's reference design and similar models + * + * Copyright (c) Clemens Ladisch + */ + +/* + * CMI8788: + * + * SPI 0 -> 1st AK4396 (front) + * SPI 1 -> 2nd AK4396 (surround) + * SPI 2 -> 3rd AK4396 (center/LFE) + * SPI 3 -> WM8785 + * SPI 4 -> 4th AK4396 (back) + * + * GPIO 0 -> DFS0 of AK5385 + * GPIO 1 -> DFS1 of AK5385 + * + * X-Meridian models: + * GPIO 4 -> enable extension S/PDIF input + * GPIO 6 -> enable on-board S/PDIF input + * + * Claro models: + * GPIO 6 -> S/PDIF from optical (0) or coaxial (1) input + * GPIO 8 -> enable headphone amplifier + * + * eClaro model: + * GPIO 2 -> M0 of CS5361 + * GPIO 3 -> M1 of CS5361 + * GPIO 8 -> enable headphone amplifier + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * CD_IN <- CD + * MIC_IN <- mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to ADC input + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "oxygen.h" +#include "xonar_dg.h" +#include "ak4396.h" +#include "cs4362a.h" +#include "wm8785.h" + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("C-Media CMI8788 driver"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable card"); + +enum { + MODEL_CMEDIA_REF, + MODEL_MERIDIAN, + MODEL_MERIDIAN_2G, + MODEL_CLARO, + MODEL_CLARO_HALO, + MODEL_ECLARO, + MODEL_FANTASIA, + MODEL_SERENADE, + MODEL_2CH_OUTPUT, + MODEL_HG2PCI, + MODEL_XONAR_DG, + MODEL_XONAR_DGX, +}; + +static const struct pci_device_id oxygen_ids[] = { + /* C-Media's reference design */ + { OXYGEN_PCI_SUBID(0x10b0, 0x0216), .driver_data = MODEL_CMEDIA_REF }, + { OXYGEN_PCI_SUBID(0x10b0, 0x0217), .driver_data = MODEL_CMEDIA_REF }, + { OXYGEN_PCI_SUBID(0x10b0, 0x0218), .driver_data = MODEL_CMEDIA_REF }, + { OXYGEN_PCI_SUBID(0x10b0, 0x0219), .driver_data = MODEL_CMEDIA_REF }, + { OXYGEN_PCI_SUBID(0x13f6, 0x0001), .driver_data = MODEL_CMEDIA_REF }, + { OXYGEN_PCI_SUBID(0x13f6, 0x0010), .driver_data = MODEL_CMEDIA_REF }, + { OXYGEN_PCI_SUBID(0x13f6, 0x8788), .driver_data = MODEL_CMEDIA_REF }, + { OXYGEN_PCI_SUBID(0x147a, 0xa017), .driver_data = MODEL_CMEDIA_REF }, + { OXYGEN_PCI_SUBID(0x1a58, 0x0910), .driver_data = MODEL_CMEDIA_REF }, + /* Asus Xonar DG */ + { OXYGEN_PCI_SUBID(0x1043, 0x8467), .driver_data = MODEL_XONAR_DG }, + /* Asus Xonar DGX */ + { OXYGEN_PCI_SUBID(0x1043, 0x8521), .driver_data = MODEL_XONAR_DGX }, + /* PCI 2.0 HD Audio */ + { OXYGEN_PCI_SUBID(0x13f6, 0x8782), .driver_data = MODEL_2CH_OUTPUT }, + /* Kuroutoshikou CMI8787-HG2PCI */ + { OXYGEN_PCI_SUBID(0x13f6, 0xffff), .driver_data = MODEL_HG2PCI }, + /* TempoTec HiFier Fantasia */ + { OXYGEN_PCI_SUBID(0x14c3, 0x1710), .driver_data = MODEL_FANTASIA }, + /* TempoTec HiFier Serenade */ + { OXYGEN_PCI_SUBID(0x14c3, 0x1711), .driver_data = MODEL_SERENADE }, + /* AuzenTech X-Meridian */ + { OXYGEN_PCI_SUBID(0x415a, 0x5431), .driver_data = MODEL_MERIDIAN }, + /* AuzenTech X-Meridian 2G */ + { OXYGEN_PCI_SUBID(0x5431, 0x017a), .driver_data = MODEL_MERIDIAN_2G }, + /* HT-Omega Claro */ + { OXYGEN_PCI_SUBID(0x7284, 0x9761), .driver_data = MODEL_CLARO }, + /* HT-Omega Claro halo */ + { OXYGEN_PCI_SUBID(0x7284, 0x9781), .driver_data = MODEL_CLARO_HALO }, + /* HT-Omega eClaro */ + { OXYGEN_PCI_SUBID(0x7284, 0x9783), .driver_data = MODEL_ECLARO }, + { } +}; +MODULE_DEVICE_TABLE(pci, oxygen_ids); + + +#define GPIO_AK5385_DFS_MASK 0x0003 +#define GPIO_AK5385_DFS_NORMAL 0x0000 +#define GPIO_AK5385_DFS_DOUBLE 0x0001 +#define GPIO_AK5385_DFS_QUAD 0x0002 + +#define GPIO_MERIDIAN_DIG_MASK 0x0050 +#define GPIO_MERIDIAN_DIG_EXT 0x0010 +#define GPIO_MERIDIAN_DIG_BOARD 0x0040 + +#define GPIO_CLARO_DIG_COAX 0x0040 +#define GPIO_CLARO_HP 0x0100 + +#define GPIO_ECLARO_CS4362A_NRESET 0x0001 /* GPIO 0: CS4362A RESET# (active-low) */ +#define GPIO_ECLARO_FRONT_ENABLE 0x0020 /* GPIO 5: front output stage enable */ + +/* CS4362A SPI: 3-byte frame [0x30, reg, value] on CE1, 1280 ns/bit clock */ +#define ECLARO_CS4362A_SPI_CONTROL \ + (OXYGEN_SPI_TRIGGER | OXYGEN_SPI_DATA_LENGTH_3 | \ + OXYGEN_SPI_CLOCK_1280 | (1 << OXYGEN_SPI_CODEC_SHIFT) | \ + OXYGEN_SPI_CEN_LATCH_CLOCK_HI) + +struct generic_data { + unsigned int dacs; + u8 spi_map[4]; + u16 spi_prefix[4]; + u8 ak4396_regs[4][5]; + u8 cs4362a_regs[15]; + u16 wm8785_regs[3]; +}; + +static void ak4396_write(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + struct generic_data *data = chip->model_data; + + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CLOCK_160 | + (data->spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI, + data->spi_prefix[codec] | (reg << 8) | value); + data->ak4396_regs[codec][reg] = value; +} + +static void ak4396_write_cached(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + struct generic_data *data = chip->model_data; + + if (value != data->ak4396_regs[codec][reg]) + ak4396_write(chip, codec, reg, value); +} + +static void eclaro_cs4362a_write(struct oxygen *chip, u8 reg, u8 value) +{ + struct generic_data *data = chip->model_data; + int err; + + if (reg < ARRAY_SIZE(data->cs4362a_regs)) + data->cs4362a_regs[reg] = value; + + err = oxygen_write_spi(chip, ECLARO_CS4362A_SPI_CONTROL, + (0x30u << 16) | ((u32)reg << 8) | value); + if (err) + dev_err(chip->card->dev, + "CS4362A SPI timeout: reg=0x%02x val=0x%02x\n", + reg, value); +} + +static void eclaro_cs4362a_write_cached(struct oxygen *chip, u8 reg, u8 value) +{ + struct generic_data *data = chip->model_data; + + if (value != data->cs4362a_regs[reg]) + eclaro_cs4362a_write(chip, reg, value); +} + +static void eclaro_cs4362a_registers_init(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + + eclaro_cs4362a_write(chip, 1, CS4362A_CPEN | CS4362A_PDN); + eclaro_cs4362a_write(chip, 2, CS4362A_DIF_LJUST); + eclaro_cs4362a_write(chip, 3, CS4362A_SOFT_RAMP | CS4362A_AMUTE); + eclaro_cs4362a_write(chip, 4, data->cs4362a_regs[4]); + eclaro_cs4362a_write(chip, 5, 0); + eclaro_cs4362a_write(chip, 6, data->cs4362a_regs[6]); + eclaro_cs4362a_write(chip, 7, data->cs4362a_regs[7]); + eclaro_cs4362a_write(chip, 8, data->cs4362a_regs[8]); + eclaro_cs4362a_write(chip, 9, data->cs4362a_regs[9]); + eclaro_cs4362a_write(chip, 10, data->cs4362a_regs[10]); + eclaro_cs4362a_write(chip, 11, data->cs4362a_regs[11]); + eclaro_cs4362a_write(chip, 12, data->cs4362a_regs[12]); + eclaro_cs4362a_write(chip, 13, data->cs4362a_regs[13]); + eclaro_cs4362a_write(chip, 14, data->cs4362a_regs[14]); + eclaro_cs4362a_write(chip, 1, CS4362A_CPEN); +} + +static void wm8785_write(struct oxygen *chip, u8 reg, unsigned int value) +{ + struct generic_data *data = chip->model_data; + + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CLOCK_160 | + (3 << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_LO, + (reg << 9) | value); + if (reg < ARRAY_SIZE(data->wm8785_regs)) + data->wm8785_regs[reg] = value; +} + +static void ak4396_registers_init(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + unsigned int i; + + for (i = 0; i < data->dacs; ++i) { + ak4396_write(chip, i, AK4396_CONTROL_1, + AK4396_DIF_24_MSB | AK4396_RSTN); + ak4396_write(chip, i, AK4396_CONTROL_2, + data->ak4396_regs[0][AK4396_CONTROL_2]); + ak4396_write(chip, i, AK4396_CONTROL_3, + AK4396_PCM); + ak4396_write(chip, i, AK4396_LCH_ATT, + chip->dac_volume[i * 2]); + ak4396_write(chip, i, AK4396_RCH_ATT, + chip->dac_volume[i * 2 + 1]); + } +} + +static void ak4396_init(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + static const u8 default_spi_map[4] = { 0, 1, 2, 4 }; + unsigned int i; + + data->dacs = chip->model.dac_channels_pcm / 2; + memcpy(data->spi_map, default_spi_map, sizeof(default_spi_map)); + for (i = 0; i < 4; ++i) + data->spi_prefix[i] = AK4396_WRITE; + data->ak4396_regs[0][AK4396_CONTROL_2] = + AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL; + ak4396_registers_init(chip); + snd_component_add(chip->card, "AK4396"); +} + +static void ak5385_init(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_AK5385_DFS_MASK); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_AK5385_DFS_MASK); + snd_component_add(chip->card, "AK5385"); +} + +static void wm8785_registers_init(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + + wm8785_write(chip, WM8785_R7, 0); + wm8785_write(chip, WM8785_R0, data->wm8785_regs[0]); + wm8785_write(chip, WM8785_R2, data->wm8785_regs[2]); +} + +static void wm8785_init(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + + data->wm8785_regs[0] = + WM8785_MCR_SLAVE | WM8785_OSR_SINGLE | WM8785_FORMAT_LJUST; + data->wm8785_regs[2] = WM8785_HPFR | WM8785_HPFL; + wm8785_registers_init(chip); + snd_component_add(chip->card, "WM8785"); +} + +static void generic_init(struct oxygen *chip) +{ + ak4396_init(chip); + wm8785_init(chip); +} + +static void meridian_init(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_MERIDIAN_DIG_MASK); + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + GPIO_MERIDIAN_DIG_BOARD, GPIO_MERIDIAN_DIG_MASK); + ak4396_init(chip); + ak5385_init(chip); +} + +static void claro_enable_hp(struct oxygen *chip) +{ + msleep(300); + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_HP); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_HP); +} + +static void claro_init(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_DIG_COAX); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_DIG_COAX); + ak4396_init(chip); + wm8785_init(chip); + claro_enable_hp(chip); +} + +static void claro_halo_init(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_DIG_COAX); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_DIG_COAX); + ak4396_init(chip); + ak5385_init(chip); + claro_enable_hp(chip); +} + +static void fantasia_init(struct oxygen *chip) +{ + ak4396_init(chip); + snd_component_add(chip->card, "CS5340"); +} + +static void stereo_output_init(struct oxygen *chip) +{ + ak4396_init(chip); +} + +static void generic_cleanup(struct oxygen *chip) +{ +} + +static void claro_disable_hp(struct oxygen *chip) +{ + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_HP); +} + +static void claro_cleanup(struct oxygen *chip) +{ + claro_disable_hp(chip); +} + +static void claro_suspend(struct oxygen *chip) +{ + claro_disable_hp(chip); +} + +static void generic_resume(struct oxygen *chip) +{ + ak4396_registers_init(chip); + wm8785_registers_init(chip); +} + +static void meridian_resume(struct oxygen *chip) +{ + ak4396_registers_init(chip); +} + +static void claro_resume(struct oxygen *chip) +{ + ak4396_registers_init(chip); + claro_enable_hp(chip); +} + +#define GPIO_CS5361_M_MASK 0x000c +#define GPIO_CS5361_M_SINGLE 0x0000 +#define GPIO_CS5361_M_DOUBLE 0x0004 +#define GPIO_CS5361_M_QUAD 0x0008 + +static void cs5361_init(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CS5361_M_MASK); + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + GPIO_CS5361_M_SINGLE, GPIO_CS5361_M_MASK); +} + +static void set_cs5361_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + unsigned int value; + + if (params_rate(params) <= 54000) + value = GPIO_CS5361_M_SINGLE; + else if (params_rate(params) <= 108000) + value = GPIO_CS5361_M_DOUBLE; + else + value = GPIO_CS5361_M_QUAD; + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + value, GPIO_CS5361_M_MASK); +} + +static void eclaro_init(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_DIG_COAX); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_DIG_COAX); + + /* Single AK4396VF on SPI CE0/CA=00 handles front L/R */ + data->dacs = 1; + data->spi_map[0] = 0; + data->spi_prefix[0] = AK4396_WRITE; + data->ak4396_regs[0][AK4396_CONTROL_2] = + AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL; + + ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS); + ak4396_write(chip, 0, AK4396_CONTROL_2, + data->ak4396_regs[0][AK4396_CONTROL_2]); + ak4396_write(chip, 0, AK4396_CONTROL_3, AK4396_PCM); + ak4396_write(chip, 0, AK4396_LCH_ATT, chip->dac_volume[0] * 2); + ak4396_write(chip, 0, AK4396_RCH_ATT, chip->dac_volume[1] * 2); + ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS | AK4396_RSTN); + + /* CS4362A (SPI CE1): surround/center-LFE/side L/R. + * GPIO 0 (RESET#, active-low) and GPIO 5 (front output enable) must + * be driven high. GPIOs 1 and 7 are outputs driven high. + */ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, 0x00a3); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, 0x00a3); + usleep_range(1000, 2000); + + data->cs4362a_regs[4] = CS4362A_RMP_DN | CS4362A_DEM_NONE; + data->cs4362a_regs[6] = CS4362A_FM_SINGLE | + CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L; + data->cs4362a_regs[7] = CS4362A_MUTE; + data->cs4362a_regs[9] = data->cs4362a_regs[6]; + data->cs4362a_regs[12] = data->cs4362a_regs[6]; + + eclaro_cs4362a_registers_init(chip); + + snd_component_add(chip->card, "AK4396"); + snd_component_add(chip->card, "CS4362A"); + cs5361_init(chip); + claro_enable_hp(chip); + snd_component_add(chip->card, "CS5361"); +} + +static void eclaro_resume(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_ECLARO_CS4362A_NRESET | GPIO_ECLARO_FRONT_ENABLE); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, + GPIO_ECLARO_CS4362A_NRESET | GPIO_ECLARO_FRONT_ENABLE); + + /* AK4396 chip 0 */ + ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS | AK4396_RSTN); + ak4396_write(chip, 0, AK4396_CONTROL_2, + data->ak4396_regs[0][AK4396_CONTROL_2]); + ak4396_write(chip, 0, AK4396_CONTROL_3, AK4396_PCM); + ak4396_write(chip, 0, AK4396_LCH_ATT, chip->dac_volume[0] * 2); + ak4396_write(chip, 0, AK4396_RCH_ATT, chip->dac_volume[1] * 2); + + eclaro_cs4362a_registers_init(chip); + + cs5361_init(chip); + claro_enable_hp(chip); +} + +static void stereo_resume(struct oxygen *chip) +{ + ak4396_registers_init(chip); +} + +static void set_ak4396_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct generic_data *data = chip->model_data; + unsigned int i; + u8 value; + + value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_DFS_MASK; + if (params_rate(params) <= 54000) + value |= AK4396_DFS_NORMAL; + else if (params_rate(params) <= 108000) + value |= AK4396_DFS_DOUBLE; + else + value |= AK4396_DFS_QUAD; + + msleep(1); /* wait for the new MCLK to become stable */ + + if (value != data->ak4396_regs[0][AK4396_CONTROL_2]) { + for (i = 0; i < data->dacs; ++i) { + ak4396_write(chip, i, AK4396_CONTROL_1, + AK4396_DIF_24_MSB); + ak4396_write(chip, i, AK4396_CONTROL_2, value); + ak4396_write(chip, i, AK4396_CONTROL_1, + AK4396_DIF_24_MSB | AK4396_RSTN); + } + } +} + +static void eclaro_set_dac_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct generic_data *data = chip->model_data; + u8 ak_value, cs_fm; + + ak_value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_DFS_MASK; + if (params_rate(params) <= 54000) { + ak_value |= AK4396_DFS_NORMAL; + cs_fm = CS4362A_FM_SINGLE; + } else if (params_rate(params) <= 108000) { + ak_value |= AK4396_DFS_DOUBLE; + cs_fm = CS4362A_FM_DOUBLE; + } else { + ak_value |= AK4396_DFS_QUAD; + cs_fm = CS4362A_FM_QUAD; + } + + usleep_range(1000, 2000); + + if (ak_value != data->ak4396_regs[0][AK4396_CONTROL_2]) { + ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS); + ak4396_write(chip, 0, AK4396_CONTROL_2, ak_value); + ak4396_write(chip, 0, AK4396_CONTROL_1, + AK4396_DIF_24_MSB | AK4396_ACKS | AK4396_RSTN); + data->ak4396_regs[0][AK4396_CONTROL_2] = ak_value; + } + + /* Update CS4362A FM mode for all three DAC pairs */ + cs_fm |= data->cs4362a_regs[6] & ~CS4362A_FM_MASK; + eclaro_cs4362a_write_cached(chip, 6, cs_fm); + eclaro_cs4362a_write_cached(chip, 12, cs_fm); + cs_fm &= CS4362A_FM_MASK; + cs_fm |= data->cs4362a_regs[9] & ~CS4362A_FM_MASK; + eclaro_cs4362a_write_cached(chip, 9, cs_fm); +} + +static void update_eclaro_volume(struct oxygen *chip) +{ + u8 mute = chip->dac_mute ? CS4362A_MUTE : 0; + + ak4396_write_cached(chip, 0, AK4396_LCH_ATT, chip->dac_volume[0] * 2); + ak4396_write_cached(chip, 0, AK4396_RCH_ATT, chip->dac_volume[1] * 2); + + /* CS4362A attenuation is inverse: 0 = 0 dB, 127 = max attenuation. + * Pair 1 (regs 7/8) is wired to the side outputs (ALSA ch 6/7); + * pair 3 (regs 13/14) is wired to the rear outputs (ALSA ch 2/3). + */ + eclaro_cs4362a_write_cached(chip, 7, mute | (127 - chip->dac_volume[6])); + eclaro_cs4362a_write_cached(chip, 8, mute | (127 - chip->dac_volume[7])); + eclaro_cs4362a_write_cached(chip, 10, mute | (127 - chip->dac_volume[4])); + eclaro_cs4362a_write_cached(chip, 11, mute | (127 - chip->dac_volume[5])); + eclaro_cs4362a_write_cached(chip, 13, mute | (127 - chip->dac_volume[2])); + eclaro_cs4362a_write_cached(chip, 14, mute | (127 - chip->dac_volume[3])); +} + +static void update_eclaro_mute(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + u8 value; + + value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_SMUTE; + if (chip->dac_mute) + value |= AK4396_SMUTE; + ak4396_write_cached(chip, 0, AK4396_CONTROL_2, value); + + /* Re-apply volume+mute to CS4362A so the mute bit is set correctly */ + update_eclaro_volume(chip); +} + +static void update_ak4396_volume(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + unsigned int i; + + for (i = 0; i < data->dacs; ++i) { + ak4396_write_cached(chip, i, AK4396_LCH_ATT, + chip->dac_volume[i * 2]); + ak4396_write_cached(chip, i, AK4396_RCH_ATT, + chip->dac_volume[i * 2 + 1]); + } +} + +static void update_ak4396_mute(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + unsigned int i; + u8 value; + + value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_SMUTE; + if (chip->dac_mute) + value |= AK4396_SMUTE; + for (i = 0; i < data->dacs; ++i) + ak4396_write_cached(chip, i, AK4396_CONTROL_2, value); +} + +static void set_wm8785_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct generic_data *data = chip->model_data; + unsigned int value; + + value = WM8785_MCR_SLAVE | WM8785_FORMAT_LJUST; + if (params_rate(params) <= 48000) + value |= WM8785_OSR_SINGLE; + else if (params_rate(params) <= 96000) + value |= WM8785_OSR_DOUBLE; + else + value |= WM8785_OSR_QUAD; + if (value != data->wm8785_regs[0]) { + wm8785_write(chip, WM8785_R7, 0); + wm8785_write(chip, WM8785_R0, value); + wm8785_write(chip, WM8785_R2, data->wm8785_regs[2]); + } +} + +static void set_ak5385_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + unsigned int value; + + if (params_rate(params) <= 54000) + value = GPIO_AK5385_DFS_NORMAL; + else if (params_rate(params) <= 108000) + value = GPIO_AK5385_DFS_DOUBLE; + else + value = GPIO_AK5385_DFS_QUAD; + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + value, GPIO_AK5385_DFS_MASK); +} + +static void set_no_params(struct oxygen *chip, struct snd_pcm_hw_params *params) +{ +} + +static int rolloff_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { + "Sharp Roll-off", "Slow Roll-off" + }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int rolloff_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct generic_data *data = chip->model_data; + + value->value.enumerated.item[0] = + (data->ak4396_regs[0][AK4396_CONTROL_2] & AK4396_SLOW) != 0; + return 0; +} + +static int rolloff_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct generic_data *data = chip->model_data; + unsigned int i; + int changed; + u8 reg; + + guard(mutex)(&chip->mutex); + reg = data->ak4396_regs[0][AK4396_CONTROL_2]; + if (value->value.enumerated.item[0]) + reg |= AK4396_SLOW; + else + reg &= ~AK4396_SLOW; + changed = reg != data->ak4396_regs[0][AK4396_CONTROL_2]; + if (changed) { + for (i = 0; i < data->dacs; ++i) + ak4396_write(chip, i, AK4396_CONTROL_2, reg); + } + return changed; +} + +static const struct snd_kcontrol_new rolloff_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Filter Playback Enum", + .info = rolloff_info, + .get = rolloff_get, + .put = rolloff_put, +}; + +static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { + "None", "High-pass Filter" + }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct generic_data *data = chip->model_data; + + value->value.enumerated.item[0] = + (data->wm8785_regs[WM8785_R2] & WM8785_HPFR) != 0; + return 0; +} + +static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct generic_data *data = chip->model_data; + unsigned int reg; + int changed; + + guard(mutex)(&chip->mutex); + reg = data->wm8785_regs[WM8785_R2] & ~(WM8785_HPFR | WM8785_HPFL); + if (value->value.enumerated.item[0]) + reg |= WM8785_HPFR | WM8785_HPFL; + changed = reg != data->wm8785_regs[WM8785_R2]; + if (changed) + wm8785_write(chip, WM8785_R2, reg); + return changed; +} + +static const struct snd_kcontrol_new hpf_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Filter Capture Enum", + .info = hpf_info, + .get = hpf_get, + .put = hpf_put, +}; + +static int meridian_dig_source_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { "On-board", "Extension" }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int claro_dig_source_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { "Optical", "Coaxial" }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int meridian_dig_source_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + + value->value.enumerated.item[0] = + !!(oxygen_read16(chip, OXYGEN_GPIO_DATA) & + GPIO_MERIDIAN_DIG_EXT); + return 0; +} + +static int claro_dig_source_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + + value->value.enumerated.item[0] = + !!(oxygen_read16(chip, OXYGEN_GPIO_DATA) & + GPIO_CLARO_DIG_COAX); + return 0; +} + +static int meridian_dig_source_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 old_reg, new_reg; + int changed; + + guard(mutex)(&chip->mutex); + old_reg = oxygen_read16(chip, OXYGEN_GPIO_DATA); + new_reg = old_reg & ~GPIO_MERIDIAN_DIG_MASK; + if (value->value.enumerated.item[0] == 0) + new_reg |= GPIO_MERIDIAN_DIG_BOARD; + else + new_reg |= GPIO_MERIDIAN_DIG_EXT; + changed = new_reg != old_reg; + if (changed) + oxygen_write16(chip, OXYGEN_GPIO_DATA, new_reg); + return changed; +} + +static int claro_dig_source_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 old_reg, new_reg; + int changed; + + guard(mutex)(&chip->mutex); + old_reg = oxygen_read16(chip, OXYGEN_GPIO_DATA); + new_reg = old_reg & ~GPIO_CLARO_DIG_COAX; + if (value->value.enumerated.item[0]) + new_reg |= GPIO_CLARO_DIG_COAX; + changed = new_reg != old_reg; + if (changed) + oxygen_write16(chip, OXYGEN_GPIO_DATA, new_reg); + return changed; +} + +static const struct snd_kcontrol_new meridian_dig_source_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Source Capture Enum", + .info = meridian_dig_source_info, + .get = meridian_dig_source_get, + .put = meridian_dig_source_put, +}; + +static const struct snd_kcontrol_new claro_dig_source_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Source Capture Enum", + .info = claro_dig_source_info, + .get = claro_dig_source_get, + .put = claro_dig_source_put, +}; + +static int generic_mixer_init(struct oxygen *chip) +{ + return snd_ctl_add(chip->card, snd_ctl_new1(&rolloff_control, chip)); +} + +static int generic_wm8785_mixer_init(struct oxygen *chip) +{ + int err; + + err = generic_mixer_init(chip); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, snd_ctl_new1(&hpf_control, chip)); + if (err < 0) + return err; + return 0; +} + +static int meridian_mixer_init(struct oxygen *chip) +{ + int err; + + err = generic_mixer_init(chip); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, + snd_ctl_new1(&meridian_dig_source_control, chip)); + if (err < 0) + return err; + return 0; +} + +static int claro_mixer_init(struct oxygen *chip) +{ + int err; + + err = generic_wm8785_mixer_init(chip); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, + snd_ctl_new1(&claro_dig_source_control, chip)); + if (err < 0) + return err; + return 0; +} + +static int claro_halo_mixer_init(struct oxygen *chip) +{ + int err; + + err = generic_mixer_init(chip); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, + snd_ctl_new1(&claro_dig_source_control, chip)); + if (err < 0) + return err; + return 0; +} + +static void dump_ak4396_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + struct generic_data *data = chip->model_data; + unsigned int dac, i; + + for (dac = 0; dac < data->dacs; ++dac) { + snd_iprintf(buffer, "\nAK4396 %u:", dac + 1); + for (i = 0; i < 5; ++i) + snd_iprintf(buffer, " %02x", data->ak4396_regs[dac][i]); + } + snd_iprintf(buffer, "\n"); +} + +static void dump_wm8785_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + struct generic_data *data = chip->model_data; + unsigned int i; + + snd_iprintf(buffer, "\nWM8785:"); + for (i = 0; i < 3; ++i) + snd_iprintf(buffer, " %03x", data->wm8785_regs[i]); + snd_iprintf(buffer, "\n"); +} + +static void dump_oxygen_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + dump_ak4396_registers(chip, buffer); + dump_wm8785_registers(chip, buffer); +} + +static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0); +/* CS4362A: 0.5 dB/step, raw=127 -> 0 dB, raw=0 -> -63.5 dB */ +static const DECLARE_TLV_DB_SCALE(eclaro_db_scale, -6350, 50, 0); + +static const struct oxygen_model model_generic = { + .shortname = "C-Media CMI8788", + .longname = "C-Media Oxygen HD Audio", + .chip = "CMI8788", + .init = generic_init, + .mixer_init = generic_wm8785_mixer_init, + .cleanup = generic_cleanup, + .resume = generic_resume, + .set_dac_params = set_ak4396_params, + .set_adc_params = set_wm8785_params, + .update_dac_volume = update_ak4396_volume, + .update_dac_mute = update_ak4396_mute, + .dump_registers = dump_oxygen_registers, + .dac_tlv = ak4396_db_scale, + .model_data_size = sizeof(struct generic_data), + .device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + PLAYBACK_2_TO_AC97_1 | + CAPTURE_0_FROM_I2S_1 | + CAPTURE_1_FROM_SPDIF | + CAPTURE_2_FROM_AC97_1 | + AC97_CD_INPUT, + .dac_channels_pcm = 8, + .dac_channels_mixer = 8, + .dac_volume_min = 0, + .dac_volume_max = 255, + .function_flags = OXYGEN_FUNCTION_SPI | + OXYGEN_FUNCTION_ENABLE_SPI_4_5, + .dac_mclks = OXYGEN_MCLKS(256, 128, 128), + .adc_mclks = OXYGEN_MCLKS(256, 256, 128), + .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, + .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; + +static int get_oxygen_model(struct oxygen *chip, + const struct pci_device_id *id) +{ + static const char *const names[] = { + [MODEL_MERIDIAN] = "AuzenTech X-Meridian", + [MODEL_MERIDIAN_2G] = "AuzenTech X-Meridian 2G", + [MODEL_CLARO] = "HT-Omega Claro", + [MODEL_CLARO_HALO] = "HT-Omega Claro halo", + [MODEL_ECLARO] = "HT-Omega eClaro", + [MODEL_FANTASIA] = "TempoTec HiFier Fantasia", + [MODEL_SERENADE] = "TempoTec HiFier Serenade", + [MODEL_HG2PCI] = "CMI8787-HG2PCI", + [MODEL_XONAR_DG] = "Xonar DG", + [MODEL_XONAR_DGX] = "Xonar DGX", + }; + + chip->model = model_generic; + switch (id->driver_data) { + case MODEL_MERIDIAN: + case MODEL_MERIDIAN_2G: + chip->model.init = meridian_init; + chip->model.mixer_init = meridian_mixer_init; + chip->model.resume = meridian_resume; + chip->model.set_adc_params = set_ak5385_params; + chip->model.dump_registers = dump_ak4396_registers; + chip->model.device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_2 | + CAPTURE_1_FROM_SPDIF; + if (id->driver_data == MODEL_MERIDIAN) + chip->model.device_config |= AC97_CD_INPUT; + break; + case MODEL_CLARO: + chip->model.init = claro_init; + chip->model.mixer_init = claro_mixer_init; + chip->model.cleanup = claro_cleanup; + chip->model.suspend = claro_suspend; + chip->model.resume = claro_resume; + break; + case MODEL_CLARO_HALO: + chip->model.init = claro_halo_init; + chip->model.mixer_init = claro_halo_mixer_init; + chip->model.cleanup = claro_cleanup; + chip->model.suspend = claro_suspend; + chip->model.resume = claro_resume; + chip->model.set_adc_params = set_ak5385_params; + chip->model.dump_registers = dump_ak4396_registers; + chip->model.device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_2 | + CAPTURE_1_FROM_SPDIF; + break; + case MODEL_ECLARO: + chip->model.init = eclaro_init; + chip->model.mixer_init = generic_mixer_init; + chip->model.cleanup = claro_cleanup; + chip->model.suspend = claro_suspend; + chip->model.resume = eclaro_resume; + chip->model.set_dac_params = eclaro_set_dac_params; + chip->model.set_adc_params = set_cs5361_params; + chip->model.update_dac_volume = update_eclaro_volume; + chip->model.update_dac_mute = update_eclaro_mute; + chip->model.dump_registers = dump_ak4396_registers; + chip->model.device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_2 | + CAPTURE_1_FROM_SPDIF; + chip->model.function_flags = OXYGEN_FUNCTION_SPI | + OXYGEN_FUNCTION_ENABLE_SPI_4_5; + chip->model.dac_mclks = OXYGEN_MCLKS(256, 128, 128); + chip->model.dac_volume_min = 0; + chip->model.dac_volume_max = 127; + chip->model.dac_tlv = eclaro_db_scale; + break; + case MODEL_FANTASIA: + case MODEL_SERENADE: + case MODEL_2CH_OUTPUT: + case MODEL_HG2PCI: + chip->model.shortname = "C-Media CMI8787"; + chip->model.chip = "CMI8787"; + if (id->driver_data == MODEL_FANTASIA) + chip->model.init = fantasia_init; + else + chip->model.init = stereo_output_init; + chip->model.resume = stereo_resume; + chip->model.mixer_init = generic_mixer_init; + chip->model.set_adc_params = set_no_params; + chip->model.dump_registers = dump_ak4396_registers; + chip->model.device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF; + if (id->driver_data == MODEL_FANTASIA) { + chip->model.device_config |= CAPTURE_0_FROM_I2S_1; + chip->model.adc_mclks = OXYGEN_MCLKS(256, 128, 128); + } + chip->model.dac_channels_pcm = 2; + chip->model.dac_channels_mixer = 2; + break; + case MODEL_XONAR_DG: + case MODEL_XONAR_DGX: + chip->model = model_xonar_dg; + break; + } + if (id->driver_data == MODEL_MERIDIAN || + id->driver_data == MODEL_MERIDIAN_2G || + id->driver_data == MODEL_CLARO_HALO) { + chip->model.misc_flags = OXYGEN_MISC_MIDI; + chip->model.device_config |= MIDI_OUTPUT | MIDI_INPUT; + } + if (id->driver_data < ARRAY_SIZE(names) && names[id->driver_data]) + chip->model.shortname = names[id->driver_data]; + return 0; +} + +static int generic_oxygen_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + ++dev; + return -ENOENT; + } + err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE, + oxygen_ids, get_oxygen_model); + if (err >= 0) + ++dev; + return err; +} + +static struct pci_driver oxygen_driver = { + .name = KBUILD_MODNAME, + .id_table = oxygen_ids, + .probe = generic_oxygen_probe, + .driver = { + .pm = pm_sleep_ptr(&oxygen_pci_pm), + }, +}; + +module_pci_driver(oxygen_driver); diff --git a/sound/pci/oxygen/oxygen.h b/sound/pci/oxygen/oxygen.h new file mode 100644 index 000000000..820026daf --- /dev/null +++ b/sound/pci/oxygen/oxygen.h @@ -0,0 +1,257 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef OXYGEN_H_INCLUDED +#define OXYGEN_H_INCLUDED + +#include +#include +#include +#include +#include "oxygen_regs.h" + +/* 1 << PCM_x == OXYGEN_CHANNEL_x */ +#define PCM_A 0 +#define PCM_B 1 +#define PCM_C 2 +#define PCM_SPDIF 3 +#define PCM_MULTICH 4 +#define PCM_AC97 5 +#define PCM_COUNT 6 + +#define OXYGEN_MCLKS(f_single, f_double, f_quad) ((MCLK_##f_single << 0) | \ + (MCLK_##f_double << 2) | \ + (MCLK_##f_quad << 4)) + +#define OXYGEN_IO_SIZE 0x100 + +#define OXYGEN_EEPROM_ID 0x434d /* "CM" */ + +/* model-specific configuration of outputs/inputs */ +#define PLAYBACK_0_TO_I2S 0x0001 + /* PLAYBACK_0_TO_AC97_0 not implemented */ +#define PLAYBACK_1_TO_SPDIF 0x0004 +#define PLAYBACK_2_TO_AC97_1 0x0008 +#define CAPTURE_0_FROM_I2S_1 0x0010 +#define CAPTURE_0_FROM_I2S_2 0x0020 + /* CAPTURE_0_FROM_AC97_0 not implemented */ +#define CAPTURE_1_FROM_SPDIF 0x0080 +#define CAPTURE_2_FROM_I2S_2 0x0100 +#define CAPTURE_2_FROM_AC97_1 0x0200 +#define CAPTURE_3_FROM_I2S_3 0x0400 +#define MIDI_OUTPUT 0x0800 +#define MIDI_INPUT 0x1000 +#define AC97_CD_INPUT 0x2000 +#define AC97_FMIC_SWITCH 0x4000 + +enum { + CONTROL_SPDIF_PCM, + CONTROL_SPDIF_INPUT_BITS, + CONTROL_MIC_CAPTURE_SWITCH, + CONTROL_LINE_CAPTURE_SWITCH, + CONTROL_CD_CAPTURE_SWITCH, + CONTROL_AUX_CAPTURE_SWITCH, + CONTROL_COUNT +}; + +#define OXYGEN_PCI_SUBID(sv, sd) \ + .vendor = PCI_VENDOR_ID_CMEDIA, \ + .device = 0x8788, \ + .subvendor = sv, \ + .subdevice = sd + +#define BROKEN_EEPROM_DRIVER_DATA ((unsigned long)-1) +#define OXYGEN_PCI_SUBID_BROKEN_EEPROM \ + OXYGEN_PCI_SUBID(PCI_VENDOR_ID_CMEDIA, 0x8788), \ + .driver_data = BROKEN_EEPROM_DRIVER_DATA + +struct pci_dev; +struct pci_device_id; +struct snd_card; +struct snd_pcm_substream; +struct snd_pcm_hardware; +struct snd_pcm_hw_params; +struct snd_kcontrol_new; +struct snd_rawmidi; +struct snd_info_buffer; +struct oxygen; + +struct oxygen_model { + const char *shortname; + const char *longname; + const char *chip; + void (*init)(struct oxygen *chip); + int (*control_filter)(struct snd_kcontrol_new *template); + int (*mixer_init)(struct oxygen *chip); + void (*cleanup)(struct oxygen *chip); + void (*suspend)(struct oxygen *chip); + void (*resume)(struct oxygen *chip); + void (*pcm_hardware_filter)(unsigned int channel, + struct snd_pcm_hardware *hardware); + void (*set_dac_params)(struct oxygen *chip, + struct snd_pcm_hw_params *params); + void (*set_adc_params)(struct oxygen *chip, + struct snd_pcm_hw_params *params); + void (*update_dac_volume)(struct oxygen *chip); + void (*update_dac_mute)(struct oxygen *chip); + void (*update_center_lfe_mix)(struct oxygen *chip, bool mixed); + unsigned int (*adjust_dac_routing)(struct oxygen *chip, + unsigned int play_routing); + void (*gpio_changed)(struct oxygen *chip); + void (*uart_input)(struct oxygen *chip); + void (*ac97_switch)(struct oxygen *chip, + unsigned int reg, unsigned int mute); + void (*dump_registers)(struct oxygen *chip, + struct snd_info_buffer *buffer); + const unsigned int *dac_tlv; + size_t model_data_size; + unsigned int device_config; + u8 dac_channels_pcm; + u8 dac_channels_mixer; + u8 dac_volume_min; + u8 dac_volume_max; + u8 misc_flags; + u8 function_flags; + u8 dac_mclks; + u8 adc_mclks; + u16 dac_i2s_format; + u16 adc_i2s_format; +}; + +struct oxygen { + unsigned long addr; + spinlock_t reg_lock; + struct mutex mutex; + struct snd_card *card; + struct pci_dev *pci; + struct snd_rawmidi *midi; + int irq; + void *model_data; + unsigned int interrupt_mask; + u8 dac_volume[8]; + u8 dac_mute; + u8 pcm_active; + u8 pcm_running; + u8 dac_routing; + u8 spdif_playback_enable; + u8 has_ac97_0; + u8 has_ac97_1; + u32 spdif_bits; + u32 spdif_pcm_bits; + struct snd_pcm_substream *streams[PCM_COUNT]; + struct snd_kcontrol *controls[CONTROL_COUNT]; + struct work_struct spdif_input_bits_work; + struct work_struct gpio_work; + wait_queue_head_t ac97_waitqueue; + union { + u8 _8[OXYGEN_IO_SIZE]; + __le16 _16[OXYGEN_IO_SIZE / 2]; + __le32 _32[OXYGEN_IO_SIZE / 4]; + } saved_registers; + u16 saved_ac97_registers[2][0x40]; + unsigned int uart_input_count; + u8 uart_input[32]; + struct oxygen_model model; +}; + +/* oxygen_lib.c */ + +int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, + struct module *owner, + const struct pci_device_id *ids, + int (*get_model)(struct oxygen *chip, + const struct pci_device_id *id + ) + ); +extern const struct dev_pm_ops oxygen_pci_pm; +void oxygen_pci_shutdown(struct pci_dev *pci); + +/* oxygen_mixer.c */ + +int oxygen_mixer_init(struct oxygen *chip); +void oxygen_update_dac_routing(struct oxygen *chip); +void oxygen_update_spdif_source(struct oxygen *chip); + +/* oxygen_pcm.c */ + +int oxygen_pcm_init(struct oxygen *chip); + +/* oxygen_io.c */ + +u8 oxygen_read8(struct oxygen *chip, unsigned int reg); +u16 oxygen_read16(struct oxygen *chip, unsigned int reg); +u32 oxygen_read32(struct oxygen *chip, unsigned int reg); +void oxygen_write8(struct oxygen *chip, unsigned int reg, u8 value); +void oxygen_write16(struct oxygen *chip, unsigned int reg, u16 value); +void oxygen_write32(struct oxygen *chip, unsigned int reg, u32 value); +void oxygen_write8_masked(struct oxygen *chip, unsigned int reg, + u8 value, u8 mask); +void oxygen_write16_masked(struct oxygen *chip, unsigned int reg, + u16 value, u16 mask); +void oxygen_write32_masked(struct oxygen *chip, unsigned int reg, + u32 value, u32 mask); + +u16 oxygen_read_ac97(struct oxygen *chip, unsigned int codec, + unsigned int index); +void oxygen_write_ac97(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 data); +void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 data, u16 mask); + +int oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data); +void oxygen_write_i2c(struct oxygen *chip, u8 device, u8 map, u8 data); + +void oxygen_reset_uart(struct oxygen *chip); +void oxygen_write_uart(struct oxygen *chip, u8 data); + +u16 oxygen_read_eeprom(struct oxygen *chip, unsigned int index); +void oxygen_write_eeprom(struct oxygen *chip, unsigned int index, u16 value); + +static inline void oxygen_set_bits8(struct oxygen *chip, + unsigned int reg, u8 value) +{ + oxygen_write8_masked(chip, reg, value, value); +} + +static inline void oxygen_set_bits16(struct oxygen *chip, + unsigned int reg, u16 value) +{ + oxygen_write16_masked(chip, reg, value, value); +} + +static inline void oxygen_set_bits32(struct oxygen *chip, + unsigned int reg, u32 value) +{ + oxygen_write32_masked(chip, reg, value, value); +} + +static inline void oxygen_clear_bits8(struct oxygen *chip, + unsigned int reg, u8 value) +{ + oxygen_write8_masked(chip, reg, 0, value); +} + +static inline void oxygen_clear_bits16(struct oxygen *chip, + unsigned int reg, u16 value) +{ + oxygen_write16_masked(chip, reg, 0, value); +} + +static inline void oxygen_clear_bits32(struct oxygen *chip, + unsigned int reg, u32 value) +{ + oxygen_write32_masked(chip, reg, 0, value); +} + +static inline void oxygen_ac97_set_bits(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 value) +{ + oxygen_write_ac97_masked(chip, codec, index, value, value); +} + +static inline void oxygen_ac97_clear_bits(struct oxygen *chip, + unsigned int codec, + unsigned int index, u16 value) +{ + oxygen_write_ac97_masked(chip, codec, index, 0, value); +} + +#endif diff --git a/sound/pci/oxygen/oxygen_io.c b/sound/pci/oxygen/oxygen_io.c new file mode 100644 index 000000000..af8f495de --- /dev/null +++ b/sound/pci/oxygen/oxygen_io.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * C-Media CMI8788 driver - helper functions + * + * Copyright (c) Clemens Ladisch + */ + +#include +#include +#include +#include +#include +#include +#include "oxygen.h" + +u8 oxygen_read8(struct oxygen *chip, unsigned int reg) +{ + return inb(chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_read8); + +u16 oxygen_read16(struct oxygen *chip, unsigned int reg) +{ + return inw(chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_read16); + +u32 oxygen_read32(struct oxygen *chip, unsigned int reg) +{ + return inl(chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_read32); + +void oxygen_write8(struct oxygen *chip, unsigned int reg, u8 value) +{ + outb(value, chip->addr + reg); + chip->saved_registers._8[reg] = value; +} +EXPORT_SYMBOL(oxygen_write8); + +void oxygen_write16(struct oxygen *chip, unsigned int reg, u16 value) +{ + outw(value, chip->addr + reg); + chip->saved_registers._16[reg / 2] = cpu_to_le16(value); +} +EXPORT_SYMBOL(oxygen_write16); + +void oxygen_write32(struct oxygen *chip, unsigned int reg, u32 value) +{ + outl(value, chip->addr + reg); + chip->saved_registers._32[reg / 4] = cpu_to_le32(value); +} +EXPORT_SYMBOL(oxygen_write32); + +void oxygen_write8_masked(struct oxygen *chip, unsigned int reg, + u8 value, u8 mask) +{ + u8 tmp = inb(chip->addr + reg); + tmp &= ~mask; + tmp |= value & mask; + outb(tmp, chip->addr + reg); + chip->saved_registers._8[reg] = tmp; +} +EXPORT_SYMBOL(oxygen_write8_masked); + +void oxygen_write16_masked(struct oxygen *chip, unsigned int reg, + u16 value, u16 mask) +{ + u16 tmp = inw(chip->addr + reg); + tmp &= ~mask; + tmp |= value & mask; + outw(tmp, chip->addr + reg); + chip->saved_registers._16[reg / 2] = cpu_to_le16(tmp); +} +EXPORT_SYMBOL(oxygen_write16_masked); + +void oxygen_write32_masked(struct oxygen *chip, unsigned int reg, + u32 value, u32 mask) +{ + u32 tmp = inl(chip->addr + reg); + tmp &= ~mask; + tmp |= value & mask; + outl(tmp, chip->addr + reg); + chip->saved_registers._32[reg / 4] = cpu_to_le32(tmp); +} +EXPORT_SYMBOL(oxygen_write32_masked); + +static int oxygen_ac97_wait(struct oxygen *chip, unsigned int mask) +{ + u8 status = 0; + + /* + * Reading the status register also clears the bits, so we have to save + * the read bits in status. + */ + wait_event_timeout(chip->ac97_waitqueue, + ({ status |= oxygen_read8(chip, OXYGEN_AC97_INTERRUPT_STATUS); + status & mask; }), + msecs_to_jiffies(1) + 1); + /* + * Check even after a timeout because this function should not require + * the AC'97 interrupt to be enabled. + */ + status |= oxygen_read8(chip, OXYGEN_AC97_INTERRUPT_STATUS); + return status & mask ? 0 : -EIO; +} + +/* + * About 10% of AC'97 register reads or writes fail to complete, but even those + * where the controller indicates completion aren't guaranteed to have actually + * happened. + * + * It's hard to assign blame to either the controller or the codec because both + * were made by C-Media ... + */ + +void oxygen_write_ac97(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 data) +{ + unsigned int count, succeeded; + u32 reg; + + reg = data; + reg |= index << OXYGEN_AC97_REG_ADDR_SHIFT; + reg |= OXYGEN_AC97_REG_DIR_WRITE; + reg |= codec << OXYGEN_AC97_REG_CODEC_SHIFT; + succeeded = 0; + for (count = 5; count > 0; --count) { + udelay(5); + oxygen_write32(chip, OXYGEN_AC97_REGS, reg); + /* require two "completed" writes, just to be sure */ + if (oxygen_ac97_wait(chip, OXYGEN_AC97_INT_WRITE_DONE) >= 0 && + ++succeeded >= 2) { + chip->saved_ac97_registers[codec][index / 2] = data; + return; + } + } + dev_err(chip->card->dev, "AC'97 write timeout\n"); +} +EXPORT_SYMBOL(oxygen_write_ac97); + +u16 oxygen_read_ac97(struct oxygen *chip, unsigned int codec, + unsigned int index) +{ + unsigned int count; + unsigned int last_read = UINT_MAX; + u32 reg; + + reg = index << OXYGEN_AC97_REG_ADDR_SHIFT; + reg |= OXYGEN_AC97_REG_DIR_READ; + reg |= codec << OXYGEN_AC97_REG_CODEC_SHIFT; + for (count = 5; count > 0; --count) { + udelay(5); + oxygen_write32(chip, OXYGEN_AC97_REGS, reg); + udelay(10); + if (oxygen_ac97_wait(chip, OXYGEN_AC97_INT_READ_DONE) >= 0) { + u16 value = oxygen_read16(chip, OXYGEN_AC97_REGS); + /* we require two consecutive reads of the same value */ + if (value == last_read) + return value; + last_read = value; + /* + * Invert the register value bits to make sure that two + * consecutive unsuccessful reads do not return the same + * value. + */ + reg ^= 0xffff; + } + } + dev_err(chip->card->dev, "AC'97 read timeout on codec %u\n", codec); + return 0; +} +EXPORT_SYMBOL(oxygen_read_ac97); + +void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 data, u16 mask) +{ + u16 value = oxygen_read_ac97(chip, codec, index); + value &= ~mask; + value |= data & mask; + oxygen_write_ac97(chip, codec, index, value); +} +EXPORT_SYMBOL(oxygen_write_ac97_masked); + +static int oxygen_wait_spi(struct oxygen *chip) +{ + unsigned int count; + + /* + * Higher timeout to be sure: 200 us; + * actual transaction should not need more than 40 us. + */ + for (count = 50; count > 0; count--) { + udelay(4); + if ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) & + OXYGEN_SPI_BUSY) == 0) + return 0; + } + dev_err(chip->card->dev, "oxygen: SPI wait timeout\n"); + return -EIO; +} + +int oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data) +{ + /* + * We need to wait AFTER initiating the SPI transaction, + * otherwise read operations will not work. + */ + oxygen_write8(chip, OXYGEN_SPI_DATA1, data); + oxygen_write8(chip, OXYGEN_SPI_DATA2, data >> 8); + if (control & OXYGEN_SPI_DATA_LENGTH_3) + oxygen_write8(chip, OXYGEN_SPI_DATA3, data >> 16); + oxygen_write8(chip, OXYGEN_SPI_CONTROL, control); + return oxygen_wait_spi(chip); +} +EXPORT_SYMBOL(oxygen_write_spi); + +void oxygen_write_i2c(struct oxygen *chip, u8 device, u8 map, u8 data) +{ + /* should not need more than about 300 us */ + msleep(1); + + oxygen_write8(chip, OXYGEN_2WIRE_MAP, map); + oxygen_write8(chip, OXYGEN_2WIRE_DATA, data); + oxygen_write8(chip, OXYGEN_2WIRE_CONTROL, + device | OXYGEN_2WIRE_DIR_WRITE); +} +EXPORT_SYMBOL(oxygen_write_i2c); + +static void _write_uart(struct oxygen *chip, unsigned int port, u8 data) +{ + if (oxygen_read8(chip, OXYGEN_MPU401 + 1) & MPU401_TX_FULL) + msleep(1); + oxygen_write8(chip, OXYGEN_MPU401 + port, data); +} + +void oxygen_reset_uart(struct oxygen *chip) +{ + _write_uart(chip, 1, MPU401_RESET); + msleep(1); /* wait for ACK */ + _write_uart(chip, 1, MPU401_ENTER_UART); +} +EXPORT_SYMBOL(oxygen_reset_uart); + +void oxygen_write_uart(struct oxygen *chip, u8 data) +{ + _write_uart(chip, 0, data); +} +EXPORT_SYMBOL(oxygen_write_uart); + +u16 oxygen_read_eeprom(struct oxygen *chip, unsigned int index) +{ + unsigned int timeout; + + oxygen_write8(chip, OXYGEN_EEPROM_CONTROL, + index | OXYGEN_EEPROM_DIR_READ); + for (timeout = 0; timeout < 100; ++timeout) { + udelay(1); + if (!(oxygen_read8(chip, OXYGEN_EEPROM_STATUS) + & OXYGEN_EEPROM_BUSY)) + break; + } + return oxygen_read16(chip, OXYGEN_EEPROM_DATA); +} + +void oxygen_write_eeprom(struct oxygen *chip, unsigned int index, u16 value) +{ + unsigned int timeout; + + oxygen_write16(chip, OXYGEN_EEPROM_DATA, value); + oxygen_write8(chip, OXYGEN_EEPROM_CONTROL, + index | OXYGEN_EEPROM_DIR_WRITE); + for (timeout = 0; timeout < 10; ++timeout) { + msleep(1); + if (!(oxygen_read8(chip, OXYGEN_EEPROM_STATUS) + & OXYGEN_EEPROM_BUSY)) + return; + } + dev_err(chip->card->dev, "EEPROM write timeout\n"); +} diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c new file mode 100644 index 000000000..6b096d654 --- /dev/null +++ b/sound/pci/oxygen/oxygen_lib.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * C-Media CMI8788 driver - main driver module + * + * Copyright (c) Clemens Ladisch + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "oxygen.h" +#include "cm9780.h" + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("C-Media CMI8788 helper library"); +MODULE_LICENSE("GPL v2"); + +#define DRIVER "oxygen" + +static inline int oxygen_uart_input_ready(struct oxygen *chip) +{ + return !(oxygen_read8(chip, OXYGEN_MPU401 + 1) & MPU401_RX_EMPTY); +} + +static void oxygen_read_uart(struct oxygen *chip) +{ + if (unlikely(!oxygen_uart_input_ready(chip))) { + /* no data, but read it anyway to clear the interrupt */ + oxygen_read8(chip, OXYGEN_MPU401); + return; + } + do { + u8 data = oxygen_read8(chip, OXYGEN_MPU401); + if (data == MPU401_ACK) + continue; + if (chip->uart_input_count >= ARRAY_SIZE(chip->uart_input)) + chip->uart_input_count = 0; + chip->uart_input[chip->uart_input_count++] = data; + } while (oxygen_uart_input_ready(chip)); + if (chip->model.uart_input) + chip->model.uart_input(chip); +} + +static irqreturn_t oxygen_interrupt(int dummy, void *dev_id) +{ + struct oxygen *chip = dev_id; + unsigned int status, clear, elapsed_streams, i; + + status = oxygen_read16(chip, OXYGEN_INTERRUPT_STATUS); + if (!status) + return IRQ_NONE; + + scoped_guard(spinlock, &chip->reg_lock) { + clear = status & (OXYGEN_CHANNEL_A | + OXYGEN_CHANNEL_B | + OXYGEN_CHANNEL_C | + OXYGEN_CHANNEL_SPDIF | + OXYGEN_CHANNEL_MULTICH | + OXYGEN_CHANNEL_AC97 | + OXYGEN_INT_SPDIF_IN_DETECT | + OXYGEN_INT_GPIO | + OXYGEN_INT_AC97); + if (clear) { + if (clear & OXYGEN_INT_SPDIF_IN_DETECT) + chip->interrupt_mask &= ~OXYGEN_INT_SPDIF_IN_DETECT; + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, + chip->interrupt_mask & ~clear); + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, + chip->interrupt_mask); + } + + elapsed_streams = status & chip->pcm_running; + } + + for (i = 0; i < PCM_COUNT; ++i) + if ((elapsed_streams & (1 << i)) && chip->streams[i]) + snd_pcm_period_elapsed(chip->streams[i]); + + if (status & OXYGEN_INT_SPDIF_IN_DETECT) { + guard(spinlock)(&chip->reg_lock); + i = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL); + if (i & (OXYGEN_SPDIF_SENSE_INT | OXYGEN_SPDIF_LOCK_INT | + OXYGEN_SPDIF_RATE_INT)) { + /* write the interrupt bit(s) to clear */ + oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, i); + schedule_work(&chip->spdif_input_bits_work); + } + } + + if (status & OXYGEN_INT_GPIO) + schedule_work(&chip->gpio_work); + + if (status & OXYGEN_INT_MIDI) { + if (chip->midi) + snd_mpu401_uart_interrupt(0, chip->midi->private_data); + else + oxygen_read_uart(chip); + } + + if (status & OXYGEN_INT_AC97) + wake_up(&chip->ac97_waitqueue); + + return IRQ_HANDLED; +} + +static void oxygen_spdif_input_bits_changed(struct work_struct *work) +{ + struct oxygen *chip = container_of(work, struct oxygen, + spdif_input_bits_work); + u32 reg; + + /* + * This function gets called when there is new activity on the SPDIF + * input, or when we lose lock on the input signal, or when the rate + * changes. + */ + msleep(1); + scoped_guard(spinlock_irq, &chip->reg_lock) { + reg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL); + if ((reg & (OXYGEN_SPDIF_SENSE_STATUS | + OXYGEN_SPDIF_LOCK_STATUS)) + == OXYGEN_SPDIF_SENSE_STATUS) { + /* + * If we detect activity on the SPDIF input but cannot lock to + * a signal, the clock bit is likely to be wrong. + */ + reg ^= OXYGEN_SPDIF_IN_CLOCK_MASK; + oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, reg); + spin_unlock_irq(&chip->reg_lock); + msleep(1); + spin_lock_irq(&chip->reg_lock); + reg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL); + if ((reg & (OXYGEN_SPDIF_SENSE_STATUS | + OXYGEN_SPDIF_LOCK_STATUS)) + == OXYGEN_SPDIF_SENSE_STATUS) { + /* nothing detected with either clock; give up */ + if ((reg & OXYGEN_SPDIF_IN_CLOCK_MASK) + == OXYGEN_SPDIF_IN_CLOCK_192) { + /* + * Reset clock to <= 96 kHz because this is + * more likely to be received next time. + */ + reg &= ~OXYGEN_SPDIF_IN_CLOCK_MASK; + reg |= OXYGEN_SPDIF_IN_CLOCK_96; + oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, reg); + } + } + } + } + + if (chip->controls[CONTROL_SPDIF_INPUT_BITS]) { + scoped_guard(spinlock_irq, &chip->reg_lock) { + chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_DETECT; + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, + chip->interrupt_mask); + } + + /* + * We don't actually know that any channel status bits have + * changed, but let's send a notification just to be sure. + */ + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->controls[CONTROL_SPDIF_INPUT_BITS]->id); + } +} + +static void oxygen_gpio_changed(struct work_struct *work) +{ + struct oxygen *chip = container_of(work, struct oxygen, gpio_work); + + if (chip->model.gpio_changed) + chip->model.gpio_changed(chip); +} + +static void oxygen_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct oxygen *chip = entry->private_data; + int i, j; + + switch (oxygen_read8(chip, OXYGEN_REVISION) & OXYGEN_PACKAGE_ID_MASK) { + case OXYGEN_PACKAGE_ID_8786: i = '6'; break; + case OXYGEN_PACKAGE_ID_8787: i = '7'; break; + case OXYGEN_PACKAGE_ID_8788: i = '8'; break; + default: i = '?'; break; + } + snd_iprintf(buffer, "CMI878%c:\n", i); + for (i = 0; i < OXYGEN_IO_SIZE; i += 0x10) { + snd_iprintf(buffer, "%02x:", i); + for (j = 0; j < 0x10; ++j) + snd_iprintf(buffer, " %02x", oxygen_read8(chip, i + j)); + snd_iprintf(buffer, "\n"); + } + if (mutex_lock_interruptible(&chip->mutex) < 0) + return; + if (chip->has_ac97_0) { + snd_iprintf(buffer, "\nAC97:\n"); + for (i = 0; i < 0x80; i += 0x10) { + snd_iprintf(buffer, "%02x:", i); + for (j = 0; j < 0x10; j += 2) + snd_iprintf(buffer, " %04x", + oxygen_read_ac97(chip, 0, i + j)); + snd_iprintf(buffer, "\n"); + } + } + if (chip->has_ac97_1) { + snd_iprintf(buffer, "\nAC97 2:\n"); + for (i = 0; i < 0x80; i += 0x10) { + snd_iprintf(buffer, "%02x:", i); + for (j = 0; j < 0x10; j += 2) + snd_iprintf(buffer, " %04x", + oxygen_read_ac97(chip, 1, i + j)); + snd_iprintf(buffer, "\n"); + } + } + mutex_unlock(&chip->mutex); + if (chip->model.dump_registers) + chip->model.dump_registers(chip, buffer); +} + +static void oxygen_proc_init(struct oxygen *chip) +{ + snd_card_ro_proc_new(chip->card, "oxygen", chip, oxygen_proc_read); +} + +static const struct pci_device_id * +oxygen_search_pci_id(struct oxygen *chip, const struct pci_device_id ids[]) +{ + u16 subdevice; + + /* + * Make sure the EEPROM pins are available, i.e., not used for SPI. + * (This function is called before we initialize or use SPI.) + */ + oxygen_clear_bits8(chip, OXYGEN_FUNCTION, + OXYGEN_FUNCTION_ENABLE_SPI_4_5); + /* + * Read the subsystem device ID directly from the EEPROM, because the + * chip didn't if the first EEPROM word was overwritten. + */ + subdevice = oxygen_read_eeprom(chip, 2); + /* use default ID if EEPROM is missing */ + if (subdevice == 0xffff && oxygen_read_eeprom(chip, 1) == 0xffff) + subdevice = 0x8788; + /* + * We use only the subsystem device ID for searching because it is + * unique even without the subsystem vendor ID, which may have been + * overwritten in the EEPROM. + */ + for (; ids->vendor; ++ids) + if (ids->subdevice == subdevice && + ids->driver_data != BROKEN_EEPROM_DRIVER_DATA) + return ids; + return NULL; +} + +static void oxygen_restore_eeprom(struct oxygen *chip, + const struct pci_device_id *id) +{ + u16 eeprom_id; + + eeprom_id = oxygen_read_eeprom(chip, 0); + if (eeprom_id != OXYGEN_EEPROM_ID && + (eeprom_id != 0xffff || id->subdevice != 0x8788)) { + /* + * This function gets called only when a known card model has + * been detected, i.e., we know there is a valid subsystem + * product ID at index 2 in the EEPROM. Therefore, we have + * been able to deduce the correct subsystem vendor ID, and + * this is enough information to restore the original EEPROM + * contents. + */ + oxygen_write_eeprom(chip, 1, id->subvendor); + oxygen_write_eeprom(chip, 0, OXYGEN_EEPROM_ID); + + oxygen_set_bits8(chip, OXYGEN_MISC, + OXYGEN_MISC_WRITE_PCI_SUBID); + pci_write_config_word(chip->pci, PCI_SUBSYSTEM_VENDOR_ID, + id->subvendor); + pci_write_config_word(chip->pci, PCI_SUBSYSTEM_ID, + id->subdevice); + oxygen_clear_bits8(chip, OXYGEN_MISC, + OXYGEN_MISC_WRITE_PCI_SUBID); + + dev_info(chip->card->dev, "EEPROM ID restored\n"); + } +} + +static void configure_pcie_bridge(struct pci_dev *pci) +{ + enum { PEX811X, PI7C9X110, XIO2001 }; + static const struct pci_device_id bridge_ids[] = { + { PCI_VDEVICE(PLX, 0x8111), .driver_data = PEX811X }, + { PCI_VDEVICE(PLX, 0x8112), .driver_data = PEX811X }, + { PCI_DEVICE(0x12d8, 0xe110), .driver_data = PI7C9X110 }, + { PCI_VDEVICE(TI, 0x8240), .driver_data = XIO2001 }, + { } + }; + struct pci_dev *bridge; + const struct pci_device_id *id; + u32 tmp; + + if (!pci->bus || !pci->bus->self) + return; + bridge = pci->bus->self; + + id = pci_match_id(bridge_ids, bridge); + if (!id) + return; + + switch (id->driver_data) { + case PEX811X: /* PLX PEX8111/PEX8112 PCIe/PCI bridge */ + pci_read_config_dword(bridge, 0x48, &tmp); + tmp |= 1; /* enable blind prefetching */ + tmp |= 1 << 11; /* enable beacon generation */ + pci_write_config_dword(bridge, 0x48, tmp); + + pci_write_config_dword(bridge, 0x84, 0x0c); + pci_read_config_dword(bridge, 0x88, &tmp); + tmp &= ~(7 << 27); + tmp |= 2 << 27; /* set prefetch size to 128 bytes */ + pci_write_config_dword(bridge, 0x88, tmp); + break; + + case PI7C9X110: /* Pericom PI7C9X110 PCIe/PCI bridge */ + pci_read_config_dword(bridge, 0x40, &tmp); + tmp |= 1; /* park the PCI arbiter to the sound chip */ + pci_write_config_dword(bridge, 0x40, tmp); + break; + + case XIO2001: /* Texas Instruments XIO2001 PCIe/PCI bridge */ + pci_read_config_dword(bridge, 0xe8, &tmp); + tmp &= ~0xf; /* request length limit: 64 bytes */ + tmp &= ~(0xf << 8); + tmp |= 1 << 8; /* request count limit: one buffer */ + pci_write_config_dword(bridge, 0xe8, tmp); + break; + } +} + +static void oxygen_init(struct oxygen *chip) +{ + unsigned int i; + + chip->dac_routing = 1; + for (i = 0; i < 8; ++i) + chip->dac_volume[i] = chip->model.dac_volume_min; + chip->dac_mute = 1; + chip->spdif_playback_enable = 0; + chip->spdif_bits = OXYGEN_SPDIF_C | OXYGEN_SPDIF_ORIGINAL | + (IEC958_AES1_CON_PCM_CODER << OXYGEN_SPDIF_CATEGORY_SHIFT); + chip->spdif_pcm_bits = chip->spdif_bits; + + if (!(oxygen_read8(chip, OXYGEN_REVISION) & OXYGEN_REVISION_2)) + oxygen_set_bits8(chip, OXYGEN_MISC, + OXYGEN_MISC_PCI_MEM_W_1_CLOCK); + + i = oxygen_read16(chip, OXYGEN_AC97_CONTROL); + chip->has_ac97_0 = (i & OXYGEN_AC97_CODEC_0) != 0; + chip->has_ac97_1 = (i & OXYGEN_AC97_CODEC_1) != 0; + + oxygen_write8_masked(chip, OXYGEN_FUNCTION, + OXYGEN_FUNCTION_RESET_CODEC | + chip->model.function_flags, + OXYGEN_FUNCTION_RESET_CODEC | + OXYGEN_FUNCTION_2WIRE_SPI_MASK | + OXYGEN_FUNCTION_ENABLE_SPI_4_5); + oxygen_write8(chip, OXYGEN_DMA_STATUS, 0); + oxygen_write8(chip, OXYGEN_DMA_PAUSE, 0); + oxygen_write8(chip, OXYGEN_PLAY_CHANNELS, + OXYGEN_PLAY_CHANNELS_2 | + OXYGEN_DMA_A_BURST_8 | + OXYGEN_DMA_MULTICH_BURST_8); + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0); + oxygen_write8_masked(chip, OXYGEN_MISC, + chip->model.misc_flags, + OXYGEN_MISC_WRITE_PCI_SUBID | + OXYGEN_MISC_REC_C_FROM_SPDIF | + OXYGEN_MISC_REC_B_FROM_AC97 | + OXYGEN_MISC_REC_A_FROM_MULTICH | + OXYGEN_MISC_MIDI); + oxygen_write8(chip, OXYGEN_REC_FORMAT, + (OXYGEN_FORMAT_16 << OXYGEN_REC_FORMAT_A_SHIFT) | + (OXYGEN_FORMAT_16 << OXYGEN_REC_FORMAT_B_SHIFT) | + (OXYGEN_FORMAT_16 << OXYGEN_REC_FORMAT_C_SHIFT)); + oxygen_write8(chip, OXYGEN_PLAY_FORMAT, + (OXYGEN_FORMAT_16 << OXYGEN_SPDIF_FORMAT_SHIFT) | + (OXYGEN_FORMAT_16 << OXYGEN_MULTICH_FORMAT_SHIFT)); + oxygen_write8(chip, OXYGEN_REC_CHANNELS, OXYGEN_REC_CHANNELS_2_2_2); + oxygen_write16(chip, OXYGEN_I2S_MULTICH_FORMAT, + OXYGEN_RATE_48000 | + chip->model.dac_i2s_format | + OXYGEN_I2S_MCLK(chip->model.dac_mclks) | + OXYGEN_I2S_BITS_16 | + OXYGEN_I2S_MASTER | + OXYGEN_I2S_BCLK_64); + if (chip->model.device_config & CAPTURE_0_FROM_I2S_1) + oxygen_write16(chip, OXYGEN_I2S_A_FORMAT, + OXYGEN_RATE_48000 | + chip->model.adc_i2s_format | + OXYGEN_I2S_MCLK(chip->model.adc_mclks) | + OXYGEN_I2S_BITS_16 | + OXYGEN_I2S_MASTER | + OXYGEN_I2S_BCLK_64); + else + oxygen_write16(chip, OXYGEN_I2S_A_FORMAT, + OXYGEN_I2S_MASTER | + OXYGEN_I2S_MUTE_MCLK); + if (chip->model.device_config & (CAPTURE_0_FROM_I2S_2 | + CAPTURE_2_FROM_I2S_2)) + oxygen_write16(chip, OXYGEN_I2S_B_FORMAT, + OXYGEN_RATE_48000 | + chip->model.adc_i2s_format | + OXYGEN_I2S_MCLK(chip->model.adc_mclks) | + OXYGEN_I2S_BITS_16 | + OXYGEN_I2S_MASTER | + OXYGEN_I2S_BCLK_64); + else + oxygen_write16(chip, OXYGEN_I2S_B_FORMAT, + OXYGEN_I2S_MASTER | + OXYGEN_I2S_MUTE_MCLK); + if (chip->model.device_config & CAPTURE_3_FROM_I2S_3) + oxygen_write16(chip, OXYGEN_I2S_C_FORMAT, + OXYGEN_RATE_48000 | + chip->model.adc_i2s_format | + OXYGEN_I2S_MCLK(chip->model.adc_mclks) | + OXYGEN_I2S_BITS_16 | + OXYGEN_I2S_MASTER | + OXYGEN_I2S_BCLK_64); + else + oxygen_write16(chip, OXYGEN_I2S_C_FORMAT, + OXYGEN_I2S_MASTER | + OXYGEN_I2S_MUTE_MCLK); + oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, + OXYGEN_SPDIF_OUT_ENABLE | + OXYGEN_SPDIF_LOOPBACK); + if (chip->model.device_config & CAPTURE_1_FROM_SPDIF) + oxygen_write32_masked(chip, OXYGEN_SPDIF_CONTROL, + OXYGEN_SPDIF_SENSE_MASK | + OXYGEN_SPDIF_LOCK_MASK | + OXYGEN_SPDIF_RATE_MASK | + OXYGEN_SPDIF_LOCK_PAR | + OXYGEN_SPDIF_IN_CLOCK_96, + OXYGEN_SPDIF_SENSE_MASK | + OXYGEN_SPDIF_LOCK_MASK | + OXYGEN_SPDIF_RATE_MASK | + OXYGEN_SPDIF_SENSE_PAR | + OXYGEN_SPDIF_LOCK_PAR | + OXYGEN_SPDIF_IN_CLOCK_MASK); + else + oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, + OXYGEN_SPDIF_SENSE_MASK | + OXYGEN_SPDIF_LOCK_MASK | + OXYGEN_SPDIF_RATE_MASK); + oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS, chip->spdif_bits); + oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS, + OXYGEN_2WIRE_LENGTH_8 | + OXYGEN_2WIRE_INTERRUPT_MASK | + OXYGEN_2WIRE_SPEED_STANDARD); + oxygen_clear_bits8(chip, OXYGEN_MPU401_CONTROL, OXYGEN_MPU401_LOOPBACK); + oxygen_write8(chip, OXYGEN_GPI_INTERRUPT_MASK, 0); + oxygen_write16(chip, OXYGEN_GPIO_INTERRUPT_MASK, 0); + oxygen_write16(chip, OXYGEN_PLAY_ROUTING, + OXYGEN_PLAY_MULTICH_I2S_DAC | + OXYGEN_PLAY_SPDIF_SPDIF | + (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT)); + oxygen_write8(chip, OXYGEN_REC_ROUTING, + OXYGEN_REC_A_ROUTE_I2S_ADC_1 | + OXYGEN_REC_B_ROUTE_I2S_ADC_2 | + OXYGEN_REC_C_ROUTE_SPDIF); + oxygen_write8(chip, OXYGEN_ADC_MONITOR, 0); + oxygen_write8(chip, OXYGEN_A_MONITOR_ROUTING, + (0 << OXYGEN_A_MONITOR_ROUTE_0_SHIFT) | + (1 << OXYGEN_A_MONITOR_ROUTE_1_SHIFT) | + (2 << OXYGEN_A_MONITOR_ROUTE_2_SHIFT) | + (3 << OXYGEN_A_MONITOR_ROUTE_3_SHIFT)); + + if (chip->has_ac97_0 | chip->has_ac97_1) + oxygen_write8(chip, OXYGEN_AC97_INTERRUPT_MASK, + OXYGEN_AC97_INT_READ_DONE | + OXYGEN_AC97_INT_WRITE_DONE); + else + oxygen_write8(chip, OXYGEN_AC97_INTERRUPT_MASK, 0); + oxygen_write32(chip, OXYGEN_AC97_OUT_CONFIG, 0); + oxygen_write32(chip, OXYGEN_AC97_IN_CONFIG, 0); + if (!(chip->has_ac97_0 | chip->has_ac97_1)) + oxygen_set_bits16(chip, OXYGEN_AC97_CONTROL, + OXYGEN_AC97_CLOCK_DISABLE); + if (!chip->has_ac97_0) { + oxygen_set_bits16(chip, OXYGEN_AC97_CONTROL, + OXYGEN_AC97_NO_CODEC_0); + } else { + oxygen_write_ac97(chip, 0, AC97_RESET, 0); + msleep(1); + oxygen_ac97_set_bits(chip, 0, CM9780_GPIO_SETUP, + CM9780_GPIO0IO | CM9780_GPIO1IO); + oxygen_ac97_set_bits(chip, 0, CM9780_MIXER, + CM9780_BSTSEL | CM9780_STRO_MIC | + CM9780_MIX2FR | CM9780_PCBSW); + oxygen_ac97_set_bits(chip, 0, CM9780_JACK, + CM9780_RSOE | CM9780_CBOE | + CM9780_SSOE | CM9780_FROE | + CM9780_MIC2MIC | CM9780_LI2LI); + oxygen_write_ac97(chip, 0, AC97_MASTER, 0x0000); + oxygen_write_ac97(chip, 0, AC97_PC_BEEP, 0x8000); + oxygen_write_ac97(chip, 0, AC97_MIC, 0x8808); + oxygen_write_ac97(chip, 0, AC97_LINE, 0x0808); + oxygen_write_ac97(chip, 0, AC97_CD, 0x8808); + oxygen_write_ac97(chip, 0, AC97_VIDEO, 0x8808); + oxygen_write_ac97(chip, 0, AC97_AUX, 0x8808); + oxygen_write_ac97(chip, 0, AC97_REC_GAIN, 0x8000); + oxygen_write_ac97(chip, 0, AC97_CENTER_LFE_MASTER, 0x8080); + oxygen_write_ac97(chip, 0, AC97_SURROUND_MASTER, 0x8080); + oxygen_ac97_clear_bits(chip, 0, CM9780_GPIO_STATUS, + CM9780_GPO0); + /* power down unused ADCs and DACs */ + oxygen_ac97_set_bits(chip, 0, AC97_POWERDOWN, + AC97_PD_PR0 | AC97_PD_PR1); + oxygen_ac97_set_bits(chip, 0, AC97_EXTENDED_STATUS, + AC97_EA_PRI | AC97_EA_PRJ | AC97_EA_PRK); + } + if (chip->has_ac97_1) { + oxygen_set_bits32(chip, OXYGEN_AC97_OUT_CONFIG, + OXYGEN_AC97_CODEC1_SLOT3 | + OXYGEN_AC97_CODEC1_SLOT4); + oxygen_write_ac97(chip, 1, AC97_RESET, 0); + msleep(1); + oxygen_write_ac97(chip, 1, AC97_MASTER, 0x0000); + oxygen_write_ac97(chip, 1, AC97_HEADPHONE, 0x8000); + oxygen_write_ac97(chip, 1, AC97_PC_BEEP, 0x8000); + oxygen_write_ac97(chip, 1, AC97_MIC, 0x8808); + oxygen_write_ac97(chip, 1, AC97_LINE, 0x8808); + oxygen_write_ac97(chip, 1, AC97_CD, 0x8808); + oxygen_write_ac97(chip, 1, AC97_VIDEO, 0x8808); + oxygen_write_ac97(chip, 1, AC97_AUX, 0x8808); + oxygen_write_ac97(chip, 1, AC97_PCM, 0x0808); + oxygen_write_ac97(chip, 1, AC97_REC_SEL, 0x0000); + oxygen_write_ac97(chip, 1, AC97_REC_GAIN, 0x0000); + oxygen_ac97_set_bits(chip, 1, 0x6a, 0x0040); + } +} + +static void oxygen_shutdown(struct oxygen *chip) +{ + guard(spinlock_irq)(&chip->reg_lock); + chip->interrupt_mask = 0; + chip->pcm_running = 0; + oxygen_write16(chip, OXYGEN_DMA_STATUS, 0); + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0); +} + +static void oxygen_card_free(struct snd_card *card) +{ + struct oxygen *chip = card->private_data; + + oxygen_shutdown(chip); + flush_work(&chip->spdif_input_bits_work); + flush_work(&chip->gpio_work); + chip->model.cleanup(chip); + mutex_destroy(&chip->mutex); +} + +static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, + struct module *owner, + const struct pci_device_id *ids, + int (*get_model)(struct oxygen *chip, + const struct pci_device_id *id + ) + ) +{ + struct snd_card *card; + struct oxygen *chip; + const struct pci_device_id *pci_id; + int err; + + err = snd_devm_card_new(&pci->dev, index, id, owner, + sizeof(*chip), &card); + if (err < 0) + return err; + + chip = card->private_data; + chip->card = card; + chip->pci = pci; + chip->irq = -1; + spin_lock_init(&chip->reg_lock); + mutex_init(&chip->mutex); + INIT_WORK(&chip->spdif_input_bits_work, + oxygen_spdif_input_bits_changed); + INIT_WORK(&chip->gpio_work, oxygen_gpio_changed); + init_waitqueue_head(&chip->ac97_waitqueue); + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + err = pcim_request_all_regions(pci, DRIVER); + if (err < 0) { + dev_err(card->dev, "cannot reserve PCI resources\n"); + return err; + } + + if (!(pci_resource_flags(pci, 0) & IORESOURCE_IO) || + pci_resource_len(pci, 0) < OXYGEN_IO_SIZE) { + dev_err(card->dev, "invalid PCI I/O range\n"); + return -ENXIO; + } + chip->addr = pci_resource_start(pci, 0); + + pci_id = oxygen_search_pci_id(chip, ids); + if (!pci_id) + return -ENODEV; + + oxygen_restore_eeprom(chip, pci_id); + err = get_model(chip, pci_id); + if (err < 0) + return err; + + if (chip->model.model_data_size) { + chip->model_data = devm_kzalloc(&pci->dev, + chip->model.model_data_size, + GFP_KERNEL); + if (!chip->model_data) + return -ENOMEM; + } + + pci_set_master(pci); + card->private_free = oxygen_card_free; + + configure_pcie_bridge(pci); + oxygen_init(chip); + chip->model.init(chip); + + err = devm_request_irq(&pci->dev, pci->irq, oxygen_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (err < 0) { + dev_err(card->dev, "cannot grab interrupt %d\n", pci->irq); + return err; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + + strscpy(card->driver, chip->model.chip); + strscpy(card->shortname, chip->model.shortname); + sprintf(card->longname, "%s at %#lx, irq %i", + chip->model.longname, chip->addr, chip->irq); + strscpy(card->mixername, chip->model.chip); + snd_component_add(card, chip->model.chip); + + err = oxygen_pcm_init(chip); + if (err < 0) + return err; + + err = oxygen_mixer_init(chip); + if (err < 0) + return err; + + if (chip->model.device_config & (MIDI_OUTPUT | MIDI_INPUT)) { + unsigned int info_flags = + MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK; + if (chip->model.device_config & MIDI_OUTPUT) + info_flags |= MPU401_INFO_OUTPUT; + if (chip->model.device_config & MIDI_INPUT) + info_flags |= MPU401_INFO_INPUT; + err = snd_mpu401_uart_new(card, 0, MPU401_HW_CMIPCI, + chip->addr + OXYGEN_MPU401, + info_flags, -1, &chip->midi); + if (err < 0) + return err; + } + + oxygen_proc_init(chip); + + scoped_guard(spinlock_irq, &chip->reg_lock) { + if (chip->model.device_config & CAPTURE_1_FROM_SPDIF) + chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_DETECT; + if (chip->has_ac97_0 | chip->has_ac97_1) + chip->interrupt_mask |= OXYGEN_INT_AC97; + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask); + } + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + return 0; +} + +int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, + struct module *owner, + const struct pci_device_id *ids, + int (*get_model)(struct oxygen *chip, + const struct pci_device_id *id)) +{ + return snd_card_free_on_error(&pci->dev, + __oxygen_pci_probe(pci, index, id, owner, ids, get_model)); +} +EXPORT_SYMBOL(oxygen_pci_probe); + +static int oxygen_pci_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct oxygen *chip = card->private_data; + unsigned int saved_interrupt_mask; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + if (chip->model.suspend) + chip->model.suspend(chip); + + scoped_guard(spinlock_irq, &chip->reg_lock) { + saved_interrupt_mask = chip->interrupt_mask; + chip->interrupt_mask = 0; + oxygen_write16(chip, OXYGEN_DMA_STATUS, 0); + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0); + } + + flush_work(&chip->spdif_input_bits_work); + flush_work(&chip->gpio_work); + chip->interrupt_mask = saved_interrupt_mask; + return 0; +} + +static const u32 registers_to_restore[OXYGEN_IO_SIZE / 32] = { + 0xffffffff, 0x00ff077f, 0x00011d08, 0x007f00ff, + 0x00300000, 0x00000fe4, 0x0ff7001f, 0x00000000 +}; +static const u32 ac97_registers_to_restore[2][0x40 / 32] = { + { 0x18284fa2, 0x03060000 }, + { 0x00007fa6, 0x00200000 } +}; + +static inline int is_bit_set(const u32 *bitmap, unsigned int bit) +{ + return bitmap[bit / 32] & (1 << (bit & 31)); +} + +static void oxygen_restore_ac97(struct oxygen *chip, unsigned int codec) +{ + unsigned int i; + + oxygen_write_ac97(chip, codec, AC97_RESET, 0); + msleep(1); + for (i = 1; i < 0x40; ++i) + if (is_bit_set(ac97_registers_to_restore[codec], i)) + oxygen_write_ac97(chip, codec, i * 2, + chip->saved_ac97_registers[codec][i]); +} + +static int oxygen_pci_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct oxygen *chip = card->private_data; + unsigned int i; + + oxygen_write16(chip, OXYGEN_DMA_STATUS, 0); + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0); + for (i = 0; i < OXYGEN_IO_SIZE; ++i) + if (is_bit_set(registers_to_restore, i)) + oxygen_write8(chip, i, chip->saved_registers._8[i]); + if (chip->has_ac97_0) + oxygen_restore_ac97(chip, 0); + if (chip->has_ac97_1) + oxygen_restore_ac97(chip, 1); + + if (chip->model.resume) + chip->model.resume(chip); + + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +EXPORT_SIMPLE_DEV_PM_OPS(oxygen_pci_pm, oxygen_pci_suspend, oxygen_pci_resume); + +void oxygen_pci_shutdown(struct pci_dev *pci) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct oxygen *chip = card->private_data; + + oxygen_shutdown(chip); + chip->model.cleanup(chip); +} +EXPORT_SYMBOL(oxygen_pci_shutdown); diff --git a/sound/pci/oxygen/oxygen_mixer.c b/sound/pci/oxygen/oxygen_mixer.c new file mode 100644 index 000000000..256a601d7 --- /dev/null +++ b/sound/pci/oxygen/oxygen_mixer.c @@ -0,0 +1,1105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * C-Media CMI8788 driver - mixer code + * + * Copyright (c) Clemens Ladisch + */ + +#include +#include +#include +#include +#include +#include "oxygen.h" +#include "cm9780.h" + +static int dac_volume_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + struct oxygen *chip = ctl->private_data; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = chip->model.dac_channels_mixer; + info->value.integer.min = chip->model.dac_volume_min; + info->value.integer.max = chip->model.dac_volume_max; + return 0; +} + +static int dac_volume_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + unsigned int i; + + guard(mutex)(&chip->mutex); + for (i = 0; i < chip->model.dac_channels_mixer; ++i) + value->value.integer.value[i] = chip->dac_volume[i]; + return 0; +} + +static int dac_volume_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + unsigned int i; + int changed; + + changed = 0; + guard(mutex)(&chip->mutex); + for (i = 0; i < chip->model.dac_channels_mixer; ++i) + if (value->value.integer.value[i] != chip->dac_volume[i]) { + chip->dac_volume[i] = value->value.integer.value[i]; + changed = 1; + } + if (changed) + chip->model.update_dac_volume(chip); + return changed; +} + +static int dac_mute_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + + guard(mutex)(&chip->mutex); + value->value.integer.value[0] = !chip->dac_mute; + return 0; +} + +static int dac_mute_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + int changed; + + guard(mutex)(&chip->mutex); + changed = (!value->value.integer.value[0]) != chip->dac_mute; + if (changed) { + chip->dac_mute = !value->value.integer.value[0]; + chip->model.update_dac_mute(chip); + } + return changed; +} + +static unsigned int upmix_item_count(struct oxygen *chip) +{ + if (chip->model.dac_channels_pcm < 8) + return 2; + else if (chip->model.update_center_lfe_mix) + return 5; + else + return 3; +} + +static int upmix_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) +{ + static const char *const names[5] = { + "Front", + "Front+Surround", + "Front+Surround+Back", + "Front+Surround+Center/LFE", + "Front+Surround+Center/LFE+Back", + }; + struct oxygen *chip = ctl->private_data; + unsigned int count = upmix_item_count(chip); + + return snd_ctl_enum_info(info, 1, count, names); +} + +static int upmix_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + + guard(mutex)(&chip->mutex); + value->value.enumerated.item[0] = chip->dac_routing; + return 0; +} + +void oxygen_update_dac_routing(struct oxygen *chip) +{ + /* DAC 0: front, DAC 1: surround, DAC 2: center/LFE, DAC 3: back */ + static const unsigned int reg_values[5] = { + /* stereo -> front */ + (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT), + /* stereo -> front+surround */ + (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT), + /* stereo -> front+surround+back */ + (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT), + /* stereo -> front+surround+center/LFE */ + (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT), + /* stereo -> front+surround+center/LFE+back */ + (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT), + }; + u8 channels; + unsigned int reg_value; + + channels = oxygen_read8(chip, OXYGEN_PLAY_CHANNELS) & + OXYGEN_PLAY_CHANNELS_MASK; + if (channels == OXYGEN_PLAY_CHANNELS_2) + reg_value = reg_values[chip->dac_routing]; + else if (channels == OXYGEN_PLAY_CHANNELS_8) + /* in 7.1 mode, "rear" channels go to the "back" jack */ + reg_value = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (3 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (1 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT); + else + reg_value = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT); + if (chip->model.adjust_dac_routing) + reg_value = chip->model.adjust_dac_routing(chip, reg_value); + oxygen_write16_masked(chip, OXYGEN_PLAY_ROUTING, reg_value, + OXYGEN_PLAY_DAC0_SOURCE_MASK | + OXYGEN_PLAY_DAC1_SOURCE_MASK | + OXYGEN_PLAY_DAC2_SOURCE_MASK | + OXYGEN_PLAY_DAC3_SOURCE_MASK); + if (chip->model.update_center_lfe_mix) + chip->model.update_center_lfe_mix(chip, chip->dac_routing > 2); +} +EXPORT_SYMBOL(oxygen_update_dac_routing); + +static int upmix_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + unsigned int count = upmix_item_count(chip); + int changed; + + if (value->value.enumerated.item[0] >= count) + return -EINVAL; + guard(mutex)(&chip->mutex); + changed = value->value.enumerated.item[0] != chip->dac_routing; + if (changed) { + chip->dac_routing = value->value.enumerated.item[0]; + oxygen_update_dac_routing(chip); + } + return changed; +} + +static int spdif_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + + guard(mutex)(&chip->mutex); + value->value.integer.value[0] = chip->spdif_playback_enable; + return 0; +} + +static unsigned int oxygen_spdif_rate(unsigned int oxygen_rate) +{ + switch (oxygen_rate) { + case OXYGEN_RATE_32000: + return IEC958_AES3_CON_FS_32000 << OXYGEN_SPDIF_CS_RATE_SHIFT; + case OXYGEN_RATE_44100: + return IEC958_AES3_CON_FS_44100 << OXYGEN_SPDIF_CS_RATE_SHIFT; + default: /* OXYGEN_RATE_48000 */ + return IEC958_AES3_CON_FS_48000 << OXYGEN_SPDIF_CS_RATE_SHIFT; + case OXYGEN_RATE_64000: + return 0xb << OXYGEN_SPDIF_CS_RATE_SHIFT; + case OXYGEN_RATE_88200: + return IEC958_AES3_CON_FS_88200 << OXYGEN_SPDIF_CS_RATE_SHIFT; + case OXYGEN_RATE_96000: + return IEC958_AES3_CON_FS_96000 << OXYGEN_SPDIF_CS_RATE_SHIFT; + case OXYGEN_RATE_176400: + return IEC958_AES3_CON_FS_176400 << OXYGEN_SPDIF_CS_RATE_SHIFT; + case OXYGEN_RATE_192000: + return IEC958_AES3_CON_FS_192000 << OXYGEN_SPDIF_CS_RATE_SHIFT; + } +} + +void oxygen_update_spdif_source(struct oxygen *chip) +{ + u32 old_control, new_control; + u16 old_routing, new_routing; + unsigned int oxygen_rate; + + old_control = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL); + old_routing = oxygen_read16(chip, OXYGEN_PLAY_ROUTING); + if (chip->pcm_active & (1 << PCM_SPDIF)) { + new_control = old_control | OXYGEN_SPDIF_OUT_ENABLE; + new_routing = (old_routing & ~OXYGEN_PLAY_SPDIF_MASK) + | OXYGEN_PLAY_SPDIF_SPDIF; + oxygen_rate = (old_control >> OXYGEN_SPDIF_OUT_RATE_SHIFT) + & OXYGEN_I2S_RATE_MASK; + /* S/PDIF rate was already set by the caller */ + } else if ((chip->pcm_active & (1 << PCM_MULTICH)) && + chip->spdif_playback_enable) { + new_routing = (old_routing & ~OXYGEN_PLAY_SPDIF_MASK) + | OXYGEN_PLAY_SPDIF_MULTICH_01; + oxygen_rate = oxygen_read16(chip, OXYGEN_I2S_MULTICH_FORMAT) + & OXYGEN_I2S_RATE_MASK; + new_control = (old_control & ~OXYGEN_SPDIF_OUT_RATE_MASK) | + (oxygen_rate << OXYGEN_SPDIF_OUT_RATE_SHIFT) | + OXYGEN_SPDIF_OUT_ENABLE; + } else { + new_control = old_control & ~OXYGEN_SPDIF_OUT_ENABLE; + new_routing = old_routing; + oxygen_rate = OXYGEN_RATE_44100; + } + if (old_routing != new_routing) { + oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, + new_control & ~OXYGEN_SPDIF_OUT_ENABLE); + oxygen_write16(chip, OXYGEN_PLAY_ROUTING, new_routing); + } + if (new_control & OXYGEN_SPDIF_OUT_ENABLE) + oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS, + oxygen_spdif_rate(oxygen_rate) | + ((chip->pcm_active & (1 << PCM_SPDIF)) ? + chip->spdif_pcm_bits : chip->spdif_bits)); + oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, new_control); +} + +static int spdif_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + int changed; + + guard(mutex)(&chip->mutex); + changed = value->value.integer.value[0] != chip->spdif_playback_enable; + if (changed) { + chip->spdif_playback_enable = !!value->value.integer.value[0]; + spin_lock_irq(&chip->reg_lock); + oxygen_update_spdif_source(chip); + spin_unlock_irq(&chip->reg_lock); + } + return changed; +} + +static int spdif_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_IEC958; + info->count = 1; + return 0; +} + +static void oxygen_to_iec958(u32 bits, struct snd_ctl_elem_value *value) +{ + value->value.iec958.status[0] = + bits & (OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C | + OXYGEN_SPDIF_PREEMPHASIS); + value->value.iec958.status[1] = /* category and original */ + bits >> OXYGEN_SPDIF_CATEGORY_SHIFT; +} + +static u32 iec958_to_oxygen(struct snd_ctl_elem_value *value) +{ + u32 bits; + + bits = value->value.iec958.status[0] & + (OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C | + OXYGEN_SPDIF_PREEMPHASIS); + bits |= value->value.iec958.status[1] << OXYGEN_SPDIF_CATEGORY_SHIFT; + if (bits & OXYGEN_SPDIF_NONAUDIO) + bits |= OXYGEN_SPDIF_V; + return bits; +} + +static inline void write_spdif_bits(struct oxygen *chip, u32 bits) +{ + oxygen_write32_masked(chip, OXYGEN_SPDIF_OUTPUT_BITS, bits, + OXYGEN_SPDIF_NONAUDIO | + OXYGEN_SPDIF_C | + OXYGEN_SPDIF_PREEMPHASIS | + OXYGEN_SPDIF_CATEGORY_MASK | + OXYGEN_SPDIF_ORIGINAL | + OXYGEN_SPDIF_V); +} + +static int spdif_default_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + + guard(mutex)(&chip->mutex); + oxygen_to_iec958(chip->spdif_bits, value); + return 0; +} + +static int spdif_default_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u32 new_bits; + int changed; + + new_bits = iec958_to_oxygen(value); + guard(mutex)(&chip->mutex); + changed = new_bits != chip->spdif_bits; + if (changed) { + chip->spdif_bits = new_bits; + if (!(chip->pcm_active & (1 << PCM_SPDIF))) + write_spdif_bits(chip, new_bits); + } + return changed; +} + +static int spdif_mask_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + value->value.iec958.status[0] = IEC958_AES0_NONAUDIO | + IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS; + value->value.iec958.status[1] = + IEC958_AES1_CON_CATEGORY | IEC958_AES1_CON_ORIGINAL; + return 0; +} + +static int spdif_pcm_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + + guard(mutex)(&chip->mutex); + oxygen_to_iec958(chip->spdif_pcm_bits, value); + return 0; +} + +static int spdif_pcm_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u32 new_bits; + int changed; + + new_bits = iec958_to_oxygen(value); + guard(mutex)(&chip->mutex); + changed = new_bits != chip->spdif_pcm_bits; + if (changed) { + chip->spdif_pcm_bits = new_bits; + if (chip->pcm_active & (1 << PCM_SPDIF)) + write_spdif_bits(chip, new_bits); + } + return changed; +} + +static int spdif_input_mask_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + value->value.iec958.status[0] = 0xff; + value->value.iec958.status[1] = 0xff; + value->value.iec958.status[2] = 0xff; + value->value.iec958.status[3] = 0xff; + return 0; +} + +static int spdif_input_default_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u32 bits; + + bits = oxygen_read32(chip, OXYGEN_SPDIF_INPUT_BITS); + value->value.iec958.status[0] = bits; + value->value.iec958.status[1] = bits >> 8; + value->value.iec958.status[2] = bits >> 16; + value->value.iec958.status[3] = bits >> 24; + return 0; +} + +static int spdif_bit_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u32 bit = ctl->private_value; + + value->value.integer.value[0] = + !!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL) & bit); + return 0; +} + +static int spdif_bit_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u32 bit = ctl->private_value; + u32 oldreg, newreg; + int changed; + + guard(spinlock_irq)(&chip->reg_lock); + oldreg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL); + if (value->value.integer.value[0]) + newreg = oldreg | bit; + else + newreg = oldreg & ~bit; + changed = newreg != oldreg; + if (changed) + oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, newreg); + return changed; +} + +static int monitor_volume_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = 0; + info->value.integer.max = 1; + return 0; +} + +static int monitor_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u8 bit = ctl->private_value; + int invert = ctl->private_value & (1 << 8); + + value->value.integer.value[0] = + !!invert ^ !!(oxygen_read8(chip, OXYGEN_ADC_MONITOR) & bit); + return 0; +} + +static int monitor_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u8 bit = ctl->private_value; + int invert = ctl->private_value & (1 << 8); + u8 oldreg, newreg; + int changed; + + guard(spinlock_irq)(&chip->reg_lock); + oldreg = oxygen_read8(chip, OXYGEN_ADC_MONITOR); + if ((!!value->value.integer.value[0] ^ !!invert) != 0) + newreg = oldreg | bit; + else + newreg = oldreg & ~bit; + changed = newreg != oldreg; + if (changed) + oxygen_write8(chip, OXYGEN_ADC_MONITOR, newreg); + return changed; +} + +static int ac97_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + unsigned int codec = (ctl->private_value >> 24) & 1; + unsigned int index = ctl->private_value & 0xff; + unsigned int bitnr = (ctl->private_value >> 8) & 0xff; + int invert = ctl->private_value & (1 << 16); + u16 reg; + + guard(mutex)(&chip->mutex); + reg = oxygen_read_ac97(chip, codec, index); + if (!(reg & (1 << bitnr)) ^ !invert) + value->value.integer.value[0] = 1; + else + value->value.integer.value[0] = 0; + return 0; +} + +static void mute_ac97_ctl(struct oxygen *chip, unsigned int control) +{ + unsigned int priv_idx; + u16 value; + + if (!chip->controls[control]) + return; + priv_idx = chip->controls[control]->private_value & 0xff; + value = oxygen_read_ac97(chip, 0, priv_idx); + if (!(value & 0x8000)) { + oxygen_write_ac97(chip, 0, priv_idx, value | 0x8000); + if (chip->model.ac97_switch) + chip->model.ac97_switch(chip, priv_idx, 0x8000); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->controls[control]->id); + } +} + +static int ac97_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + unsigned int codec = (ctl->private_value >> 24) & 1; + unsigned int index = ctl->private_value & 0xff; + unsigned int bitnr = (ctl->private_value >> 8) & 0xff; + int invert = ctl->private_value & (1 << 16); + u16 oldreg, newreg; + int change; + + guard(mutex)(&chip->mutex); + oldreg = oxygen_read_ac97(chip, codec, index); + newreg = oldreg; + if (!value->value.integer.value[0] ^ !invert) + newreg |= 1 << bitnr; + else + newreg &= ~(1 << bitnr); + change = newreg != oldreg; + if (change) { + oxygen_write_ac97(chip, codec, index, newreg); + if (codec == 0 && chip->model.ac97_switch) + chip->model.ac97_switch(chip, index, newreg & 0x8000); + if (index == AC97_LINE) { + oxygen_write_ac97_masked(chip, 0, CM9780_GPIO_STATUS, + newreg & 0x8000 ? + CM9780_GPO0 : 0, CM9780_GPO0); + if (!(newreg & 0x8000)) { + mute_ac97_ctl(chip, CONTROL_MIC_CAPTURE_SWITCH); + mute_ac97_ctl(chip, CONTROL_CD_CAPTURE_SWITCH); + mute_ac97_ctl(chip, CONTROL_AUX_CAPTURE_SWITCH); + } + } else if ((index == AC97_MIC || index == AC97_CD || + index == AC97_VIDEO || index == AC97_AUX) && + bitnr == 15 && !(newreg & 0x8000)) { + mute_ac97_ctl(chip, CONTROL_LINE_CAPTURE_SWITCH); + oxygen_write_ac97_masked(chip, 0, CM9780_GPIO_STATUS, + CM9780_GPO0, CM9780_GPO0); + } + } + return change; +} + +static int ac97_volume_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + int stereo = (ctl->private_value >> 16) & 1; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = stereo ? 2 : 1; + info->value.integer.min = 0; + info->value.integer.max = 0x1f; + return 0; +} + +static int ac97_volume_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + unsigned int codec = (ctl->private_value >> 24) & 1; + int stereo = (ctl->private_value >> 16) & 1; + unsigned int index = ctl->private_value & 0xff; + u16 reg; + + guard(mutex)(&chip->mutex); + reg = oxygen_read_ac97(chip, codec, index); + if (!stereo) { + value->value.integer.value[0] = 31 - (reg & 0x1f); + } else { + value->value.integer.value[0] = 31 - ((reg >> 8) & 0x1f); + value->value.integer.value[1] = 31 - (reg & 0x1f); + } + return 0; +} + +static int ac97_volume_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + unsigned int codec = (ctl->private_value >> 24) & 1; + int stereo = (ctl->private_value >> 16) & 1; + unsigned int index = ctl->private_value & 0xff; + u16 oldreg, newreg; + int change; + + guard(mutex)(&chip->mutex); + oldreg = oxygen_read_ac97(chip, codec, index); + if (!stereo) { + newreg = oldreg & ~0x1f; + newreg |= 31 - (value->value.integer.value[0] & 0x1f); + } else { + newreg = oldreg & ~0x1f1f; + newreg |= (31 - (value->value.integer.value[0] & 0x1f)) << 8; + newreg |= 31 - (value->value.integer.value[1] & 0x1f); + } + change = newreg != oldreg; + if (change) + oxygen_write_ac97(chip, codec, index, newreg); + return change; +} + +static int mic_fmic_source_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[] = { "Mic Jack", "Front Panel" }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int mic_fmic_source_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + + guard(mutex)(&chip->mutex); + value->value.enumerated.item[0] = + !!(oxygen_read_ac97(chip, 0, CM9780_JACK) & CM9780_FMIC2MIC); + return 0; +} + +static int mic_fmic_source_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 oldreg, newreg; + int change; + + guard(mutex)(&chip->mutex); + oldreg = oxygen_read_ac97(chip, 0, CM9780_JACK); + if (value->value.enumerated.item[0]) + newreg = oldreg | CM9780_FMIC2MIC; + else + newreg = oldreg & ~CM9780_FMIC2MIC; + change = newreg != oldreg; + if (change) + oxygen_write_ac97(chip, 0, CM9780_JACK, newreg); + return change; +} + +static int ac97_fp_rec_volume_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 0; + info->value.integer.max = 7; + return 0; +} + +static int ac97_fp_rec_volume_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 reg; + + guard(mutex)(&chip->mutex); + reg = oxygen_read_ac97(chip, 1, AC97_REC_GAIN); + value->value.integer.value[0] = reg & 7; + value->value.integer.value[1] = (reg >> 8) & 7; + return 0; +} + +static int ac97_fp_rec_volume_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 oldreg, newreg; + int change; + + guard(mutex)(&chip->mutex); + oldreg = oxygen_read_ac97(chip, 1, AC97_REC_GAIN); + newreg = oldreg & ~0x0707; + newreg = newreg | (value->value.integer.value[0] & 7); + newreg = newreg | ((value->value.integer.value[1] & 7) << 8); + change = newreg != oldreg; + if (change) + oxygen_write_ac97(chip, 1, AC97_REC_GAIN, newreg); + return change; +} + +#define AC97_SWITCH(xname, codec, index, bitnr, invert) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = snd_ctl_boolean_mono_info, \ + .get = ac97_switch_get, \ + .put = ac97_switch_put, \ + .private_value = ((codec) << 24) | ((invert) << 16) | \ + ((bitnr) << 8) | (index), \ + } +#define AC97_VOLUME(xname, codec, index, stereo) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = ac97_volume_info, \ + .get = ac97_volume_get, \ + .put = ac97_volume_put, \ + .tlv = { .p = ac97_db_scale, }, \ + .private_value = ((codec) << 24) | ((stereo) << 16) | (index), \ + } + +static DECLARE_TLV_DB_SCALE(monitor_db_scale, -600, 600, 0); +static DECLARE_TLV_DB_SCALE(ac97_db_scale, -3450, 150, 0); +static DECLARE_TLV_DB_SCALE(ac97_rec_db_scale, 0, 150, 0); + +static const struct snd_kcontrol_new controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = dac_volume_info, + .get = dac_volume_get, + .put = dac_volume_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = dac_mute_get, + .put = dac_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Stereo Upmixing", + .info = upmix_info, + .get = upmix_get, + .put = upmix_put, + }, +}; + +static const struct snd_kcontrol_new spdif_output_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), + .info = snd_ctl_boolean_mono_info, + .get = spdif_switch_get, + .put = spdif_switch_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .device = 1, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = spdif_info, + .get = spdif_default_get, + .put = spdif_default_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .device = 1, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = spdif_info, + .get = spdif_mask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .device = 1, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .info = spdif_info, + .get = spdif_pcm_get, + .put = spdif_pcm_put, + }, +}; + +static const struct snd_kcontrol_new spdif_input_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .device = 1, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = spdif_info, + .get = spdif_input_mask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .device = 1, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = spdif_info, + .get = spdif_input_default_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("Loopback ", NONE, SWITCH), + .info = snd_ctl_boolean_mono_info, + .get = spdif_bit_switch_get, + .put = spdif_bit_switch_put, + .private_value = OXYGEN_SPDIF_LOOPBACK, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("Validity Check ",CAPTURE,SWITCH), + .info = snd_ctl_boolean_mono_info, + .get = spdif_bit_switch_get, + .put = spdif_bit_switch_put, + .private_value = OXYGEN_SPDIF_SPDVALID, + }, +}; + +static const struct { + unsigned int pcm_dev; + struct snd_kcontrol_new controls[2]; +} monitor_controls[] = { + { + .pcm_dev = CAPTURE_0_FROM_I2S_1, + .controls = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Input Monitor Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_A, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Input Monitor Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = monitor_volume_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_A_HALF_VOL + | (1 << 8), + .tlv = { .p = monitor_db_scale, }, + }, + }, + }, + { + .pcm_dev = CAPTURE_0_FROM_I2S_2, + .controls = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Input Monitor Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_B, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Input Monitor Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = monitor_volume_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_B_HALF_VOL + | (1 << 8), + .tlv = { .p = monitor_db_scale, }, + }, + }, + }, + { + .pcm_dev = CAPTURE_2_FROM_I2S_2, + .controls = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Input Monitor Playback Switch", + .index = 1, + .info = snd_ctl_boolean_mono_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_B, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Input Monitor Playback Volume", + .index = 1, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = monitor_volume_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_B_HALF_VOL + | (1 << 8), + .tlv = { .p = monitor_db_scale, }, + }, + }, + }, + { + .pcm_dev = CAPTURE_3_FROM_I2S_3, + .controls = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Input Monitor Playback Switch", + .index = 2, + .info = snd_ctl_boolean_mono_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_C, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Input Monitor Playback Volume", + .index = 2, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = monitor_volume_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_C_HALF_VOL + | (1 << 8), + .tlv = { .p = monitor_db_scale, }, + }, + }, + }, + { + .pcm_dev = CAPTURE_1_FROM_SPDIF, + .controls = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Input Monitor Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_C, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Input Monitor Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = monitor_volume_info, + .get = monitor_get, + .put = monitor_put, + .private_value = OXYGEN_ADC_MONITOR_C_HALF_VOL + | (1 << 8), + .tlv = { .p = monitor_db_scale, }, + }, + }, + }, +}; + +static const struct snd_kcontrol_new ac97_controls[] = { + AC97_VOLUME("Mic Capture Volume", 0, AC97_MIC, 0), + AC97_SWITCH("Mic Capture Switch", 0, AC97_MIC, 15, 1), + AC97_SWITCH("Mic Boost (+20dB)", 0, AC97_MIC, 6, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Source Capture Enum", + .info = mic_fmic_source_info, + .get = mic_fmic_source_get, + .put = mic_fmic_source_put, + }, + AC97_SWITCH("Line Capture Switch", 0, AC97_LINE, 15, 1), + AC97_VOLUME("CD Capture Volume", 0, AC97_CD, 1), + AC97_SWITCH("CD Capture Switch", 0, AC97_CD, 15, 1), + AC97_VOLUME("Aux Capture Volume", 0, AC97_AUX, 1), + AC97_SWITCH("Aux Capture Switch", 0, AC97_AUX, 15, 1), +}; + +static const struct snd_kcontrol_new ac97_fp_controls[] = { + AC97_VOLUME("Front Panel Playback Volume", 1, AC97_HEADPHONE, 1), + AC97_SWITCH("Front Panel Playback Switch", 1, AC97_HEADPHONE, 15, 1), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Front Panel Capture Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = ac97_fp_rec_volume_info, + .get = ac97_fp_rec_volume_get, + .put = ac97_fp_rec_volume_put, + .tlv = { .p = ac97_rec_db_scale, }, + }, + AC97_SWITCH("Front Panel Capture Switch", 1, AC97_REC_GAIN, 15, 1), +}; + +static void oxygen_any_ctl_free(struct snd_kcontrol *ctl) +{ + struct oxygen *chip = ctl->private_data; + unsigned int i; + + /* I'm too lazy to write a function for each control :-) */ + for (i = 0; i < ARRAY_SIZE(chip->controls); ++i) + chip->controls[i] = NULL; +} + +static int add_controls(struct oxygen *chip, + const struct snd_kcontrol_new controls[], + unsigned int count) +{ + static const char *const known_ctl_names[CONTROL_COUNT] = { + [CONTROL_SPDIF_PCM] = + SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), + [CONTROL_SPDIF_INPUT_BITS] = + SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + [CONTROL_MIC_CAPTURE_SWITCH] = "Mic Capture Switch", + [CONTROL_LINE_CAPTURE_SWITCH] = "Line Capture Switch", + [CONTROL_CD_CAPTURE_SWITCH] = "CD Capture Switch", + [CONTROL_AUX_CAPTURE_SWITCH] = "Aux Capture Switch", + }; + unsigned int i; + struct snd_kcontrol_new template; + struct snd_kcontrol *ctl; + int j, err; + + for (i = 0; i < count; ++i) { + template = controls[i]; + if (chip->model.control_filter) { + err = chip->model.control_filter(&template); + if (err < 0) + return err; + if (err == 1) + continue; + } + if (!strcmp(template.name, "Stereo Upmixing") && + chip->model.dac_channels_pcm == 2) + continue; + if (!strcmp(template.name, "Mic Source Capture Enum") && + !(chip->model.device_config & AC97_FMIC_SWITCH)) + continue; + if (!strncmp(template.name, "CD Capture ", 11) && + !(chip->model.device_config & AC97_CD_INPUT)) + continue; + if (!strcmp(template.name, "Master Playback Volume") && + chip->model.dac_tlv) { + template.tlv.p = chip->model.dac_tlv; + template.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + } + ctl = snd_ctl_new1(&template, chip); + if (!ctl) + return -ENOMEM; + err = snd_ctl_add(chip->card, ctl); + if (err < 0) + return err; + j = match_string(known_ctl_names, CONTROL_COUNT, ctl->id.name); + if (j >= 0) { + chip->controls[j] = ctl; + ctl->private_free = oxygen_any_ctl_free; + } + } + return 0; +} + +int oxygen_mixer_init(struct oxygen *chip) +{ + unsigned int i; + int err; + + err = add_controls(chip, controls, ARRAY_SIZE(controls)); + if (err < 0) + return err; + if (chip->model.device_config & PLAYBACK_1_TO_SPDIF) { + err = add_controls(chip, spdif_output_controls, + ARRAY_SIZE(spdif_output_controls)); + if (err < 0) + return err; + } + if (chip->model.device_config & CAPTURE_1_FROM_SPDIF) { + err = add_controls(chip, spdif_input_controls, + ARRAY_SIZE(spdif_input_controls)); + if (err < 0) + return err; + } + for (i = 0; i < ARRAY_SIZE(monitor_controls); ++i) { + if (!(chip->model.device_config & monitor_controls[i].pcm_dev)) + continue; + err = add_controls(chip, monitor_controls[i].controls, + ARRAY_SIZE(monitor_controls[i].controls)); + if (err < 0) + return err; + } + if (chip->has_ac97_0) { + err = add_controls(chip, ac97_controls, + ARRAY_SIZE(ac97_controls)); + if (err < 0) + return err; + } + if (chip->has_ac97_1) { + err = add_controls(chip, ac97_fp_controls, + ARRAY_SIZE(ac97_fp_controls)); + if (err < 0) + return err; + } + return chip->model.mixer_init ? chip->model.mixer_init(chip) : 0; +} diff --git a/sound/pci/oxygen/oxygen_pcm.c b/sound/pci/oxygen/oxygen_pcm.c new file mode 100644 index 000000000..b71635601 --- /dev/null +++ b/sound/pci/oxygen/oxygen_pcm.c @@ -0,0 +1,772 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * C-Media CMI8788 driver - PCM code + * + * Copyright (c) Clemens Ladisch + */ + +#include +#include +#include +#include +#include +#include "oxygen.h" + +/* most DMA channels have a 16-bit counter for 32-bit words */ +#define BUFFER_BYTES_MAX ((1 << 16) * 4) +/* the multichannel DMA channel has a 24-bit counter */ +#define BUFFER_BYTES_MAX_MULTICH ((1 << 24) * 4) + +#define FIFO_BYTES 256 +#define FIFO_BYTES_MULTICH 1024 + +#define PERIOD_BYTES_MIN 64 + +#define DEFAULT_BUFFER_BYTES (BUFFER_BYTES_MAX / 2) +#define DEFAULT_BUFFER_BYTES_MULTICH (1024 * 1024) + +static const struct snd_pcm_hardware oxygen_stereo_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .rate_min = 32000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX, + .periods_min = 1, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = FIFO_BYTES, +}; +static const struct snd_pcm_hardware oxygen_multichannel_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .rate_min = 32000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = BUFFER_BYTES_MAX_MULTICH, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX_MULTICH, + .periods_min = 1, + .periods_max = BUFFER_BYTES_MAX_MULTICH / PERIOD_BYTES_MIN, + .fifo_size = FIFO_BYTES_MULTICH, +}; +static const struct snd_pcm_hardware oxygen_ac97_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX, + .periods_min = 1, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = FIFO_BYTES, +}; + +static const struct snd_pcm_hardware *const oxygen_hardware[PCM_COUNT] = { + [PCM_A] = &oxygen_stereo_hardware, + [PCM_B] = &oxygen_stereo_hardware, + [PCM_C] = &oxygen_stereo_hardware, + [PCM_SPDIF] = &oxygen_stereo_hardware, + [PCM_MULTICH] = &oxygen_multichannel_hardware, + [PCM_AC97] = &oxygen_ac97_hardware, +}; + +static inline unsigned int +oxygen_substream_channel(struct snd_pcm_substream *substream) +{ + return (unsigned int)(uintptr_t)substream->runtime->private_data; +} + +static int oxygen_open(struct snd_pcm_substream *substream, + unsigned int channel) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + runtime->private_data = (void *)(uintptr_t)channel; + if (channel == PCM_B && chip->has_ac97_1 && + (chip->model.device_config & CAPTURE_2_FROM_AC97_1)) + runtime->hw = oxygen_ac97_hardware; + else + runtime->hw = *oxygen_hardware[channel]; + switch (channel) { + case PCM_C: + if (chip->model.device_config & CAPTURE_1_FROM_SPDIF) { + runtime->hw.rates &= ~(SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_64000); + runtime->hw.rate_min = 44100; + } + fallthrough; + case PCM_A: + case PCM_B: + runtime->hw.fifo_size = 0; + break; + case PCM_MULTICH: + runtime->hw.channels_max = chip->model.dac_channels_pcm; + break; + } + if (chip->model.pcm_hardware_filter) + chip->model.pcm_hardware_filter(channel, &runtime->hw); + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (err < 0) + return err; + if (runtime->hw.formats & SNDRV_PCM_FMTBIT_S32_LE) { + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + } + if (runtime->hw.channels_max > 2) { + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + 2); + if (err < 0) + return err; + } + snd_pcm_set_sync(substream); + chip->streams[channel] = substream; + + guard(mutex)(&chip->mutex); + chip->pcm_active |= 1 << channel; + if (channel == PCM_SPDIF) { + chip->spdif_pcm_bits = chip->spdif_bits; + chip->controls[CONTROL_SPDIF_PCM]->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, + &chip->controls[CONTROL_SPDIF_PCM]->id); + } + + return 0; +} + +static int oxygen_rec_a_open(struct snd_pcm_substream *substream) +{ + return oxygen_open(substream, PCM_A); +} + +static int oxygen_rec_b_open(struct snd_pcm_substream *substream) +{ + return oxygen_open(substream, PCM_B); +} + +static int oxygen_rec_c_open(struct snd_pcm_substream *substream) +{ + return oxygen_open(substream, PCM_C); +} + +static int oxygen_spdif_open(struct snd_pcm_substream *substream) +{ + return oxygen_open(substream, PCM_SPDIF); +} + +static int oxygen_multich_open(struct snd_pcm_substream *substream) +{ + return oxygen_open(substream, PCM_MULTICH); +} + +static int oxygen_ac97_open(struct snd_pcm_substream *substream) +{ + return oxygen_open(substream, PCM_AC97); +} + +static int oxygen_close(struct snd_pcm_substream *substream) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + unsigned int channel = oxygen_substream_channel(substream); + + guard(mutex)(&chip->mutex); + chip->pcm_active &= ~(1 << channel); + if (channel == PCM_SPDIF) { + chip->controls[CONTROL_SPDIF_PCM]->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, + &chip->controls[CONTROL_SPDIF_PCM]->id); + } + if (channel == PCM_SPDIF || channel == PCM_MULTICH) + oxygen_update_spdif_source(chip); + + chip->streams[channel] = NULL; + return 0; +} + +static unsigned int oxygen_format(struct snd_pcm_hw_params *hw_params) +{ + if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE) + return OXYGEN_FORMAT_24; + else + return OXYGEN_FORMAT_16; +} + +static unsigned int oxygen_rate(struct snd_pcm_hw_params *hw_params) +{ + switch (params_rate(hw_params)) { + case 32000: + return OXYGEN_RATE_32000; + case 44100: + return OXYGEN_RATE_44100; + default: /* 48000 */ + return OXYGEN_RATE_48000; + case 64000: + return OXYGEN_RATE_64000; + case 88200: + return OXYGEN_RATE_88200; + case 96000: + return OXYGEN_RATE_96000; + case 176400: + return OXYGEN_RATE_176400; + case 192000: + return OXYGEN_RATE_192000; + } +} + +static unsigned int oxygen_i2s_bits(struct snd_pcm_hw_params *hw_params) +{ + if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE) + return OXYGEN_I2S_BITS_24; + else + return OXYGEN_I2S_BITS_16; +} + +static unsigned int oxygen_play_channels(struct snd_pcm_hw_params *hw_params) +{ + switch (params_channels(hw_params)) { + default: /* 2 */ + return OXYGEN_PLAY_CHANNELS_2; + case 4: + return OXYGEN_PLAY_CHANNELS_4; + case 6: + return OXYGEN_PLAY_CHANNELS_6; + case 8: + return OXYGEN_PLAY_CHANNELS_8; + } +} + +static const unsigned int channel_base_registers[PCM_COUNT] = { + [PCM_A] = OXYGEN_DMA_A_ADDRESS, + [PCM_B] = OXYGEN_DMA_B_ADDRESS, + [PCM_C] = OXYGEN_DMA_C_ADDRESS, + [PCM_SPDIF] = OXYGEN_DMA_SPDIF_ADDRESS, + [PCM_MULTICH] = OXYGEN_DMA_MULTICH_ADDRESS, + [PCM_AC97] = OXYGEN_DMA_AC97_ADDRESS, +}; + +static int oxygen_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + unsigned int channel = oxygen_substream_channel(substream); + + oxygen_write32(chip, channel_base_registers[channel], + (u32)substream->runtime->dma_addr); + if (channel == PCM_MULTICH) { + oxygen_write32(chip, OXYGEN_DMA_MULTICH_COUNT, + params_buffer_bytes(hw_params) / 4 - 1); + oxygen_write32(chip, OXYGEN_DMA_MULTICH_TCOUNT, + params_period_bytes(hw_params) / 4 - 1); + } else { + oxygen_write16(chip, channel_base_registers[channel] + 4, + params_buffer_bytes(hw_params) / 4 - 1); + oxygen_write16(chip, channel_base_registers[channel] + 6, + params_period_bytes(hw_params) / 4 - 1); + } + return 0; +} + +static u16 get_mclk(struct oxygen *chip, unsigned int channel, + struct snd_pcm_hw_params *params) +{ + unsigned int mclks, shift; + + if (channel == PCM_MULTICH) + mclks = chip->model.dac_mclks; + else + mclks = chip->model.adc_mclks; + + if (params_rate(params) <= 48000) + shift = 0; + else if (params_rate(params) <= 96000) + shift = 2; + else + shift = 4; + + return OXYGEN_I2S_MCLK(mclks >> shift); +} + +static int oxygen_rec_a_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + int err; + + err = oxygen_hw_params(substream, hw_params); + if (err < 0) + return err; + + scoped_guard(spinlock_irq, &chip->reg_lock) { + oxygen_write8_masked(chip, OXYGEN_REC_FORMAT, + oxygen_format(hw_params) << OXYGEN_REC_FORMAT_A_SHIFT, + OXYGEN_REC_FORMAT_A_MASK); + oxygen_write16_masked(chip, OXYGEN_I2S_A_FORMAT, + oxygen_rate(hw_params) | + chip->model.adc_i2s_format | + get_mclk(chip, PCM_A, hw_params) | + oxygen_i2s_bits(hw_params), + OXYGEN_I2S_RATE_MASK | + OXYGEN_I2S_FORMAT_MASK | + OXYGEN_I2S_MCLK_MASK | + OXYGEN_I2S_BITS_MASK); + } + + guard(mutex)(&chip->mutex); + chip->model.set_adc_params(chip, hw_params); + return 0; +} + +static int oxygen_rec_b_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + int is_ac97; + int err; + + err = oxygen_hw_params(substream, hw_params); + if (err < 0) + return err; + + is_ac97 = chip->has_ac97_1 && + (chip->model.device_config & CAPTURE_2_FROM_AC97_1); + + scoped_guard(spinlock_irq, &chip->reg_lock) { + oxygen_write8_masked(chip, OXYGEN_REC_FORMAT, + oxygen_format(hw_params) << OXYGEN_REC_FORMAT_B_SHIFT, + OXYGEN_REC_FORMAT_B_MASK); + if (!is_ac97) + oxygen_write16_masked(chip, OXYGEN_I2S_B_FORMAT, + oxygen_rate(hw_params) | + chip->model.adc_i2s_format | + get_mclk(chip, PCM_B, hw_params) | + oxygen_i2s_bits(hw_params), + OXYGEN_I2S_RATE_MASK | + OXYGEN_I2S_FORMAT_MASK | + OXYGEN_I2S_MCLK_MASK | + OXYGEN_I2S_BITS_MASK); + } + + if (!is_ac97) { + guard(mutex)(&chip->mutex); + chip->model.set_adc_params(chip, hw_params); + } + return 0; +} + +static int oxygen_rec_c_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + bool is_spdif; + int err; + + err = oxygen_hw_params(substream, hw_params); + if (err < 0) + return err; + + is_spdif = chip->model.device_config & CAPTURE_1_FROM_SPDIF; + + scoped_guard(spinlock_irq, &chip->reg_lock) { + oxygen_write8_masked(chip, OXYGEN_REC_FORMAT, + oxygen_format(hw_params) << OXYGEN_REC_FORMAT_C_SHIFT, + OXYGEN_REC_FORMAT_C_MASK); + if (!is_spdif) + oxygen_write16_masked(chip, OXYGEN_I2S_C_FORMAT, + oxygen_rate(hw_params) | + chip->model.adc_i2s_format | + get_mclk(chip, PCM_B, hw_params) | + oxygen_i2s_bits(hw_params), + OXYGEN_I2S_RATE_MASK | + OXYGEN_I2S_FORMAT_MASK | + OXYGEN_I2S_MCLK_MASK | + OXYGEN_I2S_BITS_MASK); + } + + if (!is_spdif) { + guard(mutex)(&chip->mutex); + chip->model.set_adc_params(chip, hw_params); + } + return 0; +} + +static int oxygen_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + int err; + + err = oxygen_hw_params(substream, hw_params); + if (err < 0) + return err; + + guard(mutex)(&chip->mutex); + guard(spinlock_irq)(&chip->reg_lock); + oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, + OXYGEN_SPDIF_OUT_ENABLE); + oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT, + oxygen_format(hw_params) << OXYGEN_SPDIF_FORMAT_SHIFT, + OXYGEN_SPDIF_FORMAT_MASK); + oxygen_write32_masked(chip, OXYGEN_SPDIF_CONTROL, + oxygen_rate(hw_params) << OXYGEN_SPDIF_OUT_RATE_SHIFT, + OXYGEN_SPDIF_OUT_RATE_MASK); + oxygen_update_spdif_source(chip); + return 0; +} + +static int oxygen_multich_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + int err; + + err = oxygen_hw_params(substream, hw_params); + if (err < 0) + return err; + + guard(mutex)(&chip->mutex); + scoped_guard(spinlock_irq, &chip->reg_lock) { + oxygen_write8_masked(chip, OXYGEN_PLAY_CHANNELS, + oxygen_play_channels(hw_params), + OXYGEN_PLAY_CHANNELS_MASK); + oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT, + oxygen_format(hw_params) << OXYGEN_MULTICH_FORMAT_SHIFT, + OXYGEN_MULTICH_FORMAT_MASK); + oxygen_write16_masked(chip, OXYGEN_I2S_MULTICH_FORMAT, + oxygen_rate(hw_params) | + chip->model.dac_i2s_format | + get_mclk(chip, PCM_MULTICH, hw_params) | + oxygen_i2s_bits(hw_params), + OXYGEN_I2S_RATE_MASK | + OXYGEN_I2S_FORMAT_MASK | + OXYGEN_I2S_MCLK_MASK | + OXYGEN_I2S_BITS_MASK); + oxygen_update_spdif_source(chip); + } + + chip->model.set_dac_params(chip, hw_params); + oxygen_update_dac_routing(chip); + return 0; +} + +static int oxygen_hw_free(struct snd_pcm_substream *substream) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + unsigned int channel = oxygen_substream_channel(substream); + unsigned int channel_mask = 1 << channel; + + guard(spinlock_irq)(&chip->reg_lock); + chip->interrupt_mask &= ~channel_mask; + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask); + + oxygen_set_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask); + oxygen_clear_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask); + + return 0; +} + +static int oxygen_spdif_hw_free(struct snd_pcm_substream *substream) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + + scoped_guard(spinlock_irq, &chip->reg_lock) { + oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, + OXYGEN_SPDIF_OUT_ENABLE); + } + return oxygen_hw_free(substream); +} + +static int oxygen_prepare(struct snd_pcm_substream *substream) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + unsigned int channel = oxygen_substream_channel(substream); + unsigned int channel_mask = 1 << channel; + + guard(spinlock_irq)(&chip->reg_lock); + oxygen_set_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask); + oxygen_clear_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask); + + if (substream->runtime->no_period_wakeup) + chip->interrupt_mask &= ~channel_mask; + else + chip->interrupt_mask |= channel_mask; + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask); + return 0; +} + +static int oxygen_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + unsigned int mask = 0; + int pausing; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_SUSPEND: + pausing = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pausing = 1; + break; + default: + return -EINVAL; + } + + snd_pcm_group_for_each_entry(s, substream) { + if (snd_pcm_substream_chip(s) == chip) { + mask |= 1 << oxygen_substream_channel(s); + snd_pcm_trigger_done(s, substream); + } + } + + guard(spinlock)(&chip->reg_lock); + if (!pausing) { + if (cmd == SNDRV_PCM_TRIGGER_START) + chip->pcm_running |= mask; + else + chip->pcm_running &= ~mask; + oxygen_write8(chip, OXYGEN_DMA_STATUS, chip->pcm_running); + } else { + if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) + oxygen_set_bits8(chip, OXYGEN_DMA_PAUSE, mask); + else + oxygen_clear_bits8(chip, OXYGEN_DMA_PAUSE, mask); + } + return 0; +} + +static snd_pcm_uframes_t oxygen_pointer(struct snd_pcm_substream *substream) +{ + struct oxygen *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int channel = oxygen_substream_channel(substream); + u32 curr_addr; + + /* no spinlock, this read should be atomic */ + curr_addr = oxygen_read32(chip, channel_base_registers[channel]); + return bytes_to_frames(runtime, curr_addr - (u32)runtime->dma_addr); +} + +static const struct snd_pcm_ops oxygen_rec_a_ops = { + .open = oxygen_rec_a_open, + .close = oxygen_close, + .hw_params = oxygen_rec_a_hw_params, + .hw_free = oxygen_hw_free, + .prepare = oxygen_prepare, + .trigger = oxygen_trigger, + .pointer = oxygen_pointer, +}; + +static const struct snd_pcm_ops oxygen_rec_b_ops = { + .open = oxygen_rec_b_open, + .close = oxygen_close, + .hw_params = oxygen_rec_b_hw_params, + .hw_free = oxygen_hw_free, + .prepare = oxygen_prepare, + .trigger = oxygen_trigger, + .pointer = oxygen_pointer, +}; + +static const struct snd_pcm_ops oxygen_rec_c_ops = { + .open = oxygen_rec_c_open, + .close = oxygen_close, + .hw_params = oxygen_rec_c_hw_params, + .hw_free = oxygen_hw_free, + .prepare = oxygen_prepare, + .trigger = oxygen_trigger, + .pointer = oxygen_pointer, +}; + +static const struct snd_pcm_ops oxygen_spdif_ops = { + .open = oxygen_spdif_open, + .close = oxygen_close, + .hw_params = oxygen_spdif_hw_params, + .hw_free = oxygen_spdif_hw_free, + .prepare = oxygen_prepare, + .trigger = oxygen_trigger, + .pointer = oxygen_pointer, +}; + +static const struct snd_pcm_ops oxygen_multich_ops = { + .open = oxygen_multich_open, + .close = oxygen_close, + .hw_params = oxygen_multich_hw_params, + .hw_free = oxygen_hw_free, + .prepare = oxygen_prepare, + .trigger = oxygen_trigger, + .pointer = oxygen_pointer, +}; + +static const struct snd_pcm_ops oxygen_ac97_ops = { + .open = oxygen_ac97_open, + .close = oxygen_close, + .hw_params = oxygen_hw_params, + .hw_free = oxygen_hw_free, + .prepare = oxygen_prepare, + .trigger = oxygen_trigger, + .pointer = oxygen_pointer, +}; + +int oxygen_pcm_init(struct oxygen *chip) +{ + struct snd_pcm *pcm; + int outs, ins; + int err; + + outs = !!(chip->model.device_config & PLAYBACK_0_TO_I2S); + ins = !!(chip->model.device_config & (CAPTURE_0_FROM_I2S_1 | + CAPTURE_0_FROM_I2S_2)); + if (outs | ins) { + err = snd_pcm_new(chip->card, "Multichannel", + 0, outs, ins, &pcm); + if (err < 0) + return err; + if (outs) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &oxygen_multich_ops); + if (chip->model.device_config & CAPTURE_0_FROM_I2S_1) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &oxygen_rec_a_ops); + else if (chip->model.device_config & CAPTURE_0_FROM_I2S_2) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &oxygen_rec_b_ops); + pcm->private_data = chip; + strscpy(pcm->name, "Multichannel"); + if (outs) + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + DEFAULT_BUFFER_BYTES_MULTICH, + BUFFER_BYTES_MAX_MULTICH); + if (ins) + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + DEFAULT_BUFFER_BYTES, + BUFFER_BYTES_MAX); + } + + outs = !!(chip->model.device_config & PLAYBACK_1_TO_SPDIF); + ins = !!(chip->model.device_config & CAPTURE_1_FROM_SPDIF); + if (outs | ins) { + err = snd_pcm_new(chip->card, "Digital", 1, outs, ins, &pcm); + if (err < 0) + return err; + if (outs) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &oxygen_spdif_ops); + if (ins) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &oxygen_rec_c_ops); + pcm->private_data = chip; + strscpy(pcm->name, "Digital"); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + DEFAULT_BUFFER_BYTES, + BUFFER_BYTES_MAX); + } + + if (chip->has_ac97_1) { + outs = !!(chip->model.device_config & PLAYBACK_2_TO_AC97_1); + ins = !!(chip->model.device_config & CAPTURE_2_FROM_AC97_1); + } else { + outs = 0; + ins = !!(chip->model.device_config & CAPTURE_2_FROM_I2S_2); + } + if (outs | ins) { + err = snd_pcm_new(chip->card, outs ? "AC97" : "Analog2", + 2, outs, ins, &pcm); + if (err < 0) + return err; + if (outs) { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &oxygen_ac97_ops); + oxygen_write8_masked(chip, OXYGEN_REC_ROUTING, + OXYGEN_REC_B_ROUTE_AC97_1, + OXYGEN_REC_B_ROUTE_MASK); + } + if (ins) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &oxygen_rec_b_ops); + pcm->private_data = chip; + strscpy(pcm->name, outs ? "Front Panel" : "Analog 2"); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + DEFAULT_BUFFER_BYTES, + BUFFER_BYTES_MAX); + } + + ins = !!(chip->model.device_config & CAPTURE_3_FROM_I2S_3); + if (ins) { + err = snd_pcm_new(chip->card, "Analog3", 3, 0, ins, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &oxygen_rec_c_ops); + oxygen_write8_masked(chip, OXYGEN_REC_ROUTING, + OXYGEN_REC_C_ROUTE_I2S_ADC_3, + OXYGEN_REC_C_ROUTE_MASK); + pcm->private_data = chip; + strscpy(pcm->name, "Analog 3"); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, + DEFAULT_BUFFER_BYTES, + BUFFER_BYTES_MAX); + } + return 0; +} diff --git a/sound/pci/oxygen/oxygen_regs.h b/sound/pci/oxygen/oxygen_regs.h new file mode 100644 index 000000000..eca9d943f --- /dev/null +++ b/sound/pci/oxygen/oxygen_regs.h @@ -0,0 +1,459 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef OXYGEN_REGS_H_INCLUDED +#define OXYGEN_REGS_H_INCLUDED + +/* recording channel A */ +#define OXYGEN_DMA_A_ADDRESS 0x00 /* 32-bit base address */ +#define OXYGEN_DMA_A_COUNT 0x04 /* buffer counter (dwords) */ +#define OXYGEN_DMA_A_TCOUNT 0x06 /* interrupt counter (dwords) */ + +/* recording channel B */ +#define OXYGEN_DMA_B_ADDRESS 0x08 +#define OXYGEN_DMA_B_COUNT 0x0c +#define OXYGEN_DMA_B_TCOUNT 0x0e + +/* recording channel C */ +#define OXYGEN_DMA_C_ADDRESS 0x10 +#define OXYGEN_DMA_C_COUNT 0x14 +#define OXYGEN_DMA_C_TCOUNT 0x16 + +/* SPDIF playback channel */ +#define OXYGEN_DMA_SPDIF_ADDRESS 0x18 +#define OXYGEN_DMA_SPDIF_COUNT 0x1c +#define OXYGEN_DMA_SPDIF_TCOUNT 0x1e + +/* multichannel playback channel */ +#define OXYGEN_DMA_MULTICH_ADDRESS 0x20 +#define OXYGEN_DMA_MULTICH_COUNT 0x24 /* 24 bits */ +#define OXYGEN_DMA_MULTICH_TCOUNT 0x28 /* 24 bits */ + +/* AC'97 (front panel) playback channel */ +#define OXYGEN_DMA_AC97_ADDRESS 0x30 +#define OXYGEN_DMA_AC97_COUNT 0x34 +#define OXYGEN_DMA_AC97_TCOUNT 0x36 + +/* all registers 0x00..0x36 return current position on read */ + +#define OXYGEN_DMA_STATUS 0x40 /* 1 = running, 0 = stop */ +#define OXYGEN_CHANNEL_A 0x01 +#define OXYGEN_CHANNEL_B 0x02 +#define OXYGEN_CHANNEL_C 0x04 +#define OXYGEN_CHANNEL_SPDIF 0x08 +#define OXYGEN_CHANNEL_MULTICH 0x10 +#define OXYGEN_CHANNEL_AC97 0x20 + +#define OXYGEN_DMA_PAUSE 0x41 /* 1 = pause */ +/* OXYGEN_CHANNEL_* */ + +#define OXYGEN_DMA_RESET 0x42 +/* OXYGEN_CHANNEL_* */ + +#define OXYGEN_PLAY_CHANNELS 0x43 +#define OXYGEN_PLAY_CHANNELS_MASK 0x03 +#define OXYGEN_PLAY_CHANNELS_2 0x00 +#define OXYGEN_PLAY_CHANNELS_4 0x01 +#define OXYGEN_PLAY_CHANNELS_6 0x02 +#define OXYGEN_PLAY_CHANNELS_8 0x03 +#define OXYGEN_DMA_A_BURST_MASK 0x04 +#define OXYGEN_DMA_A_BURST_8 0x00 /* dwords */ +#define OXYGEN_DMA_A_BURST_16 0x04 +#define OXYGEN_DMA_MULTICH_BURST_MASK 0x08 +#define OXYGEN_DMA_MULTICH_BURST_8 0x00 +#define OXYGEN_DMA_MULTICH_BURST_16 0x08 + +#define OXYGEN_INTERRUPT_MASK 0x44 +/* OXYGEN_CHANNEL_* */ +#define OXYGEN_INT_SPDIF_IN_DETECT 0x0100 +#define OXYGEN_INT_MCU 0x0200 +#define OXYGEN_INT_2WIRE 0x0400 +#define OXYGEN_INT_GPIO 0x0800 +#define OXYGEN_INT_MCB 0x2000 +#define OXYGEN_INT_AC97 0x4000 + +#define OXYGEN_INTERRUPT_STATUS 0x46 +/* OXYGEN_CHANNEL_* amd OXYGEN_INT_* */ +#define OXYGEN_INT_MIDI 0x1000 + +#define OXYGEN_MISC 0x48 +#define OXYGEN_MISC_WRITE_PCI_SUBID 0x01 +#define OXYGEN_MISC_LATENCY_3F 0x02 +#define OXYGEN_MISC_REC_C_FROM_SPDIF 0x04 +#define OXYGEN_MISC_REC_B_FROM_AC97 0x08 +#define OXYGEN_MISC_REC_A_FROM_MULTICH 0x10 +#define OXYGEN_MISC_PCI_MEM_W_1_CLOCK 0x20 +#define OXYGEN_MISC_MIDI 0x40 +#define OXYGEN_MISC_CRYSTAL_MASK 0x80 +#define OXYGEN_MISC_CRYSTAL_24576 0x00 +#define OXYGEN_MISC_CRYSTAL_27 0x80 /* MHz */ + +#define OXYGEN_REC_FORMAT 0x4a +#define OXYGEN_REC_FORMAT_A_MASK 0x03 +#define OXYGEN_REC_FORMAT_A_SHIFT 0 +#define OXYGEN_REC_FORMAT_B_MASK 0x0c +#define OXYGEN_REC_FORMAT_B_SHIFT 2 +#define OXYGEN_REC_FORMAT_C_MASK 0x30 +#define OXYGEN_REC_FORMAT_C_SHIFT 4 +#define OXYGEN_FORMAT_16 0x00 +#define OXYGEN_FORMAT_24 0x01 +#define OXYGEN_FORMAT_32 0x02 + +#define OXYGEN_PLAY_FORMAT 0x4b +#define OXYGEN_SPDIF_FORMAT_MASK 0x03 +#define OXYGEN_SPDIF_FORMAT_SHIFT 0 +#define OXYGEN_MULTICH_FORMAT_MASK 0x0c +#define OXYGEN_MULTICH_FORMAT_SHIFT 2 +/* OXYGEN_FORMAT_* */ + +#define OXYGEN_REC_CHANNELS 0x4c +#define OXYGEN_REC_CHANNELS_MASK 0x07 +#define OXYGEN_REC_CHANNELS_2_2_2 0x00 /* DMA A, B, C */ +#define OXYGEN_REC_CHANNELS_4_2_2 0x01 +#define OXYGEN_REC_CHANNELS_6_0_2 0x02 +#define OXYGEN_REC_CHANNELS_6_2_0 0x03 +#define OXYGEN_REC_CHANNELS_8_0_0 0x04 + +#define OXYGEN_FUNCTION 0x50 +#define OXYGEN_FUNCTION_CLOCK_MASK 0x01 +#define OXYGEN_FUNCTION_CLOCK_PLL 0x00 +#define OXYGEN_FUNCTION_CLOCK_CRYSTAL 0x01 +#define OXYGEN_FUNCTION_RESET_CODEC 0x02 +#define OXYGEN_FUNCTION_RESET_POL 0x04 +#define OXYGEN_FUNCTION_PWDN 0x08 +#define OXYGEN_FUNCTION_PWDN_EN 0x10 +#define OXYGEN_FUNCTION_PWDN_POL 0x20 +#define OXYGEN_FUNCTION_2WIRE_SPI_MASK 0x40 +#define OXYGEN_FUNCTION_SPI 0x00 +#define OXYGEN_FUNCTION_2WIRE 0x40 +#define OXYGEN_FUNCTION_ENABLE_SPI_4_5 0x80 /* 0 = EEPROM */ + +#define OXYGEN_I2S_MULTICH_FORMAT 0x60 +#define OXYGEN_I2S_RATE_MASK 0x0007 /* LRCK */ +#define OXYGEN_RATE_32000 0x0000 +#define OXYGEN_RATE_44100 0x0001 +#define OXYGEN_RATE_48000 0x0002 +#define OXYGEN_RATE_64000 0x0003 +#define OXYGEN_RATE_88200 0x0004 +#define OXYGEN_RATE_96000 0x0005 +#define OXYGEN_RATE_176400 0x0006 +#define OXYGEN_RATE_192000 0x0007 +#define OXYGEN_I2S_FORMAT_MASK 0x0008 +#define OXYGEN_I2S_FORMAT_I2S 0x0000 +#define OXYGEN_I2S_FORMAT_LJUST 0x0008 +#define OXYGEN_I2S_MCLK_MASK 0x0030 /* MCLK/LRCK */ +#define OXYGEN_I2S_MCLK_SHIFT 4 +#define MCLK_128 0 +#define MCLK_256 1 +#define MCLK_512 2 +#define OXYGEN_I2S_MCLK(f) (((f) & 3) << OXYGEN_I2S_MCLK_SHIFT) +#define OXYGEN_I2S_BITS_MASK 0x00c0 +#define OXYGEN_I2S_BITS_16 0x0000 +#define OXYGEN_I2S_BITS_20 0x0040 +#define OXYGEN_I2S_BITS_24 0x0080 +#define OXYGEN_I2S_BITS_32 0x00c0 +#define OXYGEN_I2S_MASTER 0x0100 +#define OXYGEN_I2S_BCLK_MASK 0x0600 /* BCLK/LRCK */ +#define OXYGEN_I2S_BCLK_64 0x0000 +#define OXYGEN_I2S_BCLK_128 0x0200 +#define OXYGEN_I2S_BCLK_256 0x0400 +#define OXYGEN_I2S_MUTE_MCLK 0x0800 + +#define OXYGEN_I2S_A_FORMAT 0x62 +#define OXYGEN_I2S_B_FORMAT 0x64 +#define OXYGEN_I2S_C_FORMAT 0x66 +/* like OXYGEN_I2S_MULTICH_FORMAT */ + +#define OXYGEN_SPDIF_CONTROL 0x70 +#define OXYGEN_SPDIF_OUT_ENABLE 0x00000002 +#define OXYGEN_SPDIF_LOOPBACK 0x00000004 /* in to out */ +#define OXYGEN_SPDIF_SENSE_MASK 0x00000008 +#define OXYGEN_SPDIF_LOCK_MASK 0x00000010 +#define OXYGEN_SPDIF_RATE_MASK 0x00000020 +#define OXYGEN_SPDIF_SPDVALID 0x00000040 +#define OXYGEN_SPDIF_SENSE_PAR 0x00000200 +#define OXYGEN_SPDIF_LOCK_PAR 0x00000400 +#define OXYGEN_SPDIF_SENSE_STATUS 0x00000800 +#define OXYGEN_SPDIF_LOCK_STATUS 0x00001000 +#define OXYGEN_SPDIF_SENSE_INT 0x00002000 /* r/wc */ +#define OXYGEN_SPDIF_LOCK_INT 0x00004000 /* r/wc */ +#define OXYGEN_SPDIF_RATE_INT 0x00008000 /* r/wc */ +#define OXYGEN_SPDIF_IN_CLOCK_MASK 0x00010000 +#define OXYGEN_SPDIF_IN_CLOCK_96 0x00000000 /* <= 96 kHz */ +#define OXYGEN_SPDIF_IN_CLOCK_192 0x00010000 /* > 96 kHz */ +#define OXYGEN_SPDIF_OUT_RATE_MASK 0x07000000 +#define OXYGEN_SPDIF_OUT_RATE_SHIFT 24 +/* OXYGEN_RATE_* << OXYGEN_SPDIF_OUT_RATE_SHIFT */ + +#define OXYGEN_SPDIF_OUTPUT_BITS 0x74 +#define OXYGEN_SPDIF_NONAUDIO 0x00000002 +#define OXYGEN_SPDIF_C 0x00000004 +#define OXYGEN_SPDIF_PREEMPHASIS 0x00000008 +#define OXYGEN_SPDIF_CATEGORY_MASK 0x000007f0 +#define OXYGEN_SPDIF_CATEGORY_SHIFT 4 +#define OXYGEN_SPDIF_ORIGINAL 0x00000800 +#define OXYGEN_SPDIF_CS_RATE_MASK 0x0000f000 +#define OXYGEN_SPDIF_CS_RATE_SHIFT 12 +#define OXYGEN_SPDIF_V 0x00010000 /* 0 = valid */ + +#define OXYGEN_SPDIF_INPUT_BITS 0x78 +/* 32 bits, IEC958_AES_* */ + +#define OXYGEN_EEPROM_CONTROL 0x80 +#define OXYGEN_EEPROM_ADDRESS_MASK 0x7f +#define OXYGEN_EEPROM_DIR_MASK 0x80 +#define OXYGEN_EEPROM_DIR_READ 0x00 +#define OXYGEN_EEPROM_DIR_WRITE 0x80 + +#define OXYGEN_EEPROM_STATUS 0x81 +#define OXYGEN_EEPROM_VALID 0x40 +#define OXYGEN_EEPROM_BUSY 0x80 + +#define OXYGEN_EEPROM_DATA 0x82 /* 16 bits */ + +#define OXYGEN_2WIRE_CONTROL 0x90 +#define OXYGEN_2WIRE_DIR_MASK 0x01 +#define OXYGEN_2WIRE_DIR_WRITE 0x00 +#define OXYGEN_2WIRE_DIR_READ 0x01 +#define OXYGEN_2WIRE_ADDRESS_MASK 0xfe /* slave device address */ +#define OXYGEN_2WIRE_ADDRESS_SHIFT 1 + +#define OXYGEN_2WIRE_MAP 0x91 /* address, 8 bits */ +#define OXYGEN_2WIRE_DATA 0x92 /* data, 16 bits */ + +#define OXYGEN_2WIRE_BUS_STATUS 0x94 +#define OXYGEN_2WIRE_BUSY 0x0001 +#define OXYGEN_2WIRE_LENGTH_MASK 0x0002 +#define OXYGEN_2WIRE_LENGTH_8 0x0000 +#define OXYGEN_2WIRE_LENGTH_16 0x0002 +#define OXYGEN_2WIRE_MANUAL_READ 0x0004 /* 0 = auto read */ +#define OXYGEN_2WIRE_WRITE_MAP_ONLY 0x0008 +#define OXYGEN_2WIRE_SLAVE_AD_MASK 0x0030 /* AD0, AD1 */ +#define OXYGEN_2WIRE_INTERRUPT_MASK 0x0040 /* 0 = int. if not responding */ +#define OXYGEN_2WIRE_SLAVE_NO_RESPONSE 0x0080 +#define OXYGEN_2WIRE_SPEED_MASK 0x0100 +#define OXYGEN_2WIRE_SPEED_STANDARD 0x0000 +#define OXYGEN_2WIRE_SPEED_FAST 0x0100 +#define OXYGEN_2WIRE_CLOCK_SYNC 0x0200 +#define OXYGEN_2WIRE_BUS_RESET 0x0400 + +#define OXYGEN_SPI_CONTROL 0x98 +#define OXYGEN_SPI_BUSY 0x01 /* read */ +#define OXYGEN_SPI_TRIGGER 0x01 /* write */ +#define OXYGEN_SPI_DATA_LENGTH_MASK 0x02 +#define OXYGEN_SPI_DATA_LENGTH_2 0x00 +#define OXYGEN_SPI_DATA_LENGTH_3 0x02 +#define OXYGEN_SPI_CLOCK_MASK 0x0c +#define OXYGEN_SPI_CLOCK_160 0x00 /* ns */ +#define OXYGEN_SPI_CLOCK_320 0x04 +#define OXYGEN_SPI_CLOCK_640 0x08 +#define OXYGEN_SPI_CLOCK_1280 0x0c +#define OXYGEN_SPI_CODEC_MASK 0x70 /* 0..5 */ +#define OXYGEN_SPI_CODEC_SHIFT 4 +#define OXYGEN_SPI_CEN_MASK 0x80 +#define OXYGEN_SPI_CEN_LATCH_CLOCK_LO 0x00 +#define OXYGEN_SPI_CEN_LATCH_CLOCK_HI 0x80 + +#define OXYGEN_SPI_DATA1 0x99 +#define OXYGEN_SPI_DATA2 0x9a +#define OXYGEN_SPI_DATA3 0x9b + +#define OXYGEN_MPU401 0xa0 + +#define OXYGEN_MPU401_CONTROL 0xa2 +#define OXYGEN_MPU401_LOOPBACK 0x01 /* TXD to RXD */ + +#define OXYGEN_GPI_DATA 0xa4 +/* bits 0..5 = pin XGPI0..XGPI5 */ + +#define OXYGEN_GPI_INTERRUPT_MASK 0xa5 +/* bits 0..5, 1 = enable */ + +#define OXYGEN_GPIO_DATA 0xa6 +/* bits 0..9 */ + +#define OXYGEN_GPIO_CONTROL 0xa8 +/* bits 0..9, 0 = input, 1 = output */ +#define OXYGEN_GPIO1_XSLAVE_RDY 0x8000 + +#define OXYGEN_GPIO_INTERRUPT_MASK 0xaa +/* bits 0..9, 1 = enable */ + +#define OXYGEN_DEVICE_SENSE 0xac +#define OXYGEN_HEAD_PHONE_DETECT 0x01 +#define OXYGEN_HEAD_PHONE_MASK 0x06 +#define OXYGEN_HEAD_PHONE_PASSIVE_SPK 0x00 +#define OXYGEN_HEAD_PHONE_HP 0x02 +#define OXYGEN_HEAD_PHONE_ACTIVE_SPK 0x04 + +#define OXYGEN_MCU_2WIRE_DATA 0xb0 + +#define OXYGEN_MCU_2WIRE_MAP 0xb2 + +#define OXYGEN_MCU_2WIRE_STATUS 0xb3 +#define OXYGEN_MCU_2WIRE_BUSY 0x01 +#define OXYGEN_MCU_2WIRE_LENGTH_MASK 0x06 +#define OXYGEN_MCU_2WIRE_LENGTH_1 0x00 +#define OXYGEN_MCU_2WIRE_LENGTH_2 0x02 +#define OXYGEN_MCU_2WIRE_LENGTH_3 0x04 +#define OXYGEN_MCU_2WIRE_WRITE 0x08 /* r/wc */ +#define OXYGEN_MCU_2WIRE_READ 0x10 /* r/wc */ +#define OXYGEN_MCU_2WIRE_DRV_XACT_FAIL 0x20 /* r/wc */ +#define OXYGEN_MCU_2WIRE_RESET 0x40 + +#define OXYGEN_MCU_2WIRE_CONTROL 0xb4 +#define OXYGEN_MCU_2WIRE_DRV_ACK 0x01 +#define OXYGEN_MCU_2WIRE_DRV_XACT 0x02 +#define OXYGEN_MCU_2WIRE_INT_MASK 0x04 +#define OXYGEN_MCU_2WIRE_SYNC_MASK 0x08 +#define OXYGEN_MCU_2WIRE_SYNC_RDY_PIN 0x00 +#define OXYGEN_MCU_2WIRE_SYNC_DATA 0x08 +#define OXYGEN_MCU_2WIRE_ADDRESS_MASK 0x30 +#define OXYGEN_MCU_2WIRE_ADDRESS_10 0x00 +#define OXYGEN_MCU_2WIRE_ADDRESS_12 0x10 +#define OXYGEN_MCU_2WIRE_ADDRESS_14 0x20 +#define OXYGEN_MCU_2WIRE_ADDRESS_16 0x30 +#define OXYGEN_MCU_2WIRE_INT_POL 0x40 +#define OXYGEN_MCU_2WIRE_SYNC_ENABLE 0x80 + +#define OXYGEN_PLAY_ROUTING 0xc0 +#define OXYGEN_PLAY_MUTE01 0x0001 +#define OXYGEN_PLAY_MUTE23 0x0002 +#define OXYGEN_PLAY_MUTE45 0x0004 +#define OXYGEN_PLAY_MUTE67 0x0008 +#define OXYGEN_PLAY_MUTE_MASK 0x000f +#define OXYGEN_PLAY_MULTICH_MASK 0x0010 +#define OXYGEN_PLAY_MULTICH_I2S_DAC 0x0000 +#define OXYGEN_PLAY_MULTICH_AC97 0x0010 +#define OXYGEN_PLAY_SPDIF_MASK 0x00e0 +#define OXYGEN_PLAY_SPDIF_SPDIF 0x0000 +#define OXYGEN_PLAY_SPDIF_MULTICH_01 0x0020 +#define OXYGEN_PLAY_SPDIF_MULTICH_23 0x0040 +#define OXYGEN_PLAY_SPDIF_MULTICH_45 0x0060 +#define OXYGEN_PLAY_SPDIF_MULTICH_67 0x0080 +#define OXYGEN_PLAY_SPDIF_REC_A 0x00a0 +#define OXYGEN_PLAY_SPDIF_REC_B 0x00c0 +#define OXYGEN_PLAY_SPDIF_I2S_ADC_3 0x00e0 +#define OXYGEN_PLAY_DAC0_SOURCE_MASK 0x0300 +#define OXYGEN_PLAY_DAC0_SOURCE_SHIFT 8 +#define OXYGEN_PLAY_DAC1_SOURCE_MASK 0x0c00 +#define OXYGEN_PLAY_DAC1_SOURCE_SHIFT 10 +#define OXYGEN_PLAY_DAC2_SOURCE_MASK 0x3000 +#define OXYGEN_PLAY_DAC2_SOURCE_SHIFT 12 +#define OXYGEN_PLAY_DAC3_SOURCE_MASK 0xc000 +#define OXYGEN_PLAY_DAC3_SOURCE_SHIFT 14 + +#define OXYGEN_REC_ROUTING 0xc2 +#define OXYGEN_MUTE_I2S_ADC_1 0x01 +#define OXYGEN_MUTE_I2S_ADC_2 0x02 +#define OXYGEN_MUTE_I2S_ADC_3 0x04 +#define OXYGEN_REC_A_ROUTE_MASK 0x08 +#define OXYGEN_REC_A_ROUTE_I2S_ADC_1 0x00 +#define OXYGEN_REC_A_ROUTE_AC97_0 0x08 +#define OXYGEN_REC_B_ROUTE_MASK 0x10 +#define OXYGEN_REC_B_ROUTE_I2S_ADC_2 0x00 +#define OXYGEN_REC_B_ROUTE_AC97_1 0x10 +#define OXYGEN_REC_C_ROUTE_MASK 0x20 +#define OXYGEN_REC_C_ROUTE_SPDIF 0x00 +#define OXYGEN_REC_C_ROUTE_I2S_ADC_3 0x20 + +#define OXYGEN_ADC_MONITOR 0xc3 +#define OXYGEN_ADC_MONITOR_A 0x01 +#define OXYGEN_ADC_MONITOR_A_HALF_VOL 0x02 +#define OXYGEN_ADC_MONITOR_B 0x04 +#define OXYGEN_ADC_MONITOR_B_HALF_VOL 0x08 +#define OXYGEN_ADC_MONITOR_C 0x10 +#define OXYGEN_ADC_MONITOR_C_HALF_VOL 0x20 + +#define OXYGEN_A_MONITOR_ROUTING 0xc4 +#define OXYGEN_A_MONITOR_ROUTE_0_MASK 0x03 +#define OXYGEN_A_MONITOR_ROUTE_0_SHIFT 0 +#define OXYGEN_A_MONITOR_ROUTE_1_MASK 0x0c +#define OXYGEN_A_MONITOR_ROUTE_1_SHIFT 2 +#define OXYGEN_A_MONITOR_ROUTE_2_MASK 0x30 +#define OXYGEN_A_MONITOR_ROUTE_2_SHIFT 4 +#define OXYGEN_A_MONITOR_ROUTE_3_MASK 0xc0 +#define OXYGEN_A_MONITOR_ROUTE_3_SHIFT 6 + +#define OXYGEN_AC97_CONTROL 0xd0 +#define OXYGEN_AC97_COLD_RESET 0x0001 +#define OXYGEN_AC97_SUSPENDED 0x0002 /* read */ +#define OXYGEN_AC97_RESUME 0x0002 /* write */ +#define OXYGEN_AC97_CLOCK_DISABLE 0x0004 +#define OXYGEN_AC97_NO_CODEC_0 0x0008 +#define OXYGEN_AC97_CODEC_0 0x0010 +#define OXYGEN_AC97_CODEC_1 0x0020 + +#define OXYGEN_AC97_INTERRUPT_MASK 0xd2 +#define OXYGEN_AC97_INT_READ_DONE 0x01 +#define OXYGEN_AC97_INT_WRITE_DONE 0x02 +#define OXYGEN_AC97_INT_CODEC_0 0x10 +#define OXYGEN_AC97_INT_CODEC_1 0x20 + +#define OXYGEN_AC97_INTERRUPT_STATUS 0xd3 +/* OXYGEN_AC97_INT_* */ + +#define OXYGEN_AC97_OUT_CONFIG 0xd4 +#define OXYGEN_AC97_CODEC1_SLOT3 0x00000001 +#define OXYGEN_AC97_CODEC1_SLOT3_VSR 0x00000002 +#define OXYGEN_AC97_CODEC1_SLOT4 0x00000010 +#define OXYGEN_AC97_CODEC1_SLOT4_VSR 0x00000020 +#define OXYGEN_AC97_CODEC0_FRONTL 0x00000100 +#define OXYGEN_AC97_CODEC0_FRONTR 0x00000200 +#define OXYGEN_AC97_CODEC0_SIDEL 0x00000400 +#define OXYGEN_AC97_CODEC0_SIDER 0x00000800 +#define OXYGEN_AC97_CODEC0_CENTER 0x00001000 +#define OXYGEN_AC97_CODEC0_BASE 0x00002000 +#define OXYGEN_AC97_CODEC0_REARL 0x00004000 +#define OXYGEN_AC97_CODEC0_REARR 0x00008000 + +#define OXYGEN_AC97_IN_CONFIG 0xd8 +#define OXYGEN_AC97_CODEC1_LINEL 0x00000001 +#define OXYGEN_AC97_CODEC1_LINEL_VSR 0x00000002 +#define OXYGEN_AC97_CODEC1_LINEL_16 0x00000000 +#define OXYGEN_AC97_CODEC1_LINEL_18 0x00000004 +#define OXYGEN_AC97_CODEC1_LINEL_20 0x00000008 +#define OXYGEN_AC97_CODEC1_LINER 0x00000010 +#define OXYGEN_AC97_CODEC1_LINER_VSR 0x00000020 +#define OXYGEN_AC97_CODEC1_LINER_16 0x00000000 +#define OXYGEN_AC97_CODEC1_LINER_18 0x00000040 +#define OXYGEN_AC97_CODEC1_LINER_20 0x00000080 +#define OXYGEN_AC97_CODEC0_LINEL 0x00000100 +#define OXYGEN_AC97_CODEC0_LINER 0x00000200 + +#define OXYGEN_AC97_REGS 0xdc +#define OXYGEN_AC97_REG_DATA_MASK 0x0000ffff +#define OXYGEN_AC97_REG_ADDR_MASK 0x007f0000 +#define OXYGEN_AC97_REG_ADDR_SHIFT 16 +#define OXYGEN_AC97_REG_DIR_MASK 0x00800000 +#define OXYGEN_AC97_REG_DIR_WRITE 0x00000000 +#define OXYGEN_AC97_REG_DIR_READ 0x00800000 +#define OXYGEN_AC97_REG_CODEC_MASK 0x01000000 +#define OXYGEN_AC97_REG_CODEC_SHIFT 24 + +#define OXYGEN_TEST 0xe0 +#define OXYGEN_TEST_RAM_SUCCEEDED 0x01 +#define OXYGEN_TEST_PLAYBACK_RAM 0x02 +#define OXYGEN_TEST_RECORD_RAM 0x04 +#define OXYGEN_TEST_PLL 0x08 +#define OXYGEN_TEST_2WIRE_LOOPBACK 0x10 + +#define OXYGEN_DMA_FLUSH 0xe1 +/* OXYGEN_CHANNEL_* */ + +#define OXYGEN_CODEC_VERSION 0xe4 +#define OXYGEN_CODEC_ID_MASK 0x07 + +#define OXYGEN_REVISION 0xe6 +#define OXYGEN_PACKAGE_ID_MASK 0x0007 +#define OXYGEN_PACKAGE_ID_8786 0x0004 +#define OXYGEN_PACKAGE_ID_8787 0x0006 +#define OXYGEN_PACKAGE_ID_8788 0x0007 +#define OXYGEN_REVISION_MASK 0xfff8 +#define OXYGEN_REVISION_2 0x0008 + +#define OXYGEN_OFFSIN_48K 0xe8 +#define OXYGEN_OFFSBASE_48K 0xe9 +#define OXYGEN_OFFSBASE_MASK 0x0fff +#define OXYGEN_OFFSIN_44K 0xec +#define OXYGEN_OFFSBASE_44K 0xed + +#endif diff --git a/sound/pci/oxygen/pcm1796.h b/sound/pci/oxygen/pcm1796.h new file mode 100644 index 000000000..d5dcb09e4 --- /dev/null +++ b/sound/pci/oxygen/pcm1796.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef PCM1796_H_INCLUDED +#define PCM1796_H_INCLUDED + +/* register 16 */ +#define PCM1796_ATL_MASK 0xff +/* register 17 */ +#define PCM1796_ATR_MASK 0xff +/* register 18 */ +#define PCM1796_MUTE 0x01 +#define PCM1796_DME 0x02 +#define PCM1796_DMF_MASK 0x0c +#define PCM1796_DMF_48 0x04 +#define PCM1796_DMF_441 0x08 +#define PCM1796_DMF_32 0x0c +#define PCM1796_FMT_MASK 0x70 +#define PCM1796_FMT_16_RJUST 0x00 +#define PCM1796_FMT_20_RJUST 0x10 +#define PCM1796_FMT_24_RJUST 0x20 +#define PCM1796_FMT_24_LJUST 0x30 +#define PCM1796_FMT_16_I2S 0x40 +#define PCM1796_FMT_24_I2S 0x50 +#define PCM1796_ATLD 0x80 +/* register 19 */ +#define PCM1796_INZD 0x01 +#define PCM1796_FLT_MASK 0x02 +#define PCM1796_FLT_SHARP 0x00 +#define PCM1796_FLT_SLOW 0x02 +#define PCM1796_DFMS 0x04 +#define PCM1796_OPE 0x10 +#define PCM1796_ATS_MASK 0x60 +#define PCM1796_ATS_1 0x00 +#define PCM1796_ATS_2 0x20 +#define PCM1796_ATS_4 0x40 +#define PCM1796_ATS_8 0x60 +#define PCM1796_REV 0x80 +/* register 20 */ +#define PCM1796_OS_MASK 0x03 +#define PCM1796_OS_64 0x00 +#define PCM1796_OS_32 0x01 +#define PCM1796_OS_128 0x02 +#define PCM1796_CHSL_MASK 0x04 +#define PCM1796_CHSL_LEFT 0x00 +#define PCM1796_CHSL_RIGHT 0x04 +#define PCM1796_MONO 0x08 +#define PCM1796_DFTH 0x10 +#define PCM1796_DSD 0x20 +#define PCM1796_SRST 0x40 +/* register 21 */ +#define PCM1796_PCMZ 0x01 +#define PCM1796_DZ_MASK 0x06 +/* register 22 */ +#define PCM1796_ZFGL 0x01 +#define PCM1796_ZFGR 0x02 +/* register 23 */ +#define PCM1796_ID_MASK 0x1f + +#endif diff --git a/sound/pci/oxygen/se6x.c b/sound/pci/oxygen/se6x.c new file mode 100644 index 000000000..9d009015d --- /dev/null +++ b/sound/pci/oxygen/se6x.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * C-Media CMI8787 driver for the Studio Evolution SE6X + * + * Copyright (c) Clemens Ladisch + */ + +/* + * CMI8787: + * + * SPI -> microcontroller (not actually used) + * GPIO 0 -> do. + * GPIO 2 -> do. + * + * DAC0 -> both PCM1792A (L+R, each in mono mode) + * ADC1 <- 1st PCM1804 + * ADC2 <- 2nd PCM1804 + * ADC3 <- 3rd PCM1804 + */ + +#include +#include +#include +#include +#include +#include +#include "oxygen.h" + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("Studio Evolution SE6X driver"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable card"); + +static const struct pci_device_id se6x_ids[] = { + { OXYGEN_PCI_SUBID(0x13f6, 0x8788) }, + { } +}; +MODULE_DEVICE_TABLE(pci, se6x_ids); + +static void se6x_init(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, 0x005); + + snd_component_add(chip->card, "PCM1792A"); + snd_component_add(chip->card, "PCM1804"); +} + +static int se6x_control_filter(struct snd_kcontrol_new *template) +{ + /* no DAC volume/mute */ + if (!strncmp(template->name, "Master Playback ", 16)) + return 1; + return 0; +} + +static void se6x_cleanup(struct oxygen *chip) +{ +} + +static void set_pcm1792a_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + /* nothing to do (the microcontroller monitors DAC_LRCK) */ +} + +static void set_pcm1804_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ +} + +static unsigned int se6x_adjust_dac_routing(struct oxygen *chip, + unsigned int play_routing) +{ + /* route the same stereo pair to DAC0 and DAC1 */ + return ( play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) | + ((play_routing << 2) & OXYGEN_PLAY_DAC1_SOURCE_MASK); +} + +static const struct oxygen_model model_se6x = { + .shortname = "Studio Evolution SE6X", + .longname = "C-Media Oxygen HD Audio", + .chip = "CMI8787", + .init = se6x_init, + .control_filter = se6x_control_filter, + .cleanup = se6x_cleanup, + .set_dac_params = set_pcm1792a_params, + .set_adc_params = set_pcm1804_params, + .adjust_dac_routing = se6x_adjust_dac_routing, + .device_config = PLAYBACK_0_TO_I2S | + CAPTURE_0_FROM_I2S_1 | + CAPTURE_2_FROM_I2S_2 | + CAPTURE_3_FROM_I2S_3, + .dac_channels_pcm = 2, + .function_flags = OXYGEN_FUNCTION_SPI, + .dac_mclks = OXYGEN_MCLKS(256, 128, 128), + .adc_mclks = OXYGEN_MCLKS(256, 256, 128), + .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, + .adc_i2s_format = OXYGEN_I2S_FORMAT_I2S, +}; + +static int se6x_get_model(struct oxygen *chip, + const struct pci_device_id *pci_id) +{ + chip->model = model_se6x; + return 0; +} + +static int se6x_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + ++dev; + return -ENOENT; + } + err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE, + se6x_ids, se6x_get_model); + if (err >= 0) + ++dev; + return err; +} + +static struct pci_driver se6x_driver = { + .name = KBUILD_MODNAME, + .id_table = se6x_ids, + .probe = se6x_probe, + .driver = { + .pm = pm_sleep_ptr(&oxygen_pci_pm), + }, + .shutdown = oxygen_pci_shutdown, +}; + +module_pci_driver(se6x_driver); diff --git a/sound/pci/oxygen/virtuoso.c b/sound/pci/oxygen/virtuoso.c new file mode 100644 index 000000000..ded62199d --- /dev/null +++ b/sound/pci/oxygen/virtuoso.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * C-Media CMI8788 driver for Asus Xonar cards + * + * Copyright (c) Clemens Ladisch + */ + +#include +#include +#include +#include +#include +#include +#include "xonar.h" + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("Asus Virtuoso driver"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable card"); + +static const struct pci_device_id xonar_ids[] = { + { OXYGEN_PCI_SUBID(0x1043, 0x8269) }, + { OXYGEN_PCI_SUBID(0x1043, 0x8275) }, + { OXYGEN_PCI_SUBID(0x1043, 0x82b7) }, + { OXYGEN_PCI_SUBID(0x1043, 0x8314) }, + { OXYGEN_PCI_SUBID(0x1043, 0x8327) }, + { OXYGEN_PCI_SUBID(0x1043, 0x834f) }, + { OXYGEN_PCI_SUBID(0x1043, 0x835c) }, + { OXYGEN_PCI_SUBID(0x1043, 0x835d) }, + { OXYGEN_PCI_SUBID(0x1043, 0x835e) }, + { OXYGEN_PCI_SUBID(0x1043, 0x838e) }, + { OXYGEN_PCI_SUBID(0x1043, 0x8428) }, + { OXYGEN_PCI_SUBID(0x1043, 0x8522) }, + { OXYGEN_PCI_SUBID(0x1043, 0x85f4) }, + { OXYGEN_PCI_SUBID_BROKEN_EEPROM }, + { } +}; +MODULE_DEVICE_TABLE(pci, xonar_ids); + +static int get_xonar_model(struct oxygen *chip, + const struct pci_device_id *id) +{ + if (get_xonar_pcm179x_model(chip, id) >= 0) + return 0; + if (get_xonar_cs43xx_model(chip, id) >= 0) + return 0; + if (get_xonar_wm87x6_model(chip, id) >= 0) + return 0; + return -EINVAL; +} + +static int xonar_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + ++dev; + return -ENOENT; + } + err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE, + xonar_ids, get_xonar_model); + if (err >= 0) + ++dev; + return err; +} + +static struct pci_driver xonar_driver = { + .name = KBUILD_MODNAME, + .id_table = xonar_ids, + .probe = xonar_probe, + .driver = { + .pm = pm_sleep_ptr(&oxygen_pci_pm), + }, + .shutdown = oxygen_pci_shutdown, +}; + +module_pci_driver(xonar_driver); diff --git a/sound/pci/oxygen/wm8766.h b/sound/pci/oxygen/wm8766.h new file mode 100644 index 000000000..be83ad49d --- /dev/null +++ b/sound/pci/oxygen/wm8766.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef WM8766_H_INCLUDED +#define WM8766_H_INCLUDED + +#define WM8766_LDA1 0x00 +#define WM8766_RDA1 0x01 +#define WM8766_DAC_CTRL 0x02 +#define WM8766_INT_CTRL 0x03 +#define WM8766_LDA2 0x04 +#define WM8766_RDA2 0x05 +#define WM8766_LDA3 0x06 +#define WM8766_RDA3 0x07 +#define WM8766_MASTDA 0x08 +#define WM8766_DAC_CTRL2 0x09 +#define WM8766_DAC_CTRL3 0x0a +#define WM8766_MUTE1 0x0c +#define WM8766_MUTE2 0x0f +#define WM8766_RESET 0x1f + +/* LDAx/RDAx/MASTDA */ +#define WM8766_ATT_MASK 0x0ff +#define WM8766_UPDATE 0x100 +/* DAC_CTRL */ +#define WM8766_MUTEALL 0x001 +#define WM8766_DEEMPALL 0x002 +#define WM8766_PWDN 0x004 +#define WM8766_ATC 0x008 +#define WM8766_IZD 0x010 +#define WM8766_PL_LEFT_MASK 0x060 +#define WM8766_PL_LEFT_MUTE 0x000 +#define WM8766_PL_LEFT_LEFT 0x020 +#define WM8766_PL_LEFT_RIGHT 0x040 +#define WM8766_PL_LEFT_LRMIX 0x060 +#define WM8766_PL_RIGHT_MASK 0x180 +#define WM8766_PL_RIGHT_MUTE 0x000 +#define WM8766_PL_RIGHT_LEFT 0x080 +#define WM8766_PL_RIGHT_RIGHT 0x100 +#define WM8766_PL_RIGHT_LRMIX 0x180 +/* INT_CTRL */ +#define WM8766_FMT_MASK 0x003 +#define WM8766_FMT_RJUST 0x000 +#define WM8766_FMT_LJUST 0x001 +#define WM8766_FMT_I2S 0x002 +#define WM8766_FMT_DSP 0x003 +#define WM8766_LRP 0x004 +#define WM8766_BCP 0x008 +#define WM8766_IWL_MASK 0x030 +#define WM8766_IWL_16 0x000 +#define WM8766_IWL_20 0x010 +#define WM8766_IWL_24 0x020 +#define WM8766_IWL_32 0x030 +#define WM8766_PHASE_MASK 0x1c0 +/* DAC_CTRL2 */ +#define WM8766_ZCD 0x001 +#define WM8766_DZFM_MASK 0x006 +#define WM8766_DMUTE_MASK 0x038 +#define WM8766_DEEMP_MASK 0x1c0 +/* DAC_CTRL3 */ +#define WM8766_DACPD_MASK 0x00e +#define WM8766_PWRDNALL 0x010 +#define WM8766_MS 0x020 +#define WM8766_RATE_MASK 0x1c0 +#define WM8766_RATE_128 0x000 +#define WM8766_RATE_192 0x040 +#define WM8766_RATE_256 0x080 +#define WM8766_RATE_384 0x0c0 +#define WM8766_RATE_512 0x100 +#define WM8766_RATE_768 0x140 +/* MUTE1 */ +#define WM8766_MPD1 0x040 +/* MUTE2 */ +#define WM8766_MPD2 0x020 + +#endif diff --git a/sound/pci/oxygen/wm8776.h b/sound/pci/oxygen/wm8776.h new file mode 100644 index 000000000..350f3829c --- /dev/null +++ b/sound/pci/oxygen/wm8776.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef WM8776_H_INCLUDED +#define WM8776_H_INCLUDED + +/* + * the following register names are from: + * wm8776.h -- WM8776 ASoC driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + */ + +#define WM8776_HPLVOL 0x00 +#define WM8776_HPRVOL 0x01 +#define WM8776_HPMASTER 0x02 +#define WM8776_DACLVOL 0x03 +#define WM8776_DACRVOL 0x04 +#define WM8776_DACMASTER 0x05 +#define WM8776_PHASESWAP 0x06 +#define WM8776_DACCTRL1 0x07 +#define WM8776_DACMUTE 0x08 +#define WM8776_DACCTRL2 0x09 +#define WM8776_DACIFCTRL 0x0a +#define WM8776_ADCIFCTRL 0x0b +#define WM8776_MSTRCTRL 0x0c +#define WM8776_PWRDOWN 0x0d +#define WM8776_ADCLVOL 0x0e +#define WM8776_ADCRVOL 0x0f +#define WM8776_ALCCTRL1 0x10 +#define WM8776_ALCCTRL2 0x11 +#define WM8776_ALCCTRL3 0x12 +#define WM8776_NOISEGATE 0x13 +#define WM8776_LIMITER 0x14 +#define WM8776_ADCMUX 0x15 +#define WM8776_OUTMUX 0x16 +#define WM8776_RESET 0x17 + + +/* HPLVOL/HPRVOL/HPMASTER */ +#define WM8776_HPATT_MASK 0x07f +#define WM8776_HPZCEN 0x080 +#define WM8776_UPDATE 0x100 + +/* DACLVOL/DACRVOL/DACMASTER */ +#define WM8776_DATT_MASK 0x0ff +/*#define WM8776_UPDATE 0x100*/ + +/* PHASESWAP */ +#define WM8776_PH_MASK 0x003 + +/* DACCTRL1 */ +#define WM8776_DZCEN 0x001 +#define WM8776_ATC 0x002 +#define WM8776_IZD 0x004 +#define WM8776_TOD 0x008 +#define WM8776_PL_LEFT_MASK 0x030 +#define WM8776_PL_LEFT_MUTE 0x000 +#define WM8776_PL_LEFT_LEFT 0x010 +#define WM8776_PL_LEFT_RIGHT 0x020 +#define WM8776_PL_LEFT_LRMIX 0x030 +#define WM8776_PL_RIGHT_MASK 0x0c0 +#define WM8776_PL_RIGHT_MUTE 0x000 +#define WM8776_PL_RIGHT_LEFT 0x040 +#define WM8776_PL_RIGHT_RIGHT 0x080 +#define WM8776_PL_RIGHT_LRMIX 0x0c0 + +/* DACMUTE */ +#define WM8776_DMUTE 0x001 + +/* DACCTRL2 */ +#define WM8776_DEEMPH 0x001 +#define WM8776_DZFM_MASK 0x006 +#define WM8776_DZFM_NONE 0x000 +#define WM8776_DZFM_LR 0x002 +#define WM8776_DZFM_BOTH 0x004 +#define WM8776_DZFM_EITHER 0x006 + +/* DACIFCTRL */ +#define WM8776_DACFMT_MASK 0x003 +#define WM8776_DACFMT_RJUST 0x000 +#define WM8776_DACFMT_LJUST 0x001 +#define WM8776_DACFMT_I2S 0x002 +#define WM8776_DACFMT_DSP 0x003 +#define WM8776_DACLRP 0x004 +#define WM8776_DACBCP 0x008 +#define WM8776_DACWL_MASK 0x030 +#define WM8776_DACWL_16 0x000 +#define WM8776_DACWL_20 0x010 +#define WM8776_DACWL_24 0x020 +#define WM8776_DACWL_32 0x030 + +/* ADCIFCTRL */ +#define WM8776_ADCFMT_MASK 0x003 +#define WM8776_ADCFMT_RJUST 0x000 +#define WM8776_ADCFMT_LJUST 0x001 +#define WM8776_ADCFMT_I2S 0x002 +#define WM8776_ADCFMT_DSP 0x003 +#define WM8776_ADCLRP 0x004 +#define WM8776_ADCBCP 0x008 +#define WM8776_ADCWL_MASK 0x030 +#define WM8776_ADCWL_16 0x000 +#define WM8776_ADCWL_20 0x010 +#define WM8776_ADCWL_24 0x020 +#define WM8776_ADCWL_32 0x030 +#define WM8776_ADCMCLK 0x040 +#define WM8776_ADCHPD 0x100 + +/* MSTRCTRL */ +#define WM8776_ADCRATE_MASK 0x007 +#define WM8776_ADCRATE_256 0x002 +#define WM8776_ADCRATE_384 0x003 +#define WM8776_ADCRATE_512 0x004 +#define WM8776_ADCRATE_768 0x005 +#define WM8776_ADCOSR 0x008 +#define WM8776_DACRATE_MASK 0x070 +#define WM8776_DACRATE_128 0x000 +#define WM8776_DACRATE_192 0x010 +#define WM8776_DACRATE_256 0x020 +#define WM8776_DACRATE_384 0x030 +#define WM8776_DACRATE_512 0x040 +#define WM8776_DACRATE_768 0x050 +#define WM8776_DACMS 0x080 +#define WM8776_ADCMS 0x100 + +/* PWRDOWN */ +#define WM8776_PDWN 0x001 +#define WM8776_ADCPD 0x002 +#define WM8776_DACPD 0x004 +#define WM8776_HPPD 0x008 +#define WM8776_AINPD 0x040 + +/* ADCLVOL/ADCRVOL */ +#define WM8776_AGMASK 0x0ff +#define WM8776_ZCA 0x100 + +/* ALCCTRL1 */ +#define WM8776_LCT_MASK 0x00f +#define WM8776_MAXGAIN_MASK 0x070 +#define WM8776_LCSEL_MASK 0x180 +#define WM8776_LCSEL_LIMITER 0x000 +#define WM8776_LCSEL_ALC_RIGHT 0x080 +#define WM8776_LCSEL_ALC_LEFT 0x100 +#define WM8776_LCSEL_ALC_STEREO 0x180 + +/* ALCCTRL2 */ +#define WM8776_HLD_MASK 0x00f +#define WM8776_ALCZC 0x080 +#define WM8776_LCEN 0x100 + +/* ALCCTRL3 */ +#define WM8776_ATK_MASK 0x00f +#define WM8776_DCY_MASK 0x0f0 + +/* NOISEGATE */ +#define WM8776_NGAT 0x001 +#define WM8776_NGTH_MASK 0x01c + +/* LIMITER */ +#define WM8776_MAXATTEN_MASK 0x00f +#define WM8776_TRANWIN_MASK 0x070 + +/* ADCMUX */ +#define WM8776_AMX_MASK 0x01f +#define WM8776_MUTERA 0x040 +#define WM8776_MUTELA 0x080 +#define WM8776_LRBOTH 0x100 + +/* OUTMUX */ +#define WM8776_MX_DAC 0x001 +#define WM8776_MX_AUX 0x002 +#define WM8776_MX_BYPASS 0x004 + +#endif diff --git a/sound/pci/oxygen/wm8785.h b/sound/pci/oxygen/wm8785.h new file mode 100644 index 000000000..21b932566 --- /dev/null +++ b/sound/pci/oxygen/wm8785.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef WM8785_H_INCLUDED +#define WM8785_H_INCLUDED + +#define WM8785_R0 0 +#define WM8785_R1 1 +#define WM8785_R2 2 +#define WM8785_R7 7 + +/* R0 */ +#define WM8785_MCR_MASK 0x007 +#define WM8785_MCR_SLAVE 0x000 +#define WM8785_MCR_MASTER_128 0x001 +#define WM8785_MCR_MASTER_192 0x002 +#define WM8785_MCR_MASTER_256 0x003 +#define WM8785_MCR_MASTER_384 0x004 +#define WM8785_MCR_MASTER_512 0x005 +#define WM8785_MCR_MASTER_768 0x006 +#define WM8785_OSR_MASK 0x018 +#define WM8785_OSR_SINGLE 0x000 +#define WM8785_OSR_DOUBLE 0x008 +#define WM8785_OSR_QUAD 0x010 +#define WM8785_FORMAT_MASK 0x060 +#define WM8785_FORMAT_RJUST 0x000 +#define WM8785_FORMAT_LJUST 0x020 +#define WM8785_FORMAT_I2S 0x040 +#define WM8785_FORMAT_DSP 0x060 +/* R1 */ +#define WM8785_WL_MASK 0x003 +#define WM8785_WL_16 0x000 +#define WM8785_WL_20 0x001 +#define WM8785_WL_24 0x002 +#define WM8785_WL_32 0x003 +#define WM8785_LRP 0x004 +#define WM8785_BCLKINV 0x008 +#define WM8785_LRSWAP 0x010 +#define WM8785_DEVNO_MASK 0x0e0 +/* R2 */ +#define WM8785_HPFR 0x001 +#define WM8785_HPFL 0x002 +#define WM8785_SDODIS 0x004 +#define WM8785_PWRDNR 0x008 +#define WM8785_PWRDNL 0x010 +#define WM8785_TDM_MASK 0x1c0 + +#endif diff --git a/sound/pci/oxygen/xonar.h b/sound/pci/oxygen/xonar.h new file mode 100644 index 000000000..3e373880c --- /dev/null +++ b/sound/pci/oxygen/xonar.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef XONAR_H_INCLUDED +#define XONAR_H_INCLUDED + +#include "oxygen.h" + +struct xonar_generic { + unsigned int anti_pop_delay; + u16 output_enable_bit; + u8 ext_power_reg; + u8 ext_power_int_reg; + u8 ext_power_bit; + u8 has_power; +}; + +struct xonar_hdmi { + u8 params[5]; +}; + +/* generic helper functions */ + +void xonar_enable_output(struct oxygen *chip); +void xonar_disable_output(struct oxygen *chip); +void xonar_init_ext_power(struct oxygen *chip); +void xonar_init_cs53x1(struct oxygen *chip); +void xonar_set_cs53x1_params(struct oxygen *chip, + struct snd_pcm_hw_params *params); + +#define XONAR_GPIO_BIT_INVERT (1 << 16) +int xonar_gpio_bit_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value); +int xonar_gpio_bit_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value); + +/* model-specific card drivers */ + +int get_xonar_pcm179x_model(struct oxygen *chip, + const struct pci_device_id *id); +int get_xonar_cs43xx_model(struct oxygen *chip, + const struct pci_device_id *id); +int get_xonar_wm87x6_model(struct oxygen *chip, + const struct pci_device_id *id); + +/* HDMI helper functions */ + +void xonar_hdmi_init(struct oxygen *chip, struct xonar_hdmi *data); +void xonar_hdmi_cleanup(struct oxygen *chip); +void xonar_hdmi_resume(struct oxygen *chip, struct xonar_hdmi *hdmi); +void xonar_hdmi_pcm_hardware_filter(unsigned int channel, + struct snd_pcm_hardware *hardware); +void xonar_set_hdmi_params(struct oxygen *chip, struct xonar_hdmi *hdmi, + struct snd_pcm_hw_params *params); +void xonar_hdmi_uart_input(struct oxygen *chip); + +#endif diff --git a/sound/pci/oxygen/xonar_cs43xx.c b/sound/pci/oxygen/xonar_cs43xx.c new file mode 100644 index 000000000..47b275865 --- /dev/null +++ b/sound/pci/oxygen/xonar_cs43xx.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * card driver for models with CS4398/CS4362A DACs (Xonar D1/DX) + * + * Copyright (c) Clemens Ladisch + */ + +/* + * Xonar D1/DX + * ----------- + * + * CMI8788: + * + * I²C <-> CS4398 (addr 1001111) (front) + * <-> CS4362A (addr 0011000) (surround, center/LFE, back) + * + * GPI 0 <- external power present (DX only) + * + * GPIO 0 -> enable output to speakers + * GPIO 1 -> route output to front panel + * GPIO 2 -> M0 of CS5361 + * GPIO 3 -> M1 of CS5361 + * GPIO 6 -> ? + * GPIO 7 -> ? + * GPIO 8 -> route input jack to line-in (0) or mic-in (1) + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * MIC_IN <- mic + * FMIC_IN <- front mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5361 input + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "xonar.h" +#include "cm9780.h" +#include "cs4398.h" +#include "cs4362a.h" + +#define GPI_EXT_POWER 0x01 +#define GPIO_D1_OUTPUT_ENABLE 0x0001 +#define GPIO_D1_FRONT_PANEL 0x0002 +#define GPIO_D1_MAGIC 0x00c0 +#define GPIO_D1_INPUT_ROUTE 0x0100 + +#define I2C_DEVICE_CS4398 0x9e /* 10011, AD1=1, AD0=1, /W=0 */ +#define I2C_DEVICE_CS4362A 0x30 /* 001100, AD0=0, /W=0 */ + +struct xonar_cs43xx { + struct xonar_generic generic; + u8 cs4398_regs[8]; + u8 cs4362a_regs[15]; +}; + +static void cs4398_write(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_cs43xx *data = chip->model_data; + + oxygen_write_i2c(chip, I2C_DEVICE_CS4398, reg, value); + if (reg < ARRAY_SIZE(data->cs4398_regs)) + data->cs4398_regs[reg] = value; +} + +static void cs4398_write_cached(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_cs43xx *data = chip->model_data; + + if (value != data->cs4398_regs[reg]) + cs4398_write(chip, reg, value); +} + +static void cs4362a_write(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_cs43xx *data = chip->model_data; + + oxygen_write_i2c(chip, I2C_DEVICE_CS4362A, reg, value); + if (reg < ARRAY_SIZE(data->cs4362a_regs)) + data->cs4362a_regs[reg] = value; +} + +static void cs4362a_write_cached(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_cs43xx *data = chip->model_data; + + if (value != data->cs4362a_regs[reg]) + cs4362a_write(chip, reg, value); +} + +static void cs43xx_registers_init(struct oxygen *chip) +{ + struct xonar_cs43xx *data = chip->model_data; + unsigned int i; + + /* set CPEN (control port mode) and power down */ + cs4398_write(chip, 8, CS4398_CPEN | CS4398_PDN); + cs4362a_write(chip, 0x01, CS4362A_PDN | CS4362A_CPEN); + /* configure */ + cs4398_write(chip, 2, data->cs4398_regs[2]); + cs4398_write(chip, 3, CS4398_ATAPI_B_R | CS4398_ATAPI_A_L); + cs4398_write(chip, 4, data->cs4398_regs[4]); + cs4398_write(chip, 5, data->cs4398_regs[5]); + cs4398_write(chip, 6, data->cs4398_regs[6]); + cs4398_write(chip, 7, data->cs4398_regs[7]); + cs4362a_write(chip, 0x02, CS4362A_DIF_LJUST); + cs4362a_write(chip, 0x03, CS4362A_MUTEC_6 | CS4362A_AMUTE | + CS4362A_RMP_UP | CS4362A_ZERO_CROSS | CS4362A_SOFT_RAMP); + cs4362a_write(chip, 0x04, data->cs4362a_regs[0x04]); + cs4362a_write(chip, 0x05, 0); + for (i = 6; i <= 14; ++i) + cs4362a_write(chip, i, data->cs4362a_regs[i]); + /* clear power down */ + cs4398_write(chip, 8, CS4398_CPEN); + cs4362a_write(chip, 0x01, CS4362A_CPEN); +} + +static void xonar_d1_init(struct oxygen *chip) +{ + struct xonar_cs43xx *data = chip->model_data; + + data->generic.anti_pop_delay = 800; + data->generic.output_enable_bit = GPIO_D1_OUTPUT_ENABLE; + data->cs4398_regs[2] = + CS4398_FM_SINGLE | CS4398_DEM_NONE | CS4398_DIF_LJUST; + data->cs4398_regs[4] = CS4398_MUTEP_LOW | + CS4398_MUTE_B | CS4398_MUTE_A | CS4398_PAMUTE; + data->cs4398_regs[5] = 60 * 2; + data->cs4398_regs[6] = 60 * 2; + data->cs4398_regs[7] = CS4398_RMP_DN | CS4398_RMP_UP | + CS4398_ZERO_CROSS | CS4398_SOFT_RAMP; + data->cs4362a_regs[4] = CS4362A_RMP_DN | CS4362A_DEM_NONE; + data->cs4362a_regs[6] = CS4362A_FM_SINGLE | + CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L; + data->cs4362a_regs[7] = 60 | CS4362A_MUTE; + data->cs4362a_regs[8] = 60 | CS4362A_MUTE; + data->cs4362a_regs[9] = data->cs4362a_regs[6]; + data->cs4362a_regs[10] = 60 | CS4362A_MUTE; + data->cs4362a_regs[11] = 60 | CS4362A_MUTE; + data->cs4362a_regs[12] = data->cs4362a_regs[6]; + data->cs4362a_regs[13] = 60 | CS4362A_MUTE; + data->cs4362a_regs[14] = 60 | CS4362A_MUTE; + + oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS, + OXYGEN_2WIRE_LENGTH_8 | + OXYGEN_2WIRE_INTERRUPT_MASK | + OXYGEN_2WIRE_SPEED_FAST); + + cs43xx_registers_init(chip); + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_D1_FRONT_PANEL | + GPIO_D1_MAGIC | + GPIO_D1_INPUT_ROUTE); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, + GPIO_D1_FRONT_PANEL | GPIO_D1_INPUT_ROUTE); + + xonar_init_cs53x1(chip); + xonar_enable_output(chip); + + snd_component_add(chip->card, "CS4398"); + snd_component_add(chip->card, "CS4362A"); + snd_component_add(chip->card, "CS5361"); +} + +static void xonar_dx_init(struct oxygen *chip) +{ + struct xonar_cs43xx *data = chip->model_data; + + data->generic.ext_power_reg = OXYGEN_GPI_DATA; + data->generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK; + data->generic.ext_power_bit = GPI_EXT_POWER; + xonar_init_ext_power(chip); + xonar_d1_init(chip); +} + +static void xonar_d1_cleanup(struct oxygen *chip) +{ + xonar_disable_output(chip); + cs4362a_write(chip, 0x01, CS4362A_PDN | CS4362A_CPEN); + oxygen_clear_bits8(chip, OXYGEN_FUNCTION, OXYGEN_FUNCTION_RESET_CODEC); +} + +static void xonar_d1_suspend(struct oxygen *chip) +{ + xonar_d1_cleanup(chip); +} + +static void xonar_d1_resume(struct oxygen *chip) +{ + oxygen_set_bits8(chip, OXYGEN_FUNCTION, OXYGEN_FUNCTION_RESET_CODEC); + msleep(1); + cs43xx_registers_init(chip); + xonar_enable_output(chip); +} + +static void set_cs43xx_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct xonar_cs43xx *data = chip->model_data; + u8 cs4398_fm, cs4362a_fm; + + if (params_rate(params) <= 50000) { + cs4398_fm = CS4398_FM_SINGLE; + cs4362a_fm = CS4362A_FM_SINGLE; + } else if (params_rate(params) <= 100000) { + cs4398_fm = CS4398_FM_DOUBLE; + cs4362a_fm = CS4362A_FM_DOUBLE; + } else { + cs4398_fm = CS4398_FM_QUAD; + cs4362a_fm = CS4362A_FM_QUAD; + } + cs4398_fm |= CS4398_DEM_NONE | CS4398_DIF_LJUST; + cs4398_write_cached(chip, 2, cs4398_fm); + cs4362a_fm |= data->cs4362a_regs[6] & ~CS4362A_FM_MASK; + cs4362a_write_cached(chip, 6, cs4362a_fm); + cs4362a_write_cached(chip, 12, cs4362a_fm); + cs4362a_fm &= CS4362A_FM_MASK; + cs4362a_fm |= data->cs4362a_regs[9] & ~CS4362A_FM_MASK; + cs4362a_write_cached(chip, 9, cs4362a_fm); +} + +static void update_cs4362a_volumes(struct oxygen *chip) +{ + unsigned int i; + u8 mute; + + mute = chip->dac_mute ? CS4362A_MUTE : 0; + for (i = 0; i < 6; ++i) + cs4362a_write_cached(chip, 7 + i + i / 2, + (127 - chip->dac_volume[2 + i]) | mute); +} + +static void update_cs43xx_volume(struct oxygen *chip) +{ + cs4398_write_cached(chip, 5, (127 - chip->dac_volume[0]) * 2); + cs4398_write_cached(chip, 6, (127 - chip->dac_volume[1]) * 2); + update_cs4362a_volumes(chip); +} + +static void update_cs43xx_mute(struct oxygen *chip) +{ + u8 reg; + + reg = CS4398_MUTEP_LOW | CS4398_PAMUTE; + if (chip->dac_mute) + reg |= CS4398_MUTE_B | CS4398_MUTE_A; + cs4398_write_cached(chip, 4, reg); + update_cs4362a_volumes(chip); +} + +static void update_cs43xx_center_lfe_mix(struct oxygen *chip, bool mixed) +{ + struct xonar_cs43xx *data = chip->model_data; + u8 reg; + + reg = data->cs4362a_regs[9] & ~CS4362A_ATAPI_MASK; + if (mixed) + reg |= CS4362A_ATAPI_B_LR | CS4362A_ATAPI_A_LR; + else + reg |= CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L; + cs4362a_write_cached(chip, 9, reg); +} + +static const struct snd_kcontrol_new front_panel_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Front Panel Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = xonar_gpio_bit_switch_get, + .put = xonar_gpio_bit_switch_put, + .private_value = GPIO_D1_FRONT_PANEL, +}; + +static int rolloff_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { + "Fast Roll-off", "Slow Roll-off" + }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int rolloff_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_cs43xx *data = chip->model_data; + + value->value.enumerated.item[0] = + (data->cs4398_regs[7] & CS4398_FILT_SEL) != 0; + return 0; +} + +static int rolloff_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_cs43xx *data = chip->model_data; + int changed; + u8 reg; + + guard(mutex)(&chip->mutex); + reg = data->cs4398_regs[7]; + if (value->value.enumerated.item[0]) + reg |= CS4398_FILT_SEL; + else + reg &= ~CS4398_FILT_SEL; + changed = reg != data->cs4398_regs[7]; + if (changed) { + cs4398_write(chip, 7, reg); + if (reg & CS4398_FILT_SEL) + reg = data->cs4362a_regs[0x04] | CS4362A_FILT_SEL; + else + reg = data->cs4362a_regs[0x04] & ~CS4362A_FILT_SEL; + cs4362a_write(chip, 0x04, reg); + } + return changed; +} + +static const struct snd_kcontrol_new rolloff_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Filter Playback Enum", + .info = rolloff_info, + .get = rolloff_get, + .put = rolloff_put, +}; + +static void xonar_d1_line_mic_ac97_switch(struct oxygen *chip, + unsigned int reg, unsigned int mute) +{ + if (reg == AC97_LINE) { + guard(spinlock_irq)(&chip->reg_lock); + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + mute ? GPIO_D1_INPUT_ROUTE : 0, + GPIO_D1_INPUT_ROUTE); + } +} + +static const DECLARE_TLV_DB_SCALE(cs4362a_db_scale, -6000, 100, 0); + +static int xonar_d1_mixer_init(struct oxygen *chip) +{ + int err; + + err = snd_ctl_add(chip->card, snd_ctl_new1(&front_panel_switch, chip)); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, snd_ctl_new1(&rolloff_control, chip)); + if (err < 0) + return err; + return 0; +} + +static void dump_cs4362a_registers(struct xonar_cs43xx *data, + struct snd_info_buffer *buffer) +{ + unsigned int i; + + snd_iprintf(buffer, "\nCS4362A:"); + for (i = 1; i <= 14; ++i) + snd_iprintf(buffer, " %02x", data->cs4362a_regs[i]); + snd_iprintf(buffer, "\n"); +} + +static void dump_d1_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + struct xonar_cs43xx *data = chip->model_data; + unsigned int i; + + snd_iprintf(buffer, "\nCS4398: 7?"); + for (i = 2; i < 8; ++i) + snd_iprintf(buffer, " %02x", data->cs4398_regs[i]); + snd_iprintf(buffer, "\n"); + dump_cs4362a_registers(data, buffer); +} + +static const struct oxygen_model model_xonar_d1 = { + .longname = "Asus Virtuoso 100", + .chip = "AV200", + .init = xonar_d1_init, + .mixer_init = xonar_d1_mixer_init, + .cleanup = xonar_d1_cleanup, + .suspend = xonar_d1_suspend, + .resume = xonar_d1_resume, + .set_dac_params = set_cs43xx_params, + .set_adc_params = xonar_set_cs53x1_params, + .update_dac_volume = update_cs43xx_volume, + .update_dac_mute = update_cs43xx_mute, + .update_center_lfe_mix = update_cs43xx_center_lfe_mix, + .ac97_switch = xonar_d1_line_mic_ac97_switch, + .dump_registers = dump_d1_registers, + .dac_tlv = cs4362a_db_scale, + .model_data_size = sizeof(struct xonar_cs43xx), + .device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_2 | + CAPTURE_1_FROM_SPDIF | + AC97_FMIC_SWITCH, + .dac_channels_pcm = 8, + .dac_channels_mixer = 8, + .dac_volume_min = 127 - 60, + .dac_volume_max = 127, + .function_flags = OXYGEN_FUNCTION_2WIRE, + .dac_mclks = OXYGEN_MCLKS(256, 128, 128), + .adc_mclks = OXYGEN_MCLKS(256, 128, 128), + .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, + .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; + +int get_xonar_cs43xx_model(struct oxygen *chip, + const struct pci_device_id *id) +{ + switch (id->subdevice) { + case 0x834f: + chip->model = model_xonar_d1; + chip->model.shortname = "Xonar D1"; + break; + case 0x8275: + case 0x8327: + chip->model = model_xonar_d1; + chip->model.shortname = "Xonar DX"; + chip->model.init = xonar_dx_init; + break; + default: + return -EINVAL; + } + return 0; +} diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c new file mode 100644 index 000000000..b90421a1d --- /dev/null +++ b/sound/pci/oxygen/xonar_dg.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * card driver for the Xonar DG/DGX + * + * Copyright (c) Clemens Ladisch + * Copyright (c) Roman Volkov + */ + +/* + * Xonar DG/DGX + * ------------ + * + * CS4245 and CS4361 both will mute all outputs if any clock ratio + * is invalid. + * + * CMI8788: + * + * SPI 0 -> CS4245 + * + * Playback: + * I²S 1 -> CS4245 + * I²S 2 -> CS4361 (center/LFE) + * I²S 3 -> CS4361 (surround) + * I²S 4 -> CS4361 (front) + * Capture: + * I²S ADC 1 <- CS4245 + * + * GPIO 3 <- ? + * GPIO 4 <- headphone detect + * GPIO 5 -> enable ADC analog circuit for the left channel + * GPIO 6 -> enable ADC analog circuit for the right channel + * GPIO 7 -> switch green rear output jack between CS4245 and the first + * channel of CS4361 (mechanical relay) + * GPIO 8 -> enable output to speakers + * + * CS4245: + * + * input 0 <- mic + * input 1 <- aux + * input 2 <- front mic + * input 4 <- line + * DAC out -> headphones + * aux out -> front panel headphones + */ + +#include +#include +#include +#include +#include +#include +#include +#include "oxygen.h" +#include "xonar_dg.h" +#include "cs4245.h" + +int cs4245_write_spi(struct oxygen *chip, u8 reg) +{ + struct dg *data = chip->model_data; + unsigned int packet; + + packet = reg << 8; + packet |= (CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 16; + packet |= data->cs4245_shadow[reg]; + + return oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_3 | + OXYGEN_SPI_CLOCK_1280 | + (0 << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI, + packet); +} + +int cs4245_read_spi(struct oxygen *chip, u8 addr) +{ + struct dg *data = chip->model_data; + int ret; + + ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI | + OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT), + ((CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 8) | addr); + if (ret < 0) + return ret; + + ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI | + OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT), + (CS4245_SPI_ADDRESS | CS4245_SPI_READ) << 8); + if (ret < 0) + return ret; + + data->cs4245_shadow[addr] = oxygen_read8(chip, OXYGEN_SPI_DATA1); + + return 0; +} + +int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op) +{ + struct dg *data = chip->model_data; + unsigned char addr; + int ret; + + for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) { + ret = (op == CS4245_SAVE_TO_SHADOW ? + cs4245_read_spi(chip, addr) : + cs4245_write_spi(chip, addr)); + if (ret < 0) + return ret; + } + return 0; +} + +static void cs4245_init(struct oxygen *chip) +{ + struct dg *data = chip->model_data; + + /* save the initial state: codec version, registers */ + cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW); + + /* + * Power up the CODEC internals, enable soft ramp & zero cross, work in + * async. mode, enable aux output from DAC. Invert DAC output as in the + * Windows driver. + */ + data->cs4245_shadow[CS4245_POWER_CTRL] = 0; + data->cs4245_shadow[CS4245_SIGNAL_SEL] = + CS4245_A_OUT_SEL_DAC | CS4245_ASYNCH; + data->cs4245_shadow[CS4245_DAC_CTRL_1] = + CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST; + data->cs4245_shadow[CS4245_DAC_CTRL_2] = + CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC; + data->cs4245_shadow[CS4245_ADC_CTRL] = + CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST; + data->cs4245_shadow[CS4245_ANALOG_IN] = + CS4245_PGA_SOFT | CS4245_PGA_ZERO; + data->cs4245_shadow[CS4245_PGA_B_CTRL] = 0; + data->cs4245_shadow[CS4245_PGA_A_CTRL] = 0; + data->cs4245_shadow[CS4245_DAC_A_CTRL] = 8; + data->cs4245_shadow[CS4245_DAC_B_CTRL] = 8; + + cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW); + snd_component_add(chip->card, "CS4245"); +} + +void dg_init(struct oxygen *chip) +{ + struct dg *data = chip->model_data; + + data->output_sel = PLAYBACK_DST_HP_FP; + data->input_sel = CAPTURE_SRC_MIC; + + cs4245_init(chip); + oxygen_write16(chip, OXYGEN_GPIO_CONTROL, + GPIO_OUTPUT_ENABLE | GPIO_HP_REAR | GPIO_INPUT_ROUTE); + /* anti-pop delay, wait some time before enabling the output */ + msleep(2500); + oxygen_write16(chip, OXYGEN_GPIO_DATA, + GPIO_OUTPUT_ENABLE | GPIO_INPUT_ROUTE); +} + +void dg_cleanup(struct oxygen *chip) +{ + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); +} + +void dg_suspend(struct oxygen *chip) +{ + dg_cleanup(chip); +} + +void dg_resume(struct oxygen *chip) +{ + cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW); + msleep(2500); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); +} + +void set_cs4245_dac_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct dg *data = chip->model_data; + unsigned char dac_ctrl; + unsigned char mclk_freq; + + dac_ctrl = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK; + mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK; + if (params_rate(params) <= 50000) { + dac_ctrl |= CS4245_DAC_FM_SINGLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; + } else if (params_rate(params) <= 100000) { + dac_ctrl |= CS4245_DAC_FM_DOUBLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; + } else { + dac_ctrl |= CS4245_DAC_FM_QUAD; + mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT; + } + data->cs4245_shadow[CS4245_DAC_CTRL_1] = dac_ctrl; + data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq; + cs4245_write_spi(chip, CS4245_DAC_CTRL_1); + cs4245_write_spi(chip, CS4245_MCLK_FREQ); +} + +void set_cs4245_adc_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct dg *data = chip->model_data; + unsigned char adc_ctrl; + unsigned char mclk_freq; + + adc_ctrl = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK; + mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK; + if (params_rate(params) <= 50000) { + adc_ctrl |= CS4245_ADC_FM_SINGLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; + } else if (params_rate(params) <= 100000) { + adc_ctrl |= CS4245_ADC_FM_DOUBLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; + } else { + adc_ctrl |= CS4245_ADC_FM_QUAD; + mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT; + } + data->cs4245_shadow[CS4245_ADC_CTRL] = adc_ctrl; + data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq; + cs4245_write_spi(chip, CS4245_ADC_CTRL); + cs4245_write_spi(chip, CS4245_MCLK_FREQ); +} + +static inline unsigned int shift_bits(unsigned int value, + unsigned int shift_from, + unsigned int shift_to, + unsigned int mask) +{ + if (shift_from < shift_to) + return (value << (shift_to - shift_from)) & mask; + else + return (value >> (shift_from - shift_to)) & mask; +} + +unsigned int adjust_dg_dac_routing(struct oxygen *chip, + unsigned int play_routing) +{ + struct dg *data = chip->model_data; + + switch (data->output_sel) { + case PLAYBACK_DST_HP: + case PLAYBACK_DST_HP_FP: + oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING, + OXYGEN_PLAY_MUTE23 | OXYGEN_PLAY_MUTE45 | + OXYGEN_PLAY_MUTE67, OXYGEN_PLAY_MUTE_MASK); + break; + case PLAYBACK_DST_MULTICH: + oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING, + OXYGEN_PLAY_MUTE01, OXYGEN_PLAY_MUTE_MASK); + break; + } + return (play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) | + shift_bits(play_routing, + OXYGEN_PLAY_DAC2_SOURCE_SHIFT, + OXYGEN_PLAY_DAC1_SOURCE_SHIFT, + OXYGEN_PLAY_DAC1_SOURCE_MASK) | + shift_bits(play_routing, + OXYGEN_PLAY_DAC1_SOURCE_SHIFT, + OXYGEN_PLAY_DAC2_SOURCE_SHIFT, + OXYGEN_PLAY_DAC2_SOURCE_MASK) | + shift_bits(play_routing, + OXYGEN_PLAY_DAC0_SOURCE_SHIFT, + OXYGEN_PLAY_DAC3_SOURCE_SHIFT, + OXYGEN_PLAY_DAC3_SOURCE_MASK); +} + +void dump_cs4245_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + struct dg *data = chip->model_data; + unsigned int addr; + + snd_iprintf(buffer, "\nCS4245:"); + cs4245_read_spi(chip, CS4245_INT_STATUS); + for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) + snd_iprintf(buffer, " %02x", data->cs4245_shadow[addr]); + snd_iprintf(buffer, "\n"); +} diff --git a/sound/pci/oxygen/xonar_dg.h b/sound/pci/oxygen/xonar_dg.h new file mode 100644 index 000000000..24d97721c --- /dev/null +++ b/sound/pci/oxygen/xonar_dg.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef XONAR_DG_H_INCLUDED +#define XONAR_DG_H_INCLUDED + +#include "oxygen.h" + +#define GPIO_MAGIC 0x0008 +#define GPIO_HP_DETECT 0x0010 +#define GPIO_INPUT_ROUTE 0x0060 +#define GPIO_HP_REAR 0x0080 +#define GPIO_OUTPUT_ENABLE 0x0100 + +#define CAPTURE_SRC_MIC 0 +#define CAPTURE_SRC_FP_MIC 1 +#define CAPTURE_SRC_LINE 2 +#define CAPTURE_SRC_AUX 3 + +#define PLAYBACK_DST_HP 0 +#define PLAYBACK_DST_HP_FP 1 +#define PLAYBACK_DST_MULTICH 2 + +enum cs4245_shadow_operation { + CS4245_SAVE_TO_SHADOW, + CS4245_LOAD_FROM_SHADOW +}; + +struct dg { + /* shadow copy of the CS4245 register space */ + unsigned char cs4245_shadow[17]; + /* output select: headphone/speakers */ + unsigned char output_sel; + /* volumes for all capture sources */ + char input_vol[4][2]; + /* input select: mic/fp mic/line/aux */ + unsigned char input_sel; +}; + +/* Xonar DG control routines */ +int cs4245_write_spi(struct oxygen *chip, u8 reg); +int cs4245_read_spi(struct oxygen *chip, u8 reg); +int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op); +void dg_init(struct oxygen *chip); +void set_cs4245_dac_params(struct oxygen *chip, + struct snd_pcm_hw_params *params); +void set_cs4245_adc_params(struct oxygen *chip, + struct snd_pcm_hw_params *params); +unsigned int adjust_dg_dac_routing(struct oxygen *chip, + unsigned int play_routing); +void dump_cs4245_registers(struct oxygen *chip, + struct snd_info_buffer *buffer); +void dg_suspend(struct oxygen *chip); +void dg_resume(struct oxygen *chip); +void dg_cleanup(struct oxygen *chip); + +extern const struct oxygen_model model_xonar_dg; + +#endif diff --git a/sound/pci/oxygen/xonar_dg_mixer.c b/sound/pci/oxygen/xonar_dg_mixer.c new file mode 100644 index 000000000..2179ff8e4 --- /dev/null +++ b/sound/pci/oxygen/xonar_dg_mixer.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Mixer controls for the Xonar DG/DGX + * + * Copyright (c) Clemens Ladisch + * Copyright (c) Roman Volkov + */ + +#include +#include +#include +#include +#include +#include +#include +#include "oxygen.h" +#include "xonar_dg.h" +#include "cs4245.h" + +/* analog output select */ + +static int output_select_apply(struct oxygen *chip) +{ + struct dg *data = chip->model_data; + + data->cs4245_shadow[CS4245_SIGNAL_SEL] &= ~CS4245_A_OUT_SEL_MASK; + if (data->output_sel == PLAYBACK_DST_HP) { + /* mute FP (aux output) amplifier, switch rear jack to CS4245 */ + oxygen_set_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR); + } else if (data->output_sel == PLAYBACK_DST_HP_FP) { + /* + * Unmute FP amplifier, switch rear jack to CS4361; + * I2S channels 2,3,4 should be inactive. + */ + oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR); + data->cs4245_shadow[CS4245_SIGNAL_SEL] |= CS4245_A_OUT_SEL_DAC; + } else { + /* + * 2.0, 4.0, 5.1: switch to CS4361, mute FP amp., + * and change playback routing. + */ + oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR); + } + return cs4245_write_spi(chip, CS4245_SIGNAL_SEL); +} + +static int output_select_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[3] = { + "Stereo Headphones", + "Stereo Headphones FP", + "Multichannel", + }; + + return snd_ctl_enum_info(info, 1, 3, names); +} + +static int output_select_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + guard(mutex)(&chip->mutex); + value->value.enumerated.item[0] = data->output_sel; + return 0; +} + +static int output_select_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + unsigned int new = value->value.enumerated.item[0]; + int changed = 0; + int ret; + + guard(mutex)(&chip->mutex); + if (data->output_sel != new) { + data->output_sel = new; + ret = output_select_apply(chip); + changed = ret >= 0 ? 1 : ret; + oxygen_update_dac_routing(chip); + } + + return changed; +} + +/* CS4245 Headphone Channels A&B Volume Control */ + +static int hp_stereo_volume_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 0; + info->value.integer.max = 255; + return 0; +} + +static int hp_stereo_volume_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + unsigned int tmp; + + guard(mutex)(&chip->mutex); + tmp = (~data->cs4245_shadow[CS4245_DAC_A_CTRL]) & 255; + val->value.integer.value[0] = tmp; + tmp = (~data->cs4245_shadow[CS4245_DAC_B_CTRL]) & 255; + val->value.integer.value[1] = tmp; + return 0; +} + +static int hp_stereo_volume_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + int ret; + int changed = 0; + long new1 = val->value.integer.value[0]; + long new2 = val->value.integer.value[1]; + + if ((new1 > 255) || (new1 < 0) || (new2 > 255) || (new2 < 0)) + return -EINVAL; + + guard(mutex)(&chip->mutex); + if ((data->cs4245_shadow[CS4245_DAC_A_CTRL] != ~new1) || + (data->cs4245_shadow[CS4245_DAC_B_CTRL] != ~new2)) { + data->cs4245_shadow[CS4245_DAC_A_CTRL] = ~new1; + data->cs4245_shadow[CS4245_DAC_B_CTRL] = ~new2; + ret = cs4245_write_spi(chip, CS4245_DAC_A_CTRL); + if (ret >= 0) + ret = cs4245_write_spi(chip, CS4245_DAC_B_CTRL); + changed = ret >= 0 ? 1 : ret; + } + + return changed; +} + +/* Headphone Mute */ + +static int hp_mute_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + guard(mutex)(&chip->mutex); + val->value.integer.value[0] = + !(data->cs4245_shadow[CS4245_DAC_CTRL_1] & CS4245_MUTE_DAC); + return 0; +} + +static int hp_mute_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + int ret; + int changed; + + if (val->value.integer.value[0] > 1) + return -EINVAL; + guard(mutex)(&chip->mutex); + data->cs4245_shadow[CS4245_DAC_CTRL_1] &= ~CS4245_MUTE_DAC; + data->cs4245_shadow[CS4245_DAC_CTRL_1] |= + (~val->value.integer.value[0] << 2) & CS4245_MUTE_DAC; + ret = cs4245_write_spi(chip, CS4245_DAC_CTRL_1); + changed = ret >= 0 ? 1 : ret; + return changed; +} + +/* capture volume for all sources */ + +static int input_volume_apply(struct oxygen *chip, char left, char right) +{ + struct dg *data = chip->model_data; + int ret; + + data->cs4245_shadow[CS4245_PGA_A_CTRL] = left; + data->cs4245_shadow[CS4245_PGA_B_CTRL] = right; + ret = cs4245_write_spi(chip, CS4245_PGA_A_CTRL); + if (ret < 0) + return ret; + return cs4245_write_spi(chip, CS4245_PGA_B_CTRL); +} + +static int input_vol_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 2 * -12; + info->value.integer.max = 2 * 12; + return 0; +} + +static int input_vol_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + unsigned int idx = ctl->private_value; + + guard(mutex)(&chip->mutex); + value->value.integer.value[0] = data->input_vol[idx][0]; + value->value.integer.value[1] = data->input_vol[idx][1]; + return 0; +} + +static int input_vol_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + unsigned int idx = ctl->private_value; + int changed = 0; + int ret = 0; + + if (value->value.integer.value[0] < 2 * -12 || + value->value.integer.value[0] > 2 * 12 || + value->value.integer.value[1] < 2 * -12 || + value->value.integer.value[1] > 2 * 12) + return -EINVAL; + guard(mutex)(&chip->mutex); + changed = data->input_vol[idx][0] != value->value.integer.value[0] || + data->input_vol[idx][1] != value->value.integer.value[1]; + if (changed) { + data->input_vol[idx][0] = value->value.integer.value[0]; + data->input_vol[idx][1] = value->value.integer.value[1]; + if (idx == data->input_sel) { + ret = input_volume_apply(chip, + data->input_vol[idx][0], + data->input_vol[idx][1]); + } + changed = ret >= 0 ? 1 : ret; + } + return changed; +} + +/* Capture Source */ + +static int input_source_apply(struct oxygen *chip) +{ + struct dg *data = chip->model_data; + + data->cs4245_shadow[CS4245_ANALOG_IN] &= ~CS4245_SEL_MASK; + if (data->input_sel == CAPTURE_SRC_FP_MIC) + data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_2; + else if (data->input_sel == CAPTURE_SRC_LINE) + data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_4; + else if (data->input_sel != CAPTURE_SRC_MIC) + data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_1; + return cs4245_write_spi(chip, CS4245_ANALOG_IN); +} + +static int input_sel_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[4] = { + "Mic", "Front Mic", "Line", "Aux" + }; + + return snd_ctl_enum_info(info, 1, 4, names); +} + +static int input_sel_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + guard(mutex)(&chip->mutex); + value->value.enumerated.item[0] = data->input_sel; + return 0; +} + +static int input_sel_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + int changed; + int ret; + + if (value->value.enumerated.item[0] > 3) + return -EINVAL; + + guard(mutex)(&chip->mutex); + changed = value->value.enumerated.item[0] != data->input_sel; + if (changed) { + data->input_sel = value->value.enumerated.item[0]; + + ret = input_source_apply(chip); + if (ret >= 0) + ret = input_volume_apply(chip, + data->input_vol[data->input_sel][0], + data->input_vol[data->input_sel][1]); + changed = ret >= 0 ? 1 : ret; + } + return changed; +} + +/* ADC high-pass filter */ + +static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { "Active", "Frozen" }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + value->value.enumerated.item[0] = + !!(data->cs4245_shadow[CS4245_ADC_CTRL] & CS4245_HPF_FREEZE); + return 0; +} + +static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + u8 reg; + int changed; + + guard(mutex)(&chip->mutex); + reg = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_HPF_FREEZE; + if (value->value.enumerated.item[0]) + reg |= CS4245_HPF_FREEZE; + changed = reg != data->cs4245_shadow[CS4245_ADC_CTRL]; + if (changed) { + data->cs4245_shadow[CS4245_ADC_CTRL] = reg; + cs4245_write_spi(chip, CS4245_ADC_CTRL); + } + return changed; +} + +#define INPUT_VOLUME(xname, index) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = input_vol_info, \ + .get = input_vol_get, \ + .put = input_vol_put, \ + .tlv = { .p = pga_db_scale }, \ + .private_value = index, \ +} +static const DECLARE_TLV_DB_MINMAX(hp_db_scale, -12550, 0); +static const DECLARE_TLV_DB_MINMAX(pga_db_scale, -1200, 1200); +static const struct snd_kcontrol_new dg_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Output Playback Enum", + .info = output_select_info, + .get = output_select_get, + .put = output_select_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = hp_stereo_volume_info, + .get = hp_stereo_volume_get, + .put = hp_stereo_volume_put, + .tlv = { .p = hp_db_scale, }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Switch", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_mono_info, + .get = hp_mute_get, + .put = hp_mute_put, + }, + INPUT_VOLUME("Mic Capture Volume", CAPTURE_SRC_MIC), + INPUT_VOLUME("Front Mic Capture Volume", CAPTURE_SRC_FP_MIC), + INPUT_VOLUME("Line Capture Volume", CAPTURE_SRC_LINE), + INPUT_VOLUME("Aux Capture Volume", CAPTURE_SRC_AUX), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = input_sel_info, + .get = input_sel_get, + .put = input_sel_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC High-pass Filter Capture Enum", + .info = hpf_info, + .get = hpf_get, + .put = hpf_put, + }, +}; + +static int dg_control_filter(struct snd_kcontrol_new *template) +{ + if (!strncmp(template->name, "Master Playback ", 16)) + return 1; + return 0; +} + +static int dg_mixer_init(struct oxygen *chip) +{ + unsigned int i; + int err; + + output_select_apply(chip); + input_source_apply(chip); + oxygen_update_dac_routing(chip); + + for (i = 0; i < ARRAY_SIZE(dg_controls); ++i) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&dg_controls[i], chip)); + if (err < 0) + return err; + } + + return 0; +} + +const struct oxygen_model model_xonar_dg = { + .longname = "C-Media Oxygen HD Audio", + .chip = "CMI8786", + .init = dg_init, + .control_filter = dg_control_filter, + .mixer_init = dg_mixer_init, + .cleanup = dg_cleanup, + .suspend = dg_suspend, + .resume = dg_resume, + .set_dac_params = set_cs4245_dac_params, + .set_adc_params = set_cs4245_adc_params, + .adjust_dac_routing = adjust_dg_dac_routing, + .dump_registers = dump_cs4245_registers, + .model_data_size = sizeof(struct dg), + .device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_1 | + CAPTURE_1_FROM_SPDIF, + .dac_channels_pcm = 6, + .dac_channels_mixer = 0, + .function_flags = OXYGEN_FUNCTION_SPI, + .dac_mclks = OXYGEN_MCLKS(256, 128, 128), + .adc_mclks = OXYGEN_MCLKS(256, 128, 128), + .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, + .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; diff --git a/sound/pci/oxygen/xonar_hdmi.c b/sound/pci/oxygen/xonar_hdmi.c new file mode 100644 index 000000000..247dcc03f --- /dev/null +++ b/sound/pci/oxygen/xonar_hdmi.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * helper functions for HDMI models (Xonar HDAV1.3/HDAV1.3 Slim) + * + * Copyright (c) Clemens Ladisch + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "xonar.h" + +static void hdmi_write_command(struct oxygen *chip, u8 command, + unsigned int count, const u8 *params) +{ + unsigned int i; + u8 checksum; + + oxygen_write_uart(chip, 0xfb); + oxygen_write_uart(chip, 0xef); + oxygen_write_uart(chip, command); + oxygen_write_uart(chip, count); + for (i = 0; i < count; ++i) + oxygen_write_uart(chip, params[i]); + checksum = 0xfb + 0xef + command + count; + for (i = 0; i < count; ++i) + checksum += params[i]; + oxygen_write_uart(chip, checksum); +} + +static void xonar_hdmi_init_commands(struct oxygen *chip, + struct xonar_hdmi *hdmi) +{ + u8 param; + + oxygen_reset_uart(chip); + param = 0; + hdmi_write_command(chip, 0x61, 1, ¶m); + param = 1; + hdmi_write_command(chip, 0x74, 1, ¶m); + hdmi_write_command(chip, 0x54, 5, hdmi->params); +} + +void xonar_hdmi_init(struct oxygen *chip, struct xonar_hdmi *hdmi) +{ + hdmi->params[1] = IEC958_AES3_CON_FS_48000; + hdmi->params[4] = 1; + xonar_hdmi_init_commands(chip, hdmi); +} + +void xonar_hdmi_cleanup(struct oxygen *chip) +{ + u8 param = 0; + + hdmi_write_command(chip, 0x74, 1, ¶m); +} + +void xonar_hdmi_resume(struct oxygen *chip, struct xonar_hdmi *hdmi) +{ + xonar_hdmi_init_commands(chip, hdmi); +} + +void xonar_hdmi_pcm_hardware_filter(unsigned int channel, + struct snd_pcm_hardware *hardware) +{ + if (channel == PCM_MULTICH) { + hardware->rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000; + hardware->rate_min = 44100; + } +} + +void xonar_set_hdmi_params(struct oxygen *chip, struct xonar_hdmi *hdmi, + struct snd_pcm_hw_params *params) +{ + hdmi->params[0] = 0; /* 1 = non-audio */ + switch (params_rate(params)) { + case 44100: + hdmi->params[1] = IEC958_AES3_CON_FS_44100; + break; + case 48000: + hdmi->params[1] = IEC958_AES3_CON_FS_48000; + break; + default: /* 96000 */ + hdmi->params[1] = IEC958_AES3_CON_FS_96000; + break; + case 192000: + hdmi->params[1] = IEC958_AES3_CON_FS_192000; + break; + } + hdmi->params[2] = params_channels(params) / 2 - 1; + if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) + hdmi->params[3] = 0; + else + hdmi->params[3] = 0xc0; + hdmi->params[4] = 1; /* ? */ + hdmi_write_command(chip, 0x54, 5, hdmi->params); +} + +void xonar_hdmi_uart_input(struct oxygen *chip) +{ + if (chip->uart_input_count >= 2 && + chip->uart_input[chip->uart_input_count - 2] == 'O' && + chip->uart_input[chip->uart_input_count - 1] == 'K') { + dev_dbg(chip->card->dev, "message from HDMI chip received:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + chip->uart_input, chip->uart_input_count); + chip->uart_input_count = 0; + } +} diff --git a/sound/pci/oxygen/xonar_lib.c b/sound/pci/oxygen/xonar_lib.c new file mode 100644 index 000000000..0edf67ce3 --- /dev/null +++ b/sound/pci/oxygen/xonar_lib.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * helper functions for Asus Xonar cards + * + * Copyright (c) Clemens Ladisch + */ + +#include +#include +#include +#include +#include +#include "xonar.h" + + +#define GPIO_CS53x1_M_MASK 0x000c +#define GPIO_CS53x1_M_SINGLE 0x0000 +#define GPIO_CS53x1_M_DOUBLE 0x0004 +#define GPIO_CS53x1_M_QUAD 0x0008 + + +void xonar_enable_output(struct oxygen *chip) +{ + struct xonar_generic *data = chip->model_data; + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, data->output_enable_bit); + msleep(data->anti_pop_delay); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, data->output_enable_bit); +} + +void xonar_disable_output(struct oxygen *chip) +{ + struct xonar_generic *data = chip->model_data; + + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, data->output_enable_bit); +} + +static void xonar_ext_power_gpio_changed(struct oxygen *chip) +{ + struct xonar_generic *data = chip->model_data; + u8 has_power; + + has_power = !!(oxygen_read8(chip, data->ext_power_reg) + & data->ext_power_bit); + if (has_power != data->has_power) { + data->has_power = has_power; + if (has_power) { + dev_notice(chip->card->dev, "power restored\n"); + } else { + dev_crit(chip->card->dev, + "Hey! Don't unplug the power cable!\n"); + /* TODO: stop PCMs */ + } + } +} + +void xonar_init_ext_power(struct oxygen *chip) +{ + struct xonar_generic *data = chip->model_data; + + oxygen_set_bits8(chip, data->ext_power_int_reg, + data->ext_power_bit); + chip->interrupt_mask |= OXYGEN_INT_GPIO; + chip->model.gpio_changed = xonar_ext_power_gpio_changed; + data->has_power = !!(oxygen_read8(chip, data->ext_power_reg) + & data->ext_power_bit); +} + +void xonar_init_cs53x1(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CS53x1_M_MASK); + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + GPIO_CS53x1_M_SINGLE, GPIO_CS53x1_M_MASK); +} + +void xonar_set_cs53x1_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + unsigned int value; + + if (params_rate(params) <= 54000) + value = GPIO_CS53x1_M_SINGLE; + else if (params_rate(params) <= 108000) + value = GPIO_CS53x1_M_DOUBLE; + else + value = GPIO_CS53x1_M_QUAD; + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + value, GPIO_CS53x1_M_MASK); +} + +int xonar_gpio_bit_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 bit = ctl->private_value; + bool invert = ctl->private_value & XONAR_GPIO_BIT_INVERT; + + value->value.integer.value[0] = + !!(oxygen_read16(chip, OXYGEN_GPIO_DATA) & bit) ^ invert; + return 0; +} + +int xonar_gpio_bit_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 bit = ctl->private_value; + bool invert = ctl->private_value & XONAR_GPIO_BIT_INVERT; + u16 old_bits, new_bits; + int changed; + + guard(spinlock_irq)(&chip->reg_lock); + old_bits = oxygen_read16(chip, OXYGEN_GPIO_DATA); + if (!!value->value.integer.value[0] ^ invert) + new_bits = old_bits | bit; + else + new_bits = old_bits & ~bit; + changed = new_bits != old_bits; + if (changed) + oxygen_write16(chip, OXYGEN_GPIO_DATA, new_bits); + return changed; +} diff --git a/sound/pci/oxygen/xonar_pcm179x.c b/sound/pci/oxygen/xonar_pcm179x.c new file mode 100644 index 000000000..837a95053 --- /dev/null +++ b/sound/pci/oxygen/xonar_pcm179x.c @@ -0,0 +1,1348 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * card driver for models with PCM1796 DACs (Xonar D2/D2X/HDAV1.3/ST/STX) + * + * Copyright (c) Clemens Ladisch + */ + +/* + * Xonar D2/D2X + * ------------ + * + * CMI8788: + * + * SPI 0 -> 1st PCM1796 (front) + * SPI 1 -> 2nd PCM1796 (surround) + * SPI 2 -> 3rd PCM1796 (center/LFE) + * SPI 4 -> 4th PCM1796 (back) + * + * GPIO 2 -> M0 of CS5381 + * GPIO 3 -> M1 of CS5381 + * GPIO 5 <- external power present (D2X only) + * GPIO 7 -> ALT + * GPIO 8 -> enable output to speakers + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * VIDEO_IN <- CD + * FMIC_IN <- mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input + */ + +/* + * Xonar HDAV1.3 (Deluxe) + * ---------------------- + * + * CMI8788: + * + * I²C <-> PCM1796 (addr 1001100) (front) + * + * GPI 0 <- external power present + * + * GPIO 0 -> enable HDMI (0) or speaker (1) output + * GPIO 2 -> M0 of CS5381 + * GPIO 3 -> M1 of CS5381 + * GPIO 4 <- daughterboard detection + * GPIO 5 <- daughterboard detection + * GPIO 6 -> ? + * GPIO 7 -> ? + * GPIO 8 -> route input jack to line-in (0) or mic-in (1) + * + * UART <-> HDMI controller + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * CD_IN <- CD + * MIC_IN <- mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input + * + * no daughterboard + * ---------------- + * + * GPIO 4 <- 1 + * + * H6 daughterboard + * ---------------- + * + * GPIO 4 <- 0 + * GPIO 5 <- 0 + * + * I²C <-> PCM1796 (addr 1001101) (surround) + * <-> PCM1796 (addr 1001110) (center/LFE) + * <-> PCM1796 (addr 1001111) (back) + * + * unknown daughterboard + * --------------------- + * + * GPIO 4 <- 0 + * GPIO 5 <- 1 + * + * I²C <-> CS4362A (addr 0011000) (surround, center/LFE, back) + */ + +/* + * Xonar Essence ST (Deluxe)/STX (II) + * ---------------------------------- + * + * CMI8788: + * + * I²C <-> PCM1792A (addr 1001100) + * <-> CS2000 (addr 1001110) (ST only) + * + * ADC1 MCLK -> REF_CLK of CS2000 (ST only) + * + * GPI 0 <- external power present (STX only) + * + * GPIO 0 -> enable output to speakers + * GPIO 1 -> route HP to front panel (0) or rear jack (1) + * GPIO 2 -> M0 of CS5381 + * GPIO 3 -> M1 of CS5381 + * GPIO 4 <- daughterboard detection + * GPIO 5 <- daughterboard detection + * GPIO 6 -> ? + * GPIO 7 -> route output to speaker jacks (0) or HP (1) + * GPIO 8 -> route input jack to line-in (0) or mic-in (1) + * + * PCM1792A: + * + * SCK <- CLK_OUT of CS2000 (ST only) + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * MIC_IN <- mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input + * + * H6 daughterboard + * ---------------- + * + * GPIO 4 <- 0 + * GPIO 5 <- 0 + */ + +/* + * Xonar Xense + * ----------- + * + * CMI8788: + * + * I²C <-> PCM1796 (addr 1001100) (front) + * <-> CS4362A (addr 0011000) (surround, center/LFE, back) + * <-> CS2000 (addr 1001110) + * + * ADC1 MCLK -> REF_CLK of CS2000 + * + * GPI 0 <- external power present + * + * GPIO 0 -> enable output + * GPIO 1 -> route HP to front panel (0) or rear jack (1) + * GPIO 2 -> M0 of CS5381 + * GPIO 3 -> M1 of CS5381 + * GPIO 4 -> enable output + * GPIO 5 -> enable output + * GPIO 6 -> ? + * GPIO 7 -> route output to HP (0) or speaker (1) + * GPIO 8 -> route input jack to mic-in (0) or line-in (1) + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * VIDEO_IN <- ? + * FMIC_IN <- mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input + * GPO 1 -> route mic-in from input jack (0) or front panel header (1) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xonar.h" +#include "cm9780.h" +#include "pcm1796.h" +#include "cs2000.h" + + +#define GPIO_D2X_EXT_POWER 0x0020 +#define GPIO_D2_ALT 0x0080 +#define GPIO_D2_OUTPUT_ENABLE 0x0100 + +#define GPI_EXT_POWER 0x01 +#define GPIO_INPUT_ROUTE 0x0100 + +#define GPIO_HDAV_OUTPUT_ENABLE 0x0001 +#define GPIO_HDAV_MAGIC 0x00c0 + +#define GPIO_DB_MASK 0x0030 +#define GPIO_DB_H6 0x0000 + +#define GPIO_ST_OUTPUT_ENABLE 0x0001 +#define GPIO_ST_HP_REAR 0x0002 +#define GPIO_ST_MAGIC 0x0040 +#define GPIO_ST_HP 0x0080 + +#define GPIO_XENSE_OUTPUT_ENABLE (0x0001 | 0x0010 | 0x0020) +#define GPIO_XENSE_SPEAKERS 0x0080 + +#define I2C_DEVICE_PCM1796(i) (0x98 + ((i) << 1)) /* 10011, ii, /W=0 */ +#define I2C_DEVICE_CS2000 0x9c /* 100111, 0, /W=0 */ + +#define PCM1796_REG_BASE 16 + + +struct xonar_pcm179x { + struct xonar_generic generic; + unsigned int dacs; + u8 pcm1796_regs[4][5]; + unsigned int current_rate; + bool h6; + bool hp_active; + s8 hp_gain_offset; + bool has_cs2000; + u8 cs2000_regs[0x1f]; + bool broken_i2c; +}; + +struct xonar_hdav { + struct xonar_pcm179x pcm179x; + struct xonar_hdmi hdmi; +}; + + +static inline void pcm1796_write_spi(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + /* maps ALSA channel pair number to SPI output */ + static const u8 codec_map[4] = { + 0, 1, 2, 4 + }; + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CLOCK_160 | + (codec_map[codec] << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI, + (reg << 8) | value); +} + +static inline void pcm1796_write_i2c(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + oxygen_write_i2c(chip, I2C_DEVICE_PCM1796(codec), reg, value); +} + +static void pcm1796_write(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + struct xonar_pcm179x *data = chip->model_data; + + if ((chip->model.function_flags & OXYGEN_FUNCTION_2WIRE_SPI_MASK) == + OXYGEN_FUNCTION_SPI) + pcm1796_write_spi(chip, codec, reg, value); + else + pcm1796_write_i2c(chip, codec, reg, value); + if ((unsigned int)(reg - PCM1796_REG_BASE) + < ARRAY_SIZE(data->pcm1796_regs[codec])) + data->pcm1796_regs[codec][reg - PCM1796_REG_BASE] = value; +} + +static void pcm1796_write_cached(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + struct xonar_pcm179x *data = chip->model_data; + + if (value != data->pcm1796_regs[codec][reg - PCM1796_REG_BASE]) + pcm1796_write(chip, codec, reg, value); +} + +static void cs2000_write(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_pcm179x *data = chip->model_data; + + oxygen_write_i2c(chip, I2C_DEVICE_CS2000, reg, value); + data->cs2000_regs[reg] = value; +} + +static void cs2000_write_cached(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_pcm179x *data = chip->model_data; + + if (value != data->cs2000_regs[reg]) + cs2000_write(chip, reg, value); +} + +static void pcm1796_registers_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + unsigned int i; + s8 gain_offset; + + msleep(1); + gain_offset = data->hp_active ? data->hp_gain_offset : 0; + for (i = 0; i < data->dacs; ++i) { + /* set ATLD before ATL/ATR */ + pcm1796_write(chip, i, 18, + data->pcm1796_regs[0][18 - PCM1796_REG_BASE]); + pcm1796_write(chip, i, 16, chip->dac_volume[i * 2] + + gain_offset); + pcm1796_write(chip, i, 17, chip->dac_volume[i * 2 + 1] + + gain_offset); + pcm1796_write(chip, i, 19, + data->pcm1796_regs[0][19 - PCM1796_REG_BASE]); + pcm1796_write(chip, i, 20, + data->pcm1796_regs[0][20 - PCM1796_REG_BASE]); + pcm1796_write(chip, i, 21, 0); + gain_offset = 0; + } +} + +static void pcm1796_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + + data->pcm1796_regs[0][18 - PCM1796_REG_BASE] = + PCM1796_FMT_24_I2S | PCM1796_ATLD; + if (!data->broken_i2c) + data->pcm1796_regs[0][18 - PCM1796_REG_BASE] |= PCM1796_MUTE; + data->pcm1796_regs[0][19 - PCM1796_REG_BASE] = + PCM1796_FLT_SHARP | PCM1796_ATS_1; + data->pcm1796_regs[0][20 - PCM1796_REG_BASE] = + data->h6 ? PCM1796_OS_64 : PCM1796_OS_128; + pcm1796_registers_init(chip); + data->current_rate = 48000; +} + +static void xonar_d2_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + + data->generic.anti_pop_delay = 300; + data->generic.output_enable_bit = GPIO_D2_OUTPUT_ENABLE; + data->dacs = 4; + + pcm1796_init(chip); + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_D2_ALT); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_D2_ALT); + + oxygen_ac97_set_bits(chip, 0, CM9780_JACK, CM9780_FMIC2MIC); + + xonar_init_cs53x1(chip); + xonar_enable_output(chip); + + snd_component_add(chip->card, "PCM1796"); + snd_component_add(chip->card, "CS5381"); +} + +static void xonar_d2x_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + + data->generic.ext_power_reg = OXYGEN_GPIO_DATA; + data->generic.ext_power_int_reg = OXYGEN_GPIO_INTERRUPT_MASK; + data->generic.ext_power_bit = GPIO_D2X_EXT_POWER; + oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_D2X_EXT_POWER); + xonar_init_ext_power(chip); + xonar_d2_init(chip); +} + +static void xonar_hdav_init(struct oxygen *chip) +{ + struct xonar_hdav *data = chip->model_data; + + oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS, + OXYGEN_2WIRE_LENGTH_8 | + OXYGEN_2WIRE_INTERRUPT_MASK | + OXYGEN_2WIRE_SPEED_STANDARD); + + data->pcm179x.generic.anti_pop_delay = 100; + data->pcm179x.generic.output_enable_bit = GPIO_HDAV_OUTPUT_ENABLE; + data->pcm179x.generic.ext_power_reg = OXYGEN_GPI_DATA; + data->pcm179x.generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK; + data->pcm179x.generic.ext_power_bit = GPI_EXT_POWER; + data->pcm179x.dacs = chip->model.dac_channels_mixer / 2; + data->pcm179x.h6 = chip->model.dac_channels_mixer > 2; + + pcm1796_init(chip); + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_HDAV_MAGIC | GPIO_INPUT_ROUTE); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_INPUT_ROUTE); + + xonar_init_cs53x1(chip); + xonar_init_ext_power(chip); + xonar_hdmi_init(chip, &data->hdmi); + xonar_enable_output(chip); + + snd_component_add(chip->card, "PCM1796"); + snd_component_add(chip->card, "CS5381"); +} + +static void xonar_st_init_i2c(struct oxygen *chip) +{ + oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS, + OXYGEN_2WIRE_LENGTH_8 | + OXYGEN_2WIRE_INTERRUPT_MASK | + OXYGEN_2WIRE_SPEED_STANDARD); +} + +static void xonar_st_init_common(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + + data->generic.output_enable_bit = GPIO_ST_OUTPUT_ENABLE; + data->dacs = chip->model.dac_channels_mixer / 2; + data->h6 = chip->model.dac_channels_mixer > 2; + data->hp_gain_offset = 2*-18; + + pcm1796_init(chip); + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR | + GPIO_ST_MAGIC | GPIO_ST_HP); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, + GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR | GPIO_ST_HP); + + xonar_init_cs53x1(chip); + xonar_enable_output(chip); + + snd_component_add(chip->card, "PCM1792A"); + snd_component_add(chip->card, "CS5381"); +} + +static void cs2000_registers_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + + cs2000_write(chip, CS2000_GLOBAL_CFG, CS2000_FREEZE); + cs2000_write(chip, CS2000_DEV_CTRL, 0); + cs2000_write(chip, CS2000_DEV_CFG_1, + CS2000_R_MOD_SEL_1 | + (0 << CS2000_R_SEL_SHIFT) | + CS2000_AUX_OUT_SRC_REF_CLK | + CS2000_EN_DEV_CFG_1); + cs2000_write(chip, CS2000_DEV_CFG_2, + (0 << CS2000_LOCK_CLK_SHIFT) | + CS2000_FRAC_N_SRC_STATIC); + cs2000_write(chip, CS2000_RATIO_0 + 0, 0x00); /* 1.0 */ + cs2000_write(chip, CS2000_RATIO_0 + 1, 0x10); + cs2000_write(chip, CS2000_RATIO_0 + 2, 0x00); + cs2000_write(chip, CS2000_RATIO_0 + 3, 0x00); + cs2000_write(chip, CS2000_FUN_CFG_1, + data->cs2000_regs[CS2000_FUN_CFG_1]); + cs2000_write(chip, CS2000_FUN_CFG_2, 0); + cs2000_write(chip, CS2000_GLOBAL_CFG, CS2000_EN_DEV_CFG_2); + msleep(3); /* PLL lock delay */ +} + +static void xonar_st_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + + data->generic.anti_pop_delay = 100; + data->h6 = chip->model.dac_channels_mixer > 2; + data->has_cs2000 = true; + data->cs2000_regs[CS2000_FUN_CFG_1] = CS2000_REF_CLK_DIV_1; + data->broken_i2c = true; + + oxygen_write16(chip, OXYGEN_I2S_A_FORMAT, + OXYGEN_RATE_48000 | + OXYGEN_I2S_FORMAT_I2S | + OXYGEN_I2S_MCLK(data->h6 ? MCLK_256 : MCLK_512) | + OXYGEN_I2S_BITS_16 | + OXYGEN_I2S_MASTER | + OXYGEN_I2S_BCLK_64); + + xonar_st_init_i2c(chip); + cs2000_registers_init(chip); + xonar_st_init_common(chip); + + snd_component_add(chip->card, "CS2000"); +} + +static void xonar_stx_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + + xonar_st_init_i2c(chip); + data->generic.anti_pop_delay = 800; + data->generic.ext_power_reg = OXYGEN_GPI_DATA; + data->generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK; + data->generic.ext_power_bit = GPI_EXT_POWER; + xonar_init_ext_power(chip); + xonar_st_init_common(chip); +} + +static void xonar_xense_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + + data->generic.ext_power_reg = OXYGEN_GPI_DATA; + data->generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK; + data->generic.ext_power_bit = GPI_EXT_POWER; + xonar_init_ext_power(chip); + + data->generic.anti_pop_delay = 100; + data->has_cs2000 = true; + data->cs2000_regs[CS2000_FUN_CFG_1] = CS2000_REF_CLK_DIV_1; + + oxygen_write16(chip, OXYGEN_I2S_A_FORMAT, + OXYGEN_RATE_48000 | + OXYGEN_I2S_FORMAT_I2S | + OXYGEN_I2S_MCLK(MCLK_512) | + OXYGEN_I2S_BITS_16 | + OXYGEN_I2S_MASTER | + OXYGEN_I2S_BCLK_64); + + xonar_st_init_i2c(chip); + cs2000_registers_init(chip); + + data->generic.output_enable_bit = GPIO_XENSE_OUTPUT_ENABLE; + data->dacs = 1; + data->hp_gain_offset = 2*-18; + + pcm1796_init(chip); + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR | + GPIO_ST_MAGIC | GPIO_XENSE_SPEAKERS); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, + GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR | + GPIO_XENSE_SPEAKERS); + + xonar_init_cs53x1(chip); + xonar_enable_output(chip); + + snd_component_add(chip->card, "PCM1796"); + snd_component_add(chip->card, "CS5381"); + snd_component_add(chip->card, "CS2000"); +} + +static void xonar_d2_cleanup(struct oxygen *chip) +{ + xonar_disable_output(chip); +} + +static void xonar_hdav_cleanup(struct oxygen *chip) +{ + xonar_hdmi_cleanup(chip); + xonar_disable_output(chip); + msleep(2); +} + +static void xonar_st_cleanup(struct oxygen *chip) +{ + xonar_disable_output(chip); +} + +static void xonar_d2_suspend(struct oxygen *chip) +{ + xonar_d2_cleanup(chip); +} + +static void xonar_hdav_suspend(struct oxygen *chip) +{ + xonar_hdav_cleanup(chip); +} + +static void xonar_st_suspend(struct oxygen *chip) +{ + xonar_st_cleanup(chip); +} + +static void xonar_d2_resume(struct oxygen *chip) +{ + pcm1796_registers_init(chip); + xonar_enable_output(chip); +} + +static void xonar_hdav_resume(struct oxygen *chip) +{ + struct xonar_hdav *data = chip->model_data; + + pcm1796_registers_init(chip); + xonar_hdmi_resume(chip, &data->hdmi); + xonar_enable_output(chip); +} + +static void xonar_stx_resume(struct oxygen *chip) +{ + pcm1796_registers_init(chip); + xonar_enable_output(chip); +} + +static void xonar_st_resume(struct oxygen *chip) +{ + cs2000_registers_init(chip); + xonar_stx_resume(chip); +} + +static void update_pcm1796_oversampling(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + unsigned int i; + u8 reg; + + if (data->current_rate <= 48000 && !data->h6) + reg = PCM1796_OS_128; + else + reg = PCM1796_OS_64; + for (i = 0; i < data->dacs; ++i) + pcm1796_write_cached(chip, i, 20, reg); +} + +static void update_pcm1796_deemph(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + unsigned int i; + u8 reg; + + reg = data->pcm1796_regs[0][18 - PCM1796_REG_BASE] & ~PCM1796_DMF_MASK; + if (data->current_rate == 48000) + reg |= PCM1796_DMF_48; + else if (data->current_rate == 44100) + reg |= PCM1796_DMF_441; + else if (data->current_rate == 32000) + reg |= PCM1796_DMF_32; + for (i = 0; i < data->dacs; ++i) + pcm1796_write_cached(chip, i, 18, reg); +} + +static void set_pcm1796_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct xonar_pcm179x *data = chip->model_data; + + msleep(1); + data->current_rate = params_rate(params); + update_pcm1796_oversampling(chip); + update_pcm1796_deemph(chip); +} + +static void update_pcm1796_volume(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + unsigned int i; + s8 gain_offset; + + gain_offset = data->hp_active ? data->hp_gain_offset : 0; + for (i = 0; i < data->dacs; ++i) { + pcm1796_write_cached(chip, i, 16, chip->dac_volume[i * 2] + + gain_offset); + pcm1796_write_cached(chip, i, 17, chip->dac_volume[i * 2 + 1] + + gain_offset); + gain_offset = 0; + } +} + +static void update_pcm1796_mute(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + unsigned int i; + u8 value; + + value = data->pcm1796_regs[0][18 - PCM1796_REG_BASE]; + if (chip->dac_mute) + value |= PCM1796_MUTE; + else + value &= ~PCM1796_MUTE; + for (i = 0; i < data->dacs; ++i) + pcm1796_write_cached(chip, i, 18, value); +} + +static void update_cs2000_rate(struct oxygen *chip, unsigned int rate) +{ + struct xonar_pcm179x *data = chip->model_data; + u8 rate_mclk, reg; + + switch (rate) { + case 32000: + case 64000: + rate_mclk = OXYGEN_RATE_32000; + break; + case 44100: + case 88200: + case 176400: + rate_mclk = OXYGEN_RATE_44100; + break; + default: + case 48000: + case 96000: + case 192000: + rate_mclk = OXYGEN_RATE_48000; + break; + } + + if (rate <= 96000 && (rate > 48000 || data->h6)) { + rate_mclk |= OXYGEN_I2S_MCLK(MCLK_256); + reg = CS2000_REF_CLK_DIV_1; + } else { + rate_mclk |= OXYGEN_I2S_MCLK(MCLK_512); + reg = CS2000_REF_CLK_DIV_2; + } + + oxygen_write16_masked(chip, OXYGEN_I2S_A_FORMAT, rate_mclk, + OXYGEN_I2S_RATE_MASK | OXYGEN_I2S_MCLK_MASK); + cs2000_write_cached(chip, CS2000_FUN_CFG_1, reg); + msleep(3); /* PLL lock delay */ +} + +static void set_st_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + update_cs2000_rate(chip, params_rate(params)); + set_pcm1796_params(chip, params); +} + +static void set_hdav_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct xonar_hdav *data = chip->model_data; + + set_pcm1796_params(chip, params); + xonar_set_hdmi_params(chip, &data->hdmi, params); +} + +static const struct snd_kcontrol_new alt_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Loopback Switch", + .info = snd_ctl_boolean_mono_info, + .get = xonar_gpio_bit_switch_get, + .put = xonar_gpio_bit_switch_put, + .private_value = GPIO_D2_ALT, +}; + +static int rolloff_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { + "Sharp Roll-off", "Slow Roll-off" + }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int rolloff_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_pcm179x *data = chip->model_data; + + value->value.enumerated.item[0] = + (data->pcm1796_regs[0][19 - PCM1796_REG_BASE] & + PCM1796_FLT_MASK) != PCM1796_FLT_SHARP; + return 0; +} + +static int rolloff_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_pcm179x *data = chip->model_data; + unsigned int i; + int changed; + u8 reg; + + guard(mutex)(&chip->mutex); + reg = data->pcm1796_regs[0][19 - PCM1796_REG_BASE]; + reg &= ~PCM1796_FLT_MASK; + if (!value->value.enumerated.item[0]) + reg |= PCM1796_FLT_SHARP; + else + reg |= PCM1796_FLT_SLOW; + changed = reg != data->pcm1796_regs[0][19 - PCM1796_REG_BASE]; + if (changed) { + for (i = 0; i < data->dacs; ++i) + pcm1796_write(chip, i, 19, reg); + } + return changed; +} + +static const struct snd_kcontrol_new rolloff_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Filter Playback Enum", + .info = rolloff_info, + .get = rolloff_get, + .put = rolloff_put, +}; + +static int deemph_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_pcm179x *data = chip->model_data; + + value->value.integer.value[0] = + !!(data->pcm1796_regs[0][18 - PCM1796_REG_BASE] & PCM1796_DME); + return 0; +} + +static int deemph_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_pcm179x *data = chip->model_data; + unsigned int i; + int changed; + u8 reg; + + guard(mutex)(&chip->mutex); + reg = data->pcm1796_regs[0][18 - PCM1796_REG_BASE]; + if (!value->value.integer.value[0]) + reg &= ~PCM1796_DME; + else + reg |= PCM1796_DME; + changed = reg != data->pcm1796_regs[0][18 - PCM1796_REG_BASE]; + if (changed) { + for (i = 0; i < data->dacs; ++i) + pcm1796_write(chip, i, 18, reg); + } + return changed; +} + +static const struct snd_kcontrol_new deemph_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "De-emphasis Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = deemph_get, + .put = deemph_put, +}; + +static const struct snd_kcontrol_new hdav_hdmi_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = xonar_gpio_bit_switch_get, + .put = xonar_gpio_bit_switch_put, + .private_value = GPIO_HDAV_OUTPUT_ENABLE | XONAR_GPIO_BIT_INVERT, +}; + +static int st_output_switch_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[3] = { + "Speakers", "Headphones", "FP Headphones" + }; + + return snd_ctl_enum_info(info, 1, 3, names); +} + +static int st_output_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 gpio; + + gpio = oxygen_read16(chip, OXYGEN_GPIO_DATA); + if (!(gpio & GPIO_ST_HP)) + value->value.enumerated.item[0] = 0; + else if (gpio & GPIO_ST_HP_REAR) + value->value.enumerated.item[0] = 1; + else + value->value.enumerated.item[0] = 2; + return 0; +} + + +static int st_output_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_pcm179x *data = chip->model_data; + u16 gpio_old, gpio; + + guard(mutex)(&chip->mutex); + gpio_old = oxygen_read16(chip, OXYGEN_GPIO_DATA); + gpio = gpio_old; + switch (value->value.enumerated.item[0]) { + case 0: + gpio &= ~(GPIO_ST_HP | GPIO_ST_HP_REAR); + break; + case 1: + gpio |= GPIO_ST_HP | GPIO_ST_HP_REAR; + break; + case 2: + gpio = (gpio | GPIO_ST_HP) & ~GPIO_ST_HP_REAR; + break; + } + oxygen_write16(chip, OXYGEN_GPIO_DATA, gpio); + data->hp_active = gpio & GPIO_ST_HP; + update_pcm1796_volume(chip); + return gpio != gpio_old; +} + +static int st_hp_volume_offset_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[4] = { + "< 32 ohms", "32-64 ohms", "64-300 ohms", "300-600 ohms" + }; + + return snd_ctl_enum_info(info, 1, 4, names); +} + +static int st_hp_volume_offset_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_pcm179x *data = chip->model_data; + + guard(mutex)(&chip->mutex); + if (data->hp_gain_offset < 2*-12) + value->value.enumerated.item[0] = 0; + else if (data->hp_gain_offset < 2*-6) + value->value.enumerated.item[0] = 1; + else if (data->hp_gain_offset < 0) + value->value.enumerated.item[0] = 2; + else + value->value.enumerated.item[0] = 3; + return 0; +} + + +static int st_hp_volume_offset_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + static const s8 offsets[] = { 2*-18, 2*-12, 2*-6, 0 }; + struct oxygen *chip = ctl->private_data; + struct xonar_pcm179x *data = chip->model_data; + s8 offset; + int changed; + + if (value->value.enumerated.item[0] > 3) + return -EINVAL; + offset = offsets[value->value.enumerated.item[0]]; + guard(mutex)(&chip->mutex); + changed = offset != data->hp_gain_offset; + if (changed) { + data->hp_gain_offset = offset; + update_pcm1796_volume(chip); + } + return changed; +} + +static const struct snd_kcontrol_new st_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Output", + .info = st_output_switch_info, + .get = st_output_switch_get, + .put = st_output_switch_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphones Impedance Playback Enum", + .info = st_hp_volume_offset_info, + .get = st_hp_volume_offset_get, + .put = st_hp_volume_offset_put, + }, +}; + +static int xense_output_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 gpio; + + gpio = oxygen_read16(chip, OXYGEN_GPIO_DATA); + if (gpio & GPIO_XENSE_SPEAKERS) + value->value.enumerated.item[0] = 0; + else if (!(gpio & GPIO_XENSE_SPEAKERS) && (gpio & GPIO_ST_HP_REAR)) + value->value.enumerated.item[0] = 1; + else + value->value.enumerated.item[0] = 2; + return 0; +} + +static int xense_output_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_pcm179x *data = chip->model_data; + u16 gpio_old, gpio; + + guard(mutex)(&chip->mutex); + gpio_old = oxygen_read16(chip, OXYGEN_GPIO_DATA); + gpio = gpio_old; + switch (value->value.enumerated.item[0]) { + case 0: + gpio |= GPIO_XENSE_SPEAKERS | GPIO_ST_HP_REAR; + break; + case 1: + gpio = (gpio | GPIO_ST_HP_REAR) & ~GPIO_XENSE_SPEAKERS; + break; + case 2: + gpio &= ~(GPIO_XENSE_SPEAKERS | GPIO_ST_HP_REAR); + break; + } + oxygen_write16(chip, OXYGEN_GPIO_DATA, gpio); + data->hp_active = !(gpio & GPIO_XENSE_SPEAKERS); + update_pcm1796_volume(chip); + return gpio != gpio_old; +} + +static const struct snd_kcontrol_new xense_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Output", + .info = st_output_switch_info, + .get = xense_output_switch_get, + .put = xense_output_switch_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphones Impedance Playback Enum", + .info = st_hp_volume_offset_info, + .get = st_hp_volume_offset_get, + .put = st_hp_volume_offset_put, + }, +}; + +static void xonar_line_mic_ac97_switch(struct oxygen *chip, + unsigned int reg, unsigned int mute) +{ + if (reg == AC97_LINE) { + guard(spinlock_irq)(&chip->reg_lock); + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + mute ? GPIO_INPUT_ROUTE : 0, + GPIO_INPUT_ROUTE); + } +} + +static const DECLARE_TLV_DB_SCALE(pcm1796_db_scale, -6000, 50, 0); + +static int xonar_d2_control_filter(struct snd_kcontrol_new *template) +{ + if (!strncmp(template->name, "CD Capture ", 11)) + /* CD in is actually connected to the video in pin */ + template->private_value ^= AC97_CD ^ AC97_VIDEO; + return 0; +} + +static int xonar_st_h6_control_filter(struct snd_kcontrol_new *template) +{ + if (!strncmp(template->name, "Master Playback ", 16)) + /* no volume/mute, as I²C to the third DAC does not work */ + return 1; + return 0; +} + +static int add_pcm1796_controls(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + int err; + + if (!data->broken_i2c) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&rolloff_control, chip)); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, + snd_ctl_new1(&deemph_control, chip)); + if (err < 0) + return err; + } + return 0; +} + +static int xonar_d2_mixer_init(struct oxygen *chip) +{ + int err; + + err = snd_ctl_add(chip->card, snd_ctl_new1(&alt_switch, chip)); + if (err < 0) + return err; + err = add_pcm1796_controls(chip); + if (err < 0) + return err; + return 0; +} + +static int xonar_hdav_mixer_init(struct oxygen *chip) +{ + int err; + + err = snd_ctl_add(chip->card, snd_ctl_new1(&hdav_hdmi_control, chip)); + if (err < 0) + return err; + err = add_pcm1796_controls(chip); + if (err < 0) + return err; + return 0; +} + +static int xonar_st_mixer_init(struct oxygen *chip) +{ + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(st_controls); ++i) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&st_controls[i], chip)); + if (err < 0) + return err; + } + err = add_pcm1796_controls(chip); + if (err < 0) + return err; + return 0; +} + +static int xonar_xense_mixer_init(struct oxygen *chip) +{ + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(xense_controls); ++i) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&xense_controls[i], chip)); + if (err < 0) + return err; + } + err = add_pcm1796_controls(chip); + if (err < 0) + return err; + return 0; +} + +static void dump_pcm1796_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + struct xonar_pcm179x *data = chip->model_data; + unsigned int dac, i; + + for (dac = 0; dac < data->dacs; ++dac) { + snd_iprintf(buffer, "\nPCM1796 %u:", dac + 1); + for (i = 0; i < 5; ++i) + snd_iprintf(buffer, " %02x", + data->pcm1796_regs[dac][i]); + } + snd_iprintf(buffer, "\n"); +} + +static void dump_cs2000_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + struct xonar_pcm179x *data = chip->model_data; + unsigned int i; + + if (data->has_cs2000) { + snd_iprintf(buffer, "\nCS2000:\n00: "); + for (i = 1; i < 0x10; ++i) + snd_iprintf(buffer, " %02x", data->cs2000_regs[i]); + snd_iprintf(buffer, "\n10:"); + for (i = 0x10; i < 0x1f; ++i) + snd_iprintf(buffer, " %02x", data->cs2000_regs[i]); + snd_iprintf(buffer, "\n"); + } +} + +static void dump_st_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + dump_pcm1796_registers(chip, buffer); + dump_cs2000_registers(chip, buffer); +} + +static const struct oxygen_model model_xonar_d2 = { + .longname = "Asus Virtuoso 200", + .chip = "AV200", + .init = xonar_d2_init, + .control_filter = xonar_d2_control_filter, + .mixer_init = xonar_d2_mixer_init, + .cleanup = xonar_d2_cleanup, + .suspend = xonar_d2_suspend, + .resume = xonar_d2_resume, + .set_dac_params = set_pcm1796_params, + .set_adc_params = xonar_set_cs53x1_params, + .update_dac_volume = update_pcm1796_volume, + .update_dac_mute = update_pcm1796_mute, + .dump_registers = dump_pcm1796_registers, + .dac_tlv = pcm1796_db_scale, + .model_data_size = sizeof(struct xonar_pcm179x), + .device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_2 | + CAPTURE_1_FROM_SPDIF | + MIDI_OUTPUT | + MIDI_INPUT | + AC97_CD_INPUT, + .dac_channels_pcm = 8, + .dac_channels_mixer = 8, + .dac_volume_min = 255 - 2*60, + .dac_volume_max = 255, + .misc_flags = OXYGEN_MISC_MIDI, + .function_flags = OXYGEN_FUNCTION_SPI | + OXYGEN_FUNCTION_ENABLE_SPI_4_5, + .dac_mclks = OXYGEN_MCLKS(512, 128, 128), + .adc_mclks = OXYGEN_MCLKS(256, 128, 128), + .dac_i2s_format = OXYGEN_I2S_FORMAT_I2S, + .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; + +static const struct oxygen_model model_xonar_hdav = { + .longname = "Asus Virtuoso 200", + .chip = "AV200", + .init = xonar_hdav_init, + .mixer_init = xonar_hdav_mixer_init, + .cleanup = xonar_hdav_cleanup, + .suspend = xonar_hdav_suspend, + .resume = xonar_hdav_resume, + .pcm_hardware_filter = xonar_hdmi_pcm_hardware_filter, + .set_dac_params = set_hdav_params, + .set_adc_params = xonar_set_cs53x1_params, + .update_dac_volume = update_pcm1796_volume, + .update_dac_mute = update_pcm1796_mute, + .uart_input = xonar_hdmi_uart_input, + .ac97_switch = xonar_line_mic_ac97_switch, + .dump_registers = dump_pcm1796_registers, + .dac_tlv = pcm1796_db_scale, + .model_data_size = sizeof(struct xonar_hdav), + .device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_2 | + CAPTURE_1_FROM_SPDIF, + .dac_channels_pcm = 8, + .dac_channels_mixer = 2, + .dac_volume_min = 255 - 2*60, + .dac_volume_max = 255, + .misc_flags = OXYGEN_MISC_MIDI, + .function_flags = OXYGEN_FUNCTION_2WIRE, + .dac_mclks = OXYGEN_MCLKS(512, 128, 128), + .adc_mclks = OXYGEN_MCLKS(256, 128, 128), + .dac_i2s_format = OXYGEN_I2S_FORMAT_I2S, + .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; + +static const struct oxygen_model model_xonar_st = { + .longname = "Asus Virtuoso 100", + .chip = "AV200", + .init = xonar_st_init, + .mixer_init = xonar_st_mixer_init, + .cleanup = xonar_st_cleanup, + .suspend = xonar_st_suspend, + .resume = xonar_st_resume, + .set_dac_params = set_st_params, + .set_adc_params = xonar_set_cs53x1_params, + .update_dac_volume = update_pcm1796_volume, + .update_dac_mute = update_pcm1796_mute, + .ac97_switch = xonar_line_mic_ac97_switch, + .dump_registers = dump_st_registers, + .dac_tlv = pcm1796_db_scale, + .model_data_size = sizeof(struct xonar_pcm179x), + .device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_2 | + CAPTURE_1_FROM_SPDIF | + AC97_FMIC_SWITCH, + .dac_channels_pcm = 2, + .dac_channels_mixer = 2, + .dac_volume_min = 255 - 2*60, + .dac_volume_max = 255, + .function_flags = OXYGEN_FUNCTION_2WIRE, + .dac_mclks = OXYGEN_MCLKS(512, 128, 128), + .adc_mclks = OXYGEN_MCLKS(256, 128, 128), + .dac_i2s_format = OXYGEN_I2S_FORMAT_I2S, + .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; + +int get_xonar_pcm179x_model(struct oxygen *chip, + const struct pci_device_id *id) +{ + switch (id->subdevice) { + case 0x8269: + chip->model = model_xonar_d2; + chip->model.shortname = "Xonar D2"; + break; + case 0x82b7: + chip->model = model_xonar_d2; + chip->model.shortname = "Xonar D2X"; + chip->model.init = xonar_d2x_init; + break; + case 0x8314: + chip->model = model_xonar_hdav; + oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK); + switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) { + default: + chip->model.shortname = "Xonar HDAV1.3"; + break; + case GPIO_DB_H6: + chip->model.shortname = "Xonar HDAV1.3+H6"; + chip->model.dac_channels_mixer = 8; + chip->model.dac_mclks = OXYGEN_MCLKS(256, 128, 128); + break; + } + break; + case 0x835d: + chip->model = model_xonar_st; + oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK); + switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) { + default: + chip->model.shortname = "Xonar ST"; + break; + case GPIO_DB_H6: + chip->model.shortname = "Xonar ST+H6"; + chip->model.control_filter = xonar_st_h6_control_filter; + chip->model.dac_channels_pcm = 8; + chip->model.dac_channels_mixer = 8; + chip->model.dac_volume_min = 255; + chip->model.dac_mclks = OXYGEN_MCLKS(256, 128, 128); + break; + } + break; + case 0x835c: + chip->model = model_xonar_st; + chip->model.shortname = "Xonar STX"; + chip->model.init = xonar_stx_init; + chip->model.resume = xonar_stx_resume; + chip->model.set_dac_params = set_pcm1796_params; + break; + case 0x85f4: + chip->model = model_xonar_st; + oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK); + switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) { + default: + chip->model.shortname = "Xonar STX II"; + break; + case GPIO_DB_H6: + chip->model.shortname = "Xonar STX II+H6"; + chip->model.dac_channels_pcm = 8; + chip->model.dac_channels_mixer = 8; + chip->model.dac_mclks = OXYGEN_MCLKS(256, 128, 128); + break; + } + chip->model.init = xonar_stx_init; + chip->model.resume = xonar_stx_resume; + chip->model.set_dac_params = set_pcm1796_params; + break; + case 0x8428: + chip->model = model_xonar_st; + chip->model.shortname = "Xonar Xense"; + chip->model.chip = "AV100"; + chip->model.init = xonar_xense_init; + chip->model.mixer_init = xonar_xense_mixer_init; + break; + default: + return -EINVAL; + } + return 0; +} diff --git a/sound/pci/oxygen/xonar_wm87x6.c b/sound/pci/oxygen/xonar_wm87x6.c new file mode 100644 index 000000000..7d92e6e20 --- /dev/null +++ b/sound/pci/oxygen/xonar_wm87x6.c @@ -0,0 +1,1322 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * card driver for models with WM8776/WM8766 DACs (Xonar DS/HDAV1.3 Slim) + * + * Copyright (c) Clemens Ladisch + */ + +/* + * Xonar DS + * -------- + * + * CMI8788: + * + * SPI 0 -> WM8766 (surround, center/LFE, back) + * SPI 1 -> WM8776 (front, input) + * + * GPIO 4 <- headphone detect, 0 = plugged + * GPIO 6 -> route input jack to mic-in (0) or line-in (1) + * GPIO 7 -> enable output to front L/R speaker channels + * GPIO 8 -> enable output to other speaker channels and front panel headphone + * + * WM8776: + * + * input 1 <- line + * input 2 <- mic + * input 3 <- front mic + * input 4 <- aux + */ + +/* + * Xonar HDAV1.3 Slim + * ------------------ + * + * CMI8788: + * + * I²C <-> WM8776 (addr 0011010) + * + * GPIO 0 -> disable HDMI output + * GPIO 1 -> enable HP output + * GPIO 6 -> firmware EEPROM I²C clock + * GPIO 7 <-> firmware EEPROM I²C data + * + * UART <-> HDMI controller + * + * WM8776: + * + * input 1 <- mic + * input 2 <- aux + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xonar.h" +#include "wm8776.h" +#include "wm8766.h" + +#define GPIO_DS_HP_DETECT 0x0010 +#define GPIO_DS_INPUT_ROUTE 0x0040 +#define GPIO_DS_OUTPUT_FRONTLR 0x0080 +#define GPIO_DS_OUTPUT_ENABLE 0x0100 + +#define GPIO_SLIM_HDMI_DISABLE 0x0001 +#define GPIO_SLIM_OUTPUT_ENABLE 0x0002 +#define GPIO_SLIM_FIRMWARE_CLK 0x0040 +#define GPIO_SLIM_FIRMWARE_DATA 0x0080 + +#define I2C_DEVICE_WM8776 0x34 /* 001101, 0, /W=0 */ + +#define LC_CONTROL_LIMITER 0x40000000 +#define LC_CONTROL_ALC 0x20000000 + +struct xonar_wm87x6 { + struct xonar_generic generic; + u16 wm8776_regs[0x17]; + u16 wm8766_regs[0x10]; + struct snd_kcontrol *line_adcmux_control; + struct snd_kcontrol *mic_adcmux_control; + struct snd_kcontrol *lc_controls[13]; + struct snd_jack *hp_jack; + struct xonar_hdmi hdmi; +}; + +static void wm8776_write_spi(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CLOCK_160 | + (1 << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_LO, + (reg << 9) | value); +} + +static void wm8776_write_i2c(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + oxygen_write_i2c(chip, I2C_DEVICE_WM8776, + (reg << 1) | (value >> 8), value); +} + +static void wm8776_write(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + struct xonar_wm87x6 *data = chip->model_data; + + if ((chip->model.function_flags & OXYGEN_FUNCTION_2WIRE_SPI_MASK) == + OXYGEN_FUNCTION_SPI) + wm8776_write_spi(chip, reg, value); + else + wm8776_write_i2c(chip, reg, value); + if (reg < ARRAY_SIZE(data->wm8776_regs)) { + /* reg >= WM8776_HPLVOL is always true */ + if (reg <= WM8776_DACMASTER) + value &= ~WM8776_UPDATE; + data->wm8776_regs[reg] = value; + } +} + +static void wm8776_write_cached(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + struct xonar_wm87x6 *data = chip->model_data; + + if (reg >= ARRAY_SIZE(data->wm8776_regs) || + value != data->wm8776_regs[reg]) + wm8776_write(chip, reg, value); +} + +static void wm8766_write(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + struct xonar_wm87x6 *data = chip->model_data; + + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CLOCK_160 | + (0 << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_LO, + (reg << 9) | value); + if (reg < ARRAY_SIZE(data->wm8766_regs)) { + /* reg >= WM8766_LDA1 is always true */ + if (reg <= WM8766_RDA1 || + (reg >= WM8766_LDA2 && reg <= WM8766_MASTDA)) + value &= ~WM8766_UPDATE; + data->wm8766_regs[reg] = value; + } +} + +static void wm8766_write_cached(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + struct xonar_wm87x6 *data = chip->model_data; + + if (reg >= ARRAY_SIZE(data->wm8766_regs) || + value != data->wm8766_regs[reg]) + wm8766_write(chip, reg, value); +} + +static void wm8776_registers_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + wm8776_write(chip, WM8776_RESET, 0); + wm8776_write(chip, WM8776_PHASESWAP, WM8776_PH_MASK); + wm8776_write(chip, WM8776_DACCTRL1, WM8776_DZCEN | + WM8776_PL_LEFT_LEFT | WM8776_PL_RIGHT_RIGHT); + wm8776_write(chip, WM8776_DACMUTE, chip->dac_mute ? WM8776_DMUTE : 0); + wm8776_write(chip, WM8776_DACIFCTRL, + WM8776_DACFMT_LJUST | WM8776_DACWL_24); + wm8776_write(chip, WM8776_ADCIFCTRL, + data->wm8776_regs[WM8776_ADCIFCTRL]); + wm8776_write(chip, WM8776_MSTRCTRL, data->wm8776_regs[WM8776_MSTRCTRL]); + wm8776_write(chip, WM8776_PWRDOWN, data->wm8776_regs[WM8776_PWRDOWN]); + wm8776_write(chip, WM8776_HPLVOL, data->wm8776_regs[WM8776_HPLVOL]); + wm8776_write(chip, WM8776_HPRVOL, data->wm8776_regs[WM8776_HPRVOL] | + WM8776_UPDATE); + wm8776_write(chip, WM8776_ADCLVOL, data->wm8776_regs[WM8776_ADCLVOL]); + wm8776_write(chip, WM8776_ADCRVOL, data->wm8776_regs[WM8776_ADCRVOL]); + wm8776_write(chip, WM8776_ADCMUX, data->wm8776_regs[WM8776_ADCMUX]); + wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0]); + wm8776_write(chip, WM8776_DACRVOL, chip->dac_volume[1] | WM8776_UPDATE); +} + +static void wm8766_registers_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + wm8766_write(chip, WM8766_RESET, 0); + wm8766_write(chip, WM8766_DAC_CTRL, data->wm8766_regs[WM8766_DAC_CTRL]); + wm8766_write(chip, WM8766_INT_CTRL, WM8766_FMT_LJUST | WM8766_IWL_24); + wm8766_write(chip, WM8766_DAC_CTRL2, + WM8766_ZCD | (chip->dac_mute ? WM8766_DMUTE_MASK : 0)); + wm8766_write(chip, WM8766_LDA1, chip->dac_volume[2]); + wm8766_write(chip, WM8766_RDA1, chip->dac_volume[3]); + wm8766_write(chip, WM8766_LDA2, chip->dac_volume[4]); + wm8766_write(chip, WM8766_RDA2, chip->dac_volume[5]); + wm8766_write(chip, WM8766_LDA3, chip->dac_volume[6]); + wm8766_write(chip, WM8766_RDA3, chip->dac_volume[7] | WM8766_UPDATE); +} + +static void wm8776_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + data->wm8776_regs[WM8776_HPLVOL] = (0x79 - 60) | WM8776_HPZCEN; + data->wm8776_regs[WM8776_HPRVOL] = (0x79 - 60) | WM8776_HPZCEN; + data->wm8776_regs[WM8776_ADCIFCTRL] = + WM8776_ADCFMT_LJUST | WM8776_ADCWL_24 | WM8776_ADCMCLK; + data->wm8776_regs[WM8776_MSTRCTRL] = + WM8776_ADCRATE_256 | WM8776_DACRATE_256; + data->wm8776_regs[WM8776_PWRDOWN] = WM8776_HPPD; + data->wm8776_regs[WM8776_ADCLVOL] = 0xa5 | WM8776_ZCA; + data->wm8776_regs[WM8776_ADCRVOL] = 0xa5 | WM8776_ZCA; + data->wm8776_regs[WM8776_ADCMUX] = 0x001; + wm8776_registers_init(chip); +} + +static void wm8766_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + data->wm8766_regs[WM8766_DAC_CTRL] = + WM8766_PL_LEFT_LEFT | WM8766_PL_RIGHT_RIGHT; + wm8766_registers_init(chip); +} + +static void xonar_ds_handle_hp_jack(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + bool hp_plugged; + unsigned int reg; + + guard(mutex)(&chip->mutex); + + hp_plugged = !(oxygen_read16(chip, OXYGEN_GPIO_DATA) & + GPIO_DS_HP_DETECT); + + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + hp_plugged ? 0 : GPIO_DS_OUTPUT_FRONTLR, + GPIO_DS_OUTPUT_FRONTLR); + + reg = data->wm8766_regs[WM8766_DAC_CTRL] & ~WM8766_MUTEALL; + if (hp_plugged) + reg |= WM8766_MUTEALL; + wm8766_write_cached(chip, WM8766_DAC_CTRL, reg); + + snd_jack_report(data->hp_jack, hp_plugged ? SND_JACK_HEADPHONE : 0); +} + +static void xonar_ds_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + data->generic.anti_pop_delay = 300; + data->generic.output_enable_bit = GPIO_DS_OUTPUT_ENABLE; + + wm8776_init(chip); + wm8766_init(chip); + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_DS_INPUT_ROUTE | GPIO_DS_OUTPUT_FRONTLR); + oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_DS_HP_DETECT); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_DS_INPUT_ROUTE); + oxygen_set_bits16(chip, OXYGEN_GPIO_INTERRUPT_MASK, GPIO_DS_HP_DETECT); + chip->interrupt_mask |= OXYGEN_INT_GPIO; + + xonar_enable_output(chip); + + snd_jack_new(chip->card, "Headphone", + SND_JACK_HEADPHONE, &data->hp_jack, false, false); + xonar_ds_handle_hp_jack(chip); + + snd_component_add(chip->card, "WM8776"); + snd_component_add(chip->card, "WM8766"); +} + +static void xonar_hdav_slim_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + data->generic.anti_pop_delay = 300; + data->generic.output_enable_bit = GPIO_SLIM_OUTPUT_ENABLE; + + wm8776_init(chip); + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_SLIM_HDMI_DISABLE | + GPIO_SLIM_FIRMWARE_CLK | + GPIO_SLIM_FIRMWARE_DATA); + + xonar_hdmi_init(chip, &data->hdmi); + xonar_enable_output(chip); + + snd_component_add(chip->card, "WM8776"); +} + +static void xonar_ds_cleanup(struct oxygen *chip) +{ + xonar_disable_output(chip); + wm8776_write(chip, WM8776_RESET, 0); +} + +static void xonar_hdav_slim_cleanup(struct oxygen *chip) +{ + xonar_hdmi_cleanup(chip); + xonar_disable_output(chip); + wm8776_write(chip, WM8776_RESET, 0); + msleep(2); +} + +static void xonar_ds_suspend(struct oxygen *chip) +{ + xonar_ds_cleanup(chip); +} + +static void xonar_hdav_slim_suspend(struct oxygen *chip) +{ + xonar_hdav_slim_cleanup(chip); +} + +static void xonar_ds_resume(struct oxygen *chip) +{ + wm8776_registers_init(chip); + wm8766_registers_init(chip); + xonar_enable_output(chip); + xonar_ds_handle_hp_jack(chip); +} + +static void xonar_hdav_slim_resume(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + wm8776_registers_init(chip); + xonar_hdmi_resume(chip, &data->hdmi); + xonar_enable_output(chip); +} + +static void wm8776_adc_hardware_filter(unsigned int channel, + struct snd_pcm_hardware *hardware) +{ + if (channel == PCM_A) { + hardware->rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + hardware->rate_max = 96000; + } +} + +static void xonar_hdav_slim_hardware_filter(unsigned int channel, + struct snd_pcm_hardware *hardware) +{ + wm8776_adc_hardware_filter(channel, hardware); + xonar_hdmi_pcm_hardware_filter(channel, hardware); +} + +static void set_wm87x6_dac_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ +} + +static void set_wm8776_adc_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + u16 reg; + + reg = WM8776_ADCRATE_256 | WM8776_DACRATE_256; + if (params_rate(params) > 48000) + reg |= WM8776_ADCOSR; + wm8776_write_cached(chip, WM8776_MSTRCTRL, reg); +} + +static void set_hdav_slim_dac_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct xonar_wm87x6 *data = chip->model_data; + + xonar_set_hdmi_params(chip, &data->hdmi, params); +} + +static void update_wm8776_volume(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + u8 to_change; + + if (chip->dac_volume[0] == chip->dac_volume[1]) { + if (chip->dac_volume[0] != data->wm8776_regs[WM8776_DACLVOL] || + chip->dac_volume[1] != data->wm8776_regs[WM8776_DACRVOL]) { + wm8776_write(chip, WM8776_DACMASTER, + chip->dac_volume[0] | WM8776_UPDATE); + data->wm8776_regs[WM8776_DACLVOL] = chip->dac_volume[0]; + data->wm8776_regs[WM8776_DACRVOL] = chip->dac_volume[0]; + } + } else { + to_change = (chip->dac_volume[0] != + data->wm8776_regs[WM8776_DACLVOL]) << 0; + to_change |= (chip->dac_volume[1] != + data->wm8776_regs[WM8776_DACLVOL]) << 1; + if (to_change & 1) + wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0] | + ((to_change & 2) ? 0 : WM8776_UPDATE)); + if (to_change & 2) + wm8776_write(chip, WM8776_DACRVOL, + chip->dac_volume[1] | WM8776_UPDATE); + } +} + +static void update_wm87x6_volume(struct oxygen *chip) +{ + static const u8 wm8766_regs[6] = { + WM8766_LDA1, WM8766_RDA1, + WM8766_LDA2, WM8766_RDA2, + WM8766_LDA3, WM8766_RDA3, + }; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int i; + u8 to_change; + + update_wm8776_volume(chip); + if (chip->dac_volume[2] == chip->dac_volume[3] && + chip->dac_volume[2] == chip->dac_volume[4] && + chip->dac_volume[2] == chip->dac_volume[5] && + chip->dac_volume[2] == chip->dac_volume[6] && + chip->dac_volume[2] == chip->dac_volume[7]) { + to_change = 0; + for (i = 0; i < 6; ++i) + if (chip->dac_volume[2] != + data->wm8766_regs[wm8766_regs[i]]) + to_change = 1; + if (to_change) { + wm8766_write(chip, WM8766_MASTDA, + chip->dac_volume[2] | WM8766_UPDATE); + for (i = 0; i < 6; ++i) + data->wm8766_regs[wm8766_regs[i]] = + chip->dac_volume[2]; + } + } else { + to_change = 0; + for (i = 0; i < 6; ++i) + to_change |= (chip->dac_volume[2 + i] != + data->wm8766_regs[wm8766_regs[i]]) << i; + for (i = 0; i < 6; ++i) + if (to_change & (1 << i)) + wm8766_write(chip, wm8766_regs[i], + chip->dac_volume[2 + i] | + ((to_change & (0x3e << i)) + ? 0 : WM8766_UPDATE)); + } +} + +static void update_wm8776_mute(struct oxygen *chip) +{ + wm8776_write_cached(chip, WM8776_DACMUTE, + chip->dac_mute ? WM8776_DMUTE : 0); +} + +static void update_wm87x6_mute(struct oxygen *chip) +{ + update_wm8776_mute(chip); + wm8766_write_cached(chip, WM8766_DAC_CTRL2, WM8766_ZCD | + (chip->dac_mute ? WM8766_DMUTE_MASK : 0)); +} + +static void update_wm8766_center_lfe_mix(struct oxygen *chip, bool mixed) +{ + struct xonar_wm87x6 *data = chip->model_data; + unsigned int reg; + + /* + * The WM8766 can mix left and right channels, but this setting + * applies to all three stereo pairs. + */ + reg = data->wm8766_regs[WM8766_DAC_CTRL] & + ~(WM8766_PL_LEFT_MASK | WM8766_PL_RIGHT_MASK); + if (mixed) + reg |= WM8766_PL_LEFT_LRMIX | WM8766_PL_RIGHT_LRMIX; + else + reg |= WM8766_PL_LEFT_LEFT | WM8766_PL_RIGHT_RIGHT; + wm8766_write_cached(chip, WM8766_DAC_CTRL, reg); +} + +static void xonar_ds_gpio_changed(struct oxygen *chip) +{ + xonar_ds_handle_hp_jack(chip); +} + +static int wm8776_bit_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + u16 bit = ctl->private_value & 0xffff; + unsigned int reg_index = (ctl->private_value >> 16) & 0xff; + bool invert = (ctl->private_value >> 24) & 1; + + value->value.integer.value[0] = + ((data->wm8776_regs[reg_index] & bit) != 0) ^ invert; + return 0; +} + +static int wm8776_bit_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + u16 bit = ctl->private_value & 0xffff; + u16 reg_value; + unsigned int reg_index = (ctl->private_value >> 16) & 0xff; + bool invert = (ctl->private_value >> 24) & 1; + int changed; + + guard(mutex)(&chip->mutex); + reg_value = data->wm8776_regs[reg_index] & ~bit; + if (value->value.integer.value[0] ^ invert) + reg_value |= bit; + changed = reg_value != data->wm8776_regs[reg_index]; + if (changed) + wm8776_write(chip, reg_index, reg_value); + return changed; +} + +static int wm8776_field_enum_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const hld[16] = { + "0 ms", "2.67 ms", "5.33 ms", "10.6 ms", + "21.3 ms", "42.7 ms", "85.3 ms", "171 ms", + "341 ms", "683 ms", "1.37 s", "2.73 s", + "5.46 s", "10.9 s", "21.8 s", "43.7 s", + }; + static const char *const atk_lim[11] = { + "0.25 ms", "0.5 ms", "1 ms", "2 ms", + "4 ms", "8 ms", "16 ms", "32 ms", + "64 ms", "128 ms", "256 ms", + }; + static const char *const atk_alc[11] = { + "8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms", + "134 ms", "269 ms", "538 ms", "1.08 s", + "2.15 s", "4.3 s", "8.6 s", + }; + static const char *const dcy_lim[11] = { + "1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms", + "19.2 ms", "38.4 ms", "76.8 ms", "154 ms", + "307 ms", "614 ms", "1.23 s", + }; + static const char *const dcy_alc[11] = { + "33.5 ms", "67.0 ms", "134 ms", "268 ms", + "536 ms", "1.07 s", "2.14 s", "4.29 s", + "8.58 s", "17.2 s", "34.3 s", + }; + static const char *const tranwin[8] = { + "0 us", "62.5 us", "125 us", "250 us", + "500 us", "1 ms", "2 ms", "4 ms", + }; + u8 max; + const char *const *names; + + max = (ctl->private_value >> 12) & 0xf; + switch ((ctl->private_value >> 24) & 0x1f) { + case WM8776_ALCCTRL2: + names = hld; + break; + case WM8776_ALCCTRL3: + if (((ctl->private_value >> 20) & 0xf) == 0) { + if (ctl->private_value & LC_CONTROL_LIMITER) + names = atk_lim; + else + names = atk_alc; + } else { + if (ctl->private_value & LC_CONTROL_LIMITER) + names = dcy_lim; + else + names = dcy_alc; + } + break; + case WM8776_LIMITER: + names = tranwin; + break; + default: + return -ENXIO; + } + return snd_ctl_enum_info(info, 1, max + 1, names); +} + +static int wm8776_field_volume_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = (ctl->private_value >> 8) & 0xf; + info->value.integer.max = (ctl->private_value >> 12) & 0xf; + return 0; +} + +static void wm8776_field_set_from_ctl(struct snd_kcontrol *ctl) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int value, reg_index, mode; + u8 min, max, shift; + u16 mask, reg_value; + bool invert; + + if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) == + WM8776_LCSEL_LIMITER) + mode = LC_CONTROL_LIMITER; + else + mode = LC_CONTROL_ALC; + if (!(ctl->private_value & mode)) + return; + + value = ctl->private_value & 0xf; + min = (ctl->private_value >> 8) & 0xf; + max = (ctl->private_value >> 12) & 0xf; + mask = (ctl->private_value >> 16) & 0xf; + shift = (ctl->private_value >> 20) & 0xf; + reg_index = (ctl->private_value >> 24) & 0x1f; + invert = (ctl->private_value >> 29) & 0x1; + + if (invert) + value = max - (value - min); + reg_value = data->wm8776_regs[reg_index]; + reg_value &= ~(mask << shift); + reg_value |= value << shift; + wm8776_write_cached(chip, reg_index, reg_value); +} + +static int wm8776_field_set(struct snd_kcontrol *ctl, unsigned int value) +{ + struct oxygen *chip = ctl->private_data; + u8 min, max; + int changed; + + min = (ctl->private_value >> 8) & 0xf; + max = (ctl->private_value >> 12) & 0xf; + if (value < min || value > max) + return -EINVAL; + guard(mutex)(&chip->mutex); + changed = value != (ctl->private_value & 0xf); + if (changed) { + ctl->private_value = (ctl->private_value & ~0xf) | value; + wm8776_field_set_from_ctl(ctl); + } + return changed; +} + +static int wm8776_field_enum_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + value->value.enumerated.item[0] = ctl->private_value & 0xf; + return 0; +} + +static int wm8776_field_volume_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + value->value.integer.value[0] = ctl->private_value & 0xf; + return 0; +} + +static int wm8776_field_enum_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + return wm8776_field_set(ctl, value->value.enumerated.item[0]); +} + +static int wm8776_field_volume_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + return wm8776_field_set(ctl, value->value.integer.value[0]); +} + +static int wm8776_hp_vol_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 0x79 - 60; + info->value.integer.max = 0x7f; + return 0; +} + +static int wm8776_hp_vol_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + + guard(mutex)(&chip->mutex); + value->value.integer.value[0] = + data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK; + value->value.integer.value[1] = + data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK; + return 0; +} + +static int wm8776_hp_vol_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + u8 to_update; + + guard(mutex)(&chip->mutex); + to_update = (value->value.integer.value[0] != + (data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK)) + << 0; + to_update |= (value->value.integer.value[1] != + (data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK)) + << 1; + if (value->value.integer.value[0] == value->value.integer.value[1]) { + if (to_update) { + wm8776_write(chip, WM8776_HPMASTER, + value->value.integer.value[0] | + WM8776_HPZCEN | WM8776_UPDATE); + data->wm8776_regs[WM8776_HPLVOL] = + value->value.integer.value[0] | WM8776_HPZCEN; + data->wm8776_regs[WM8776_HPRVOL] = + value->value.integer.value[0] | WM8776_HPZCEN; + } + } else { + if (to_update & 1) + wm8776_write(chip, WM8776_HPLVOL, + value->value.integer.value[0] | + WM8776_HPZCEN | + ((to_update & 2) ? 0 : WM8776_UPDATE)); + if (to_update & 2) + wm8776_write(chip, WM8776_HPRVOL, + value->value.integer.value[1] | + WM8776_HPZCEN | WM8776_UPDATE); + } + return to_update != 0; +} + +static int wm8776_input_mux_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int mux_bit = ctl->private_value; + + value->value.integer.value[0] = + !!(data->wm8776_regs[WM8776_ADCMUX] & mux_bit); + return 0; +} + +static int wm8776_input_mux_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + struct snd_kcontrol *other_ctl; + unsigned int mux_bit = ctl->private_value; + u16 reg; + int changed; + + guard(mutex)(&chip->mutex); + reg = data->wm8776_regs[WM8776_ADCMUX]; + if (value->value.integer.value[0]) { + reg |= mux_bit; + /* line-in and mic-in are exclusive */ + mux_bit ^= 3; + if (reg & mux_bit) { + reg &= ~mux_bit; + if (mux_bit == 1) + other_ctl = data->line_adcmux_control; + else + other_ctl = data->mic_adcmux_control; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &other_ctl->id); + } + } else + reg &= ~mux_bit; + changed = reg != data->wm8776_regs[WM8776_ADCMUX]; + if (changed) { + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + reg & 1 ? GPIO_DS_INPUT_ROUTE : 0, + GPIO_DS_INPUT_ROUTE); + wm8776_write(chip, WM8776_ADCMUX, reg); + } + return changed; +} + +static int wm8776_input_vol_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 0xa5; + info->value.integer.max = 0xff; + return 0; +} + +static int wm8776_input_vol_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + + guard(mutex)(&chip->mutex); + value->value.integer.value[0] = + data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK; + value->value.integer.value[1] = + data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK; + return 0; +} + +static int wm8776_input_vol_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + int changed = 0; + + guard(mutex)(&chip->mutex); + changed = (value->value.integer.value[0] != + (data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK)) || + (value->value.integer.value[1] != + (data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK)); + wm8776_write_cached(chip, WM8776_ADCLVOL, + value->value.integer.value[0] | WM8776_ZCA); + wm8776_write_cached(chip, WM8776_ADCRVOL, + value->value.integer.value[1] | WM8776_ZCA); + return changed; +} + +static int wm8776_level_control_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[3] = { + "None", "Peak Limiter", "Automatic Level Control" + }; + + return snd_ctl_enum_info(info, 1, 3, names); +} + +static int wm8776_level_control_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + + if (!(data->wm8776_regs[WM8776_ALCCTRL2] & WM8776_LCEN)) + value->value.enumerated.item[0] = 0; + else if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) == + WM8776_LCSEL_LIMITER) + value->value.enumerated.item[0] = 1; + else + value->value.enumerated.item[0] = 2; + return 0; +} + +static void activate_control(struct oxygen *chip, + struct snd_kcontrol *ctl, unsigned int mode) +{ + unsigned int access; + + if (ctl->private_value & mode) + access = 0; + else + access = SNDRV_CTL_ELEM_ACCESS_INACTIVE; + if ((ctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) != access) { + ctl->vd[0].access ^= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + } +} + +static int wm8776_level_control_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int mode = 0, i; + u16 ctrl1, ctrl2; + int changed; + + if (value->value.enumerated.item[0] >= 3) + return -EINVAL; + guard(mutex)(&chip->mutex); + changed = value->value.enumerated.item[0] != ctl->private_value; + if (changed) { + ctl->private_value = value->value.enumerated.item[0]; + ctrl1 = data->wm8776_regs[WM8776_ALCCTRL1]; + ctrl2 = data->wm8776_regs[WM8776_ALCCTRL2]; + switch (value->value.enumerated.item[0]) { + default: + wm8776_write_cached(chip, WM8776_ALCCTRL2, + ctrl2 & ~WM8776_LCEN); + break; + case 1: + wm8776_write_cached(chip, WM8776_ALCCTRL1, + (ctrl1 & ~WM8776_LCSEL_MASK) | + WM8776_LCSEL_LIMITER); + wm8776_write_cached(chip, WM8776_ALCCTRL2, + ctrl2 | WM8776_LCEN); + mode = LC_CONTROL_LIMITER; + break; + case 2: + wm8776_write_cached(chip, WM8776_ALCCTRL1, + (ctrl1 & ~WM8776_LCSEL_MASK) | + WM8776_LCSEL_ALC_STEREO); + wm8776_write_cached(chip, WM8776_ALCCTRL2, + ctrl2 | WM8776_LCEN); + mode = LC_CONTROL_ALC; + break; + } + for (i = 0; i < ARRAY_SIZE(data->lc_controls); ++i) + activate_control(chip, data->lc_controls[i], mode); + } + return changed; +} + +static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { + "None", "High-pass Filter" + }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + + value->value.enumerated.item[0] = + !(data->wm8776_regs[WM8776_ADCIFCTRL] & WM8776_ADCHPD); + return 0; +} + +static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int reg; + int changed; + + guard(mutex)(&chip->mutex); + reg = data->wm8776_regs[WM8776_ADCIFCTRL] & ~WM8776_ADCHPD; + if (!value->value.enumerated.item[0]) + reg |= WM8776_ADCHPD; + changed = reg != data->wm8776_regs[WM8776_ADCIFCTRL]; + if (changed) + wm8776_write(chip, WM8776_ADCIFCTRL, reg); + return changed; +} + +#define WM8776_BIT_SWITCH(xname, reg, bit, invert, flags) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = snd_ctl_boolean_mono_info, \ + .get = wm8776_bit_switch_get, \ + .put = wm8776_bit_switch_put, \ + .private_value = ((reg) << 16) | (bit) | ((invert) << 24) | (flags), \ +} +#define _WM8776_FIELD_CTL(xname, reg, shift, initval, min, max, mask, flags) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .private_value = (initval) | ((min) << 8) | ((max) << 12) | \ + ((mask) << 16) | ((shift) << 20) | ((reg) << 24) | (flags) +#define WM8776_FIELD_CTL_ENUM(xname, reg, shift, init, min, max, mask, flags) {\ + _WM8776_FIELD_CTL(xname " Capture Enum", \ + reg, shift, init, min, max, mask, flags), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_INACTIVE, \ + .info = wm8776_field_enum_info, \ + .get = wm8776_field_enum_get, \ + .put = wm8776_field_enum_put, \ +} +#define WM8776_FIELD_CTL_VOLUME(a, b, c, d, e, f, g, h, tlv_p) { \ + _WM8776_FIELD_CTL(a " Capture Volume", b, c, d, e, f, g, h), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_INACTIVE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = wm8776_field_volume_info, \ + .get = wm8776_field_volume_get, \ + .put = wm8776_field_volume_put, \ + .tlv = { .p = tlv_p }, \ +} + +static const DECLARE_TLV_DB_SCALE(wm87x6_dac_db_scale, -6000, 50, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_adc_db_scale, -2100, 50, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_hp_db_scale, -6000, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_lct_db_scale, -1600, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_db_scale, 0, 400, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_ngth_db_scale, -7800, 600, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_db_scale, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_db_scale, -2100, 400, 0); + +static const struct snd_kcontrol_new ds_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Volume", + .info = wm8776_hp_vol_info, + .get = wm8776_hp_vol_get, + .put = wm8776_hp_vol_put, + .tlv = { .p = wm8776_hp_db_scale }, + }, + WM8776_BIT_SWITCH("Headphone Playback Switch", + WM8776_PWRDOWN, WM8776_HPPD, 1, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Capture Volume", + .info = wm8776_input_vol_info, + .get = wm8776_input_vol_get, + .put = wm8776_input_vol_put, + .tlv = { .p = wm8776_adc_db_scale }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Capture Switch", + .info = snd_ctl_boolean_mono_info, + .get = wm8776_input_mux_get, + .put = wm8776_input_mux_put, + .private_value = 1 << 0, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Capture Switch", + .info = snd_ctl_boolean_mono_info, + .get = wm8776_input_mux_get, + .put = wm8776_input_mux_put, + .private_value = 1 << 1, + }, + WM8776_BIT_SWITCH("Front Mic Capture Switch", + WM8776_ADCMUX, 1 << 2, 0, 0), + WM8776_BIT_SWITCH("Aux Capture Switch", + WM8776_ADCMUX, 1 << 3, 0, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Filter Capture Enum", + .info = hpf_info, + .get = hpf_get, + .put = hpf_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Level Control Capture Enum", + .info = wm8776_level_control_info, + .get = wm8776_level_control_get, + .put = wm8776_level_control_put, + .private_value = 0, + }, +}; +static const struct snd_kcontrol_new hdav_slim_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = xonar_gpio_bit_switch_get, + .put = xonar_gpio_bit_switch_put, + .private_value = GPIO_SLIM_HDMI_DISABLE | XONAR_GPIO_BIT_INVERT, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Volume", + .info = wm8776_hp_vol_info, + .get = wm8776_hp_vol_get, + .put = wm8776_hp_vol_put, + .tlv = { .p = wm8776_hp_db_scale }, + }, + WM8776_BIT_SWITCH("Headphone Playback Switch", + WM8776_PWRDOWN, WM8776_HPPD, 1, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Capture Volume", + .info = wm8776_input_vol_info, + .get = wm8776_input_vol_get, + .put = wm8776_input_vol_put, + .tlv = { .p = wm8776_adc_db_scale }, + }, + WM8776_BIT_SWITCH("Mic Capture Switch", + WM8776_ADCMUX, 1 << 0, 0, 0), + WM8776_BIT_SWITCH("Aux Capture Switch", + WM8776_ADCMUX, 1 << 1, 0, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Filter Capture Enum", + .info = hpf_info, + .get = hpf_get, + .put = hpf_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Level Control Capture Enum", + .info = wm8776_level_control_info, + .get = wm8776_level_control_get, + .put = wm8776_level_control_put, + .private_value = 0, + }, +}; +static const struct snd_kcontrol_new lc_controls[] = { + WM8776_FIELD_CTL_VOLUME("Limiter Threshold", + WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf, + LC_CONTROL_LIMITER, wm8776_lct_db_scale), + WM8776_FIELD_CTL_ENUM("Limiter Attack Time", + WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf, + LC_CONTROL_LIMITER), + WM8776_FIELD_CTL_ENUM("Limiter Decay Time", + WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf, + LC_CONTROL_LIMITER), + WM8776_FIELD_CTL_ENUM("Limiter Transient Window", + WM8776_LIMITER, 4, 2, 0, 7, 0x7, + LC_CONTROL_LIMITER), + WM8776_FIELD_CTL_VOLUME("Limiter Maximum Attenuation", + WM8776_LIMITER, 0, 6, 3, 12, 0xf, + LC_CONTROL_LIMITER, + wm8776_maxatten_lim_db_scale), + WM8776_FIELD_CTL_VOLUME("ALC Target Level", + WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf, + LC_CONTROL_ALC, wm8776_lct_db_scale), + WM8776_FIELD_CTL_ENUM("ALC Attack Time", + WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf, + LC_CONTROL_ALC), + WM8776_FIELD_CTL_ENUM("ALC Decay Time", + WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf, + LC_CONTROL_ALC), + WM8776_FIELD_CTL_VOLUME("ALC Maximum Gain", + WM8776_ALCCTRL1, 4, 7, 1, 7, 0x7, + LC_CONTROL_ALC, wm8776_maxgain_db_scale), + WM8776_FIELD_CTL_VOLUME("ALC Maximum Attenuation", + WM8776_LIMITER, 0, 10, 10, 15, 0xf, + LC_CONTROL_ALC, wm8776_maxatten_alc_db_scale), + WM8776_FIELD_CTL_ENUM("ALC Hold Time", + WM8776_ALCCTRL2, 0, 0, 0, 15, 0xf, + LC_CONTROL_ALC), + WM8776_BIT_SWITCH("Noise Gate Capture Switch", + WM8776_NOISEGATE, WM8776_NGAT, 0, + LC_CONTROL_ALC), + WM8776_FIELD_CTL_VOLUME("Noise Gate Threshold", + WM8776_NOISEGATE, 2, 0, 0, 7, 0x7, + LC_CONTROL_ALC, wm8776_ngth_db_scale), +}; + +static int add_lc_controls(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + unsigned int i; + struct snd_kcontrol *ctl; + int err; + + BUILD_BUG_ON(ARRAY_SIZE(lc_controls) != ARRAY_SIZE(data->lc_controls)); + for (i = 0; i < ARRAY_SIZE(lc_controls); ++i) { + ctl = snd_ctl_new1(&lc_controls[i], chip); + if (!ctl) + return -ENOMEM; + err = snd_ctl_add(chip->card, ctl); + if (err < 0) + return err; + data->lc_controls[i] = ctl; + } + return 0; +} + +static int xonar_ds_mixer_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + unsigned int i; + struct snd_kcontrol *ctl; + int err; + + for (i = 0; i < ARRAY_SIZE(ds_controls); ++i) { + ctl = snd_ctl_new1(&ds_controls[i], chip); + if (!ctl) + return -ENOMEM; + err = snd_ctl_add(chip->card, ctl); + if (err < 0) + return err; + if (!strcmp(ctl->id.name, "Line Capture Switch")) + data->line_adcmux_control = ctl; + else if (!strcmp(ctl->id.name, "Mic Capture Switch")) + data->mic_adcmux_control = ctl; + } + if (!data->line_adcmux_control || !data->mic_adcmux_control) + return -ENXIO; + + return add_lc_controls(chip); +} + +static int xonar_hdav_slim_mixer_init(struct oxygen *chip) +{ + unsigned int i; + struct snd_kcontrol *ctl; + int err; + + for (i = 0; i < ARRAY_SIZE(hdav_slim_controls); ++i) { + ctl = snd_ctl_new1(&hdav_slim_controls[i], chip); + if (!ctl) + return -ENOMEM; + err = snd_ctl_add(chip->card, ctl); + if (err < 0) + return err; + } + + return add_lc_controls(chip); +} + +static void dump_wm8776_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + struct xonar_wm87x6 *data = chip->model_data; + unsigned int i; + + snd_iprintf(buffer, "\nWM8776:\n00:"); + for (i = 0; i < 0x10; ++i) + snd_iprintf(buffer, " %03x", data->wm8776_regs[i]); + snd_iprintf(buffer, "\n10:"); + for (i = 0x10; i < 0x17; ++i) + snd_iprintf(buffer, " %03x", data->wm8776_regs[i]); + snd_iprintf(buffer, "\n"); +} + +static void dump_wm87x6_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + struct xonar_wm87x6 *data = chip->model_data; + unsigned int i; + + dump_wm8776_registers(chip, buffer); + snd_iprintf(buffer, "\nWM8766:\n00:"); + for (i = 0; i < 0x10; ++i) + snd_iprintf(buffer, " %03x", data->wm8766_regs[i]); + snd_iprintf(buffer, "\n"); +} + +static const struct oxygen_model model_xonar_ds = { + .longname = "Asus Virtuoso 66", + .chip = "AV200", + .init = xonar_ds_init, + .mixer_init = xonar_ds_mixer_init, + .cleanup = xonar_ds_cleanup, + .suspend = xonar_ds_suspend, + .resume = xonar_ds_resume, + .pcm_hardware_filter = wm8776_adc_hardware_filter, + .set_dac_params = set_wm87x6_dac_params, + .set_adc_params = set_wm8776_adc_params, + .update_dac_volume = update_wm87x6_volume, + .update_dac_mute = update_wm87x6_mute, + .update_center_lfe_mix = update_wm8766_center_lfe_mix, + .gpio_changed = xonar_ds_gpio_changed, + .dump_registers = dump_wm87x6_registers, + .dac_tlv = wm87x6_dac_db_scale, + .model_data_size = sizeof(struct xonar_wm87x6), + .device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_1 | + CAPTURE_1_FROM_SPDIF, + .dac_channels_pcm = 8, + .dac_channels_mixer = 8, + .dac_volume_min = 255 - 2*60, + .dac_volume_max = 255, + .function_flags = OXYGEN_FUNCTION_SPI, + .dac_mclks = OXYGEN_MCLKS(256, 256, 128), + .adc_mclks = OXYGEN_MCLKS(256, 256, 128), + .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, + .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; + +static const struct oxygen_model model_xonar_hdav_slim = { + .shortname = "Xonar HDAV1.3 Slim", + .longname = "Asus Virtuoso 200", + .chip = "AV200", + .init = xonar_hdav_slim_init, + .mixer_init = xonar_hdav_slim_mixer_init, + .cleanup = xonar_hdav_slim_cleanup, + .suspend = xonar_hdav_slim_suspend, + .resume = xonar_hdav_slim_resume, + .pcm_hardware_filter = xonar_hdav_slim_hardware_filter, + .set_dac_params = set_hdav_slim_dac_params, + .set_adc_params = set_wm8776_adc_params, + .update_dac_volume = update_wm8776_volume, + .update_dac_mute = update_wm8776_mute, + .uart_input = xonar_hdmi_uart_input, + .dump_registers = dump_wm8776_registers, + .dac_tlv = wm87x6_dac_db_scale, + .model_data_size = sizeof(struct xonar_wm87x6), + .device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_1 | + CAPTURE_1_FROM_SPDIF, + .dac_channels_pcm = 8, + .dac_channels_mixer = 2, + .dac_volume_min = 255 - 2*60, + .dac_volume_max = 255, + .function_flags = OXYGEN_FUNCTION_2WIRE, + .dac_mclks = OXYGEN_MCLKS(256, 256, 128), + .adc_mclks = OXYGEN_MCLKS(256, 256, 128), + .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, + .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; + +int get_xonar_wm87x6_model(struct oxygen *chip, + const struct pci_device_id *id) +{ + switch (id->subdevice) { + case 0x838e: + chip->model = model_xonar_ds; + chip->model.shortname = "Xonar DS"; + break; + case 0x8522: + chip->model = model_xonar_ds; + chip->model.shortname = "Xonar DSX"; + break; + case 0x835e: + chip->model = model_xonar_hdav_slim; + break; + default: + return -EINVAL; + } + return 0; +} diff --git a/sound/pci/pcxhr/Makefile b/sound/pci/pcxhr/Makefile new file mode 100644 index 000000000..0ea1e5ccb --- /dev/null +++ b/sound/pci/pcxhr/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-pcxhr-y := pcxhr.o pcxhr_hwdep.o pcxhr_mixer.o pcxhr_core.o pcxhr_mix22.o +obj-$(CONFIG_SND_PCXHR) += snd-pcxhr.o diff --git a/sound/pci/pcxhr/pcxhr.c b/sound/pci/pcxhr/pcxhr.c new file mode 100644 index 000000000..60cc0721d --- /dev/null +++ b/sound/pci/pcxhr/pcxhr.c @@ -0,0 +1,1650 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram pcxhr compatible soundcards + * + * main file with alsa callbacks + * + * Copyright (c) 2004 by Digigram + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_mixer.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" +#include "pcxhr_mix22.h" + +#define DRIVER_NAME "pcxhr" + +MODULE_AUTHOR("Markus Bollinger , " + "Marc Titinger "); +MODULE_DESCRIPTION("Digigram " DRIVER_NAME " " PCXHR_DRIVER_VERSION_STRING); +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_PNP;/* Enable this card */ +static bool mono[SNDRV_CARDS]; /* capture mono only */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Digigram " DRIVER_NAME " soundcard"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Digigram " DRIVER_NAME " soundcard"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Digigram " DRIVER_NAME " soundcard"); +module_param_array(mono, bool, NULL, 0444); +MODULE_PARM_DESC(mono, "Mono capture mode (default is stereo)"); + +enum { + PCI_ID_VX882HR, + PCI_ID_PCX882HR, + PCI_ID_VX881HR, + PCI_ID_PCX881HR, + PCI_ID_VX882E, + PCI_ID_PCX882E, + PCI_ID_VX881E, + PCI_ID_PCX881E, + PCI_ID_VX1222HR, + PCI_ID_PCX1222HR, + PCI_ID_VX1221HR, + PCI_ID_PCX1221HR, + PCI_ID_VX1222E, + PCI_ID_PCX1222E, + PCI_ID_VX1221E, + PCI_ID_PCX1221E, + PCI_ID_VX222HR, + PCI_ID_VX222E, + PCI_ID_PCX22HR, + PCI_ID_PCX22E, + PCI_ID_VX222HRMIC, + PCI_ID_VX222E_MIC, + PCI_ID_PCX924HR, + PCI_ID_PCX924E, + PCI_ID_PCX924HRMIC, + PCI_ID_PCX924E_MIC, + PCI_ID_VX442HR, + PCI_ID_PCX442HR, + PCI_ID_VX442E, + PCI_ID_PCX442E, + PCI_ID_VX822HR, + PCI_ID_PCX822HR, + PCI_ID_VX822E, + PCI_ID_PCX822E, + PCI_ID_LAST +}; + +static const struct pci_device_id pcxhr_ids[] = { + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xb001), .driver_data = PCI_ID_VX882HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xb101), .driver_data = PCI_ID_PCX882HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xb201), .driver_data = PCI_ID_VX881HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xb301), .driver_data = PCI_ID_PCX881HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xb021), .driver_data = PCI_ID_VX882E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xb121), .driver_data = PCI_ID_PCX882E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xb221), .driver_data = PCI_ID_VX881E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xb321), .driver_data = PCI_ID_PCX881E }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xb401), .driver_data = PCI_ID_VX1222HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xb501), .driver_data = PCI_ID_PCX1222HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xb601), .driver_data = PCI_ID_VX1221HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xb701), .driver_data = PCI_ID_PCX1221HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xb421), .driver_data = PCI_ID_VX1222E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xb521), .driver_data = PCI_ID_PCX1222E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xb621), .driver_data = PCI_ID_VX1221E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xb721), .driver_data = PCI_ID_PCX1221E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xba01), .driver_data = PCI_ID_VX222HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xba21), .driver_data = PCI_ID_VX222E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xbd01), .driver_data = PCI_ID_PCX22HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xbd21), .driver_data = PCI_ID_PCX22E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xbc01), .driver_data = PCI_ID_VX222HRMIC }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xbc21), .driver_data = PCI_ID_VX222E_MIC }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xbb01), .driver_data = PCI_ID_PCX924HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xbb21), .driver_data = PCI_ID_PCX924E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xbf01), .driver_data = PCI_ID_PCX924HRMIC }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xbf21), .driver_data = PCI_ID_PCX924E_MIC }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xd001), .driver_data = PCI_ID_VX442HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xd101), .driver_data = PCI_ID_PCX442HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xd021), .driver_data = PCI_ID_VX442E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xd121), .driver_data = PCI_ID_PCX442E }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xd201), .driver_data = PCI_ID_VX822HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9656, 0x1369, 0xd301), .driver_data = PCI_ID_PCX822HR }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xd221), .driver_data = PCI_ID_VX822E }, + { PCI_DEVICE_SUB(0x10b5, 0x9056, 0x1369, 0xd321), .driver_data = PCI_ID_PCX822E }, + { } +}; + +MODULE_DEVICE_TABLE(pci, pcxhr_ids); + +struct board_parameters { + char* board_name; + short playback_chips; + short capture_chips; + short fw_file_set; + short firmware_num; +}; +static const struct board_parameters pcxhr_board_params[] = { +[PCI_ID_VX882HR] = { "VX882HR", 4, 4, 0, 41 }, +[PCI_ID_PCX882HR] = { "PCX882HR", 4, 4, 0, 41 }, +[PCI_ID_VX881HR] = { "VX881HR", 4, 4, 0, 41 }, +[PCI_ID_PCX881HR] = { "PCX881HR", 4, 4, 0, 41 }, +[PCI_ID_VX882E] = { "VX882e", 4, 4, 1, 41 }, +[PCI_ID_PCX882E] = { "PCX882e", 4, 4, 1, 41 }, +[PCI_ID_VX881E] = { "VX881e", 4, 4, 1, 41 }, +[PCI_ID_PCX881E] = { "PCX881e", 4, 4, 1, 41 }, +[PCI_ID_VX1222HR] = { "VX1222HR", 6, 1, 2, 42 }, +[PCI_ID_PCX1222HR] = { "PCX1222HR", 6, 1, 2, 42 }, +[PCI_ID_VX1221HR] = { "VX1221HR", 6, 1, 2, 42 }, +[PCI_ID_PCX1221HR] = { "PCX1221HR", 6, 1, 2, 42 }, +[PCI_ID_VX1222E] = { "VX1222e", 6, 1, 3, 42 }, +[PCI_ID_PCX1222E] = { "PCX1222e", 6, 1, 3, 42 }, +[PCI_ID_VX1221E] = { "VX1221e", 6, 1, 3, 42 }, +[PCI_ID_PCX1221E] = { "PCX1221e", 6, 1, 3, 42 }, +[PCI_ID_VX222HR] = { "VX222HR", 1, 1, 4, 44 }, +[PCI_ID_VX222E] = { "VX222e", 1, 1, 4, 44 }, +[PCI_ID_PCX22HR] = { "PCX22HR", 1, 0, 4, 44 }, +[PCI_ID_PCX22E] = { "PCX22e", 1, 0, 4, 44 }, +[PCI_ID_VX222HRMIC] = { "VX222HR-Mic", 1, 1, 5, 44 }, +[PCI_ID_VX222E_MIC] = { "VX222e-Mic", 1, 1, 5, 44 }, +[PCI_ID_PCX924HR] = { "PCX924HR", 1, 1, 5, 44 }, +[PCI_ID_PCX924E] = { "PCX924e", 1, 1, 5, 44 }, +[PCI_ID_PCX924HRMIC] = { "PCX924HR-Mic", 1, 1, 5, 44 }, +[PCI_ID_PCX924E_MIC] = { "PCX924e-Mic", 1, 1, 5, 44 }, +[PCI_ID_VX442HR] = { "VX442HR", 2, 2, 0, 41 }, +[PCI_ID_PCX442HR] = { "PCX442HR", 2, 2, 0, 41 }, +[PCI_ID_VX442E] = { "VX442e", 2, 2, 1, 41 }, +[PCI_ID_PCX442E] = { "PCX442e", 2, 2, 1, 41 }, +[PCI_ID_VX822HR] = { "VX822HR", 4, 1, 2, 42 }, +[PCI_ID_PCX822HR] = { "PCX822HR", 4, 1, 2, 42 }, +[PCI_ID_VX822E] = { "VX822e", 4, 1, 3, 42 }, +[PCI_ID_PCX822E] = { "PCX822e", 4, 1, 3, 42 }, +}; + +/* boards without hw AES1 and SRC onboard are all using fw_file_set==4 */ +/* VX222HR, VX222e, PCX22HR and PCX22e */ +#define PCXHR_BOARD_HAS_AES1(x) (x->fw_file_set != 4) +/* some boards do not support 192kHz on digital AES input plugs */ +#define PCXHR_BOARD_AESIN_NO_192K(x) ((x->capture_chips == 0) || \ + (x->fw_file_set == 0) || \ + (x->fw_file_set == 2)) + +int pcxhr_pll_freq_register(unsigned int freq, unsigned int max_freq, + unsigned int *pllreg, unsigned int *realfreq) +{ + unsigned int reg; + + if (freq < 6900 || freq > max_freq) + return -EINVAL; + reg = (28224000 * 2) / freq; + reg = (reg - 1) / 2; + if (reg < 0x100) + *pllreg = reg + 0xc00; + else if (reg < 0x200) + *pllreg = reg + 0x800; + else if (reg < 0x400) + *pllreg = reg & 0x1ff; + else if (reg < 0x800) { + *pllreg = ((reg >> 1) & 0x1ff) + 0x200; + reg &= ~1; + } else { + *pllreg = ((reg >> 2) & 0x1ff) + 0x400; + reg &= ~3; + } + if (realfreq) + *realfreq = (28224000 / (reg + 1)); + return 0; +} + + +#define PCXHR_FREQ_REG_MASK 0x1f +#define PCXHR_FREQ_QUARTZ_48000 0x00 +#define PCXHR_FREQ_QUARTZ_24000 0x01 +#define PCXHR_FREQ_QUARTZ_12000 0x09 +#define PCXHR_FREQ_QUARTZ_32000 0x08 +#define PCXHR_FREQ_QUARTZ_16000 0x04 +#define PCXHR_FREQ_QUARTZ_8000 0x0c +#define PCXHR_FREQ_QUARTZ_44100 0x02 +#define PCXHR_FREQ_QUARTZ_22050 0x0a +#define PCXHR_FREQ_QUARTZ_11025 0x06 +#define PCXHR_FREQ_PLL 0x05 +#define PCXHR_FREQ_QUARTZ_192000 0x10 +#define PCXHR_FREQ_QUARTZ_96000 0x18 +#define PCXHR_FREQ_QUARTZ_176400 0x14 +#define PCXHR_FREQ_QUARTZ_88200 0x1c +#define PCXHR_FREQ_QUARTZ_128000 0x12 +#define PCXHR_FREQ_QUARTZ_64000 0x1a + +#define PCXHR_FREQ_WORD_CLOCK 0x0f +#define PCXHR_FREQ_SYNC_AES 0x0e +#define PCXHR_FREQ_AES_1 0x07 +#define PCXHR_FREQ_AES_2 0x0b +#define PCXHR_FREQ_AES_3 0x03 +#define PCXHR_FREQ_AES_4 0x0d + +static int pcxhr_get_clock_reg(struct pcxhr_mgr *mgr, unsigned int rate, + unsigned int *reg, unsigned int *freq) +{ + unsigned int val, realfreq, pllreg; + struct pcxhr_rmh rmh; + int err; + + realfreq = rate; + switch (mgr->use_clock_type) { + case PCXHR_CLOCK_TYPE_INTERNAL : /* clock by quartz or pll */ + switch (rate) { + case 48000 : val = PCXHR_FREQ_QUARTZ_48000; break; + case 24000 : val = PCXHR_FREQ_QUARTZ_24000; break; + case 12000 : val = PCXHR_FREQ_QUARTZ_12000; break; + case 32000 : val = PCXHR_FREQ_QUARTZ_32000; break; + case 16000 : val = PCXHR_FREQ_QUARTZ_16000; break; + case 8000 : val = PCXHR_FREQ_QUARTZ_8000; break; + case 44100 : val = PCXHR_FREQ_QUARTZ_44100; break; + case 22050 : val = PCXHR_FREQ_QUARTZ_22050; break; + case 11025 : val = PCXHR_FREQ_QUARTZ_11025; break; + case 192000 : val = PCXHR_FREQ_QUARTZ_192000; break; + case 96000 : val = PCXHR_FREQ_QUARTZ_96000; break; + case 176400 : val = PCXHR_FREQ_QUARTZ_176400; break; + case 88200 : val = PCXHR_FREQ_QUARTZ_88200; break; + case 128000 : val = PCXHR_FREQ_QUARTZ_128000; break; + case 64000 : val = PCXHR_FREQ_QUARTZ_64000; break; + default : + val = PCXHR_FREQ_PLL; + /* get the value for the pll register */ + err = pcxhr_pll_freq_register(rate, 110000, &pllreg, + &realfreq); + if (err) + return err; + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd[0] |= IO_NUM_REG_GENCLK; + rmh.cmd[1] = pllreg & MASK_DSP_WORD; + rmh.cmd[2] = pllreg >> 24; + rmh.cmd_len = 3; + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) { + dev_err(&mgr->pci->dev, + "error CMD_ACCESS_IO_WRITE " + "for PLL register : %x!\n", err); + return err; + } + } + break; + case PCXHR_CLOCK_TYPE_WORD_CLOCK: + val = PCXHR_FREQ_WORD_CLOCK; + break; + case PCXHR_CLOCK_TYPE_AES_SYNC: + val = PCXHR_FREQ_SYNC_AES; + break; + case PCXHR_CLOCK_TYPE_AES_1: + val = PCXHR_FREQ_AES_1; + break; + case PCXHR_CLOCK_TYPE_AES_2: + val = PCXHR_FREQ_AES_2; + break; + case PCXHR_CLOCK_TYPE_AES_3: + val = PCXHR_FREQ_AES_3; + break; + case PCXHR_CLOCK_TYPE_AES_4: + val = PCXHR_FREQ_AES_4; + break; + default: + return -EINVAL; + } + *reg = val; + *freq = realfreq; + return 0; +} + + +static int pcxhr_sub_set_clock(struct pcxhr_mgr *mgr, + unsigned int rate, + int *changed) +{ + unsigned int val, realfreq, speed; + struct pcxhr_rmh rmh; + int err; + + err = pcxhr_get_clock_reg(mgr, rate, &val, &realfreq); + if (err) + return err; + + /* codec speed modes */ + if (rate < 55000) + speed = 0; /* single speed */ + else if (rate < 100000) + speed = 1; /* dual speed */ + else + speed = 2; /* quad speed */ + if (mgr->codec_speed != speed) { + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); /* mute outputs */ + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + if (DSP_EXT_CMD_SET(mgr)) { + rmh.cmd[1] = 1; + rmh.cmd_len = 2; + } + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); /* set speed ratio */ + rmh.cmd[0] |= IO_NUM_SPEED_RATIO; + rmh.cmd[1] = speed; + rmh.cmd_len = 2; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + } + /* set the new frequency */ + dev_dbg(&mgr->pci->dev, "clock register : set %x\n", val); + err = pcxhr_write_io_num_reg_cont(mgr, PCXHR_FREQ_REG_MASK, + val, changed); + if (err) + return err; + + mgr->sample_rate_real = realfreq; + mgr->cur_clock_type = mgr->use_clock_type; + + /* unmute after codec speed modes */ + if (mgr->codec_speed != speed) { + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); /* unmute outputs */ + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + if (DSP_EXT_CMD_SET(mgr)) { + rmh.cmd[1] = 1; + rmh.cmd_len = 2; + } + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + mgr->codec_speed = speed; /* save new codec speed */ + } + + dev_dbg(&mgr->pci->dev, "%s to %dHz (realfreq=%d)\n", __func__, + rate, realfreq); + return 0; +} + +#define PCXHR_MODIFY_CLOCK_S_BIT 0x04 + +#define PCXHR_IRQ_TIMER_FREQ 92000 +#define PCXHR_IRQ_TIMER_PERIOD 48 + +int pcxhr_set_clock(struct pcxhr_mgr *mgr, unsigned int rate) +{ + struct pcxhr_rmh rmh; + int err, changed; + + if (rate == 0) + return 0; /* nothing to do */ + + if (mgr->is_hr_stereo) + err = hr222_sub_set_clock(mgr, rate, &changed); + else + err = pcxhr_sub_set_clock(mgr, rate, &changed); + + if (err) + return err; + + if (changed) { + pcxhr_init_rmh(&rmh, CMD_MODIFY_CLOCK); + rmh.cmd[0] |= PCXHR_MODIFY_CLOCK_S_BIT; /* resync fifos */ + if (rate < PCXHR_IRQ_TIMER_FREQ) + rmh.cmd[1] = PCXHR_IRQ_TIMER_PERIOD; + else + rmh.cmd[1] = PCXHR_IRQ_TIMER_PERIOD * 2; + rmh.cmd[2] = rate; + rmh.cmd_len = 3; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + } + return 0; +} + + +static int pcxhr_sub_get_external_clock(struct pcxhr_mgr *mgr, + enum pcxhr_clock_type clock_type, + int *sample_rate) +{ + struct pcxhr_rmh rmh; + unsigned char reg; + int err, rate; + + switch (clock_type) { + case PCXHR_CLOCK_TYPE_WORD_CLOCK: + reg = REG_STATUS_WORD_CLOCK; + break; + case PCXHR_CLOCK_TYPE_AES_SYNC: + reg = REG_STATUS_AES_SYNC; + break; + case PCXHR_CLOCK_TYPE_AES_1: + reg = REG_STATUS_AES_1; + break; + case PCXHR_CLOCK_TYPE_AES_2: + reg = REG_STATUS_AES_2; + break; + case PCXHR_CLOCK_TYPE_AES_3: + reg = REG_STATUS_AES_3; + break; + case PCXHR_CLOCK_TYPE_AES_4: + reg = REG_STATUS_AES_4; + break; + default: + return -EINVAL; + } + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); + rmh.cmd_len = 2; + rmh.cmd[0] |= IO_NUM_REG_STATUS; + if (mgr->last_reg_stat != reg) { + rmh.cmd[1] = reg; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + udelay(100); /* wait minimum 2 sample_frames at 32kHz ! */ + mgr->last_reg_stat = reg; + } + rmh.cmd[1] = REG_STATUS_CURRENT; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + switch (rmh.stat[1] & 0x0f) { + case REG_STATUS_SYNC_32000 : rate = 32000; break; + case REG_STATUS_SYNC_44100 : rate = 44100; break; + case REG_STATUS_SYNC_48000 : rate = 48000; break; + case REG_STATUS_SYNC_64000 : rate = 64000; break; + case REG_STATUS_SYNC_88200 : rate = 88200; break; + case REG_STATUS_SYNC_96000 : rate = 96000; break; + case REG_STATUS_SYNC_128000 : rate = 128000; break; + case REG_STATUS_SYNC_176400 : rate = 176400; break; + case REG_STATUS_SYNC_192000 : rate = 192000; break; + default: rate = 0; + } + dev_dbg(&mgr->pci->dev, "External clock is at %d Hz\n", rate); + *sample_rate = rate; + return 0; +} + + +int pcxhr_get_external_clock(struct pcxhr_mgr *mgr, + enum pcxhr_clock_type clock_type, + int *sample_rate) +{ + if (mgr->is_hr_stereo) + return hr222_get_external_clock(mgr, clock_type, + sample_rate); + else + return pcxhr_sub_get_external_clock(mgr, clock_type, + sample_rate); +} + +/* + * start or stop playback/capture substream + */ +static int pcxhr_set_stream_state(struct snd_pcxhr *chip, + struct pcxhr_stream *stream) +{ + int err; + struct pcxhr_rmh rmh; + int stream_mask, start; + + if (stream->status == PCXHR_STREAM_STATUS_SCHEDULE_RUN) + start = 1; + else { + if (stream->status != PCXHR_STREAM_STATUS_SCHEDULE_STOP) { + dev_err(chip->card->dev, + "%s CANNOT be stopped\n", __func__); + return -EINVAL; + } + start = 0; + } + if (!stream->substream) + return -EINVAL; + + stream->timer_abs_periods = 0; + stream->timer_period_frag = 0; /* reset theoretical stream pos */ + stream->timer_buf_periods = 0; + stream->timer_is_synced = 0; + + stream_mask = + stream->pipe->is_capture ? 1 : 1<substream->number; + + pcxhr_init_rmh(&rmh, start ? CMD_START_STREAM : CMD_STOP_STREAM); + pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture, + stream->pipe->first_audio, 0, stream_mask); + + chip = snd_pcm_substream_chip(stream->substream); + + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + dev_err(chip->card->dev, + "ERROR %s err=%x;\n", __func__, err); + stream->status = + start ? PCXHR_STREAM_STATUS_STARTED : PCXHR_STREAM_STATUS_STOPPED; + return err; +} + +#define HEADER_FMT_BASE_LIN 0xfed00000 +#define HEADER_FMT_BASE_FLOAT 0xfad00000 +#define HEADER_FMT_INTEL 0x00008000 +#define HEADER_FMT_24BITS 0x00004000 +#define HEADER_FMT_16BITS 0x00002000 +#define HEADER_FMT_UPTO11 0x00000200 +#define HEADER_FMT_UPTO32 0x00000100 +#define HEADER_FMT_MONO 0x00000080 + +static int pcxhr_set_format(struct pcxhr_stream *stream) +{ + int err, is_capture, sample_rate, stream_num; + struct snd_pcxhr *chip; + struct pcxhr_rmh rmh; + unsigned int header; + + chip = snd_pcm_substream_chip(stream->substream); + switch (stream->format) { + case SNDRV_PCM_FORMAT_U8: + header = HEADER_FMT_BASE_LIN; + break; + case SNDRV_PCM_FORMAT_S16_LE: + header = HEADER_FMT_BASE_LIN | + HEADER_FMT_16BITS | HEADER_FMT_INTEL; + break; + case SNDRV_PCM_FORMAT_S16_BE: + header = HEADER_FMT_BASE_LIN | HEADER_FMT_16BITS; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + header = HEADER_FMT_BASE_LIN | + HEADER_FMT_24BITS | HEADER_FMT_INTEL; + break; + case SNDRV_PCM_FORMAT_S24_3BE: + header = HEADER_FMT_BASE_LIN | HEADER_FMT_24BITS; + break; + case SNDRV_PCM_FORMAT_FLOAT_LE: + header = HEADER_FMT_BASE_FLOAT | HEADER_FMT_INTEL; + break; + default: + dev_err(chip->card->dev, + "error %s() : unknown format\n", __func__); + return -EINVAL; + } + + sample_rate = chip->mgr->sample_rate; + if (sample_rate <= 32000 && sample_rate !=0) { + if (sample_rate <= 11025) + header |= HEADER_FMT_UPTO11; + else + header |= HEADER_FMT_UPTO32; + } + if (stream->channels == 1) + header |= HEADER_FMT_MONO; + + is_capture = stream->pipe->is_capture; + stream_num = is_capture ? 0 : stream->substream->number; + + pcxhr_init_rmh(&rmh, is_capture ? + CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT); + pcxhr_set_pipe_cmd_params(&rmh, is_capture, stream->pipe->first_audio, + stream_num, 0); + if (is_capture) { + /* bug with old dsp versions: */ + /* bit 12 also sets the format of the playback stream */ + if (DSP_EXT_CMD_SET(chip->mgr)) + rmh.cmd[0] |= 1<<10; + else + rmh.cmd[0] |= 1<<12; + } + rmh.cmd[1] = 0; + rmh.cmd_len = 2; + if (DSP_EXT_CMD_SET(chip->mgr)) { + /* add channels and set bit 19 if channels>2 */ + rmh.cmd[1] = stream->channels; + if (!is_capture) { + /* playback : add channel mask to command */ + rmh.cmd[2] = (stream->channels == 1) ? 0x01 : 0x03; + rmh.cmd_len = 3; + } + } + rmh.cmd[rmh.cmd_len++] = header >> 8; + rmh.cmd[rmh.cmd_len++] = (header & 0xff) << 16; + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + dev_err(chip->card->dev, + "ERROR %s err=%x;\n", __func__, err); + return err; +} + +static int pcxhr_update_r_buffer(struct pcxhr_stream *stream) +{ + int err, is_capture, stream_num; + struct pcxhr_rmh rmh; + struct snd_pcm_substream *subs = stream->substream; + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + + is_capture = (subs->stream == SNDRV_PCM_STREAM_CAPTURE); + stream_num = is_capture ? 0 : subs->number; + + dev_dbg(chip->card->dev, + "%s(pcm%c%d) : addr(%p) bytes(%zx) subs(%d)\n", __func__, + is_capture ? 'c' : 'p', + chip->chip_idx, (void *)(long)subs->runtime->dma_addr, + subs->runtime->dma_bytes, subs->number); + + pcxhr_init_rmh(&rmh, CMD_UPDATE_R_BUFFERS); + pcxhr_set_pipe_cmd_params(&rmh, is_capture, stream->pipe->first_audio, + stream_num, 0); + + /* max buffer size is 2 MByte */ + snd_BUG_ON(subs->runtime->dma_bytes >= 0x200000); + /* size in bits */ + rmh.cmd[1] = subs->runtime->dma_bytes * 8; + /* most significant byte */ + rmh.cmd[2] = subs->runtime->dma_addr >> 24; + /* this is a circular buffer */ + rmh.cmd[2] |= 1<<19; + /* least 3 significant bytes */ + rmh.cmd[3] = subs->runtime->dma_addr & MASK_DSP_WORD; + rmh.cmd_len = 4; + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + dev_err(chip->card->dev, + "ERROR CMD_UPDATE_R_BUFFERS err=%x;\n", err); + return err; +} + + +#if 0 +static int pcxhr_pipe_sample_count(struct pcxhr_stream *stream, + snd_pcm_uframes_t *sample_count) +{ + struct pcxhr_rmh rmh; + int err; + pcxhr_t *chip = snd_pcm_substream_chip(stream->substream); + pcxhr_init_rmh(&rmh, CMD_PIPE_SAMPLE_COUNT); + pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture, 0, 0, + 1<pipe->first_audio); + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err == 0) { + *sample_count = ((snd_pcm_uframes_t)rmh.stat[0]) << 24; + *sample_count += (snd_pcm_uframes_t)rmh.stat[1]; + } + dev_dbg(chip->card->dev, "PIPE_SAMPLE_COUNT = %lx\n", *sample_count); + return err; +} +#endif + +static inline int pcxhr_stream_scheduled_get_pipe(struct pcxhr_stream *stream, + struct pcxhr_pipe **pipe) +{ + if (stream->status == PCXHR_STREAM_STATUS_SCHEDULE_RUN) { + *pipe = stream->pipe; + return 1; + } + return 0; +} + +static void pcxhr_start_linked_stream(struct pcxhr_mgr *mgr) +{ + int i, j, err; + struct pcxhr_pipe *pipe; + struct snd_pcxhr *chip; + int capture_mask = 0; + int playback_mask = 0; + +#ifdef CONFIG_SND_DEBUG_VERBOSE + ktime_t start_time, stop_time, diff_time; + + start_time = ktime_get(); +#endif + guard(mutex)(&mgr->setup_mutex); + + /* check the pipes concerned and build pipe_array */ + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_capt; j++) { + if (pcxhr_stream_scheduled_get_pipe(&chip->capture_stream[j], &pipe)) + capture_mask |= (1 << pipe->first_audio); + } + for (j = 0; j < chip->nb_streams_play; j++) { + if (pcxhr_stream_scheduled_get_pipe(&chip->playback_stream[j], &pipe)) { + playback_mask |= (1 << pipe->first_audio); + break; /* add only once, as all playback + * streams of one chip use the same pipe + */ + } + } + } + if (capture_mask == 0 && playback_mask == 0) { + dev_err(&mgr->pci->dev, "%s : no pipes\n", __func__); + return; + } + + dev_dbg(&mgr->pci->dev, "%s : playback_mask=%x capture_mask=%x\n", + __func__, playback_mask, capture_mask); + + /* synchronous stop of all the pipes concerned */ + err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 0); + if (err) { + dev_err(&mgr->pci->dev, "%s : " + "error stop pipes (P%x C%x)\n", + __func__, playback_mask, capture_mask); + return; + } + + /* the dsp lost format and buffer info with the stop pipe */ + for (i = 0; i < mgr->num_cards; i++) { + struct pcxhr_stream *stream; + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_capt; j++) { + stream = &chip->capture_stream[j]; + if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) { + err = pcxhr_set_format(stream); + err = pcxhr_update_r_buffer(stream); + } + } + for (j = 0; j < chip->nb_streams_play; j++) { + stream = &chip->playback_stream[j]; + if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) { + err = pcxhr_set_format(stream); + err = pcxhr_update_r_buffer(stream); + } + } + } + /* start all the streams */ + for (i = 0; i < mgr->num_cards; i++) { + struct pcxhr_stream *stream; + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_capt; j++) { + stream = &chip->capture_stream[j]; + if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) + err = pcxhr_set_stream_state(chip, stream); + } + for (j = 0; j < chip->nb_streams_play; j++) { + stream = &chip->playback_stream[j]; + if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) + err = pcxhr_set_stream_state(chip, stream); + } + } + + /* synchronous start of all the pipes concerned */ + err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 1); + if (err) { + dev_err(&mgr->pci->dev, "%s : " + "error start pipes (P%x C%x)\n", + __func__, playback_mask, capture_mask); + return; + } + + /* put the streams into the running state now + * (increment pointer by interrupt) + */ + guard(mutex)(&mgr->lock); + for ( i =0; i < mgr->num_cards; i++) { + struct pcxhr_stream *stream; + chip = mgr->chip[i]; + for(j = 0; j < chip->nb_streams_capt; j++) { + stream = &chip->capture_stream[j]; + if(stream->status == PCXHR_STREAM_STATUS_STARTED) + stream->status = PCXHR_STREAM_STATUS_RUNNING; + } + for (j = 0; j < chip->nb_streams_play; j++) { + stream = &chip->playback_stream[j]; + if (stream->status == PCXHR_STREAM_STATUS_STARTED) { + /* playback will already have advanced ! */ + stream->timer_period_frag += mgr->granularity; + stream->status = PCXHR_STREAM_STATUS_RUNNING; + } + } + } + +#ifdef CONFIG_SND_DEBUG_VERBOSE + stop_time = ktime_get(); + diff_time = ktime_sub(stop_time, start_time); + dev_dbg(&mgr->pci->dev, "***TRIGGER START*** TIME = %ld (err = %x)\n", + (long)(ktime_to_ns(diff_time)), err); +#endif +} + + +/* + * trigger callback + */ +static int pcxhr_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct pcxhr_stream *stream; + struct snd_pcm_substream *s; + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev_dbg(chip->card->dev, "SNDRV_PCM_TRIGGER_START\n"); + if (snd_pcm_stream_linked(subs)) { + snd_pcm_group_for_each_entry(s, subs) { + if (snd_pcm_substream_chip(s) != chip) + continue; + stream = s->runtime->private_data; + stream->status = + PCXHR_STREAM_STATUS_SCHEDULE_RUN; + snd_pcm_trigger_done(s, subs); + } + pcxhr_start_linked_stream(chip->mgr); + } else { + stream = subs->runtime->private_data; + dev_dbg(chip->card->dev, "Only one Substream %c %d\n", + stream->pipe->is_capture ? 'C' : 'P', + stream->pipe->first_audio); + if (pcxhr_set_format(stream)) + return -EINVAL; + if (pcxhr_update_r_buffer(stream)) + return -EINVAL; + + stream->status = PCXHR_STREAM_STATUS_SCHEDULE_RUN; + if (pcxhr_set_stream_state(chip, stream)) + return -EINVAL; + stream->status = PCXHR_STREAM_STATUS_RUNNING; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(chip->card->dev, "SNDRV_PCM_TRIGGER_STOP\n"); + snd_pcm_group_for_each_entry(s, subs) { + stream = s->runtime->private_data; + stream->status = PCXHR_STREAM_STATUS_SCHEDULE_STOP; + if (pcxhr_set_stream_state(chip, stream)) + return -EINVAL; + snd_pcm_trigger_done(s, subs); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* TODO */ + default: + return -EINVAL; + } + return 0; +} + + +static int pcxhr_hardware_timer(struct pcxhr_mgr *mgr, int start) +{ + struct pcxhr_rmh rmh; + int err; + + pcxhr_init_rmh(&rmh, CMD_SET_TIMER_INTERRUPT); + if (start) { + /* last dsp time invalid */ + mgr->dsp_time_last = PCXHR_DSP_TIME_INVALID; + rmh.cmd[0] |= mgr->granularity; + } + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) + dev_err(&mgr->pci->dev, "error %s err(%x)\n", __func__, + err); + return err; +} + +/* + * prepare callback for all pcms + */ +static int pcxhr_prepare(struct snd_pcm_substream *subs) +{ + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct pcxhr_mgr *mgr = chip->mgr; + int err = 0; + + dev_dbg(chip->card->dev, + "%s : period_size(%lx) periods(%x) buffer_size(%lx)\n", __func__, + subs->runtime->period_size, subs->runtime->periods, + subs->runtime->buffer_size); + + guard(mutex)(&mgr->setup_mutex); + + do { + /* only the first stream can choose the sample rate */ + /* set the clock only once (first stream) */ + if (mgr->sample_rate != subs->runtime->rate) { + err = pcxhr_set_clock(mgr, subs->runtime->rate); + if (err) + break; + if (mgr->sample_rate == 0) + /* start the DSP-timer */ + err = pcxhr_hardware_timer(mgr, 1); + mgr->sample_rate = subs->runtime->rate; + } + } while(0); /* do only once (so we can use break instead of goto) */ + + return err; +} + + +/* + * HW_PARAMS callback for all pcms + */ +static int pcxhr_hw_params(struct snd_pcm_substream *subs, + struct snd_pcm_hw_params *hw) +{ + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct pcxhr_mgr *mgr = chip->mgr; + struct pcxhr_stream *stream = subs->runtime->private_data; + + guard(mutex)(&mgr->setup_mutex); + + /* set up channels */ + stream->channels = params_channels(hw); + /* set up format for the stream */ + stream->format = params_format(hw); + + return 0; +} + + +/* + * CONFIGURATION SPACE for all pcms, mono pcm must update channels_max + */ +static const struct snd_pcm_hardware pcxhr_caps = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_FLOAT_LE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_192000), + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (32*1024), + /* 1 byte == 1 frame U8 mono (PCXHR_GRANULARITY is frames!) */ + .period_bytes_min = (2*PCXHR_GRANULARITY), + .period_bytes_max = (16*1024), + .periods_min = 2, + .periods_max = (32*1024/PCXHR_GRANULARITY), +}; + + +static int pcxhr_open(struct snd_pcm_substream *subs) +{ + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct pcxhr_mgr *mgr = chip->mgr; + struct snd_pcm_runtime *runtime = subs->runtime; + struct pcxhr_stream *stream; + int err; + + guard(mutex)(&mgr->setup_mutex); + + /* copy the struct snd_pcm_hardware struct */ + runtime->hw = pcxhr_caps; + + if( subs->stream == SNDRV_PCM_STREAM_PLAYBACK ) { + dev_dbg(chip->card->dev, "%s playback chip%d subs%d\n", + __func__, chip->chip_idx, subs->number); + stream = &chip->playback_stream[subs->number]; + } else { + dev_dbg(chip->card->dev, "%s capture chip%d subs%d\n", + __func__, chip->chip_idx, subs->number); + if (mgr->mono_capture) + runtime->hw.channels_max = 1; + else + runtime->hw.channels_min = 2; + stream = &chip->capture_stream[subs->number]; + } + if (stream->status != PCXHR_STREAM_STATUS_FREE){ + /* streams in use */ + dev_err(chip->card->dev, "%s chip%d subs%d in use\n", + __func__, chip->chip_idx, subs->number); + return -EBUSY; + } + + /* float format support is in some cases buggy on stereo cards */ + if (mgr->is_hr_stereo) + runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_FLOAT_LE; + + /* buffer-size should better be multiple of period-size */ + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + /* if a sample rate is already used or fixed by external clock, + * the stream cannot change + */ + if (mgr->sample_rate) + runtime->hw.rate_min = runtime->hw.rate_max = mgr->sample_rate; + else { + if (mgr->use_clock_type != PCXHR_CLOCK_TYPE_INTERNAL) { + int external_rate; + if (pcxhr_get_external_clock(mgr, mgr->use_clock_type, + &external_rate) || + external_rate == 0) { + /* cannot detect the external clock rate */ + return -EBUSY; + } + runtime->hw.rate_min = external_rate; + runtime->hw.rate_max = external_rate; + } + } + + stream->status = PCXHR_STREAM_STATUS_OPEN; + stream->substream = subs; + stream->channels = 0; /* not configured yet */ + + runtime->private_data = stream; + + /* better get a divisor of granularity values (96 or 192) */ + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32); + snd_pcm_set_sync(subs); + + mgr->ref_count_rate++; + + return 0; +} + + +static int pcxhr_close(struct snd_pcm_substream *subs) +{ + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct pcxhr_mgr *mgr = chip->mgr; + struct pcxhr_stream *stream = subs->runtime->private_data; + + guard(mutex)(&mgr->setup_mutex); + + dev_dbg(chip->card->dev, "%s chip%d subs%d\n", __func__, + chip->chip_idx, subs->number); + + /* sample rate released */ + if (--mgr->ref_count_rate == 0) { + mgr->sample_rate = 0; /* the sample rate is no more locked */ + pcxhr_hardware_timer(mgr, 0); /* stop the DSP-timer */ + } + + stream->status = PCXHR_STREAM_STATUS_FREE; + stream->substream = NULL; + + return 0; +} + + +static snd_pcm_uframes_t pcxhr_stream_pointer(struct snd_pcm_substream *subs) +{ + u_int32_t timer_period_frag; + int timer_buf_periods; + struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + struct pcxhr_stream *stream = runtime->private_data; + + guard(mutex)(&chip->mgr->lock); + + /* get the period fragment and the nb of periods in the buffer */ + timer_period_frag = stream->timer_period_frag; + timer_buf_periods = stream->timer_buf_periods; + + return (snd_pcm_uframes_t)((timer_buf_periods * runtime->period_size) + + timer_period_frag); +} + + +static const struct snd_pcm_ops pcxhr_ops = { + .open = pcxhr_open, + .close = pcxhr_close, + .prepare = pcxhr_prepare, + .hw_params = pcxhr_hw_params, + .trigger = pcxhr_trigger, + .pointer = pcxhr_stream_pointer, +}; + +/* + */ +int pcxhr_create_pcm(struct snd_pcxhr *chip) +{ + int err; + struct snd_pcm *pcm; + char name[32]; + + snprintf(name, sizeof(name), "pcxhr %d", chip->chip_idx); + err = snd_pcm_new(chip->card, name, 0, + chip->nb_streams_play, + chip->nb_streams_capt, &pcm); + if (err < 0) { + dev_err(chip->card->dev, "cannot create pcm %s\n", name); + return err; + } + pcm->private_data = chip; + + if (chip->nb_streams_play) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcxhr_ops); + if (chip->nb_streams_capt) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcxhr_ops); + + pcm->info_flags = 0; + pcm->nonatomic = true; + strscpy(pcm->name, name); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->mgr->pci->dev, + 32*1024, 32*1024); + chip->pcm = pcm; + return 0; +} + +static int pcxhr_chip_free(struct snd_pcxhr *chip) +{ + kfree(chip); + return 0; +} + +static int pcxhr_chip_dev_free(struct snd_device *device) +{ + struct snd_pcxhr *chip = device->device_data; + return pcxhr_chip_free(chip); +} + + +/* + */ +static int pcxhr_create(struct pcxhr_mgr *mgr, + struct snd_card *card, int idx) +{ + int err; + struct snd_pcxhr *chip; + static const struct snd_device_ops ops = { + .dev_free = pcxhr_chip_dev_free, + }; + + chip = kzalloc_obj(*chip); + if (!chip) + return -ENOMEM; + + chip->card = card; + chip->chip_idx = idx; + chip->mgr = mgr; + card->sync_irq = mgr->irq; + + if (idx < mgr->playback_chips) + /* stereo or mono streams */ + chip->nb_streams_play = PCXHR_PLAYBACK_STREAMS; + + if (idx < mgr->capture_chips) { + if (mgr->mono_capture) + chip->nb_streams_capt = 2; /* 2 mono streams */ + else + chip->nb_streams_capt = 1; /* or 1 stereo stream */ + } + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + pcxhr_chip_free(chip); + return err; + } + + mgr->chip[idx] = chip; + + return 0; +} + +/* proc interface */ +static void pcxhr_proc_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcxhr *chip = entry->private_data; + struct pcxhr_mgr *mgr = chip->mgr; + + snd_iprintf(buffer, "\n%s\n", mgr->name); + + /* stats available when embedded DSP is running */ + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) { + struct pcxhr_rmh rmh; + short ver_maj = (mgr->dsp_version >> 16) & 0xff; + short ver_min = (mgr->dsp_version >> 8) & 0xff; + short ver_build = mgr->dsp_version & 0xff; + snd_iprintf(buffer, "module version %s\n", + PCXHR_DRIVER_VERSION_STRING); + snd_iprintf(buffer, "dsp version %d.%d.%d\n", + ver_maj, ver_min, ver_build); + if (mgr->board_has_analog) + snd_iprintf(buffer, "analog io available\n"); + else + snd_iprintf(buffer, "digital only board\n"); + + /* calc cpu load of the dsp */ + pcxhr_init_rmh(&rmh, CMD_GET_DSP_RESOURCES); + if( ! pcxhr_send_msg(mgr, &rmh) ) { + int cur = rmh.stat[0]; + int ref = rmh.stat[1]; + if (ref > 0) { + if (mgr->sample_rate_real != 0 && + mgr->sample_rate_real != 48000) { + ref = (ref * 48000) / + mgr->sample_rate_real; + if (mgr->sample_rate_real >= + PCXHR_IRQ_TIMER_FREQ) + ref *= 2; + } + cur = 100 - (100 * cur) / ref; + snd_iprintf(buffer, "cpu load %d%%\n", cur); + snd_iprintf(buffer, "buffer pool %d/%d\n", + rmh.stat[2], rmh.stat[3]); + } + } + snd_iprintf(buffer, "dma granularity : %d\n", + mgr->granularity); + snd_iprintf(buffer, "dsp time errors : %d\n", + mgr->dsp_time_err); + snd_iprintf(buffer, "dsp async pipe xrun errors : %d\n", + mgr->async_err_pipe_xrun); + snd_iprintf(buffer, "dsp async stream xrun errors : %d\n", + mgr->async_err_stream_xrun); + snd_iprintf(buffer, "dsp async last other error : %x\n", + mgr->async_err_other_last); + /* debug zone dsp */ + rmh.cmd[0] = 0x4200 + PCXHR_SIZE_MAX_STATUS; + rmh.cmd_len = 1; + rmh.stat_len = PCXHR_SIZE_MAX_STATUS; + rmh.dsp_stat = 0; + rmh.cmd_idx = CMD_LAST_INDEX; + if( ! pcxhr_send_msg(mgr, &rmh) ) { + int i; + if (rmh.stat_len > 8) + rmh.stat_len = 8; + for (i = 0; i < rmh.stat_len; i++) + snd_iprintf(buffer, "debug[%02d] = %06x\n", + i, rmh.stat[i]); + } + } else + snd_iprintf(buffer, "no firmware loaded\n"); + snd_iprintf(buffer, "\n"); +} +static void pcxhr_proc_sync(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcxhr *chip = entry->private_data; + struct pcxhr_mgr *mgr = chip->mgr; + static const char *textsHR22[3] = { + "Internal", "AES Sync", "AES 1" + }; + static const char *textsPCXHR[7] = { + "Internal", "Word", "AES Sync", + "AES 1", "AES 2", "AES 3", "AES 4" + }; + const char **texts; + int max_clock; + if (mgr->is_hr_stereo) { + texts = textsHR22; + max_clock = HR22_CLOCK_TYPE_MAX; + } else { + texts = textsPCXHR; + max_clock = PCXHR_CLOCK_TYPE_MAX; + } + + snd_iprintf(buffer, "\n%s\n", mgr->name); + snd_iprintf(buffer, "Current Sample Clock\t: %s\n", + texts[mgr->cur_clock_type]); + snd_iprintf(buffer, "Current Sample Rate\t= %d\n", + mgr->sample_rate_real); + /* commands available when embedded DSP is running */ + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) { + int i, err, sample_rate; + for (i = 1; i <= max_clock; i++) { + err = pcxhr_get_external_clock(mgr, i, &sample_rate); + if (err) + break; + snd_iprintf(buffer, "%s Clock\t\t= %d\n", + texts[i], sample_rate); + } + } else + snd_iprintf(buffer, "no firmware loaded\n"); + snd_iprintf(buffer, "\n"); +} + +static void pcxhr_proc_gpio_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcxhr *chip = entry->private_data; + struct pcxhr_mgr *mgr = chip->mgr; + /* commands available when embedded DSP is running */ + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) { + /* gpio ports on stereo boards only available */ + int value = 0; + hr222_read_gpio(mgr, 1, &value); /* GPI */ + snd_iprintf(buffer, "GPI: 0x%x\n", value); + hr222_read_gpio(mgr, 0, &value); /* GP0 */ + snd_iprintf(buffer, "GPO: 0x%x\n", value); + } else + snd_iprintf(buffer, "no firmware loaded\n"); + snd_iprintf(buffer, "\n"); +} +static void pcxhr_proc_gpo_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcxhr *chip = entry->private_data; + struct pcxhr_mgr *mgr = chip->mgr; + char line[64]; + int value; + /* commands available when embedded DSP is running */ + if (!(mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX))) + return; + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "GPO: 0x%x", &value) != 1) + continue; + hr222_write_gpo(mgr, value); /* GP0 */ + } +} + +/* Access to the results of the CMD_GET_TIME_CODE RMH */ +#define TIME_CODE_VALID_MASK 0x00800000 +#define TIME_CODE_NEW_MASK 0x00400000 +#define TIME_CODE_BACK_MASK 0x00200000 +#define TIME_CODE_WAIT_MASK 0x00100000 + +/* Values for the CMD_MANAGE_SIGNAL RMH */ +#define MANAGE_SIGNAL_TIME_CODE 0x01 +#define MANAGE_SIGNAL_MIDI 0x02 + +/* linear time code read proc*/ +static void pcxhr_proc_ltc(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcxhr *chip = entry->private_data; + struct pcxhr_mgr *mgr = chip->mgr; + struct pcxhr_rmh rmh; + unsigned int ltcHrs, ltcMin, ltcSec, ltcFrm; + int err; + /* commands available when embedded DSP is running */ + if (!(mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX))) { + snd_iprintf(buffer, "no firmware loaded\n"); + return; + } + if (!mgr->capture_ltc) { + pcxhr_init_rmh(&rmh, CMD_MANAGE_SIGNAL); + rmh.cmd[0] |= MANAGE_SIGNAL_TIME_CODE; + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + snd_iprintf(buffer, "ltc not activated (%d)\n", err); + return; + } + if (mgr->is_hr_stereo) + hr222_manage_timecode(mgr, 1); + else + pcxhr_write_io_num_reg_cont(mgr, REG_CONT_VALSMPTE, + REG_CONT_VALSMPTE, NULL); + mgr->capture_ltc = 1; + } + pcxhr_init_rmh(&rmh, CMD_GET_TIME_CODE); + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + snd_iprintf(buffer, "ltc read error (err=%d)\n", err); + return ; + } + ltcHrs = 10*((rmh.stat[0] >> 8) & 0x3) + (rmh.stat[0] & 0xf); + ltcMin = 10*((rmh.stat[1] >> 16) & 0x7) + ((rmh.stat[1] >> 8) & 0xf); + ltcSec = 10*(rmh.stat[1] & 0x7) + ((rmh.stat[2] >> 16) & 0xf); + ltcFrm = 10*((rmh.stat[2] >> 8) & 0x3) + (rmh.stat[2] & 0xf); + + snd_iprintf(buffer, "timecode: %02u:%02u:%02u-%02u\n", + ltcHrs, ltcMin, ltcSec, ltcFrm); + snd_iprintf(buffer, "raw: 0x%04x%06x%06x\n", rmh.stat[0] & 0x00ffff, + rmh.stat[1] & 0xffffff, rmh.stat[2] & 0xffffff); + /*snd_iprintf(buffer, "dsp ref time: 0x%06x%06x\n", + rmh.stat[3] & 0xffffff, rmh.stat[4] & 0xffffff);*/ + if (!(rmh.stat[0] & TIME_CODE_VALID_MASK)) { + snd_iprintf(buffer, "warning: linear timecode not valid\n"); + } +} + +static void pcxhr_proc_init(struct snd_pcxhr *chip) +{ + snd_card_ro_proc_new(chip->card, "info", chip, pcxhr_proc_info); + snd_card_ro_proc_new(chip->card, "sync", chip, pcxhr_proc_sync); + /* gpio available on stereo sound cards only */ + if (chip->mgr->is_hr_stereo) + snd_card_rw_proc_new(chip->card, "gpio", chip, + pcxhr_proc_gpio_read, + pcxhr_proc_gpo_write); + snd_card_ro_proc_new(chip->card, "ltc", chip, pcxhr_proc_ltc); +} +/* end of proc interface */ + +/* + * release all the cards assigned to a manager instance + */ +static int pcxhr_free(struct pcxhr_mgr *mgr) +{ + unsigned int i; + + for (i = 0; i < mgr->num_cards; i++) { + if (mgr->chip[i]) + snd_card_free(mgr->chip[i]->card); + } + + /* reset board if some firmware was loaded */ + if(mgr->dsp_loaded) { + pcxhr_reset_board(mgr); + dev_dbg(&mgr->pci->dev, "reset pcxhr !\n"); + } + + /* release irq */ + if (mgr->irq >= 0) + free_irq(mgr->irq, mgr); + + pci_release_regions(mgr->pci); + + /* free hostport purgebuffer */ + if (mgr->hostport.area) { + snd_dma_free_pages(&mgr->hostport); + mgr->hostport.area = NULL; + } + + kfree(mgr->prmh); + + pci_disable_device(mgr->pci); + kfree(mgr); + return 0; +} + +/* + * probe function - creates the card manager + */ +static int pcxhr_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct pcxhr_mgr *mgr; + unsigned int i; + int err; + size_t size; + char *card_name; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (! enable[dev]) { + dev++; + return -ENOENT; + } + + /* enable PCI device */ + err = pci_enable_device(pci); + if (err < 0) + return err; + pci_set_master(pci); + + /* check if we can restrict PCI DMA transfers to 32 bits */ + if (dma_set_mask(&pci->dev, DMA_BIT_MASK(32)) < 0) { + dev_err(&pci->dev, + "architecture does not support 32bit PCI busmaster DMA\n"); + pci_disable_device(pci); + return -ENXIO; + } + + /* alloc card manager */ + mgr = kzalloc_obj(*mgr); + if (! mgr) { + pci_disable_device(pci); + return -ENOMEM; + } + + if (snd_BUG_ON(pci_id->driver_data >= PCI_ID_LAST)) { + kfree(mgr); + pci_disable_device(pci); + return -ENODEV; + } + card_name = + pcxhr_board_params[pci_id->driver_data].board_name; + mgr->playback_chips = + pcxhr_board_params[pci_id->driver_data].playback_chips; + mgr->capture_chips = + pcxhr_board_params[pci_id->driver_data].capture_chips; + mgr->fw_file_set = + pcxhr_board_params[pci_id->driver_data].fw_file_set; + mgr->firmware_num = + pcxhr_board_params[pci_id->driver_data].firmware_num; + mgr->mono_capture = mono[dev]; + mgr->is_hr_stereo = (mgr->playback_chips == 1); + mgr->board_has_aes1 = PCXHR_BOARD_HAS_AES1(mgr); + mgr->board_aes_in_192k = !PCXHR_BOARD_AESIN_NO_192K(mgr); + + if (mgr->is_hr_stereo) + mgr->granularity = PCXHR_GRANULARITY_HR22; + else + mgr->granularity = PCXHR_GRANULARITY; + + /* resource assignment */ + err = pci_request_regions(pci, card_name); + if (err < 0) { + kfree(mgr); + pci_disable_device(pci); + return err; + } + for (i = 0; i < 3; i++) + mgr->port[i] = pci_resource_start(pci, i); + + mgr->pci = pci; + mgr->irq = -1; + + /* ISR lock */ + mutex_init(&mgr->lock); + mutex_init(&mgr->msg_lock); + + /* init setup mutex*/ + mutex_init(&mgr->setup_mutex); + + if (request_threaded_irq(pci->irq, pcxhr_interrupt, + pcxhr_threaded_irq, IRQF_SHARED, + KBUILD_MODNAME, mgr)) { + dev_err(&pci->dev, "unable to grab IRQ %d\n", pci->irq); + pcxhr_free(mgr); + return -EBUSY; + } + mgr->irq = pci->irq; + + snprintf(mgr->name, sizeof(mgr->name), + "Digigram at 0x%lx & 0x%lx, 0x%lx irq %i", + mgr->port[0], mgr->port[1], mgr->port[2], mgr->irq); + + mgr->prmh = kmalloc(sizeof(*mgr->prmh) + + sizeof(u32) * (PCXHR_SIZE_MAX_LONG_STATUS - + PCXHR_SIZE_MAX_STATUS), + GFP_KERNEL); + if (! mgr->prmh) { + pcxhr_free(mgr); + return -ENOMEM; + } + + for (i=0; i < PCXHR_MAX_CARDS; i++) { + struct snd_card *card; + char tmpid[16]; + int idx; + + if (i >= max(mgr->playback_chips, mgr->capture_chips)) + break; + mgr->num_cards++; + + if (index[dev] < 0) + idx = index[dev]; + else + idx = index[dev] + i; + + snprintf(tmpid, sizeof(tmpid), "%s-%d", + id[dev] ? id[dev] : card_name, i); + err = snd_card_new(&pci->dev, idx, tmpid, THIS_MODULE, + 0, &card); + + if (err < 0) { + dev_err(&pci->dev, "cannot allocate the card %d\n", i); + pcxhr_free(mgr); + return err; + } + + strscpy(card->driver, DRIVER_NAME); + snprintf(card->shortname, sizeof(card->shortname), + "Digigram [PCM #%d]", i); + snprintf(card->longname, sizeof(card->longname), + "%s [PCM #%d]", mgr->name, i); + + err = pcxhr_create(mgr, card, i); + if (err < 0) { + snd_card_free(card); + pcxhr_free(mgr); + return err; + } + + if (i == 0) + /* init proc interface only for chip0 */ + pcxhr_proc_init(mgr->chip[i]); + + err = snd_card_register(card); + if (err < 0) { + pcxhr_free(mgr); + return err; + } + } + + /* create hostport purgebuffer */ + size = PAGE_ALIGN(sizeof(struct pcxhr_hostport)); + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + size, &mgr->hostport) < 0) { + pcxhr_free(mgr); + return -ENOMEM; + } + /* init purgebuffer */ + memset(mgr->hostport.area, 0, size); + + /* create a DSP loader */ + err = pcxhr_setup_firmware(mgr); + if (err < 0) { + pcxhr_free(mgr); + return err; + } + + pci_set_drvdata(pci, mgr); + dev++; + return 0; +} + +static void pcxhr_remove(struct pci_dev *pci) +{ + pcxhr_free(pci_get_drvdata(pci)); +} + +static struct pci_driver pcxhr_driver = { + .name = KBUILD_MODNAME, + .id_table = pcxhr_ids, + .probe = pcxhr_probe, + .remove = pcxhr_remove, +}; + +module_pci_driver(pcxhr_driver); diff --git a/sound/pci/pcxhr/pcxhr.h b/sound/pci/pcxhr/pcxhr.h new file mode 100644 index 000000000..011227b1f --- /dev/null +++ b/sound/pci/pcxhr/pcxhr.h @@ -0,0 +1,201 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram pcxhr soundcards + * + * main header file + * + * Copyright (c) 2004 by Digigram + */ + +#ifndef __SOUND_PCXHR_H +#define __SOUND_PCXHR_H + +#include +#include +#include + +#define PCXHR_DRIVER_VERSION 0x000906 /* 0.9.6 */ +#define PCXHR_DRIVER_VERSION_STRING "0.9.6" /* 0.9.6 */ + + +#define PCXHR_MAX_CARDS 6 +#define PCXHR_PLAYBACK_STREAMS 4 + +#define PCXHR_GRANULARITY 96 /* min 96 and multiple of 48 */ +/* transfer granularity of pipes and the dsp time (MBOX4) */ +#define PCXHR_GRANULARITY_MIN 96 +/* TODO : granularity could be 64 or 128 */ +#define PCXHR_GRANULARITY_HR22 192 /* granularity for stereo cards */ + +struct snd_pcxhr; +struct pcxhr_mgr; + +struct pcxhr_stream; +struct pcxhr_pipe; + +enum pcxhr_clock_type { + PCXHR_CLOCK_TYPE_INTERNAL = 0, + PCXHR_CLOCK_TYPE_WORD_CLOCK, + PCXHR_CLOCK_TYPE_AES_SYNC, + PCXHR_CLOCK_TYPE_AES_1, + PCXHR_CLOCK_TYPE_AES_2, + PCXHR_CLOCK_TYPE_AES_3, + PCXHR_CLOCK_TYPE_AES_4, + PCXHR_CLOCK_TYPE_MAX = PCXHR_CLOCK_TYPE_AES_4, + HR22_CLOCK_TYPE_INTERNAL = PCXHR_CLOCK_TYPE_INTERNAL, + HR22_CLOCK_TYPE_AES_SYNC, + HR22_CLOCK_TYPE_AES_1, + HR22_CLOCK_TYPE_MAX = HR22_CLOCK_TYPE_AES_1, +}; + +struct pcxhr_mgr { + unsigned int num_cards; + struct snd_pcxhr *chip[PCXHR_MAX_CARDS]; + + struct pci_dev *pci; + + int irq; + + int granularity; + + /* card access with 1 mem bar and 2 io bar's */ + unsigned long port[3]; + + /* share the name */ + char name[40]; /* name of this soundcard */ + + struct pcxhr_rmh *prmh; + + struct mutex lock; /* interrupt lock */ + struct mutex msg_lock; /* message lock */ + + struct mutex setup_mutex; /* mutex used in hw_params, open and close */ + struct mutex mixer_mutex; /* mutex for mixer */ + + /* hardware interface */ + unsigned int dsp_loaded; /* bit flags of loaded dsp indices */ + unsigned int dsp_version; /* read from embedded once firmware is loaded */ + int playback_chips; + int capture_chips; + int fw_file_set; + int firmware_num; + unsigned int is_hr_stereo:1; + unsigned int board_has_aes1:1; /* if 1 board has AES1 plug and SRC */ + unsigned int board_has_analog:1; /* if 0 the board is digital only */ + unsigned int board_has_mic:1; /* if 1 the board has microphone input */ + unsigned int board_aes_in_192k:1;/* if 1 the aes input plugs do support 192kHz */ + unsigned int mono_capture:1; /* if 1 the board does mono capture */ + unsigned int capture_ltc:1; /* if 1 the board captures LTC input */ + + struct snd_dma_buffer hostport; + + enum pcxhr_clock_type use_clock_type; /* clock type selected by mixer */ + enum pcxhr_clock_type cur_clock_type; /* current clock type synced */ + int sample_rate; + int ref_count_rate; + int timer_toggle; /* timer interrupt toggles between the two values 0x200 and 0x300 */ + int dsp_time_last; /* the last dsp time (read by interrupt) */ + int dsp_time_err; /* dsp time errors */ + unsigned int src_it_dsp; /* dsp interrupt source */ + unsigned int io_num_reg_cont; /* backup of IO_NUM_REG_CONT */ + unsigned int codec_speed; /* speed mode of the codecs */ + unsigned int sample_rate_real; /* current real sample rate */ + int last_reg_stat; + int async_err_stream_xrun; + int async_err_pipe_xrun; + int async_err_other_last; + + unsigned char xlx_cfg; /* copy of PCXHR_XLX_CFG register */ + unsigned char xlx_selmic; /* copy of PCXHR_XLX_SELMIC register */ + unsigned char dsp_reset; /* copy of PCXHR_DSP_RESET register */ +}; + + +enum pcxhr_stream_status { + PCXHR_STREAM_STATUS_FREE, + PCXHR_STREAM_STATUS_OPEN, + PCXHR_STREAM_STATUS_SCHEDULE_RUN, + PCXHR_STREAM_STATUS_STARTED, + PCXHR_STREAM_STATUS_RUNNING, + PCXHR_STREAM_STATUS_SCHEDULE_STOP, + PCXHR_STREAM_STATUS_STOPPED, + PCXHR_STREAM_STATUS_PAUSED +}; + +struct pcxhr_stream { + struct snd_pcm_substream *substream; + snd_pcm_format_t format; + struct pcxhr_pipe *pipe; + + enum pcxhr_stream_status status; /* free, open, running, draining, pause */ + + u_int64_t timer_abs_periods; /* timer: samples elapsed since TRIGGER_START (multiple of period_size) */ + u_int32_t timer_period_frag; /* timer: samples elapsed since last call to snd_pcm_period_elapsed (0..period_size) */ + u_int32_t timer_buf_periods; /* nb of periods in the buffer that have already elapsed */ + int timer_is_synced; /* if(0) : timer needs to be resynced with real hardware pointer */ + + int channels; +}; + + +enum pcxhr_pipe_status { + PCXHR_PIPE_UNDEFINED, + PCXHR_PIPE_DEFINED +}; + +struct pcxhr_pipe { + enum pcxhr_pipe_status status; + int is_capture; /* this is a capture pipe */ + int first_audio; /* first audio num */ +}; + + +struct snd_pcxhr { + struct snd_card *card; + struct pcxhr_mgr *mgr; + int chip_idx; /* zero based */ + + struct snd_pcm *pcm; /* PCM */ + + struct pcxhr_pipe playback_pipe; /* 1 stereo pipe only */ + struct pcxhr_pipe capture_pipe[2]; /* 1 stereo or 2 mono pipes */ + + struct pcxhr_stream playback_stream[PCXHR_PLAYBACK_STREAMS]; + struct pcxhr_stream capture_stream[2]; /* 1 stereo or 2 mono streams */ + int nb_streams_play; + int nb_streams_capt; + + int analog_playback_active[2]; /* Mixer : Master Playback !mute */ + int analog_playback_volume[2]; /* Mixer : Master Playback Volume */ + int analog_capture_volume[2]; /* Mixer : Master Capture Volume */ + int digital_playback_active[PCXHR_PLAYBACK_STREAMS][2]; + int digital_playback_volume[PCXHR_PLAYBACK_STREAMS][2]; + int digital_capture_volume[2]; /* Mixer : Digital Capture Volume */ + int monitoring_active[2]; /* Mixer : Monitoring Active */ + int monitoring_volume[2]; /* Mixer : Monitoring Volume */ + int audio_capture_source; /* Mixer : Audio Capture Source */ + int mic_volume; /* used by cards with MIC only */ + int mic_boost; /* used by cards with MIC only */ + int mic_active; /* used by cards with MIC only */ + int analog_capture_active; /* used by cards with MIC only */ + int phantom_power; /* used by cards with MIC only */ + + unsigned char aes_bits[5]; /* Mixer : IEC958_AES bits */ +}; + +struct pcxhr_hostport +{ + char purgebuffer[6]; + char reserved[2]; +}; + +/* exported */ +int pcxhr_pll_freq_register(unsigned int freq, unsigned int max_freq, + unsigned int *pllreg, unsigned int *realfreq); +int pcxhr_create_pcm(struct snd_pcxhr *chip); +int pcxhr_set_clock(struct pcxhr_mgr *mgr, unsigned int rate); +int pcxhr_get_external_clock(struct pcxhr_mgr *mgr, + enum pcxhr_clock_type clock_type, + int *sample_rate); + +#endif /* __SOUND_PCXHR_H */ diff --git a/sound/pci/pcxhr/pcxhr_core.c b/sound/pci/pcxhr/pcxhr_core.c new file mode 100644 index 000000000..b3b9ab4f3 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_core.c @@ -0,0 +1,1326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram pcxhr compatible soundcards + * + * low level interface with interrupt and message handling implementation + * + * Copyright (c) 2004 by Digigram + */ + +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_mixer.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" + + +/* registers used on the PLX (port 1) */ +#define PCXHR_PLX_OFFSET_MIN 0x40 +#define PCXHR_PLX_MBOX0 0x40 +#define PCXHR_PLX_MBOX1 0x44 +#define PCXHR_PLX_MBOX2 0x48 +#define PCXHR_PLX_MBOX3 0x4C +#define PCXHR_PLX_MBOX4 0x50 +#define PCXHR_PLX_MBOX5 0x54 +#define PCXHR_PLX_MBOX6 0x58 +#define PCXHR_PLX_MBOX7 0x5C +#define PCXHR_PLX_L2PCIDB 0x64 +#define PCXHR_PLX_IRQCS 0x68 +#define PCXHR_PLX_CHIPSC 0x6C + +/* registers used on the DSP (port 2) */ +#define PCXHR_DSP_ICR 0x00 +#define PCXHR_DSP_CVR 0x04 +#define PCXHR_DSP_ISR 0x08 +#define PCXHR_DSP_IVR 0x0C +#define PCXHR_DSP_RXH 0x14 +#define PCXHR_DSP_TXH 0x14 +#define PCXHR_DSP_RXM 0x18 +#define PCXHR_DSP_TXM 0x18 +#define PCXHR_DSP_RXL 0x1C +#define PCXHR_DSP_TXL 0x1C +#define PCXHR_DSP_RESET 0x20 +#define PCXHR_DSP_OFFSET_MAX 0x20 + +/* access to the card */ +#define PCXHR_PLX 1 +#define PCXHR_DSP 2 + +#if (PCXHR_DSP_OFFSET_MAX > PCXHR_PLX_OFFSET_MIN) +#error PCXHR_REG_TO_PORT(x) +#else +#define PCXHR_REG_TO_PORT(x) ((x)>PCXHR_DSP_OFFSET_MAX ? PCXHR_PLX : PCXHR_DSP) +#endif +#define PCXHR_INPB(mgr,x) inb((mgr)->port[PCXHR_REG_TO_PORT(x)] + (x)) +#define PCXHR_INPL(mgr,x) inl((mgr)->port[PCXHR_REG_TO_PORT(x)] + (x)) +#define PCXHR_OUTPB(mgr,x,data) outb((data), (mgr)->port[PCXHR_REG_TO_PORT(x)] + (x)) +#define PCXHR_OUTPL(mgr,x,data) outl((data), (mgr)->port[PCXHR_REG_TO_PORT(x)] + (x)) +/* attention : access the PCXHR_DSP_* registers with inb and outb only ! */ + +/* params used with PCXHR_PLX_MBOX0 */ +#define PCXHR_MBOX0_HF5 (1 << 0) +#define PCXHR_MBOX0_HF4 (1 << 1) +#define PCXHR_MBOX0_BOOT_HERE (1 << 23) +/* params used with PCXHR_PLX_IRQCS */ +#define PCXHR_IRQCS_ENABLE_PCIIRQ (1 << 8) +#define PCXHR_IRQCS_ENABLE_PCIDB (1 << 9) +#define PCXHR_IRQCS_ACTIVE_PCIDB (1 << 13) +/* params used with PCXHR_PLX_CHIPSC */ +#define PCXHR_CHIPSC_INIT_VALUE 0x100D767E +#define PCXHR_CHIPSC_RESET_XILINX (1 << 16) +#define PCXHR_CHIPSC_GPI_USERI (1 << 17) +#define PCXHR_CHIPSC_DATA_CLK (1 << 24) +#define PCXHR_CHIPSC_DATA_IN (1 << 26) + +/* params used with PCXHR_DSP_ICR */ +#define PCXHR_ICR_HI08_RREQ 0x01 +#define PCXHR_ICR_HI08_TREQ 0x02 +#define PCXHR_ICR_HI08_HDRQ 0x04 +#define PCXHR_ICR_HI08_HF0 0x08 +#define PCXHR_ICR_HI08_HF1 0x10 +#define PCXHR_ICR_HI08_HLEND 0x20 +#define PCXHR_ICR_HI08_INIT 0x80 +/* params used with PCXHR_DSP_CVR */ +#define PCXHR_CVR_HI08_HC 0x80 +/* params used with PCXHR_DSP_ISR */ +#define PCXHR_ISR_HI08_RXDF 0x01 +#define PCXHR_ISR_HI08_TXDE 0x02 +#define PCXHR_ISR_HI08_TRDY 0x04 +#define PCXHR_ISR_HI08_ERR 0x08 +#define PCXHR_ISR_HI08_CHK 0x10 +#define PCXHR_ISR_HI08_HREQ 0x80 + + +/* constants used for delay in msec */ +#define PCXHR_WAIT_DEFAULT 2 +#define PCXHR_WAIT_IT 25 +#define PCXHR_WAIT_IT_EXTRA 65 + +/* + * pcxhr_check_reg_bit - wait for the specified bit is set/reset on a register + * @reg: register to check + * @mask: bit mask + * @bit: resultant bit to be checked + * @time: time-out of loop in msec + * + * returns zero if a bit matches, or a negative error code. + */ +static int pcxhr_check_reg_bit(struct pcxhr_mgr *mgr, unsigned int reg, + unsigned char mask, unsigned char bit, int time, + unsigned char* read) +{ + int i = 0; + unsigned long end_time = jiffies + (time * HZ + 999) / 1000; + do { + *read = PCXHR_INPB(mgr, reg); + if ((*read & mask) == bit) { + if (i > 100) + dev_dbg(&mgr->pci->dev, + "ATTENTION! check_reg(%x) loopcount=%d\n", + reg, i); + return 0; + } + i++; + } while (time_after_eq(end_time, jiffies)); + dev_err(&mgr->pci->dev, + "pcxhr_check_reg_bit: timeout, reg=%x, mask=0x%x, val=%x\n", + reg, mask, *read); + return -EIO; +} + +/* constants used with pcxhr_check_reg_bit() */ +#define PCXHR_TIMEOUT_DSP 200 + + +#define PCXHR_MASK_EXTRA_INFO 0x0000FE +#define PCXHR_MASK_IT_HF0 0x000100 +#define PCXHR_MASK_IT_HF1 0x000200 +#define PCXHR_MASK_IT_NO_HF0_HF1 0x000400 +#define PCXHR_MASK_IT_MANAGE_HF5 0x000800 +#define PCXHR_MASK_IT_WAIT 0x010000 +#define PCXHR_MASK_IT_WAIT_EXTRA 0x020000 + +#define PCXHR_IT_SEND_BYTE_XILINX (0x0000003C | PCXHR_MASK_IT_HF0) +#define PCXHR_IT_TEST_XILINX (0x0000003C | PCXHR_MASK_IT_HF1 | \ + PCXHR_MASK_IT_MANAGE_HF5) +#define PCXHR_IT_DOWNLOAD_BOOT (0x0000000C | PCXHR_MASK_IT_HF1 | \ + PCXHR_MASK_IT_MANAGE_HF5 | \ + PCXHR_MASK_IT_WAIT) +#define PCXHR_IT_RESET_BOARD_FUNC (0x0000000C | PCXHR_MASK_IT_HF0 | \ + PCXHR_MASK_IT_MANAGE_HF5 | \ + PCXHR_MASK_IT_WAIT_EXTRA) +#define PCXHR_IT_DOWNLOAD_DSP (0x0000000C | \ + PCXHR_MASK_IT_MANAGE_HF5 | \ + PCXHR_MASK_IT_WAIT) +#define PCXHR_IT_DEBUG (0x0000005A | PCXHR_MASK_IT_NO_HF0_HF1) +#define PCXHR_IT_RESET_SEMAPHORE (0x0000005C | PCXHR_MASK_IT_NO_HF0_HF1) +#define PCXHR_IT_MESSAGE (0x00000074 | PCXHR_MASK_IT_NO_HF0_HF1) +#define PCXHR_IT_RESET_CHK (0x00000076 | PCXHR_MASK_IT_NO_HF0_HF1) +#define PCXHR_IT_UPDATE_RBUFFER (0x00000078 | PCXHR_MASK_IT_NO_HF0_HF1) + +static int pcxhr_send_it_dsp(struct pcxhr_mgr *mgr, + unsigned int itdsp, int atomic) +{ + int err; + unsigned char reg; + + if (itdsp & PCXHR_MASK_IT_MANAGE_HF5) { + /* clear hf5 bit */ + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0, + PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & + ~PCXHR_MBOX0_HF5); + } + if ((itdsp & PCXHR_MASK_IT_NO_HF0_HF1) == 0) { + reg = (PCXHR_ICR_HI08_RREQ | + PCXHR_ICR_HI08_TREQ | + PCXHR_ICR_HI08_HDRQ); + if (itdsp & PCXHR_MASK_IT_HF0) + reg |= PCXHR_ICR_HI08_HF0; + if (itdsp & PCXHR_MASK_IT_HF1) + reg |= PCXHR_ICR_HI08_HF1; + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg); + } + reg = (unsigned char)(((itdsp & PCXHR_MASK_EXTRA_INFO) >> 1) | + PCXHR_CVR_HI08_HC); + PCXHR_OUTPB(mgr, PCXHR_DSP_CVR, reg); + if (itdsp & PCXHR_MASK_IT_WAIT) { + if (atomic) + mdelay(PCXHR_WAIT_IT); + else + msleep(PCXHR_WAIT_IT); + } + if (itdsp & PCXHR_MASK_IT_WAIT_EXTRA) { + if (atomic) + mdelay(PCXHR_WAIT_IT_EXTRA); + else + msleep(PCXHR_WAIT_IT); + } + /* wait for CVR_HI08_HC == 0 */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_CVR, PCXHR_CVR_HI08_HC, 0, + PCXHR_TIMEOUT_DSP, ®); + if (err) { + dev_err(&mgr->pci->dev, "pcxhr_send_it_dsp : TIMEOUT CVR\n"); + return err; + } + if (itdsp & PCXHR_MASK_IT_MANAGE_HF5) { + /* wait for hf5 bit */ + err = pcxhr_check_reg_bit(mgr, PCXHR_PLX_MBOX0, + PCXHR_MBOX0_HF5, + PCXHR_MBOX0_HF5, + PCXHR_TIMEOUT_DSP, + ®); + if (err) { + dev_err(&mgr->pci->dev, + "pcxhr_send_it_dsp : TIMEOUT HF5\n"); + return err; + } + } + return 0; /* retry not handled here */ +} + +void pcxhr_reset_xilinx_com(struct pcxhr_mgr *mgr) +{ + /* reset second xilinx */ + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, + PCXHR_CHIPSC_INIT_VALUE & ~PCXHR_CHIPSC_RESET_XILINX); +} + +static void pcxhr_enable_irq(struct pcxhr_mgr *mgr, int enable) +{ + unsigned int reg = PCXHR_INPL(mgr, PCXHR_PLX_IRQCS); + /* enable/disable interrupts */ + if (enable) + reg |= (PCXHR_IRQCS_ENABLE_PCIIRQ | PCXHR_IRQCS_ENABLE_PCIDB); + else + reg &= ~(PCXHR_IRQCS_ENABLE_PCIIRQ | PCXHR_IRQCS_ENABLE_PCIDB); + PCXHR_OUTPL(mgr, PCXHR_PLX_IRQCS, reg); +} + +void pcxhr_reset_dsp(struct pcxhr_mgr *mgr) +{ + /* disable interrupts */ + pcxhr_enable_irq(mgr, 0); + + /* let's reset the DSP */ + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, 0); + msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */ + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, 3); + msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */ + + /* reset mailbox */ + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0, 0); +} + +void pcxhr_enable_dsp(struct pcxhr_mgr *mgr) +{ + /* enable interrupts */ + pcxhr_enable_irq(mgr, 1); +} + +/* + * load the xilinx image + */ +int pcxhr_load_xilinx_binary(struct pcxhr_mgr *mgr, + const struct firmware *xilinx, int second) +{ + unsigned int i; + unsigned int chipsc; + unsigned char data; + unsigned char mask; + const unsigned char *image; + + /* test first xilinx */ + chipsc = PCXHR_INPL(mgr, PCXHR_PLX_CHIPSC); + /* REV01 cards do not support the PCXHR_CHIPSC_GPI_USERI bit anymore */ + /* this bit will always be 1; + * no possibility to test presence of first xilinx + */ + if(second) { + if ((chipsc & PCXHR_CHIPSC_GPI_USERI) == 0) { + dev_err(&mgr->pci->dev, "error loading first xilinx\n"); + return -EINVAL; + } + /* activate second xilinx */ + chipsc |= PCXHR_CHIPSC_RESET_XILINX; + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */ + } + image = xilinx->data; + for (i = 0; i < xilinx->size; i++, image++) { + data = *image; + mask = 0x80; + while (mask) { + chipsc &= ~(PCXHR_CHIPSC_DATA_CLK | + PCXHR_CHIPSC_DATA_IN); + if (data & mask) + chipsc |= PCXHR_CHIPSC_DATA_IN; + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + chipsc |= PCXHR_CHIPSC_DATA_CLK; + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + mask >>= 1; + } + /* don't take too much time in this loop... */ + cond_resched(); + } + chipsc &= ~(PCXHR_CHIPSC_DATA_CLK | PCXHR_CHIPSC_DATA_IN); + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + /* wait 2 msec (time to boot the xilinx before any access) */ + msleep( PCXHR_WAIT_DEFAULT ); + return 0; +} + +/* + * send an executable file to the DSP + */ +static int pcxhr_download_dsp(struct pcxhr_mgr *mgr, const struct firmware *dsp) +{ + int err; + unsigned int i; + unsigned int len; + const unsigned char *data; + unsigned char dummy; + /* check the length of boot image */ + if (dsp->size <= 0) + return -EINVAL; + if (dsp->size % 3) + return -EINVAL; + if (snd_BUG_ON(!dsp->data)) + return -EINVAL; + /* transfert data buffer from PC to DSP */ + for (i = 0; i < dsp->size; i += 3) { + data = dsp->data + i; + if (i == 0) { + /* test data header consistency */ + len = (unsigned int)((data[0]<<16) + + (data[1]<<8) + + data[2]); + if (len && (dsp->size != (len + 2) * 3)) + return -EINVAL; + } + /* wait DSP ready for new transfer */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, + PCXHR_TIMEOUT_DSP, &dummy); + if (err) { + dev_err(&mgr->pci->dev, + "dsp loading error at position %d\n", i); + return err; + } + /* send host data */ + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, data[0]); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, data[1]); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, data[2]); + + /* don't take too much time in this loop... */ + cond_resched(); + } + /* give some time to boot the DSP */ + msleep(PCXHR_WAIT_DEFAULT); + return 0; +} + +/* + * load the eeprom image + */ +int pcxhr_load_eeprom_binary(struct pcxhr_mgr *mgr, + const struct firmware *eeprom) +{ + int err; + unsigned char reg; + + /* init value of the ICR register */ + reg = PCXHR_ICR_HI08_RREQ | PCXHR_ICR_HI08_TREQ | PCXHR_ICR_HI08_HDRQ; + if (PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & PCXHR_MBOX0_BOOT_HERE) { + /* no need to load the eeprom binary, + * but init the HI08 interface + */ + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg | PCXHR_ICR_HI08_INIT); + msleep(PCXHR_WAIT_DEFAULT); + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg); + msleep(PCXHR_WAIT_DEFAULT); + dev_dbg(&mgr->pci->dev, "no need to load eeprom boot\n"); + return 0; + } + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg); + + err = pcxhr_download_dsp(mgr, eeprom); + if (err) + return err; + /* wait for chk bit */ + return pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, ®); +} + +/* + * load the boot image + */ +int pcxhr_load_boot_binary(struct pcxhr_mgr *mgr, const struct firmware *boot) +{ + int err; + unsigned int physaddr = mgr->hostport.addr; + unsigned char dummy; + + /* send the hostport address to the DSP (only the upper 24 bit !) */ + if (snd_BUG_ON(physaddr & 0xff)) + return -EINVAL; + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX1, (physaddr >> 8)); + + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_DOWNLOAD_BOOT, 0); + if (err) + return err; + /* clear hf5 bit */ + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0, + PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & ~PCXHR_MBOX0_HF5); + + err = pcxhr_download_dsp(mgr, boot); + if (err) + return err; + /* wait for hf5 bit */ + return pcxhr_check_reg_bit(mgr, PCXHR_PLX_MBOX0, PCXHR_MBOX0_HF5, + PCXHR_MBOX0_HF5, PCXHR_TIMEOUT_DSP, &dummy); +} + +/* + * load the final dsp image + */ +int pcxhr_load_dsp_binary(struct pcxhr_mgr *mgr, const struct firmware *dsp) +{ + int err; + unsigned char dummy; + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_BOARD_FUNC, 0); + if (err) + return err; + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_DOWNLOAD_DSP, 0); + if (err) + return err; + err = pcxhr_download_dsp(mgr, dsp); + if (err) + return err; + /* wait for chk bit */ + return pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, + PCXHR_TIMEOUT_DSP, &dummy); +} + + +struct pcxhr_cmd_info { + u32 opcode; /* command word */ + u16 st_length; /* status length */ + u16 st_type; /* status type (RMH_SSIZE_XXX) */ +}; + +/* RMH status type */ +enum { + RMH_SSIZE_FIXED = 0, /* status size fix (st_length = 0..x) */ + RMH_SSIZE_ARG = 1, /* status size given in the LSB byte */ + RMH_SSIZE_MASK = 2, /* status size given in bitmask */ +}; + +/* + * Array of DSP commands + */ +static const struct pcxhr_cmd_info pcxhr_dsp_cmds[] = { +[CMD_VERSION] = { 0x010000, 1, RMH_SSIZE_FIXED }, +[CMD_SUPPORTED] = { 0x020000, 4, RMH_SSIZE_FIXED }, +[CMD_TEST_IT] = { 0x040000, 1, RMH_SSIZE_FIXED }, +[CMD_SEND_IRQA] = { 0x070001, 0, RMH_SSIZE_FIXED }, +[CMD_ACCESS_IO_WRITE] = { 0x090000, 1, RMH_SSIZE_ARG }, +[CMD_ACCESS_IO_READ] = { 0x094000, 1, RMH_SSIZE_ARG }, +[CMD_ASYNC] = { 0x0a0000, 1, RMH_SSIZE_ARG }, +[CMD_MODIFY_CLOCK] = { 0x0d0000, 0, RMH_SSIZE_FIXED }, +[CMD_RESYNC_AUDIO_INPUTS] = { 0x0e0000, 0, RMH_SSIZE_FIXED }, +[CMD_GET_DSP_RESOURCES] = { 0x100000, 4, RMH_SSIZE_FIXED }, +[CMD_SET_TIMER_INTERRUPT] = { 0x110000, 0, RMH_SSIZE_FIXED }, +[CMD_RES_PIPE] = { 0x400000, 0, RMH_SSIZE_FIXED }, +[CMD_FREE_PIPE] = { 0x410000, 0, RMH_SSIZE_FIXED }, +[CMD_CONF_PIPE] = { 0x422101, 0, RMH_SSIZE_FIXED }, +[CMD_STOP_PIPE] = { 0x470004, 0, RMH_SSIZE_FIXED }, +[CMD_PIPE_SAMPLE_COUNT] = { 0x49a000, 2, RMH_SSIZE_FIXED }, +[CMD_CAN_START_PIPE] = { 0x4b0000, 1, RMH_SSIZE_FIXED }, +[CMD_START_STREAM] = { 0x802000, 0, RMH_SSIZE_FIXED }, +[CMD_STREAM_OUT_LEVEL_ADJUST] = { 0x822000, 0, RMH_SSIZE_FIXED }, +[CMD_STOP_STREAM] = { 0x832000, 0, RMH_SSIZE_FIXED }, +[CMD_UPDATE_R_BUFFERS] = { 0x840000, 0, RMH_SSIZE_FIXED }, +[CMD_FORMAT_STREAM_OUT] = { 0x860000, 0, RMH_SSIZE_FIXED }, +[CMD_FORMAT_STREAM_IN] = { 0x870000, 0, RMH_SSIZE_FIXED }, +[CMD_STREAM_SAMPLE_COUNT] = { 0x902000, 2, RMH_SSIZE_FIXED }, +[CMD_AUDIO_LEVEL_ADJUST] = { 0xc22000, 0, RMH_SSIZE_FIXED }, +[CMD_GET_TIME_CODE] = { 0x060000, 5, RMH_SSIZE_FIXED }, +[CMD_MANAGE_SIGNAL] = { 0x0f0000, 0, RMH_SSIZE_FIXED }, +}; + +#ifdef CONFIG_SND_DEBUG_VERBOSE +static const char * const cmd_names[] = { +[CMD_VERSION] = "CMD_VERSION", +[CMD_SUPPORTED] = "CMD_SUPPORTED", +[CMD_TEST_IT] = "CMD_TEST_IT", +[CMD_SEND_IRQA] = "CMD_SEND_IRQA", +[CMD_ACCESS_IO_WRITE] = "CMD_ACCESS_IO_WRITE", +[CMD_ACCESS_IO_READ] = "CMD_ACCESS_IO_READ", +[CMD_ASYNC] = "CMD_ASYNC", +[CMD_MODIFY_CLOCK] = "CMD_MODIFY_CLOCK", +[CMD_RESYNC_AUDIO_INPUTS] = "CMD_RESYNC_AUDIO_INPUTS", +[CMD_GET_DSP_RESOURCES] = "CMD_GET_DSP_RESOURCES", +[CMD_SET_TIMER_INTERRUPT] = "CMD_SET_TIMER_INTERRUPT", +[CMD_RES_PIPE] = "CMD_RES_PIPE", +[CMD_FREE_PIPE] = "CMD_FREE_PIPE", +[CMD_CONF_PIPE] = "CMD_CONF_PIPE", +[CMD_STOP_PIPE] = "CMD_STOP_PIPE", +[CMD_PIPE_SAMPLE_COUNT] = "CMD_PIPE_SAMPLE_COUNT", +[CMD_CAN_START_PIPE] = "CMD_CAN_START_PIPE", +[CMD_START_STREAM] = "CMD_START_STREAM", +[CMD_STREAM_OUT_LEVEL_ADJUST] = "CMD_STREAM_OUT_LEVEL_ADJUST", +[CMD_STOP_STREAM] = "CMD_STOP_STREAM", +[CMD_UPDATE_R_BUFFERS] = "CMD_UPDATE_R_BUFFERS", +[CMD_FORMAT_STREAM_OUT] = "CMD_FORMAT_STREAM_OUT", +[CMD_FORMAT_STREAM_IN] = "CMD_FORMAT_STREAM_IN", +[CMD_STREAM_SAMPLE_COUNT] = "CMD_STREAM_SAMPLE_COUNT", +[CMD_AUDIO_LEVEL_ADJUST] = "CMD_AUDIO_LEVEL_ADJUST", +[CMD_GET_TIME_CODE] = "CMD_GET_TIME_CODE", +[CMD_MANAGE_SIGNAL] = "CMD_MANAGE_SIGNAL", +}; +#endif + + +static int pcxhr_read_rmh_status(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh) +{ + int err; + int i; + u32 data; + u32 size_mask; + unsigned char reg; + int max_stat_len; + + if (rmh->stat_len < PCXHR_SIZE_MAX_STATUS) + max_stat_len = PCXHR_SIZE_MAX_STATUS; + else max_stat_len = rmh->stat_len; + + for (i = 0; i < rmh->stat_len; i++) { + /* wait for receiver full */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_RXDF, + PCXHR_ISR_HI08_RXDF, + PCXHR_TIMEOUT_DSP, ®); + if (err) { + dev_err(&mgr->pci->dev, + "ERROR RMH stat: ISR:RXDF=1 (ISR = %x; i=%d )\n", + reg, i); + return err; + } + /* read data */ + data = PCXHR_INPB(mgr, PCXHR_DSP_TXH) << 16; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXM) << 8; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXL); + + /* need to update rmh->stat_len on the fly ?? */ + if (!i) { + if (rmh->dsp_stat != RMH_SSIZE_FIXED) { + if (rmh->dsp_stat == RMH_SSIZE_ARG) { + rmh->stat_len = (data & 0x0000ff) + 1; + data &= 0xffff00; + } else { + /* rmh->dsp_stat == RMH_SSIZE_MASK */ + rmh->stat_len = 1; + size_mask = data; + while (size_mask) { + if (size_mask & 1) + rmh->stat_len++; + size_mask >>= 1; + } + } + } + } +#ifdef CONFIG_SND_DEBUG_VERBOSE + if (rmh->cmd_idx < CMD_LAST_INDEX) + dev_dbg(&mgr->pci->dev, " stat[%d]=%x\n", i, data); +#endif + if (i < max_stat_len) + rmh->stat[i] = data; + } + if (rmh->stat_len > max_stat_len) { + dev_dbg(&mgr->pci->dev, "PCXHR : rmh->stat_len=%x too big\n", + rmh->stat_len); + rmh->stat_len = max_stat_len; + } + return 0; +} + +static int pcxhr_send_msg_nolock(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh) +{ + int err; + int i; + u32 data; + unsigned char reg; + + if (snd_BUG_ON(rmh->cmd_len >= PCXHR_SIZE_MAX_CMD)) + return -EINVAL; + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_MESSAGE, 1); + if (err) { + dev_err(&mgr->pci->dev, + "pcxhr_send_message : ED_DSP_CRASHED\n"); + return err; + } + /* wait for chk bit */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + /* reset irq chk */ + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_CHK, 1); + if (err) + return err; + /* wait for chk bit == 0*/ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, 0, + PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + + data = rmh->cmd[0]; + + if (rmh->cmd_len > 1) + data |= 0x008000; /* MASK_MORE_THAN_1_WORD_COMMAND */ + else + data &= 0xff7fff; /* MASK_1_WORD_COMMAND */ +#ifdef CONFIG_SND_DEBUG_VERBOSE + if (rmh->cmd_idx < CMD_LAST_INDEX) + dev_dbg(&mgr->pci->dev, "MSG cmd[0]=%x (%s)\n", + data, cmd_names[rmh->cmd_idx]); +#endif + + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF)); + + if (rmh->cmd_len > 1) { + /* send length */ + data = rmh->cmd_len - 1; + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, + PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF)); + + for (i=1; i < rmh->cmd_len; i++) { + /* send other words */ + data = rmh->cmd[i]; +#ifdef CONFIG_SND_DEBUG_VERBOSE + if (rmh->cmd_idx < CMD_LAST_INDEX) + dev_dbg(&mgr->pci->dev, + " cmd[%d]=%x\n", i, data); +#endif + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, + PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF)); + } + } + /* wait for chk bit */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + /* test status ISR */ + if (reg & PCXHR_ISR_HI08_ERR) { + /* ERROR, wait for receiver full */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_RXDF, + PCXHR_ISR_HI08_RXDF, + PCXHR_TIMEOUT_DSP, ®); + if (err) { + dev_err(&mgr->pci->dev, + "ERROR RMH: ISR:RXDF=1 (ISR = %x)\n", reg); + return err; + } + /* read error code */ + data = PCXHR_INPB(mgr, PCXHR_DSP_TXH) << 16; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXM) << 8; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXL); + dev_err(&mgr->pci->dev, "ERROR RMH(%d): 0x%x\n", + rmh->cmd_idx, data); + err = -EINVAL; + } else { + /* read the response data */ + err = pcxhr_read_rmh_status(mgr, rmh); + } + /* reset semaphore */ + if (pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_SEMAPHORE, 1) < 0) + return -EIO; + return err; +} + + +/** + * pcxhr_init_rmh - initialize the RMH instance + * @rmh: the rmh pointer to be initialized + * @cmd: the rmh command to be set + */ +void pcxhr_init_rmh(struct pcxhr_rmh *rmh, int cmd) +{ + if (snd_BUG_ON(cmd >= CMD_LAST_INDEX)) + return; + rmh->cmd[0] = pcxhr_dsp_cmds[cmd].opcode; + rmh->cmd_len = 1; + rmh->stat_len = pcxhr_dsp_cmds[cmd].st_length; + rmh->dsp_stat = pcxhr_dsp_cmds[cmd].st_type; + rmh->cmd_idx = cmd; +} + + +void pcxhr_set_pipe_cmd_params(struct pcxhr_rmh *rmh, int capture, + unsigned int param1, unsigned int param2, + unsigned int param3) +{ + snd_BUG_ON(param1 > MASK_FIRST_FIELD); + if (capture) + rmh->cmd[0] |= 0x800; /* COMMAND_RECORD_MASK */ + if (param1) + rmh->cmd[0] |= (param1 << FIELD_SIZE); + if (param2) { + snd_BUG_ON(param2 > MASK_FIRST_FIELD); + rmh->cmd[0] |= param2; + } + if(param3) { + snd_BUG_ON(param3 > MASK_DSP_WORD); + rmh->cmd[1] = param3; + rmh->cmd_len = 2; + } +} + +/* + * pcxhr_send_msg - send a DSP message with spinlock + * @rmh: the rmh record to send and receive + * + * returns 0 if successful, or a negative error code. + */ +int pcxhr_send_msg(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh) +{ + guard(mutex)(&mgr->msg_lock); + return pcxhr_send_msg_nolock(mgr, rmh); +} + +static inline int pcxhr_pipes_running(struct pcxhr_mgr *mgr) +{ + int start_mask = PCXHR_INPL(mgr, PCXHR_PLX_MBOX2); + /* least segnificant 12 bits are the pipe states + * for the playback audios + * next 12 bits are the pipe states for the capture audios + * (PCXHR_PIPE_STATE_CAPTURE_OFFSET) + */ + start_mask &= 0xffffff; + dev_dbg(&mgr->pci->dev, "CMD_PIPE_STATE MBOX2=0x%06x\n", start_mask); + return start_mask; +} + +#define PCXHR_PIPE_STATE_CAPTURE_OFFSET 12 +#define MAX_WAIT_FOR_DSP 20 + +static int pcxhr_prepair_pipe_start(struct pcxhr_mgr *mgr, + int audio_mask, int *retry) +{ + struct pcxhr_rmh rmh; + int err; + int audio = 0; + + *retry = 0; + while (audio_mask) { + if (audio_mask & 1) { + pcxhr_init_rmh(&rmh, CMD_CAN_START_PIPE); + if (audio < PCXHR_PIPE_STATE_CAPTURE_OFFSET) { + /* can start playback pipe */ + pcxhr_set_pipe_cmd_params(&rmh, 0, audio, 0, 0); + } else { + /* can start capture pipe */ + pcxhr_set_pipe_cmd_params(&rmh, 1, audio - + PCXHR_PIPE_STATE_CAPTURE_OFFSET, + 0, 0); + } + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + dev_err(&mgr->pci->dev, + "error pipe start " + "(CMD_CAN_START_PIPE) err=%x!\n", + err); + return err; + } + /* if the pipe couldn't be prepaired for start, + * retry it later + */ + if (rmh.stat[0] == 0) + *retry |= (1<>=1; + audio++; + } + return 0; +} + +static int pcxhr_stop_pipes(struct pcxhr_mgr *mgr, int audio_mask) +{ + struct pcxhr_rmh rmh; + int err; + int audio = 0; + + while (audio_mask) { + if (audio_mask & 1) { + pcxhr_init_rmh(&rmh, CMD_STOP_PIPE); + if (audio < PCXHR_PIPE_STATE_CAPTURE_OFFSET) { + /* stop playback pipe */ + pcxhr_set_pipe_cmd_params(&rmh, 0, audio, 0, 0); + } else { + /* stop capture pipe */ + pcxhr_set_pipe_cmd_params(&rmh, 1, audio - + PCXHR_PIPE_STATE_CAPTURE_OFFSET, + 0, 0); + } + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + dev_err(&mgr->pci->dev, + "error pipe stop " + "(CMD_STOP_PIPE) err=%x!\n", err); + return err; + } + } + audio_mask>>=1; + audio++; + } + return 0; +} + +static int pcxhr_toggle_pipes(struct pcxhr_mgr *mgr, int audio_mask) +{ + struct pcxhr_rmh rmh; + int err; + int audio = 0; + + while (audio_mask) { + if (audio_mask & 1) { + pcxhr_init_rmh(&rmh, CMD_CONF_PIPE); + if (audio < PCXHR_PIPE_STATE_CAPTURE_OFFSET) + pcxhr_set_pipe_cmd_params(&rmh, 0, 0, 0, + 1 << audio); + else + pcxhr_set_pipe_cmd_params(&rmh, 1, 0, 0, + 1 << (audio - PCXHR_PIPE_STATE_CAPTURE_OFFSET)); + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + dev_err(&mgr->pci->dev, + "error pipe start " + "(CMD_CONF_PIPE) err=%x!\n", err); + return err; + } + } + audio_mask>>=1; + audio++; + } + /* now fire the interrupt on the card */ + pcxhr_init_rmh(&rmh, CMD_SEND_IRQA); + err = pcxhr_send_msg(mgr, &rmh); + if (err) { + dev_err(&mgr->pci->dev, + "error pipe start (CMD_SEND_IRQA) err=%x!\n", + err); + return err; + } + return 0; +} + + + +int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask, + int capture_mask, int start) +{ + int state, i, err; + int audio_mask; + +#ifdef CONFIG_SND_DEBUG_VERBOSE + ktime_t start_time, stop_time, diff_time; + + start_time = ktime_get(); +#endif + audio_mask = (playback_mask | + (capture_mask << PCXHR_PIPE_STATE_CAPTURE_OFFSET)); + /* current pipe state (playback + record) */ + state = pcxhr_pipes_running(mgr); + dev_dbg(&mgr->pci->dev, + "pcxhr_set_pipe_state %s (mask %x current %x)\n", + start ? "START" : "STOP", audio_mask, state); + if (start) { + /* start only pipes that are not yet started */ + audio_mask &= ~state; + state = audio_mask; + for (i = 0; i < MAX_WAIT_FOR_DSP; i++) { + err = pcxhr_prepair_pipe_start(mgr, state, &state); + if (err) + return err; + if (state == 0) + break; /* success, all pipes prepaired */ + mdelay(1); /* wait 1 millisecond and retry */ + } + } else { + audio_mask &= state; /* stop only pipes that are started */ + } + if (audio_mask == 0) + return 0; + + err = pcxhr_toggle_pipes(mgr, audio_mask); + if (err) + return err; + + i = 0; + while (1) { + state = pcxhr_pipes_running(mgr); + /* have all pipes the new state ? */ + if ((state & audio_mask) == (start ? audio_mask : 0)) + break; + if (++i >= MAX_WAIT_FOR_DSP * 100) { + dev_err(&mgr->pci->dev, "error pipe start/stop\n"); + return -EBUSY; + } + udelay(10); /* wait 10 microseconds */ + } + if (!start) { + err = pcxhr_stop_pipes(mgr, audio_mask); + if (err) + return err; + } +#ifdef CONFIG_SND_DEBUG_VERBOSE + stop_time = ktime_get(); + diff_time = ktime_sub(stop_time, start_time); + dev_dbg(&mgr->pci->dev, "***SET PIPE STATE*** TIME = %ld (err = %x)\n", + (long)(ktime_to_ns(diff_time)), err); +#endif + return 0; +} + +int pcxhr_write_io_num_reg_cont(struct pcxhr_mgr *mgr, unsigned int mask, + unsigned int value, int *changed) +{ + struct pcxhr_rmh rmh; + int err; + + guard(mutex)(&mgr->msg_lock); + if ((mgr->io_num_reg_cont & mask) == value) { + dev_dbg(&mgr->pci->dev, + "IO_NUM_REG_CONT mask %x already is set to %x\n", + mask, value); + if (changed) + *changed = 0; + return 0; /* already programmed */ + } + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd[0] |= IO_NUM_REG_CONT; + rmh.cmd[1] = mask; + rmh.cmd[2] = value; + rmh.cmd_len = 3; + err = pcxhr_send_msg_nolock(mgr, &rmh); + if (err == 0) { + mgr->io_num_reg_cont &= ~mask; + mgr->io_num_reg_cont |= value; + if (changed) + *changed = 1; + } + return err; +} + +#define PCXHR_IRQ_TIMER 0x000300 +#define PCXHR_IRQ_FREQ_CHANGE 0x000800 +#define PCXHR_IRQ_TIME_CODE 0x001000 +#define PCXHR_IRQ_NOTIFY 0x002000 +#define PCXHR_IRQ_ASYNC 0x008000 +#define PCXHR_IRQ_MASK 0x00bb00 +#define PCXHR_FATAL_DSP_ERR 0xff0000 + +enum pcxhr_async_err_src { + PCXHR_ERR_PIPE, + PCXHR_ERR_STREAM, + PCXHR_ERR_AUDIO +}; + +static int pcxhr_handle_async_err(struct pcxhr_mgr *mgr, u32 err, + enum pcxhr_async_err_src err_src, int pipe, + int is_capture) +{ + static const char * const err_src_name[] = { + [PCXHR_ERR_PIPE] = "Pipe", + [PCXHR_ERR_STREAM] = "Stream", + [PCXHR_ERR_AUDIO] = "Audio" + }; + + if (err & 0xfff) + err &= 0xfff; + else + err = ((err >> 12) & 0xfff); + if (!err) + return 0; + dev_dbg(&mgr->pci->dev, "CMD_ASYNC : Error %s %s Pipe %d err=%x\n", + err_src_name[err_src], + is_capture ? "Record" : "Play", pipe, err); + if (err == 0xe01) + mgr->async_err_stream_xrun++; + else if (err == 0xe10) + mgr->async_err_pipe_xrun++; + else + mgr->async_err_other_last = (int)err; + return 1; +} + + +static void pcxhr_msg_thread(struct pcxhr_mgr *mgr) +{ + struct pcxhr_rmh *prmh = mgr->prmh; + int err; + int i, j; + + if (mgr->src_it_dsp & PCXHR_IRQ_FREQ_CHANGE) + dev_dbg(&mgr->pci->dev, + "PCXHR_IRQ_FREQ_CHANGE event occurred\n"); + if (mgr->src_it_dsp & PCXHR_IRQ_TIME_CODE) + dev_dbg(&mgr->pci->dev, + "PCXHR_IRQ_TIME_CODE event occurred\n"); + if (mgr->src_it_dsp & PCXHR_IRQ_NOTIFY) + dev_dbg(&mgr->pci->dev, + "PCXHR_IRQ_NOTIFY event occurred\n"); + if (mgr->src_it_dsp & (PCXHR_IRQ_FREQ_CHANGE | PCXHR_IRQ_TIME_CODE)) { + /* clear events FREQ_CHANGE and TIME_CODE */ + pcxhr_init_rmh(prmh, CMD_TEST_IT); + err = pcxhr_send_msg(mgr, prmh); + dev_dbg(&mgr->pci->dev, "CMD_TEST_IT : err=%x, stat=%x\n", + err, prmh->stat[0]); + } + if (mgr->src_it_dsp & PCXHR_IRQ_ASYNC) { + dev_dbg(&mgr->pci->dev, + "PCXHR_IRQ_ASYNC event occurred\n"); + + pcxhr_init_rmh(prmh, CMD_ASYNC); + prmh->cmd[0] |= 1; /* add SEL_ASYNC_EVENTS */ + /* this is the only one extra long response command */ + prmh->stat_len = PCXHR_SIZE_MAX_LONG_STATUS; + err = pcxhr_send_msg(mgr, prmh); + if (err) + dev_err(&mgr->pci->dev, "ERROR pcxhr_msg_thread=%x;\n", + err); + i = 1; + while (i < prmh->stat_len) { + int nb_audio = ((prmh->stat[i] >> FIELD_SIZE) & + MASK_FIRST_FIELD); + int nb_stream = ((prmh->stat[i] >> (2*FIELD_SIZE)) & + MASK_FIRST_FIELD); + int pipe = prmh->stat[i] & MASK_FIRST_FIELD; + int is_capture = prmh->stat[i] & 0x400000; + u32 err2; + + if (prmh->stat[i] & 0x800000) { /* if BIT_END */ + dev_dbg(&mgr->pci->dev, + "TASKLET : End%sPipe %d\n", + is_capture ? "Record" : "Play", + pipe); + } + i++; + err2 = prmh->stat[i] ? prmh->stat[i] : prmh->stat[i+1]; + if (err2) + pcxhr_handle_async_err(mgr, err2, + PCXHR_ERR_PIPE, + pipe, is_capture); + i += 2; + for (j = 0; j < nb_stream; j++) { + err2 = prmh->stat[i] ? + prmh->stat[i] : prmh->stat[i+1]; + if (err2) + pcxhr_handle_async_err(mgr, err2, + PCXHR_ERR_STREAM, + pipe, + is_capture); + i += 2; + } + for (j = 0; j < nb_audio; j++) { + err2 = prmh->stat[i] ? + prmh->stat[i] : prmh->stat[i+1]; + if (err2) + pcxhr_handle_async_err(mgr, err2, + PCXHR_ERR_AUDIO, + pipe, + is_capture); + i += 2; + } + } + } +} + +static u_int64_t pcxhr_stream_read_position(struct pcxhr_mgr *mgr, + struct pcxhr_stream *stream) +{ + u_int64_t hw_sample_count; + struct pcxhr_rmh rmh; + int err, stream_mask; + + stream_mask = stream->pipe->is_capture ? 1 : 1<substream->number; + + /* get sample count for one stream */ + pcxhr_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT); + pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture, + stream->pipe->first_audio, 0, stream_mask); + /* rmh.stat_len = 2; */ /* 2 resp data for each stream of the pipe */ + + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return 0; + + hw_sample_count = ((u_int64_t)rmh.stat[0]) << 24; + hw_sample_count += (u_int64_t)rmh.stat[1]; + + dev_dbg(&mgr->pci->dev, + "stream %c%d : abs samples real(%llu) timer(%llu)\n", + stream->pipe->is_capture ? 'C' : 'P', + stream->substream->number, + hw_sample_count, + stream->timer_abs_periods + stream->timer_period_frag + + mgr->granularity); + return hw_sample_count; +} + +static void pcxhr_update_timer_pos(struct pcxhr_mgr *mgr, + struct pcxhr_stream *stream, + int samples_to_add) +{ + if (stream->substream && + (stream->status == PCXHR_STREAM_STATUS_RUNNING)) { + u_int64_t new_sample_count; + int elapsed = 0; + int hardware_read = 0; + struct snd_pcm_runtime *runtime = stream->substream->runtime; + + if (samples_to_add < 0) { + stream->timer_is_synced = 0; + /* add default if no hardware_read possible */ + samples_to_add = mgr->granularity; + } + + if (!stream->timer_is_synced) { + if ((stream->timer_abs_periods != 0) || + ((stream->timer_period_frag + samples_to_add) >= + runtime->period_size)) { + new_sample_count = + pcxhr_stream_read_position(mgr, stream); + hardware_read = 1; + if (new_sample_count >= mgr->granularity) { + /* sub security offset because of + * jitter and finer granularity of + * dsp time (MBOX4) + */ + new_sample_count -= mgr->granularity; + stream->timer_is_synced = 1; + } + } + } + if (!hardware_read) { + /* if we didn't try to sync the position, increment it + * by PCXHR_GRANULARITY every timer interrupt + */ + new_sample_count = stream->timer_abs_periods + + stream->timer_period_frag + samples_to_add; + } + while (1) { + u_int64_t new_elapse_pos = stream->timer_abs_periods + + runtime->period_size; + if (new_elapse_pos > new_sample_count) + break; + elapsed = 1; + stream->timer_buf_periods++; + if (stream->timer_buf_periods >= runtime->periods) + stream->timer_buf_periods = 0; + stream->timer_abs_periods = new_elapse_pos; + } + if (new_sample_count >= stream->timer_abs_periods) { + stream->timer_period_frag = + (u_int32_t)(new_sample_count - + stream->timer_abs_periods); + } else { + dev_err(&mgr->pci->dev, + "ERROR new_sample_count too small ??? %ld\n", + (long unsigned int)new_sample_count); + } + + if (elapsed) { + mutex_unlock(&mgr->lock); + snd_pcm_period_elapsed(stream->substream); + mutex_lock(&mgr->lock); + } + } +} + +irqreturn_t pcxhr_interrupt(int irq, void *dev_id) +{ + struct pcxhr_mgr *mgr = dev_id; + unsigned int reg; + bool wake_thread = false; + + reg = PCXHR_INPL(mgr, PCXHR_PLX_IRQCS); + if (! (reg & PCXHR_IRQCS_ACTIVE_PCIDB)) { + /* this device did not cause the interrupt */ + return IRQ_NONE; + } + + /* clear interrupt */ + reg = PCXHR_INPL(mgr, PCXHR_PLX_L2PCIDB); + PCXHR_OUTPL(mgr, PCXHR_PLX_L2PCIDB, reg); + + /* timer irq occurred */ + if (reg & PCXHR_IRQ_TIMER) { + int timer_toggle = reg & PCXHR_IRQ_TIMER; + if (timer_toggle == mgr->timer_toggle) { + dev_dbg(&mgr->pci->dev, "ERROR TIMER TOGGLE\n"); + mgr->dsp_time_err++; + } + + mgr->timer_toggle = timer_toggle; + mgr->src_it_dsp = reg; + wake_thread = true; + } + + /* other irq's handled in the thread */ + if (reg & PCXHR_IRQ_MASK) { + if (reg & PCXHR_IRQ_ASYNC) { + /* as we didn't request any async notifications, + * some kind of xrun error will probably occurred + */ + /* better resynchronize all streams next interrupt : */ + mgr->dsp_time_last = PCXHR_DSP_TIME_INVALID; + } + mgr->src_it_dsp = reg; + wake_thread = true; + } +#ifdef CONFIG_SND_DEBUG_VERBOSE + if (reg & PCXHR_FATAL_DSP_ERR) + dev_dbg(&mgr->pci->dev, "FATAL DSP ERROR : %x\n", reg); +#endif + + return wake_thread ? IRQ_WAKE_THREAD : IRQ_HANDLED; +} + +irqreturn_t pcxhr_threaded_irq(int irq, void *dev_id) +{ + struct pcxhr_mgr *mgr = dev_id; + int i, j; + struct snd_pcxhr *chip; + + guard(mutex)(&mgr->lock); + if (mgr->src_it_dsp & PCXHR_IRQ_TIMER) { + /* is a 24 bit counter */ + int dsp_time_new = + PCXHR_INPL(mgr, PCXHR_PLX_MBOX4) & PCXHR_DSP_TIME_MASK; + int dsp_time_diff = dsp_time_new - mgr->dsp_time_last; + + if ((dsp_time_diff < 0) && + (mgr->dsp_time_last != PCXHR_DSP_TIME_INVALID)) { + /* handle dsp counter wraparound without resync */ + int tmp_diff = dsp_time_diff + PCXHR_DSP_TIME_MASK + 1; + dev_dbg(&mgr->pci->dev, + "WARNING DSP timestamp old(%d) new(%d)", + mgr->dsp_time_last, dsp_time_new); + if (tmp_diff > 0 && tmp_diff <= (2*mgr->granularity)) { + dev_dbg(&mgr->pci->dev, + "-> timestamp wraparound OK: " + "diff=%d\n", tmp_diff); + dsp_time_diff = tmp_diff; + } else { + dev_dbg(&mgr->pci->dev, + "-> resynchronize all streams\n"); + mgr->dsp_time_err++; + } + } +#ifdef CONFIG_SND_DEBUG_VERBOSE + if (dsp_time_diff == 0) + dev_dbg(&mgr->pci->dev, + "ERROR DSP TIME NO DIFF time(%d)\n", + dsp_time_new); + else if (dsp_time_diff >= (2*mgr->granularity)) + dev_dbg(&mgr->pci->dev, + "ERROR DSP TIME TOO BIG old(%d) add(%d)\n", + mgr->dsp_time_last, + dsp_time_new - mgr->dsp_time_last); + else if (dsp_time_diff % mgr->granularity) + dev_dbg(&mgr->pci->dev, + "ERROR DSP TIME increased by %d\n", + dsp_time_diff); +#endif + mgr->dsp_time_last = dsp_time_new; + + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_capt; j++) + pcxhr_update_timer_pos(mgr, + &chip->capture_stream[j], + dsp_time_diff); + } + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + for (j = 0; j < chip->nb_streams_play; j++) + pcxhr_update_timer_pos(mgr, + &chip->playback_stream[j], + dsp_time_diff); + } + } + + pcxhr_msg_thread(mgr); + return IRQ_HANDLED; +} diff --git a/sound/pci/pcxhr/pcxhr_core.h b/sound/pci/pcxhr/pcxhr_core.h new file mode 100644 index 000000000..d8319b983 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_core.h @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * low level interface with interrupt and message handling + * + * Copyright (c) 2004 by Digigram + */ + +#ifndef __SOUND_PCXHR_CORE_H +#define __SOUND_PCXHR_CORE_H + +struct firmware; +struct pcxhr_mgr; + +/* init and firmware download commands */ +void pcxhr_reset_xilinx_com(struct pcxhr_mgr *mgr); +void pcxhr_reset_dsp(struct pcxhr_mgr *mgr); +void pcxhr_enable_dsp(struct pcxhr_mgr *mgr); +int pcxhr_load_xilinx_binary(struct pcxhr_mgr *mgr, const struct firmware *xilinx, int second); +int pcxhr_load_eeprom_binary(struct pcxhr_mgr *mgr, const struct firmware *eeprom); +int pcxhr_load_boot_binary(struct pcxhr_mgr *mgr, const struct firmware *boot); +int pcxhr_load_dsp_binary(struct pcxhr_mgr *mgr, const struct firmware *dsp); + +/* DSP time available on MailBox4 register : 24 bit time samples() */ +#define PCXHR_DSP_TIME_MASK 0x00ffffff +#define PCXHR_DSP_TIME_INVALID 0x10000000 + + +#define PCXHR_SIZE_MAX_CMD 8 +#define PCXHR_SIZE_MAX_STATUS 16 +#define PCXHR_SIZE_MAX_LONG_STATUS 256 + +struct pcxhr_rmh { + u16 cmd_len; /* length of the command to send (WORDs) */ + u16 stat_len; /* length of the status received (WORDs) */ + u16 dsp_stat; /* status type, RMP_SSIZE_XXX */ + u16 cmd_idx; /* index of the command */ + u32 cmd[PCXHR_SIZE_MAX_CMD]; + u32 stat[PCXHR_SIZE_MAX_STATUS]; +}; + +enum { + CMD_VERSION, /* cmd_len = 2 stat_len = 1 */ + CMD_SUPPORTED, /* cmd_len = 1 stat_len = 4 */ + CMD_TEST_IT, /* cmd_len = 1 stat_len = 1 */ + CMD_SEND_IRQA, /* cmd_len = 1 stat_len = 0 */ + CMD_ACCESS_IO_WRITE, /* cmd_len >= 1 stat_len >= 1 */ + CMD_ACCESS_IO_READ, /* cmd_len >= 1 stat_len >= 1 */ + CMD_ASYNC, /* cmd_len = 1 stat_len = 1 */ + CMD_MODIFY_CLOCK, /* cmd_len = 3 stat_len = 0 */ + CMD_RESYNC_AUDIO_INPUTS, /* cmd_len = 1 stat_len = 0 */ + CMD_GET_DSP_RESOURCES, /* cmd_len = 1 stat_len = 4 */ + CMD_SET_TIMER_INTERRUPT, /* cmd_len = 1 stat_len = 0 */ + CMD_RES_PIPE, /* cmd_len >=2 stat_len = 0 */ + CMD_FREE_PIPE, /* cmd_len = 1 stat_len = 0 */ + CMD_CONF_PIPE, /* cmd_len = 2 stat_len = 0 */ + CMD_STOP_PIPE, /* cmd_len = 1 stat_len = 0 */ + CMD_PIPE_SAMPLE_COUNT, /* cmd_len = 2 stat_len = 2 */ + CMD_CAN_START_PIPE, /* cmd_len >= 1 stat_len = 1 */ + CMD_START_STREAM, /* cmd_len = 2 stat_len = 0 */ + CMD_STREAM_OUT_LEVEL_ADJUST, /* cmd_len >= 1 stat_len = 0 */ + CMD_STOP_STREAM, /* cmd_len = 2 stat_len = 0 */ + CMD_UPDATE_R_BUFFERS, /* cmd_len = 4 stat_len = 0 */ + CMD_FORMAT_STREAM_OUT, /* cmd_len >= 2 stat_len = 0 */ + CMD_FORMAT_STREAM_IN, /* cmd_len >= 4 stat_len = 0 */ + CMD_STREAM_SAMPLE_COUNT, /* cmd_len = 2 stat_len = (2 * nb_stream) */ + CMD_AUDIO_LEVEL_ADJUST, /* cmd_len = 3 stat_len = 0 */ + CMD_GET_TIME_CODE, /* cmd_len = 1 stat_len = 5 */ + CMD_MANAGE_SIGNAL, /* cmd_len = 1 stat_len = 0 */ + CMD_LAST_INDEX +}; + +#define MASK_DSP_WORD 0x00ffffff +#define MASK_ALL_STREAM 0x00ffffff +#define MASK_DSP_WORD_LEVEL 0x000001ff +#define MASK_FIRST_FIELD 0x0000001f +#define FIELD_SIZE 5 + +/* + init the rmh struct; by default cmd_len is set to 1 + */ +void pcxhr_init_rmh(struct pcxhr_rmh *rmh, int cmd); + +void pcxhr_set_pipe_cmd_params(struct pcxhr_rmh* rmh, int capture, unsigned int param1, + unsigned int param2, unsigned int param3); + +#define DSP_EXT_CMD_SET(x) (x->dsp_version > 0x012800) + +/* + send the rmh + */ +int pcxhr_send_msg(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh); + + +/* values used for CMD_ACCESS_IO_WRITE and CMD_ACCESS_IO_READ */ +#define IO_NUM_REG_CONT 0 +#define IO_NUM_REG_GENCLK 1 +#define IO_NUM_REG_MUTE_OUT 2 +#define IO_NUM_SPEED_RATIO 4 +#define IO_NUM_REG_STATUS 5 +#define IO_NUM_REG_CUER 10 +#define IO_NUM_UER_CHIP_REG 11 +#define IO_NUM_REG_CONFIG_SRC 12 +#define IO_NUM_REG_OUT_ANA_LEVEL 20 +#define IO_NUM_REG_IN_ANA_LEVEL 21 + +#define REG_CONT_VALSMPTE 0x000800 +#define REG_CONT_UNMUTE_INPUTS 0x020000 + +/* parameters used with register IO_NUM_REG_STATUS */ +#define REG_STATUS_OPTIONS 0 +#define REG_STATUS_AES_SYNC 8 +#define REG_STATUS_AES_1 9 +#define REG_STATUS_AES_2 10 +#define REG_STATUS_AES_3 11 +#define REG_STATUS_AES_4 12 +#define REG_STATUS_WORD_CLOCK 13 +#define REG_STATUS_INTER_SYNC 14 +#define REG_STATUS_CURRENT 0x80 +/* results */ +#define REG_STATUS_OPT_NO_VIDEO_SIGNAL 0x01 +#define REG_STATUS_OPT_DAUGHTER_MASK 0x1c +#define REG_STATUS_OPT_ANALOG_BOARD 0x00 +#define REG_STATUS_OPT_NO_DAUGHTER 0x1c +#define REG_STATUS_OPT_COMPANION_MASK 0xe0 +#define REG_STATUS_OPT_NO_COMPANION 0xe0 +#define REG_STATUS_SYNC_32000 0x00 +#define REG_STATUS_SYNC_44100 0x01 +#define REG_STATUS_SYNC_48000 0x02 +#define REG_STATUS_SYNC_64000 0x03 +#define REG_STATUS_SYNC_88200 0x04 +#define REG_STATUS_SYNC_96000 0x05 +#define REG_STATUS_SYNC_128000 0x06 +#define REG_STATUS_SYNC_176400 0x07 +#define REG_STATUS_SYNC_192000 0x08 + +int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask, int capture_mask, int start); + +int pcxhr_write_io_num_reg_cont(struct pcxhr_mgr *mgr, unsigned int mask, + unsigned int value, int *changed); + +/* codec parameters */ +#define CS8416_RUN 0x200401 +#define CS8416_FORMAT_DETECT 0x200b00 +#define CS8416_CSB0 0x201900 +#define CS8416_CSB1 0x201a00 +#define CS8416_CSB2 0x201b00 +#define CS8416_CSB3 0x201c00 +#define CS8416_CSB4 0x201d00 +#define CS8416_VERSION 0x207f00 + +#define CS8420_DATA_FLOW_CTL 0x200301 +#define CS8420_CLOCK_SRC_CTL 0x200401 +#define CS8420_RECEIVER_ERRORS 0x201000 +#define CS8420_SRC_RATIO 0x201e00 +#define CS8420_CSB0 0x202000 +#define CS8420_CSB1 0x202100 +#define CS8420_CSB2 0x202200 +#define CS8420_CSB3 0x202300 +#define CS8420_CSB4 0x202400 +#define CS8420_VERSION 0x207f00 + +#define CS4271_MODE_CTL_1 0x200101 +#define CS4271_DAC_CTL 0x200201 +#define CS4271_VOLMIX 0x200301 +#define CS4271_VOLMUTE_LEFT 0x200401 +#define CS4271_VOLMUTE_RIGHT 0x200501 +#define CS4271_ADC_CTL 0x200601 +#define CS4271_MODE_CTL_2 0x200701 + +#define CHIP_SIG_AND_MAP_SPI 0xff7f00 + +/* codec selection */ +#define CS4271_01_CS 0x160018 +#define CS4271_23_CS 0x160019 +#define CS4271_45_CS 0x16001a +#define CS4271_67_CS 0x16001b +#define CS4271_89_CS 0x16001c +#define CS4271_AB_CS 0x16001d +#define CS8420_01_CS 0x080090 +#define CS8420_23_CS 0x080092 +#define CS8420_45_CS 0x080094 +#define CS8420_67_CS 0x080096 +#define CS8416_01_CS 0x080098 + + +/* interrupt handling */ +irqreturn_t pcxhr_interrupt(int irq, void *dev_id); +irqreturn_t pcxhr_threaded_irq(int irq, void *dev_id); + +#endif /* __SOUND_PCXHR_CORE_H */ diff --git a/sound/pci/pcxhr/pcxhr_hwdep.c b/sound/pci/pcxhr/pcxhr_hwdep.c new file mode 100644 index 000000000..54f395057 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_hwdep.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram pcxhr compatible soundcards + * + * hwdep device manager + * + * Copyright (c) 2004 by Digigram + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_mixer.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" +#include "pcxhr_mix22.h" + + +static int pcxhr_sub_init(struct pcxhr_mgr *mgr); +/* + * get basic information and init pcxhr card + */ +static int pcxhr_init_board(struct pcxhr_mgr *mgr) +{ + int err; + struct pcxhr_rmh rmh; + int card_streams; + + /* calc the number of all streams used */ + if (mgr->mono_capture) + card_streams = mgr->capture_chips * 2; + else + card_streams = mgr->capture_chips; + card_streams += mgr->playback_chips * PCXHR_PLAYBACK_STREAMS; + + /* enable interrupts */ + pcxhr_enable_dsp(mgr); + + pcxhr_init_rmh(&rmh, CMD_SUPPORTED); + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + /* test 4, 8 or 12 phys out */ + if ((rmh.stat[0] & MASK_FIRST_FIELD) < mgr->playback_chips * 2) + return -EINVAL; + /* test 4, 8 or 2 phys in */ + if (((rmh.stat[0] >> (2 * FIELD_SIZE)) & MASK_FIRST_FIELD) < + mgr->capture_chips * 2) + return -EINVAL; + /* test max nb substream per board */ + if ((rmh.stat[1] & 0x5F) < card_streams) + return -EINVAL; + /* test max nb substream per pipe */ + if (((rmh.stat[1] >> 7) & 0x5F) < PCXHR_PLAYBACK_STREAMS) + return -EINVAL; + dev_dbg(&mgr->pci->dev, + "supported formats : playback=%x capture=%x\n", + rmh.stat[2], rmh.stat[3]); + + pcxhr_init_rmh(&rmh, CMD_VERSION); + /* firmware num for DSP */ + rmh.cmd[0] |= mgr->firmware_num; + /* transfer granularity in samples (should be multiple of 48) */ + rmh.cmd[1] = (1<<23) + mgr->granularity; + rmh.cmd_len = 2; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + dev_dbg(&mgr->pci->dev, + "PCXHR DSP version is %d.%d.%d\n", (rmh.stat[0]>>16)&0xff, + (rmh.stat[0]>>8)&0xff, rmh.stat[0]&0xff); + mgr->dsp_version = rmh.stat[0]; + + if (mgr->is_hr_stereo) + err = hr222_sub_init(mgr); + else + err = pcxhr_sub_init(mgr); + return err; +} + +static int pcxhr_sub_init(struct pcxhr_mgr *mgr) +{ + int err; + struct pcxhr_rmh rmh; + + /* get options */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); + rmh.cmd[0] |= IO_NUM_REG_STATUS; + rmh.cmd[1] = REG_STATUS_OPTIONS; + rmh.cmd_len = 2; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + + if ((rmh.stat[1] & REG_STATUS_OPT_DAUGHTER_MASK) == + REG_STATUS_OPT_ANALOG_BOARD) + mgr->board_has_analog = 1; /* analog addon board found */ + + /* unmute inputs */ + err = pcxhr_write_io_num_reg_cont(mgr, REG_CONT_UNMUTE_INPUTS, + REG_CONT_UNMUTE_INPUTS, NULL); + if (err) + return err; + /* unmute outputs (a write to IO_NUM_REG_MUTE_OUT mutes!) */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + if (DSP_EXT_CMD_SET(mgr)) { + rmh.cmd[1] = 1; /* unmute digital plugs */ + rmh.cmd_len = 2; + } + err = pcxhr_send_msg(mgr, &rmh); + return err; +} + +void pcxhr_reset_board(struct pcxhr_mgr *mgr) +{ + struct pcxhr_rmh rmh; + + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) { + /* mute outputs */ + if (!mgr->is_hr_stereo) { + /* a read to IO_NUM_REG_MUTE_OUT register unmutes! */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + pcxhr_send_msg(mgr, &rmh); + /* mute inputs */ + pcxhr_write_io_num_reg_cont(mgr, REG_CONT_UNMUTE_INPUTS, + 0, NULL); + } + /* stereo cards mute with reset of dsp */ + } + /* reset pcxhr dsp */ + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_EPRM_INDEX)) + pcxhr_reset_dsp(mgr); + /* reset second xilinx */ + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_XLX_COM_INDEX)) { + pcxhr_reset_xilinx_com(mgr); + mgr->dsp_loaded = 1; + } + return; +} + + +/* + * allocate a playback/capture pipe (pcmp0/pcmc0) + */ +static int pcxhr_dsp_allocate_pipe(struct pcxhr_mgr *mgr, + struct pcxhr_pipe *pipe, + int is_capture, int pin) +{ + int stream_count, audio_count; + int err; + struct pcxhr_rmh rmh; + + if (is_capture) { + stream_count = 1; + if (mgr->mono_capture) + audio_count = 1; + else + audio_count = 2; + } else { + stream_count = PCXHR_PLAYBACK_STREAMS; + audio_count = 2; /* always stereo */ + } + dev_dbg(&mgr->pci->dev, "snd_add_ref_pipe pin(%d) pcm%c0\n", + pin, is_capture ? 'c' : 'p'); + pipe->is_capture = is_capture; + pipe->first_audio = pin; + /* define pipe (P_PCM_ONLY_MASK (0x020000) is not necessary) */ + pcxhr_init_rmh(&rmh, CMD_RES_PIPE); + pcxhr_set_pipe_cmd_params(&rmh, is_capture, pin, + audio_count, stream_count); + rmh.cmd[1] |= 0x020000; /* add P_PCM_ONLY_MASK */ + if (DSP_EXT_CMD_SET(mgr)) { + /* add channel mask to command */ + rmh.cmd[rmh.cmd_len++] = (audio_count == 1) ? 0x01 : 0x03; + } + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) { + dev_err(&mgr->pci->dev, "error pipe allocation " + "(CMD_RES_PIPE) err=%x!\n", err); + return err; + } + pipe->status = PCXHR_PIPE_DEFINED; + + return 0; +} + +/* + * free playback/capture pipe (pcmp0/pcmc0) + */ +#if 0 +static int pcxhr_dsp_free_pipe( struct pcxhr_mgr *mgr, struct pcxhr_pipe *pipe) +{ + struct pcxhr_rmh rmh; + int capture_mask = 0; + int playback_mask = 0; + int err = 0; + + if (pipe->is_capture) + capture_mask = (1 << pipe->first_audio); + else + playback_mask = (1 << pipe->first_audio); + + /* stop one pipe */ + err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 0); + if (err < 0) + dev_err(&mgr->pci->dev, "error stopping pipe!\n"); + /* release the pipe */ + pcxhr_init_rmh(&rmh, CMD_FREE_PIPE); + pcxhr_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->first_audio, + 0, 0); + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) + dev_err(&mgr->pci->dev, "error pipe release " + "(CMD_FREE_PIPE) err(%x)\n", err); + pipe->status = PCXHR_PIPE_UNDEFINED; + return err; +} +#endif + + +static int pcxhr_config_pipes(struct pcxhr_mgr *mgr) +{ + int err, i, j; + struct snd_pcxhr *chip; + struct pcxhr_pipe *pipe; + + /* allocate the pipes on the dsp */ + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + if (chip->nb_streams_play) { + pipe = &chip->playback_pipe; + err = pcxhr_dsp_allocate_pipe( mgr, pipe, 0, i*2); + if (err) + return err; + for(j = 0; j < chip->nb_streams_play; j++) + chip->playback_stream[j].pipe = pipe; + } + for (j = 0; j < chip->nb_streams_capt; j++) { + pipe = &chip->capture_pipe[j]; + err = pcxhr_dsp_allocate_pipe(mgr, pipe, 1, i*2 + j); + if (err) + return err; + chip->capture_stream[j].pipe = pipe; + } + } + return 0; +} + +static int pcxhr_start_pipes(struct pcxhr_mgr *mgr) +{ + int i, j; + struct snd_pcxhr *chip; + int playback_mask = 0; + int capture_mask = 0; + + /* start all the pipes on the dsp */ + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + if (chip->nb_streams_play) + playback_mask |= 1 << chip->playback_pipe.first_audio; + for (j = 0; j < chip->nb_streams_capt; j++) + capture_mask |= 1 << chip->capture_pipe[j].first_audio; + } + return pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 1); +} + + +static int pcxhr_dsp_load(struct pcxhr_mgr *mgr, int index, + const struct firmware *dsp) +{ + int err, card_index; + + dev_dbg(&mgr->pci->dev, + "loading dsp [%d] size = %zd\n", index, dsp->size); + + switch (index) { + case PCXHR_FIRMWARE_XLX_INT_INDEX: + pcxhr_reset_xilinx_com(mgr); + return pcxhr_load_xilinx_binary(mgr, dsp, 0); + + case PCXHR_FIRMWARE_XLX_COM_INDEX: + pcxhr_reset_xilinx_com(mgr); + return pcxhr_load_xilinx_binary(mgr, dsp, 1); + + case PCXHR_FIRMWARE_DSP_EPRM_INDEX: + pcxhr_reset_dsp(mgr); + return pcxhr_load_eeprom_binary(mgr, dsp); + + case PCXHR_FIRMWARE_DSP_BOOT_INDEX: + return pcxhr_load_boot_binary(mgr, dsp); + + case PCXHR_FIRMWARE_DSP_MAIN_INDEX: + err = pcxhr_load_dsp_binary(mgr, dsp); + if (err) + return err; + break; /* continue with first init */ + default: + dev_err(&mgr->pci->dev, "wrong file index\n"); + return -EFAULT; + } /* end of switch file index*/ + + /* first communication with embedded */ + err = pcxhr_init_board(mgr); + if (err < 0) { + dev_err(&mgr->pci->dev, "pcxhr could not be set up\n"); + return err; + } + err = pcxhr_config_pipes(mgr); + if (err < 0) { + dev_err(&mgr->pci->dev, "pcxhr pipes could not be set up\n"); + return err; + } + /* create devices and mixer in accordance with HW options*/ + for (card_index = 0; card_index < mgr->num_cards; card_index++) { + struct snd_pcxhr *chip = mgr->chip[card_index]; + + err = pcxhr_create_pcm(chip); + if (err < 0) + return err; + + if (card_index == 0) { + err = pcxhr_create_mixer(chip->mgr); + if (err < 0) + return err; + } + err = snd_card_register(chip->card); + if (err < 0) + return err; + } + err = pcxhr_start_pipes(mgr); + if (err < 0) { + dev_err(&mgr->pci->dev, "pcxhr pipes could not be started\n"); + return err; + } + dev_dbg(&mgr->pci->dev, + "pcxhr firmware downloaded and successfully set up\n"); + + return 0; +} + +/* + * fw loader entry + */ +int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) +{ + static const char * const fw_files[][5] = { + [0] = { "xlxint.dat", "xlxc882hr.dat", + "dspe882.e56", "dspb882hr.b56", "dspd882.d56" }, + [1] = { "xlxint.dat", "xlxc882e.dat", + "dspe882.e56", "dspb882e.b56", "dspd882.d56" }, + [2] = { "xlxint.dat", "xlxc1222hr.dat", + "dspe882.e56", "dspb1222hr.b56", "dspd1222.d56" }, + [3] = { "xlxint.dat", "xlxc1222e.dat", + "dspe882.e56", "dspb1222e.b56", "dspd1222.d56" }, + [4] = { NULL, "xlxc222.dat", + "dspe924.e56", "dspb924.b56", "dspd222.d56" }, + [5] = { NULL, "xlxc924.dat", + "dspe924.e56", "dspb924.b56", "dspd222.d56" }, + }; + char path[32]; + + int i, err; + int fw_set = mgr->fw_file_set; + + for (i = 0; i < 5; i++) { + if (!fw_files[fw_set][i]) + continue; + sprintf(path, "pcxhr/%s", fw_files[fw_set][i]); + const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "pcxhr: can't load firmware %s\n", + path); + return -ENOENT; + } + /* fake hwdep dsp record */ + err = pcxhr_dsp_load(mgr, i, fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; + } + return 0; +} + +MODULE_FIRMWARE("pcxhr/xlxint.dat"); +MODULE_FIRMWARE("pcxhr/xlxc882hr.dat"); +MODULE_FIRMWARE("pcxhr/xlxc882e.dat"); +MODULE_FIRMWARE("pcxhr/dspe882.e56"); +MODULE_FIRMWARE("pcxhr/dspb882hr.b56"); +MODULE_FIRMWARE("pcxhr/dspb882e.b56"); +MODULE_FIRMWARE("pcxhr/dspd882.d56"); + +MODULE_FIRMWARE("pcxhr/xlxc1222hr.dat"); +MODULE_FIRMWARE("pcxhr/xlxc1222e.dat"); +MODULE_FIRMWARE("pcxhr/dspb1222hr.b56"); +MODULE_FIRMWARE("pcxhr/dspb1222e.b56"); +MODULE_FIRMWARE("pcxhr/dspd1222.d56"); + +MODULE_FIRMWARE("pcxhr/xlxc222.dat"); +MODULE_FIRMWARE("pcxhr/xlxc924.dat"); +MODULE_FIRMWARE("pcxhr/dspe924.e56"); +MODULE_FIRMWARE("pcxhr/dspb924.b56"); +MODULE_FIRMWARE("pcxhr/dspd222.d56"); diff --git a/sound/pci/pcxhr/pcxhr_hwdep.h b/sound/pci/pcxhr/pcxhr_hwdep.h new file mode 100644 index 000000000..f7a440e4b --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_hwdep.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * definitions and makros for basic card access + * + * Copyright (c) 2004 by Digigram + */ + +#ifndef __SOUND_PCXHR_HWDEP_H +#define __SOUND_PCXHR_HWDEP_H + + +/* firmware status codes */ +#define PCXHR_FIRMWARE_XLX_INT_INDEX 0 +#define PCXHR_FIRMWARE_XLX_COM_INDEX 1 +#define PCXHR_FIRMWARE_DSP_EPRM_INDEX 2 +#define PCXHR_FIRMWARE_DSP_BOOT_INDEX 3 +#define PCXHR_FIRMWARE_DSP_MAIN_INDEX 4 +#define PCXHR_FIRMWARE_FILES_MAX_INDEX 5 + + +/* exported */ +int pcxhr_setup_firmware(struct pcxhr_mgr *mgr); +void pcxhr_reset_board(struct pcxhr_mgr *mgr); + +#endif /* __SOUND_PCXHR_HWDEP_H */ diff --git a/sound/pci/pcxhr/pcxhr_mix22.c b/sound/pci/pcxhr/pcxhr_mix22.c new file mode 100644 index 000000000..76beb443d --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mix22.c @@ -0,0 +1,825 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram pcxhr compatible soundcards + * + * mixer interface for stereo cards + * + * Copyright (c) 2004 by Digigram + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_core.h" +#include "pcxhr_mix22.h" + + +/* registers used on the DSP and Xilinx (port 2) : HR stereo cards only */ +#define PCXHR_DSP_RESET 0x20 +#define PCXHR_XLX_CFG 0x24 +#define PCXHR_XLX_RUER 0x28 +#define PCXHR_XLX_DATA 0x2C +#define PCXHR_XLX_STATUS 0x30 +#define PCXHR_XLX_LOFREQ 0x34 +#define PCXHR_XLX_HIFREQ 0x38 +#define PCXHR_XLX_CSUER 0x3C +#define PCXHR_XLX_SELMIC 0x40 + +#define PCXHR_DSP 2 + +/* byte access only ! */ +#define PCXHR_INPB(mgr, x) inb((mgr)->port[PCXHR_DSP] + (x)) +#define PCXHR_OUTPB(mgr, x, data) outb((data), (mgr)->port[PCXHR_DSP] + (x)) + + +/* values for PCHR_DSP_RESET register */ +#define PCXHR_DSP_RESET_DSP 0x01 +#define PCXHR_DSP_RESET_MUTE 0x02 +#define PCXHR_DSP_RESET_CODEC 0x08 +#define PCXHR_DSP_RESET_SMPTE 0x10 +#define PCXHR_DSP_RESET_GPO_OFFSET 5 +#define PCXHR_DSP_RESET_GPO_MASK 0x60 + +/* values for PCHR_XLX_CFG register */ +#define PCXHR_CFG_SYNCDSP_MASK 0x80 +#define PCXHR_CFG_DEPENDENCY_MASK 0x60 +#define PCXHR_CFG_INDEPENDANT_SEL 0x00 +#define PCXHR_CFG_MASTER_SEL 0x40 +#define PCXHR_CFG_SLAVE_SEL 0x20 +#define PCXHR_CFG_DATA_UER1_SEL_MASK 0x10 /* 0 (UER0), 1(UER1) */ +#define PCXHR_CFG_DATAIN_SEL_MASK 0x08 /* 0 (ana), 1 (UER) */ +#define PCXHR_CFG_SRC_MASK 0x04 /* 0 (Bypass), 1 (SRC Actif) */ +#define PCXHR_CFG_CLOCK_UER1_SEL_MASK 0x02 /* 0 (UER0), 1(UER1) */ +#define PCXHR_CFG_CLOCKIN_SEL_MASK 0x01 /* 0 (internal), 1 (AES/EBU) */ + +/* values for PCHR_XLX_DATA register */ +#define PCXHR_DATA_CODEC 0x80 +#define AKM_POWER_CONTROL_CMD 0xA007 +#define AKM_RESET_ON_CMD 0xA100 +#define AKM_RESET_OFF_CMD 0xA103 +#define AKM_CLOCK_INF_55K_CMD 0xA240 +#define AKM_CLOCK_SUP_55K_CMD 0xA24D +#define AKM_MUTE_CMD 0xA38D +#define AKM_UNMUTE_CMD 0xA30D +#define AKM_LEFT_LEVEL_CMD 0xA600 +#define AKM_RIGHT_LEVEL_CMD 0xA700 + +/* values for PCHR_XLX_STATUS register - READ */ +#define PCXHR_STAT_SRC_LOCK 0x01 +#define PCXHR_STAT_LEVEL_IN 0x02 +#define PCXHR_STAT_GPI_OFFSET 2 +#define PCXHR_STAT_GPI_MASK 0x0C +#define PCXHR_STAT_MIC_CAPS 0x10 +/* values for PCHR_XLX_STATUS register - WRITE */ +#define PCXHR_STAT_FREQ_SYNC_MASK 0x01 +#define PCXHR_STAT_FREQ_UER1_MASK 0x02 +#define PCXHR_STAT_FREQ_SAVE_MASK 0x80 + +/* values for PCHR_XLX_CSUER register */ +#define PCXHR_SUER1_BIT_U_READ_MASK 0x80 +#define PCXHR_SUER1_BIT_C_READ_MASK 0x40 +#define PCXHR_SUER1_DATA_PRESENT_MASK 0x20 +#define PCXHR_SUER1_CLOCK_PRESENT_MASK 0x10 +#define PCXHR_SUER_BIT_U_READ_MASK 0x08 +#define PCXHR_SUER_BIT_C_READ_MASK 0x04 +#define PCXHR_SUER_DATA_PRESENT_MASK 0x02 +#define PCXHR_SUER_CLOCK_PRESENT_MASK 0x01 + +#define PCXHR_SUER_BIT_U_WRITE_MASK 0x02 +#define PCXHR_SUER_BIT_C_WRITE_MASK 0x01 + +/* values for PCXHR_XLX_SELMIC register - WRITE */ +#define PCXHR_SELMIC_PREAMPLI_OFFSET 2 +#define PCXHR_SELMIC_PREAMPLI_MASK 0x0C +#define PCXHR_SELMIC_PHANTOM_ALIM 0x80 + + +static const unsigned char g_hr222_p_level[] = { + 0x00, /* [000] -49.5 dB: AKM[000] = -1.#INF dB (mute) */ + 0x01, /* [001] -49.0 dB: AKM[001] = -48.131 dB (diff=0.86920 dB) */ + 0x01, /* [002] -48.5 dB: AKM[001] = -48.131 dB (diff=0.36920 dB) */ + 0x01, /* [003] -48.0 dB: AKM[001] = -48.131 dB (diff=0.13080 dB) */ + 0x01, /* [004] -47.5 dB: AKM[001] = -48.131 dB (diff=0.63080 dB) */ + 0x01, /* [005] -46.5 dB: AKM[001] = -48.131 dB (diff=1.63080 dB) */ + 0x01, /* [006] -47.0 dB: AKM[001] = -48.131 dB (diff=1.13080 dB) */ + 0x01, /* [007] -46.0 dB: AKM[001] = -48.131 dB (diff=2.13080 dB) */ + 0x01, /* [008] -45.5 dB: AKM[001] = -48.131 dB (diff=2.63080 dB) */ + 0x02, /* [009] -45.0 dB: AKM[002] = -42.110 dB (diff=2.88980 dB) */ + 0x02, /* [010] -44.5 dB: AKM[002] = -42.110 dB (diff=2.38980 dB) */ + 0x02, /* [011] -44.0 dB: AKM[002] = -42.110 dB (diff=1.88980 dB) */ + 0x02, /* [012] -43.5 dB: AKM[002] = -42.110 dB (diff=1.38980 dB) */ + 0x02, /* [013] -43.0 dB: AKM[002] = -42.110 dB (diff=0.88980 dB) */ + 0x02, /* [014] -42.5 dB: AKM[002] = -42.110 dB (diff=0.38980 dB) */ + 0x02, /* [015] -42.0 dB: AKM[002] = -42.110 dB (diff=0.11020 dB) */ + 0x02, /* [016] -41.5 dB: AKM[002] = -42.110 dB (diff=0.61020 dB) */ + 0x02, /* [017] -41.0 dB: AKM[002] = -42.110 dB (diff=1.11020 dB) */ + 0x02, /* [018] -40.5 dB: AKM[002] = -42.110 dB (diff=1.61020 dB) */ + 0x03, /* [019] -40.0 dB: AKM[003] = -38.588 dB (diff=1.41162 dB) */ + 0x03, /* [020] -39.5 dB: AKM[003] = -38.588 dB (diff=0.91162 dB) */ + 0x03, /* [021] -39.0 dB: AKM[003] = -38.588 dB (diff=0.41162 dB) */ + 0x03, /* [022] -38.5 dB: AKM[003] = -38.588 dB (diff=0.08838 dB) */ + 0x03, /* [023] -38.0 dB: AKM[003] = -38.588 dB (diff=0.58838 dB) */ + 0x03, /* [024] -37.5 dB: AKM[003] = -38.588 dB (diff=1.08838 dB) */ + 0x04, /* [025] -37.0 dB: AKM[004] = -36.090 dB (diff=0.91040 dB) */ + 0x04, /* [026] -36.5 dB: AKM[004] = -36.090 dB (diff=0.41040 dB) */ + 0x04, /* [027] -36.0 dB: AKM[004] = -36.090 dB (diff=0.08960 dB) */ + 0x04, /* [028] -35.5 dB: AKM[004] = -36.090 dB (diff=0.58960 dB) */ + 0x05, /* [029] -35.0 dB: AKM[005] = -34.151 dB (diff=0.84860 dB) */ + 0x05, /* [030] -34.5 dB: AKM[005] = -34.151 dB (diff=0.34860 dB) */ + 0x05, /* [031] -34.0 dB: AKM[005] = -34.151 dB (diff=0.15140 dB) */ + 0x05, /* [032] -33.5 dB: AKM[005] = -34.151 dB (diff=0.65140 dB) */ + 0x06, /* [033] -33.0 dB: AKM[006] = -32.568 dB (diff=0.43222 dB) */ + 0x06, /* [034] -32.5 dB: AKM[006] = -32.568 dB (diff=0.06778 dB) */ + 0x06, /* [035] -32.0 dB: AKM[006] = -32.568 dB (diff=0.56778 dB) */ + 0x07, /* [036] -31.5 dB: AKM[007] = -31.229 dB (diff=0.27116 dB) */ + 0x07, /* [037] -31.0 dB: AKM[007] = -31.229 dB (diff=0.22884 dB) */ + 0x08, /* [038] -30.5 dB: AKM[008] = -30.069 dB (diff=0.43100 dB) */ + 0x08, /* [039] -30.0 dB: AKM[008] = -30.069 dB (diff=0.06900 dB) */ + 0x09, /* [040] -29.5 dB: AKM[009] = -29.046 dB (diff=0.45405 dB) */ + 0x09, /* [041] -29.0 dB: AKM[009] = -29.046 dB (diff=0.04595 dB) */ + 0x0a, /* [042] -28.5 dB: AKM[010] = -28.131 dB (diff=0.36920 dB) */ + 0x0a, /* [043] -28.0 dB: AKM[010] = -28.131 dB (diff=0.13080 dB) */ + 0x0b, /* [044] -27.5 dB: AKM[011] = -27.303 dB (diff=0.19705 dB) */ + 0x0b, /* [045] -27.0 dB: AKM[011] = -27.303 dB (diff=0.30295 dB) */ + 0x0c, /* [046] -26.5 dB: AKM[012] = -26.547 dB (diff=0.04718 dB) */ + 0x0d, /* [047] -26.0 dB: AKM[013] = -25.852 dB (diff=0.14806 dB) */ + 0x0e, /* [048] -25.5 dB: AKM[014] = -25.208 dB (diff=0.29176 dB) */ + 0x0e, /* [049] -25.0 dB: AKM[014] = -25.208 dB (diff=0.20824 dB) */ + 0x0f, /* [050] -24.5 dB: AKM[015] = -24.609 dB (diff=0.10898 dB) */ + 0x10, /* [051] -24.0 dB: AKM[016] = -24.048 dB (diff=0.04840 dB) */ + 0x11, /* [052] -23.5 dB: AKM[017] = -23.522 dB (diff=0.02183 dB) */ + 0x12, /* [053] -23.0 dB: AKM[018] = -23.025 dB (diff=0.02535 dB) */ + 0x13, /* [054] -22.5 dB: AKM[019] = -22.556 dB (diff=0.05573 dB) */ + 0x14, /* [055] -22.0 dB: AKM[020] = -22.110 dB (diff=0.11020 dB) */ + 0x15, /* [056] -21.5 dB: AKM[021] = -21.686 dB (diff=0.18642 dB) */ + 0x17, /* [057] -21.0 dB: AKM[023] = -20.896 dB (diff=0.10375 dB) */ + 0x18, /* [058] -20.5 dB: AKM[024] = -20.527 dB (diff=0.02658 dB) */ + 0x1a, /* [059] -20.0 dB: AKM[026] = -19.831 dB (diff=0.16866 dB) */ + 0x1b, /* [060] -19.5 dB: AKM[027] = -19.504 dB (diff=0.00353 dB) */ + 0x1d, /* [061] -19.0 dB: AKM[029] = -18.883 dB (diff=0.11716 dB) */ + 0x1e, /* [062] -18.5 dB: AKM[030] = -18.588 dB (diff=0.08838 dB) */ + 0x20, /* [063] -18.0 dB: AKM[032] = -18.028 dB (diff=0.02780 dB) */ + 0x22, /* [064] -17.5 dB: AKM[034] = -17.501 dB (diff=0.00123 dB) */ + 0x24, /* [065] -17.0 dB: AKM[036] = -17.005 dB (diff=0.00475 dB) */ + 0x26, /* [066] -16.5 dB: AKM[038] = -16.535 dB (diff=0.03513 dB) */ + 0x28, /* [067] -16.0 dB: AKM[040] = -16.090 dB (diff=0.08960 dB) */ + 0x2b, /* [068] -15.5 dB: AKM[043] = -15.461 dB (diff=0.03857 dB) */ + 0x2d, /* [069] -15.0 dB: AKM[045] = -15.067 dB (diff=0.06655 dB) */ + 0x30, /* [070] -14.5 dB: AKM[048] = -14.506 dB (diff=0.00598 dB) */ + 0x33, /* [071] -14.0 dB: AKM[051] = -13.979 dB (diff=0.02060 dB) */ + 0x36, /* [072] -13.5 dB: AKM[054] = -13.483 dB (diff=0.01707 dB) */ + 0x39, /* [073] -13.0 dB: AKM[057] = -13.013 dB (diff=0.01331 dB) */ + 0x3c, /* [074] -12.5 dB: AKM[060] = -12.568 dB (diff=0.06778 dB) */ + 0x40, /* [075] -12.0 dB: AKM[064] = -12.007 dB (diff=0.00720 dB) */ + 0x44, /* [076] -11.5 dB: AKM[068] = -11.481 dB (diff=0.01937 dB) */ + 0x48, /* [077] -11.0 dB: AKM[072] = -10.984 dB (diff=0.01585 dB) */ + 0x4c, /* [078] -10.5 dB: AKM[076] = -10.515 dB (diff=0.01453 dB) */ + 0x51, /* [079] -10.0 dB: AKM[081] = -9.961 dB (diff=0.03890 dB) */ + 0x55, /* [080] -9.5 dB: AKM[085] = -9.542 dB (diff=0.04243 dB) */ + 0x5a, /* [081] -9.0 dB: AKM[090] = -9.046 dB (diff=0.04595 dB) */ + 0x60, /* [082] -8.5 dB: AKM[096] = -8.485 dB (diff=0.01462 dB) */ + 0x66, /* [083] -8.0 dB: AKM[102] = -7.959 dB (diff=0.04120 dB) */ + 0x6c, /* [084] -7.5 dB: AKM[108] = -7.462 dB (diff=0.03767 dB) */ + 0x72, /* [085] -7.0 dB: AKM[114] = -6.993 dB (diff=0.00729 dB) */ + 0x79, /* [086] -6.5 dB: AKM[121] = -6.475 dB (diff=0.02490 dB) */ + 0x80, /* [087] -6.0 dB: AKM[128] = -5.987 dB (diff=0.01340 dB) */ + 0x87, /* [088] -5.5 dB: AKM[135] = -5.524 dB (diff=0.02413 dB) */ + 0x8f, /* [089] -5.0 dB: AKM[143] = -5.024 dB (diff=0.02408 dB) */ + 0x98, /* [090] -4.5 dB: AKM[152] = -4.494 dB (diff=0.00607 dB) */ + 0xa1, /* [091] -4.0 dB: AKM[161] = -3.994 dB (diff=0.00571 dB) */ + 0xaa, /* [092] -3.5 dB: AKM[170] = -3.522 dB (diff=0.02183 dB) */ + 0xb5, /* [093] -3.0 dB: AKM[181] = -2.977 dB (diff=0.02277 dB) */ + 0xbf, /* [094] -2.5 dB: AKM[191] = -2.510 dB (diff=0.01014 dB) */ + 0xcb, /* [095] -2.0 dB: AKM[203] = -1.981 dB (diff=0.01912 dB) */ + 0xd7, /* [096] -1.5 dB: AKM[215] = -1.482 dB (diff=0.01797 dB) */ + 0xe3, /* [097] -1.0 dB: AKM[227] = -1.010 dB (diff=0.01029 dB) */ + 0xf1, /* [098] -0.5 dB: AKM[241] = -0.490 dB (diff=0.00954 dB) */ + 0xff, /* [099] +0.0 dB: AKM[255] = +0.000 dB (diff=0.00000 dB) */ +}; + + +static void hr222_config_akm(struct pcxhr_mgr *mgr, unsigned short data) +{ + unsigned short mask = 0x8000; + /* activate access to codec registers */ + PCXHR_INPB(mgr, PCXHR_XLX_HIFREQ); + + while (mask) { + PCXHR_OUTPB(mgr, PCXHR_XLX_DATA, + data & mask ? PCXHR_DATA_CODEC : 0); + mask >>= 1; + } + /* termiate access to codec registers */ + PCXHR_INPB(mgr, PCXHR_XLX_RUER); +} + + +static int hr222_set_hw_playback_level(struct pcxhr_mgr *mgr, + int idx, int level) +{ + unsigned short cmd; + if (idx > 1 || + level < 0 || + level >= ARRAY_SIZE(g_hr222_p_level)) + return -EINVAL; + + if (idx == 0) + cmd = AKM_LEFT_LEVEL_CMD; + else + cmd = AKM_RIGHT_LEVEL_CMD; + + /* conversion from PmBoardCodedLevel to AKM nonlinear programming */ + cmd += g_hr222_p_level[level]; + + hr222_config_akm(mgr, cmd); + return 0; +} + + +static int hr222_set_hw_capture_level(struct pcxhr_mgr *mgr, + int level_l, int level_r, int level_mic) +{ + /* program all input levels at the same time */ + unsigned int data; + int i; + + if (!mgr->capture_chips) + return -EINVAL; /* no PCX22 */ + + data = ((level_mic & 0xff) << 24); /* micro is mono, but apply */ + data |= ((level_mic & 0xff) << 16); /* level on both channels */ + data |= ((level_r & 0xff) << 8); /* line input right channel */ + data |= (level_l & 0xff); /* line input left channel */ + + PCXHR_INPB(mgr, PCXHR_XLX_DATA); /* activate input codec */ + /* send 32 bits (4 x 8 bits) */ + for (i = 0; i < 32; i++, data <<= 1) { + PCXHR_OUTPB(mgr, PCXHR_XLX_DATA, + (data & 0x80000000) ? PCXHR_DATA_CODEC : 0); + } + PCXHR_INPB(mgr, PCXHR_XLX_RUER); /* close input level codec */ + return 0; +} + +static void hr222_micro_boost(struct pcxhr_mgr *mgr, int level); + +int hr222_sub_init(struct pcxhr_mgr *mgr) +{ + unsigned char reg; + + mgr->board_has_analog = 1; /* analog always available */ + mgr->xlx_cfg = PCXHR_CFG_SYNCDSP_MASK; + + reg = PCXHR_INPB(mgr, PCXHR_XLX_STATUS); + if (reg & PCXHR_STAT_MIC_CAPS) + mgr->board_has_mic = 1; /* microphone available */ + dev_dbg(&mgr->pci->dev, + "MIC input available = %d\n", mgr->board_has_mic); + + /* reset codec */ + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, + PCXHR_DSP_RESET_DSP); + msleep(5); + mgr->dsp_reset = PCXHR_DSP_RESET_DSP | + PCXHR_DSP_RESET_MUTE | + PCXHR_DSP_RESET_CODEC; + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, mgr->dsp_reset); + /* hr222_write_gpo(mgr, 0); does the same */ + msleep(5); + + /* config AKM */ + hr222_config_akm(mgr, AKM_POWER_CONTROL_CMD); + hr222_config_akm(mgr, AKM_CLOCK_INF_55K_CMD); + hr222_config_akm(mgr, AKM_UNMUTE_CMD); + hr222_config_akm(mgr, AKM_RESET_OFF_CMD); + + /* init micro boost */ + hr222_micro_boost(mgr, 0); + + return 0; +} + + +int hr222_sub_set_clock(struct pcxhr_mgr *mgr, + unsigned int rate, + int *changed) +{ + unsigned int speed, pllreg = 0; + int err; + unsigned realfreq = rate; + + switch (mgr->use_clock_type) { + case HR22_CLOCK_TYPE_INTERNAL: + err = pcxhr_pll_freq_register(rate, 219000, + &pllreg, &realfreq); + if (err) + return err; + + mgr->xlx_cfg &= ~(PCXHR_CFG_CLOCKIN_SEL_MASK | + PCXHR_CFG_CLOCK_UER1_SEL_MASK); + break; + case HR22_CLOCK_TYPE_AES_SYNC: + mgr->xlx_cfg |= PCXHR_CFG_CLOCKIN_SEL_MASK; + mgr->xlx_cfg &= ~PCXHR_CFG_CLOCK_UER1_SEL_MASK; + break; + case HR22_CLOCK_TYPE_AES_1: + if (!mgr->board_has_aes1) + return -EINVAL; + + mgr->xlx_cfg |= (PCXHR_CFG_CLOCKIN_SEL_MASK | + PCXHR_CFG_CLOCK_UER1_SEL_MASK); + break; + default: + return -EINVAL; + } + hr222_config_akm(mgr, AKM_MUTE_CMD); + + if (mgr->use_clock_type == HR22_CLOCK_TYPE_INTERNAL) { + PCXHR_OUTPB(mgr, PCXHR_XLX_HIFREQ, pllreg >> 8); + PCXHR_OUTPB(mgr, PCXHR_XLX_LOFREQ, pllreg & 0xff); + } + + /* set clock source */ + PCXHR_OUTPB(mgr, PCXHR_XLX_CFG, mgr->xlx_cfg); + + /* codec speed modes */ + speed = rate < 55000 ? 0 : 1; + if (mgr->codec_speed != speed) { + mgr->codec_speed = speed; + if (speed == 0) + hr222_config_akm(mgr, AKM_CLOCK_INF_55K_CMD); + else + hr222_config_akm(mgr, AKM_CLOCK_SUP_55K_CMD); + } + + mgr->sample_rate_real = realfreq; + mgr->cur_clock_type = mgr->use_clock_type; + + if (changed) + *changed = 1; + + hr222_config_akm(mgr, AKM_UNMUTE_CMD); + + dev_dbg(&mgr->pci->dev, "set_clock to %dHz (realfreq=%d pllreg=%x)\n", + rate, realfreq, pllreg); + return 0; +} + +int hr222_get_external_clock(struct pcxhr_mgr *mgr, + enum pcxhr_clock_type clock_type, + int *sample_rate) +{ + int rate, calc_rate = 0; + unsigned int ticks; + unsigned char mask, reg; + + if (clock_type == HR22_CLOCK_TYPE_AES_SYNC) { + + mask = (PCXHR_SUER_CLOCK_PRESENT_MASK | + PCXHR_SUER_DATA_PRESENT_MASK); + reg = PCXHR_STAT_FREQ_SYNC_MASK; + + } else if (clock_type == HR22_CLOCK_TYPE_AES_1 && mgr->board_has_aes1) { + + mask = (PCXHR_SUER1_CLOCK_PRESENT_MASK | + PCXHR_SUER1_DATA_PRESENT_MASK); + reg = PCXHR_STAT_FREQ_UER1_MASK; + + } else { + dev_dbg(&mgr->pci->dev, + "get_external_clock : type %d not supported\n", + clock_type); + return -EINVAL; /* other clocks not supported */ + } + + if ((PCXHR_INPB(mgr, PCXHR_XLX_CSUER) & mask) != mask) { + dev_dbg(&mgr->pci->dev, + "get_external_clock(%d) = 0 Hz\n", clock_type); + *sample_rate = 0; + return 0; /* no external clock locked */ + } + + PCXHR_OUTPB(mgr, PCXHR_XLX_STATUS, reg); /* calculate freq */ + + /* save the measured clock frequency */ + reg |= PCXHR_STAT_FREQ_SAVE_MASK; + + if (mgr->last_reg_stat != reg) { + udelay(500); /* wait min 2 cycles of lowest freq (8000) */ + mgr->last_reg_stat = reg; + } + + PCXHR_OUTPB(mgr, PCXHR_XLX_STATUS, reg); /* save */ + + /* get the frequency */ + ticks = (unsigned int)PCXHR_INPB(mgr, PCXHR_XLX_CFG); + ticks = (ticks & 0x03) << 8; + ticks |= (unsigned int)PCXHR_INPB(mgr, PCXHR_DSP_RESET); + + if (ticks != 0) + calc_rate = 28224000 / ticks; + /* rounding */ + if (calc_rate > 184200) + rate = 192000; + else if (calc_rate > 152200) + rate = 176400; + else if (calc_rate > 112000) + rate = 128000; + else if (calc_rate > 92100) + rate = 96000; + else if (calc_rate > 76100) + rate = 88200; + else if (calc_rate > 56000) + rate = 64000; + else if (calc_rate > 46050) + rate = 48000; + else if (calc_rate > 38050) + rate = 44100; + else if (calc_rate > 28000) + rate = 32000; + else if (calc_rate > 23025) + rate = 24000; + else if (calc_rate > 19025) + rate = 22050; + else if (calc_rate > 14000) + rate = 16000; + else if (calc_rate > 11512) + rate = 12000; + else if (calc_rate > 9512) + rate = 11025; + else if (calc_rate > 7000) + rate = 8000; + else + rate = 0; + + dev_dbg(&mgr->pci->dev, "External clock is at %d Hz (measured %d Hz)\n", + rate, calc_rate); + *sample_rate = rate; + return 0; +} + + +int hr222_read_gpio(struct pcxhr_mgr *mgr, int is_gpi, int *value) +{ + if (is_gpi) { + unsigned char reg = PCXHR_INPB(mgr, PCXHR_XLX_STATUS); + *value = (int)(reg & PCXHR_STAT_GPI_MASK) >> + PCXHR_STAT_GPI_OFFSET; + } else { + *value = (int)(mgr->dsp_reset & PCXHR_DSP_RESET_GPO_MASK) >> + PCXHR_DSP_RESET_GPO_OFFSET; + } + return 0; +} + + +int hr222_write_gpo(struct pcxhr_mgr *mgr, int value) +{ + unsigned char reg = mgr->dsp_reset & ~PCXHR_DSP_RESET_GPO_MASK; + + reg |= (unsigned char)(value << PCXHR_DSP_RESET_GPO_OFFSET) & + PCXHR_DSP_RESET_GPO_MASK; + + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, reg); + mgr->dsp_reset = reg; + return 0; +} + +int hr222_manage_timecode(struct pcxhr_mgr *mgr, int enable) +{ + if (enable) + mgr->dsp_reset |= PCXHR_DSP_RESET_SMPTE; + else + mgr->dsp_reset &= ~PCXHR_DSP_RESET_SMPTE; + + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, mgr->dsp_reset); + return 0; +} + +int hr222_update_analog_audio_level(struct snd_pcxhr *chip, + int is_capture, int channel) +{ + dev_dbg(chip->card->dev, + "hr222_update_analog_audio_level(%s chan=%d)\n", + snd_pcm_direction_name(is_capture), channel); + if (is_capture) { + int level_l, level_r, level_mic; + /* we have to update all levels */ + if (chip->analog_capture_active) { + level_l = chip->analog_capture_volume[0]; + level_r = chip->analog_capture_volume[1]; + } else { + level_l = HR222_LINE_CAPTURE_LEVEL_MIN; + level_r = HR222_LINE_CAPTURE_LEVEL_MIN; + } + if (chip->mic_active) + level_mic = chip->mic_volume; + else + level_mic = HR222_MICRO_CAPTURE_LEVEL_MIN; + return hr222_set_hw_capture_level(chip->mgr, + level_l, level_r, level_mic); + } else { + int vol; + if (chip->analog_playback_active[channel]) + vol = chip->analog_playback_volume[channel]; + else + vol = HR222_LINE_PLAYBACK_LEVEL_MIN; + return hr222_set_hw_playback_level(chip->mgr, channel, vol); + } +} + + +/*texts[5] = {"Line", "Digital", "Digi+SRC", "Mic", "Line+Mic"}*/ +#define SOURCE_LINE 0 +#define SOURCE_DIGITAL 1 +#define SOURCE_DIGISRC 2 +#define SOURCE_MIC 3 +#define SOURCE_LINEMIC 4 + +int hr222_set_audio_source(struct snd_pcxhr *chip) +{ + int digital = 0; + /* default analog source */ + chip->mgr->xlx_cfg &= ~(PCXHR_CFG_SRC_MASK | + PCXHR_CFG_DATAIN_SEL_MASK | + PCXHR_CFG_DATA_UER1_SEL_MASK); + + if (chip->audio_capture_source == SOURCE_DIGISRC) { + chip->mgr->xlx_cfg |= PCXHR_CFG_SRC_MASK; + digital = 1; + } else { + if (chip->audio_capture_source == SOURCE_DIGITAL) + digital = 1; + } + if (digital) { + chip->mgr->xlx_cfg |= PCXHR_CFG_DATAIN_SEL_MASK; + if (chip->mgr->board_has_aes1) { + /* get data from the AES1 plug */ + chip->mgr->xlx_cfg |= PCXHR_CFG_DATA_UER1_SEL_MASK; + } + /* chip->mic_active = 0; */ + /* chip->analog_capture_active = 0; */ + } else { + int update_lvl = 0; + chip->analog_capture_active = 0; + chip->mic_active = 0; + if (chip->audio_capture_source == SOURCE_LINE || + chip->audio_capture_source == SOURCE_LINEMIC) { + if (chip->analog_capture_active == 0) + update_lvl = 1; + chip->analog_capture_active = 1; + } + if (chip->audio_capture_source == SOURCE_MIC || + chip->audio_capture_source == SOURCE_LINEMIC) { + if (chip->mic_active == 0) + update_lvl = 1; + chip->mic_active = 1; + } + if (update_lvl) { + /* capture: update all 3 mutes/unmutes with one call */ + hr222_update_analog_audio_level(chip, 1, 0); + } + } + /* set the source infos (max 3 bits modified) */ + PCXHR_OUTPB(chip->mgr, PCXHR_XLX_CFG, chip->mgr->xlx_cfg); + return 0; +} + + +int hr222_iec958_capture_byte(struct snd_pcxhr *chip, + int aes_idx, unsigned char *aes_bits) +{ + unsigned char idx = (unsigned char)(aes_idx * 8); + unsigned char temp = 0; + unsigned char mask = chip->mgr->board_has_aes1 ? + PCXHR_SUER1_BIT_C_READ_MASK : PCXHR_SUER_BIT_C_READ_MASK; + int i; + for (i = 0; i < 8; i++) { + PCXHR_OUTPB(chip->mgr, PCXHR_XLX_RUER, idx++); /* idx < 192 */ + temp <<= 1; + if (PCXHR_INPB(chip->mgr, PCXHR_XLX_CSUER) & mask) + temp |= 1; + } + dev_dbg(chip->card->dev, "read iec958 AES %d byte %d = 0x%x\n", + chip->chip_idx, aes_idx, temp); + *aes_bits = temp; + return 0; +} + + +int hr222_iec958_update_byte(struct snd_pcxhr *chip, + int aes_idx, unsigned char aes_bits) +{ + int i; + unsigned char new_bits = aes_bits; + unsigned char old_bits = chip->aes_bits[aes_idx]; + unsigned char idx = (unsigned char)(aes_idx * 8); + for (i = 0; i < 8; i++) { + if ((old_bits & 0x01) != (new_bits & 0x01)) { + /* idx < 192 */ + PCXHR_OUTPB(chip->mgr, PCXHR_XLX_RUER, idx); + /* write C and U bit */ + PCXHR_OUTPB(chip->mgr, PCXHR_XLX_CSUER, new_bits&0x01 ? + PCXHR_SUER_BIT_C_WRITE_MASK : 0); + } + idx++; + old_bits >>= 1; + new_bits >>= 1; + } + chip->aes_bits[aes_idx] = aes_bits; + return 0; +} + +static void hr222_micro_boost(struct pcxhr_mgr *mgr, int level) +{ + unsigned char boost_mask; + boost_mask = (unsigned char) (level << PCXHR_SELMIC_PREAMPLI_OFFSET); + if (boost_mask & (~PCXHR_SELMIC_PREAMPLI_MASK)) + return; /* only values form 0 to 3 accepted */ + + mgr->xlx_selmic &= ~PCXHR_SELMIC_PREAMPLI_MASK; + mgr->xlx_selmic |= boost_mask; + + PCXHR_OUTPB(mgr, PCXHR_XLX_SELMIC, mgr->xlx_selmic); + + dev_dbg(&mgr->pci->dev, "hr222_micro_boost : set %x\n", boost_mask); +} + +static void hr222_phantom_power(struct pcxhr_mgr *mgr, int power) +{ + if (power) + mgr->xlx_selmic |= PCXHR_SELMIC_PHANTOM_ALIM; + else + mgr->xlx_selmic &= ~PCXHR_SELMIC_PHANTOM_ALIM; + + PCXHR_OUTPB(mgr, PCXHR_XLX_SELMIC, mgr->xlx_selmic); + + dev_dbg(&mgr->pci->dev, "hr222_phantom_power : set %d\n", power); +} + + +/* mic level */ +static const DECLARE_TLV_DB_SCALE(db_scale_mic_hr222, -9850, 50, 650); + +static int hr222_mic_vol_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 = HR222_MICRO_CAPTURE_LEVEL_MIN; /* -98 dB */ + /* gains from 9 dB to 31.5 dB not recommended; use micboost instead */ + uinfo->value.integer.max = HR222_MICRO_CAPTURE_LEVEL_MAX; /* +7 dB */ + return 0; +} + +static int hr222_mic_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->mic_volume; + return 0; +} + +static int hr222_mic_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + + guard(mutex)(&chip->mgr->mixer_mutex); + if (chip->mic_volume != ucontrol->value.integer.value[0]) { + changed = 1; + chip->mic_volume = ucontrol->value.integer.value[0]; + hr222_update_analog_audio_level(chip, 1, 0); + } + return changed; +} + +static const struct snd_kcontrol_new hr222_control_mic_level = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Mic Capture Volume", + .info = hr222_mic_vol_info, + .get = hr222_mic_vol_get, + .put = hr222_mic_vol_put, + .tlv = { .p = db_scale_mic_hr222 }, +}; + + +/* mic boost level */ +static const DECLARE_TLV_DB_SCALE(db_scale_micboost_hr222, 0, 1800, 5400); + +static int hr222_mic_boost_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; /* 0 dB */ + uinfo->value.integer.max = 3; /* 54 dB */ + return 0; +} + +static int hr222_mic_boost_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->mic_boost; + return 0; +} + +static int hr222_mic_boost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + + guard(mutex)(&chip->mgr->mixer_mutex); + if (chip->mic_boost != ucontrol->value.integer.value[0]) { + changed = 1; + chip->mic_boost = ucontrol->value.integer.value[0]; + hr222_micro_boost(chip->mgr, chip->mic_boost); + } + return changed; +} + +static const struct snd_kcontrol_new hr222_control_mic_boost = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "MicBoost Capture Volume", + .info = hr222_mic_boost_info, + .get = hr222_mic_boost_get, + .put = hr222_mic_boost_put, + .tlv = { .p = db_scale_micboost_hr222 }, +}; + + +/******************* Phantom power switch *******************/ +#define hr222_phantom_power_info snd_ctl_boolean_mono_info + +static int hr222_phantom_power_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->phantom_power; + return 0; +} + +static int hr222_phantom_power_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int power, changed = 0; + + guard(mutex)(&chip->mgr->mixer_mutex); + power = !!ucontrol->value.integer.value[0]; + if (chip->phantom_power != power) { + hr222_phantom_power(chip->mgr, power); + chip->phantom_power = power; + changed = 1; + } + return changed; +} + +static const struct snd_kcontrol_new hr222_phantom_power_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Phantom Power Switch", + .info = hr222_phantom_power_info, + .get = hr222_phantom_power_get, + .put = hr222_phantom_power_put, +}; + + +int hr222_add_mic_controls(struct snd_pcxhr *chip) +{ + int err; + if (!chip->mgr->board_has_mic) + return 0; + + /* controls */ + err = snd_ctl_add(chip->card, snd_ctl_new1(&hr222_control_mic_level, + chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, snd_ctl_new1(&hr222_control_mic_boost, + chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, snd_ctl_new1(&hr222_phantom_power_switch, + chip)); + return err; +} diff --git a/sound/pci/pcxhr/pcxhr_mix22.h b/sound/pci/pcxhr/pcxhr_mix22.h new file mode 100644 index 000000000..b1e4ffca8 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mix22.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * low level interface with interrupt ans message handling + * + * Copyright (c) 2004 by Digigram + */ + +#ifndef __SOUND_PCXHR_MIX22_H +#define __SOUND_PCXHR_MIX22_H + +struct pcxhr_mgr; + +int hr222_sub_init(struct pcxhr_mgr *mgr); +int hr222_sub_set_clock(struct pcxhr_mgr *mgr, unsigned int rate, + int *changed); +int hr222_get_external_clock(struct pcxhr_mgr *mgr, + enum pcxhr_clock_type clock_type, + int *sample_rate); + +int hr222_read_gpio(struct pcxhr_mgr *mgr, int is_gpi, int *value); +int hr222_write_gpo(struct pcxhr_mgr *mgr, int value); +int hr222_manage_timecode(struct pcxhr_mgr *mgr, int enable); + +#define HR222_LINE_PLAYBACK_LEVEL_MIN 0 /* -25.5 dB */ +#define HR222_LINE_PLAYBACK_ZERO_LEVEL 51 /* 0.0 dB */ +#define HR222_LINE_PLAYBACK_LEVEL_MAX 99 /* +24.0 dB */ + +#define HR222_LINE_CAPTURE_LEVEL_MIN 0 /* -111.5 dB */ +#define HR222_LINE_CAPTURE_ZERO_LEVEL 223 /* 0.0 dB */ +#define HR222_LINE_CAPTURE_LEVEL_MAX 255 /* +16 dB */ +#define HR222_MICRO_CAPTURE_LEVEL_MIN 0 /* -98.5 dB */ +#define HR222_MICRO_CAPTURE_LEVEL_MAX 210 /* +6.5 dB */ + +int hr222_update_analog_audio_level(struct snd_pcxhr *chip, + int is_capture, + int channel); +int hr222_set_audio_source(struct snd_pcxhr *chip); +int hr222_iec958_capture_byte(struct snd_pcxhr *chip, int aes_idx, + unsigned char *aes_bits); +int hr222_iec958_update_byte(struct snd_pcxhr *chip, int aes_idx, + unsigned char aes_bits); + +int hr222_add_mic_controls(struct snd_pcxhr *chip); + +#endif /* __SOUND_PCXHR_MIX22_H */ diff --git a/sound/pci/pcxhr/pcxhr_mixer.c b/sound/pci/pcxhr/pcxhr_mixer.c new file mode 100644 index 000000000..03daa2832 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mixer.c @@ -0,0 +1,1232 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#define __NO_VERSION__ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * mixer callbacks + * + * Copyright (c) 2004 by Digigram + */ + +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" +#include +#include +#include +#include "pcxhr_mixer.h" +#include "pcxhr_mix22.h" + +#define PCXHR_LINE_CAPTURE_LEVEL_MIN 0 /* -112.0 dB */ +#define PCXHR_LINE_CAPTURE_LEVEL_MAX 255 /* +15.5 dB */ +#define PCXHR_LINE_CAPTURE_ZERO_LEVEL 224 /* 0.0 dB ( 0 dBu -> 0 dBFS ) */ + +#define PCXHR_LINE_PLAYBACK_LEVEL_MIN 0 /* -104.0 dB */ +#define PCXHR_LINE_PLAYBACK_LEVEL_MAX 128 /* +24.0 dB */ +#define PCXHR_LINE_PLAYBACK_ZERO_LEVEL 104 /* 0.0 dB ( 0 dBFS -> 0 dBu ) */ + +static const DECLARE_TLV_DB_SCALE(db_scale_analog_capture, -11200, 50, 1550); +static const DECLARE_TLV_DB_SCALE(db_scale_analog_playback, -10400, 100, 2400); + +static const DECLARE_TLV_DB_SCALE(db_scale_a_hr222_capture, -11150, 50, 1600); +static const DECLARE_TLV_DB_SCALE(db_scale_a_hr222_playback, -2550, 50, 2400); + +static int pcxhr_update_analog_audio_level(struct snd_pcxhr *chip, + int is_capture, int channel) +{ + int err, vol; + struct pcxhr_rmh rmh; + + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + if (is_capture) { + rmh.cmd[0] |= IO_NUM_REG_IN_ANA_LEVEL; + rmh.cmd[2] = chip->analog_capture_volume[channel]; + } else { + rmh.cmd[0] |= IO_NUM_REG_OUT_ANA_LEVEL; + if (chip->analog_playback_active[channel]) + vol = chip->analog_playback_volume[channel]; + else + vol = PCXHR_LINE_PLAYBACK_LEVEL_MIN; + /* playback analog levels are inversed */ + rmh.cmd[2] = PCXHR_LINE_PLAYBACK_LEVEL_MAX - vol; + } + rmh.cmd[1] = 1 << ((2 * chip->chip_idx) + channel); /* audio mask */ + rmh.cmd_len = 3; + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err < 0) { + dev_dbg(chip->card->dev, + "error update_analog_audio_level card(%d)" + " is_capture(%d) err(%x)\n", + chip->chip_idx, is_capture, err); + return -EINVAL; + } + return 0; +} + +/* + * analog level control + */ +static int pcxhr_analog_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + if (kcontrol->private_value == 0) { /* playback */ + if (chip->mgr->is_hr_stereo) { + uinfo->value.integer.min = + HR222_LINE_PLAYBACK_LEVEL_MIN; /* -25 dB */ + uinfo->value.integer.max = + HR222_LINE_PLAYBACK_LEVEL_MAX; /* +24 dB */ + } else { + uinfo->value.integer.min = + PCXHR_LINE_PLAYBACK_LEVEL_MIN; /*-104 dB */ + uinfo->value.integer.max = + PCXHR_LINE_PLAYBACK_LEVEL_MAX; /* +24 dB */ + } + } else { /* capture */ + if (chip->mgr->is_hr_stereo) { + uinfo->value.integer.min = + HR222_LINE_CAPTURE_LEVEL_MIN; /*-112 dB */ + uinfo->value.integer.max = + HR222_LINE_CAPTURE_LEVEL_MAX; /* +15.5 dB */ + } else { + uinfo->value.integer.min = + PCXHR_LINE_CAPTURE_LEVEL_MIN; /*-112 dB */ + uinfo->value.integer.max = + PCXHR_LINE_CAPTURE_LEVEL_MAX; /* +15.5 dB */ + } + } + return 0; +} + +static int pcxhr_analog_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + if (kcontrol->private_value == 0) { /* playback */ + ucontrol->value.integer.value[0] = chip->analog_playback_volume[0]; + ucontrol->value.integer.value[1] = chip->analog_playback_volume[1]; + } else { /* capture */ + ucontrol->value.integer.value[0] = chip->analog_capture_volume[0]; + ucontrol->value.integer.value[1] = chip->analog_capture_volume[1]; + } + return 0; +} + +static int pcxhr_analog_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int is_capture, i; + + guard(mutex)(&chip->mgr->mixer_mutex); + is_capture = (kcontrol->private_value != 0); + for (i = 0; i < 2; i++) { + int new_volume = ucontrol->value.integer.value[i]; + int *stored_volume = is_capture ? + &chip->analog_capture_volume[i] : + &chip->analog_playback_volume[i]; + if (is_capture) { + if (chip->mgr->is_hr_stereo) { + if (new_volume < HR222_LINE_CAPTURE_LEVEL_MIN || + new_volume > HR222_LINE_CAPTURE_LEVEL_MAX) + continue; + } else { + if (new_volume < PCXHR_LINE_CAPTURE_LEVEL_MIN || + new_volume > PCXHR_LINE_CAPTURE_LEVEL_MAX) + continue; + } + } else { + if (chip->mgr->is_hr_stereo) { + if (new_volume < HR222_LINE_PLAYBACK_LEVEL_MIN || + new_volume > HR222_LINE_PLAYBACK_LEVEL_MAX) + continue; + } else { + if (new_volume < PCXHR_LINE_PLAYBACK_LEVEL_MIN || + new_volume > PCXHR_LINE_PLAYBACK_LEVEL_MAX) + continue; + } + } + if (*stored_volume != new_volume) { + *stored_volume = new_volume; + changed = 1; + if (chip->mgr->is_hr_stereo) + hr222_update_analog_audio_level(chip, + is_capture, i); + else + pcxhr_update_analog_audio_level(chip, + is_capture, i); + } + } + return changed; +} + +static const struct snd_kcontrol_new pcxhr_control_analog_level = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + /* name will be filled later */ + .info = pcxhr_analog_vol_info, + .get = pcxhr_analog_vol_get, + .put = pcxhr_analog_vol_put, + /* tlv will be filled later */ +}; + +/* shared */ + +#define pcxhr_sw_info snd_ctl_boolean_stereo_info + +static int pcxhr_audio_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->analog_playback_active[0]; + ucontrol->value.integer.value[1] = chip->analog_playback_active[1]; + return 0; +} + +static int pcxhr_audio_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int i, changed = 0; + + guard(mutex)(&chip->mgr->mixer_mutex); + for(i = 0; i < 2; i++) { + if (chip->analog_playback_active[i] != + ucontrol->value.integer.value[i]) { + chip->analog_playback_active[i] = + !!ucontrol->value.integer.value[i]; + changed = 1; + /* update playback levels */ + if (chip->mgr->is_hr_stereo) + hr222_update_analog_audio_level(chip, 0, i); + else + pcxhr_update_analog_audio_level(chip, 0, i); + } + } + return changed; +} + +static const struct snd_kcontrol_new pcxhr_control_output_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = pcxhr_sw_info, /* shared */ + .get = pcxhr_audio_sw_get, + .put = pcxhr_audio_sw_put +}; + + +#define PCXHR_DIGITAL_LEVEL_MIN 0x000 /* -110 dB */ +#define PCXHR_DIGITAL_LEVEL_MAX 0x1ff /* +18 dB */ +#define PCXHR_DIGITAL_ZERO_LEVEL 0x1b7 /* 0 dB */ + +static const DECLARE_TLV_DB_SCALE(db_scale_digital, -10975, 25, 1800); + +#define MORE_THAN_ONE_STREAM_LEVEL 0x000001 +#define VALID_STREAM_PAN_LEVEL_MASK 0x800000 +#define VALID_STREAM_LEVEL_MASK 0x400000 +#define VALID_STREAM_LEVEL_1_MASK 0x200000 +#define VALID_STREAM_LEVEL_2_MASK 0x100000 + +static int pcxhr_update_playback_stream_level(struct snd_pcxhr* chip, int idx) +{ + int err; + struct pcxhr_rmh rmh; + struct pcxhr_pipe *pipe = &chip->playback_pipe; + int left, right; + + if (chip->digital_playback_active[idx][0]) + left = chip->digital_playback_volume[idx][0]; + else + left = PCXHR_DIGITAL_LEVEL_MIN; + if (chip->digital_playback_active[idx][1]) + right = chip->digital_playback_volume[idx][1]; + else + right = PCXHR_DIGITAL_LEVEL_MIN; + + pcxhr_init_rmh(&rmh, CMD_STREAM_OUT_LEVEL_ADJUST); + /* add pipe and stream mask */ + pcxhr_set_pipe_cmd_params(&rmh, 0, pipe->first_audio, 0, 1<left / right->right panoramic level */ + rmh.cmd[0] |= MORE_THAN_ONE_STREAM_LEVEL; + rmh.cmd[2] = VALID_STREAM_PAN_LEVEL_MASK | VALID_STREAM_LEVEL_1_MASK; + rmh.cmd[2] |= (left << 10); + rmh.cmd[3] = VALID_STREAM_PAN_LEVEL_MASK | VALID_STREAM_LEVEL_2_MASK; + rmh.cmd[3] |= right; + rmh.cmd_len = 4; + + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err < 0) { + dev_dbg(chip->card->dev, "error update_playback_stream_level " + "card(%d) err(%x)\n", chip->chip_idx, err); + return -EINVAL; + } + return 0; +} + +#define AUDIO_IO_HAS_MUTE_LEVEL 0x400000 +#define AUDIO_IO_HAS_MUTE_MONITOR_1 0x200000 +#define VALID_AUDIO_IO_DIGITAL_LEVEL 0x000001 +#define VALID_AUDIO_IO_MONITOR_LEVEL 0x000002 +#define VALID_AUDIO_IO_MUTE_LEVEL 0x000004 +#define VALID_AUDIO_IO_MUTE_MONITOR_1 0x000008 + +static int pcxhr_update_audio_pipe_level(struct snd_pcxhr *chip, + int capture, int channel) +{ + int err; + struct pcxhr_rmh rmh; + struct pcxhr_pipe *pipe; + + if (capture) + pipe = &chip->capture_pipe[0]; + else + pipe = &chip->playback_pipe; + + pcxhr_init_rmh(&rmh, CMD_AUDIO_LEVEL_ADJUST); + /* add channel mask */ + pcxhr_set_pipe_cmd_params(&rmh, capture, 0, 0, + 1 << (channel + pipe->first_audio)); + /* TODO : if mask (3 << pipe->first_audio) is used, left and right + * channel will be programmed to the same params */ + if (capture) { + rmh.cmd[0] |= VALID_AUDIO_IO_DIGITAL_LEVEL; + /* VALID_AUDIO_IO_MUTE_LEVEL not yet handled + * (capture pipe level) */ + rmh.cmd[2] = chip->digital_capture_volume[channel]; + } else { + rmh.cmd[0] |= VALID_AUDIO_IO_MONITOR_LEVEL | + VALID_AUDIO_IO_MUTE_MONITOR_1; + /* VALID_AUDIO_IO_DIGITAL_LEVEL and VALID_AUDIO_IO_MUTE_LEVEL + * not yet handled (playback pipe level) + */ + rmh.cmd[2] = chip->monitoring_volume[channel] << 10; + if (chip->monitoring_active[channel] == 0) + rmh.cmd[2] |= AUDIO_IO_HAS_MUTE_MONITOR_1; + } + rmh.cmd_len = 3; + + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err < 0) { + dev_dbg(chip->card->dev, + "error update_audio_level(%d) err=%x\n", + chip->chip_idx, err); + return -EINVAL; + } + return 0; +} + + +/* shared */ +static int pcxhr_digital_vol_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 = PCXHR_DIGITAL_LEVEL_MIN; /* -109.5 dB */ + uinfo->value.integer.max = PCXHR_DIGITAL_LEVEL_MAX; /* 18.0 dB */ + return 0; +} + + +static int pcxhr_pcm_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + int *stored_volume; + int is_capture = kcontrol->private_value; + + guard(mutex)(&chip->mgr->mixer_mutex); + if (is_capture) /* digital capture */ + stored_volume = chip->digital_capture_volume; + else /* digital playback */ + stored_volume = chip->digital_playback_volume[idx]; + ucontrol->value.integer.value[0] = stored_volume[0]; + ucontrol->value.integer.value[1] = stored_volume[1]; + return 0; +} + +static int pcxhr_pcm_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + int changed = 0; + int is_capture = kcontrol->private_value; + int *stored_volume; + int i; + + guard(mutex)(&chip->mgr->mixer_mutex); + if (is_capture) /* digital capture */ + stored_volume = chip->digital_capture_volume; + else /* digital playback */ + stored_volume = chip->digital_playback_volume[idx]; + for (i = 0; i < 2; i++) { + int vol = ucontrol->value.integer.value[i]; + if (vol < PCXHR_DIGITAL_LEVEL_MIN || + vol > PCXHR_DIGITAL_LEVEL_MAX) + continue; + if (stored_volume[i] != vol) { + stored_volume[i] = vol; + changed = 1; + if (is_capture) /* update capture volume */ + pcxhr_update_audio_pipe_level(chip, 1, i); + } + } + if (!is_capture && changed) /* update playback volume */ + pcxhr_update_playback_stream_level(chip, idx); + return changed; +} + +static const struct snd_kcontrol_new snd_pcxhr_pcm_vol = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + /* name will be filled later */ + /* count will be filled later */ + .info = pcxhr_digital_vol_info, /* shared */ + .get = pcxhr_pcm_vol_get, + .put = pcxhr_pcm_vol_put, + .tlv = { .p = db_scale_digital }, +}; + + +static int pcxhr_pcm_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->digital_playback_active[idx][0]; + ucontrol->value.integer.value[1] = chip->digital_playback_active[idx][1]; + return 0; +} + +static int pcxhr_pcm_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */ + int i, j; + + guard(mutex)(&chip->mgr->mixer_mutex); + j = idx; + for (i = 0; i < 2; i++) { + if (chip->digital_playback_active[j][i] != + ucontrol->value.integer.value[i]) { + chip->digital_playback_active[j][i] = + !!ucontrol->value.integer.value[i]; + changed = 1; + } + } + if (changed) + pcxhr_update_playback_stream_level(chip, idx); + return changed; +} + +static const struct snd_kcontrol_new pcxhr_control_pcm_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .count = PCXHR_PLAYBACK_STREAMS, + .info = pcxhr_sw_info, /* shared */ + .get = pcxhr_pcm_sw_get, + .put = pcxhr_pcm_sw_put +}; + + +/* + * monitoring level control + */ + +static int pcxhr_monitor_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->monitoring_volume[0]; + ucontrol->value.integer.value[1] = chip->monitoring_volume[1]; + return 0; +} + +static int pcxhr_monitor_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int i; + + guard(mutex)(&chip->mgr->mixer_mutex); + for (i = 0; i < 2; i++) { + if (chip->monitoring_volume[i] != + ucontrol->value.integer.value[i]) { + chip->monitoring_volume[i] = + ucontrol->value.integer.value[i]; + if (chip->monitoring_active[i]) + /* update monitoring volume and mute */ + /* do only when monitoring is unmuted */ + pcxhr_update_audio_pipe_level(chip, 0, i); + changed = 1; + } + } + return changed; +} + +static const struct snd_kcontrol_new pcxhr_control_monitor_vol = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Monitoring Playback Volume", + .info = pcxhr_digital_vol_info, /* shared */ + .get = pcxhr_monitor_vol_get, + .put = pcxhr_monitor_vol_put, + .tlv = { .p = db_scale_digital }, +}; + +/* + * monitoring switch control + */ + +static int pcxhr_monitor_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mgr->mixer_mutex); + ucontrol->value.integer.value[0] = chip->monitoring_active[0]; + ucontrol->value.integer.value[1] = chip->monitoring_active[1]; + return 0; +} + +static int pcxhr_monitor_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int i; + + guard(mutex)(&chip->mgr->mixer_mutex); + for (i = 0; i < 2; i++) { + if (chip->monitoring_active[i] != + ucontrol->value.integer.value[i]) { + chip->monitoring_active[i] = + !!ucontrol->value.integer.value[i]; + changed |= (1<chip_idx) { + case 0 : mask = PCXHR_SOURCE_AUDIO01_UER; codec = CS8420_01_CS; break; + case 1 : mask = PCXHR_SOURCE_AUDIO23_UER; codec = CS8420_23_CS; break; + case 2 : mask = PCXHR_SOURCE_AUDIO45_UER; codec = CS8420_45_CS; break; + case 3 : mask = PCXHR_SOURCE_AUDIO67_UER; codec = CS8420_67_CS; break; + default: return -EINVAL; + } + if (chip->audio_capture_source != 0) { + reg = mask; /* audio source from digital plug */ + } else { + reg = 0; /* audio source from analog plug */ + } + /* set the input source */ + pcxhr_write_io_num_reg_cont(chip->mgr, mask, reg, &changed); + /* resync them (otherwise channel inversion possible) */ + if (changed) { + pcxhr_init_rmh(&rmh, CMD_RESYNC_AUDIO_INPUTS); + rmh.cmd[0] |= (1 << chip->chip_idx); + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + return err; + } + if (chip->mgr->board_aes_in_192k) { + int i; + unsigned int src_config = 0xC0; + /* update all src configs with one call */ + for (i = 0; (i < 4) && (i < chip->mgr->capture_chips); i++) { + if (chip->mgr->chip[i]->audio_capture_source == 2) + src_config |= (1 << (3 - i)); + } + /* set codec SRC on off */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd_len = 2; + rmh.cmd[0] |= IO_NUM_REG_CONFIG_SRC; + rmh.cmd[1] = src_config; + err = pcxhr_send_msg(chip->mgr, &rmh); + } else { + int use_src = 0; + if (chip->audio_capture_source == 2) + use_src = 1; + /* set codec SRC on off */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd_len = 3; + rmh.cmd[0] |= IO_NUM_UER_CHIP_REG; + rmh.cmd[1] = codec; + rmh.cmd[2] = ((CS8420_DATA_FLOW_CTL & CHIP_SIG_AND_MAP_SPI) | + (use_src ? 0x41 : 0x54)); + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + return err; + rmh.cmd[2] = ((CS8420_CLOCK_SRC_CTL & CHIP_SIG_AND_MAP_SPI) | + (use_src ? 0x41 : 0x49)); + err = pcxhr_send_msg(chip->mgr, &rmh); + } + return err; +} + +static int pcxhr_audio_src_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *texts[5] = { + "Line", "Digital", "Digi+SRC", "Mic", "Line+Mic" + }; + int i; + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + i = 2; /* no SRC, no Mic available */ + if (chip->mgr->board_has_aes1) { + i = 3; /* SRC available */ + if (chip->mgr->board_has_mic) + i = 5; /* Mic and MicroMix available */ + } + return snd_ctl_enum_info(uinfo, 1, i, texts); +} + +static int pcxhr_audio_src_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->audio_capture_source; + return 0; +} + +static int pcxhr_audio_src_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int ret = 0; + int i = 2; /* no SRC, no Mic available */ + if (chip->mgr->board_has_aes1) { + i = 3; /* SRC available */ + if (chip->mgr->board_has_mic) + i = 5; /* Mic and MicroMix available */ + } + if (ucontrol->value.enumerated.item[0] >= i) + return -EINVAL; + guard(mutex)(&chip->mgr->mixer_mutex); + if (chip->audio_capture_source != ucontrol->value.enumerated.item[0]) { + chip->audio_capture_source = ucontrol->value.enumerated.item[0]; + if (chip->mgr->is_hr_stereo) + hr222_set_audio_source(chip); + else + pcxhr_set_audio_source(chip); + ret = 1; + } + return ret; +} + +static const struct snd_kcontrol_new pcxhr_control_audio_src = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = pcxhr_audio_src_info, + .get = pcxhr_audio_src_get, + .put = pcxhr_audio_src_put, +}; + + +/* + * clock type selection + * enum pcxhr_clock_type { + * PCXHR_CLOCK_TYPE_INTERNAL = 0, + * PCXHR_CLOCK_TYPE_WORD_CLOCK, + * PCXHR_CLOCK_TYPE_AES_SYNC, + * PCXHR_CLOCK_TYPE_AES_1, + * PCXHR_CLOCK_TYPE_AES_2, + * PCXHR_CLOCK_TYPE_AES_3, + * PCXHR_CLOCK_TYPE_AES_4, + * PCXHR_CLOCK_TYPE_MAX = PCXHR_CLOCK_TYPE_AES_4, + * HR22_CLOCK_TYPE_INTERNAL = PCXHR_CLOCK_TYPE_INTERNAL, + * HR22_CLOCK_TYPE_AES_SYNC, + * HR22_CLOCK_TYPE_AES_1, + * HR22_CLOCK_TYPE_MAX = HR22_CLOCK_TYPE_AES_1, + * }; + */ + +static int pcxhr_clock_type_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *textsPCXHR[7] = { + "Internal", "WordClock", "AES Sync", + "AES 1", "AES 2", "AES 3", "AES 4" + }; + static const char *textsHR22[3] = { + "Internal", "AES Sync", "AES 1" + }; + const char **texts; + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + int clock_items = 2; /* at least Internal and AES Sync clock */ + if (mgr->board_has_aes1) { + clock_items += mgr->capture_chips; /* add AES x */ + if (!mgr->is_hr_stereo) + clock_items += 1; /* add word clock */ + } + if (mgr->is_hr_stereo) { + texts = textsHR22; + snd_BUG_ON(clock_items > (HR22_CLOCK_TYPE_MAX+1)); + } else { + texts = textsPCXHR; + snd_BUG_ON(clock_items > (PCXHR_CLOCK_TYPE_MAX+1)); + } + return snd_ctl_enum_info(uinfo, 1, clock_items, texts); +} + +static int pcxhr_clock_type_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = mgr->use_clock_type; + return 0; +} + +static int pcxhr_clock_type_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + int rate, ret = 0; + unsigned int clock_items = 2; /* at least Internal and AES Sync clock */ + if (mgr->board_has_aes1) { + clock_items += mgr->capture_chips; /* add AES x */ + if (!mgr->is_hr_stereo) + clock_items += 1; /* add word clock */ + } + if (ucontrol->value.enumerated.item[0] >= clock_items) + return -EINVAL; + guard(mutex)(&mgr->mixer_mutex); + if (mgr->use_clock_type != ucontrol->value.enumerated.item[0]) { + guard(mutex)(&mgr->setup_mutex); + mgr->use_clock_type = ucontrol->value.enumerated.item[0]; + rate = 0; + if (mgr->use_clock_type != PCXHR_CLOCK_TYPE_INTERNAL) { + pcxhr_get_external_clock(mgr, mgr->use_clock_type, + &rate); + } else { + rate = mgr->sample_rate; + if (!rate) + rate = 48000; + } + if (rate) { + pcxhr_set_clock(mgr, rate); + if (mgr->sample_rate) + mgr->sample_rate = rate; + } + ret = 1; /* return 1 even if the set was not done. ok ? */ + } + return ret; +} + +static const struct snd_kcontrol_new pcxhr_control_clock_type = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Mode", + .info = pcxhr_clock_type_info, + .get = pcxhr_clock_type_get, + .put = pcxhr_clock_type_put, +}; + +/* + * clock rate control + * specific control that scans the sample rates on the external plugs + */ +static int pcxhr_clock_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3 + mgr->capture_chips; + uinfo->value.integer.min = 0; /* clock not present */ + uinfo->value.integer.max = 192000; /* max sample rate 192 kHz */ + return 0; +} + +static int pcxhr_clock_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol); + int i, err, rate; + + guard(mutex)(&mgr->mixer_mutex); + for(i = 0; i < 3 + mgr->capture_chips; i++) { + if (i == PCXHR_CLOCK_TYPE_INTERNAL) + rate = mgr->sample_rate_real; + else { + err = pcxhr_get_external_clock(mgr, i, &rate); + if (err) + break; + } + ucontrol->value.integer.value[i] = rate; + } + return 0; +} + +static const struct snd_kcontrol_new pcxhr_control_clock_rate = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Clock Rates", + .info = pcxhr_clock_rate_info, + .get = pcxhr_clock_rate_get, +}; + +/* + * IEC958 status bits + */ +static int pcxhr_iec958_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int pcxhr_iec958_capture_byte(struct snd_pcxhr *chip, + int aes_idx, unsigned char *aes_bits) +{ + int i, err; + unsigned char temp; + struct pcxhr_rmh rmh; + + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); + rmh.cmd[0] |= IO_NUM_UER_CHIP_REG; + switch (chip->chip_idx) { + /* instead of CS8420_01_CS use CS8416_01_CS for AES SYNC plug */ + case 0: rmh.cmd[1] = CS8420_01_CS; break; + case 1: rmh.cmd[1] = CS8420_23_CS; break; + case 2: rmh.cmd[1] = CS8420_45_CS; break; + case 3: rmh.cmd[1] = CS8420_67_CS; break; + default: return -EINVAL; + } + if (chip->mgr->board_aes_in_192k) { + switch (aes_idx) { + case 0: rmh.cmd[2] = CS8416_CSB0; break; + case 1: rmh.cmd[2] = CS8416_CSB1; break; + case 2: rmh.cmd[2] = CS8416_CSB2; break; + case 3: rmh.cmd[2] = CS8416_CSB3; break; + case 4: rmh.cmd[2] = CS8416_CSB4; break; + default: return -EINVAL; + } + } else { + switch (aes_idx) { + /* instead of CS8420_CSB0 use CS8416_CSBx for AES SYNC plug */ + case 0: rmh.cmd[2] = CS8420_CSB0; break; + case 1: rmh.cmd[2] = CS8420_CSB1; break; + case 2: rmh.cmd[2] = CS8420_CSB2; break; + case 3: rmh.cmd[2] = CS8420_CSB3; break; + case 4: rmh.cmd[2] = CS8420_CSB4; break; + default: return -EINVAL; + } + } + /* size and code the chip id for the fpga */ + rmh.cmd[1] &= 0x0fffff; + /* chip signature + map for spi read */ + rmh.cmd[2] &= CHIP_SIG_AND_MAP_SPI; + rmh.cmd_len = 3; + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + return err; + + if (chip->mgr->board_aes_in_192k) { + temp = (unsigned char)rmh.stat[1]; + } else { + temp = 0; + /* reversed bit order (not with CS8416_01_CS) */ + for (i = 0; i < 8; i++) { + temp <<= 1; + if (rmh.stat[1] & (1 << i)) + temp |= 1; + } + } + dev_dbg(chip->card->dev, "read iec958 AES %d byte %d = 0x%x\n", + chip->chip_idx, aes_idx, temp); + *aes_bits = temp; + return 0; +} + +static int pcxhr_iec958_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + unsigned char aes_bits; + int i, err; + + guard(mutex)(&chip->mgr->mixer_mutex); + for(i = 0; i < 5; i++) { + if (kcontrol->private_value == 0) /* playback */ + aes_bits = chip->aes_bits[i]; + else { /* capture */ + if (chip->mgr->is_hr_stereo) + err = hr222_iec958_capture_byte(chip, i, + &aes_bits); + else + err = pcxhr_iec958_capture_byte(chip, i, + &aes_bits); + if (err) + break; + } + ucontrol->value.iec958.status[i] = aes_bits; + } + return 0; +} + +static int pcxhr_iec958_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + for (i = 0; i < 5; i++) + ucontrol->value.iec958.status[i] = 0xff; + return 0; +} + +static int pcxhr_iec958_update_byte(struct snd_pcxhr *chip, + int aes_idx, unsigned char aes_bits) +{ + int i, err, cmd; + unsigned char new_bits = aes_bits; + unsigned char old_bits = chip->aes_bits[aes_idx]; + struct pcxhr_rmh rmh; + + for (i = 0; i < 8; i++) { + if ((old_bits & 0x01) != (new_bits & 0x01)) { + cmd = chip->chip_idx & 0x03; /* chip index 0..3 */ + if (chip->chip_idx > 3) + /* new bit used if chip_idx>3 (PCX1222HR) */ + cmd |= 1 << 22; + cmd |= ((aes_idx << 3) + i) << 2; /* add bit offset */ + cmd |= (new_bits & 0x01) << 23; /* add bit value */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd[0] |= IO_NUM_REG_CUER; + rmh.cmd[1] = cmd; + rmh.cmd_len = 2; + dev_dbg(chip->card->dev, + "write iec958 AES %d byte %d bit %d (cmd %x)\n", + chip->chip_idx, aes_idx, i, cmd); + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err) + return err; + } + old_bits >>= 1; + new_bits >>= 1; + } + chip->aes_bits[aes_idx] = aes_bits; + return 0; +} + +static int pcxhr_iec958_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + int i, changed = 0; + + /* playback */ + guard(mutex)(&chip->mgr->mixer_mutex); + for (i = 0; i < 5; i++) { + if (ucontrol->value.iec958.status[i] != chip->aes_bits[i]) { + if (chip->mgr->is_hr_stereo) + hr222_iec958_update_byte(chip, i, + ucontrol->value.iec958.status[i]); + else + pcxhr_iec958_update_byte(chip, i, + ucontrol->value.iec958.status[i]); + changed = 1; + } + } + return changed; +} + +static const struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = pcxhr_iec958_info, + .get = pcxhr_iec958_mask_get +}; +static const struct snd_kcontrol_new pcxhr_control_playback_iec958 = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = pcxhr_iec958_info, + .get = pcxhr_iec958_get, + .put = pcxhr_iec958_put, + .private_value = 0 /* playback */ +}; + +static const struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK), + .info = pcxhr_iec958_info, + .get = pcxhr_iec958_mask_get +}; +static const struct snd_kcontrol_new pcxhr_control_capture_iec958 = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT), + .info = pcxhr_iec958_info, + .get = pcxhr_iec958_get, + .private_value = 1 /* capture */ +}; + +static void pcxhr_init_audio_levels(struct snd_pcxhr *chip) +{ + int i; + + for (i = 0; i < 2; i++) { + if (chip->nb_streams_play) { + int j; + /* at boot time the digital volumes are unmuted 0dB */ + for (j = 0; j < PCXHR_PLAYBACK_STREAMS; j++) { + chip->digital_playback_active[j][i] = 1; + chip->digital_playback_volume[j][i] = + PCXHR_DIGITAL_ZERO_LEVEL; + } + /* after boot, only two bits are set on the uer + * interface + */ + chip->aes_bits[0] = (IEC958_AES0_PROFESSIONAL | + IEC958_AES0_PRO_FS_48000); +#ifdef CONFIG_SND_DEBUG + /* analog volumes for playback + * (is LEVEL_MIN after boot) + */ + chip->analog_playback_active[i] = 1; + if (chip->mgr->is_hr_stereo) + chip->analog_playback_volume[i] = + HR222_LINE_PLAYBACK_ZERO_LEVEL; + else { + chip->analog_playback_volume[i] = + PCXHR_LINE_PLAYBACK_ZERO_LEVEL; + pcxhr_update_analog_audio_level(chip, 0, i); + } +#endif + /* stereo cards need to be initialised after boot */ + if (chip->mgr->is_hr_stereo) + hr222_update_analog_audio_level(chip, 0, i); + } + if (chip->nb_streams_capt) { + /* at boot time the digital volumes are unmuted 0dB */ + chip->digital_capture_volume[i] = + PCXHR_DIGITAL_ZERO_LEVEL; + chip->analog_capture_active = 1; +#ifdef CONFIG_SND_DEBUG + /* analog volumes for playback + * (is LEVEL_MIN after boot) + */ + if (chip->mgr->is_hr_stereo) + chip->analog_capture_volume[i] = + HR222_LINE_CAPTURE_ZERO_LEVEL; + else { + chip->analog_capture_volume[i] = + PCXHR_LINE_CAPTURE_ZERO_LEVEL; + pcxhr_update_analog_audio_level(chip, 1, i); + } +#endif + /* stereo cards need to be initialised after boot */ + if (chip->mgr->is_hr_stereo) + hr222_update_analog_audio_level(chip, 1, i); + } + } + + return; +} + + +int pcxhr_create_mixer(struct pcxhr_mgr *mgr) +{ + struct snd_pcxhr *chip; + int err, i; + + mutex_init(&mgr->mixer_mutex); /* can be in another place */ + + for (i = 0; i < mgr->num_cards; i++) { + struct snd_kcontrol_new temp; + chip = mgr->chip[i]; + + if (chip->nb_streams_play) { + /* analog output level control */ + temp = pcxhr_control_analog_level; + temp.name = "Master Playback Volume"; + temp.private_value = 0; /* playback */ + if (mgr->is_hr_stereo) + temp.tlv.p = db_scale_a_hr222_playback; + else + temp.tlv.p = db_scale_analog_playback; + err = snd_ctl_add(chip->card, + snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + + /* output mute controls */ + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_output_switch, + chip)); + if (err < 0) + return err; + + temp = snd_pcxhr_pcm_vol; + temp.name = "PCM Playback Volume"; + temp.count = PCXHR_PLAYBACK_STREAMS; + temp.private_value = 0; /* playback */ + err = snd_ctl_add(chip->card, + snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_pcm_switch, chip)); + if (err < 0) + return err; + + /* IEC958 controls */ + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_playback_iec958_mask, + chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_playback_iec958, + chip)); + if (err < 0) + return err; + } + if (chip->nb_streams_capt) { + /* analog input level control */ + temp = pcxhr_control_analog_level; + temp.name = "Line Capture Volume"; + temp.private_value = 1; /* capture */ + if (mgr->is_hr_stereo) + temp.tlv.p = db_scale_a_hr222_capture; + else + temp.tlv.p = db_scale_analog_capture; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + + temp = snd_pcxhr_pcm_vol; + temp.name = "PCM Capture Volume"; + temp.count = 1; + temp.private_value = 1; /* capture */ + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + + /* Audio source */ + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_audio_src, chip)); + if (err < 0) + return err; + + /* IEC958 controls */ + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_capture_iec958_mask, + chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_capture_iec958, + chip)); + if (err < 0) + return err; + + if (mgr->is_hr_stereo) { + err = hr222_add_mic_controls(chip); + if (err < 0) + return err; + } + } + /* monitoring only if playback and capture device available */ + if (chip->nb_streams_capt > 0 && chip->nb_streams_play > 0) { + /* monitoring */ + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_monitor_vol, chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_monitor_sw, chip)); + if (err < 0) + return err; + } + + if (i == 0) { + /* clock mode only one control per pcxhr */ + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_clock_type, mgr)); + if (err < 0) + return err; + /* non standard control used to scan + * the external clock presence/frequencies + */ + err = snd_ctl_add(chip->card, + snd_ctl_new1(&pcxhr_control_clock_rate, mgr)); + if (err < 0) + return err; + } + + /* init values for the mixer data */ + pcxhr_init_audio_levels(chip); + } + + return 0; +} diff --git a/sound/pci/pcxhr/pcxhr_mixer.h b/sound/pci/pcxhr/pcxhr_mixer.h new file mode 100644 index 000000000..9c0862064 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mixer.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * include file for mixer + * + * Copyright (c) 2004 by Digigram + */ + +#ifndef __SOUND_PCXHR_MIXER_H +#define __SOUND_PCXHR_MIXER_H + +/* exported */ +int pcxhr_create_mixer(struct pcxhr_mgr *mgr); + +#endif /* __SOUND_PCXHR_MIXER_H */ diff --git a/sound/pci/riptide/Makefile b/sound/pci/riptide/Makefile new file mode 100644 index 000000000..9b4e95958 --- /dev/null +++ b/sound/pci/riptide/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-riptide-y := riptide.o + +obj-$(CONFIG_SND_RIPTIDE) += snd-riptide.o diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c new file mode 100644 index 000000000..99c00e46c --- /dev/null +++ b/sound/pci/riptide/riptide.c @@ -0,0 +1,2172 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant Riptide Soundchip + * + * Copyright (c) 2004 Peter Gruber + */ +/* + History: + - 02/15/2004 first release + + This Driver is based on the OSS Driver version from Linuxant (riptide-0.6lnxtbeta03111100) + credits from the original files: + + MODULE NAME: cnxt_rt.h + AUTHOR: K. Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 02/1/2000 KNL + + MODULE NAME: int_mdl.c + AUTHOR: Konstantin Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 10/01/99 KNL + + MODULE NAME: riptide.h + AUTHOR: O. Druzhinin (Transcribed by OLD) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 10/16/97 OLD + + MODULE NAME: Rp_Cmdif.cpp + AUTHOR: O. Druzhinin (Transcribed by OLD) + K. Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Adopted from NT4 driver 6/22/99 OLD + Ported to Linux 9/01/99 KNL + + MODULE NAME: rt_hw.c + AUTHOR: O. Druzhinin (Transcribed by OLD) + C. Lazarev (Transcribed by CNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 11/18/97 OLD + Hardware functions for RipTide 11/24/97 CNL + (ES1) are coded + Hardware functions for RipTide 12/24/97 CNL + (A0) are coded + Hardware functions for RipTide 03/20/98 CNL + (A1) are coded + Boot loader is included 05/07/98 CNL + Redesigned for WDM 07/27/98 CNL + Redesigned for Linux 09/01/99 CNL + + MODULE NAME: rt_hw.h + AUTHOR: C. Lazarev (Transcribed by CNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 11/18/97 CNL + + MODULE NAME: rt_mdl.c + AUTHOR: Konstantin Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 10/01/99 KNL + + MODULE NAME: mixer.h + AUTHOR: K. Kenney + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created from MS W95 Sample 11/28/95 KRS + RipTide 10/15/97 KRS + Adopted for Windows NT driver 01/20/98 CNL +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK 1 +#endif + +MODULE_AUTHOR("Peter Gruber "); +MODULE_DESCRIPTION("riptide"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("riptide.hex"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; + +#ifdef SUPPORT_JOYSTICK +static int joystick_port[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS - 1)] = 0x200 }; +#endif +static int mpu_port[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS - 1)] = 0x330 }; +static int opl3_port[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS - 1)] = 0x388 }; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Riptide soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Riptide soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Riptide soundcard."); +#ifdef SUPPORT_JOYSTICK +module_param_hw_array(joystick_port, int, ioport, NULL, 0444); +MODULE_PARM_DESC(joystick_port, "Joystick port # for Riptide soundcard."); +#endif +module_param_hw_array(mpu_port, int, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU401 port # for Riptide driver."); +module_param_hw_array(opl3_port, int, ioport, NULL, 0444); +MODULE_PARM_DESC(opl3_port, "OPL3 port # for Riptide driver."); + +/* + */ + +#define MPU401_HW_RIPTIDE MPU401_HW_MPU401 +#define OPL3_HW_RIPTIDE OPL3_HW_OPL3 + +#define PCI_EXT_CapId 0x40 +#define PCI_EXT_NextCapPrt 0x41 +#define PCI_EXT_PWMC 0x42 +#define PCI_EXT_PWSCR 0x44 +#define PCI_EXT_Data00 0x46 +#define PCI_EXT_PMSCR_BSE 0x47 +#define PCI_EXT_SB_Base 0x48 +#define PCI_EXT_FM_Base 0x4a +#define PCI_EXT_MPU_Base 0x4C +#define PCI_EXT_Game_Base 0x4E +#define PCI_EXT_Legacy_Mask 0x50 +#define PCI_EXT_AsicRev 0x52 +#define PCI_EXT_Reserved3 0x53 + +#define LEGACY_ENABLE_ALL 0x8000 /* legacy device options */ +#define LEGACY_ENABLE_SB 0x4000 +#define LEGACY_ENABLE_FM 0x2000 +#define LEGACY_ENABLE_MPU_INT 0x1000 +#define LEGACY_ENABLE_MPU 0x0800 +#define LEGACY_ENABLE_GAMEPORT 0x0400 + +#define MAX_WRITE_RETRY 10 /* cmd interface limits */ +#define MAX_ERROR_COUNT 10 +#define CMDIF_TIMEOUT 50000 +#define RESET_TRIES 5 + +#define READ_PORT_ULONG(p) inl((unsigned long)&(p)) +#define WRITE_PORT_ULONG(p,x) outl(x,(unsigned long)&(p)) + +#define READ_AUDIO_CONTROL(p) READ_PORT_ULONG(p->audio_control) +#define WRITE_AUDIO_CONTROL(p,x) WRITE_PORT_ULONG(p->audio_control,x) +#define UMASK_AUDIO_CONTROL(p,x) WRITE_PORT_ULONG(p->audio_control,READ_PORT_ULONG(p->audio_control)|x) +#define MASK_AUDIO_CONTROL(p,x) WRITE_PORT_ULONG(p->audio_control,READ_PORT_ULONG(p->audio_control)&x) +#define READ_AUDIO_STATUS(p) READ_PORT_ULONG(p->audio_status) + +#define SET_GRESET(p) UMASK_AUDIO_CONTROL(p,0x0001) /* global reset switch */ +#define UNSET_GRESET(p) MASK_AUDIO_CONTROL(p,~0x0001) +#define SET_AIE(p) UMASK_AUDIO_CONTROL(p,0x0004) /* interrupt enable */ +#define UNSET_AIE(p) MASK_AUDIO_CONTROL(p,~0x0004) +#define SET_AIACK(p) UMASK_AUDIO_CONTROL(p,0x0008) /* interrupt acknowledge */ +#define UNSET_AIACKT(p) MASKAUDIO_CONTROL(p,~0x0008) +#define SET_ECMDAE(p) UMASK_AUDIO_CONTROL(p,0x0010) +#define UNSET_ECMDAE(p) MASK_AUDIO_CONTROL(p,~0x0010) +#define SET_ECMDBE(p) UMASK_AUDIO_CONTROL(p,0x0020) +#define UNSET_ECMDBE(p) MASK_AUDIO_CONTROL(p,~0x0020) +#define SET_EDATAF(p) UMASK_AUDIO_CONTROL(p,0x0040) +#define UNSET_EDATAF(p) MASK_AUDIO_CONTROL(p,~0x0040) +#define SET_EDATBF(p) UMASK_AUDIO_CONTROL(p,0x0080) +#define UNSET_EDATBF(p) MASK_AUDIO_CONTROL(p,~0x0080) +#define SET_ESBIRQON(p) UMASK_AUDIO_CONTROL(p,0x0100) +#define UNSET_ESBIRQON(p) MASK_AUDIO_CONTROL(p,~0x0100) +#define SET_EMPUIRQ(p) UMASK_AUDIO_CONTROL(p,0x0200) +#define UNSET_EMPUIRQ(p) MASK_AUDIO_CONTROL(p,~0x0200) +#define IS_CMDE(a) (READ_PORT_ULONG(a->stat)&0x1) /* cmd empty */ +#define IS_DATF(a) (READ_PORT_ULONG(a->stat)&0x2) /* data filled */ +#define IS_READY(p) (READ_AUDIO_STATUS(p)&0x0001) +#define IS_DLREADY(p) (READ_AUDIO_STATUS(p)&0x0002) +#define IS_DLERR(p) (READ_AUDIO_STATUS(p)&0x0004) +#define IS_GERR(p) (READ_AUDIO_STATUS(p)&0x0008) /* error ! */ +#define IS_CMDAEIRQ(p) (READ_AUDIO_STATUS(p)&0x0010) +#define IS_CMDBEIRQ(p) (READ_AUDIO_STATUS(p)&0x0020) +#define IS_DATAFIRQ(p) (READ_AUDIO_STATUS(p)&0x0040) +#define IS_DATBFIRQ(p) (READ_AUDIO_STATUS(p)&0x0080) +#define IS_EOBIRQ(p) (READ_AUDIO_STATUS(p)&0x0100) /* interrupt status */ +#define IS_EOSIRQ(p) (READ_AUDIO_STATUS(p)&0x0200) +#define IS_EOCIRQ(p) (READ_AUDIO_STATUS(p)&0x0400) +#define IS_UNSLIRQ(p) (READ_AUDIO_STATUS(p)&0x0800) +#define IS_SBIRQ(p) (READ_AUDIO_STATUS(p)&0x1000) +#define IS_MPUIRQ(p) (READ_AUDIO_STATUS(p)&0x2000) + +#define RESP 0x00000001 /* command flags */ +#define PARM 0x00000002 +#define CMDA 0x00000004 +#define CMDB 0x00000008 +#define NILL 0x00000000 + +#define LONG0(a) ((u32)a) /* shifts and masks */ +#define BYTE0(a) (LONG0(a)&0xff) +#define BYTE1(a) (BYTE0(a)<<8) +#define BYTE2(a) (BYTE0(a)<<16) +#define BYTE3(a) (BYTE0(a)<<24) +#define WORD0(a) (LONG0(a)&0xffff) +#define WORD1(a) (WORD0(a)<<8) +#define WORD2(a) (WORD0(a)<<16) +#define TRINIB0(a) (LONG0(a)&0xffffff) +#define TRINIB1(a) (TRINIB0(a)<<8) + +#define RET(a) ((union cmdret *)(a)) + +#define SEND_GETV(p,b) sendcmd(p,RESP,GETV,0,RET(b)) /* get version */ +#define SEND_GETC(p,b,c) sendcmd(p,PARM|RESP,GETC,c,RET(b)) +#define SEND_GUNS(p,b) sendcmd(p,RESP,GUNS,0,RET(b)) +#define SEND_SCID(p,b) sendcmd(p,RESP,SCID,0,RET(b)) +#define SEND_RMEM(p,b,c,d) sendcmd(p,PARM|RESP,RMEM|BYTE1(b),LONG0(c),RET(d)) /* memory access for firmware write */ +#define SEND_SMEM(p,b,c) sendcmd(p,PARM,SMEM|BYTE1(b),LONG0(c),RET(0)) /* memory access for firmware write */ +#define SEND_WMEM(p,b,c) sendcmd(p,PARM,WMEM|BYTE1(b),LONG0(c),RET(0)) /* memory access for firmware write */ +#define SEND_SDTM(p,b,c) sendcmd(p,PARM|RESP,SDTM|TRINIB1(b),0,RET(c)) /* memory access for firmware write */ +#define SEND_GOTO(p,b) sendcmd(p,PARM,GOTO,LONG0(b),RET(0)) /* memory access for firmware write */ +#define SEND_SETDPLL(p) sendcmd(p,0,ARM_SETDPLL,0,RET(0)) +#define SEND_SSTR(p,b,c) sendcmd(p,PARM,SSTR|BYTE3(b),LONG0(c),RET(0)) /* start stream */ +#define SEND_PSTR(p,b) sendcmd(p,PARM,PSTR,BYTE3(b),RET(0)) /* pause stream */ +#define SEND_KSTR(p,b) sendcmd(p,PARM,KSTR,BYTE3(b),RET(0)) /* stop stream */ +#define SEND_KDMA(p) sendcmd(p,0,KDMA,0,RET(0)) /* stop all dma */ +#define SEND_GPOS(p,b,c,d) sendcmd(p,PARM|RESP,GPOS,BYTE3(c)|BYTE2(b),RET(d)) /* get position in dma */ +#define SEND_SETF(p,b,c,d,e,f,g) sendcmd(p,PARM,SETF|WORD1(b)|BYTE3(c),d|BYTE1(e)|BYTE2(f)|BYTE3(g),RET(0)) /* set sample format at mixer */ +#define SEND_GSTS(p,b,c,d) sendcmd(p,PARM|RESP,GSTS,BYTE3(c)|BYTE2(b),RET(d)) +#define SEND_NGPOS(p,b,c,d) sendcmd(p,PARM|RESP,NGPOS,BYTE3(c)|BYTE2(b),RET(d)) +#define SEND_PSEL(p,b,c) sendcmd(p,PARM,PSEL,BYTE2(b)|BYTE3(c),RET(0)) /* activate lbus path */ +#define SEND_PCLR(p,b,c) sendcmd(p,PARM,PCLR,BYTE2(b)|BYTE3(c),RET(0)) /* deactivate lbus path */ +#define SEND_PLST(p,b) sendcmd(p,PARM,PLST,BYTE3(b),RET(0)) +#define SEND_RSSV(p,b,c,d) sendcmd(p,PARM|RESP,RSSV,BYTE2(b)|BYTE3(c),RET(d)) +#define SEND_LSEL(p,b,c,d,e,f,g,h) sendcmd(p,PARM,LSEL|BYTE1(b)|BYTE2(c)|BYTE3(d),BYTE0(e)|BYTE1(f)|BYTE2(g)|BYTE3(h),RET(0)) /* select paths for internal connections */ +#define SEND_SSRC(p,b,c,d,e) sendcmd(p,PARM,SSRC|BYTE1(b)|WORD2(c),WORD0(d)|WORD2(e),RET(0)) /* configure source */ +#define SEND_SLST(p,b) sendcmd(p,PARM,SLST,BYTE3(b),RET(0)) +#define SEND_RSRC(p,b,c) sendcmd(p,RESP,RSRC|BYTE1(b),0,RET(c)) /* read source config */ +#define SEND_SSRB(p,b,c) sendcmd(p,PARM,SSRB|BYTE1(b),WORD2(c),RET(0)) +#define SEND_SDGV(p,b,c,d,e) sendcmd(p,PARM,SDGV|BYTE2(b)|BYTE3(c),WORD0(d)|WORD2(e),RET(0)) /* set digital mixer */ +#define SEND_RDGV(p,b,c,d) sendcmd(p,PARM|RESP,RDGV|BYTE2(b)|BYTE3(c),0,RET(d)) /* read digital mixer */ +#define SEND_DLST(p,b) sendcmd(p,PARM,DLST,BYTE3(b),RET(0)) +#define SEND_SACR(p,b,c) sendcmd(p,PARM,SACR,WORD0(b)|WORD2(c),RET(0)) /* set AC97 register */ +#define SEND_RACR(p,b,c) sendcmd(p,PARM|RESP,RACR,WORD2(b),RET(c)) /* get AC97 register */ +#define SEND_ALST(p,b) sendcmd(p,PARM,ALST,BYTE3(b),RET(0)) +#define SEND_TXAC(p,b,c,d,e,f) sendcmd(p,PARM,TXAC|BYTE1(b)|WORD2(c),WORD0(d)|BYTE2(e)|BYTE3(f),RET(0)) +#define SEND_RXAC(p,b,c,d) sendcmd(p,PARM|RESP,RXAC,BYTE2(b)|BYTE3(c),RET(d)) +#define SEND_SI2S(p,b) sendcmd(p,PARM,SI2S,WORD2(b),RET(0)) + +#define EOB_STATUS 0x80000000 /* status flags : block boundary */ +#define EOS_STATUS 0x40000000 /* : stoppped */ +#define EOC_STATUS 0x20000000 /* : stream end */ +#define ERR_STATUS 0x10000000 +#define EMPTY_STATUS 0x08000000 + +#define IEOB_ENABLE 0x1 /* enable interrupts for status notification above */ +#define IEOS_ENABLE 0x2 +#define IEOC_ENABLE 0x4 +#define RDONCE 0x8 +#define DESC_MAX_MASK 0xff + +#define ST_PLAY 0x1 /* stream states */ +#define ST_STOP 0x2 +#define ST_PAUSE 0x4 + +#define I2S_INTDEC 3 /* config for I2S link */ +#define I2S_MERGER 0 +#define I2S_SPLITTER 0 +#define I2S_MIXER 7 +#define I2S_RATE 44100 + +#define MODEM_INTDEC 4 /* config for modem link */ +#define MODEM_MERGER 3 +#define MODEM_SPLITTER 0 +#define MODEM_MIXER 11 + +#define FM_INTDEC 3 /* config for FM/OPL3 link */ +#define FM_MERGER 0 +#define FM_SPLITTER 0 +#define FM_MIXER 9 + +#define SPLIT_PATH 0x80 /* path splitting flag */ + +enum FIRMWARE { + DATA_REC = 0, EXT_END_OF_FILE, EXT_SEG_ADDR_REC, EXT_GOTO_CMD_REC, + EXT_LIN_ADDR_REC, +}; + +enum CMDS { + GETV = 0x00, GETC, GUNS, SCID, RMEM = + 0x10, SMEM, WMEM, SDTM, GOTO, SSTR = + 0x20, PSTR, KSTR, KDMA, GPOS, SETF, GSTS, NGPOS, PSEL = + 0x30, PCLR, PLST, RSSV, LSEL, SSRC = 0x40, SLST, RSRC, SSRB, SDGV = + 0x50, RDGV, DLST, SACR = 0x60, RACR, ALST, TXAC, RXAC, SI2S = + 0x70, ARM_SETDPLL = 0x72, +}; + +enum E1SOURCE { + ARM2LBUS_FIFO0 = 0, ARM2LBUS_FIFO1, ARM2LBUS_FIFO2, ARM2LBUS_FIFO3, + ARM2LBUS_FIFO4, ARM2LBUS_FIFO5, ARM2LBUS_FIFO6, ARM2LBUS_FIFO7, + ARM2LBUS_FIFO8, ARM2LBUS_FIFO9, ARM2LBUS_FIFO10, ARM2LBUS_FIFO11, + ARM2LBUS_FIFO12, ARM2LBUS_FIFO13, ARM2LBUS_FIFO14, ARM2LBUS_FIFO15, + INTER0_OUT, INTER1_OUT, INTER2_OUT, INTER3_OUT, INTER4_OUT, + INTERM0_OUT, INTERM1_OUT, INTERM2_OUT, INTERM3_OUT, INTERM4_OUT, + INTERM5_OUT, INTERM6_OUT, DECIMM0_OUT, DECIMM1_OUT, DECIMM2_OUT, + DECIMM3_OUT, DECIM0_OUT, SR3_4_OUT, OPL3_SAMPLE, ASRC0, ASRC1, + ACLNK2PADC, ACLNK2MODEM0RX, ACLNK2MIC, ACLNK2MODEM1RX, ACLNK2HNDMIC, + DIGITAL_MIXER_OUT0, GAINFUNC0_OUT, GAINFUNC1_OUT, GAINFUNC2_OUT, + GAINFUNC3_OUT, GAINFUNC4_OUT, SOFTMODEMTX, SPLITTER0_OUTL, + SPLITTER0_OUTR, SPLITTER1_OUTL, SPLITTER1_OUTR, SPLITTER2_OUTL, + SPLITTER2_OUTR, SPLITTER3_OUTL, SPLITTER3_OUTR, MERGER0_OUT, + MERGER1_OUT, MERGER2_OUT, MERGER3_OUT, ARM2LBUS_FIFO_DIRECT, NO_OUT +}; + +enum E2SINK { + LBUS2ARM_FIFO0 = 0, LBUS2ARM_FIFO1, LBUS2ARM_FIFO2, LBUS2ARM_FIFO3, + LBUS2ARM_FIFO4, LBUS2ARM_FIFO5, LBUS2ARM_FIFO6, LBUS2ARM_FIFO7, + INTER0_IN, INTER1_IN, INTER2_IN, INTER3_IN, INTER4_IN, INTERM0_IN, + INTERM1_IN, INTERM2_IN, INTERM3_IN, INTERM4_IN, INTERM5_IN, INTERM6_IN, + DECIMM0_IN, DECIMM1_IN, DECIMM2_IN, DECIMM3_IN, DECIM0_IN, SR3_4_IN, + PDAC2ACLNK, MODEM0TX2ACLNK, MODEM1TX2ACLNK, HNDSPK2ACLNK, + DIGITAL_MIXER_IN0, DIGITAL_MIXER_IN1, DIGITAL_MIXER_IN2, + DIGITAL_MIXER_IN3, DIGITAL_MIXER_IN4, DIGITAL_MIXER_IN5, + DIGITAL_MIXER_IN6, DIGITAL_MIXER_IN7, DIGITAL_MIXER_IN8, + DIGITAL_MIXER_IN9, DIGITAL_MIXER_IN10, DIGITAL_MIXER_IN11, + GAINFUNC0_IN, GAINFUNC1_IN, GAINFUNC2_IN, GAINFUNC3_IN, GAINFUNC4_IN, + SOFTMODEMRX, SPLITTER0_IN, SPLITTER1_IN, SPLITTER2_IN, SPLITTER3_IN, + MERGER0_INL, MERGER0_INR, MERGER1_INL, MERGER1_INR, MERGER2_INL, + MERGER2_INR, MERGER3_INL, MERGER3_INR, E2SINK_MAX +}; + +enum LBUS_SINK { + LS_SRC_INTERPOLATOR = 0, LS_SRC_INTERPOLATORM, LS_SRC_DECIMATOR, + LS_SRC_DECIMATORM, LS_MIXER_IN, LS_MIXER_GAIN_FUNCTION, + LS_SRC_SPLITTER, LS_SRC_MERGER, LS_NONE1, LS_NONE2, +}; + +enum RT_CHANNEL_IDS { + M0TX = 0, M1TX, TAMTX, HSSPKR, PDAC, DSNDTX0, DSNDTX1, DSNDTX2, + DSNDTX3, DSNDTX4, DSNDTX5, DSNDTX6, DSNDTX7, WVSTRTX, COP3DTX, SPARE, + M0RX, HSMIC, M1RX, CLEANRX, MICADC, PADC, COPRX1, COPRX2, + CHANNEL_ID_COUNTER +}; + +enum { SB_CMD = 0, MODEM_CMD, I2S_CMD0, I2S_CMD1, FM_CMD, MAX_CMD }; + +struct lbuspath { + const unsigned char *noconv; + const unsigned char *stereo; + const unsigned char *mono; +}; + +struct cmdport { + u32 data1; /* cmd,param */ + u32 data2; /* param */ + u32 stat; /* status */ + u32 pad[5]; +}; + +struct riptideport { + u32 audio_control; /* status registers */ + u32 audio_status; + u32 pad[2]; + struct cmdport port[2]; /* command ports */ +}; + +struct cmdif { + struct device *dev; + struct riptideport *hwport; + spinlock_t lock; + unsigned int cmdcnt; /* cmd statistics */ + unsigned int cmdtime; + unsigned int cmdtimemax; + unsigned int cmdtimemin; + unsigned int errcnt; + int is_reset; +}; + +struct riptide_firmware { + u16 ASIC; + u16 CODEC; + u16 AUXDSP; + u16 PROG; +}; + +union cmdret { + u8 retbytes[8]; + u16 retwords[4]; + u32 retlongs[2]; +}; + +union firmware_version { + union cmdret ret; + struct riptide_firmware firmware; +}; + +#define get_pcmhwdev(substream) (struct pcmhw *)(substream->runtime->private_data) + +#define PLAYBACK_SUBSTREAMS 3 +struct snd_riptide { + struct snd_card *card; + struct pci_dev *pci; + const struct firmware *fw_entry; + + struct cmdif *cif; + + struct snd_pcm *pcm; + struct snd_pcm *pcm_i2s; + struct snd_rawmidi *rmidi; + struct snd_opl3 *opl3; + struct snd_ac97 *ac97; + struct snd_ac97_bus *ac97_bus; + + struct snd_pcm_substream *playback_substream[PLAYBACK_SUBSTREAMS]; + struct snd_pcm_substream *capture_substream; + + int openstreams; + + int irq; + unsigned long port; + unsigned short mpuaddr; + unsigned short opladdr; +#ifdef SUPPORT_JOYSTICK + unsigned short gameaddr; +#endif + struct resource *res_port; + + unsigned short device_id; + + union firmware_version firmware; + + spinlock_t lock; + struct snd_info_entry *proc_entry; + + unsigned long received_irqs; + unsigned long handled_irqs; + int in_suspend; +}; + +struct sgd { /* scatter gather desriptor */ + __le32 dwNextLink; + __le32 dwSegPtrPhys; + __le32 dwSegLen; + __le32 dwStat_Ctl; +}; + +struct pcmhw { /* pcm descriptor */ + struct lbuspath paths; + const unsigned char *lbuspath; + unsigned char source; + unsigned char intdec[2]; + unsigned char mixer; + unsigned char id; + unsigned char state; + unsigned int rate; + unsigned int channels; + snd_pcm_format_t format; + struct snd_dma_buffer sgdlist; + struct sgd *sgdbuf; + unsigned int size; + unsigned int pages; + unsigned int oldpos; + unsigned int pointer; +}; + +#define CMDRET_ZERO (union cmdret){{(u32)0, (u32) 0}} + +static int sendcmd(struct cmdif *cif, u32 flags, u32 cmd, u32 parm, + union cmdret *ret); +static int getsourcesink(struct cmdif *cif, unsigned char source, + unsigned char sink, unsigned char *a, + unsigned char *b); +static int snd_riptide_initialize(struct snd_riptide *chip); +static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip); + +/* + */ + +static const struct pci_device_id snd_riptide_ids[] = { + { PCI_DEVICE(0x127a, 0x4310) }, + { PCI_DEVICE(0x127a, 0x4320) }, + { PCI_DEVICE(0x127a, 0x4330) }, + { PCI_DEVICE(0x127a, 0x4340) }, + {0,}, +}; + +#ifdef SUPPORT_JOYSTICK +static const struct pci_device_id snd_riptide_joystick_ids[] = { + { PCI_DEVICE(0x127a, 0x4312) }, + { PCI_DEVICE(0x127a, 0x4322) }, + { PCI_DEVICE(0x127a, 0x4332) }, + { PCI_DEVICE(0x127a, 0x4342) }, + {0,}, +}; +#endif + +MODULE_DEVICE_TABLE(pci, snd_riptide_ids); + +/* + */ + +static const unsigned char lbusin2out[E2SINK_MAX + 1][2] = { + {NO_OUT, LS_NONE1}, {NO_OUT, LS_NONE2}, {NO_OUT, LS_NONE1}, {NO_OUT, + LS_NONE2}, + {NO_OUT, LS_NONE1}, {NO_OUT, LS_NONE2}, {NO_OUT, LS_NONE1}, {NO_OUT, + LS_NONE2}, + {INTER0_OUT, LS_SRC_INTERPOLATOR}, {INTER1_OUT, LS_SRC_INTERPOLATOR}, + {INTER2_OUT, LS_SRC_INTERPOLATOR}, {INTER3_OUT, LS_SRC_INTERPOLATOR}, + {INTER4_OUT, LS_SRC_INTERPOLATOR}, {INTERM0_OUT, LS_SRC_INTERPOLATORM}, + {INTERM1_OUT, LS_SRC_INTERPOLATORM}, {INTERM2_OUT, + LS_SRC_INTERPOLATORM}, + {INTERM3_OUT, LS_SRC_INTERPOLATORM}, {INTERM4_OUT, + LS_SRC_INTERPOLATORM}, + {INTERM5_OUT, LS_SRC_INTERPOLATORM}, {INTERM6_OUT, + LS_SRC_INTERPOLATORM}, + {DECIMM0_OUT, LS_SRC_DECIMATORM}, {DECIMM1_OUT, LS_SRC_DECIMATORM}, + {DECIMM2_OUT, LS_SRC_DECIMATORM}, {DECIMM3_OUT, LS_SRC_DECIMATORM}, + {DECIM0_OUT, LS_SRC_DECIMATOR}, {SR3_4_OUT, LS_NONE1}, {NO_OUT, + LS_NONE2}, + {NO_OUT, LS_NONE1}, {NO_OUT, LS_NONE2}, {NO_OUT, LS_NONE1}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN}, + {GAINFUNC0_OUT, LS_MIXER_GAIN_FUNCTION}, {GAINFUNC1_OUT, + LS_MIXER_GAIN_FUNCTION}, + {GAINFUNC2_OUT, LS_MIXER_GAIN_FUNCTION}, {GAINFUNC3_OUT, + LS_MIXER_GAIN_FUNCTION}, + {GAINFUNC4_OUT, LS_MIXER_GAIN_FUNCTION}, {SOFTMODEMTX, LS_NONE1}, + {SPLITTER0_OUTL, LS_SRC_SPLITTER}, {SPLITTER1_OUTL, LS_SRC_SPLITTER}, + {SPLITTER2_OUTL, LS_SRC_SPLITTER}, {SPLITTER3_OUTL, LS_SRC_SPLITTER}, + {MERGER0_OUT, LS_SRC_MERGER}, {MERGER0_OUT, LS_SRC_MERGER}, + {MERGER1_OUT, LS_SRC_MERGER}, + {MERGER1_OUT, LS_SRC_MERGER}, {MERGER2_OUT, LS_SRC_MERGER}, + {MERGER2_OUT, LS_SRC_MERGER}, + {MERGER3_OUT, LS_SRC_MERGER}, {MERGER3_OUT, LS_SRC_MERGER}, {NO_OUT, + LS_NONE2}, +}; + +static const unsigned char lbus_play_opl3[] = { + DIGITAL_MIXER_IN0 + FM_MIXER, 0xff +}; +static const unsigned char lbus_play_modem[] = { + DIGITAL_MIXER_IN0 + MODEM_MIXER, 0xff +}; +static const unsigned char lbus_play_i2s[] = { + INTER0_IN + I2S_INTDEC, DIGITAL_MIXER_IN0 + I2S_MIXER, 0xff +}; +static const unsigned char lbus_play_out[] = { + PDAC2ACLNK, 0xff +}; +static const unsigned char lbus_play_outhp[] = { + HNDSPK2ACLNK, 0xff +}; +static const unsigned char lbus_play_noconv1[] = { + DIGITAL_MIXER_IN0, 0xff +}; +static const unsigned char lbus_play_stereo1[] = { + INTER0_IN, DIGITAL_MIXER_IN0, 0xff +}; +static const unsigned char lbus_play_mono1[] = { + INTERM0_IN, DIGITAL_MIXER_IN0, 0xff +}; +static const unsigned char lbus_play_noconv2[] = { + DIGITAL_MIXER_IN1, 0xff +}; +static const unsigned char lbus_play_stereo2[] = { + INTER1_IN, DIGITAL_MIXER_IN1, 0xff +}; +static const unsigned char lbus_play_mono2[] = { + INTERM1_IN, DIGITAL_MIXER_IN1, 0xff +}; +static const unsigned char lbus_play_noconv3[] = { + DIGITAL_MIXER_IN2, 0xff +}; +static const unsigned char lbus_play_stereo3[] = { + INTER2_IN, DIGITAL_MIXER_IN2, 0xff +}; +static const unsigned char lbus_play_mono3[] = { + INTERM2_IN, DIGITAL_MIXER_IN2, 0xff +}; +static const unsigned char lbus_rec_noconv1[] = { + LBUS2ARM_FIFO5, 0xff +}; +static const unsigned char lbus_rec_stereo1[] = { + DECIM0_IN, LBUS2ARM_FIFO5, 0xff +}; +static const unsigned char lbus_rec_mono1[] = { + DECIMM3_IN, LBUS2ARM_FIFO5, 0xff +}; + +static const unsigned char play_ids[] = { 4, 1, 2, }; +static const unsigned char play_sources[] = { + ARM2LBUS_FIFO4, ARM2LBUS_FIFO1, ARM2LBUS_FIFO2, +}; +static const struct lbuspath lbus_play_paths[] = { + { + .noconv = lbus_play_noconv1, + .stereo = lbus_play_stereo1, + .mono = lbus_play_mono1, + }, + { + .noconv = lbus_play_noconv2, + .stereo = lbus_play_stereo2, + .mono = lbus_play_mono2, + }, + { + .noconv = lbus_play_noconv3, + .stereo = lbus_play_stereo3, + .mono = lbus_play_mono3, + }, +}; +static const struct lbuspath lbus_rec_path = { + .noconv = lbus_rec_noconv1, + .stereo = lbus_rec_stereo1, + .mono = lbus_rec_mono1, +}; + +#define FIRMWARE_VERSIONS 1 +static union firmware_version firmware_versions[] = { + { + .firmware = { + .ASIC = 3, + .CODEC = 2, + .AUXDSP = 3, + .PROG = 773, + }, + }, +}; + +static u32 atoh(const unsigned char *in, unsigned int len) +{ + u32 sum = 0; + unsigned int mult = 1; + unsigned char c; + + while (len) { + int value; + + c = in[len - 1]; + value = hex_to_bin(c); + if (value >= 0) + sum += mult * value; + mult *= 16; + --len; + } + return sum; +} + +static int senddata(struct cmdif *cif, const unsigned char *in, u32 offset) +{ + u32 addr; + u32 data; + u32 i; + const unsigned char *p; + + i = atoh(&in[1], 2); + addr = offset + atoh(&in[3], 4); + if (SEND_SMEM(cif, 0, addr) != 0) + return -EACCES; + p = in + 9; + while (i) { + data = atoh(p, 8); + if (SEND_WMEM(cif, 2, + ((data & 0x0f0f0f0f) << 4) | ((data & 0xf0f0f0f0) + >> 4))) + return -EACCES; + i -= 4; + p += 8; + } + return 0; +} + +static int loadfirmware(struct cmdif *cif, const unsigned char *img, + unsigned int size) +{ + const unsigned char *in; + u32 laddr, saddr, t, val; + int err = 0; + + laddr = saddr = 0; + while (size > 0 && err == 0) { + in = img; + if (in[0] == ':') { + t = atoh(&in[7], 2); + switch (t) { + case DATA_REC: + err = senddata(cif, in, laddr + saddr); + break; + case EXT_SEG_ADDR_REC: + saddr = atoh(&in[9], 4) << 4; + break; + case EXT_LIN_ADDR_REC: + laddr = atoh(&in[9], 4) << 16; + break; + case EXT_GOTO_CMD_REC: + val = atoh(&in[9], 8); + if (SEND_GOTO(cif, val) != 0) + err = -EACCES; + break; + case EXT_END_OF_FILE: + size = 0; + break; + default: + break; + } + while (size > 0) { + size--; + if (*img++ == '\n') + break; + } + } + } + dev_dbg(cif->dev, "load firmware return %d\n", err); + return err; +} + +static void +alloclbuspath(struct cmdif *cif, unsigned char source, + const unsigned char *path, unsigned char *mixer, unsigned char *s) +{ + while (*path != 0xff) { + unsigned char sink, type; + + sink = *path & (~SPLIT_PATH); + if (sink != E2SINK_MAX) { + dev_dbg(cif->dev, "alloc path 0x%x->0x%x\n", source, sink); + SEND_PSEL(cif, source, sink); + source = lbusin2out[sink][0]; + type = lbusin2out[sink][1]; + if (type == LS_MIXER_IN) { + if (mixer) + *mixer = sink - DIGITAL_MIXER_IN0; + } + if (type == LS_SRC_DECIMATORM || + type == LS_SRC_DECIMATOR || + type == LS_SRC_INTERPOLATORM || + type == LS_SRC_INTERPOLATOR) { + if (s) { + if (s[0] != 0xff) + s[1] = sink; + else + s[0] = sink; + } + } + } + if (*path++ & SPLIT_PATH) { + const unsigned char *npath = path; + + while (*npath != 0xff) + npath++; + alloclbuspath(cif, source + 1, ++npath, mixer, s); + } + } +} + +static void +freelbuspath(struct cmdif *cif, unsigned char source, const unsigned char *path) +{ + while (*path != 0xff) { + unsigned char sink; + + sink = *path & (~SPLIT_PATH); + if (sink != E2SINK_MAX) { + dev_dbg(cif->dev, "free path 0x%x->0x%x\n", source, sink); + SEND_PCLR(cif, source, sink); + source = lbusin2out[sink][0]; + } + if (*path++ & SPLIT_PATH) { + const unsigned char *npath = path; + + while (*npath != 0xff) + npath++; + freelbuspath(cif, source + 1, ++npath); + } + } +} + +static int writearm(struct cmdif *cif, u32 addr, u32 data, u32 mask) +{ + union cmdret rptr = CMDRET_ZERO; + unsigned int i = MAX_WRITE_RETRY; + int flag = 1; + + SEND_RMEM(cif, 0x02, addr, &rptr); + rptr.retlongs[0] &= (~mask); + + while (--i) { + SEND_SMEM(cif, 0x01, addr); + SEND_WMEM(cif, 0x02, (rptr.retlongs[0] | data)); + SEND_RMEM(cif, 0x02, addr, &rptr); + if ((rptr.retlongs[0] & data) == data) { + flag = 0; + break; + } else + rptr.retlongs[0] &= ~mask; + } + dev_dbg(cif->dev, "send arm 0x%x 0x%x 0x%x return %d\n", addr, data, mask, + flag); + return flag; +} + +static int sendcmd(struct cmdif *cif, u32 flags, u32 cmd, u32 parm, + union cmdret *ret) +{ + int i, j; + int err; + unsigned int time = 0; + unsigned long irqflags; + struct riptideport *hwport; + struct cmdport *cmdport = NULL; + + if (snd_BUG_ON(!cif)) + return -EINVAL; + + hwport = cif->hwport; + if (cif->errcnt > MAX_ERROR_COUNT) { + if (cif->is_reset) { + dev_err(cif->dev, + "Riptide: Too many failed cmds, reinitializing\n"); + if (riptide_reset(cif, NULL) == 0) { + cif->errcnt = 0; + return -EIO; + } + } + dev_err(cif->dev, "Riptide: Initialization failed.\n"); + return -EINVAL; + } + if (ret) { + ret->retlongs[0] = 0; + ret->retlongs[1] = 0; + } + i = 0; + spin_lock_irqsave(&cif->lock, irqflags); + while (i++ < CMDIF_TIMEOUT && !IS_READY(cif->hwport)) + udelay(10); + if (i > CMDIF_TIMEOUT) { + err = -EBUSY; + goto errout; + } + + err = 0; + for (j = 0, time = 0; time < CMDIF_TIMEOUT; j++, time += 2) { + cmdport = &(hwport->port[j % 2]); + if (IS_DATF(cmdport)) { /* free pending data */ + READ_PORT_ULONG(cmdport->data1); + READ_PORT_ULONG(cmdport->data2); + } + if (IS_CMDE(cmdport)) { + if (flags & PARM) /* put data */ + WRITE_PORT_ULONG(cmdport->data2, parm); + WRITE_PORT_ULONG(cmdport->data1, cmd); /* write cmd */ + if ((flags & RESP) && ret) { + while (!IS_DATF(cmdport) && + time < CMDIF_TIMEOUT) { + udelay(10); + time++; + } + if (time < CMDIF_TIMEOUT) { /* read response */ + ret->retlongs[0] = + READ_PORT_ULONG(cmdport->data1); + ret->retlongs[1] = + READ_PORT_ULONG(cmdport->data2); + } else { + err = -ENOSYS; + goto errout; + } + } + break; + } + udelay(20); + } + if (time == CMDIF_TIMEOUT) { + err = -ENODATA; + goto errout; + } + spin_unlock_irqrestore(&cif->lock, irqflags); + + cif->cmdcnt++; /* update command statistics */ + cif->cmdtime += time; + if (time > cif->cmdtimemax) + cif->cmdtimemax = time; + if (time < cif->cmdtimemin) + cif->cmdtimemin = time; + if ((cif->cmdcnt) % 1000 == 0) + dev_dbg(cif->dev, + "send cmd %d time: %d mintime: %d maxtime %d err: %d\n", + cif->cmdcnt, cif->cmdtime, cif->cmdtimemin, + cif->cmdtimemax, cif->errcnt); + return 0; + + errout: + cif->errcnt++; + spin_unlock_irqrestore(&cif->lock, irqflags); + dev_dbg(cif->dev, + "send cmd %d hw: 0x%x flag: 0x%x cmd: 0x%x parm: 0x%x ret: 0x%x 0x%x CMDE: %d DATF: %d failed %d\n", + cif->cmdcnt, (int)((void *)&(cmdport->stat) - (void *)hwport), + flags, cmd, parm, ret ? ret->retlongs[0] : 0, + ret ? ret->retlongs[1] : 0, IS_CMDE(cmdport), IS_DATF(cmdport), + err); + return err; +} + +static int +setmixer(struct cmdif *cif, short num, unsigned short rval, unsigned short lval) +{ + union cmdret rptr = CMDRET_ZERO; + int i = 0; + + dev_dbg(cif->dev, "sent mixer %d: 0x%x 0x%x\n", num, rval, lval); + do { + SEND_SDGV(cif, num, num, rval, lval); + SEND_RDGV(cif, num, num, &rptr); + if (rptr.retwords[0] == lval && rptr.retwords[1] == rval) + return 0; + } while (i++ < MAX_WRITE_RETRY); + dev_dbg(cif->dev, "sent mixer failed\n"); + return -EIO; +} + +static int getpaths(struct cmdif *cif, unsigned char *o) +{ + unsigned char src[E2SINK_MAX]; + unsigned char sink[E2SINK_MAX]; + int i, j = 0; + + for (i = 0; i < E2SINK_MAX; i++) { + getsourcesink(cif, i, i, &src[i], &sink[i]); + if (sink[i] < E2SINK_MAX) { + o[j++] = sink[i]; + o[j++] = i; + } + } + return j; +} + +static int +getsourcesink(struct cmdif *cif, unsigned char source, unsigned char sink, + unsigned char *a, unsigned char *b) +{ + union cmdret rptr = CMDRET_ZERO; + + if (SEND_RSSV(cif, source, sink, &rptr) && + SEND_RSSV(cif, source, sink, &rptr)) + return -EIO; + *a = rptr.retbytes[0]; + *b = rptr.retbytes[1]; + dev_dbg(cif->dev, "%s 0x%x 0x%x\n", __func__, *a, *b); + return 0; +} + +static int +getsamplerate(struct cmdif *cif, unsigned char *intdec, unsigned int *rate) +{ + unsigned char *s; + unsigned int p[2] = { 0, 0 }; + int i; + union cmdret rptr = CMDRET_ZERO; + + s = intdec; + for (i = 0; i < 2; i++) { + if (*s != 0xff) { + if (SEND_RSRC(cif, *s, &rptr) && + SEND_RSRC(cif, *s, &rptr)) + return -EIO; + p[i] += rptr.retwords[1]; + p[i] *= rptr.retwords[2]; + p[i] += rptr.retwords[3]; + p[i] /= 65536; + } + s++; + } + if (p[0]) { + if (p[1] != p[0]) + dev_dbg(cif->dev, "rates differ %d %d\n", p[0], p[1]); + *rate = (unsigned int)p[0]; + } else + *rate = (unsigned int)p[1]; + dev_dbg(cif->dev, "getsampleformat %d %d %d\n", intdec[0], intdec[1], *rate); + return 0; +} + +static int +setsampleformat(struct cmdif *cif, + unsigned char mixer, unsigned char id, + unsigned char channels, snd_pcm_format_t format) +{ + unsigned char w, ch, sig, order; + + dev_dbg(cif->dev, + "%s mixer: %d id: %d channels: %d format: %d\n", + __func__, mixer, id, channels, format); + ch = channels == 1; + w = snd_pcm_format_width(format) == 8; + sig = snd_pcm_format_unsigned(format) != 0; + order = snd_pcm_format_big_endian(format) != 0; + + if (SEND_SETF(cif, mixer, w, ch, order, sig, id) && + SEND_SETF(cif, mixer, w, ch, order, sig, id)) { + dev_dbg(cif->dev, "%s failed\n", __func__); + return -EIO; + } + return 0; +} + +static int +setsamplerate(struct cmdif *cif, unsigned char *intdec, unsigned int rate) +{ + u32 D, M, N; + union cmdret rptr = CMDRET_ZERO; + int i; + + dev_dbg(cif->dev, "%s intdec: %d,%d rate: %d\n", __func__, + intdec[0], intdec[1], rate); + D = 48000; + M = ((rate == 48000) ? 47999 : rate) * 65536; + N = M % D; + M /= D; + for (i = 0; i < 2; i++) { + if (*intdec != 0xff) { + do { + SEND_SSRC(cif, *intdec, D, M, N); + SEND_RSRC(cif, *intdec, &rptr); + } while (rptr.retwords[1] != D && + rptr.retwords[2] != M && + rptr.retwords[3] != N && + i++ < MAX_WRITE_RETRY); + if (i > MAX_WRITE_RETRY) { + dev_dbg(cif->dev, "sent samplerate %d: %d failed\n", + *intdec, rate); + return -EIO; + } + } + intdec++; + } + return 0; +} + +static int +getmixer(struct cmdif *cif, short num, unsigned short *rval, + unsigned short *lval) +{ + union cmdret rptr = CMDRET_ZERO; + + if (SEND_RDGV(cif, num, num, &rptr) && SEND_RDGV(cif, num, num, &rptr)) + return -EIO; + *rval = rptr.retwords[0]; + *lval = rptr.retwords[1]; + dev_dbg(cif->dev, "got mixer %d: 0x%x 0x%x\n", num, *rval, *lval); + return 0; +} + +static irqreturn_t riptide_handleirq(int irq, void *dev_id) +{ + struct snd_riptide *chip = dev_id; + struct cmdif *cif = chip->cif; + struct snd_pcm_substream *substream[PLAYBACK_SUBSTREAMS + 1]; + struct snd_pcm_runtime *runtime; + struct pcmhw *data = NULL; + unsigned int pos, period_bytes; + struct sgd *c; + int i, j; + unsigned int flag; + + if (!cif) + return IRQ_HANDLED; + + for (i = 0; i < PLAYBACK_SUBSTREAMS; i++) + substream[i] = chip->playback_substream[i]; + substream[i] = chip->capture_substream; + for (i = 0; i < PLAYBACK_SUBSTREAMS + 1; i++) { + if (!substream[i]) + continue; + runtime = substream[i]->runtime; + if (!runtime) + continue; + data = runtime->private_data; + if (!data) + continue; + if (data->state != ST_STOP) { + pos = 0; + for (j = 0; j < data->pages; j++) { + c = &data->sgdbuf[j]; + flag = le32_to_cpu(c->dwStat_Ctl); + if (flag & EOB_STATUS) + pos += le32_to_cpu(c->dwSegLen); + if (flag & EOC_STATUS) + pos += le32_to_cpu(c->dwSegLen); + if ((flag & EOS_STATUS) + && (data->state == ST_PLAY)) { + data->state = ST_STOP; + dev_err(cif->dev, + "Riptide: DMA stopped unexpectedly\n"); + } + c->dwStat_Ctl = + cpu_to_le32(flag & + ~(EOS_STATUS | EOB_STATUS | + EOC_STATUS)); + } + data->pointer += pos; + pos += data->oldpos; + if (data->state != ST_STOP) { + period_bytes = + frames_to_bytes(runtime, + runtime->period_size); + dev_dbg(cif->dev, + "interrupt 0x%x after 0x%lx of 0x%lx frames in period\n", + READ_AUDIO_STATUS(cif->hwport), + bytes_to_frames(runtime, pos), + runtime->period_size); + j = 0; + if (pos >= period_bytes) { + j++; + while (pos >= period_bytes) + pos -= period_bytes; + } + data->oldpos = pos; + if (j > 0) + snd_pcm_period_elapsed(substream[i]); + } + } + } + + return IRQ_HANDLED; +} + +static int riptide_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_riptide *chip = card->private_data; + + chip->in_suspend = 1; + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(chip->ac97); + return 0; +} + +static int riptide_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_riptide *chip = card->private_data; + + snd_riptide_initialize(chip); + snd_ac97_resume(chip->ac97); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + chip->in_suspend = 0; + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(riptide_pm, riptide_suspend, riptide_resume); + +static int try_to_load_firmware(struct cmdif *cif, struct snd_riptide *chip) +{ + union firmware_version firmware = { .ret = CMDRET_ZERO }; + int i, timeout, err; + + for (i = 0; i < 2; i++) { + WRITE_PORT_ULONG(cif->hwport->port[i].data1, 0); + WRITE_PORT_ULONG(cif->hwport->port[i].data2, 0); + } + SET_GRESET(cif->hwport); + udelay(100); + UNSET_GRESET(cif->hwport); + udelay(100); + + for (timeout = 100000; --timeout; udelay(10)) { + if (IS_READY(cif->hwport) && !IS_GERR(cif->hwport)) + break; + } + if (!timeout) { + dev_err(cif->dev, + "Riptide: device not ready, audio status: 0x%x ready: %d gerr: %d\n", + READ_AUDIO_STATUS(cif->hwport), + IS_READY(cif->hwport), IS_GERR(cif->hwport)); + return -EIO; + } else { + dev_dbg(cif->dev, + "Riptide: audio status: 0x%x ready: %d gerr: %d\n", + READ_AUDIO_STATUS(cif->hwport), + IS_READY(cif->hwport), IS_GERR(cif->hwport)); + } + + SEND_GETV(cif, &firmware.ret); + dev_dbg(cif->dev, + "Firmware version: ASIC: %d CODEC %d AUXDSP %d PROG %d\n", + firmware.firmware.ASIC, firmware.firmware.CODEC, + firmware.firmware.AUXDSP, firmware.firmware.PROG); + + if (!chip) + return 1; + + for (i = 0; i < FIRMWARE_VERSIONS; i++) { + if (!memcmp(&firmware_versions[i], &firmware, sizeof(firmware))) + return 1; /* OK */ + + } + + dev_dbg(cif->dev, "Writing Firmware\n"); + if (!chip->fw_entry) { + err = request_firmware(&chip->fw_entry, "riptide.hex", + &chip->pci->dev); + if (err) { + dev_err(cif->dev, + "Riptide: Firmware not available %d\n", err); + return -EIO; + } + } + err = loadfirmware(cif, chip->fw_entry->data, chip->fw_entry->size); + if (err) { + dev_err(cif->dev, + "Riptide: Could not load firmware %d\n", err); + return err; + } + + chip->firmware = firmware; + + return 1; /* OK */ +} + +static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip) +{ + union cmdret rptr = CMDRET_ZERO; + int err, tries; + + if (!cif) + return -EINVAL; + + cif->cmdcnt = 0; + cif->cmdtime = 0; + cif->cmdtimemax = 0; + cif->cmdtimemin = 0xffffffff; + cif->errcnt = 0; + cif->is_reset = 0; + + tries = RESET_TRIES; + do { + err = try_to_load_firmware(cif, chip); + if (err < 0) + return err; + } while (!err && --tries); + + SEND_SACR(cif, 0, AC97_RESET); + SEND_RACR(cif, AC97_RESET, &rptr); + dev_dbg(cif->dev, "AC97: 0x%x 0x%x\n", rptr.retlongs[0], rptr.retlongs[1]); + + SEND_PLST(cif, 0); + SEND_SLST(cif, 0); + SEND_DLST(cif, 0); + SEND_ALST(cif, 0); + SEND_KDMA(cif); + + writearm(cif, 0x301F8, 1, 1); + writearm(cif, 0x301F4, 1, 1); + + SEND_LSEL(cif, MODEM_CMD, 0, 0, MODEM_INTDEC, MODEM_MERGER, + MODEM_SPLITTER, MODEM_MIXER); + setmixer(cif, MODEM_MIXER, 0x7fff, 0x7fff); + alloclbuspath(cif, ARM2LBUS_FIFO13, lbus_play_modem, NULL, NULL); + + SEND_LSEL(cif, FM_CMD, 0, 0, FM_INTDEC, FM_MERGER, FM_SPLITTER, + FM_MIXER); + setmixer(cif, FM_MIXER, 0x7fff, 0x7fff); + writearm(cif, 0x30648 + FM_MIXER * 4, 0x01, 0x00000005); + writearm(cif, 0x301A8, 0x02, 0x00000002); + writearm(cif, 0x30264, 0x08, 0xffffffff); + alloclbuspath(cif, OPL3_SAMPLE, lbus_play_opl3, NULL, NULL); + + SEND_SSRC(cif, I2S_INTDEC, 48000, + ((u32) I2S_RATE * 65536) / 48000, + ((u32) I2S_RATE * 65536) % 48000); + SEND_LSEL(cif, I2S_CMD0, 0, 0, I2S_INTDEC, I2S_MERGER, I2S_SPLITTER, + I2S_MIXER); + SEND_SI2S(cif, 1); + alloclbuspath(cif, ARM2LBUS_FIFO0, lbus_play_i2s, NULL, NULL); + alloclbuspath(cif, DIGITAL_MIXER_OUT0, lbus_play_out, NULL, NULL); + alloclbuspath(cif, DIGITAL_MIXER_OUT0, lbus_play_outhp, NULL, NULL); + + SET_AIACK(cif->hwport); + SET_AIE(cif->hwport); + SET_AIACK(cif->hwport); + cif->is_reset = 1; + + return 0; +} + +static const struct snd_pcm_hardware snd_riptide_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 + | SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5500, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (64 * 1024), + .period_bytes_min = PAGE_SIZE >> 1, + .period_bytes_max = PAGE_SIZE << 8, + .periods_min = 2, + .periods_max = 64, + .fifo_size = 0, +}; +static const struct snd_pcm_hardware snd_riptide_capture = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 + | SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5500, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (64 * 1024), + .period_bytes_min = PAGE_SIZE >> 1, + .period_bytes_max = PAGE_SIZE << 3, + .periods_min = 2, + .periods_max = 64, + .fifo_size = 0, +}; + +static snd_pcm_uframes_t snd_riptide_pointer(struct snd_pcm_substream + *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmhw *data = get_pcmhwdev(substream); + struct cmdif *cif = chip->cif; + union cmdret rptr = CMDRET_ZERO; + snd_pcm_uframes_t ret; + + SEND_GPOS(cif, 0, data->id, &rptr); + if (data->size && runtime->period_size) { + dev_dbg(cif->dev, + "pointer stream %d position 0x%x(0x%x in buffer) bytes 0x%lx(0x%lx in period) frames\n", + data->id, rptr.retlongs[1], rptr.retlongs[1] % data->size, + bytes_to_frames(runtime, rptr.retlongs[1]), + bytes_to_frames(runtime, + rptr.retlongs[1]) % runtime->period_size); + if (rptr.retlongs[1] > data->pointer) + ret = + bytes_to_frames(runtime, + rptr.retlongs[1] % data->size); + else + ret = + bytes_to_frames(runtime, + data->pointer % data->size); + } else { + dev_dbg(cif->dev, + "stream not started or strange parms (%d %ld)\n", + data->size, runtime->period_size); + ret = bytes_to_frames(runtime, 0); + } + return ret; +} + +static int snd_riptide_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int i, j; + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + struct cmdif *cif = chip->cif; + union cmdret rptr = CMDRET_ZERO; + + spin_lock(&chip->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (!(data->state & ST_PLAY)) { + SEND_SSTR(cif, data->id, data->sgdlist.addr); + SET_AIE(cif->hwport); + data->state = ST_PLAY; + if (data->mixer != 0xff) + setmixer(cif, data->mixer, 0x7fff, 0x7fff); + chip->openstreams++; + data->oldpos = 0; + data->pointer = 0; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (data->mixer != 0xff) + setmixer(cif, data->mixer, 0, 0); + setmixer(cif, data->mixer, 0, 0); + SEND_KSTR(cif, data->id); + data->state = ST_STOP; + chip->openstreams--; + j = 0; + do { + i = rptr.retlongs[1]; + SEND_GPOS(cif, 0, data->id, &rptr); + udelay(1); + } while (i != rptr.retlongs[1] && j++ < MAX_WRITE_RETRY); + if (j > MAX_WRITE_RETRY) + dev_err(cif->dev, "Riptide: Could not stop stream!"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (!(data->state & ST_PAUSE)) { + SEND_PSTR(cif, data->id); + data->state |= ST_PAUSE; + chip->openstreams--; + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (data->state & ST_PAUSE) { + SEND_SSTR(cif, data->id, data->sgdlist.addr); + data->state &= ~ST_PAUSE; + chip->openstreams++; + } + break; + default: + spin_unlock(&chip->lock); + return -EINVAL; + } + spin_unlock(&chip->lock); + return 0; +} + +static int snd_riptide_prepare(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmhw *data = get_pcmhwdev(substream); + struct cmdif *cif = chip->cif; + const unsigned char *lbuspath = NULL; + unsigned int rate, channels; + int err = 0; + snd_pcm_format_t format; + + if (snd_BUG_ON(!cif || !data)) + return -EINVAL; + + dev_dbg(cif->dev, "prepare id %d ch: %d f:0x%x r:%d\n", data->id, + runtime->channels, runtime->format, runtime->rate); + + spin_lock_irq(&chip->lock); + channels = runtime->channels; + format = runtime->format; + rate = runtime->rate; + switch (channels) { + case 1: + if (rate == 48000 && format == SNDRV_PCM_FORMAT_S16_LE) + lbuspath = data->paths.noconv; + else + lbuspath = data->paths.mono; + break; + case 2: + if (rate == 48000 && format == SNDRV_PCM_FORMAT_S16_LE) + lbuspath = data->paths.noconv; + else + lbuspath = data->paths.stereo; + break; + } + dev_dbg(cif->dev, "use sgdlist at 0x%p\n", data->sgdlist.area); + if (data->sgdlist.area) { + unsigned int i, j, size, pages, f, pt, period; + struct sgd *c, *p = NULL; + + size = frames_to_bytes(runtime, runtime->buffer_size); + period = frames_to_bytes(runtime, runtime->period_size); + f = PAGE_SIZE; + while ((size + (f >> 1) - 1) <= (f << 7) && (f << 1) > period) + f = f >> 1; + pages = DIV_ROUND_UP(size, f); + data->size = size; + data->pages = pages; + dev_dbg(cif->dev, + "create sgd size: 0x%x pages %d of size 0x%x for period 0x%x\n", + size, pages, f, period); + pt = 0; + j = 0; + for (i = 0; i < pages; i++) { + unsigned int ofs, addr; + c = &data->sgdbuf[i]; + if (p) + p->dwNextLink = cpu_to_le32(data->sgdlist.addr + + (i * + sizeof(struct + sgd))); + c->dwNextLink = cpu_to_le32(data->sgdlist.addr); + ofs = j << PAGE_SHIFT; + addr = snd_pcm_sgbuf_get_addr(substream, ofs) + pt; + c->dwSegPtrPhys = cpu_to_le32(addr); + pt = (pt + f) % PAGE_SIZE; + if (pt == 0) + j++; + c->dwSegLen = cpu_to_le32(f); + c->dwStat_Ctl = + cpu_to_le32(IEOB_ENABLE | IEOS_ENABLE | + IEOC_ENABLE); + p = c; + size -= f; + } + data->sgdbuf[i].dwSegLen = cpu_to_le32(size); + } + if (lbuspath && lbuspath != data->lbuspath) { + if (data->lbuspath) + freelbuspath(cif, data->source, data->lbuspath); + alloclbuspath(cif, data->source, lbuspath, + &data->mixer, data->intdec); + data->lbuspath = lbuspath; + data->rate = 0; + } + if (data->rate != rate || data->format != format || + data->channels != channels) { + data->rate = rate; + data->format = format; + data->channels = channels; + if (setsampleformat + (cif, data->mixer, data->id, channels, format) + || setsamplerate(cif, data->intdec, rate)) + err = -EIO; + } + spin_unlock_irq(&chip->lock); + return err; +} + +static int +snd_riptide_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + struct snd_dma_buffer *sgdlist = &data->sgdlist; + int err; + + dev_dbg(chip->card->dev, "hw params id %d (sgdlist: 0x%p 0x%lx %d)\n", + data->id, sgdlist->area, (unsigned long)sgdlist->addr, + (int)sgdlist->bytes); + if (sgdlist->area) + snd_dma_free_pages(sgdlist); + err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + sizeof(struct sgd) * (DESC_MAX_MASK + 1), + sgdlist); + if (err < 0) { + dev_err(chip->card->dev, + "Riptide: failed to alloc %d dma bytes\n", + (int)sizeof(struct sgd) * (DESC_MAX_MASK + 1)); + return err; + } + data->sgdbuf = (struct sgd *)sgdlist->area; + return 0; +} + +static int snd_riptide_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + struct cmdif *cif = chip->cif; + + if (cif && data) { + if (data->lbuspath) + freelbuspath(cif, data->source, data->lbuspath); + data->lbuspath = NULL; + data->source = 0xff; + data->intdec[0] = 0xff; + data->intdec[1] = 0xff; + + if (data->sgdlist.area) { + snd_dma_free_pages(&data->sgdlist); + data->sgdlist.area = NULL; + } + } + return 0; +} + +static int snd_riptide_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmhw *data; + int sub_num = substream->number; + + chip->playback_substream[sub_num] = substream; + runtime->hw = snd_riptide_playback; + + data = kzalloc_obj(struct pcmhw); + if (data == NULL) + return -ENOMEM; + data->paths = lbus_play_paths[sub_num]; + data->id = play_ids[sub_num]; + data->source = play_sources[sub_num]; + data->intdec[0] = 0xff; + data->intdec[1] = 0xff; + data->state = ST_STOP; + runtime->private_data = data; + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +} + +static int snd_riptide_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmhw *data; + + chip->capture_substream = substream; + runtime->hw = snd_riptide_capture; + + data = kzalloc_obj(struct pcmhw); + if (data == NULL) + return -ENOMEM; + data->paths = lbus_rec_path; + data->id = PADC; + data->source = ACLNK2PADC; + data->intdec[0] = 0xff; + data->intdec[1] = 0xff; + data->state = ST_STOP; + runtime->private_data = data; + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +} + +static int snd_riptide_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + int sub_num = substream->number; + + substream->runtime->private_data = NULL; + chip->playback_substream[sub_num] = NULL; + kfree(data); + return 0; +} + +static int snd_riptide_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_riptide *chip = snd_pcm_substream_chip(substream); + struct pcmhw *data = get_pcmhwdev(substream); + + substream->runtime->private_data = NULL; + chip->capture_substream = NULL; + kfree(data); + return 0; +} + +static const struct snd_pcm_ops snd_riptide_playback_ops = { + .open = snd_riptide_playback_open, + .close = snd_riptide_playback_close, + .hw_params = snd_riptide_hw_params, + .hw_free = snd_riptide_hw_free, + .prepare = snd_riptide_prepare, + .trigger = snd_riptide_trigger, + .pointer = snd_riptide_pointer, +}; +static const struct snd_pcm_ops snd_riptide_capture_ops = { + .open = snd_riptide_capture_open, + .close = snd_riptide_capture_close, + .hw_params = snd_riptide_hw_params, + .hw_free = snd_riptide_hw_free, + .prepare = snd_riptide_prepare, + .trigger = snd_riptide_trigger, + .pointer = snd_riptide_pointer, +}; + +static int snd_riptide_pcm(struct snd_riptide *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "RIPTIDE", device, PLAYBACK_SUBSTREAMS, 1, + &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_riptide_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_riptide_capture_ops); + pcm->private_data = chip; + pcm->info_flags = 0; + strscpy(pcm->name, "RIPTIDE"); + chip->pcm = pcm; + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci->dev, 64 * 1024, 128 * 1024); + return 0; +} + +static irqreturn_t +snd_riptide_interrupt(int irq, void *dev_id) +{ + struct snd_riptide *chip = dev_id; + struct cmdif *cif = chip->cif; + irqreturn_t ret = IRQ_HANDLED; + + if (cif) { + chip->received_irqs++; + if (IS_EOBIRQ(cif->hwport) || IS_EOSIRQ(cif->hwport) || + IS_EOCIRQ(cif->hwport)) { + chip->handled_irqs++; + ret = IRQ_WAKE_THREAD; + } + if (chip->rmidi && IS_MPUIRQ(cif->hwport)) { + chip->handled_irqs++; + snd_mpu401_uart_interrupt(irq, + chip->rmidi->private_data); + } + SET_AIACK(cif->hwport); + } + return ret; +} + +static void +snd_riptide_codec_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct snd_riptide *chip = ac97->private_data; + struct cmdif *cif = chip->cif; + union cmdret rptr = CMDRET_ZERO; + int i = 0; + + if (snd_BUG_ON(!cif)) + return; + + dev_dbg(cif->dev, "Write AC97 reg 0x%x 0x%x\n", reg, val); + do { + SEND_SACR(cif, val, reg); + SEND_RACR(cif, reg, &rptr); + } while (rptr.retwords[1] != val && i++ < MAX_WRITE_RETRY); + if (i > MAX_WRITE_RETRY) + dev_dbg(cif->dev, "Write AC97 reg failed\n"); +} + +static unsigned short snd_riptide_codec_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct snd_riptide *chip = ac97->private_data; + struct cmdif *cif = chip->cif; + union cmdret rptr = CMDRET_ZERO; + + if (snd_BUG_ON(!cif)) + return 0; + + if (SEND_RACR(cif, reg, &rptr) != 0) + SEND_RACR(cif, reg, &rptr); + dev_dbg(cif->dev, "Read AC97 reg 0x%x got 0x%x\n", reg, rptr.retwords[1]); + return rptr.retwords[1]; +} + +static int snd_riptide_initialize(struct snd_riptide *chip) +{ + struct cmdif *cif; + unsigned int device_id; + int err; + + if (snd_BUG_ON(!chip)) + return -EINVAL; + + cif = chip->cif; + if (!cif) { + cif = kzalloc_obj(struct cmdif); + if (!cif) + return -ENOMEM; + cif->dev = chip->card->dev; + cif->hwport = (struct riptideport *)chip->port; + spin_lock_init(&cif->lock); + chip->cif = cif; + } + cif->is_reset = 0; + err = riptide_reset(cif, chip); + if (err) + return err; + device_id = chip->device_id; + switch (device_id) { + case 0x4310: + case 0x4320: + case 0x4330: + dev_dbg(cif->dev, "Modem enable?\n"); + SEND_SETDPLL(cif); + break; + } + dev_dbg(cif->dev, "Enabling MPU IRQs\n"); + if (chip->rmidi) + SET_EMPUIRQ(cif->hwport); + return err; +} + +static void snd_riptide_free(struct snd_card *card) +{ + struct snd_riptide *chip = card->private_data; + struct cmdif *cif; + + cif = chip->cif; + if (cif) { + SET_GRESET(cif->hwport); + udelay(100); + UNSET_GRESET(cif->hwport); + kfree(chip->cif); + } + release_firmware(chip->fw_entry); +} + +static int +snd_riptide_create(struct snd_card *card, struct pci_dev *pci) +{ + struct snd_riptide *chip = card->private_data; + struct riptideport *hwport; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->lock); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->openstreams = 0; + chip->port = pci_resource_start(pci, 0); + chip->received_irqs = 0; + chip->handled_irqs = 0; + chip->cif = NULL; + card->private_free = snd_riptide_free; + + err = pcim_request_all_regions(pci, "RIPTIDE"); + if (err < 0) + return err; + hwport = (struct riptideport *)chip->port; + UNSET_AIE(hwport); + + if (devm_request_threaded_irq(&pci->dev, pci->irq, + snd_riptide_interrupt, + riptide_handleirq, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(&pci->dev, "Riptide: unable to grab IRQ %d\n", + pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + chip->device_id = pci->device; + pci_set_master(pci); + err = snd_riptide_initialize(chip); + if (err < 0) + return err; + + return 0; +} + +static void +snd_riptide_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_riptide *chip = entry->private_data; + struct pcmhw *data; + int i; + struct cmdif *cif = NULL; + unsigned char p[256]; + unsigned short rval = 0, lval = 0; + unsigned int rate; + + if (!chip) + return; + + snd_iprintf(buffer, "%s\n\n", chip->card->longname); + snd_iprintf(buffer, "Device ID: 0x%x\nReceived IRQs: (%ld)%ld\nPorts:", + chip->device_id, chip->handled_irqs, chip->received_irqs); + for (i = 0; i < 64; i += 4) + snd_iprintf(buffer, "%c%02x: %08x", + (i % 16) ? ' ' : '\n', i, inl(chip->port + i)); + cif = chip->cif; + if (cif) { + snd_iprintf(buffer, + "\nVersion: ASIC: %d CODEC: %d AUXDSP: %d PROG: %d", + chip->firmware.firmware.ASIC, + chip->firmware.firmware.CODEC, + chip->firmware.firmware.AUXDSP, + chip->firmware.firmware.PROG); + snd_iprintf(buffer, "\nDigital mixer:"); + for (i = 0; i < 12; i++) { + getmixer(cif, i, &rval, &lval); + snd_iprintf(buffer, "\n %d: %d %d", i, rval, lval); + } + snd_iprintf(buffer, + "\nARM Commands num: %d failed: %d time: %d max: %d min: %d", + cif->cmdcnt, cif->errcnt, + cif->cmdtime, cif->cmdtimemax, cif->cmdtimemin); + } + snd_iprintf(buffer, "\nOpen streams %d:\n", chip->openstreams); + for (i = 0; i < PLAYBACK_SUBSTREAMS; i++) { + if (!chip->playback_substream[i] || + !chip->playback_substream[i]->runtime) + continue; + data = chip->playback_substream[i]->runtime->private_data; + if (data) { + snd_iprintf(buffer, + "stream: %d mixer: %d source: %d (%d,%d)\n", + data->id, data->mixer, data->source, + data->intdec[0], data->intdec[1]); + if (!(getsamplerate(cif, data->intdec, &rate))) + snd_iprintf(buffer, "rate: %d\n", rate); + } + } + if (chip->capture_substream && chip->capture_substream->runtime) { + data = chip->capture_substream->runtime->private_data; + if (data) { + snd_iprintf(buffer, + "stream: %d mixer: %d source: %d (%d,%d)\n", + data->id, data->mixer, + data->source, data->intdec[0], data->intdec[1]); + if (!(getsamplerate(cif, data->intdec, &rate))) + snd_iprintf(buffer, "rate: %d\n", rate); + } + } + snd_iprintf(buffer, "Paths:\n"); + i = getpaths(cif, p); + while (i >= 2) { + i -= 2; + snd_iprintf(buffer, "%x->%x ", p[i], p[i + 1]); + } + snd_iprintf(buffer, "\n"); +} + +static void snd_riptide_proc_init(struct snd_riptide *chip) +{ + snd_card_ro_proc_new(chip->card, "riptide", chip, + snd_riptide_proc_read); +} + +static int snd_riptide_mixer(struct snd_riptide *chip) +{ + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err = 0; + static const struct snd_ac97_bus_ops ops = { + .write = snd_riptide_codec_write, + .read = snd_riptide_codec_read, + }; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.scaps = AC97_SCAP_SKIP_MODEM; + + err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus); + if (err < 0) + return err; + + chip->ac97_bus = pbus; + ac97.pci = chip->pci; + err = snd_ac97_mixer(pbus, &ac97, &chip->ac97); + if (err < 0) + return err; + return err; +} + +#ifdef SUPPORT_JOYSTICK + +static int +snd_riptide_joystick_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + static int dev; + struct gameport *gameport; + int ret; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + + if (!enable[dev]) { + ret = -ENOENT; + goto inc_dev; + } + + if (!joystick_port[dev]) { + ret = 0; + goto inc_dev; + } + + gameport = gameport_allocate_port(); + if (!gameport) { + ret = -ENOMEM; + goto inc_dev; + } + if (!request_region(joystick_port[dev], 8, "Riptide gameport")) { + dev_err(&pci->dev, + "Riptide: cannot grab gameport 0x%x\n", + joystick_port[dev]); + gameport_free_port(gameport); + ret = -EBUSY; + goto inc_dev; + } + + gameport->io = joystick_port[dev]; + gameport_register_port(gameport); + pci_set_drvdata(pci, gameport); + + ret = 0; +inc_dev: + dev++; + return ret; +} + +static void snd_riptide_joystick_remove(struct pci_dev *pci) +{ + struct gameport *gameport = pci_get_drvdata(pci); + if (gameport) { + release_region(gameport->io, 8); + gameport_unregister_port(gameport); + } +} +#endif + +static int +__snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_riptide *chip; + unsigned short val; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + err = snd_riptide_create(card, pci); + if (err < 0) + return err; + err = snd_riptide_pcm(chip, 0); + if (err < 0) + return err; + err = snd_riptide_mixer(chip); + if (err < 0) + return err; + + val = LEGACY_ENABLE_ALL; + if (opl3_port[dev]) + val |= LEGACY_ENABLE_FM; +#ifdef SUPPORT_JOYSTICK + if (joystick_port[dev]) + val |= LEGACY_ENABLE_GAMEPORT; +#endif + if (mpu_port[dev]) + val |= LEGACY_ENABLE_MPU_INT | LEGACY_ENABLE_MPU; + val |= (chip->irq << 4) & 0xf0; + pci_write_config_word(chip->pci, PCI_EXT_Legacy_Mask, val); + if (mpu_port[dev]) { + val = mpu_port[dev]; + pci_write_config_word(chip->pci, PCI_EXT_MPU_Base, val); + err = snd_mpu401_uart_new(card, 0, MPU401_HW_RIPTIDE, + val, MPU401_INFO_IRQ_HOOK, -1, + &chip->rmidi); + if (err < 0) + dev_warn(&pci->dev, + "Riptide: Can't Allocate MPU at 0x%x\n", + val); + else + chip->mpuaddr = val; + } + if (opl3_port[dev]) { + val = opl3_port[dev]; + pci_write_config_word(chip->pci, PCI_EXT_FM_Base, val); + err = snd_opl3_create(card, val, val + 2, + OPL3_HW_RIPTIDE, 0, &chip->opl3); + if (err < 0) + dev_warn(&pci->dev, + "Riptide: Can't Allocate OPL3 at 0x%x\n", + val); + else { + chip->opladdr = val; + err = snd_opl3_hwdep_new(chip->opl3, 0, 1, NULL); + if (err < 0) + dev_warn(&pci->dev, + "Riptide: Can't Allocate OPL3-HWDEP\n"); + } + } +#ifdef SUPPORT_JOYSTICK + if (joystick_port[dev]) { + val = joystick_port[dev]; + pci_write_config_word(chip->pci, PCI_EXT_Game_Base, val); + chip->gameaddr = val; + } +#endif + + strscpy(card->driver, "RIPTIDE"); + strscpy(card->shortname, "Riptide"); +#ifdef SUPPORT_JOYSTICK + scnprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %i mpu 0x%x opl3 0x%x gameport 0x%x", + card->shortname, chip->port, chip->irq, chip->mpuaddr, + chip->opladdr, chip->gameaddr); +#else + scnprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %i mpu 0x%x opl3 0x%x", + card->shortname, chip->port, chip->irq, chip->mpuaddr, + chip->opladdr); +#endif + snd_riptide_proc_init(chip); + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int +snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_card_riptide_probe(pci, pci_id)); +} + +static struct pci_driver driver = { + .name = KBUILD_MODNAME, + .id_table = snd_riptide_ids, + .probe = snd_card_riptide_probe, + .driver = { + .pm = &riptide_pm, + }, +}; + +#ifdef SUPPORT_JOYSTICK +static struct pci_driver joystick_driver = { + .name = KBUILD_MODNAME "-joystick", + .id_table = snd_riptide_joystick_ids, + .probe = snd_riptide_joystick_probe, + .remove = snd_riptide_joystick_remove, +}; +#endif + +static int __init alsa_card_riptide_init(void) +{ + int err; + err = pci_register_driver(&driver); + if (err < 0) + return err; +#if defined(SUPPORT_JOYSTICK) + err = pci_register_driver(&joystick_driver); + /* On failure unregister formerly registered audio driver */ + if (err < 0) + pci_unregister_driver(&driver); +#endif + return err; +} + +static void __exit alsa_card_riptide_exit(void) +{ + pci_unregister_driver(&driver); +#if defined(SUPPORT_JOYSTICK) + pci_unregister_driver(&joystick_driver); +#endif +} + +module_init(alsa_card_riptide_init); +module_exit(alsa_card_riptide_exit); diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c new file mode 100644 index 000000000..454a30a2c --- /dev/null +++ b/sound/pci/rme32.c @@ -0,0 +1,1877 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for RME Digi32, Digi32/8 and Digi32 PRO audio interfaces + * + * Copyright (c) 2002-2004 Martin Langer , + * Pilo Chambert + * + * Thanks to : Anders Torger , + * Henk Hesselink + * for writing the digi96-driver + * and RME for all informations. + * + * **************************************************************************** + * + * Note #1 "Sek'd models" ................................... martin 2002-12-07 + * + * Identical soundcards by Sek'd were labeled: + * RME Digi 32 = Sek'd Prodif 32 + * RME Digi 32 Pro = Sek'd Prodif 96 + * RME Digi 32/8 = Sek'd Prodif Gold + * + * **************************************************************************** + * + * Note #2 "full duplex mode" ............................... martin 2002-12-07 + * + * Full duplex doesn't work. All cards (32, 32/8, 32Pro) are working identical + * in this mode. Rec data and play data are using the same buffer therefore. At + * first you have got the playing bits in the buffer and then (after playing + * them) they were overwitten by the captured sound of the CS8412/14. Both + * modes (play/record) are running harmonically hand in hand in the same buffer + * and you have only one start bit plus one interrupt bit to control this + * paired action. + * This is opposite to the latter rme96 where playing and capturing is totally + * separated and so their full duplex mode is supported by alsa (using two + * start bits and two interrupts for two different buffers). + * But due to the wrong sequence of playing and capturing ALSA shows no solved + * full duplex support for the rme32 at the moment. That's bad, but I'm not + * able to solve it. Are you motivated enough to solve this problem now? Your + * patch would be welcome! + * + * **************************************************************************** + * + * "The story after the long seeking" -- tiwai + * + * Ok, the situation regarding the full duplex is now improved a bit. + * In the fullduplex mode (given by the module parameter), the hardware buffer + * is split to halves for read and write directions at the DMA pointer. + * That is, the half above the current DMA pointer is used for write, and + * the half below is used for read. To mangle this strange behavior, an + * software intermediate buffer is introduced. This is, of course, not good + * from the viewpoint of the data transfer efficiency. However, this allows + * you to use arbitrary buffer sizes, instead of the fixed I/O buffer size. + * + * **************************************************************************** + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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_PNP; /* Enable this card */ +static bool fullduplex[SNDRV_CARDS]; // = {[0 ... (SNDRV_CARDS - 1)] = 1}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for RME Digi32 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for RME Digi32 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable RME Digi32 soundcard."); +module_param_array(fullduplex, bool, NULL, 0444); +MODULE_PARM_DESC(fullduplex, "Support full-duplex mode."); +MODULE_AUTHOR("Martin Langer , Pilo Chambert "); +MODULE_DESCRIPTION("RME Digi32, Digi32/8, Digi32 PRO"); +MODULE_LICENSE("GPL"); + +/* Defines for RME Digi32 series */ +#define RME32_SPDIF_NCHANNELS 2 + +/* Playback and capture buffer size */ +#define RME32_BUFFER_SIZE 0x20000 + +/* IO area size */ +#define RME32_IO_SIZE 0x30000 + +/* IO area offsets */ +#define RME32_IO_DATA_BUFFER 0x0 +#define RME32_IO_CONTROL_REGISTER 0x20000 +#define RME32_IO_GET_POS 0x20000 +#define RME32_IO_CONFIRM_ACTION_IRQ 0x20004 +#define RME32_IO_RESET_POS 0x20100 + +/* Write control register bits */ +#define RME32_WCR_START (1 << 0) /* startbit */ +#define RME32_WCR_MONO (1 << 1) /* 0=stereo, 1=mono + Setting the whole card to mono + doesn't seem to be very useful. + A software-solution can handle + full-duplex with one direction in + stereo and the other way in mono. + So, the hardware should work all + the time in stereo! */ +#define RME32_WCR_MODE24 (1 << 2) /* 0=16bit, 1=32bit */ +#define RME32_WCR_SEL (1 << 3) /* 0=input on output, 1=normal playback/capture */ +#define RME32_WCR_FREQ_0 (1 << 4) /* frequency (play) */ +#define RME32_WCR_FREQ_1 (1 << 5) +#define RME32_WCR_INP_0 (1 << 6) /* input switch */ +#define RME32_WCR_INP_1 (1 << 7) +#define RME32_WCR_RESET (1 << 8) /* Reset address */ +#define RME32_WCR_MUTE (1 << 9) /* digital mute for output */ +#define RME32_WCR_PRO (1 << 10) /* 1=professional, 0=consumer */ +#define RME32_WCR_DS_BM (1 << 11) /* 1=DoubleSpeed (only PRO-Version); 1=BlockMode (only Adat-Version) */ +#define RME32_WCR_ADAT (1 << 12) /* Adat Mode (only Adat-Version) */ +#define RME32_WCR_AUTOSYNC (1 << 13) /* AutoSync */ +#define RME32_WCR_PD (1 << 14) /* DAC Reset (only PRO-Version) */ +#define RME32_WCR_EMP (1 << 15) /* 1=Emphasis on (only PRO-Version) */ + +#define RME32_WCR_BITPOS_FREQ_0 4 +#define RME32_WCR_BITPOS_FREQ_1 5 +#define RME32_WCR_BITPOS_INP_0 6 +#define RME32_WCR_BITPOS_INP_1 7 + +/* Read control register bits */ +#define RME32_RCR_AUDIO_ADDR_MASK 0x1ffff +#define RME32_RCR_LOCK (1 << 23) /* 1=locked, 0=not locked */ +#define RME32_RCR_ERF (1 << 26) /* 1=Error, 0=no Error */ +#define RME32_RCR_FREQ_0 (1 << 27) /* CS841x frequency (record) */ +#define RME32_RCR_FREQ_1 (1 << 28) +#define RME32_RCR_FREQ_2 (1 << 29) +#define RME32_RCR_KMODE (1 << 30) /* card mode: 1=PLL, 0=quartz */ +#define RME32_RCR_IRQ (1 << 31) /* interrupt */ + +#define RME32_RCR_BITPOS_F0 27 +#define RME32_RCR_BITPOS_F1 28 +#define RME32_RCR_BITPOS_F2 29 + +/* Input types */ +#define RME32_INPUT_OPTICAL 0 +#define RME32_INPUT_COAXIAL 1 +#define RME32_INPUT_INTERNAL 2 +#define RME32_INPUT_XLR 3 + +/* Clock modes */ +#define RME32_CLOCKMODE_SLAVE 0 +#define RME32_CLOCKMODE_MASTER_32 1 +#define RME32_CLOCKMODE_MASTER_44 2 +#define RME32_CLOCKMODE_MASTER_48 3 + +/* Block sizes in bytes */ +#define RME32_BLOCK_SIZE 8192 + +/* Software intermediate buffer (max) size */ +#define RME32_MID_BUFFER_SIZE (1024*1024) + +/* Hardware revisions */ +#define RME32_32_REVISION 192 +#define RME32_328_REVISION_OLD 100 +#define RME32_328_REVISION_NEW 101 +#define RME32_PRO_REVISION_WITH_8412 192 +#define RME32_PRO_REVISION_WITH_8414 150 + + +struct rme32 { + spinlock_t lock; + int irq; + unsigned long port; + void __iomem *iobase; + + u32 wcreg; /* cached write control register value */ + u32 wcreg_spdif; /* S/PDIF setup */ + u32 wcreg_spdif_stream; /* S/PDIF setup (temporary) */ + u32 rcreg; /* cached read control register value */ + + u8 rev; /* card revision number */ + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + int playback_frlog; /* log2 of framesize */ + int capture_frlog; + + size_t playback_periodsize; /* in bytes, zero if not used */ + size_t capture_periodsize; /* in bytes, zero if not used */ + + unsigned int fullduplex_mode; + int running; + + struct snd_pcm_indirect playback_pcm; + struct snd_pcm_indirect capture_pcm; + + struct snd_card *card; + struct snd_pcm *spdif_pcm; + struct snd_pcm *adat_pcm; + struct pci_dev *pci; + struct snd_kcontrol *spdif_ctl; +}; + +static const struct pci_device_id snd_rme32_ids[] = { + { PCI_VDEVICE(XILINX_RME, PCI_DEVICE_ID_RME_DIGI32) }, + { PCI_VDEVICE(XILINX_RME, PCI_DEVICE_ID_RME_DIGI32_8) }, + { PCI_VDEVICE(XILINX_RME, PCI_DEVICE_ID_RME_DIGI32_PRO) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_rme32_ids); + +#define RME32_ISWORKING(rme32) ((rme32)->wcreg & RME32_WCR_START) +#define RME32_PRO_WITH_8414(rme32) ((rme32)->pci->device == PCI_DEVICE_ID_RME_DIGI32_PRO && (rme32)->rev == RME32_PRO_REVISION_WITH_8414) + +static int snd_rme32_playback_prepare(struct snd_pcm_substream *substream); + +static int snd_rme32_capture_prepare(struct snd_pcm_substream *substream); + +static int snd_rme32_pcm_trigger(struct snd_pcm_substream *substream, int cmd); + +static void snd_rme32_proc_init(struct rme32 * rme32); + +static int snd_rme32_create_switches(struct snd_card *card, struct rme32 * rme32); + +static inline unsigned int snd_rme32_pcm_byteptr(struct rme32 * rme32) +{ + return (readl(rme32->iobase + RME32_IO_GET_POS) + & RME32_RCR_AUDIO_ADDR_MASK); +} + +/* silence callback for halfduplex mode */ +static int snd_rme32_playback_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + unsigned long count) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + + memset_io(rme32->iobase + RME32_IO_DATA_BUFFER + pos, 0, count); + return 0; +} + +/* copy callback for halfduplex mode */ +static int snd_rme32_playback_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *src, unsigned long count) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + + if (copy_from_iter_toio(rme32->iobase + RME32_IO_DATA_BUFFER + pos, + count, src) != count) + return -EFAULT; + return 0; +} + +/* copy callback for halfduplex mode */ +static int snd_rme32_capture_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *dst, unsigned long count) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + + if (copy_to_iter_fromio(rme32->iobase + RME32_IO_DATA_BUFFER + pos, + count, dst) != count) + return -EFAULT; + return 0; +} + +/* + * SPDIF I/O capabilities (half-duplex mode) + */ +static const struct snd_pcm_hardware snd_rme32_spdif_info = { + .info = (SNDRV_PCM_INFO_MMAP_IOMEM | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_SYNC_APPLPTR), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE), + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = RME32_BUFFER_SIZE, + .period_bytes_min = RME32_BLOCK_SIZE, + .period_bytes_max = RME32_BLOCK_SIZE, + .periods_min = RME32_BUFFER_SIZE / RME32_BLOCK_SIZE, + .periods_max = RME32_BUFFER_SIZE / RME32_BLOCK_SIZE, + .fifo_size = 0, +}; + +/* + * ADAT I/O capabilities (half-duplex mode) + */ +static const struct snd_pcm_hardware snd_rme32_adat_info = +{ + .info = (SNDRV_PCM_INFO_MMAP_IOMEM | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_SYNC_APPLPTR), + .formats= SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = RME32_BUFFER_SIZE, + .period_bytes_min = RME32_BLOCK_SIZE, + .period_bytes_max = RME32_BLOCK_SIZE, + .periods_min = RME32_BUFFER_SIZE / RME32_BLOCK_SIZE, + .periods_max = RME32_BUFFER_SIZE / RME32_BLOCK_SIZE, + .fifo_size = 0, +}; + +/* + * SPDIF I/O capabilities (full-duplex mode) + */ +static const struct snd_pcm_hardware snd_rme32_spdif_fd_info = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_SYNC_APPLPTR), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE), + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = RME32_MID_BUFFER_SIZE, + .period_bytes_min = RME32_BLOCK_SIZE, + .period_bytes_max = RME32_BLOCK_SIZE, + .periods_min = 2, + .periods_max = RME32_MID_BUFFER_SIZE / RME32_BLOCK_SIZE, + .fifo_size = 0, +}; + +/* + * ADAT I/O capabilities (full-duplex mode) + */ +static const struct snd_pcm_hardware snd_rme32_adat_fd_info = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_SYNC_APPLPTR), + .formats= SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = RME32_MID_BUFFER_SIZE, + .period_bytes_min = RME32_BLOCK_SIZE, + .period_bytes_max = RME32_BLOCK_SIZE, + .periods_min = 2, + .periods_max = RME32_MID_BUFFER_SIZE / RME32_BLOCK_SIZE, + .fifo_size = 0, +}; + +static void snd_rme32_reset_dac(struct rme32 *rme32) +{ + writel(rme32->wcreg | RME32_WCR_PD, + rme32->iobase + RME32_IO_CONTROL_REGISTER); + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); +} + +static int snd_rme32_playback_getrate(struct rme32 * rme32) +{ + int rate; + + rate = ((rme32->wcreg >> RME32_WCR_BITPOS_FREQ_0) & 1) + + (((rme32->wcreg >> RME32_WCR_BITPOS_FREQ_1) & 1) << 1); + switch (rate) { + case 1: + rate = 32000; + break; + case 2: + rate = 44100; + break; + case 3: + rate = 48000; + break; + default: + return -1; + } + return (rme32->wcreg & RME32_WCR_DS_BM) ? rate << 1 : rate; +} + +static int snd_rme32_capture_getrate(struct rme32 * rme32, int *is_adat) +{ + int n; + + *is_adat = 0; + if (rme32->rcreg & RME32_RCR_LOCK) { + /* ADAT rate */ + *is_adat = 1; + } + if (rme32->rcreg & RME32_RCR_ERF) { + return -1; + } + + /* S/PDIF rate */ + n = ((rme32->rcreg >> RME32_RCR_BITPOS_F0) & 1) + + (((rme32->rcreg >> RME32_RCR_BITPOS_F1) & 1) << 1) + + (((rme32->rcreg >> RME32_RCR_BITPOS_F2) & 1) << 2); + + if (RME32_PRO_WITH_8414(rme32)) + switch (n) { /* supporting the CS8414 */ + case 0: + case 1: + case 2: + return -1; + case 3: + return 96000; + case 4: + return 88200; + case 5: + return 48000; + case 6: + return 44100; + case 7: + return 32000; + default: + return -1; + } + else + switch (n) { /* supporting the CS8412 */ + case 0: + return -1; + case 1: + return 48000; + case 2: + return 44100; + case 3: + return 32000; + case 4: + return 48000; + case 5: + return 44100; + case 6: + return 44056; + case 7: + return 32000; + default: + break; + } + return -1; +} + +static int snd_rme32_playback_setrate(struct rme32 * rme32, int rate) +{ + int ds; + + ds = rme32->wcreg & RME32_WCR_DS_BM; + switch (rate) { + case 32000: + rme32->wcreg &= ~RME32_WCR_DS_BM; + rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) & + ~RME32_WCR_FREQ_1; + break; + case 44100: + rme32->wcreg &= ~RME32_WCR_DS_BM; + rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_1) & + ~RME32_WCR_FREQ_0; + break; + case 48000: + rme32->wcreg &= ~RME32_WCR_DS_BM; + rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) | + RME32_WCR_FREQ_1; + break; + case 64000: + if (rme32->pci->device != PCI_DEVICE_ID_RME_DIGI32_PRO) + return -EINVAL; + rme32->wcreg |= RME32_WCR_DS_BM; + rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) & + ~RME32_WCR_FREQ_1; + break; + case 88200: + if (rme32->pci->device != PCI_DEVICE_ID_RME_DIGI32_PRO) + return -EINVAL; + rme32->wcreg |= RME32_WCR_DS_BM; + rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_1) & + ~RME32_WCR_FREQ_0; + break; + case 96000: + if (rme32->pci->device != PCI_DEVICE_ID_RME_DIGI32_PRO) + return -EINVAL; + rme32->wcreg |= RME32_WCR_DS_BM; + rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) | + RME32_WCR_FREQ_1; + break; + default: + return -EINVAL; + } + if ((!ds && rme32->wcreg & RME32_WCR_DS_BM) || + (ds && !(rme32->wcreg & RME32_WCR_DS_BM))) + { + /* change to/from double-speed: reset the DAC (if available) */ + snd_rme32_reset_dac(rme32); + } else { + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + } + return 0; +} + +static int snd_rme32_setclockmode(struct rme32 * rme32, int mode) +{ + switch (mode) { + case RME32_CLOCKMODE_SLAVE: + /* AutoSync */ + rme32->wcreg = (rme32->wcreg & ~RME32_WCR_FREQ_0) & + ~RME32_WCR_FREQ_1; + break; + case RME32_CLOCKMODE_MASTER_32: + /* Internal 32.0kHz */ + rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) & + ~RME32_WCR_FREQ_1; + break; + case RME32_CLOCKMODE_MASTER_44: + /* Internal 44.1kHz */ + rme32->wcreg = (rme32->wcreg & ~RME32_WCR_FREQ_0) | + RME32_WCR_FREQ_1; + break; + case RME32_CLOCKMODE_MASTER_48: + /* Internal 48.0kHz */ + rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) | + RME32_WCR_FREQ_1; + break; + default: + return -EINVAL; + } + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + return 0; +} + +static int snd_rme32_getclockmode(struct rme32 * rme32) +{ + return ((rme32->wcreg >> RME32_WCR_BITPOS_FREQ_0) & 1) + + (((rme32->wcreg >> RME32_WCR_BITPOS_FREQ_1) & 1) << 1); +} + +static int snd_rme32_setinputtype(struct rme32 * rme32, int type) +{ + switch (type) { + case RME32_INPUT_OPTICAL: + rme32->wcreg = (rme32->wcreg & ~RME32_WCR_INP_0) & + ~RME32_WCR_INP_1; + break; + case RME32_INPUT_COAXIAL: + rme32->wcreg = (rme32->wcreg | RME32_WCR_INP_0) & + ~RME32_WCR_INP_1; + break; + case RME32_INPUT_INTERNAL: + rme32->wcreg = (rme32->wcreg & ~RME32_WCR_INP_0) | + RME32_WCR_INP_1; + break; + case RME32_INPUT_XLR: + rme32->wcreg = (rme32->wcreg | RME32_WCR_INP_0) | + RME32_WCR_INP_1; + break; + default: + return -EINVAL; + } + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + return 0; +} + +static int snd_rme32_getinputtype(struct rme32 * rme32) +{ + return ((rme32->wcreg >> RME32_WCR_BITPOS_INP_0) & 1) + + (((rme32->wcreg >> RME32_WCR_BITPOS_INP_1) & 1) << 1); +} + +static void +snd_rme32_setframelog(struct rme32 * rme32, int n_channels, int is_playback) +{ + int frlog; + + if (n_channels == 2) { + frlog = 1; + } else { + /* assume 8 channels */ + frlog = 3; + } + if (is_playback) { + frlog += (rme32->wcreg & RME32_WCR_MODE24) ? 2 : 1; + rme32->playback_frlog = frlog; + } else { + frlog += (rme32->wcreg & RME32_WCR_MODE24) ? 2 : 1; + rme32->capture_frlog = frlog; + } +} + +static int snd_rme32_setformat(struct rme32 *rme32, snd_pcm_format_t format) +{ + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + rme32->wcreg &= ~RME32_WCR_MODE24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + rme32->wcreg |= RME32_WCR_MODE24; + break; + default: + return -EINVAL; + } + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + return 0; +} + +static int +snd_rme32_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int err, rate, dummy; + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (!rme32->fullduplex_mode) { + runtime->dma_area = (void __force *)(rme32->iobase + + RME32_IO_DATA_BUFFER); + runtime->dma_addr = rme32->port + RME32_IO_DATA_BUFFER; + runtime->dma_bytes = RME32_BUFFER_SIZE; + } + + guard(spinlock_irq)(&rme32->lock); + rate = 0; + if (rme32->rcreg & RME32_RCR_KMODE) + rate = snd_rme32_capture_getrate(rme32, &dummy); + if (rate > 0) { + /* AutoSync */ + if ((int)params_rate(params) != rate) + return -EIO; + } else { + err = snd_rme32_playback_setrate(rme32, params_rate(params)); + if (err < 0) + return err; + } + err = snd_rme32_setformat(rme32, params_format(params)); + if (err < 0) + return err; + + snd_rme32_setframelog(rme32, params_channels(params), 1); + if (rme32->capture_periodsize != 0) { + if (params_period_size(params) << rme32->playback_frlog != rme32->capture_periodsize) + return -EBUSY; + } + rme32->playback_periodsize = params_period_size(params) << rme32->playback_frlog; + /* S/PDIF setup */ + if ((rme32->wcreg & RME32_WCR_ADAT) == 0) { + rme32->wcreg &= ~(RME32_WCR_PRO | RME32_WCR_EMP); + rme32->wcreg |= rme32->wcreg_spdif_stream; + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + } + + return 0; +} + +static int +snd_rme32_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int err, isadat, rate; + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (!rme32->fullduplex_mode) { + runtime->dma_area = (void __force *)rme32->iobase + + RME32_IO_DATA_BUFFER; + runtime->dma_addr = rme32->port + RME32_IO_DATA_BUFFER; + runtime->dma_bytes = RME32_BUFFER_SIZE; + } + + guard(spinlock_irq)(&rme32->lock); + /* enable AutoSync for record-preparing */ + rme32->wcreg |= RME32_WCR_AUTOSYNC; + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + + err = snd_rme32_setformat(rme32, params_format(params)); + if (err < 0) + return err; + err = snd_rme32_playback_setrate(rme32, params_rate(params)); + if (err < 0) + return err; + rate = snd_rme32_capture_getrate(rme32, &isadat); + if (rate > 0) { + if ((int)params_rate(params) != rate) + return -EIO; + if ((isadat && runtime->hw.channels_min == 2) || + (!isadat && runtime->hw.channels_min == 8)) + return -EIO; + } + /* AutoSync off for recording */ + rme32->wcreg &= ~RME32_WCR_AUTOSYNC; + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + + snd_rme32_setframelog(rme32, params_channels(params), 0); + if (rme32->playback_periodsize != 0) { + if (params_period_size(params) << rme32->capture_frlog != + rme32->playback_periodsize) + return -EBUSY; + } + rme32->capture_periodsize = + params_period_size(params) << rme32->capture_frlog; + + return 0; +} + +static void snd_rme32_pcm_start(struct rme32 * rme32, int from_pause) +{ + if (!from_pause) { + writel(0, rme32->iobase + RME32_IO_RESET_POS); + } + + rme32->wcreg |= RME32_WCR_START; + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); +} + +static void snd_rme32_pcm_stop(struct rme32 * rme32, int to_pause) +{ + /* + * Check if there is an unconfirmed IRQ, if so confirm it, or else + * the hardware will not stop generating interrupts + */ + rme32->rcreg = readl(rme32->iobase + RME32_IO_CONTROL_REGISTER); + if (rme32->rcreg & RME32_RCR_IRQ) { + writel(0, rme32->iobase + RME32_IO_CONFIRM_ACTION_IRQ); + } + rme32->wcreg &= ~RME32_WCR_START; + if (rme32->wcreg & RME32_WCR_SEL) + rme32->wcreg |= RME32_WCR_MUTE; + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + if (! to_pause) + writel(0, rme32->iobase + RME32_IO_RESET_POS); +} + +static irqreturn_t snd_rme32_interrupt(int irq, void *dev_id) +{ + struct rme32 *rme32 = (struct rme32 *) dev_id; + + rme32->rcreg = readl(rme32->iobase + RME32_IO_CONTROL_REGISTER); + if (!(rme32->rcreg & RME32_RCR_IRQ)) { + return IRQ_NONE; + } else { + if (rme32->capture_substream) { + snd_pcm_period_elapsed(rme32->capture_substream); + } + if (rme32->playback_substream) { + snd_pcm_period_elapsed(rme32->playback_substream); + } + writel(0, rme32->iobase + RME32_IO_CONFIRM_ACTION_IRQ); + } + return IRQ_HANDLED; +} + +static const unsigned int period_bytes[] = { RME32_BLOCK_SIZE }; + +static const struct snd_pcm_hw_constraint_list hw_constraints_period_bytes = { + .count = ARRAY_SIZE(period_bytes), + .list = period_bytes, + .mask = 0 +}; + +static void snd_rme32_set_buffer_constraint(struct rme32 *rme32, struct snd_pcm_runtime *runtime) +{ + if (! rme32->fullduplex_mode) { + snd_pcm_hw_constraint_single(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + RME32_BUFFER_SIZE); + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &hw_constraints_period_bytes); + } +} + +static int snd_rme32_playback_spdif_open(struct snd_pcm_substream *substream) +{ + int rate, dummy; + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_sync(substream); + + scoped_guard(spinlock_irq, &rme32->lock) { + if (rme32->playback_substream != NULL) + return -EBUSY; + rme32->wcreg &= ~RME32_WCR_ADAT; + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + rme32->playback_substream = substream; + } + + if (rme32->fullduplex_mode) + runtime->hw = snd_rme32_spdif_fd_info; + else + runtime->hw = snd_rme32_spdif_info; + if (rme32->pci->device == PCI_DEVICE_ID_RME_DIGI32_PRO) { + runtime->hw.rates |= SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000; + runtime->hw.rate_max = 96000; + } + rate = 0; + if (rme32->rcreg & RME32_RCR_KMODE) + rate = snd_rme32_capture_getrate(rme32, &dummy); + if (rate > 0) { + /* AutoSync */ + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } + + snd_rme32_set_buffer_constraint(rme32, runtime); + + rme32->wcreg_spdif_stream = rme32->wcreg_spdif; + rme32->spdif_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(rme32->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &rme32->spdif_ctl->id); + return 0; +} + +static int snd_rme32_capture_spdif_open(struct snd_pcm_substream *substream) +{ + int isadat, rate; + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_sync(substream); + + scoped_guard(spinlock_irq, &rme32->lock) { + if (rme32->capture_substream != NULL) + return -EBUSY; + rme32->capture_substream = substream; + } + + if (rme32->fullduplex_mode) + runtime->hw = snd_rme32_spdif_fd_info; + else + runtime->hw = snd_rme32_spdif_info; + if (RME32_PRO_WITH_8414(rme32)) { + runtime->hw.rates |= SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000; + runtime->hw.rate_max = 96000; + } + rate = snd_rme32_capture_getrate(rme32, &isadat); + if (rate > 0) { + if (isadat) { + return -EIO; + } + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } + + snd_rme32_set_buffer_constraint(rme32, runtime); + + return 0; +} + +static int +snd_rme32_playback_adat_open(struct snd_pcm_substream *substream) +{ + int rate, dummy; + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_sync(substream); + + scoped_guard(spinlock_irq, &rme32->lock) { + if (rme32->playback_substream != NULL) + return -EBUSY; + rme32->wcreg |= RME32_WCR_ADAT; + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + rme32->playback_substream = substream; + } + + if (rme32->fullduplex_mode) + runtime->hw = snd_rme32_adat_fd_info; + else + runtime->hw = snd_rme32_adat_info; + rate = 0; + if (rme32->rcreg & RME32_RCR_KMODE) + rate = snd_rme32_capture_getrate(rme32, &dummy); + if (rate > 0) { + /* AutoSync */ + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } + + snd_rme32_set_buffer_constraint(rme32, runtime); + return 0; +} + +static int +snd_rme32_capture_adat_open(struct snd_pcm_substream *substream) +{ + int isadat, rate; + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (rme32->fullduplex_mode) + runtime->hw = snd_rme32_adat_fd_info; + else + runtime->hw = snd_rme32_adat_info; + rate = snd_rme32_capture_getrate(rme32, &isadat); + if (rate > 0) { + if (!isadat) { + return -EIO; + } + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + scoped_guard(spinlock_irq, &rme32->lock) { + if (rme32->capture_substream != NULL) + return -EBUSY; + rme32->capture_substream = substream; + } + + snd_rme32_set_buffer_constraint(rme32, runtime); + return 0; +} + +static int snd_rme32_playback_close(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + int spdif = 0; + + scoped_guard(spinlock_irq, &rme32->lock) { + rme32->playback_substream = NULL; + rme32->playback_periodsize = 0; + spdif = (rme32->wcreg & RME32_WCR_ADAT) == 0; + } + if (spdif) { + rme32->spdif_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(rme32->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, + &rme32->spdif_ctl->id); + } + return 0; +} + +static int snd_rme32_capture_close(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&rme32->lock); + rme32->capture_substream = NULL; + rme32->capture_periodsize = 0; + return 0; +} + +static int snd_rme32_playback_prepare(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&rme32->lock); + if (rme32->fullduplex_mode) { + memset(&rme32->playback_pcm, 0, sizeof(rme32->playback_pcm)); + rme32->playback_pcm.hw_buffer_size = RME32_BUFFER_SIZE; + rme32->playback_pcm.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream); + } else { + writel(0, rme32->iobase + RME32_IO_RESET_POS); + } + if (rme32->wcreg & RME32_WCR_SEL) + rme32->wcreg &= ~RME32_WCR_MUTE; + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + return 0; +} + +static int snd_rme32_capture_prepare(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&rme32->lock); + if (rme32->fullduplex_mode) { + memset(&rme32->capture_pcm, 0, sizeof(rme32->capture_pcm)); + rme32->capture_pcm.hw_buffer_size = RME32_BUFFER_SIZE; + rme32->capture_pcm.hw_queue_size = RME32_BUFFER_SIZE / 2; + rme32->capture_pcm.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream); + } else { + writel(0, rme32->iobase + RME32_IO_RESET_POS); + } + return 0; +} + +static int +snd_rme32_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + + guard(spinlock)(&rme32->lock); + snd_pcm_group_for_each_entry(s, substream) { + if (s != rme32->playback_substream && + s != rme32->capture_substream) + continue; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + rme32->running |= (1 << s->stream); + if (rme32->fullduplex_mode) { + /* remember the current DMA position */ + if (s == rme32->playback_substream) { + rme32->playback_pcm.hw_io = + rme32->playback_pcm.hw_data = snd_rme32_pcm_byteptr(rme32); + } else { + rme32->capture_pcm.hw_io = + rme32->capture_pcm.hw_data = snd_rme32_pcm_byteptr(rme32); + } + } + break; + case SNDRV_PCM_TRIGGER_STOP: + rme32->running &= ~(1 << s->stream); + break; + } + snd_pcm_trigger_done(s, substream); + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (rme32->running && ! RME32_ISWORKING(rme32)) + snd_rme32_pcm_start(rme32, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (! rme32->running && RME32_ISWORKING(rme32)) + snd_rme32_pcm_stop(rme32, 0); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (rme32->running && RME32_ISWORKING(rme32)) + snd_rme32_pcm_stop(rme32, 1); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (rme32->running && ! RME32_ISWORKING(rme32)) + snd_rme32_pcm_start(rme32, 1); + break; + } + return 0; +} + +/* pointer callback for halfduplex mode */ +static snd_pcm_uframes_t +snd_rme32_playback_pointer(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + return snd_rme32_pcm_byteptr(rme32) >> rme32->playback_frlog; +} + +static snd_pcm_uframes_t +snd_rme32_capture_pointer(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + return snd_rme32_pcm_byteptr(rme32) >> rme32->capture_frlog; +} + + +/* ack and pointer callbacks for fullduplex mode */ +static void snd_rme32_pb_trans_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect *rec, size_t bytes) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + memcpy_toio(rme32->iobase + RME32_IO_DATA_BUFFER + rec->hw_data, + substream->runtime->dma_area + rec->sw_data, bytes); +} + +static int snd_rme32_playback_fd_ack(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + struct snd_pcm_indirect *rec, *cprec; + + rec = &rme32->playback_pcm; + cprec = &rme32->capture_pcm; + scoped_guard(spinlock, &rme32->lock) { + rec->hw_queue_size = RME32_BUFFER_SIZE; + if (rme32->running & (1 << SNDRV_PCM_STREAM_CAPTURE)) + rec->hw_queue_size -= cprec->hw_ready; + } + return snd_pcm_indirect_playback_transfer(substream, rec, + snd_rme32_pb_trans_copy); +} + +static void snd_rme32_cp_trans_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect *rec, size_t bytes) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + memcpy_fromio(substream->runtime->dma_area + rec->sw_data, + rme32->iobase + RME32_IO_DATA_BUFFER + rec->hw_data, + bytes); +} + +static int snd_rme32_capture_fd_ack(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + return snd_pcm_indirect_capture_transfer(substream, &rme32->capture_pcm, + snd_rme32_cp_trans_copy); +} + +static snd_pcm_uframes_t +snd_rme32_playback_fd_pointer(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + return snd_pcm_indirect_playback_pointer(substream, &rme32->playback_pcm, + snd_rme32_pcm_byteptr(rme32)); +} + +static snd_pcm_uframes_t +snd_rme32_capture_fd_pointer(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + return snd_pcm_indirect_capture_pointer(substream, &rme32->capture_pcm, + snd_rme32_pcm_byteptr(rme32)); +} + +/* for halfduplex mode */ +static const struct snd_pcm_ops snd_rme32_playback_spdif_ops = { + .open = snd_rme32_playback_spdif_open, + .close = snd_rme32_playback_close, + .hw_params = snd_rme32_playback_hw_params, + .prepare = snd_rme32_playback_prepare, + .trigger = snd_rme32_pcm_trigger, + .pointer = snd_rme32_playback_pointer, + .copy = snd_rme32_playback_copy, + .fill_silence = snd_rme32_playback_silence, + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static const struct snd_pcm_ops snd_rme32_capture_spdif_ops = { + .open = snd_rme32_capture_spdif_open, + .close = snd_rme32_capture_close, + .hw_params = snd_rme32_capture_hw_params, + .prepare = snd_rme32_capture_prepare, + .trigger = snd_rme32_pcm_trigger, + .pointer = snd_rme32_capture_pointer, + .copy = snd_rme32_capture_copy, + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static const struct snd_pcm_ops snd_rme32_playback_adat_ops = { + .open = snd_rme32_playback_adat_open, + .close = snd_rme32_playback_close, + .hw_params = snd_rme32_playback_hw_params, + .prepare = snd_rme32_playback_prepare, + .trigger = snd_rme32_pcm_trigger, + .pointer = snd_rme32_playback_pointer, + .copy = snd_rme32_playback_copy, + .fill_silence = snd_rme32_playback_silence, + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static const struct snd_pcm_ops snd_rme32_capture_adat_ops = { + .open = snd_rme32_capture_adat_open, + .close = snd_rme32_capture_close, + .hw_params = snd_rme32_capture_hw_params, + .prepare = snd_rme32_capture_prepare, + .trigger = snd_rme32_pcm_trigger, + .pointer = snd_rme32_capture_pointer, + .copy = snd_rme32_capture_copy, + .mmap = snd_pcm_lib_mmap_iomem, +}; + +/* for fullduplex mode */ +static const struct snd_pcm_ops snd_rme32_playback_spdif_fd_ops = { + .open = snd_rme32_playback_spdif_open, + .close = snd_rme32_playback_close, + .hw_params = snd_rme32_playback_hw_params, + .prepare = snd_rme32_playback_prepare, + .trigger = snd_rme32_pcm_trigger, + .pointer = snd_rme32_playback_fd_pointer, + .ack = snd_rme32_playback_fd_ack, +}; + +static const struct snd_pcm_ops snd_rme32_capture_spdif_fd_ops = { + .open = snd_rme32_capture_spdif_open, + .close = snd_rme32_capture_close, + .hw_params = snd_rme32_capture_hw_params, + .prepare = snd_rme32_capture_prepare, + .trigger = snd_rme32_pcm_trigger, + .pointer = snd_rme32_capture_fd_pointer, + .ack = snd_rme32_capture_fd_ack, +}; + +static const struct snd_pcm_ops snd_rme32_playback_adat_fd_ops = { + .open = snd_rme32_playback_adat_open, + .close = snd_rme32_playback_close, + .hw_params = snd_rme32_playback_hw_params, + .prepare = snd_rme32_playback_prepare, + .trigger = snd_rme32_pcm_trigger, + .pointer = snd_rme32_playback_fd_pointer, + .ack = snd_rme32_playback_fd_ack, +}; + +static const struct snd_pcm_ops snd_rme32_capture_adat_fd_ops = { + .open = snd_rme32_capture_adat_open, + .close = snd_rme32_capture_close, + .hw_params = snd_rme32_capture_hw_params, + .prepare = snd_rme32_capture_prepare, + .trigger = snd_rme32_pcm_trigger, + .pointer = snd_rme32_capture_fd_pointer, + .ack = snd_rme32_capture_fd_ack, +}; + +static void snd_rme32_free(struct rme32 *rme32) +{ + if (rme32->irq >= 0) + snd_rme32_pcm_stop(rme32, 0); +} + +static void snd_rme32_free_spdif_pcm(struct snd_pcm *pcm) +{ + struct rme32 *rme32 = (struct rme32 *) pcm->private_data; + rme32->spdif_pcm = NULL; +} + +static void +snd_rme32_free_adat_pcm(struct snd_pcm *pcm) +{ + struct rme32 *rme32 = (struct rme32 *) pcm->private_data; + rme32->adat_pcm = NULL; +} + +static int snd_rme32_create(struct rme32 *rme32) +{ + struct pci_dev *pci = rme32->pci; + int err; + + rme32->irq = -1; + spin_lock_init(&rme32->lock); + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + err = pcim_request_all_regions(pci, "RME32"); + if (err < 0) + return err; + rme32->port = pci_resource_start(rme32->pci, 0); + + rme32->iobase = devm_ioremap(&pci->dev, rme32->port, RME32_IO_SIZE); + if (!rme32->iobase) { + dev_err(rme32->card->dev, + "unable to remap memory region 0x%lx-0x%lx\n", + rme32->port, rme32->port + RME32_IO_SIZE - 1); + return -ENOMEM; + } + + if (devm_request_irq(&pci->dev, pci->irq, snd_rme32_interrupt, + IRQF_SHARED, KBUILD_MODNAME, rme32)) { + dev_err(rme32->card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + rme32->irq = pci->irq; + rme32->card->sync_irq = rme32->irq; + + /* read the card's revision number */ + pci_read_config_byte(pci, 8, &rme32->rev); + + /* set up ALSA pcm device for S/PDIF */ + err = snd_pcm_new(rme32->card, "Digi32 IEC958", 0, 1, 1, &rme32->spdif_pcm); + if (err < 0) + return err; + rme32->spdif_pcm->private_data = rme32; + rme32->spdif_pcm->private_free = snd_rme32_free_spdif_pcm; + strscpy(rme32->spdif_pcm->name, "Digi32 IEC958"); + if (rme32->fullduplex_mode) { + snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_rme32_playback_spdif_fd_ops); + snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_rme32_capture_spdif_fd_ops); + snd_pcm_set_managed_buffer_all(rme32->spdif_pcm, SNDRV_DMA_TYPE_CONTINUOUS, + NULL, 0, RME32_MID_BUFFER_SIZE); + rme32->spdif_pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + } else { + snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_rme32_playback_spdif_ops); + snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_rme32_capture_spdif_ops); + rme32->spdif_pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + } + + /* set up ALSA pcm device for ADAT */ + if ((pci->device == PCI_DEVICE_ID_RME_DIGI32) || + (pci->device == PCI_DEVICE_ID_RME_DIGI32_PRO)) { + /* ADAT is not available on DIGI32 and DIGI32 Pro */ + rme32->adat_pcm = NULL; + } + else { + err = snd_pcm_new(rme32->card, "Digi32 ADAT", 1, + 1, 1, &rme32->adat_pcm); + if (err < 0) + return err; + rme32->adat_pcm->private_data = rme32; + rme32->adat_pcm->private_free = snd_rme32_free_adat_pcm; + strscpy(rme32->adat_pcm->name, "Digi32 ADAT"); + if (rme32->fullduplex_mode) { + snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_rme32_playback_adat_fd_ops); + snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_rme32_capture_adat_fd_ops); + snd_pcm_set_managed_buffer_all(rme32->adat_pcm, SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 0, RME32_MID_BUFFER_SIZE); + rme32->adat_pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + } else { + snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_rme32_playback_adat_ops); + snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_rme32_capture_adat_ops); + rme32->adat_pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + } + } + + + rme32->playback_periodsize = 0; + rme32->capture_periodsize = 0; + + /* make sure playback/capture is stopped, if by some reason active */ + snd_rme32_pcm_stop(rme32, 0); + + /* reset DAC */ + snd_rme32_reset_dac(rme32); + + /* reset buffer pointer */ + writel(0, rme32->iobase + RME32_IO_RESET_POS); + + /* set default values in registers */ + rme32->wcreg = RME32_WCR_SEL | /* normal playback */ + RME32_WCR_INP_0 | /* input select */ + RME32_WCR_MUTE; /* muting on */ + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + + + /* init switch interface */ + err = snd_rme32_create_switches(rme32->card, rme32); + if (err < 0) + return err; + + /* init proc interface */ + snd_rme32_proc_init(rme32); + + rme32->capture_substream = NULL; + rme32->playback_substream = NULL; + + return 0; +} + +/* + * proc interface + */ + +static void +snd_rme32_proc_read(struct snd_info_entry * entry, struct snd_info_buffer *buffer) +{ + int n; + struct rme32 *rme32 = (struct rme32 *) entry->private_data; + + rme32->rcreg = readl(rme32->iobase + RME32_IO_CONTROL_REGISTER); + + snd_iprintf(buffer, rme32->card->longname); + snd_iprintf(buffer, " (index #%d)\n", rme32->card->number + 1); + + snd_iprintf(buffer, "\nGeneral settings\n"); + if (rme32->fullduplex_mode) + snd_iprintf(buffer, " Full-duplex mode\n"); + else + snd_iprintf(buffer, " Half-duplex mode\n"); + if (RME32_PRO_WITH_8414(rme32)) { + snd_iprintf(buffer, " receiver: CS8414\n"); + } else { + snd_iprintf(buffer, " receiver: CS8412\n"); + } + if (rme32->wcreg & RME32_WCR_MODE24) { + snd_iprintf(buffer, " format: 24 bit"); + } else { + snd_iprintf(buffer, " format: 16 bit"); + } + if (rme32->wcreg & RME32_WCR_MONO) { + snd_iprintf(buffer, ", Mono\n"); + } else { + snd_iprintf(buffer, ", Stereo\n"); + } + + snd_iprintf(buffer, "\nInput settings\n"); + switch (snd_rme32_getinputtype(rme32)) { + case RME32_INPUT_OPTICAL: + snd_iprintf(buffer, " input: optical"); + break; + case RME32_INPUT_COAXIAL: + snd_iprintf(buffer, " input: coaxial"); + break; + case RME32_INPUT_INTERNAL: + snd_iprintf(buffer, " input: internal"); + break; + case RME32_INPUT_XLR: + snd_iprintf(buffer, " input: XLR"); + break; + } + if (snd_rme32_capture_getrate(rme32, &n) < 0) { + snd_iprintf(buffer, "\n sample rate: no valid signal\n"); + } else { + if (n) { + snd_iprintf(buffer, " (8 channels)\n"); + } else { + snd_iprintf(buffer, " (2 channels)\n"); + } + snd_iprintf(buffer, " sample rate: %d Hz\n", + snd_rme32_capture_getrate(rme32, &n)); + } + + snd_iprintf(buffer, "\nOutput settings\n"); + if (rme32->wcreg & RME32_WCR_SEL) { + snd_iprintf(buffer, " output signal: normal playback"); + } else { + snd_iprintf(buffer, " output signal: same as input"); + } + if (rme32->wcreg & RME32_WCR_MUTE) { + snd_iprintf(buffer, " (muted)\n"); + } else { + snd_iprintf(buffer, "\n"); + } + + /* master output frequency */ + if (! + ((!(rme32->wcreg & RME32_WCR_FREQ_0)) + && (!(rme32->wcreg & RME32_WCR_FREQ_1)))) { + snd_iprintf(buffer, " sample rate: %d Hz\n", + snd_rme32_playback_getrate(rme32)); + } + if (rme32->rcreg & RME32_RCR_KMODE) { + snd_iprintf(buffer, " sample clock source: AutoSync\n"); + } else { + snd_iprintf(buffer, " sample clock source: Internal\n"); + } + if (rme32->wcreg & RME32_WCR_PRO) { + snd_iprintf(buffer, " format: AES/EBU (professional)\n"); + } else { + snd_iprintf(buffer, " format: IEC958 (consumer)\n"); + } + if (rme32->wcreg & RME32_WCR_EMP) { + snd_iprintf(buffer, " emphasis: on\n"); + } else { + snd_iprintf(buffer, " emphasis: off\n"); + } +} + +static void snd_rme32_proc_init(struct rme32 *rme32) +{ + snd_card_ro_proc_new(rme32->card, "rme32", rme32, snd_rme32_proc_read); +} + +/* + * control interface + */ + +#define snd_rme32_info_loopback_control snd_ctl_boolean_mono_info + +static int +snd_rme32_get_loopback_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme32->lock); + ucontrol->value.integer.value[0] = + rme32->wcreg & RME32_WCR_SEL ? 0 : 1; + return 0; +} +static int +snd_rme32_put_loopback_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ucontrol->value.integer.value[0] ? 0 : RME32_WCR_SEL; + guard(spinlock_irq)(&rme32->lock); + val = (rme32->wcreg & ~RME32_WCR_SEL) | val; + change = val != rme32->wcreg; + if (ucontrol->value.integer.value[0]) + val &= ~RME32_WCR_MUTE; + else + val |= RME32_WCR_MUTE; + rme32->wcreg = val; + writel(val, rme32->iobase + RME32_IO_CONTROL_REGISTER); + return change; +} + +static int +snd_rme32_info_inputtype_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + static const char * const texts[4] = { + "Optical", "Coaxial", "Internal", "XLR" + }; + int num_items; + + switch (rme32->pci->device) { + case PCI_DEVICE_ID_RME_DIGI32: + case PCI_DEVICE_ID_RME_DIGI32_8: + num_items = 3; + break; + case PCI_DEVICE_ID_RME_DIGI32_PRO: + num_items = 4; + break; + default: + snd_BUG(); + return -EINVAL; + } + return snd_ctl_enum_info(uinfo, 1, num_items, texts); +} +static int +snd_rme32_get_inputtype_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + unsigned int items = 3; + + guard(spinlock_irq)(&rme32->lock); + ucontrol->value.enumerated.item[0] = snd_rme32_getinputtype(rme32); + + switch (rme32->pci->device) { + case PCI_DEVICE_ID_RME_DIGI32: + case PCI_DEVICE_ID_RME_DIGI32_8: + items = 3; + break; + case PCI_DEVICE_ID_RME_DIGI32_PRO: + items = 4; + break; + default: + snd_BUG(); + break; + } + if (ucontrol->value.enumerated.item[0] >= items) { + ucontrol->value.enumerated.item[0] = items - 1; + } + + return 0; +} +static int +snd_rme32_put_inputtype_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change, items = 3; + + switch (rme32->pci->device) { + case PCI_DEVICE_ID_RME_DIGI32: + case PCI_DEVICE_ID_RME_DIGI32_8: + items = 3; + break; + case PCI_DEVICE_ID_RME_DIGI32_PRO: + items = 4; + break; + default: + snd_BUG(); + break; + } + val = ucontrol->value.enumerated.item[0] % items; + + guard(spinlock_irq)(&rme32->lock); + change = val != (unsigned int)snd_rme32_getinputtype(rme32); + snd_rme32_setinputtype(rme32, val); + return change; +} + +static int +snd_rme32_info_clockmode_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { "AutoSync", + "Internal 32.0kHz", + "Internal 44.1kHz", + "Internal 48.0kHz" }; + + return snd_ctl_enum_info(uinfo, 1, 4, texts); +} +static int +snd_rme32_get_clockmode_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme32->lock); + ucontrol->value.enumerated.item[0] = snd_rme32_getclockmode(rme32); + return 0; +} +static int +snd_rme32_put_clockmode_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ucontrol->value.enumerated.item[0] % 3; + guard(spinlock_irq)(&rme32->lock); + change = val != (unsigned int)snd_rme32_getclockmode(rme32); + snd_rme32_setclockmode(rme32, val); + return change; +} + +static u32 snd_rme32_convert_from_aes(struct snd_aes_iec958 * aes) +{ + u32 val = 0; + val |= (aes->status[0] & IEC958_AES0_PROFESSIONAL) ? RME32_WCR_PRO : 0; + if (val & RME32_WCR_PRO) + val |= (aes->status[0] & IEC958_AES0_PRO_EMPHASIS_5015) ? RME32_WCR_EMP : 0; + else + val |= (aes->status[0] & IEC958_AES0_CON_EMPHASIS_5015) ? RME32_WCR_EMP : 0; + return val; +} + +static void snd_rme32_convert_to_aes(struct snd_aes_iec958 * aes, u32 val) +{ + aes->status[0] = ((val & RME32_WCR_PRO) ? IEC958_AES0_PROFESSIONAL : 0); + if (val & RME32_WCR_PRO) + aes->status[0] |= (val & RME32_WCR_EMP) ? IEC958_AES0_PRO_EMPHASIS_5015 : 0; + else + aes->status[0] |= (val & RME32_WCR_EMP) ? IEC958_AES0_CON_EMPHASIS_5015 : 0; +} + +static int snd_rme32_control_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_rme32_control_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + + snd_rme32_convert_to_aes(&ucontrol->value.iec958, + rme32->wcreg_spdif); + return 0; +} + +static int snd_rme32_control_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + int change; + u32 val; + + val = snd_rme32_convert_from_aes(&ucontrol->value.iec958); + guard(spinlock_irq)(&rme32->lock); + change = val != rme32->wcreg_spdif; + rme32->wcreg_spdif = val; + return change; +} + +static int snd_rme32_control_spdif_stream_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_rme32_control_spdif_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value * + ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + + snd_rme32_convert_to_aes(&ucontrol->value.iec958, + rme32->wcreg_spdif_stream); + return 0; +} + +static int snd_rme32_control_spdif_stream_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value * + ucontrol) +{ + struct rme32 *rme32 = snd_kcontrol_chip(kcontrol); + int change; + u32 val; + + val = snd_rme32_convert_from_aes(&ucontrol->value.iec958); + guard(spinlock_irq)(&rme32->lock); + change = val != rme32->wcreg_spdif_stream; + rme32->wcreg_spdif_stream = val; + rme32->wcreg &= ~(RME32_WCR_PRO | RME32_WCR_EMP); + rme32->wcreg |= val; + writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER); + return change; +} + +static int snd_rme32_control_spdif_mask_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_rme32_control_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value * + ucontrol) +{ + ucontrol->value.iec958.status[0] = kcontrol->private_value; + return 0; +} + +static const struct snd_kcontrol_new snd_rme32_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = snd_rme32_control_spdif_info, + .get = snd_rme32_control_spdif_get, + .put = snd_rme32_control_spdif_put + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), + .info = snd_rme32_control_spdif_stream_info, + .get = snd_rme32_control_spdif_stream_get, + .put = snd_rme32_control_spdif_stream_put + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .info = snd_rme32_control_spdif_mask_info, + .get = snd_rme32_control_spdif_mask_get, + .private_value = IEC958_AES0_PROFESSIONAL | IEC958_AES0_CON_EMPHASIS + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK), + .info = snd_rme32_control_spdif_mask_info, + .get = snd_rme32_control_spdif_mask_get, + .private_value = IEC958_AES0_PROFESSIONAL | IEC958_AES0_PRO_EMPHASIS + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Connector", + .info = snd_rme32_info_inputtype_control, + .get = snd_rme32_get_inputtype_control, + .put = snd_rme32_put_inputtype_control + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Loopback Input", + .info = snd_rme32_info_loopback_control, + .get = snd_rme32_get_loopback_control, + .put = snd_rme32_put_loopback_control + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sample Clock Source", + .info = snd_rme32_info_clockmode_control, + .get = snd_rme32_get_clockmode_control, + .put = snd_rme32_put_clockmode_control + } +}; + +static int snd_rme32_create_switches(struct snd_card *card, struct rme32 * rme32) +{ + int idx, err; + struct snd_kcontrol *kctl; + + for (idx = 0; idx < (int)ARRAY_SIZE(snd_rme32_controls); idx++) { + kctl = snd_ctl_new1(&snd_rme32_controls[idx], rme32); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + if (idx == 1) /* IEC958 (S/PDIF) Stream */ + rme32->spdif_ctl = kctl; + } + + return 0; +} + +/* + * Card initialisation + */ + +static void snd_rme32_card_free(struct snd_card *card) +{ + snd_rme32_free(card->private_data); +} + +static int +__snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static int dev; + struct rme32 *rme32; + struct snd_card *card; + int err; + + if (dev >= SNDRV_CARDS) { + return -ENODEV; + } + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*rme32), &card); + if (err < 0) + return err; + card->private_free = snd_rme32_card_free; + rme32 = (struct rme32 *) card->private_data; + rme32->card = card; + rme32->pci = pci; + if (fullduplex[dev]) + rme32->fullduplex_mode = 1; + err = snd_rme32_create(rme32); + if (err < 0) + return err; + + strscpy(card->driver, "Digi32"); + switch (rme32->pci->device) { + case PCI_DEVICE_ID_RME_DIGI32: + strscpy(card->shortname, "RME Digi32"); + break; + case PCI_DEVICE_ID_RME_DIGI32_8: + strscpy(card->shortname, "RME Digi32/8"); + break; + case PCI_DEVICE_ID_RME_DIGI32_PRO: + strscpy(card->shortname, "RME Digi32 PRO"); + break; + } + sprintf(card->longname, "%s (Rev. %d) at 0x%lx, irq %d", + card->shortname, rme32->rev, rme32->port, rme32->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int +snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_rme32_probe(pci, pci_id)); +} + +static struct pci_driver rme32_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_rme32_ids, + .probe = snd_rme32_probe, +}; + +module_pci_driver(rme32_driver); diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c new file mode 100644 index 000000000..892fcc598 --- /dev/null +++ b/sound/pci/rme96.c @@ -0,0 +1,2442 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for RME Digi96, Digi96/8 and Digi96/8 PRO/PAD/PST audio + * interfaces + * + * Copyright (c) 2000, 2001 Anders Torger + * + * Thanks to Henk Hesselink for the analog volume control + * code. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* note, two last pcis should be equal, it is not a bug */ + +MODULE_AUTHOR("Anders Torger "); +MODULE_DESCRIPTION("RME Digi96, Digi96/8, Digi96/8 PRO, Digi96/8 PST, " + "Digi96/8 PAD"); +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_PNP; /* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for RME Digi96 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for RME Digi96 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable RME Digi96 soundcard."); + +/* + * Defines for RME Digi96 series, from internal RME reference documents + * dated 12.01.00 + */ + +#define RME96_SPDIF_NCHANNELS 2 + +/* Playback and capture buffer size */ +#define RME96_BUFFER_SIZE 0x10000 + +/* IO area size */ +#define RME96_IO_SIZE 0x60000 + +/* IO area offsets */ +#define RME96_IO_PLAY_BUFFER 0x0 +#define RME96_IO_REC_BUFFER 0x10000 +#define RME96_IO_CONTROL_REGISTER 0x20000 +#define RME96_IO_ADDITIONAL_REG 0x20004 +#define RME96_IO_CONFIRM_PLAY_IRQ 0x20008 +#define RME96_IO_CONFIRM_REC_IRQ 0x2000C +#define RME96_IO_SET_PLAY_POS 0x40000 +#define RME96_IO_RESET_PLAY_POS 0x4FFFC +#define RME96_IO_SET_REC_POS 0x50000 +#define RME96_IO_RESET_REC_POS 0x5FFFC +#define RME96_IO_GET_PLAY_POS 0x20000 +#define RME96_IO_GET_REC_POS 0x30000 + +/* Write control register bits */ +#define RME96_WCR_START (1 << 0) +#define RME96_WCR_START_2 (1 << 1) +#define RME96_WCR_GAIN_0 (1 << 2) +#define RME96_WCR_GAIN_1 (1 << 3) +#define RME96_WCR_MODE24 (1 << 4) +#define RME96_WCR_MODE24_2 (1 << 5) +#define RME96_WCR_BM (1 << 6) +#define RME96_WCR_BM_2 (1 << 7) +#define RME96_WCR_ADAT (1 << 8) +#define RME96_WCR_FREQ_0 (1 << 9) +#define RME96_WCR_FREQ_1 (1 << 10) +#define RME96_WCR_DS (1 << 11) +#define RME96_WCR_PRO (1 << 12) +#define RME96_WCR_EMP (1 << 13) +#define RME96_WCR_SEL (1 << 14) +#define RME96_WCR_MASTER (1 << 15) +#define RME96_WCR_PD (1 << 16) +#define RME96_WCR_INP_0 (1 << 17) +#define RME96_WCR_INP_1 (1 << 18) +#define RME96_WCR_THRU_0 (1 << 19) +#define RME96_WCR_THRU_1 (1 << 20) +#define RME96_WCR_THRU_2 (1 << 21) +#define RME96_WCR_THRU_3 (1 << 22) +#define RME96_WCR_THRU_4 (1 << 23) +#define RME96_WCR_THRU_5 (1 << 24) +#define RME96_WCR_THRU_6 (1 << 25) +#define RME96_WCR_THRU_7 (1 << 26) +#define RME96_WCR_DOLBY (1 << 27) +#define RME96_WCR_MONITOR_0 (1 << 28) +#define RME96_WCR_MONITOR_1 (1 << 29) +#define RME96_WCR_ISEL (1 << 30) +#define RME96_WCR_IDIS (1 << 31) + +#define RME96_WCR_BITPOS_GAIN_0 2 +#define RME96_WCR_BITPOS_GAIN_1 3 +#define RME96_WCR_BITPOS_FREQ_0 9 +#define RME96_WCR_BITPOS_FREQ_1 10 +#define RME96_WCR_BITPOS_INP_0 17 +#define RME96_WCR_BITPOS_INP_1 18 +#define RME96_WCR_BITPOS_MONITOR_0 28 +#define RME96_WCR_BITPOS_MONITOR_1 29 + +/* Read control register bits */ +#define RME96_RCR_AUDIO_ADDR_MASK 0xFFFF +#define RME96_RCR_IRQ_2 (1 << 16) +#define RME96_RCR_T_OUT (1 << 17) +#define RME96_RCR_DEV_ID_0 (1 << 21) +#define RME96_RCR_DEV_ID_1 (1 << 22) +#define RME96_RCR_LOCK (1 << 23) +#define RME96_RCR_VERF (1 << 26) +#define RME96_RCR_F0 (1 << 27) +#define RME96_RCR_F1 (1 << 28) +#define RME96_RCR_F2 (1 << 29) +#define RME96_RCR_AUTOSYNC (1 << 30) +#define RME96_RCR_IRQ (1 << 31) + +#define RME96_RCR_BITPOS_F0 27 +#define RME96_RCR_BITPOS_F1 28 +#define RME96_RCR_BITPOS_F2 29 + +/* Additional register bits */ +#define RME96_AR_WSEL (1 << 0) +#define RME96_AR_ANALOG (1 << 1) +#define RME96_AR_FREQPAD_0 (1 << 2) +#define RME96_AR_FREQPAD_1 (1 << 3) +#define RME96_AR_FREQPAD_2 (1 << 4) +#define RME96_AR_PD2 (1 << 5) +#define RME96_AR_DAC_EN (1 << 6) +#define RME96_AR_CLATCH (1 << 7) +#define RME96_AR_CCLK (1 << 8) +#define RME96_AR_CDATA (1 << 9) + +#define RME96_AR_BITPOS_F0 2 +#define RME96_AR_BITPOS_F1 3 +#define RME96_AR_BITPOS_F2 4 + +/* Monitor tracks */ +#define RME96_MONITOR_TRACKS_1_2 0 +#define RME96_MONITOR_TRACKS_3_4 1 +#define RME96_MONITOR_TRACKS_5_6 2 +#define RME96_MONITOR_TRACKS_7_8 3 + +/* Attenuation */ +#define RME96_ATTENUATION_0 0 +#define RME96_ATTENUATION_6 1 +#define RME96_ATTENUATION_12 2 +#define RME96_ATTENUATION_18 3 + +/* Input types */ +#define RME96_INPUT_OPTICAL 0 +#define RME96_INPUT_COAXIAL 1 +#define RME96_INPUT_INTERNAL 2 +#define RME96_INPUT_XLR 3 +#define RME96_INPUT_ANALOG 4 + +/* Clock modes */ +#define RME96_CLOCKMODE_SLAVE 0 +#define RME96_CLOCKMODE_MASTER 1 +#define RME96_CLOCKMODE_WORDCLOCK 2 + +/* Block sizes in bytes */ +#define RME96_SMALL_BLOCK_SIZE 2048 +#define RME96_LARGE_BLOCK_SIZE 8192 + +/* Volume control */ +#define RME96_AD1852_VOL_BITS 14 +#define RME96_AD1855_VOL_BITS 10 + +/* Defines for snd_rme96_trigger */ +#define RME96_TB_START_PLAYBACK 1 +#define RME96_TB_START_CAPTURE 2 +#define RME96_TB_STOP_PLAYBACK 4 +#define RME96_TB_STOP_CAPTURE 8 +#define RME96_TB_RESET_PLAYPOS 16 +#define RME96_TB_RESET_CAPTUREPOS 32 +#define RME96_TB_CLEAR_PLAYBACK_IRQ 64 +#define RME96_TB_CLEAR_CAPTURE_IRQ 128 +#define RME96_RESUME_PLAYBACK (RME96_TB_START_PLAYBACK) +#define RME96_RESUME_CAPTURE (RME96_TB_START_CAPTURE) +#define RME96_RESUME_BOTH (RME96_RESUME_PLAYBACK \ + | RME96_RESUME_CAPTURE) +#define RME96_START_PLAYBACK (RME96_TB_START_PLAYBACK \ + | RME96_TB_RESET_PLAYPOS) +#define RME96_START_CAPTURE (RME96_TB_START_CAPTURE \ + | RME96_TB_RESET_CAPTUREPOS) +#define RME96_START_BOTH (RME96_START_PLAYBACK \ + | RME96_START_CAPTURE) +#define RME96_STOP_PLAYBACK (RME96_TB_STOP_PLAYBACK \ + | RME96_TB_CLEAR_PLAYBACK_IRQ) +#define RME96_STOP_CAPTURE (RME96_TB_STOP_CAPTURE \ + | RME96_TB_CLEAR_CAPTURE_IRQ) +#define RME96_STOP_BOTH (RME96_STOP_PLAYBACK \ + | RME96_STOP_CAPTURE) + +struct rme96 { + spinlock_t lock; + int irq; + unsigned long port; + void __iomem *iobase; + + u32 wcreg; /* cached write control register value */ + u32 wcreg_spdif; /* S/PDIF setup */ + u32 wcreg_spdif_stream; /* S/PDIF setup (temporary) */ + u32 rcreg; /* cached read control register value */ + u32 areg; /* cached additional register value */ + u16 vol[2]; /* cached volume of analog output */ + + u8 rev; /* card revision number */ + + u32 playback_pointer; + u32 capture_pointer; + void *playback_suspend_buffer; + void *capture_suspend_buffer; + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + int playback_frlog; /* log2 of framesize */ + int capture_frlog; + + size_t playback_periodsize; /* in bytes, zero if not used */ + size_t capture_periodsize; /* in bytes, zero if not used */ + + struct snd_card *card; + struct snd_pcm *spdif_pcm; + struct snd_pcm *adat_pcm; + struct pci_dev *pci; + struct snd_kcontrol *spdif_ctl; +}; + +static const struct pci_device_id snd_rme96_ids[] = { + { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96) }, + { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96_8) }, + { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96_8_PRO) }, + { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_rme96_ids); + +#define RME96_ISPLAYING(rme96) ((rme96)->wcreg & RME96_WCR_START) +#define RME96_ISRECORDING(rme96) ((rme96)->wcreg & RME96_WCR_START_2) +#define RME96_HAS_ANALOG_IN(rme96) ((rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST) +#define RME96_HAS_ANALOG_OUT(rme96) ((rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PRO || \ + (rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST) +#define RME96_DAC_IS_1852(rme96) (RME96_HAS_ANALOG_OUT(rme96) && (rme96)->rev >= 4) +#define RME96_DAC_IS_1855(rme96) (((rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST && (rme96)->rev < 4) || \ + ((rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PRO && (rme96)->rev == 2)) +#define RME96_185X_MAX_OUT(rme96) ((1 << (RME96_DAC_IS_1852(rme96) ? RME96_AD1852_VOL_BITS : RME96_AD1855_VOL_BITS)) - 1) + +static int +snd_rme96_playback_prepare(struct snd_pcm_substream *substream); + +static int +snd_rme96_capture_prepare(struct snd_pcm_substream *substream); + +static int +snd_rme96_playback_trigger(struct snd_pcm_substream *substream, + int cmd); + +static int +snd_rme96_capture_trigger(struct snd_pcm_substream *substream, + int cmd); + +static snd_pcm_uframes_t +snd_rme96_playback_pointer(struct snd_pcm_substream *substream); + +static snd_pcm_uframes_t +snd_rme96_capture_pointer(struct snd_pcm_substream *substream); + +static void snd_rme96_proc_init(struct rme96 *rme96); + +static int +snd_rme96_create_switches(struct snd_card *card, + struct rme96 *rme96); + +static int +snd_rme96_getinputtype(struct rme96 *rme96); + +static inline unsigned int +snd_rme96_playback_ptr(struct rme96 *rme96) +{ + return (readl(rme96->iobase + RME96_IO_GET_PLAY_POS) + & RME96_RCR_AUDIO_ADDR_MASK) >> rme96->playback_frlog; +} + +static inline unsigned int +snd_rme96_capture_ptr(struct rme96 *rme96) +{ + return (readl(rme96->iobase + RME96_IO_GET_REC_POS) + & RME96_RCR_AUDIO_ADDR_MASK) >> rme96->capture_frlog; +} + +static int +snd_rme96_playback_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, unsigned long count) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + + memset_io(rme96->iobase + RME96_IO_PLAY_BUFFER + pos, + 0, count); + return 0; +} + +static int +snd_rme96_playback_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *src, unsigned long count) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + + if (copy_from_iter_toio(rme96->iobase + RME96_IO_PLAY_BUFFER + pos, + count, src) != count) + return -EFAULT; + return 0; +} + +static int +snd_rme96_capture_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *dst, unsigned long count) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + + if (copy_to_iter_fromio(rme96->iobase + RME96_IO_REC_BUFFER + pos, + count, dst) != count) + return -EFAULT; + return 0; +} + +/* + * Digital output capabilities (S/PDIF) + */ +static const struct snd_pcm_hardware snd_rme96_playback_spdif_info = +{ + .info = (SNDRV_PCM_INFO_MMAP_IOMEM | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE), + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000), + .rate_min = 32000, + .rate_max = 96000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = RME96_BUFFER_SIZE, + .period_bytes_min = RME96_SMALL_BLOCK_SIZE, + .period_bytes_max = RME96_LARGE_BLOCK_SIZE, + .periods_min = RME96_BUFFER_SIZE / RME96_LARGE_BLOCK_SIZE, + .periods_max = RME96_BUFFER_SIZE / RME96_SMALL_BLOCK_SIZE, + .fifo_size = 0, +}; + +/* + * Digital input capabilities (S/PDIF) + */ +static const struct snd_pcm_hardware snd_rme96_capture_spdif_info = +{ + .info = (SNDRV_PCM_INFO_MMAP_IOMEM | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE), + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000), + .rate_min = 32000, + .rate_max = 96000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = RME96_BUFFER_SIZE, + .period_bytes_min = RME96_SMALL_BLOCK_SIZE, + .period_bytes_max = RME96_LARGE_BLOCK_SIZE, + .periods_min = RME96_BUFFER_SIZE / RME96_LARGE_BLOCK_SIZE, + .periods_max = RME96_BUFFER_SIZE / RME96_SMALL_BLOCK_SIZE, + .fifo_size = 0, +}; + +/* + * Digital output capabilities (ADAT) + */ +static const struct snd_pcm_hardware snd_rme96_playback_adat_info = +{ + .info = (SNDRV_PCM_INFO_MMAP_IOMEM | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE), + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = RME96_BUFFER_SIZE, + .period_bytes_min = RME96_SMALL_BLOCK_SIZE, + .period_bytes_max = RME96_LARGE_BLOCK_SIZE, + .periods_min = RME96_BUFFER_SIZE / RME96_LARGE_BLOCK_SIZE, + .periods_max = RME96_BUFFER_SIZE / RME96_SMALL_BLOCK_SIZE, + .fifo_size = 0, +}; + +/* + * Digital input capabilities (ADAT) + */ +static const struct snd_pcm_hardware snd_rme96_capture_adat_info = +{ + .info = (SNDRV_PCM_INFO_MMAP_IOMEM | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE), + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = RME96_BUFFER_SIZE, + .period_bytes_min = RME96_SMALL_BLOCK_SIZE, + .period_bytes_max = RME96_LARGE_BLOCK_SIZE, + .periods_min = RME96_BUFFER_SIZE / RME96_LARGE_BLOCK_SIZE, + .periods_max = RME96_BUFFER_SIZE / RME96_SMALL_BLOCK_SIZE, + .fifo_size = 0, +}; + +/* + * The CDATA, CCLK and CLATCH bits can be used to write to the SPI interface + * of the AD1852 or AD1852 D/A converter on the board. CDATA must be set up + * on the falling edge of CCLK and be stable on the rising edge. The rising + * edge of CLATCH after the last data bit clocks in the whole data word. + * A fast processor could probably drive the SPI interface faster than the + * DAC can handle (3MHz for the 1855, unknown for the 1852). The udelay(1) + * limits the data rate to 500KHz and only causes a delay of 33 microsecs. + * + * NOTE: increased delay from 1 to 10, since there where problems setting + * the volume. + */ +static void +snd_rme96_write_SPI(struct rme96 *rme96, u16 val) +{ + int i; + + for (i = 0; i < 16; i++) { + if (val & 0x8000) { + rme96->areg |= RME96_AR_CDATA; + } else { + rme96->areg &= ~RME96_AR_CDATA; + } + rme96->areg &= ~(RME96_AR_CCLK | RME96_AR_CLATCH); + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + udelay(10); + rme96->areg |= RME96_AR_CCLK; + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + udelay(10); + val <<= 1; + } + rme96->areg &= ~(RME96_AR_CCLK | RME96_AR_CDATA); + rme96->areg |= RME96_AR_CLATCH; + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + udelay(10); + rme96->areg &= ~RME96_AR_CLATCH; + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); +} + +static void +snd_rme96_apply_dac_volume(struct rme96 *rme96) +{ + if (RME96_DAC_IS_1852(rme96)) { + snd_rme96_write_SPI(rme96, (rme96->vol[0] << 2) | 0x0); + snd_rme96_write_SPI(rme96, (rme96->vol[1] << 2) | 0x2); + } else if (RME96_DAC_IS_1855(rme96)) { + snd_rme96_write_SPI(rme96, (rme96->vol[0] & 0x3FF) | 0x000); + snd_rme96_write_SPI(rme96, (rme96->vol[1] & 0x3FF) | 0x400); + } +} + +static void +snd_rme96_reset_dac(struct rme96 *rme96) +{ + writel(rme96->wcreg | RME96_WCR_PD, + rme96->iobase + RME96_IO_CONTROL_REGISTER); + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); +} + +static int +snd_rme96_getmontracks(struct rme96 *rme96) +{ + return ((rme96->wcreg >> RME96_WCR_BITPOS_MONITOR_0) & 1) + + (((rme96->wcreg >> RME96_WCR_BITPOS_MONITOR_1) & 1) << 1); +} + +static int +snd_rme96_setmontracks(struct rme96 *rme96, + int montracks) +{ + if (montracks & 1) { + rme96->wcreg |= RME96_WCR_MONITOR_0; + } else { + rme96->wcreg &= ~RME96_WCR_MONITOR_0; + } + if (montracks & 2) { + rme96->wcreg |= RME96_WCR_MONITOR_1; + } else { + rme96->wcreg &= ~RME96_WCR_MONITOR_1; + } + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + return 0; +} + +static int +snd_rme96_getattenuation(struct rme96 *rme96) +{ + return ((rme96->wcreg >> RME96_WCR_BITPOS_GAIN_0) & 1) + + (((rme96->wcreg >> RME96_WCR_BITPOS_GAIN_1) & 1) << 1); +} + +static int +snd_rme96_setattenuation(struct rme96 *rme96, + int attenuation) +{ + switch (attenuation) { + case 0: + rme96->wcreg = (rme96->wcreg & ~RME96_WCR_GAIN_0) & + ~RME96_WCR_GAIN_1; + break; + case 1: + rme96->wcreg = (rme96->wcreg | RME96_WCR_GAIN_0) & + ~RME96_WCR_GAIN_1; + break; + case 2: + rme96->wcreg = (rme96->wcreg & ~RME96_WCR_GAIN_0) | + RME96_WCR_GAIN_1; + break; + case 3: + rme96->wcreg = (rme96->wcreg | RME96_WCR_GAIN_0) | + RME96_WCR_GAIN_1; + break; + default: + return -EINVAL; + } + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + return 0; +} + +static int +snd_rme96_capture_getrate(struct rme96 *rme96, + int *is_adat) +{ + int n, rate; + + *is_adat = 0; + if (rme96->areg & RME96_AR_ANALOG) { + /* Analog input, overrides S/PDIF setting */ + n = ((rme96->areg >> RME96_AR_BITPOS_F0) & 1) + + (((rme96->areg >> RME96_AR_BITPOS_F1) & 1) << 1); + switch (n) { + case 1: + rate = 32000; + break; + case 2: + rate = 44100; + break; + case 3: + rate = 48000; + break; + default: + return -1; + } + return (rme96->areg & RME96_AR_BITPOS_F2) ? rate << 1 : rate; + } + + rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER); + if (rme96->rcreg & RME96_RCR_LOCK) { + /* ADAT rate */ + *is_adat = 1; + if (rme96->rcreg & RME96_RCR_T_OUT) { + return 48000; + } + return 44100; + } + + if (rme96->rcreg & RME96_RCR_VERF) { + return -1; + } + + /* S/PDIF rate */ + n = ((rme96->rcreg >> RME96_RCR_BITPOS_F0) & 1) + + (((rme96->rcreg >> RME96_RCR_BITPOS_F1) & 1) << 1) + + (((rme96->rcreg >> RME96_RCR_BITPOS_F2) & 1) << 2); + + switch (n) { + case 0: + if (rme96->rcreg & RME96_RCR_T_OUT) { + return 64000; + } + return -1; + case 3: return 96000; + case 4: return 88200; + case 5: return 48000; + case 6: return 44100; + case 7: return 32000; + default: + break; + } + return -1; +} + +static int +snd_rme96_playback_getrate(struct rme96 *rme96) +{ + int rate, dummy; + + if (!(rme96->wcreg & RME96_WCR_MASTER) && + snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG) { + rate = snd_rme96_capture_getrate(rme96, &dummy); + if (rate > 0) { + /* slave clock */ + return rate; + } + } + + rate = ((rme96->wcreg >> RME96_WCR_BITPOS_FREQ_0) & 1) + + (((rme96->wcreg >> RME96_WCR_BITPOS_FREQ_1) & 1) << 1); + switch (rate) { + case 1: + rate = 32000; + break; + case 2: + rate = 44100; + break; + case 3: + rate = 48000; + break; + default: + return -1; + } + return (rme96->wcreg & RME96_WCR_DS) ? rate << 1 : rate; +} + +static int +snd_rme96_playback_setrate(struct rme96 *rme96, + int rate) +{ + int ds; + + ds = rme96->wcreg & RME96_WCR_DS; + switch (rate) { + case 32000: + rme96->wcreg &= ~RME96_WCR_DS; + rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) & + ~RME96_WCR_FREQ_1; + break; + case 44100: + rme96->wcreg &= ~RME96_WCR_DS; + rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_1) & + ~RME96_WCR_FREQ_0; + break; + case 48000: + rme96->wcreg &= ~RME96_WCR_DS; + rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) | + RME96_WCR_FREQ_1; + break; + case 64000: + rme96->wcreg |= RME96_WCR_DS; + rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) & + ~RME96_WCR_FREQ_1; + break; + case 88200: + rme96->wcreg |= RME96_WCR_DS; + rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_1) & + ~RME96_WCR_FREQ_0; + break; + case 96000: + rme96->wcreg |= RME96_WCR_DS; + rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) | + RME96_WCR_FREQ_1; + break; + default: + return -EINVAL; + } + if ((!ds && rme96->wcreg & RME96_WCR_DS) || + (ds && !(rme96->wcreg & RME96_WCR_DS))) + { + /* change to/from double-speed: reset the DAC (if available) */ + snd_rme96_reset_dac(rme96); + return 1; /* need to restore volume */ + } else { + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + return 0; + } +} + +static int +snd_rme96_capture_analog_setrate(struct rme96 *rme96, + int rate) +{ + switch (rate) { + case 32000: + rme96->areg = ((rme96->areg | RME96_AR_FREQPAD_0) & + ~RME96_AR_FREQPAD_1) & ~RME96_AR_FREQPAD_2; + break; + case 44100: + rme96->areg = ((rme96->areg & ~RME96_AR_FREQPAD_0) | + RME96_AR_FREQPAD_1) & ~RME96_AR_FREQPAD_2; + break; + case 48000: + rme96->areg = ((rme96->areg | RME96_AR_FREQPAD_0) | + RME96_AR_FREQPAD_1) & ~RME96_AR_FREQPAD_2; + break; + case 64000: + if (rme96->rev < 4) { + return -EINVAL; + } + rme96->areg = ((rme96->areg | RME96_AR_FREQPAD_0) & + ~RME96_AR_FREQPAD_1) | RME96_AR_FREQPAD_2; + break; + case 88200: + if (rme96->rev < 4) { + return -EINVAL; + } + rme96->areg = ((rme96->areg & ~RME96_AR_FREQPAD_0) | + RME96_AR_FREQPAD_1) | RME96_AR_FREQPAD_2; + break; + case 96000: + rme96->areg = ((rme96->areg | RME96_AR_FREQPAD_0) | + RME96_AR_FREQPAD_1) | RME96_AR_FREQPAD_2; + break; + default: + return -EINVAL; + } + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + return 0; +} + +static int +snd_rme96_setclockmode(struct rme96 *rme96, + int mode) +{ + switch (mode) { + case RME96_CLOCKMODE_SLAVE: + /* AutoSync */ + rme96->wcreg &= ~RME96_WCR_MASTER; + rme96->areg &= ~RME96_AR_WSEL; + break; + case RME96_CLOCKMODE_MASTER: + /* Internal */ + rme96->wcreg |= RME96_WCR_MASTER; + rme96->areg &= ~RME96_AR_WSEL; + break; + case RME96_CLOCKMODE_WORDCLOCK: + /* Word clock is a master mode */ + rme96->wcreg |= RME96_WCR_MASTER; + rme96->areg |= RME96_AR_WSEL; + break; + default: + return -EINVAL; + } + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + return 0; +} + +static int +snd_rme96_getclockmode(struct rme96 *rme96) +{ + if (rme96->areg & RME96_AR_WSEL) { + return RME96_CLOCKMODE_WORDCLOCK; + } + return (rme96->wcreg & RME96_WCR_MASTER) ? RME96_CLOCKMODE_MASTER : + RME96_CLOCKMODE_SLAVE; +} + +static int +snd_rme96_setinputtype(struct rme96 *rme96, + int type) +{ + int n; + + switch (type) { + case RME96_INPUT_OPTICAL: + rme96->wcreg = (rme96->wcreg & ~RME96_WCR_INP_0) & + ~RME96_WCR_INP_1; + break; + case RME96_INPUT_COAXIAL: + rme96->wcreg = (rme96->wcreg | RME96_WCR_INP_0) & + ~RME96_WCR_INP_1; + break; + case RME96_INPUT_INTERNAL: + rme96->wcreg = (rme96->wcreg & ~RME96_WCR_INP_0) | + RME96_WCR_INP_1; + break; + case RME96_INPUT_XLR: + if ((rme96->pci->device != PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST && + rme96->pci->device != PCI_DEVICE_ID_RME_DIGI96_8_PRO) || + (rme96->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST && + rme96->rev > 4)) + { + /* Only Digi96/8 PRO and Digi96/8 PAD supports XLR */ + return -EINVAL; + } + rme96->wcreg = (rme96->wcreg | RME96_WCR_INP_0) | + RME96_WCR_INP_1; + break; + case RME96_INPUT_ANALOG: + if (!RME96_HAS_ANALOG_IN(rme96)) { + return -EINVAL; + } + rme96->areg |= RME96_AR_ANALOG; + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + if (rme96->rev < 4) { + /* + * Revision less than 004 does not support 64 and + * 88.2 kHz + */ + if (snd_rme96_capture_getrate(rme96, &n) == 88200) { + snd_rme96_capture_analog_setrate(rme96, 44100); + } + if (snd_rme96_capture_getrate(rme96, &n) == 64000) { + snd_rme96_capture_analog_setrate(rme96, 32000); + } + } + return 0; + default: + return -EINVAL; + } + if (type != RME96_INPUT_ANALOG && RME96_HAS_ANALOG_IN(rme96)) { + rme96->areg &= ~RME96_AR_ANALOG; + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + } + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + return 0; +} + +static int +snd_rme96_getinputtype(struct rme96 *rme96) +{ + if (rme96->areg & RME96_AR_ANALOG) { + return RME96_INPUT_ANALOG; + } + return ((rme96->wcreg >> RME96_WCR_BITPOS_INP_0) & 1) + + (((rme96->wcreg >> RME96_WCR_BITPOS_INP_1) & 1) << 1); +} + +static void +snd_rme96_setframelog(struct rme96 *rme96, + int n_channels, + int is_playback) +{ + int frlog; + + if (n_channels == 2) { + frlog = 1; + } else { + /* assume 8 channels */ + frlog = 3; + } + if (is_playback) { + frlog += (rme96->wcreg & RME96_WCR_MODE24) ? 2 : 1; + rme96->playback_frlog = frlog; + } else { + frlog += (rme96->wcreg & RME96_WCR_MODE24_2) ? 2 : 1; + rme96->capture_frlog = frlog; + } +} + +static int +snd_rme96_playback_setformat(struct rme96 *rme96, snd_pcm_format_t format) +{ + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + rme96->wcreg &= ~RME96_WCR_MODE24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + rme96->wcreg |= RME96_WCR_MODE24; + break; + default: + return -EINVAL; + } + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + return 0; +} + +static int +snd_rme96_capture_setformat(struct rme96 *rme96, snd_pcm_format_t format) +{ + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + rme96->wcreg &= ~RME96_WCR_MODE24_2; + break; + case SNDRV_PCM_FORMAT_S32_LE: + rme96->wcreg |= RME96_WCR_MODE24_2; + break; + default: + return -EINVAL; + } + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + return 0; +} + +static void +snd_rme96_set_period_properties(struct rme96 *rme96, + size_t period_bytes) +{ + switch (period_bytes) { + case RME96_LARGE_BLOCK_SIZE: + rme96->wcreg &= ~RME96_WCR_ISEL; + break; + case RME96_SMALL_BLOCK_SIZE: + rme96->wcreg |= RME96_WCR_ISEL; + break; + default: + snd_BUG(); + break; + } + rme96->wcreg &= ~RME96_WCR_IDIS; + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); +} + +static int +snd_rme96_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err, rate, dummy; + bool apply_dac_volume = false; + + runtime->dma_area = (void __force *)(rme96->iobase + + RME96_IO_PLAY_BUFFER); + runtime->dma_addr = rme96->port + RME96_IO_PLAY_BUFFER; + runtime->dma_bytes = RME96_BUFFER_SIZE; + + scoped_guard(spinlock_irq, &rme96->lock) { + rate = 0; + if (!(rme96->wcreg & RME96_WCR_MASTER) && + snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG) + rate = snd_rme96_capture_getrate(rme96, &dummy); + if (rate > 0) { + /* slave clock */ + if ((int)params_rate(params) != rate) + return -EIO; + } else { + err = snd_rme96_playback_setrate(rme96, params_rate(params)); + if (err < 0) + return err; + apply_dac_volume = err > 0; /* need to restore volume later? */ + } + + err = snd_rme96_playback_setformat(rme96, params_format(params)); + if (err < 0) + goto error; + snd_rme96_setframelog(rme96, params_channels(params), 1); + if (rme96->capture_periodsize != 0) { + if (params_period_size(params) << rme96->playback_frlog != + rme96->capture_periodsize) { + err = -EBUSY; + goto error; + } + } + rme96->playback_periodsize = + params_period_size(params) << rme96->playback_frlog; + snd_rme96_set_period_properties(rme96, rme96->playback_periodsize); + /* S/PDIF setup */ + if ((rme96->wcreg & RME96_WCR_ADAT) == 0) { + rme96->wcreg &= ~(RME96_WCR_PRO | RME96_WCR_DOLBY | RME96_WCR_EMP); + writel(rme96->wcreg |= rme96->wcreg_spdif_stream, rme96->iobase + RME96_IO_CONTROL_REGISTER); + } + + err = 0; + } + error: + if (apply_dac_volume) { + usleep_range(3000, 10000); + snd_rme96_apply_dac_volume(rme96); + } + + return err; +} + +static int +snd_rme96_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err, isadat, rate; + + runtime->dma_area = (void __force *)(rme96->iobase + + RME96_IO_REC_BUFFER); + runtime->dma_addr = rme96->port + RME96_IO_REC_BUFFER; + runtime->dma_bytes = RME96_BUFFER_SIZE; + + guard(spinlock_irq)(&rme96->lock); + err = snd_rme96_capture_setformat(rme96, params_format(params)); + if (err < 0) + return err; + if (snd_rme96_getinputtype(rme96) == RME96_INPUT_ANALOG) { + err = snd_rme96_capture_analog_setrate(rme96, params_rate(params)); + if (err < 0) + return err; + } else { + rate = snd_rme96_capture_getrate(rme96, &isadat); + if (rate > 0) { + if ((int)params_rate(params) != rate) + return -EIO; + if ((isadat && runtime->hw.channels_min == 2) || + (!isadat && runtime->hw.channels_min == 8)) + return -EIO; + } + } + snd_rme96_setframelog(rme96, params_channels(params), 0); + if (rme96->playback_periodsize != 0) { + if (params_period_size(params) << rme96->capture_frlog != + rme96->playback_periodsize) + return -EBUSY; + } + rme96->capture_periodsize = + params_period_size(params) << rme96->capture_frlog; + snd_rme96_set_period_properties(rme96, rme96->capture_periodsize); + + return 0; +} + +static void +snd_rme96_trigger(struct rme96 *rme96, + int op) +{ + if (op & RME96_TB_RESET_PLAYPOS) + writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS); + if (op & RME96_TB_RESET_CAPTUREPOS) + writel(0, rme96->iobase + RME96_IO_RESET_REC_POS); + if (op & RME96_TB_CLEAR_PLAYBACK_IRQ) { + rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER); + if (rme96->rcreg & RME96_RCR_IRQ) + writel(0, rme96->iobase + RME96_IO_CONFIRM_PLAY_IRQ); + } + if (op & RME96_TB_CLEAR_CAPTURE_IRQ) { + rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER); + if (rme96->rcreg & RME96_RCR_IRQ_2) + writel(0, rme96->iobase + RME96_IO_CONFIRM_REC_IRQ); + } + if (op & RME96_TB_START_PLAYBACK) + rme96->wcreg |= RME96_WCR_START; + if (op & RME96_TB_STOP_PLAYBACK) + rme96->wcreg &= ~RME96_WCR_START; + if (op & RME96_TB_START_CAPTURE) + rme96->wcreg |= RME96_WCR_START_2; + if (op & RME96_TB_STOP_CAPTURE) + rme96->wcreg &= ~RME96_WCR_START_2; + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); +} + + + +static irqreturn_t +snd_rme96_interrupt(int irq, + void *dev_id) +{ + struct rme96 *rme96 = (struct rme96 *)dev_id; + + rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER); + /* fastpath out, to ease interrupt sharing */ + if (!((rme96->rcreg & RME96_RCR_IRQ) || + (rme96->rcreg & RME96_RCR_IRQ_2))) + { + return IRQ_NONE; + } + + if (rme96->rcreg & RME96_RCR_IRQ) { + /* playback */ + snd_pcm_period_elapsed(rme96->playback_substream); + writel(0, rme96->iobase + RME96_IO_CONFIRM_PLAY_IRQ); + } + if (rme96->rcreg & RME96_RCR_IRQ_2) { + /* capture */ + snd_pcm_period_elapsed(rme96->capture_substream); + writel(0, rme96->iobase + RME96_IO_CONFIRM_REC_IRQ); + } + return IRQ_HANDLED; +} + +static const unsigned int period_bytes[] = { RME96_SMALL_BLOCK_SIZE, RME96_LARGE_BLOCK_SIZE }; + +static const struct snd_pcm_hw_constraint_list hw_constraints_period_bytes = { + .count = ARRAY_SIZE(period_bytes), + .list = period_bytes, + .mask = 0 +}; + +static void +rme96_set_buffer_size_constraint(struct rme96 *rme96, + struct snd_pcm_runtime *runtime) +{ + unsigned int size; + + snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + RME96_BUFFER_SIZE); + size = rme96->playback_periodsize; + if (!size) + size = rme96->capture_periodsize; + if (size) + snd_pcm_hw_constraint_single(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + size); + else + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &hw_constraints_period_bytes); +} + +static int +snd_rme96_playback_spdif_open(struct snd_pcm_substream *substream) +{ + int rate, dummy; + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_sync(substream); + scoped_guard(spinlock_irq, &rme96->lock) { + if (rme96->playback_substream) + return -EBUSY; + rme96->wcreg &= ~RME96_WCR_ADAT; + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + rme96->playback_substream = substream; + } + + runtime->hw = snd_rme96_playback_spdif_info; + if (!(rme96->wcreg & RME96_WCR_MASTER) && + snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG) { + rate = snd_rme96_capture_getrate(rme96, &dummy); + if (rate > 0) { + /* slave clock */ + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } + } + rme96_set_buffer_size_constraint(rme96, runtime); + + rme96->wcreg_spdif_stream = rme96->wcreg_spdif; + rme96->spdif_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(rme96->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &rme96->spdif_ctl->id); + return 0; +} + +static int +snd_rme96_capture_spdif_open(struct snd_pcm_substream *substream) +{ + int isadat, rate; + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_sync(substream); + runtime->hw = snd_rme96_capture_spdif_info; + if (snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG) { + rate = snd_rme96_capture_getrate(rme96, &isadat); + if (rate > 0) { + if (isadat) + return -EIO; + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } + } + + scoped_guard(spinlock_irq, &rme96->lock) { + if (rme96->capture_substream) + return -EBUSY; + rme96->capture_substream = substream; + } + + rme96_set_buffer_size_constraint(rme96, runtime); + return 0; +} + +static int +snd_rme96_playback_adat_open(struct snd_pcm_substream *substream) +{ + int rate, dummy; + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_sync(substream); + scoped_guard(spinlock_irq, &rme96->lock) { + if (rme96->playback_substream) + return -EBUSY; + rme96->wcreg |= RME96_WCR_ADAT; + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + rme96->playback_substream = substream; + } + + runtime->hw = snd_rme96_playback_adat_info; + if (!(rme96->wcreg & RME96_WCR_MASTER) && + snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG) { + rate = snd_rme96_capture_getrate(rme96, &dummy); + if (rate > 0) { + /* slave clock */ + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } + } + + rme96_set_buffer_size_constraint(rme96, runtime); + return 0; +} + +static int +snd_rme96_capture_adat_open(struct snd_pcm_substream *substream) +{ + int isadat, rate; + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_sync(substream); + runtime->hw = snd_rme96_capture_adat_info; + if (snd_rme96_getinputtype(rme96) == RME96_INPUT_ANALOG) { + /* makes no sense to use analog input. Note that analog + expension cards AEB4/8-I are RME96_INPUT_INTERNAL */ + return -EIO; + } + rate = snd_rme96_capture_getrate(rme96, &isadat); + if (rate > 0) { + if (!isadat) { + return -EIO; + } + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } + + scoped_guard(spinlock_irq, &rme96->lock) { + if (rme96->capture_substream) + return -EBUSY; + rme96->capture_substream = substream; + } + + rme96_set_buffer_size_constraint(rme96, runtime); + return 0; +} + +static int +snd_rme96_playback_close(struct snd_pcm_substream *substream) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + int spdif = 0; + + scoped_guard(spinlock_irq, &rme96->lock) { + if (RME96_ISPLAYING(rme96)) + snd_rme96_trigger(rme96, RME96_STOP_PLAYBACK); + rme96->playback_substream = NULL; + rme96->playback_periodsize = 0; + spdif = (rme96->wcreg & RME96_WCR_ADAT) == 0; + } + if (spdif) { + rme96->spdif_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(rme96->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &rme96->spdif_ctl->id); + } + return 0; +} + +static int +snd_rme96_capture_close(struct snd_pcm_substream *substream) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&rme96->lock); + if (RME96_ISRECORDING(rme96)) { + snd_rme96_trigger(rme96, RME96_STOP_CAPTURE); + } + rme96->capture_substream = NULL; + rme96->capture_periodsize = 0; + return 0; +} + +static int +snd_rme96_playback_prepare(struct snd_pcm_substream *substream) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&rme96->lock); + if (RME96_ISPLAYING(rme96)) { + snd_rme96_trigger(rme96, RME96_STOP_PLAYBACK); + } + writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS); + return 0; +} + +static int +snd_rme96_capture_prepare(struct snd_pcm_substream *substream) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&rme96->lock); + if (RME96_ISRECORDING(rme96)) { + snd_rme96_trigger(rme96, RME96_STOP_CAPTURE); + } + writel(0, rme96->iobase + RME96_IO_RESET_REC_POS); + return 0; +} + +static int +snd_rme96_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + bool sync; + + snd_pcm_group_for_each_entry(s, substream) { + if (snd_pcm_substream_chip(s) == rme96) + snd_pcm_trigger_done(s, substream); + } + + sync = (rme96->playback_substream && rme96->capture_substream) && + (rme96->playback_substream->group == + rme96->capture_substream->group); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!RME96_ISPLAYING(rme96)) { + if (substream != rme96->playback_substream) + return -EBUSY; + snd_rme96_trigger(rme96, sync ? RME96_START_BOTH + : RME96_START_PLAYBACK); + } + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + if (RME96_ISPLAYING(rme96)) { + if (substream != rme96->playback_substream) + return -EBUSY; + snd_rme96_trigger(rme96, sync ? RME96_STOP_BOTH + : RME96_STOP_PLAYBACK); + } + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (RME96_ISPLAYING(rme96)) + snd_rme96_trigger(rme96, sync ? RME96_STOP_BOTH + : RME96_STOP_PLAYBACK); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!RME96_ISPLAYING(rme96)) + snd_rme96_trigger(rme96, sync ? RME96_RESUME_BOTH + : RME96_RESUME_PLAYBACK); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int +snd_rme96_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + bool sync; + + snd_pcm_group_for_each_entry(s, substream) { + if (snd_pcm_substream_chip(s) == rme96) + snd_pcm_trigger_done(s, substream); + } + + sync = (rme96->playback_substream && rme96->capture_substream) && + (rme96->playback_substream->group == + rme96->capture_substream->group); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!RME96_ISRECORDING(rme96)) { + if (substream != rme96->capture_substream) + return -EBUSY; + snd_rme96_trigger(rme96, sync ? RME96_START_BOTH + : RME96_START_CAPTURE); + } + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + if (RME96_ISRECORDING(rme96)) { + if (substream != rme96->capture_substream) + return -EBUSY; + snd_rme96_trigger(rme96, sync ? RME96_STOP_BOTH + : RME96_STOP_CAPTURE); + } + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (RME96_ISRECORDING(rme96)) + snd_rme96_trigger(rme96, sync ? RME96_STOP_BOTH + : RME96_STOP_CAPTURE); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!RME96_ISRECORDING(rme96)) + snd_rme96_trigger(rme96, sync ? RME96_RESUME_BOTH + : RME96_RESUME_CAPTURE); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +snd_rme96_playback_pointer(struct snd_pcm_substream *substream) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + return snd_rme96_playback_ptr(rme96); +} + +static snd_pcm_uframes_t +snd_rme96_capture_pointer(struct snd_pcm_substream *substream) +{ + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + return snd_rme96_capture_ptr(rme96); +} + +static const struct snd_pcm_ops snd_rme96_playback_spdif_ops = { + .open = snd_rme96_playback_spdif_open, + .close = snd_rme96_playback_close, + .hw_params = snd_rme96_playback_hw_params, + .prepare = snd_rme96_playback_prepare, + .trigger = snd_rme96_playback_trigger, + .pointer = snd_rme96_playback_pointer, + .copy = snd_rme96_playback_copy, + .fill_silence = snd_rme96_playback_silence, + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static const struct snd_pcm_ops snd_rme96_capture_spdif_ops = { + .open = snd_rme96_capture_spdif_open, + .close = snd_rme96_capture_close, + .hw_params = snd_rme96_capture_hw_params, + .prepare = snd_rme96_capture_prepare, + .trigger = snd_rme96_capture_trigger, + .pointer = snd_rme96_capture_pointer, + .copy = snd_rme96_capture_copy, + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static const struct snd_pcm_ops snd_rme96_playback_adat_ops = { + .open = snd_rme96_playback_adat_open, + .close = snd_rme96_playback_close, + .hw_params = snd_rme96_playback_hw_params, + .prepare = snd_rme96_playback_prepare, + .trigger = snd_rme96_playback_trigger, + .pointer = snd_rme96_playback_pointer, + .copy = snd_rme96_playback_copy, + .fill_silence = snd_rme96_playback_silence, + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static const struct snd_pcm_ops snd_rme96_capture_adat_ops = { + .open = snd_rme96_capture_adat_open, + .close = snd_rme96_capture_close, + .hw_params = snd_rme96_capture_hw_params, + .prepare = snd_rme96_capture_prepare, + .trigger = snd_rme96_capture_trigger, + .pointer = snd_rme96_capture_pointer, + .copy = snd_rme96_capture_copy, + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static void +snd_rme96_free(struct rme96 *rme96) +{ + if (rme96->irq >= 0) { + snd_rme96_trigger(rme96, RME96_STOP_BOTH); + rme96->areg &= ~RME96_AR_DAC_EN; + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + } + vfree(rme96->playback_suspend_buffer); + vfree(rme96->capture_suspend_buffer); +} + +static void +snd_rme96_free_spdif_pcm(struct snd_pcm *pcm) +{ + struct rme96 *rme96 = pcm->private_data; + rme96->spdif_pcm = NULL; +} + +static void +snd_rme96_free_adat_pcm(struct snd_pcm *pcm) +{ + struct rme96 *rme96 = pcm->private_data; + rme96->adat_pcm = NULL; +} + +static int +snd_rme96_create(struct rme96 *rme96) +{ + struct pci_dev *pci = rme96->pci; + int err; + + rme96->irq = -1; + spin_lock_init(&rme96->lock); + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + err = pcim_request_all_regions(pci, "RME96"); + if (err < 0) + return err; + rme96->port = pci_resource_start(rme96->pci, 0); + + rme96->iobase = devm_ioremap(&pci->dev, rme96->port, RME96_IO_SIZE); + if (!rme96->iobase) { + dev_err(rme96->card->dev, + "unable to remap memory region 0x%lx-0x%lx\n", + rme96->port, rme96->port + RME96_IO_SIZE - 1); + return -EBUSY; + } + + if (devm_request_irq(&pci->dev, pci->irq, snd_rme96_interrupt, + IRQF_SHARED, KBUILD_MODNAME, rme96)) { + dev_err(rme96->card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + rme96->irq = pci->irq; + rme96->card->sync_irq = rme96->irq; + + /* read the card's revision number */ + pci_read_config_byte(pci, 8, &rme96->rev); + + /* set up ALSA pcm device for S/PDIF */ + err = snd_pcm_new(rme96->card, "Digi96 IEC958", 0, + 1, 1, &rme96->spdif_pcm); + if (err < 0) + return err; + + rme96->spdif_pcm->private_data = rme96; + rme96->spdif_pcm->private_free = snd_rme96_free_spdif_pcm; + strscpy(rme96->spdif_pcm->name, "Digi96 IEC958"); + snd_pcm_set_ops(rme96->spdif_pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_rme96_playback_spdif_ops); + snd_pcm_set_ops(rme96->spdif_pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_rme96_capture_spdif_ops); + + rme96->spdif_pcm->info_flags = 0; + + /* set up ALSA pcm device for ADAT */ + if (pci->device == PCI_DEVICE_ID_RME_DIGI96) { + /* ADAT is not available on the base model */ + rme96->adat_pcm = NULL; + } else { + err = snd_pcm_new(rme96->card, "Digi96 ADAT", 1, + 1, 1, &rme96->adat_pcm); + if (err < 0) + return err; + rme96->adat_pcm->private_data = rme96; + rme96->adat_pcm->private_free = snd_rme96_free_adat_pcm; + strscpy(rme96->adat_pcm->name, "Digi96 ADAT"); + snd_pcm_set_ops(rme96->adat_pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_rme96_playback_adat_ops); + snd_pcm_set_ops(rme96->adat_pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_rme96_capture_adat_ops); + + rme96->adat_pcm->info_flags = 0; + } + + rme96->playback_periodsize = 0; + rme96->capture_periodsize = 0; + + /* make sure playback/capture is stopped, if by some reason active */ + snd_rme96_trigger(rme96, RME96_STOP_BOTH); + + /* set default values in registers */ + rme96->wcreg = + RME96_WCR_FREQ_1 | /* set 44.1 kHz playback */ + RME96_WCR_SEL | /* normal playback */ + RME96_WCR_MASTER | /* set to master clock mode */ + RME96_WCR_INP_0; /* set coaxial input */ + + rme96->areg = RME96_AR_FREQPAD_1; /* set 44.1 kHz analog capture */ + + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + + /* reset the ADC */ + writel(rme96->areg | RME96_AR_PD2, + rme96->iobase + RME96_IO_ADDITIONAL_REG); + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + + /* reset and enable the DAC (order is important). */ + snd_rme96_reset_dac(rme96); + rme96->areg |= RME96_AR_DAC_EN; + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + + /* reset playback and record buffer pointers */ + writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS); + writel(0, rme96->iobase + RME96_IO_RESET_REC_POS); + + /* reset volume */ + rme96->vol[0] = rme96->vol[1] = 0; + if (RME96_HAS_ANALOG_OUT(rme96)) { + snd_rme96_apply_dac_volume(rme96); + } + + /* init switch interface */ + err = snd_rme96_create_switches(rme96->card, rme96); + if (err < 0) + return err; + + /* init proc interface */ + snd_rme96_proc_init(rme96); + + return 0; +} + +/* + * proc interface + */ + +static void +snd_rme96_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + int n; + struct rme96 *rme96 = entry->private_data; + + rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER); + + snd_iprintf(buffer, rme96->card->longname); + snd_iprintf(buffer, " (index #%d)\n", rme96->card->number + 1); + + snd_iprintf(buffer, "\nGeneral settings\n"); + if (rme96->wcreg & RME96_WCR_IDIS) { + snd_iprintf(buffer, " period size: N/A (interrupts " + "disabled)\n"); + } else if (rme96->wcreg & RME96_WCR_ISEL) { + snd_iprintf(buffer, " period size: 2048 bytes\n"); + } else { + snd_iprintf(buffer, " period size: 8192 bytes\n"); + } + snd_iprintf(buffer, "\nInput settings\n"); + switch (snd_rme96_getinputtype(rme96)) { + case RME96_INPUT_OPTICAL: + snd_iprintf(buffer, " input: optical"); + break; + case RME96_INPUT_COAXIAL: + snd_iprintf(buffer, " input: coaxial"); + break; + case RME96_INPUT_INTERNAL: + snd_iprintf(buffer, " input: internal"); + break; + case RME96_INPUT_XLR: + snd_iprintf(buffer, " input: XLR"); + break; + case RME96_INPUT_ANALOG: + snd_iprintf(buffer, " input: analog"); + break; + } + if (snd_rme96_capture_getrate(rme96, &n) < 0) { + snd_iprintf(buffer, "\n sample rate: no valid signal\n"); + } else { + if (n) { + snd_iprintf(buffer, " (8 channels)\n"); + } else { + snd_iprintf(buffer, " (2 channels)\n"); + } + snd_iprintf(buffer, " sample rate: %d Hz\n", + snd_rme96_capture_getrate(rme96, &n)); + } + if (rme96->wcreg & RME96_WCR_MODE24_2) { + snd_iprintf(buffer, " sample format: 24 bit\n"); + } else { + snd_iprintf(buffer, " sample format: 16 bit\n"); + } + + snd_iprintf(buffer, "\nOutput settings\n"); + if (rme96->wcreg & RME96_WCR_SEL) { + snd_iprintf(buffer, " output signal: normal playback\n"); + } else { + snd_iprintf(buffer, " output signal: same as input\n"); + } + snd_iprintf(buffer, " sample rate: %d Hz\n", + snd_rme96_playback_getrate(rme96)); + if (rme96->wcreg & RME96_WCR_MODE24) { + snd_iprintf(buffer, " sample format: 24 bit\n"); + } else { + snd_iprintf(buffer, " sample format: 16 bit\n"); + } + if (rme96->areg & RME96_AR_WSEL) { + snd_iprintf(buffer, " sample clock source: word clock\n"); + } else if (rme96->wcreg & RME96_WCR_MASTER) { + snd_iprintf(buffer, " sample clock source: internal\n"); + } else if (snd_rme96_getinputtype(rme96) == RME96_INPUT_ANALOG) { + snd_iprintf(buffer, " sample clock source: autosync (internal anyway due to analog input setting)\n"); + } else if (snd_rme96_capture_getrate(rme96, &n) < 0) { + snd_iprintf(buffer, " sample clock source: autosync (internal anyway due to no valid signal)\n"); + } else { + snd_iprintf(buffer, " sample clock source: autosync\n"); + } + if (rme96->wcreg & RME96_WCR_PRO) { + snd_iprintf(buffer, " format: AES/EBU (professional)\n"); + } else { + snd_iprintf(buffer, " format: IEC958 (consumer)\n"); + } + if (rme96->wcreg & RME96_WCR_EMP) { + snd_iprintf(buffer, " emphasis: on\n"); + } else { + snd_iprintf(buffer, " emphasis: off\n"); + } + if (rme96->wcreg & RME96_WCR_DOLBY) { + snd_iprintf(buffer, " non-audio (dolby): on\n"); + } else { + snd_iprintf(buffer, " non-audio (dolby): off\n"); + } + if (RME96_HAS_ANALOG_IN(rme96)) { + snd_iprintf(buffer, "\nAnalog output settings\n"); + switch (snd_rme96_getmontracks(rme96)) { + case RME96_MONITOR_TRACKS_1_2: + snd_iprintf(buffer, " monitored ADAT tracks: 1+2\n"); + break; + case RME96_MONITOR_TRACKS_3_4: + snd_iprintf(buffer, " monitored ADAT tracks: 3+4\n"); + break; + case RME96_MONITOR_TRACKS_5_6: + snd_iprintf(buffer, " monitored ADAT tracks: 5+6\n"); + break; + case RME96_MONITOR_TRACKS_7_8: + snd_iprintf(buffer, " monitored ADAT tracks: 7+8\n"); + break; + } + switch (snd_rme96_getattenuation(rme96)) { + case RME96_ATTENUATION_0: + snd_iprintf(buffer, " attenuation: 0 dB\n"); + break; + case RME96_ATTENUATION_6: + snd_iprintf(buffer, " attenuation: -6 dB\n"); + break; + case RME96_ATTENUATION_12: + snd_iprintf(buffer, " attenuation: -12 dB\n"); + break; + case RME96_ATTENUATION_18: + snd_iprintf(buffer, " attenuation: -18 dB\n"); + break; + } + snd_iprintf(buffer, " volume left: %u\n", rme96->vol[0]); + snd_iprintf(buffer, " volume right: %u\n", rme96->vol[1]); + } +} + +static void snd_rme96_proc_init(struct rme96 *rme96) +{ + snd_card_ro_proc_new(rme96->card, "rme96", rme96, snd_rme96_proc_read); +} + +/* + * control interface + */ + +#define snd_rme96_info_loopback_control snd_ctl_boolean_mono_info + +static int +snd_rme96_get_loopback_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme96->lock); + ucontrol->value.integer.value[0] = rme96->wcreg & RME96_WCR_SEL ? 0 : 1; + return 0; +} +static int +snd_rme96_put_loopback_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ucontrol->value.integer.value[0] ? 0 : RME96_WCR_SEL; + guard(spinlock_irq)(&rme96->lock); + val = (rme96->wcreg & ~RME96_WCR_SEL) | val; + change = val != rme96->wcreg; + rme96->wcreg = val; + writel(val, rme96->iobase + RME96_IO_CONTROL_REGISTER); + return change; +} + +static int +snd_rme96_info_inputtype_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const _texts[5] = { + "Optical", "Coaxial", "Internal", "XLR", "Analog" + }; + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + const char *texts[5] = { + _texts[0], _texts[1], _texts[2], _texts[3], _texts[4] + }; + int num_items; + + switch (rme96->pci->device) { + case PCI_DEVICE_ID_RME_DIGI96: + case PCI_DEVICE_ID_RME_DIGI96_8: + num_items = 3; + break; + case PCI_DEVICE_ID_RME_DIGI96_8_PRO: + num_items = 4; + break; + case PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST: + if (rme96->rev > 4) { + /* PST */ + num_items = 4; + texts[3] = _texts[4]; /* Analog instead of XLR */ + } else { + /* PAD */ + num_items = 5; + } + break; + default: + snd_BUG(); + return -EINVAL; + } + return snd_ctl_enum_info(uinfo, 1, num_items, texts); +} +static int +snd_rme96_get_inputtype_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + unsigned int items = 3; + + guard(spinlock_irq)(&rme96->lock); + ucontrol->value.enumerated.item[0] = snd_rme96_getinputtype(rme96); + + switch (rme96->pci->device) { + case PCI_DEVICE_ID_RME_DIGI96: + case PCI_DEVICE_ID_RME_DIGI96_8: + items = 3; + break; + case PCI_DEVICE_ID_RME_DIGI96_8_PRO: + items = 4; + break; + case PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST: + if (rme96->rev > 4) { + /* for handling PST case, (INPUT_ANALOG is moved to INPUT_XLR */ + if (ucontrol->value.enumerated.item[0] == RME96_INPUT_ANALOG) { + ucontrol->value.enumerated.item[0] = RME96_INPUT_XLR; + } + items = 4; + } else { + items = 5; + } + break; + default: + snd_BUG(); + break; + } + if (ucontrol->value.enumerated.item[0] >= items) { + ucontrol->value.enumerated.item[0] = items - 1; + } + + return 0; +} +static int +snd_rme96_put_inputtype_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change, items = 3; + + switch (rme96->pci->device) { + case PCI_DEVICE_ID_RME_DIGI96: + case PCI_DEVICE_ID_RME_DIGI96_8: + items = 3; + break; + case PCI_DEVICE_ID_RME_DIGI96_8_PRO: + items = 4; + break; + case PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST: + if (rme96->rev > 4) { + items = 4; + } else { + items = 5; + } + break; + default: + snd_BUG(); + break; + } + val = ucontrol->value.enumerated.item[0] % items; + + /* special case for PST */ + if (rme96->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST && rme96->rev > 4) { + if (val == RME96_INPUT_XLR) { + val = RME96_INPUT_ANALOG; + } + } + + guard(spinlock_irq)(&rme96->lock); + change = (int)val != snd_rme96_getinputtype(rme96); + snd_rme96_setinputtype(rme96, val); + return change; +} + +static int +snd_rme96_info_clockmode_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { "AutoSync", "Internal", "Word" }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} +static int +snd_rme96_get_clockmode_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme96->lock); + ucontrol->value.enumerated.item[0] = snd_rme96_getclockmode(rme96); + return 0; +} +static int +snd_rme96_put_clockmode_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ucontrol->value.enumerated.item[0] % 3; + guard(spinlock_irq)(&rme96->lock); + change = (int)val != snd_rme96_getclockmode(rme96); + snd_rme96_setclockmode(rme96, val); + return change; +} + +static int +snd_rme96_info_attenuation_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "0 dB", "-6 dB", "-12 dB", "-18 dB" + }; + + return snd_ctl_enum_info(uinfo, 1, 4, texts); +} +static int +snd_rme96_get_attenuation_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme96->lock); + ucontrol->value.enumerated.item[0] = snd_rme96_getattenuation(rme96); + return 0; +} +static int +snd_rme96_put_attenuation_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ucontrol->value.enumerated.item[0] % 4; + guard(spinlock_irq)(&rme96->lock); + + change = (int)val != snd_rme96_getattenuation(rme96); + snd_rme96_setattenuation(rme96, val); + return change; +} + +static int +snd_rme96_info_montracks_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { "1+2", "3+4", "5+6", "7+8" }; + + return snd_ctl_enum_info(uinfo, 1, 4, texts); +} +static int +snd_rme96_get_montracks_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme96->lock); + ucontrol->value.enumerated.item[0] = snd_rme96_getmontracks(rme96); + return 0; +} +static int +snd_rme96_put_montracks_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ucontrol->value.enumerated.item[0] % 4; + guard(spinlock_irq)(&rme96->lock); + change = (int)val != snd_rme96_getmontracks(rme96); + snd_rme96_setmontracks(rme96, val); + return change; +} + +static u32 snd_rme96_convert_from_aes(struct snd_aes_iec958 *aes) +{ + u32 val = 0; + val |= (aes->status[0] & IEC958_AES0_PROFESSIONAL) ? RME96_WCR_PRO : 0; + val |= (aes->status[0] & IEC958_AES0_NONAUDIO) ? RME96_WCR_DOLBY : 0; + if (val & RME96_WCR_PRO) + val |= (aes->status[0] & IEC958_AES0_PRO_EMPHASIS_5015) ? RME96_WCR_EMP : 0; + else + val |= (aes->status[0] & IEC958_AES0_CON_EMPHASIS_5015) ? RME96_WCR_EMP : 0; + return val; +} + +static void snd_rme96_convert_to_aes(struct snd_aes_iec958 *aes, u32 val) +{ + aes->status[0] = ((val & RME96_WCR_PRO) ? IEC958_AES0_PROFESSIONAL : 0) | + ((val & RME96_WCR_DOLBY) ? IEC958_AES0_NONAUDIO : 0); + if (val & RME96_WCR_PRO) + aes->status[0] |= (val & RME96_WCR_EMP) ? IEC958_AES0_PRO_EMPHASIS_5015 : 0; + else + aes->status[0] |= (val & RME96_WCR_EMP) ? IEC958_AES0_CON_EMPHASIS_5015 : 0; +} + +static int snd_rme96_control_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_rme96_control_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + + snd_rme96_convert_to_aes(&ucontrol->value.iec958, rme96->wcreg_spdif); + return 0; +} + +static int snd_rme96_control_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + int change; + u32 val; + + val = snd_rme96_convert_from_aes(&ucontrol->value.iec958); + guard(spinlock_irq)(&rme96->lock); + change = val != rme96->wcreg_spdif; + rme96->wcreg_spdif = val; + return change; +} + +static int snd_rme96_control_spdif_stream_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_rme96_control_spdif_stream_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + + snd_rme96_convert_to_aes(&ucontrol->value.iec958, rme96->wcreg_spdif_stream); + return 0; +} + +static int snd_rme96_control_spdif_stream_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + int change; + u32 val; + + val = snd_rme96_convert_from_aes(&ucontrol->value.iec958); + guard(spinlock_irq)(&rme96->lock); + change = val != rme96->wcreg_spdif_stream; + rme96->wcreg_spdif_stream = val; + rme96->wcreg &= ~(RME96_WCR_PRO | RME96_WCR_DOLBY | RME96_WCR_EMP); + rme96->wcreg |= val; + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); + return change; +} + +static int snd_rme96_control_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_rme96_control_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = kcontrol->private_value; + return 0; +} + +static int +snd_rme96_dac_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = RME96_185X_MAX_OUT(rme96); + return 0; +} + +static int +snd_rme96_dac_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme96->lock); + u->value.integer.value[0] = rme96->vol[0]; + u->value.integer.value[1] = rme96->vol[1]; + + return 0; +} + +static int +snd_rme96_dac_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u) +{ + struct rme96 *rme96 = snd_kcontrol_chip(kcontrol); + int change = 0; + unsigned int vol, maxvol; + + + if (!RME96_HAS_ANALOG_OUT(rme96)) + return -EINVAL; + maxvol = RME96_185X_MAX_OUT(rme96); + guard(spinlock_irq)(&rme96->lock); + vol = u->value.integer.value[0]; + if (vol != rme96->vol[0] && vol <= maxvol) { + rme96->vol[0] = vol; + change = 1; + } + vol = u->value.integer.value[1]; + if (vol != rme96->vol[1] && vol <= maxvol) { + rme96->vol[1] = vol; + change = 1; + } + if (change) + snd_rme96_apply_dac_volume(rme96); + + return change; +} + +static const struct snd_kcontrol_new snd_rme96_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_rme96_control_spdif_info, + .get = snd_rme96_control_spdif_get, + .put = snd_rme96_control_spdif_put +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .info = snd_rme96_control_spdif_stream_info, + .get = snd_rme96_control_spdif_stream_get, + .put = snd_rme96_control_spdif_stream_put +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK), + .info = snd_rme96_control_spdif_mask_info, + .get = snd_rme96_control_spdif_mask_get, + .private_value = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_CON_EMPHASIS +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK), + .info = snd_rme96_control_spdif_mask_info, + .get = snd_rme96_control_spdif_mask_get, + .private_value = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_PRO_EMPHASIS +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Connector", + .info = snd_rme96_info_inputtype_control, + .get = snd_rme96_get_inputtype_control, + .put = snd_rme96_put_inputtype_control +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Loopback Input", + .info = snd_rme96_info_loopback_control, + .get = snd_rme96_get_loopback_control, + .put = snd_rme96_put_loopback_control +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sample Clock Source", + .info = snd_rme96_info_clockmode_control, + .get = snd_rme96_get_clockmode_control, + .put = snd_rme96_put_clockmode_control +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Tracks", + .info = snd_rme96_info_montracks_control, + .get = snd_rme96_get_montracks_control, + .put = snd_rme96_put_montracks_control +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Attenuation", + .info = snd_rme96_info_attenuation_control, + .get = snd_rme96_get_attenuation_control, + .put = snd_rme96_put_attenuation_control +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Playback Volume", + .info = snd_rme96_dac_volume_info, + .get = snd_rme96_dac_volume_get, + .put = snd_rme96_dac_volume_put +} +}; + +static int +snd_rme96_create_switches(struct snd_card *card, + struct rme96 *rme96) +{ + int idx, err; + struct snd_kcontrol *kctl; + + for (idx = 0; idx < 7; idx++) { + kctl = snd_ctl_new1(&snd_rme96_controls[idx], rme96); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + if (idx == 1) /* IEC958 (S/PDIF) Stream */ + rme96->spdif_ctl = kctl; + } + + if (RME96_HAS_ANALOG_OUT(rme96)) { + for (idx = 7; idx < 10; idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_rme96_controls[idx], rme96)); + if (err < 0) + return err; + } + } + + return 0; +} + +/* + * Card initialisation + */ + +static int rme96_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct rme96 *rme96 = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + /* save capture & playback pointers */ + rme96->playback_pointer = readl(rme96->iobase + RME96_IO_GET_PLAY_POS) + & RME96_RCR_AUDIO_ADDR_MASK; + rme96->capture_pointer = readl(rme96->iobase + RME96_IO_GET_REC_POS) + & RME96_RCR_AUDIO_ADDR_MASK; + + /* save playback and capture buffers */ + memcpy_fromio(rme96->playback_suspend_buffer, + rme96->iobase + RME96_IO_PLAY_BUFFER, RME96_BUFFER_SIZE); + memcpy_fromio(rme96->capture_suspend_buffer, + rme96->iobase + RME96_IO_REC_BUFFER, RME96_BUFFER_SIZE); + + /* disable the DAC */ + rme96->areg &= ~RME96_AR_DAC_EN; + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + return 0; +} + +static int rme96_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct rme96 *rme96 = card->private_data; + + /* reset playback and record buffer pointers */ + writel(0, rme96->iobase + RME96_IO_SET_PLAY_POS + + rme96->playback_pointer); + writel(0, rme96->iobase + RME96_IO_SET_REC_POS + + rme96->capture_pointer); + + /* restore playback and capture buffers */ + memcpy_toio(rme96->iobase + RME96_IO_PLAY_BUFFER, + rme96->playback_suspend_buffer, RME96_BUFFER_SIZE); + memcpy_toio(rme96->iobase + RME96_IO_REC_BUFFER, + rme96->capture_suspend_buffer, RME96_BUFFER_SIZE); + + /* reset the ADC */ + writel(rme96->areg | RME96_AR_PD2, + rme96->iobase + RME96_IO_ADDITIONAL_REG); + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + + /* reset and enable DAC, restore analog volume */ + snd_rme96_reset_dac(rme96); + rme96->areg |= RME96_AR_DAC_EN; + writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG); + if (RME96_HAS_ANALOG_OUT(rme96)) { + usleep_range(3000, 10000); + snd_rme96_apply_dac_volume(rme96); + } + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(rme96_pm, rme96_suspend, rme96_resume); + +static void snd_rme96_card_free(struct snd_card *card) +{ + snd_rme96_free(card->private_data); +} + +static int +__snd_rme96_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct rme96 *rme96; + struct snd_card *card; + int err; + u8 val; + + if (dev >= SNDRV_CARDS) { + return -ENODEV; + } + if (!enable[dev]) { + dev++; + return -ENOENT; + } + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*rme96), &card); + if (err < 0) + return err; + card->private_free = snd_rme96_card_free; + rme96 = card->private_data; + rme96->card = card; + rme96->pci = pci; + err = snd_rme96_create(rme96); + if (err) + return err; + + if (IS_ENABLED(CONFIG_PM_SLEEP)) { + rme96->playback_suspend_buffer = vmalloc(RME96_BUFFER_SIZE); + if (!rme96->playback_suspend_buffer) + return -ENOMEM; + rme96->capture_suspend_buffer = vmalloc(RME96_BUFFER_SIZE); + if (!rme96->capture_suspend_buffer) + return -ENOMEM; + } + + strscpy(card->driver, "Digi96"); + switch (rme96->pci->device) { + case PCI_DEVICE_ID_RME_DIGI96: + strscpy(card->shortname, "RME Digi96"); + break; + case PCI_DEVICE_ID_RME_DIGI96_8: + strscpy(card->shortname, "RME Digi96/8"); + break; + case PCI_DEVICE_ID_RME_DIGI96_8_PRO: + strscpy(card->shortname, "RME Digi96/8 PRO"); + break; + case PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST: + pci_read_config_byte(rme96->pci, 8, &val); + if (val < 5) { + strscpy(card->shortname, "RME Digi96/8 PAD"); + } else { + strscpy(card->shortname, "RME Digi96/8 PST"); + } + break; + } + sprintf(card->longname, "%s at 0x%lx, irq %d", card->shortname, + rme96->port, rme96->irq); + err = snd_card_register(card); + if (err) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_rme96_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_rme96_probe(pci, pci_id)); +} + +static struct pci_driver rme96_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_rme96_ids, + .probe = snd_rme96_probe, + .driver = { + .pm = &rme96_pm, + }, +}; + +module_pci_driver(rme96_driver); diff --git a/sound/pci/rme9652/Makefile b/sound/pci/rme9652/Makefile new file mode 100644 index 000000000..cc99ae892 --- /dev/null +++ b/sound/pci/rme9652/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-rme9652-y := rme9652.o +snd-hdsp-y := hdsp.o +snd-hdspm-y := hdspm.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_RME9652) += snd-rme9652.o +obj-$(CONFIG_SND_HDSP) += snd-hdsp.o +obj-$(CONFIG_SND_HDSPM) +=snd-hdspm.o diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c new file mode 100644 index 000000000..31cc2d91c --- /dev/null +++ b/sound/pci/rme9652/hdsp.c @@ -0,0 +1,5375 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for RME Hammerfall DSP audio interface(s) + * + * Copyright (c) 2002 Paul Davis + * Marcus Andersson + * Thomas Charbonnel + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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_PNP; /* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for RME Hammerfall DSP interface."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for RME Hammerfall DSP interface."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable/disable specific Hammerfall DSP soundcards."); +MODULE_AUTHOR("Paul Davis , Marcus Andersson, Thomas Charbonnel "); +MODULE_DESCRIPTION("RME Hammerfall DSP"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("rpm_firmware.bin"); +MODULE_FIRMWARE("multiface_firmware.bin"); +MODULE_FIRMWARE("multiface_firmware_rev11.bin"); +MODULE_FIRMWARE("digiface_firmware.bin"); +MODULE_FIRMWARE("digiface_firmware_rev11.bin"); + +#define HDSP_MAX_CHANNELS 26 +#define HDSP_MAX_DS_CHANNELS 14 +#define HDSP_MAX_QS_CHANNELS 8 +#define DIGIFACE_SS_CHANNELS 26 +#define DIGIFACE_DS_CHANNELS 14 +#define MULTIFACE_SS_CHANNELS 18 +#define MULTIFACE_DS_CHANNELS 14 +#define H9652_SS_CHANNELS 26 +#define H9652_DS_CHANNELS 14 +/* This does not include possible Analog Extension Boards + AEBs are detected at card initialization +*/ +#define H9632_SS_CHANNELS 12 +#define H9632_DS_CHANNELS 8 +#define H9632_QS_CHANNELS 4 +#define RPM_CHANNELS 6 + +/* Write registers. These are defined as byte-offsets from the iobase value. + */ +#define HDSP_resetPointer 0 +#define HDSP_freqReg 0 +#define HDSP_outputBufferAddress 32 +#define HDSP_inputBufferAddress 36 +#define HDSP_controlRegister 64 +#define HDSP_interruptConfirmation 96 +#define HDSP_outputEnable 128 +#define HDSP_control2Reg 256 +#define HDSP_midiDataOut0 352 +#define HDSP_midiDataOut1 356 +#define HDSP_fifoData 368 +#define HDSP_inputEnable 384 + +/* Read registers. These are defined as byte-offsets from the iobase value + */ + +#define HDSP_statusRegister 0 +#define HDSP_timecode 128 +#define HDSP_status2Register 192 +#define HDSP_midiDataIn0 360 +#define HDSP_midiDataIn1 364 +#define HDSP_midiStatusOut0 384 +#define HDSP_midiStatusOut1 388 +#define HDSP_midiStatusIn0 392 +#define HDSP_midiStatusIn1 396 +#define HDSP_fifoStatus 400 + +/* the meters are regular i/o-mapped registers, but offset + considerably from the rest. the peak registers are reset + when read; the least-significant 4 bits are full-scale counters; + the actual peak value is in the most-significant 24 bits. +*/ + +#define HDSP_playbackPeakLevel 4096 /* 26 * 32 bit values */ +#define HDSP_inputPeakLevel 4224 /* 26 * 32 bit values */ +#define HDSP_outputPeakLevel 4352 /* (26+2) * 32 bit values */ +#define HDSP_playbackRmsLevel 4612 /* 26 * 64 bit values */ +#define HDSP_inputRmsLevel 4868 /* 26 * 64 bit values */ + + +/* This is for H9652 cards + Peak values are read downward from the base + Rms values are read upward + There are rms values for the outputs too + 26*3 values are read in ss mode + 14*3 in ds mode, with no gap between values +*/ +#define HDSP_9652_peakBase 7164 +#define HDSP_9652_rmsBase 4096 + +/* c.f. the hdsp_9632_meters_t struct */ +#define HDSP_9632_metersBase 4096 + +#define HDSP_IO_EXTENT 7168 + +/* control2 register bits */ + +#define HDSP_TMS 0x01 +#define HDSP_TCK 0x02 +#define HDSP_TDI 0x04 +#define HDSP_JTAG 0x08 +#define HDSP_PWDN 0x10 +#define HDSP_PROGRAM 0x020 +#define HDSP_CONFIG_MODE_0 0x040 +#define HDSP_CONFIG_MODE_1 0x080 +#define HDSP_VERSION_BIT (0x100 | HDSP_S_LOAD) +#define HDSP_BIGENDIAN_MODE 0x200 +#define HDSP_RD_MULTIPLE 0x400 +#define HDSP_9652_ENABLE_MIXER 0x800 +#define HDSP_S200 0x800 +#define HDSP_S300 (0x100 | HDSP_S200) /* dummy, purpose of 0x100 unknown */ +#define HDSP_CYCLIC_MODE 0x1000 +#define HDSP_TDO 0x10000000 + +#define HDSP_S_PROGRAM (HDSP_CYCLIC_MODE|HDSP_PROGRAM|HDSP_CONFIG_MODE_0) +#define HDSP_S_LOAD (HDSP_CYCLIC_MODE|HDSP_PROGRAM|HDSP_CONFIG_MODE_1) + +/* Control Register bits */ + +#define HDSP_Start (1<<0) /* start engine */ +#define HDSP_Latency0 (1<<1) /* buffer size = 2^n where n is defined by Latency{2,1,0} */ +#define HDSP_Latency1 (1<<2) /* [ see above ] */ +#define HDSP_Latency2 (1<<3) /* [ see above ] */ +#define HDSP_ClockModeMaster (1<<4) /* 1=Master, 0=Slave/Autosync */ +#define HDSP_AudioInterruptEnable (1<<5) /* what do you think ? */ +#define HDSP_Frequency0 (1<<6) /* 0=44.1kHz/88.2kHz/176.4kHz 1=48kHz/96kHz/192kHz */ +#define HDSP_Frequency1 (1<<7) /* 0=32kHz/64kHz/128kHz */ +#define HDSP_DoubleSpeed (1<<8) /* 0=normal speed, 1=double speed */ +#define HDSP_SPDIFProfessional (1<<9) /* 0=consumer, 1=professional */ +#define HDSP_SPDIFEmphasis (1<<10) /* 0=none, 1=on */ +#define HDSP_SPDIFNonAudio (1<<11) /* 0=off, 1=on */ +#define HDSP_SPDIFOpticalOut (1<<12) /* 1=use 1st ADAT connector for SPDIF, 0=do not */ +#define HDSP_SyncRef2 (1<<13) +#define HDSP_SPDIFInputSelect0 (1<<14) +#define HDSP_SPDIFInputSelect1 (1<<15) +#define HDSP_SyncRef0 (1<<16) +#define HDSP_SyncRef1 (1<<17) +#define HDSP_AnalogExtensionBoard (1<<18) /* For H9632 cards */ +#define HDSP_XLRBreakoutCable (1<<20) /* For H9632 cards */ +#define HDSP_Midi0InterruptEnable (1<<22) +#define HDSP_Midi1InterruptEnable (1<<23) +#define HDSP_LineOut (1<<24) +#define HDSP_ADGain0 (1<<25) /* From here : H9632 specific */ +#define HDSP_ADGain1 (1<<26) +#define HDSP_DAGain0 (1<<27) +#define HDSP_DAGain1 (1<<28) +#define HDSP_PhoneGain0 (1<<29) +#define HDSP_PhoneGain1 (1<<30) +#define HDSP_QuadSpeed (1<<31) + +/* RPM uses some of the registers for special purposes */ +#define HDSP_RPM_Inp12 0x04A00 +#define HDSP_RPM_Inp12_Phon_6dB 0x00800 /* Dolby */ +#define HDSP_RPM_Inp12_Phon_0dB 0x00000 /* .. */ +#define HDSP_RPM_Inp12_Phon_n6dB 0x04000 /* inp_0 */ +#define HDSP_RPM_Inp12_Line_0dB 0x04200 /* Dolby+PRO */ +#define HDSP_RPM_Inp12_Line_n6dB 0x00200 /* PRO */ + +#define HDSP_RPM_Inp34 0x32000 +#define HDSP_RPM_Inp34_Phon_6dB 0x20000 /* SyncRef1 */ +#define HDSP_RPM_Inp34_Phon_0dB 0x00000 /* .. */ +#define HDSP_RPM_Inp34_Phon_n6dB 0x02000 /* SyncRef2 */ +#define HDSP_RPM_Inp34_Line_0dB 0x30000 /* SyncRef1+SyncRef0 */ +#define HDSP_RPM_Inp34_Line_n6dB 0x10000 /* SyncRef0 */ + +#define HDSP_RPM_Bypass 0x01000 + +#define HDSP_RPM_Disconnect 0x00001 + +#define HDSP_ADGainMask (HDSP_ADGain0|HDSP_ADGain1) +#define HDSP_ADGainMinus10dBV HDSP_ADGainMask +#define HDSP_ADGainPlus4dBu (HDSP_ADGain0) +#define HDSP_ADGainLowGain 0 + +#define HDSP_DAGainMask (HDSP_DAGain0|HDSP_DAGain1) +#define HDSP_DAGainHighGain HDSP_DAGainMask +#define HDSP_DAGainPlus4dBu (HDSP_DAGain0) +#define HDSP_DAGainMinus10dBV 0 + +#define HDSP_PhoneGainMask (HDSP_PhoneGain0|HDSP_PhoneGain1) +#define HDSP_PhoneGain0dB HDSP_PhoneGainMask +#define HDSP_PhoneGainMinus6dB (HDSP_PhoneGain0) +#define HDSP_PhoneGainMinus12dB 0 + +#define HDSP_LatencyMask (HDSP_Latency0|HDSP_Latency1|HDSP_Latency2) +#define HDSP_FrequencyMask (HDSP_Frequency0|HDSP_Frequency1|HDSP_DoubleSpeed|HDSP_QuadSpeed) + +#define HDSP_SPDIFInputMask (HDSP_SPDIFInputSelect0|HDSP_SPDIFInputSelect1) +#define HDSP_SPDIFInputADAT1 0 +#define HDSP_SPDIFInputCoaxial (HDSP_SPDIFInputSelect0) +#define HDSP_SPDIFInputCdrom (HDSP_SPDIFInputSelect1) +#define HDSP_SPDIFInputAES (HDSP_SPDIFInputSelect0|HDSP_SPDIFInputSelect1) + +#define HDSP_SyncRefMask (HDSP_SyncRef0|HDSP_SyncRef1|HDSP_SyncRef2) +#define HDSP_SyncRef_ADAT1 0 +#define HDSP_SyncRef_ADAT2 (HDSP_SyncRef0) +#define HDSP_SyncRef_ADAT3 (HDSP_SyncRef1) +#define HDSP_SyncRef_SPDIF (HDSP_SyncRef0|HDSP_SyncRef1) +#define HDSP_SyncRef_WORD (HDSP_SyncRef2) +#define HDSP_SyncRef_ADAT_SYNC (HDSP_SyncRef0|HDSP_SyncRef2) + +/* Sample Clock Sources */ + +#define HDSP_CLOCK_SOURCE_AUTOSYNC 0 +#define HDSP_CLOCK_SOURCE_INTERNAL_32KHZ 1 +#define HDSP_CLOCK_SOURCE_INTERNAL_44_1KHZ 2 +#define HDSP_CLOCK_SOURCE_INTERNAL_48KHZ 3 +#define HDSP_CLOCK_SOURCE_INTERNAL_64KHZ 4 +#define HDSP_CLOCK_SOURCE_INTERNAL_88_2KHZ 5 +#define HDSP_CLOCK_SOURCE_INTERNAL_96KHZ 6 +#define HDSP_CLOCK_SOURCE_INTERNAL_128KHZ 7 +#define HDSP_CLOCK_SOURCE_INTERNAL_176_4KHZ 8 +#define HDSP_CLOCK_SOURCE_INTERNAL_192KHZ 9 + +/* Preferred sync reference choices - used by "pref_sync_ref" control switch */ + +#define HDSP_SYNC_FROM_WORD 0 +#define HDSP_SYNC_FROM_SPDIF 1 +#define HDSP_SYNC_FROM_ADAT1 2 +#define HDSP_SYNC_FROM_ADAT_SYNC 3 +#define HDSP_SYNC_FROM_ADAT2 4 +#define HDSP_SYNC_FROM_ADAT3 5 + +/* SyncCheck status */ + +#define HDSP_SYNC_CHECK_NO_LOCK 0 +#define HDSP_SYNC_CHECK_LOCK 1 +#define HDSP_SYNC_CHECK_SYNC 2 + +/* AutoSync references - used by "autosync_ref" control switch */ + +#define HDSP_AUTOSYNC_FROM_WORD 0 +#define HDSP_AUTOSYNC_FROM_ADAT_SYNC 1 +#define HDSP_AUTOSYNC_FROM_SPDIF 2 +#define HDSP_AUTOSYNC_FROM_NONE 3 +#define HDSP_AUTOSYNC_FROM_ADAT1 4 +#define HDSP_AUTOSYNC_FROM_ADAT2 5 +#define HDSP_AUTOSYNC_FROM_ADAT3 6 + +/* Possible sources of S/PDIF input */ + +#define HDSP_SPDIFIN_OPTICAL 0 /* optical (ADAT1) */ +#define HDSP_SPDIFIN_COAXIAL 1 /* coaxial (RCA) */ +#define HDSP_SPDIFIN_INTERNAL 2 /* internal (CDROM) */ +#define HDSP_SPDIFIN_AES 3 /* xlr for H9632 (AES)*/ + +#define HDSP_Frequency32KHz HDSP_Frequency0 +#define HDSP_Frequency44_1KHz HDSP_Frequency1 +#define HDSP_Frequency48KHz (HDSP_Frequency1|HDSP_Frequency0) +#define HDSP_Frequency64KHz (HDSP_DoubleSpeed|HDSP_Frequency0) +#define HDSP_Frequency88_2KHz (HDSP_DoubleSpeed|HDSP_Frequency1) +#define HDSP_Frequency96KHz (HDSP_DoubleSpeed|HDSP_Frequency1|HDSP_Frequency0) +/* For H9632 cards */ +#define HDSP_Frequency128KHz (HDSP_QuadSpeed|HDSP_DoubleSpeed|HDSP_Frequency0) +#define HDSP_Frequency176_4KHz (HDSP_QuadSpeed|HDSP_DoubleSpeed|HDSP_Frequency1) +#define HDSP_Frequency192KHz (HDSP_QuadSpeed|HDSP_DoubleSpeed|HDSP_Frequency1|HDSP_Frequency0) +/* RME says n = 104857600000000, but in the windows MADI driver, I see: + return 104857600000000 / rate; // 100 MHz + return 110100480000000 / rate; // 105 MHz +*/ +#define DDS_NUMERATOR 104857600000000ULL /* = 2^20 * 10^8 */ + +#define hdsp_encode_latency(x) (((x)<<1) & HDSP_LatencyMask) +#define hdsp_decode_latency(x) (((x) & HDSP_LatencyMask)>>1) + +#define hdsp_encode_spdif_in(x) (((x)&0x3)<<14) +#define hdsp_decode_spdif_in(x) (((x)>>14)&0x3) + +/* Status Register bits */ + +#define HDSP_audioIRQPending (1<<0) +#define HDSP_Lock2 (1<<1) /* this is for Digiface and H9652 */ +#define HDSP_spdifFrequency3 HDSP_Lock2 /* this is for H9632 only */ +#define HDSP_Lock1 (1<<2) +#define HDSP_Lock0 (1<<3) +#define HDSP_SPDIFSync (1<<4) +#define HDSP_TimecodeLock (1<<5) +#define HDSP_BufferPositionMask 0x000FFC0 /* Bit 6..15 : h/w buffer pointer */ +#define HDSP_Sync2 (1<<16) +#define HDSP_Sync1 (1<<17) +#define HDSP_Sync0 (1<<18) +#define HDSP_DoubleSpeedStatus (1<<19) +#define HDSP_ConfigError (1<<20) +#define HDSP_DllError (1<<21) +#define HDSP_spdifFrequency0 (1<<22) +#define HDSP_spdifFrequency1 (1<<23) +#define HDSP_spdifFrequency2 (1<<24) +#define HDSP_SPDIFErrorFlag (1<<25) +#define HDSP_BufferID (1<<26) +#define HDSP_TimecodeSync (1<<27) +#define HDSP_AEBO (1<<28) /* H9632 specific Analog Extension Boards */ +#define HDSP_AEBI (1<<29) /* 0 = present, 1 = absent */ +#define HDSP_midi0IRQPending (1<<30) +#define HDSP_midi1IRQPending (1<<31) + +#define HDSP_spdifFrequencyMask (HDSP_spdifFrequency0|HDSP_spdifFrequency1|HDSP_spdifFrequency2) +#define HDSP_spdifFrequencyMask_9632 (HDSP_spdifFrequency0|\ + HDSP_spdifFrequency1|\ + HDSP_spdifFrequency2|\ + HDSP_spdifFrequency3) + +#define HDSP_spdifFrequency32KHz (HDSP_spdifFrequency0) +#define HDSP_spdifFrequency44_1KHz (HDSP_spdifFrequency1) +#define HDSP_spdifFrequency48KHz (HDSP_spdifFrequency0|HDSP_spdifFrequency1) + +#define HDSP_spdifFrequency64KHz (HDSP_spdifFrequency2) +#define HDSP_spdifFrequency88_2KHz (HDSP_spdifFrequency0|HDSP_spdifFrequency2) +#define HDSP_spdifFrequency96KHz (HDSP_spdifFrequency2|HDSP_spdifFrequency1) + +/* This is for H9632 cards */ +#define HDSP_spdifFrequency128KHz (HDSP_spdifFrequency0|\ + HDSP_spdifFrequency1|\ + HDSP_spdifFrequency2) +#define HDSP_spdifFrequency176_4KHz HDSP_spdifFrequency3 +#define HDSP_spdifFrequency192KHz (HDSP_spdifFrequency3|HDSP_spdifFrequency0) + +/* Status2 Register bits */ + +#define HDSP_version0 (1<<0) +#define HDSP_version1 (1<<1) +#define HDSP_version2 (1<<2) +#define HDSP_wc_lock (1<<3) +#define HDSP_wc_sync (1<<4) +#define HDSP_inp_freq0 (1<<5) +#define HDSP_inp_freq1 (1<<6) +#define HDSP_inp_freq2 (1<<7) +#define HDSP_SelSyncRef0 (1<<8) +#define HDSP_SelSyncRef1 (1<<9) +#define HDSP_SelSyncRef2 (1<<10) + +#define HDSP_wc_valid (HDSP_wc_lock|HDSP_wc_sync) + +#define HDSP_systemFrequencyMask (HDSP_inp_freq0|HDSP_inp_freq1|HDSP_inp_freq2) +#define HDSP_systemFrequency32 (HDSP_inp_freq0) +#define HDSP_systemFrequency44_1 (HDSP_inp_freq1) +#define HDSP_systemFrequency48 (HDSP_inp_freq0|HDSP_inp_freq1) +#define HDSP_systemFrequency64 (HDSP_inp_freq2) +#define HDSP_systemFrequency88_2 (HDSP_inp_freq0|HDSP_inp_freq2) +#define HDSP_systemFrequency96 (HDSP_inp_freq1|HDSP_inp_freq2) +/* FIXME : more values for 9632 cards ? */ + +#define HDSP_SelSyncRefMask (HDSP_SelSyncRef0|HDSP_SelSyncRef1|HDSP_SelSyncRef2) +#define HDSP_SelSyncRef_ADAT1 0 +#define HDSP_SelSyncRef_ADAT2 (HDSP_SelSyncRef0) +#define HDSP_SelSyncRef_ADAT3 (HDSP_SelSyncRef1) +#define HDSP_SelSyncRef_SPDIF (HDSP_SelSyncRef0|HDSP_SelSyncRef1) +#define HDSP_SelSyncRef_WORD (HDSP_SelSyncRef2) +#define HDSP_SelSyncRef_ADAT_SYNC (HDSP_SelSyncRef0|HDSP_SelSyncRef2) + +/* Card state flags */ + +#define HDSP_InitializationComplete (1<<0) +#define HDSP_FirmwareLoaded (1<<1) +#define HDSP_FirmwareCached (1<<2) + +/* FIFO wait times, defined in terms of 1/10ths of msecs */ + +#define HDSP_LONG_WAIT 5000 +#define HDSP_SHORT_WAIT 30 + +#define UNITY_GAIN 32768 +#define MINUS_INFINITY_GAIN 0 + +/* the size of a substream (1 mono data stream) */ + +#define HDSP_CHANNEL_BUFFER_SAMPLES (16*1024) +#define HDSP_CHANNEL_BUFFER_BYTES (4*HDSP_CHANNEL_BUFFER_SAMPLES) + +/* the size of the area we need to allocate for DMA transfers. the + size is the same regardless of the number of channels - the + Multiface still uses the same memory area. + + Note that we allocate 1 more channel than is apparently needed + because the h/w seems to write 1 byte beyond the end of the last + page. Sigh. +*/ + +#define HDSP_DMA_AREA_BYTES ((HDSP_MAX_CHANNELS+1) * HDSP_CHANNEL_BUFFER_BYTES) +#define HDSP_DMA_AREA_KILOBYTES (HDSP_DMA_AREA_BYTES/1024) + +#define HDSP_FIRMWARE_SIZE (24413 * 4) + +struct hdsp_9632_meters { + u32 input_peak[16]; + u32 playback_peak[16]; + u32 output_peak[16]; + u32 xxx_peak[16]; + u32 padding[64]; + u32 input_rms_low[16]; + u32 playback_rms_low[16]; + u32 output_rms_low[16]; + u32 xxx_rms_low[16]; + u32 input_rms_high[16]; + u32 playback_rms_high[16]; + u32 output_rms_high[16]; + u32 xxx_rms_high[16]; +}; + +struct hdsp_midi { + struct hdsp *hdsp; + int id; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *input; + struct snd_rawmidi_substream *output; + signed char istimer; /* timer in use */ + struct timer_list timer; + spinlock_t lock; + int pending; +}; + +struct hdsp { + spinlock_t lock; + struct snd_pcm_substream *capture_substream; + struct snd_pcm_substream *playback_substream; + struct hdsp_midi midi[2]; + struct work_struct midi_work; + int use_midi_work; + int precise_ptr; + u32 control_register; /* cached value */ + u32 control2_register; /* cached value */ + u32 creg_spdif; + u32 creg_spdif_stream; + int clock_source_locked; + char *card_name; /* digiface/multiface/rpm */ + enum HDSP_IO_Type io_type; /* ditto, but for code use */ + unsigned short firmware_rev; + unsigned short state; /* stores state bits */ + const struct firmware *firmware; + u32 *fw_uploaded; + size_t period_bytes; /* guess what this is */ + unsigned char max_channels; + unsigned char qs_in_channels; /* quad speed mode for H9632 */ + unsigned char ds_in_channels; + unsigned char ss_in_channels; /* different for multiface/digiface */ + unsigned char qs_out_channels; + unsigned char ds_out_channels; + unsigned char ss_out_channels; + u32 io_loopback; /* output loopback channel states*/ + + /* DMA buffers; those are copied instances from the original snd_dma_buf + * objects (which are managed via devres) for the address alignments + */ + struct snd_dma_buffer capture_dma_buf; + struct snd_dma_buffer playback_dma_buf; + unsigned char *capture_buffer; /* suitably aligned address */ + unsigned char *playback_buffer; /* suitably aligned address */ + + pid_t capture_pid; + pid_t playback_pid; + int running; + int system_sample_rate; + const signed char *channel_map; + int dev; + int irq; + unsigned long port; + void __iomem *iobase; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_hwdep *hwdep; + struct pci_dev *pci; + struct snd_kcontrol *spdif_ctl; + unsigned short mixer_matrix[HDSP_MATRIX_MIXER_SIZE]; + unsigned int dds_value; /* last value written to freq register */ +}; + +/* These tables map the ALSA channels 1..N to the channels that we + need to use in order to find the relevant channel buffer. RME + refer to this kind of mapping as between "the ADAT channel and + the DMA channel." We index it using the logical audio channel, + and the value is the DMA channel (i.e. channel buffer number) + where the data for that channel can be read/written from/to. +*/ + +static const signed char channel_map_df_ss[HDSP_MAX_CHANNELS] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25 +}; + +static const char channel_map_mf_ss[HDSP_MAX_CHANNELS] = { /* Multiface */ + /* Analog */ + 0, 1, 2, 3, 4, 5, 6, 7, + /* ADAT 2 */ + 16, 17, 18, 19, 20, 21, 22, 23, + /* SPDIF */ + 24, 25, + -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const signed char channel_map_ds[HDSP_MAX_CHANNELS] = { + /* ADAT channels are remapped */ + 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, + /* channels 12 and 13 are S/PDIF */ + 24, 25, + /* others don't exist */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const signed char channel_map_H9632_ss[HDSP_MAX_CHANNELS] = { + /* ADAT channels */ + 0, 1, 2, 3, 4, 5, 6, 7, + /* SPDIF */ + 8, 9, + /* Analog */ + 10, 11, + /* AO4S-192 and AI4S-192 extension boards */ + 12, 13, 14, 15, + /* others don't exist */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1 +}; + +static const signed char channel_map_H9632_ds[HDSP_MAX_CHANNELS] = { + /* ADAT */ + 1, 3, 5, 7, + /* SPDIF */ + 8, 9, + /* Analog */ + 10, 11, + /* AO4S-192 and AI4S-192 extension boards */ + 12, 13, 14, 15, + /* others don't exist */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1 +}; + +static const signed char channel_map_H9632_qs[HDSP_MAX_CHANNELS] = { + /* ADAT is disabled in this mode */ + /* SPDIF */ + 8, 9, + /* Analog */ + 10, 11, + /* AO4S-192 and AI4S-192 extension boards */ + 12, 13, 14, 15, + /* others don't exist */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1 +}; + +static struct snd_dma_buffer * +snd_hammerfall_get_buffer(struct pci_dev *pci, size_t size) +{ + return snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, size); +} + +static const struct pci_device_id snd_hdsp_ids[] = { + { + .vendor = PCI_VENDOR_ID_XILINX, + .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, /* RME Hammerfall-DSP */ + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, snd_hdsp_ids); + +/* prototypes */ +static int snd_hdsp_create_alsa_devices(struct snd_card *card, struct hdsp *hdsp); +static int snd_hdsp_create_pcm(struct snd_card *card, struct hdsp *hdsp); +static int snd_hdsp_enable_io (struct hdsp *hdsp); +static void snd_hdsp_initialize_midi_flush (struct hdsp *hdsp); +static void snd_hdsp_initialize_channels (struct hdsp *hdsp); +static int hdsp_fifo_wait(struct hdsp *hdsp, int count, int timeout); +static int hdsp_autosync_ref(struct hdsp *hdsp); +static int snd_hdsp_set_defaults(struct hdsp *hdsp); +static void snd_hdsp_9652_enable_mixer (struct hdsp *hdsp); + +static int hdsp_playback_to_output_key (struct hdsp *hdsp, int in, int out) +{ + switch (hdsp->io_type) { + case Multiface: + case Digiface: + case RPM: + default: + if (hdsp->firmware_rev == 0xa) + return (64 * out) + (32 + (in)); + else + return (52 * out) + (26 + (in)); + case H9632: + return (32 * out) + (16 + (in)); + case H9652: + return (52 * out) + (26 + (in)); + } +} + +static int hdsp_input_to_output_key (struct hdsp *hdsp, int in, int out) +{ + switch (hdsp->io_type) { + case Multiface: + case Digiface: + case RPM: + default: + if (hdsp->firmware_rev == 0xa) + return (64 * out) + in; + else + return (52 * out) + in; + case H9632: + return (32 * out) + in; + case H9652: + return (52 * out) + in; + } +} + +static void hdsp_write(struct hdsp *hdsp, int reg, int val) +{ + writel(val, hdsp->iobase + reg); +} + +static unsigned int hdsp_read(struct hdsp *hdsp, int reg) +{ + return readl (hdsp->iobase + reg); +} + +static int hdsp_check_for_iobox (struct hdsp *hdsp) +{ + int i; + + if (hdsp->io_type == H9652 || hdsp->io_type == H9632) return 0; + for (i = 0; i < 500; i++) { + if (0 == (hdsp_read(hdsp, HDSP_statusRegister) & + HDSP_ConfigError)) { + if (i) { + dev_dbg(hdsp->card->dev, + "IO box found after %d ms\n", + (20 * i)); + } + return 0; + } + msleep(20); + } + dev_err(hdsp->card->dev, "no IO box connected!\n"); + hdsp->state &= ~HDSP_FirmwareLoaded; + return -EIO; +} + +static int hdsp_wait_for_iobox(struct hdsp *hdsp, unsigned int loops, + unsigned int delay) +{ + unsigned int i; + + if (hdsp->io_type == H9652 || hdsp->io_type == H9632) + return 0; + + for (i = 0; i != loops; ++i) { + if (hdsp_read(hdsp, HDSP_statusRegister) & HDSP_ConfigError) + msleep(delay); + else { + dev_dbg(hdsp->card->dev, "iobox found after %ums!\n", + i * delay); + return 0; + } + } + + dev_info(hdsp->card->dev, "no IO box connected!\n"); + hdsp->state &= ~HDSP_FirmwareLoaded; + return -EIO; +} + +static int snd_hdsp_load_firmware_from_cache(struct hdsp *hdsp) { + + int i; + const u32 *cache; + + if (hdsp->fw_uploaded) + cache = hdsp->fw_uploaded; + else { + if (!hdsp->firmware) + return -ENODEV; + cache = (u32 *)hdsp->firmware->data; + if (!cache) + return -ENODEV; + } + + if ((hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DllError) != 0) { + + dev_info(hdsp->card->dev, "loading firmware\n"); + + hdsp_write (hdsp, HDSP_control2Reg, HDSP_S_PROGRAM); + hdsp_write (hdsp, HDSP_fifoData, 0); + + if (hdsp_fifo_wait (hdsp, 0, HDSP_LONG_WAIT)) { + dev_info(hdsp->card->dev, + "timeout waiting for download preparation\n"); + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S200); + return -EIO; + } + + hdsp_write (hdsp, HDSP_control2Reg, HDSP_S_LOAD); + + for (i = 0; i < HDSP_FIRMWARE_SIZE / 4; ++i) { + hdsp_write(hdsp, HDSP_fifoData, cache[i]); + if (hdsp_fifo_wait (hdsp, 127, HDSP_LONG_WAIT)) { + dev_info(hdsp->card->dev, + "timeout during firmware loading\n"); + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S200); + return -EIO; + } + } + + hdsp_fifo_wait(hdsp, 3, HDSP_LONG_WAIT); + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S200); + + ssleep(3); +#ifdef SNDRV_BIG_ENDIAN + hdsp->control2_register = HDSP_BIGENDIAN_MODE; +#else + hdsp->control2_register = 0; +#endif + hdsp_write (hdsp, HDSP_control2Reg, hdsp->control2_register); + dev_info(hdsp->card->dev, "finished firmware loading\n"); + + } + if (hdsp->state & HDSP_InitializationComplete) { + dev_info(hdsp->card->dev, + "firmware loaded from cache, restoring defaults\n"); + guard(spinlock_irqsave)(&hdsp->lock); + snd_hdsp_set_defaults(hdsp); + } + + hdsp->state |= HDSP_FirmwareLoaded; + + return 0; +} + +static int hdsp_get_iobox_version (struct hdsp *hdsp) +{ + if ((hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DllError) != 0) { + + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S_LOAD); + hdsp_write(hdsp, HDSP_fifoData, 0); + + if (hdsp_fifo_wait(hdsp, 0, HDSP_SHORT_WAIT) < 0) { + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S300); + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S_LOAD); + } + + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S200 | HDSP_PROGRAM); + hdsp_write (hdsp, HDSP_fifoData, 0); + if (hdsp_fifo_wait(hdsp, 0, HDSP_SHORT_WAIT) < 0) + goto set_multi; + + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S_LOAD); + hdsp_write(hdsp, HDSP_fifoData, 0); + if (hdsp_fifo_wait(hdsp, 0, HDSP_SHORT_WAIT) == 0) { + hdsp->io_type = Digiface; + dev_info(hdsp->card->dev, "Digiface found\n"); + return 0; + } + + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S300); + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S_LOAD); + hdsp_write(hdsp, HDSP_fifoData, 0); + if (hdsp_fifo_wait(hdsp, 0, HDSP_SHORT_WAIT) == 0) + goto set_multi; + + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S300); + hdsp_write(hdsp, HDSP_control2Reg, HDSP_S_LOAD); + hdsp_write(hdsp, HDSP_fifoData, 0); + if (hdsp_fifo_wait(hdsp, 0, HDSP_SHORT_WAIT) < 0) + goto set_multi; + + hdsp->io_type = RPM; + dev_info(hdsp->card->dev, "RPM found\n"); + return 0; + } else { + /* firmware was already loaded, get iobox type */ + if (hdsp_read(hdsp, HDSP_status2Register) & HDSP_version2) + hdsp->io_type = RPM; + else if (hdsp_read(hdsp, HDSP_status2Register) & HDSP_version1) + hdsp->io_type = Multiface; + else + hdsp->io_type = Digiface; + } + return 0; + +set_multi: + hdsp->io_type = Multiface; + dev_info(hdsp->card->dev, "Multiface found\n"); + return 0; +} + + +static int hdsp_request_fw_loader(struct hdsp *hdsp); + +static int hdsp_check_for_firmware (struct hdsp *hdsp, int load_on_demand) +{ + if (hdsp->io_type == H9652 || hdsp->io_type == H9632) + return 0; + if ((hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DllError) != 0) { + hdsp->state &= ~HDSP_FirmwareLoaded; + if (! load_on_demand) + return -EIO; + dev_err(hdsp->card->dev, "firmware not present.\n"); + /* try to load firmware */ + if (! (hdsp->state & HDSP_FirmwareCached)) { + if (! hdsp_request_fw_loader(hdsp)) + return 0; + dev_err(hdsp->card->dev, + "No firmware loaded nor cached, please upload firmware.\n"); + return -EIO; + } + if (snd_hdsp_load_firmware_from_cache(hdsp) != 0) { + dev_err(hdsp->card->dev, + "Firmware loading from cache failed, please upload manually.\n"); + return -EIO; + } + } + return 0; +} + + +static int hdsp_fifo_wait(struct hdsp *hdsp, int count, int timeout) +{ + int i; + + /* the fifoStatus registers reports on how many words + are available in the command FIFO. + */ + + for (i = 0; i < timeout; i++) { + + if ((int)(hdsp_read (hdsp, HDSP_fifoStatus) & 0xff) <= count) + return 0; + + /* not very friendly, but we only do this during a firmware + load and changing the mixer, so we just put up with it. + */ + + udelay (100); + } + + dev_warn(hdsp->card->dev, + "wait for FIFO status <= %d failed after %d iterations\n", + count, timeout); + return -1; +} + +static int hdsp_read_gain (struct hdsp *hdsp, unsigned int addr) +{ + if (addr >= HDSP_MATRIX_MIXER_SIZE) + return 0; + + return hdsp->mixer_matrix[addr]; +} + +static int hdsp_write_gain(struct hdsp *hdsp, unsigned int addr, unsigned short data) +{ + unsigned int ad; + + if (addr >= HDSP_MATRIX_MIXER_SIZE) + return -1; + + if (hdsp->io_type == H9652 || hdsp->io_type == H9632) { + + /* from martin bjornsen: + + "You can only write dwords to the + mixer memory which contain two + mixer values in the low and high + word. So if you want to change + value 0 you have to read value 1 + from the cache and write both to + the first dword in the mixer + memory." + */ + + if (hdsp->io_type == H9632 && addr >= 512) + return 0; + + if (hdsp->io_type == H9652 && addr >= 1352) + return 0; + + hdsp->mixer_matrix[addr] = data; + + + /* `addr' addresses a 16-bit wide address, but + the address space accessed via hdsp_write + uses byte offsets. put another way, addr + varies from 0 to 1351, but to access the + corresponding memory location, we need + to access 0 to 2703 ... + */ + ad = addr/2; + + hdsp_write (hdsp, 4096 + (ad*4), + (hdsp->mixer_matrix[(addr&0x7fe)+1] << 16) + + hdsp->mixer_matrix[addr&0x7fe]); + + return 0; + + } else { + + ad = (addr << 16) + data; + + if (hdsp_fifo_wait(hdsp, 127, HDSP_LONG_WAIT)) + return -1; + + hdsp_write (hdsp, HDSP_fifoData, ad); + hdsp->mixer_matrix[addr] = data; + + } + + return 0; +} + +static int snd_hdsp_use_is_exclusive(struct hdsp *hdsp) +{ + int ret = 1; + + guard(spinlock_irqsave)(&hdsp->lock); + if ((hdsp->playback_pid != hdsp->capture_pid) && + (hdsp->playback_pid >= 0) && (hdsp->capture_pid >= 0)) + ret = 0; + return ret; +} + +static int hdsp_spdif_sample_rate(struct hdsp *hdsp) +{ + unsigned int status = hdsp_read(hdsp, HDSP_statusRegister); + unsigned int rate_bits = (status & HDSP_spdifFrequencyMask); + + /* For the 9632, the mask is different */ + if (hdsp->io_type == H9632) + rate_bits = (status & HDSP_spdifFrequencyMask_9632); + + if (status & HDSP_SPDIFErrorFlag) + return 0; + + switch (rate_bits) { + case HDSP_spdifFrequency32KHz: return 32000; + case HDSP_spdifFrequency44_1KHz: return 44100; + case HDSP_spdifFrequency48KHz: return 48000; + case HDSP_spdifFrequency64KHz: return 64000; + case HDSP_spdifFrequency88_2KHz: return 88200; + case HDSP_spdifFrequency96KHz: return 96000; + case HDSP_spdifFrequency128KHz: + if (hdsp->io_type == H9632) return 128000; + break; + case HDSP_spdifFrequency176_4KHz: + if (hdsp->io_type == H9632) return 176400; + break; + case HDSP_spdifFrequency192KHz: + if (hdsp->io_type == H9632) return 192000; + break; + default: + break; + } + dev_warn(hdsp->card->dev, + "unknown spdif frequency status; bits = 0x%x, status = 0x%x\n", + rate_bits, status); + return 0; +} + +static int hdsp_external_sample_rate(struct hdsp *hdsp) +{ + unsigned int status2 = hdsp_read(hdsp, HDSP_status2Register); + unsigned int rate_bits = status2 & HDSP_systemFrequencyMask; + + /* For the 9632 card, there seems to be no bit for indicating external + * sample rate greater than 96kHz. The card reports the corresponding + * single speed. So the best means seems to get spdif rate when + * autosync reference is spdif */ + if (hdsp->io_type == H9632 && + hdsp_autosync_ref(hdsp) == HDSP_AUTOSYNC_FROM_SPDIF) + return hdsp_spdif_sample_rate(hdsp); + + switch (rate_bits) { + case HDSP_systemFrequency32: return 32000; + case HDSP_systemFrequency44_1: return 44100; + case HDSP_systemFrequency48: return 48000; + case HDSP_systemFrequency64: return 64000; + case HDSP_systemFrequency88_2: return 88200; + case HDSP_systemFrequency96: return 96000; + default: + return 0; + } +} + +static void hdsp_compute_period_size(struct hdsp *hdsp) +{ + hdsp->period_bytes = 1 << ((hdsp_decode_latency(hdsp->control_register) + 8)); +} + +static snd_pcm_uframes_t hdsp_hw_pointer(struct hdsp *hdsp) +{ + int position; + + position = hdsp_read(hdsp, HDSP_statusRegister); + + if (!hdsp->precise_ptr) + return (position & HDSP_BufferID) ? (hdsp->period_bytes / 4) : 0; + + position &= HDSP_BufferPositionMask; + position /= 4; + position &= (hdsp->period_bytes/2) - 1; + return position; +} + +static void hdsp_reset_hw_pointer(struct hdsp *hdsp) +{ + hdsp_write (hdsp, HDSP_resetPointer, 0); + if (hdsp->io_type == H9632 && hdsp->firmware_rev >= 152) + /* HDSP_resetPointer = HDSP_freqReg, which is strange and + * requires (?) to write again DDS value after a reset pointer + * (at least, it works like this) */ + hdsp_write (hdsp, HDSP_freqReg, hdsp->dds_value); +} + +static void hdsp_start_audio(struct hdsp *s) +{ + s->control_register |= (HDSP_AudioInterruptEnable | HDSP_Start); + hdsp_write(s, HDSP_controlRegister, s->control_register); +} + +static void hdsp_stop_audio(struct hdsp *s) +{ + s->control_register &= ~(HDSP_Start | HDSP_AudioInterruptEnable); + hdsp_write(s, HDSP_controlRegister, s->control_register); +} + +static void hdsp_silence_playback(struct hdsp *hdsp) +{ + memset(hdsp->playback_buffer, 0, HDSP_DMA_AREA_BYTES); +} + +static int hdsp_set_interrupt_interval(struct hdsp *s, unsigned int frames) +{ + int n; + + frames >>= 7; + n = 0; + while (frames) { + n++; + frames >>= 1; + } + + s->control_register &= ~HDSP_LatencyMask; + s->control_register |= hdsp_encode_latency(n); + + hdsp_write(s, HDSP_controlRegister, s->control_register); + + hdsp_compute_period_size(s); + + return 0; +} + +static void hdsp_set_dds_value(struct hdsp *hdsp, int rate) +{ + u64 n; + + if (rate >= 112000) + rate /= 4; + else if (rate >= 56000) + rate /= 2; + + n = DDS_NUMERATOR; + n = div_u64(n, rate); + /* n should be less than 2^32 for being written to FREQ register */ + snd_BUG_ON(n >> 32); + /* HDSP_freqReg and HDSP_resetPointer are the same, so keep the DDS + value to write it after a reset */ + hdsp->dds_value = n; + hdsp_write(hdsp, HDSP_freqReg, hdsp->dds_value); +} + +static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally) +{ + int reject_if_open = 0; + int current_rate; + int rate_bits; + + /* ASSUMPTION: hdsp->lock is either held, or + there is no need for it (e.g. during module + initialization). + */ + + if (!(hdsp->control_register & HDSP_ClockModeMaster)) { + if (called_internally) { + /* request from ctl or card initialization */ + dev_err(hdsp->card->dev, + "device is not running as a clock master: cannot set sample rate.\n"); + return -1; + } else { + /* hw_param request while in AutoSync mode */ + int external_freq = hdsp_external_sample_rate(hdsp); + int spdif_freq = hdsp_spdif_sample_rate(hdsp); + + if ((spdif_freq == external_freq*2) && (hdsp_autosync_ref(hdsp) >= HDSP_AUTOSYNC_FROM_ADAT1)) + dev_info(hdsp->card->dev, + "Detected ADAT in double speed mode\n"); + else if (hdsp->io_type == H9632 && (spdif_freq == external_freq*4) && (hdsp_autosync_ref(hdsp) >= HDSP_AUTOSYNC_FROM_ADAT1)) + dev_info(hdsp->card->dev, + "Detected ADAT in quad speed mode\n"); + else if (rate != external_freq) { + dev_info(hdsp->card->dev, + "No AutoSync source for requested rate\n"); + return -1; + } + } + } + + current_rate = hdsp->system_sample_rate; + + /* Changing from a "single speed" to a "double speed" rate is + not allowed if any substreams are open. This is because + such a change causes a shift in the location of + the DMA buffers and a reduction in the number of available + buffers. + + Note that a similar but essentially insoluble problem + exists for externally-driven rate changes. All we can do + is to flag rate changes in the read/write routines. */ + + if (rate > 96000 && hdsp->io_type != H9632) + return -EINVAL; + + switch (rate) { + case 32000: + if (current_rate > 48000) + reject_if_open = 1; + rate_bits = HDSP_Frequency32KHz; + break; + case 44100: + if (current_rate > 48000) + reject_if_open = 1; + rate_bits = HDSP_Frequency44_1KHz; + break; + case 48000: + if (current_rate > 48000) + reject_if_open = 1; + rate_bits = HDSP_Frequency48KHz; + break; + case 64000: + if (current_rate <= 48000 || current_rate > 96000) + reject_if_open = 1; + rate_bits = HDSP_Frequency64KHz; + break; + case 88200: + if (current_rate <= 48000 || current_rate > 96000) + reject_if_open = 1; + rate_bits = HDSP_Frequency88_2KHz; + break; + case 96000: + if (current_rate <= 48000 || current_rate > 96000) + reject_if_open = 1; + rate_bits = HDSP_Frequency96KHz; + break; + case 128000: + if (current_rate < 128000) + reject_if_open = 1; + rate_bits = HDSP_Frequency128KHz; + break; + case 176400: + if (current_rate < 128000) + reject_if_open = 1; + rate_bits = HDSP_Frequency176_4KHz; + break; + case 192000: + if (current_rate < 128000) + reject_if_open = 1; + rate_bits = HDSP_Frequency192KHz; + break; + default: + return -EINVAL; + } + + if (reject_if_open && (hdsp->capture_pid >= 0 || hdsp->playback_pid >= 0)) { + dev_warn(hdsp->card->dev, + "cannot change speed mode (capture PID = %d, playback PID = %d)\n", + hdsp->capture_pid, + hdsp->playback_pid); + return -EBUSY; + } + + hdsp->control_register &= ~HDSP_FrequencyMask; + hdsp->control_register |= rate_bits; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + + /* For HDSP9632 rev 152, need to set DDS value in FREQ register */ + if (hdsp->io_type == H9632 && hdsp->firmware_rev >= 152) + hdsp_set_dds_value(hdsp, rate); + + if (rate >= 128000) { + hdsp->channel_map = channel_map_H9632_qs; + } else if (rate > 48000) { + if (hdsp->io_type == H9632) + hdsp->channel_map = channel_map_H9632_ds; + else + hdsp->channel_map = channel_map_ds; + } else { + switch (hdsp->io_type) { + case RPM: + case Multiface: + hdsp->channel_map = channel_map_mf_ss; + break; + case Digiface: + case H9652: + hdsp->channel_map = channel_map_df_ss; + break; + case H9632: + hdsp->channel_map = channel_map_H9632_ss; + break; + default: + /* should never happen */ + break; + } + } + + hdsp->system_sample_rate = rate; + + return 0; +} + +/*---------------------------------------------------------------------------- + MIDI + ----------------------------------------------------------------------------*/ + +static unsigned char snd_hdsp_midi_read_byte (struct hdsp *hdsp, int id) +{ + /* the hardware already does the relevant bit-mask with 0xff */ + if (id) + return hdsp_read(hdsp, HDSP_midiDataIn1); + else + return hdsp_read(hdsp, HDSP_midiDataIn0); +} + +static void snd_hdsp_midi_write_byte (struct hdsp *hdsp, int id, int val) +{ + /* the hardware already does the relevant bit-mask with 0xff */ + if (id) + hdsp_write(hdsp, HDSP_midiDataOut1, val); + else + hdsp_write(hdsp, HDSP_midiDataOut0, val); +} + +static int snd_hdsp_midi_input_available (struct hdsp *hdsp, int id) +{ + if (id) + return (hdsp_read(hdsp, HDSP_midiStatusIn1) & 0xff); + else + return (hdsp_read(hdsp, HDSP_midiStatusIn0) & 0xff); +} + +static int snd_hdsp_midi_output_possible (struct hdsp *hdsp, int id) +{ + int fifo_bytes_used; + + if (id) + fifo_bytes_used = hdsp_read(hdsp, HDSP_midiStatusOut1) & 0xff; + else + fifo_bytes_used = hdsp_read(hdsp, HDSP_midiStatusOut0) & 0xff; + + if (fifo_bytes_used < 128) + return 128 - fifo_bytes_used; + else + return 0; +} + +static void snd_hdsp_flush_midi_input (struct hdsp *hdsp, int id) +{ + int count = 256; + + while (snd_hdsp_midi_input_available(hdsp, id) && --count) + snd_hdsp_midi_read_byte(hdsp, id); +} + +static int snd_hdsp_midi_output_write (struct hdsp_midi *hmidi) +{ + int n_pending; + int to_write; + int i; + unsigned char buf[128]; + + /* Output is not interrupt driven */ + + guard(spinlock_irqsave)(&hmidi->lock); + if (hmidi->output) { + if (!snd_rawmidi_transmit_empty (hmidi->output)) { + n_pending = snd_hdsp_midi_output_possible(hmidi->hdsp, hmidi->id); + if (n_pending > 0) { + if (n_pending > (int)sizeof (buf)) + n_pending = sizeof (buf); + + to_write = snd_rawmidi_transmit(hmidi->output, buf, n_pending); + if (to_write > 0) { + for (i = 0; i < to_write; ++i) + snd_hdsp_midi_write_byte (hmidi->hdsp, hmidi->id, buf[i]); + } + } + } + } + return 0; +} + +static int snd_hdsp_midi_input_read (struct hdsp_midi *hmidi) +{ + unsigned char buf[128]; /* this buffer is designed to match the MIDI input FIFO size */ + int n_pending; + int i; + + scoped_guard(spinlock_irqsave, &hmidi->lock) { + n_pending = snd_hdsp_midi_input_available(hmidi->hdsp, hmidi->id); + if (n_pending > 0) { + if (hmidi->input) { + if (n_pending > (int)sizeof(buf)) + n_pending = sizeof(buf); + for (i = 0; i < n_pending; ++i) + buf[i] = snd_hdsp_midi_read_byte(hmidi->hdsp, hmidi->id); + if (n_pending) + snd_rawmidi_receive(hmidi->input, buf, n_pending); + } else { + /* flush the MIDI input FIFO */ + while (--n_pending) + snd_hdsp_midi_read_byte(hmidi->hdsp, hmidi->id); + } + } + hmidi->pending = 0; + if (hmidi->id) + hmidi->hdsp->control_register |= HDSP_Midi1InterruptEnable; + else + hmidi->hdsp->control_register |= HDSP_Midi0InterruptEnable; + hdsp_write(hmidi->hdsp, HDSP_controlRegister, hmidi->hdsp->control_register); + } + return snd_hdsp_midi_output_write (hmidi); +} + +static void snd_hdsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct hdsp *hdsp; + struct hdsp_midi *hmidi; + u32 ie; + + hmidi = (struct hdsp_midi *) substream->rmidi->private_data; + hdsp = hmidi->hdsp; + ie = hmidi->id ? HDSP_Midi1InterruptEnable : HDSP_Midi0InterruptEnable; + guard(spinlock_irqsave)(&hdsp->lock); + if (up) { + if (!(hdsp->control_register & ie)) { + snd_hdsp_flush_midi_input (hdsp, hmidi->id); + hdsp->control_register |= ie; + } + } else { + hdsp->control_register &= ~ie; + } + + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); +} + +static void snd_hdsp_midi_output_timer(struct timer_list *t) +{ + struct hdsp_midi *hmidi = timer_container_of(hmidi, t, timer); + + snd_hdsp_midi_output_write(hmidi); + guard(spinlock_irqsave)(&hmidi->lock); + + /* this does not bump hmidi->istimer, because the + kernel automatically removed the timer when it + expired, and we are now adding it back, thus + leaving istimer wherever it was set before. + */ + + if (hmidi->istimer) + mod_timer(&hmidi->timer, 1 + jiffies); +} + +static void snd_hdsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct hdsp_midi *hmidi; + + hmidi = (struct hdsp_midi *) substream->rmidi->private_data; + scoped_guard(spinlock_irqsave, &hmidi->lock) { + if (up) { + if (!hmidi->istimer) { + timer_setup(&hmidi->timer, snd_hdsp_midi_output_timer, + 0); + mod_timer(&hmidi->timer, 1 + jiffies); + hmidi->istimer++; + } + } else { + if (hmidi->istimer && --hmidi->istimer <= 0) + timer_delete(&hmidi->timer); + } + } + if (up) + snd_hdsp_midi_output_write(hmidi); +} + +static int snd_hdsp_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct hdsp_midi *hmidi; + + hmidi = (struct hdsp_midi *) substream->rmidi->private_data; + guard(spinlock_irq)(&hmidi->lock); + snd_hdsp_flush_midi_input (hmidi->hdsp, hmidi->id); + hmidi->input = substream; + + return 0; +} + +static int snd_hdsp_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct hdsp_midi *hmidi; + + hmidi = (struct hdsp_midi *) substream->rmidi->private_data; + guard(spinlock_irq)(&hmidi->lock); + hmidi->output = substream; + + return 0; +} + +static int snd_hdsp_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct hdsp_midi *hmidi; + + snd_hdsp_midi_input_trigger (substream, 0); + + hmidi = (struct hdsp_midi *) substream->rmidi->private_data; + guard(spinlock_irq)(&hmidi->lock); + hmidi->input = NULL; + + return 0; +} + +static int snd_hdsp_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct hdsp_midi *hmidi; + + snd_hdsp_midi_output_trigger (substream, 0); + + hmidi = (struct hdsp_midi *) substream->rmidi->private_data; + guard(spinlock_irq)(&hmidi->lock); + hmidi->output = NULL; + + return 0; +} + +static const struct snd_rawmidi_ops snd_hdsp_midi_output = +{ + .open = snd_hdsp_midi_output_open, + .close = snd_hdsp_midi_output_close, + .trigger = snd_hdsp_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_hdsp_midi_input = +{ + .open = snd_hdsp_midi_input_open, + .close = snd_hdsp_midi_input_close, + .trigger = snd_hdsp_midi_input_trigger, +}; + +static int snd_hdsp_create_midi (struct snd_card *card, struct hdsp *hdsp, int id) +{ + char buf[40]; + + hdsp->midi[id].id = id; + hdsp->midi[id].rmidi = NULL; + hdsp->midi[id].input = NULL; + hdsp->midi[id].output = NULL; + hdsp->midi[id].hdsp = hdsp; + hdsp->midi[id].istimer = 0; + hdsp->midi[id].pending = 0; + spin_lock_init (&hdsp->midi[id].lock); + + snprintf(buf, sizeof(buf), "%s MIDI %d", card->shortname, id + 1); + if (snd_rawmidi_new (card, buf, id, 1, 1, &hdsp->midi[id].rmidi) < 0) + return -1; + + sprintf(hdsp->midi[id].rmidi->name, "HDSP MIDI %d", id+1); + hdsp->midi[id].rmidi->private_data = &hdsp->midi[id]; + + snd_rawmidi_set_ops (hdsp->midi[id].rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_hdsp_midi_output); + snd_rawmidi_set_ops (hdsp->midi[id].rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_hdsp_midi_input); + + hdsp->midi[id].rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} + +/*----------------------------------------------------------------------------- + Control Interface + ----------------------------------------------------------------------------*/ + +static u32 snd_hdsp_convert_from_aes(struct snd_aes_iec958 *aes) +{ + u32 val = 0; + val |= (aes->status[0] & IEC958_AES0_PROFESSIONAL) ? HDSP_SPDIFProfessional : 0; + val |= (aes->status[0] & IEC958_AES0_NONAUDIO) ? HDSP_SPDIFNonAudio : 0; + if (val & HDSP_SPDIFProfessional) + val |= (aes->status[0] & IEC958_AES0_PRO_EMPHASIS_5015) ? HDSP_SPDIFEmphasis : 0; + else + val |= (aes->status[0] & IEC958_AES0_CON_EMPHASIS_5015) ? HDSP_SPDIFEmphasis : 0; + return val; +} + +static void snd_hdsp_convert_to_aes(struct snd_aes_iec958 *aes, u32 val) +{ + aes->status[0] = ((val & HDSP_SPDIFProfessional) ? IEC958_AES0_PROFESSIONAL : 0) | + ((val & HDSP_SPDIFNonAudio) ? IEC958_AES0_NONAUDIO : 0); + if (val & HDSP_SPDIFProfessional) + aes->status[0] |= (val & HDSP_SPDIFEmphasis) ? IEC958_AES0_PRO_EMPHASIS_5015 : 0; + else + aes->status[0] |= (val & HDSP_SPDIFEmphasis) ? IEC958_AES0_CON_EMPHASIS_5015 : 0; +} + +static int snd_hdsp_control_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_hdsp_control_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + snd_hdsp_convert_to_aes(&ucontrol->value.iec958, hdsp->creg_spdif); + return 0; +} + +static int snd_hdsp_control_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + u32 val; + + val = snd_hdsp_convert_from_aes(&ucontrol->value.iec958); + guard(spinlock_irq)(&hdsp->lock); + change = val != hdsp->creg_spdif; + hdsp->creg_spdif = val; + return change; +} + +static int snd_hdsp_control_spdif_stream_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_hdsp_control_spdif_stream_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + snd_hdsp_convert_to_aes(&ucontrol->value.iec958, hdsp->creg_spdif_stream); + return 0; +} + +static int snd_hdsp_control_spdif_stream_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + u32 val; + + val = snd_hdsp_convert_from_aes(&ucontrol->value.iec958); + guard(spinlock_irq)(&hdsp->lock); + change = val != hdsp->creg_spdif_stream; + hdsp->creg_spdif_stream = val; + hdsp->control_register &= ~(HDSP_SPDIFProfessional | HDSP_SPDIFNonAudio | HDSP_SPDIFEmphasis); + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register |= val); + return change; +} + +static int snd_hdsp_control_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_hdsp_control_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = kcontrol->private_value; + return 0; +} + +#define HDSP_SPDIF_IN(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_spdif_in, \ + .get = snd_hdsp_get_spdif_in, \ + .put = snd_hdsp_put_spdif_in } + +static unsigned int hdsp_spdif_in(struct hdsp *hdsp) +{ + return hdsp_decode_spdif_in(hdsp->control_register & HDSP_SPDIFInputMask); +} + +static int hdsp_set_spdif_input(struct hdsp *hdsp, int in) +{ + hdsp->control_register &= ~HDSP_SPDIFInputMask; + hdsp->control_register |= hdsp_encode_spdif_in(in); + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; +} + +static int snd_hdsp_info_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "Optical", "Coaxial", "Internal", "AES" + }; + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + return snd_ctl_enum_info(uinfo, 1, (hdsp->io_type == H9632) ? 4 : 3, + texts); +} + +static int snd_hdsp_get_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_spdif_in(hdsp); + return 0; +} + +static int snd_hdsp_put_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0] % ((hdsp->io_type == H9632) ? 4 : 3); + guard(spinlock_irq)(&hdsp->lock); + change = val != hdsp_spdif_in(hdsp); + if (change) + hdsp_set_spdif_input(hdsp, val); + return change; +} + +#define HDSP_TOGGLE_SETTING(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .private_value = xindex, \ + .info = snd_hdsp_info_toggle_setting, \ + .get = snd_hdsp_get_toggle_setting, \ + .put = snd_hdsp_put_toggle_setting \ +} + +static int hdsp_toggle_setting(struct hdsp *hdsp, u32 regmask) +{ + return (hdsp->control_register & regmask) ? 1 : 0; +} + +static int hdsp_set_toggle_setting(struct hdsp *hdsp, u32 regmask, int out) +{ + if (out) + hdsp->control_register |= regmask; + else + hdsp->control_register &= ~regmask; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + + return 0; +} + +#define snd_hdsp_info_toggle_setting snd_ctl_boolean_mono_info + +static int snd_hdsp_get_toggle_setting(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + u32 regmask = kcontrol->private_value; + + guard(spinlock_irq)(&hdsp->lock); + ucontrol->value.integer.value[0] = hdsp_toggle_setting(hdsp, regmask); + return 0; +} + +static int snd_hdsp_put_toggle_setting(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + u32 regmask = kcontrol->private_value; + int change; + unsigned int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&hdsp->lock); + change = (int) val != hdsp_toggle_setting(hdsp, regmask); + if (change) + hdsp_set_toggle_setting(hdsp, regmask, val); + return change; +} + +#define HDSP_SPDIF_SAMPLE_RATE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ, \ + .info = snd_hdsp_info_spdif_sample_rate, \ + .get = snd_hdsp_get_spdif_sample_rate \ +} + +static int snd_hdsp_info_spdif_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "32000", "44100", "48000", "64000", "88200", "96000", + "None", "128000", "176400", "192000" + }; + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + return snd_ctl_enum_info(uinfo, 1, (hdsp->io_type == H9632) ? 10 : 7, + texts); +} + +static int snd_hdsp_get_spdif_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + switch (hdsp_spdif_sample_rate(hdsp)) { + case 32000: + ucontrol->value.enumerated.item[0] = 0; + break; + case 44100: + ucontrol->value.enumerated.item[0] = 1; + break; + case 48000: + ucontrol->value.enumerated.item[0] = 2; + break; + case 64000: + ucontrol->value.enumerated.item[0] = 3; + break; + case 88200: + ucontrol->value.enumerated.item[0] = 4; + break; + case 96000: + ucontrol->value.enumerated.item[0] = 5; + break; + case 128000: + ucontrol->value.enumerated.item[0] = 7; + break; + case 176400: + ucontrol->value.enumerated.item[0] = 8; + break; + case 192000: + ucontrol->value.enumerated.item[0] = 9; + break; + default: + ucontrol->value.enumerated.item[0] = 6; + } + return 0; +} + +#define HDSP_SYSTEM_SAMPLE_RATE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ, \ + .info = snd_hdsp_info_system_sample_rate, \ + .get = snd_hdsp_get_system_sample_rate \ +} + +static int snd_hdsp_info_system_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + return 0; +} + +static int snd_hdsp_get_system_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp->system_sample_rate; + return 0; +} + +#define HDSP_AUTOSYNC_SAMPLE_RATE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ, \ + .info = snd_hdsp_info_autosync_sample_rate, \ + .get = snd_hdsp_get_autosync_sample_rate \ +} + +static int snd_hdsp_info_autosync_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + static const char * const texts[] = { + "32000", "44100", "48000", "64000", "88200", "96000", + "None", "128000", "176400", "192000" + }; + + return snd_ctl_enum_info(uinfo, 1, (hdsp->io_type == H9632) ? 10 : 7, + texts); +} + +static int snd_hdsp_get_autosync_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + switch (hdsp_external_sample_rate(hdsp)) { + case 32000: + ucontrol->value.enumerated.item[0] = 0; + break; + case 44100: + ucontrol->value.enumerated.item[0] = 1; + break; + case 48000: + ucontrol->value.enumerated.item[0] = 2; + break; + case 64000: + ucontrol->value.enumerated.item[0] = 3; + break; + case 88200: + ucontrol->value.enumerated.item[0] = 4; + break; + case 96000: + ucontrol->value.enumerated.item[0] = 5; + break; + case 128000: + ucontrol->value.enumerated.item[0] = 7; + break; + case 176400: + ucontrol->value.enumerated.item[0] = 8; + break; + case 192000: + ucontrol->value.enumerated.item[0] = 9; + break; + default: + ucontrol->value.enumerated.item[0] = 6; + } + return 0; +} + +#define HDSP_SYSTEM_CLOCK_MODE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ, \ + .info = snd_hdsp_info_system_clock_mode, \ + .get = snd_hdsp_get_system_clock_mode \ +} + +static int hdsp_system_clock_mode(struct hdsp *hdsp) +{ + if (hdsp->control_register & HDSP_ClockModeMaster) + return 0; + else if (hdsp_external_sample_rate(hdsp) != hdsp->system_sample_rate) + return 0; + return 1; +} + +static int snd_hdsp_info_system_clock_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"Master", "Slave" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_hdsp_get_system_clock_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_system_clock_mode(hdsp); + return 0; +} + +#define HDSP_CLOCK_SOURCE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_clock_source, \ + .get = snd_hdsp_get_clock_source, \ + .put = snd_hdsp_put_clock_source \ +} + +static int hdsp_clock_source(struct hdsp *hdsp) +{ + if (hdsp->control_register & HDSP_ClockModeMaster) { + switch (hdsp->system_sample_rate) { + case 32000: + return 1; + case 44100: + return 2; + case 48000: + return 3; + case 64000: + return 4; + case 88200: + return 5; + case 96000: + return 6; + case 128000: + return 7; + case 176400: + return 8; + case 192000: + return 9; + default: + return 3; + } + } else { + return 0; + } +} + +static int hdsp_set_clock_source(struct hdsp *hdsp, int mode) +{ + int rate; + switch (mode) { + case HDSP_CLOCK_SOURCE_AUTOSYNC: + if (hdsp_external_sample_rate(hdsp) != 0) { + if (!hdsp_set_rate(hdsp, hdsp_external_sample_rate(hdsp), 1)) { + hdsp->control_register &= ~HDSP_ClockModeMaster; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; + } + } + return -1; + case HDSP_CLOCK_SOURCE_INTERNAL_32KHZ: + rate = 32000; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_44_1KHZ: + rate = 44100; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_48KHZ: + rate = 48000; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_64KHZ: + rate = 64000; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_88_2KHZ: + rate = 88200; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_96KHZ: + rate = 96000; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_128KHZ: + rate = 128000; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_176_4KHZ: + rate = 176400; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_192KHZ: + rate = 192000; + break; + default: + rate = 48000; + } + hdsp->control_register |= HDSP_ClockModeMaster; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + hdsp_set_rate(hdsp, rate, 1); + return 0; +} + +static int snd_hdsp_info_clock_source(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "AutoSync", "Internal 32.0 kHz", "Internal 44.1 kHz", + "Internal 48.0 kHz", "Internal 64.0 kHz", "Internal 88.2 kHz", + "Internal 96.0 kHz", "Internal 128 kHz", "Internal 176.4 kHz", + "Internal 192.0 KHz" + }; + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + return snd_ctl_enum_info(uinfo, 1, (hdsp->io_type == H9632) ? 10 : 7, + texts); +} + +static int snd_hdsp_get_clock_source(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_clock_source(hdsp); + return 0; +} + +static int snd_hdsp_put_clock_source(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0]; + if (val < 0) val = 0; + if (hdsp->io_type == H9632) { + if (val > 9) + val = 9; + } else { + if (val > 6) + val = 6; + } + guard(spinlock_irq)(&hdsp->lock); + if (val != hdsp_clock_source(hdsp)) + change = (hdsp_set_clock_source(hdsp, val) == 0) ? 1 : 0; + else + change = 0; + return change; +} + +#define snd_hdsp_info_clock_source_lock snd_ctl_boolean_mono_info + +static int snd_hdsp_get_clock_source_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = hdsp->clock_source_locked; + return 0; +} + +static int snd_hdsp_put_clock_source_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + + change = (int)ucontrol->value.integer.value[0] != hdsp->clock_source_locked; + if (change) + hdsp->clock_source_locked = !!ucontrol->value.integer.value[0]; + return change; +} + +#define HDSP_DA_GAIN(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_da_gain, \ + .get = snd_hdsp_get_da_gain, \ + .put = snd_hdsp_put_da_gain \ +} + +static int hdsp_da_gain(struct hdsp *hdsp) +{ + switch (hdsp->control_register & HDSP_DAGainMask) { + case HDSP_DAGainHighGain: + return 0; + case HDSP_DAGainPlus4dBu: + return 1; + case HDSP_DAGainMinus10dBV: + return 2; + default: + return 1; + } +} + +static int hdsp_set_da_gain(struct hdsp *hdsp, int mode) +{ + hdsp->control_register &= ~HDSP_DAGainMask; + switch (mode) { + case 0: + hdsp->control_register |= HDSP_DAGainHighGain; + break; + case 1: + hdsp->control_register |= HDSP_DAGainPlus4dBu; + break; + case 2: + hdsp->control_register |= HDSP_DAGainMinus10dBV; + break; + default: + return -1; + + } + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; +} + +static int snd_hdsp_info_da_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"Hi Gain", "+4 dBu", "-10 dbV"}; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_hdsp_get_da_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_da_gain(hdsp); + return 0; +} + +static int snd_hdsp_put_da_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0]; + if (val < 0) val = 0; + if (val > 2) val = 2; + guard(spinlock_irq)(&hdsp->lock); + if (val != hdsp_da_gain(hdsp)) + change = (hdsp_set_da_gain(hdsp, val) == 0) ? 1 : 0; + else + change = 0; + return change; +} + +#define HDSP_AD_GAIN(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_ad_gain, \ + .get = snd_hdsp_get_ad_gain, \ + .put = snd_hdsp_put_ad_gain \ +} + +static int hdsp_ad_gain(struct hdsp *hdsp) +{ + switch (hdsp->control_register & HDSP_ADGainMask) { + case HDSP_ADGainMinus10dBV: + return 0; + case HDSP_ADGainPlus4dBu: + return 1; + case HDSP_ADGainLowGain: + return 2; + default: + return 1; + } +} + +static int hdsp_set_ad_gain(struct hdsp *hdsp, int mode) +{ + hdsp->control_register &= ~HDSP_ADGainMask; + switch (mode) { + case 0: + hdsp->control_register |= HDSP_ADGainMinus10dBV; + break; + case 1: + hdsp->control_register |= HDSP_ADGainPlus4dBu; + break; + case 2: + hdsp->control_register |= HDSP_ADGainLowGain; + break; + default: + return -1; + + } + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; +} + +static int snd_hdsp_info_ad_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"-10 dBV", "+4 dBu", "Lo Gain"}; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_hdsp_get_ad_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_ad_gain(hdsp); + return 0; +} + +static int snd_hdsp_put_ad_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0]; + if (val < 0) val = 0; + if (val > 2) val = 2; + guard(spinlock_irq)(&hdsp->lock); + if (val != hdsp_ad_gain(hdsp)) + change = (hdsp_set_ad_gain(hdsp, val) == 0) ? 1 : 0; + else + change = 0; + return change; +} + +#define HDSP_PHONE_GAIN(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_phone_gain, \ + .get = snd_hdsp_get_phone_gain, \ + .put = snd_hdsp_put_phone_gain \ +} + +static int hdsp_phone_gain(struct hdsp *hdsp) +{ + switch (hdsp->control_register & HDSP_PhoneGainMask) { + case HDSP_PhoneGain0dB: + return 0; + case HDSP_PhoneGainMinus6dB: + return 1; + case HDSP_PhoneGainMinus12dB: + return 2; + default: + return 0; + } +} + +static int hdsp_set_phone_gain(struct hdsp *hdsp, int mode) +{ + hdsp->control_register &= ~HDSP_PhoneGainMask; + switch (mode) { + case 0: + hdsp->control_register |= HDSP_PhoneGain0dB; + break; + case 1: + hdsp->control_register |= HDSP_PhoneGainMinus6dB; + break; + case 2: + hdsp->control_register |= HDSP_PhoneGainMinus12dB; + break; + default: + return -1; + + } + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; +} + +static int snd_hdsp_info_phone_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"0 dB", "-6 dB", "-12 dB"}; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_hdsp_get_phone_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_phone_gain(hdsp); + return 0; +} + +static int snd_hdsp_put_phone_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0]; + if (val < 0) val = 0; + if (val > 2) val = 2; + guard(spinlock_irq)(&hdsp->lock); + if (val != hdsp_phone_gain(hdsp)) + change = (hdsp_set_phone_gain(hdsp, val) == 0) ? 1 : 0; + else + change = 0; + return change; +} + +#define HDSP_PREF_SYNC_REF(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_pref_sync_ref, \ + .get = snd_hdsp_get_pref_sync_ref, \ + .put = snd_hdsp_put_pref_sync_ref \ +} + +static int hdsp_pref_sync_ref(struct hdsp *hdsp) +{ + /* Notice that this looks at the requested sync source, + not the one actually in use. + */ + + switch (hdsp->control_register & HDSP_SyncRefMask) { + case HDSP_SyncRef_ADAT1: + return HDSP_SYNC_FROM_ADAT1; + case HDSP_SyncRef_ADAT2: + return HDSP_SYNC_FROM_ADAT2; + case HDSP_SyncRef_ADAT3: + return HDSP_SYNC_FROM_ADAT3; + case HDSP_SyncRef_SPDIF: + return HDSP_SYNC_FROM_SPDIF; + case HDSP_SyncRef_WORD: + return HDSP_SYNC_FROM_WORD; + case HDSP_SyncRef_ADAT_SYNC: + return HDSP_SYNC_FROM_ADAT_SYNC; + default: + return HDSP_SYNC_FROM_WORD; + } + return 0; +} + +static int hdsp_set_pref_sync_ref(struct hdsp *hdsp, int pref) +{ + hdsp->control_register &= ~HDSP_SyncRefMask; + switch (pref) { + case HDSP_SYNC_FROM_ADAT1: + hdsp->control_register &= ~HDSP_SyncRefMask; /* clear SyncRef bits */ + break; + case HDSP_SYNC_FROM_ADAT2: + hdsp->control_register |= HDSP_SyncRef_ADAT2; + break; + case HDSP_SYNC_FROM_ADAT3: + hdsp->control_register |= HDSP_SyncRef_ADAT3; + break; + case HDSP_SYNC_FROM_SPDIF: + hdsp->control_register |= HDSP_SyncRef_SPDIF; + break; + case HDSP_SYNC_FROM_WORD: + hdsp->control_register |= HDSP_SyncRef_WORD; + break; + case HDSP_SYNC_FROM_ADAT_SYNC: + hdsp->control_register |= HDSP_SyncRef_ADAT_SYNC; + break; + default: + return -1; + } + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; +} + +static int snd_hdsp_info_pref_sync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "Word", "IEC958", "ADAT1", "ADAT Sync", "ADAT2", "ADAT3" + }; + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int num_items; + + switch (hdsp->io_type) { + case Digiface: + case H9652: + num_items = 6; + break; + case Multiface: + num_items = 4; + break; + case H9632: + num_items = 3; + break; + default: + return -EINVAL; + } + + return snd_ctl_enum_info(uinfo, 1, num_items, texts); +} + +static int snd_hdsp_get_pref_sync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_pref_sync_ref(hdsp); + return 0; +} + +static int snd_hdsp_put_pref_sync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change, max; + unsigned int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + + switch (hdsp->io_type) { + case Digiface: + case H9652: + max = 6; + break; + case Multiface: + max = 4; + break; + case H9632: + max = 3; + break; + default: + return -EIO; + } + + val = ucontrol->value.enumerated.item[0] % max; + guard(spinlock_irq)(&hdsp->lock); + change = (int)val != hdsp_pref_sync_ref(hdsp); + hdsp_set_pref_sync_ref(hdsp, val); + return change; +} + +#define HDSP_AUTOSYNC_REF(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ, \ + .info = snd_hdsp_info_autosync_ref, \ + .get = snd_hdsp_get_autosync_ref, \ +} + +static int hdsp_autosync_ref(struct hdsp *hdsp) +{ + /* This looks at the autosync selected sync reference */ + unsigned int status2 = hdsp_read(hdsp, HDSP_status2Register); + + switch (status2 & HDSP_SelSyncRefMask) { + case HDSP_SelSyncRef_WORD: + return HDSP_AUTOSYNC_FROM_WORD; + case HDSP_SelSyncRef_ADAT_SYNC: + return HDSP_AUTOSYNC_FROM_ADAT_SYNC; + case HDSP_SelSyncRef_SPDIF: + return HDSP_AUTOSYNC_FROM_SPDIF; + case HDSP_SelSyncRefMask: + return HDSP_AUTOSYNC_FROM_NONE; + case HDSP_SelSyncRef_ADAT1: + return HDSP_AUTOSYNC_FROM_ADAT1; + case HDSP_SelSyncRef_ADAT2: + return HDSP_AUTOSYNC_FROM_ADAT2; + case HDSP_SelSyncRef_ADAT3: + return HDSP_AUTOSYNC_FROM_ADAT3; + default: + return HDSP_AUTOSYNC_FROM_WORD; + } + return 0; +} + +static int snd_hdsp_info_autosync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "Word", "ADAT Sync", "IEC958", "None", "ADAT1", "ADAT2", "ADAT3" + }; + + return snd_ctl_enum_info(uinfo, 1, 7, texts); +} + +static int snd_hdsp_get_autosync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_autosync_ref(hdsp); + return 0; +} + +#define HDSP_PRECISE_POINTER(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_precise_pointer, \ + .get = snd_hdsp_get_precise_pointer, \ + .put = snd_hdsp_put_precise_pointer \ +} + +static int hdsp_set_precise_pointer(struct hdsp *hdsp, int precise) +{ + if (precise) + hdsp->precise_ptr = 1; + else + hdsp->precise_ptr = 0; + return 0; +} + +#define snd_hdsp_info_precise_pointer snd_ctl_boolean_mono_info + +static int snd_hdsp_get_precise_pointer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&hdsp->lock); + ucontrol->value.integer.value[0] = hdsp->precise_ptr; + return 0; +} + +static int snd_hdsp_put_precise_pointer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&hdsp->lock); + change = (int)val != hdsp->precise_ptr; + hdsp_set_precise_pointer(hdsp, val); + return change; +} + +#define HDSP_USE_MIDI_WORK(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_use_midi_work, \ + .get = snd_hdsp_get_use_midi_work, \ + .put = snd_hdsp_put_use_midi_work \ +} + +static int hdsp_set_use_midi_work(struct hdsp *hdsp, int use_work) +{ + if (use_work) + hdsp->use_midi_work = 1; + else + hdsp->use_midi_work = 0; + return 0; +} + +#define snd_hdsp_info_use_midi_work snd_ctl_boolean_mono_info + +static int snd_hdsp_get_use_midi_work(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&hdsp->lock); + ucontrol->value.integer.value[0] = hdsp->use_midi_work; + return 0; +} + +static int snd_hdsp_put_use_midi_work(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&hdsp->lock); + change = (int)val != hdsp->use_midi_work; + hdsp_set_use_midi_work(hdsp, val); + return change; +} + +#define HDSP_MIXER(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \ + .name = xname, \ + .index = xindex, \ + .device = 0, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdsp_info_mixer, \ + .get = snd_hdsp_get_mixer, \ + .put = snd_hdsp_put_mixer \ +} + +static int snd_hdsp_info_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 65536; + uinfo->value.integer.step = 1; + return 0; +} + +static int snd_hdsp_get_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int source; + int destination; + int addr; + + source = ucontrol->value.integer.value[0]; + destination = ucontrol->value.integer.value[1]; + + if (source >= hdsp->max_channels) + addr = hdsp_playback_to_output_key(hdsp,source-hdsp->max_channels,destination); + else + addr = hdsp_input_to_output_key(hdsp,source, destination); + + guard(spinlock_irq)(&hdsp->lock); + ucontrol->value.integer.value[2] = hdsp_read_gain (hdsp, addr); + return 0; +} + +static int snd_hdsp_put_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + int source; + int destination; + int gain; + int addr; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + + source = ucontrol->value.integer.value[0]; + destination = ucontrol->value.integer.value[1]; + + if (source >= hdsp->max_channels) + addr = hdsp_playback_to_output_key(hdsp,source-hdsp->max_channels, destination); + else + addr = hdsp_input_to_output_key(hdsp,source, destination); + + gain = ucontrol->value.integer.value[2]; + + guard(spinlock_irq)(&hdsp->lock); + change = gain != hdsp_read_gain(hdsp, addr); + if (change) + hdsp_write_gain(hdsp, addr, gain); + return change; +} + +#define HDSP_WC_SYNC_CHECK(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdsp_info_sync_check, \ + .get = snd_hdsp_get_wc_sync_check \ +} + +static int snd_hdsp_info_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"No Lock", "Lock", "Sync" }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int hdsp_wc_sync_check(struct hdsp *hdsp) +{ + int status2 = hdsp_read(hdsp, HDSP_status2Register); + if (status2 & HDSP_wc_lock) { + if (status2 & HDSP_wc_sync) + return 2; + else + return 1; + } else + return 0; + return 0; +} + +static int snd_hdsp_get_wc_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_wc_sync_check(hdsp); + return 0; +} + +#define HDSP_SPDIF_SYNC_CHECK(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdsp_info_sync_check, \ + .get = snd_hdsp_get_spdif_sync_check \ +} + +static int hdsp_spdif_sync_check(struct hdsp *hdsp) +{ + int status = hdsp_read(hdsp, HDSP_statusRegister); + if (status & HDSP_SPDIFErrorFlag) + return 0; + else { + if (status & HDSP_SPDIFSync) + return 2; + else + return 1; + } + return 0; +} + +static int snd_hdsp_get_spdif_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_spdif_sync_check(hdsp); + return 0; +} + +#define HDSP_ADATSYNC_SYNC_CHECK(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdsp_info_sync_check, \ + .get = snd_hdsp_get_adatsync_sync_check \ +} + +static int hdsp_adatsync_sync_check(struct hdsp *hdsp) +{ + int status = hdsp_read(hdsp, HDSP_statusRegister); + if (status & HDSP_TimecodeLock) { + if (status & HDSP_TimecodeSync) + return 2; + else + return 1; + } else + return 0; +} + +static int snd_hdsp_get_adatsync_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_adatsync_sync_check(hdsp); + return 0; +} + +#define HDSP_ADAT_SYNC_CHECK \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdsp_info_sync_check, \ + .get = snd_hdsp_get_adat_sync_check \ +} + +static int hdsp_adat_sync_check(struct hdsp *hdsp, int idx) +{ + int status = hdsp_read(hdsp, HDSP_statusRegister); + + if (status & (HDSP_Lock0>>idx)) { + if (status & (HDSP_Sync0>>idx)) + return 2; + else + return 1; + } else + return 0; +} + +static int snd_hdsp_get_adat_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + int offset; + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + offset = ucontrol->id.index - 1; + if (snd_BUG_ON(offset < 0)) + return -EINVAL; + + switch (hdsp->io_type) { + case Digiface: + case H9652: + if (offset >= 3) + return -EINVAL; + break; + case Multiface: + case H9632: + if (offset >= 1) + return -EINVAL; + break; + default: + return -EIO; + } + + ucontrol->value.enumerated.item[0] = hdsp_adat_sync_check(hdsp, offset); + return 0; +} + +#define HDSP_DDS_OFFSET(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_dds_offset, \ + .get = snd_hdsp_get_dds_offset, \ + .put = snd_hdsp_put_dds_offset \ +} + +static int hdsp_dds_offset(struct hdsp *hdsp) +{ + u64 n; + unsigned int dds_value = hdsp->dds_value; + int system_sample_rate = hdsp->system_sample_rate; + + if (!dds_value) + return 0; + + n = DDS_NUMERATOR; + /* + * dds_value = n / rate + * rate = n / dds_value + */ + n = div_u64(n, dds_value); + if (system_sample_rate >= 112000) + n *= 4; + else if (system_sample_rate >= 56000) + n *= 2; + return ((int)n) - system_sample_rate; +} + +static int hdsp_set_dds_offset(struct hdsp *hdsp, int offset_hz) +{ + int rate = hdsp->system_sample_rate + offset_hz; + hdsp_set_dds_value(hdsp, rate); + return 0; +} + +static int snd_hdsp_info_dds_offset(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -5000; + uinfo->value.integer.max = 5000; + return 0; +} + +static int snd_hdsp_get_dds_offset(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = hdsp_dds_offset(hdsp); + return 0; +} + +static int snd_hdsp_put_dds_offset(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.integer.value[0]; + guard(spinlock_irq)(&hdsp->lock); + if (val != hdsp_dds_offset(hdsp)) + change = (hdsp_set_dds_offset(hdsp, val) == 0) ? 1 : 0; + else + change = 0; + return change; +} + +static const struct snd_kcontrol_new snd_hdsp_9632_controls[] = { +HDSP_DA_GAIN("DA Gain", 0), +HDSP_AD_GAIN("AD Gain", 0), +HDSP_PHONE_GAIN("Phones Gain", 0), +HDSP_TOGGLE_SETTING("XLR Breakout Cable", HDSP_XLRBreakoutCable), +HDSP_DDS_OFFSET("DDS Sample Rate Offset", 0) +}; + +static const struct snd_kcontrol_new snd_hdsp_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_hdsp_control_spdif_info, + .get = snd_hdsp_control_spdif_get, + .put = snd_hdsp_control_spdif_put, +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .info = snd_hdsp_control_spdif_stream_info, + .get = snd_hdsp_control_spdif_stream_get, + .put = snd_hdsp_control_spdif_stream_put, +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK), + .info = snd_hdsp_control_spdif_mask_info, + .get = snd_hdsp_control_spdif_mask_get, + .private_value = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_CON_EMPHASIS, +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK), + .info = snd_hdsp_control_spdif_mask_info, + .get = snd_hdsp_control_spdif_mask_get, + .private_value = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_PRO_EMPHASIS, +}, +HDSP_MIXER("Mixer", 0), +HDSP_SPDIF_IN("IEC958 Input Connector", 0), +HDSP_TOGGLE_SETTING("IEC958 Output also on ADAT1", HDSP_SPDIFOpticalOut), +HDSP_TOGGLE_SETTING("IEC958 Professional Bit", HDSP_SPDIFProfessional), +HDSP_TOGGLE_SETTING("IEC958 Emphasis Bit", HDSP_SPDIFEmphasis), +HDSP_TOGGLE_SETTING("IEC958 Non-audio Bit", HDSP_SPDIFNonAudio), +/* 'Sample Clock Source' complies with the alsa control naming scheme */ +HDSP_CLOCK_SOURCE("Sample Clock Source", 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sample Clock Source Locking", + .info = snd_hdsp_info_clock_source_lock, + .get = snd_hdsp_get_clock_source_lock, + .put = snd_hdsp_put_clock_source_lock, +}, +HDSP_SYSTEM_CLOCK_MODE("System Clock Mode", 0), +HDSP_PREF_SYNC_REF("Preferred Sync Reference", 0), +HDSP_AUTOSYNC_REF("AutoSync Reference", 0), +HDSP_SPDIF_SAMPLE_RATE("SPDIF Sample Rate", 0), +HDSP_SYSTEM_SAMPLE_RATE("System Sample Rate", 0), +/* 'External Rate' complies with the alsa control naming scheme */ +HDSP_AUTOSYNC_SAMPLE_RATE("External Rate", 0), +HDSP_WC_SYNC_CHECK("Word Clock Lock Status", 0), +HDSP_SPDIF_SYNC_CHECK("SPDIF Lock Status", 0), +HDSP_ADATSYNC_SYNC_CHECK("ADAT Sync Lock Status", 0), +HDSP_TOGGLE_SETTING("Line Out", HDSP_LineOut), +HDSP_PRECISE_POINTER("Precise Pointer", 0), +HDSP_USE_MIDI_WORK("Use Midi Tasklet", 0), +}; + + +static int hdsp_rpm_input12(struct hdsp *hdsp) +{ + switch (hdsp->control_register & HDSP_RPM_Inp12) { + case HDSP_RPM_Inp12_Phon_6dB: + return 0; + case HDSP_RPM_Inp12_Phon_n6dB: + return 2; + case HDSP_RPM_Inp12_Line_0dB: + return 3; + case HDSP_RPM_Inp12_Line_n6dB: + return 4; + } + return 1; +} + + +static int snd_hdsp_get_rpm_input12(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_rpm_input12(hdsp); + return 0; +} + + +static int hdsp_set_rpm_input12(struct hdsp *hdsp, int mode) +{ + hdsp->control_register &= ~HDSP_RPM_Inp12; + switch (mode) { + case 0: + hdsp->control_register |= HDSP_RPM_Inp12_Phon_6dB; + break; + case 1: + break; + case 2: + hdsp->control_register |= HDSP_RPM_Inp12_Phon_n6dB; + break; + case 3: + hdsp->control_register |= HDSP_RPM_Inp12_Line_0dB; + break; + case 4: + hdsp->control_register |= HDSP_RPM_Inp12_Line_n6dB; + break; + default: + return -1; + } + + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; +} + + +static int snd_hdsp_put_rpm_input12(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0]; + if (val < 0) + val = 0; + if (val > 4) + val = 4; + guard(spinlock_irq)(&hdsp->lock); + if (val != hdsp_rpm_input12(hdsp)) + change = (hdsp_set_rpm_input12(hdsp, val) == 0) ? 1 : 0; + else + change = 0; + return change; +} + + +static int snd_hdsp_info_rpm_input(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { + "Phono +6dB", "Phono 0dB", "Phono -6dB", "Line 0dB", "Line -6dB" + }; + + return snd_ctl_enum_info(uinfo, 1, 5, texts); +} + + +static int hdsp_rpm_input34(struct hdsp *hdsp) +{ + switch (hdsp->control_register & HDSP_RPM_Inp34) { + case HDSP_RPM_Inp34_Phon_6dB: + return 0; + case HDSP_RPM_Inp34_Phon_n6dB: + return 2; + case HDSP_RPM_Inp34_Line_0dB: + return 3; + case HDSP_RPM_Inp34_Line_n6dB: + return 4; + } + return 1; +} + + +static int snd_hdsp_get_rpm_input34(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdsp_rpm_input34(hdsp); + return 0; +} + + +static int hdsp_set_rpm_input34(struct hdsp *hdsp, int mode) +{ + hdsp->control_register &= ~HDSP_RPM_Inp34; + switch (mode) { + case 0: + hdsp->control_register |= HDSP_RPM_Inp34_Phon_6dB; + break; + case 1: + break; + case 2: + hdsp->control_register |= HDSP_RPM_Inp34_Phon_n6dB; + break; + case 3: + hdsp->control_register |= HDSP_RPM_Inp34_Line_0dB; + break; + case 4: + hdsp->control_register |= HDSP_RPM_Inp34_Line_n6dB; + break; + default: + return -1; + } + + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; +} + + +static int snd_hdsp_put_rpm_input34(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0]; + if (val < 0) + val = 0; + if (val > 4) + val = 4; + guard(spinlock_irq)(&hdsp->lock); + if (val != hdsp_rpm_input34(hdsp)) + change = (hdsp_set_rpm_input34(hdsp, val) == 0) ? 1 : 0; + else + change = 0; + return change; +} + + +/* RPM Bypass switch */ +static int hdsp_rpm_bypass(struct hdsp *hdsp) +{ + return (hdsp->control_register & HDSP_RPM_Bypass) ? 1 : 0; +} + + +static int snd_hdsp_get_rpm_bypass(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = hdsp_rpm_bypass(hdsp); + return 0; +} + + +static int hdsp_set_rpm_bypass(struct hdsp *hdsp, int on) +{ + if (on) + hdsp->control_register |= HDSP_RPM_Bypass; + else + hdsp->control_register &= ~HDSP_RPM_Bypass; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; +} + + +static int snd_hdsp_put_rpm_bypass(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&hdsp->lock); + change = (int)val != hdsp_rpm_bypass(hdsp); + hdsp_set_rpm_bypass(hdsp, val); + return change; +} + + +static int snd_hdsp_info_rpm_bypass(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"On", "Off"}; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + + +/* RPM Disconnect switch */ +static int hdsp_rpm_disconnect(struct hdsp *hdsp) +{ + return (hdsp->control_register & HDSP_RPM_Disconnect) ? 1 : 0; +} + + +static int snd_hdsp_get_rpm_disconnect(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = hdsp_rpm_disconnect(hdsp); + return 0; +} + + +static int hdsp_set_rpm_disconnect(struct hdsp *hdsp, int on) +{ + if (on) + hdsp->control_register |= HDSP_RPM_Disconnect; + else + hdsp->control_register &= ~HDSP_RPM_Disconnect; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + return 0; +} + + +static int snd_hdsp_put_rpm_disconnect(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hdsp *hdsp = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_hdsp_use_is_exclusive(hdsp)) + return -EBUSY; + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&hdsp->lock); + change = (int)val != hdsp_rpm_disconnect(hdsp); + hdsp_set_rpm_disconnect(hdsp, val); + return change; +} + +static int snd_hdsp_info_rpm_disconnect(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"On", "Off"}; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static const struct snd_kcontrol_new snd_hdsp_rpm_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "RPM Bypass", + .get = snd_hdsp_get_rpm_bypass, + .put = snd_hdsp_put_rpm_bypass, + .info = snd_hdsp_info_rpm_bypass + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "RPM Disconnect", + .get = snd_hdsp_get_rpm_disconnect, + .put = snd_hdsp_put_rpm_disconnect, + .info = snd_hdsp_info_rpm_disconnect + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 1/2", + .get = snd_hdsp_get_rpm_input12, + .put = snd_hdsp_put_rpm_input12, + .info = snd_hdsp_info_rpm_input + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 3/4", + .get = snd_hdsp_get_rpm_input34, + .put = snd_hdsp_put_rpm_input34, + .info = snd_hdsp_info_rpm_input + }, + HDSP_SYSTEM_SAMPLE_RATE("System Sample Rate", 0), + HDSP_MIXER("Mixer", 0) +}; + +static const struct snd_kcontrol_new snd_hdsp_96xx_aeb = + HDSP_TOGGLE_SETTING("Analog Extension Board", + HDSP_AnalogExtensionBoard); +static struct snd_kcontrol_new snd_hdsp_adat_sync_check = HDSP_ADAT_SYNC_CHECK; + + +static bool hdsp_loopback_get(struct hdsp *const hdsp, const u8 channel) +{ + return hdsp->io_loopback & (1 << channel); +} + +static int hdsp_loopback_set(struct hdsp *const hdsp, const u8 channel, const bool enable) +{ + if (hdsp_loopback_get(hdsp, channel) == enable) + return 0; + + hdsp->io_loopback ^= (1 << channel); + + hdsp_write(hdsp, HDSP_inputEnable + (4 * (hdsp->max_channels + channel)), enable); + + return 1; +} + +static int snd_hdsp_loopback_get(struct snd_kcontrol *const kcontrol, + struct snd_ctl_elem_value *const ucontrol) +{ + struct hdsp *const hdsp = snd_kcontrol_chip(kcontrol); + const u8 channel = snd_ctl_get_ioff(kcontrol, &ucontrol->id); + + if (channel >= hdsp->max_channels) + return -ENOENT; + + ucontrol->value.integer.value[0] = hdsp_loopback_get(hdsp, channel); + + return 0; +} + +static int snd_hdsp_loopback_put(struct snd_kcontrol *const kcontrol, + struct snd_ctl_elem_value *const ucontrol) +{ + struct hdsp *const hdsp = snd_kcontrol_chip(kcontrol); + const u8 channel = snd_ctl_get_ioff(kcontrol, &ucontrol->id); + const bool enable = ucontrol->value.integer.value[0] & 1; + + if (channel >= hdsp->max_channels) + return -ENOENT; + + return hdsp_loopback_set(hdsp, channel, enable); +} + +static struct snd_kcontrol_new snd_hdsp_loopback_control = { + .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, + .name = "Output Loopback", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_mono_info, + .get = snd_hdsp_loopback_get, + .put = snd_hdsp_loopback_put +}; + +static int snd_hdsp_create_controls(struct snd_card *card, struct hdsp *hdsp) +{ + unsigned int idx; + int err; + struct snd_kcontrol *kctl; + + if (hdsp->io_type == RPM) { + /* RPM Bypass, Disconnect and Input switches */ + for (idx = 0; idx < ARRAY_SIZE(snd_hdsp_rpm_controls); idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_hdsp_rpm_controls[idx], hdsp)); + if (err < 0) + return err; + } + return 0; + } + + for (idx = 0; idx < ARRAY_SIZE(snd_hdsp_controls); idx++) { + kctl = snd_ctl_new1(&snd_hdsp_controls[idx], hdsp); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + if (idx == 1) /* IEC958 (S/PDIF) Stream */ + hdsp->spdif_ctl = kctl; + } + + /* ADAT SyncCheck status */ + snd_hdsp_adat_sync_check.name = "ADAT Lock Status"; + snd_hdsp_adat_sync_check.index = 1; + kctl = snd_ctl_new1(&snd_hdsp_adat_sync_check, hdsp); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + if (hdsp->io_type == Digiface || hdsp->io_type == H9652) { + for (idx = 1; idx < 3; ++idx) { + snd_hdsp_adat_sync_check.index = idx+1; + kctl = snd_ctl_new1(&snd_hdsp_adat_sync_check, hdsp); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + } + + /* DA, AD and Phone gain and XLR breakout cable controls for H9632 cards */ + if (hdsp->io_type == H9632) { + for (idx = 0; idx < ARRAY_SIZE(snd_hdsp_9632_controls); idx++) { + kctl = snd_ctl_new1(&snd_hdsp_9632_controls[idx], hdsp); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + } + + /* Output loopback controls for H9632 cards */ + if (hdsp->io_type == H9632) { + snd_hdsp_loopback_control.count = hdsp->max_channels; + kctl = snd_ctl_new1(&snd_hdsp_loopback_control, hdsp); + if (kctl == NULL) + return -ENOMEM; + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + + /* AEB control for H96xx card */ + if (hdsp->io_type == H9632 || hdsp->io_type == H9652) { + kctl = snd_ctl_new1(&snd_hdsp_96xx_aeb, hdsp); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + + return 0; +} + +/*------------------------------------------------------------ + /proc interface + ------------------------------------------------------------*/ + +static void +snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct hdsp *hdsp = entry->private_data; + unsigned int status; + unsigned int status2; + char *pref_sync_ref; + char *autosync_ref; + char *system_clock_mode; + char *clock_source; + int x; + + status = hdsp_read(hdsp, HDSP_statusRegister); + status2 = hdsp_read(hdsp, HDSP_status2Register); + + snd_iprintf(buffer, "%s (Card #%d)\n", hdsp->card_name, + hdsp->card->number + 1); + snd_iprintf(buffer, "Buffers: capture %p playback %p\n", + hdsp->capture_buffer, hdsp->playback_buffer); + snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n", + hdsp->irq, hdsp->port, (unsigned long)hdsp->iobase); + snd_iprintf(buffer, "Control register: 0x%x\n", hdsp->control_register); + snd_iprintf(buffer, "Control2 register: 0x%x\n", + hdsp->control2_register); + snd_iprintf(buffer, "Status register: 0x%x\n", status); + snd_iprintf(buffer, "Status2 register: 0x%x\n", status2); + + if (hdsp_check_for_iobox(hdsp)) { + snd_iprintf(buffer, "No I/O box connected.\n" + "Please connect one and upload firmware.\n"); + return; + } + + if (hdsp_check_for_firmware(hdsp, 0)) { + if (hdsp->state & HDSP_FirmwareCached) { + if (snd_hdsp_load_firmware_from_cache(hdsp) != 0) { + snd_iprintf(buffer, "Firmware loading from " + "cache failed, " + "please upload manually.\n"); + return; + } + } else { + int err; + + err = hdsp_request_fw_loader(hdsp); + if (err < 0) { + snd_iprintf(buffer, + "No firmware loaded nor cached, " + "please upload firmware.\n"); + return; + } + } + } + + snd_iprintf(buffer, "FIFO status: %d\n", hdsp_read(hdsp, HDSP_fifoStatus) & 0xff); + snd_iprintf(buffer, "MIDI1 Output status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusOut0)); + snd_iprintf(buffer, "MIDI1 Input status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusIn0)); + snd_iprintf(buffer, "MIDI2 Output status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusOut1)); + snd_iprintf(buffer, "MIDI2 Input status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusIn1)); + snd_iprintf(buffer, "Use Midi Tasklet: %s\n", str_on_off(hdsp->use_midi_work)); + + snd_iprintf(buffer, "\n"); + + x = 1 << (6 + hdsp_decode_latency(hdsp->control_register & HDSP_LatencyMask)); + + snd_iprintf(buffer, "Buffer Size (Latency): %d samples (2 periods of %lu bytes)\n", x, (unsigned long) hdsp->period_bytes); + snd_iprintf(buffer, "Hardware pointer (frames): %ld\n", hdsp_hw_pointer(hdsp)); + snd_iprintf(buffer, "Precise pointer: %s\n", str_on_off(hdsp->precise_ptr)); + snd_iprintf(buffer, "Line out: %s\n", str_on_off(hdsp->control_register & HDSP_LineOut)); + + snd_iprintf(buffer, "Firmware version: %d\n", (status2&HDSP_version0)|(status2&HDSP_version1)<<1|(status2&HDSP_version2)<<2); + + snd_iprintf(buffer, "\n"); + + switch (hdsp_clock_source(hdsp)) { + case HDSP_CLOCK_SOURCE_AUTOSYNC: + clock_source = "AutoSync"; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_32KHZ: + clock_source = "Internal 32 kHz"; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_44_1KHZ: + clock_source = "Internal 44.1 kHz"; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_48KHZ: + clock_source = "Internal 48 kHz"; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_64KHZ: + clock_source = "Internal 64 kHz"; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_88_2KHZ: + clock_source = "Internal 88.2 kHz"; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_96KHZ: + clock_source = "Internal 96 kHz"; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_128KHZ: + clock_source = "Internal 128 kHz"; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_176_4KHZ: + clock_source = "Internal 176.4 kHz"; + break; + case HDSP_CLOCK_SOURCE_INTERNAL_192KHZ: + clock_source = "Internal 192 kHz"; + break; + default: + clock_source = "Error"; + } + snd_iprintf (buffer, "Sample Clock Source: %s\n", clock_source); + + if (hdsp_system_clock_mode(hdsp)) + system_clock_mode = "Slave"; + else + system_clock_mode = "Master"; + + switch (hdsp_pref_sync_ref (hdsp)) { + case HDSP_SYNC_FROM_WORD: + pref_sync_ref = "Word Clock"; + break; + case HDSP_SYNC_FROM_ADAT_SYNC: + pref_sync_ref = "ADAT Sync"; + break; + case HDSP_SYNC_FROM_SPDIF: + pref_sync_ref = "SPDIF"; + break; + case HDSP_SYNC_FROM_ADAT1: + pref_sync_ref = "ADAT1"; + break; + case HDSP_SYNC_FROM_ADAT2: + pref_sync_ref = "ADAT2"; + break; + case HDSP_SYNC_FROM_ADAT3: + pref_sync_ref = "ADAT3"; + break; + default: + pref_sync_ref = "Word Clock"; + break; + } + snd_iprintf (buffer, "Preferred Sync Reference: %s\n", pref_sync_ref); + + switch (hdsp_autosync_ref (hdsp)) { + case HDSP_AUTOSYNC_FROM_WORD: + autosync_ref = "Word Clock"; + break; + case HDSP_AUTOSYNC_FROM_ADAT_SYNC: + autosync_ref = "ADAT Sync"; + break; + case HDSP_AUTOSYNC_FROM_SPDIF: + autosync_ref = "SPDIF"; + break; + case HDSP_AUTOSYNC_FROM_NONE: + autosync_ref = "None"; + break; + case HDSP_AUTOSYNC_FROM_ADAT1: + autosync_ref = "ADAT1"; + break; + case HDSP_AUTOSYNC_FROM_ADAT2: + autosync_ref = "ADAT2"; + break; + case HDSP_AUTOSYNC_FROM_ADAT3: + autosync_ref = "ADAT3"; + break; + default: + autosync_ref = "---"; + break; + } + snd_iprintf (buffer, "AutoSync Reference: %s\n", autosync_ref); + + snd_iprintf (buffer, "AutoSync Frequency: %d\n", hdsp_external_sample_rate(hdsp)); + + snd_iprintf (buffer, "System Clock Mode: %s\n", system_clock_mode); + + snd_iprintf (buffer, "System Clock Frequency: %d\n", hdsp->system_sample_rate); + snd_iprintf (buffer, "System Clock Locked: %s\n", hdsp->clock_source_locked ? "Yes" : "No"); + + snd_iprintf(buffer, "\n"); + + if (hdsp->io_type != RPM) { + switch (hdsp_spdif_in(hdsp)) { + case HDSP_SPDIFIN_OPTICAL: + snd_iprintf(buffer, "IEC958 input: Optical\n"); + break; + case HDSP_SPDIFIN_COAXIAL: + snd_iprintf(buffer, "IEC958 input: Coaxial\n"); + break; + case HDSP_SPDIFIN_INTERNAL: + snd_iprintf(buffer, "IEC958 input: Internal\n"); + break; + case HDSP_SPDIFIN_AES: + snd_iprintf(buffer, "IEC958 input: AES\n"); + break; + default: + snd_iprintf(buffer, "IEC958 input: ???\n"); + break; + } + } + + if (RPM == hdsp->io_type) { + if (hdsp->control_register & HDSP_RPM_Bypass) + snd_iprintf(buffer, "RPM Bypass: disabled\n"); + else + snd_iprintf(buffer, "RPM Bypass: enabled\n"); + if (hdsp->control_register & HDSP_RPM_Disconnect) + snd_iprintf(buffer, "RPM disconnected\n"); + else + snd_iprintf(buffer, "RPM connected\n"); + + switch (hdsp->control_register & HDSP_RPM_Inp12) { + case HDSP_RPM_Inp12_Phon_6dB: + snd_iprintf(buffer, "Input 1/2: Phono, 6dB\n"); + break; + case HDSP_RPM_Inp12_Phon_0dB: + snd_iprintf(buffer, "Input 1/2: Phono, 0dB\n"); + break; + case HDSP_RPM_Inp12_Phon_n6dB: + snd_iprintf(buffer, "Input 1/2: Phono, -6dB\n"); + break; + case HDSP_RPM_Inp12_Line_0dB: + snd_iprintf(buffer, "Input 1/2: Line, 0dB\n"); + break; + case HDSP_RPM_Inp12_Line_n6dB: + snd_iprintf(buffer, "Input 1/2: Line, -6dB\n"); + break; + default: + snd_iprintf(buffer, "Input 1/2: ???\n"); + } + + switch (hdsp->control_register & HDSP_RPM_Inp34) { + case HDSP_RPM_Inp34_Phon_6dB: + snd_iprintf(buffer, "Input 3/4: Phono, 6dB\n"); + break; + case HDSP_RPM_Inp34_Phon_0dB: + snd_iprintf(buffer, "Input 3/4: Phono, 0dB\n"); + break; + case HDSP_RPM_Inp34_Phon_n6dB: + snd_iprintf(buffer, "Input 3/4: Phono, -6dB\n"); + break; + case HDSP_RPM_Inp34_Line_0dB: + snd_iprintf(buffer, "Input 3/4: Line, 0dB\n"); + break; + case HDSP_RPM_Inp34_Line_n6dB: + snd_iprintf(buffer, "Input 3/4: Line, -6dB\n"); + break; + default: + snd_iprintf(buffer, "Input 3/4: ???\n"); + } + + } else { + if (hdsp->control_register & HDSP_SPDIFOpticalOut) + snd_iprintf(buffer, "IEC958 output: Coaxial & ADAT1\n"); + else + snd_iprintf(buffer, "IEC958 output: Coaxial only\n"); + + if (hdsp->control_register & HDSP_SPDIFProfessional) + snd_iprintf(buffer, "IEC958 quality: Professional\n"); + else + snd_iprintf(buffer, "IEC958 quality: Consumer\n"); + + if (hdsp->control_register & HDSP_SPDIFEmphasis) + snd_iprintf(buffer, "IEC958 emphasis: on\n"); + else + snd_iprintf(buffer, "IEC958 emphasis: off\n"); + + if (hdsp->control_register & HDSP_SPDIFNonAudio) + snd_iprintf(buffer, "IEC958 NonAudio: on\n"); + else + snd_iprintf(buffer, "IEC958 NonAudio: off\n"); + x = hdsp_spdif_sample_rate(hdsp); + if (x != 0) + snd_iprintf(buffer, "IEC958 sample rate: %d\n", x); + else + snd_iprintf(buffer, "IEC958 sample rate: Error flag set\n"); + } + snd_iprintf(buffer, "\n"); + + /* Sync Check */ + x = status & HDSP_Sync0; + if (status & HDSP_Lock0) + snd_iprintf(buffer, "ADAT1: %s\n", x ? "Sync" : "Lock"); + else + snd_iprintf(buffer, "ADAT1: No Lock\n"); + + switch (hdsp->io_type) { + case Digiface: + case H9652: + x = status & HDSP_Sync1; + if (status & HDSP_Lock1) + snd_iprintf(buffer, "ADAT2: %s\n", x ? "Sync" : "Lock"); + else + snd_iprintf(buffer, "ADAT2: No Lock\n"); + x = status & HDSP_Sync2; + if (status & HDSP_Lock2) + snd_iprintf(buffer, "ADAT3: %s\n", x ? "Sync" : "Lock"); + else + snd_iprintf(buffer, "ADAT3: No Lock\n"); + break; + default: + /* relax */ + break; + } + + x = status & HDSP_SPDIFSync; + if (status & HDSP_SPDIFErrorFlag) + snd_iprintf (buffer, "SPDIF: No Lock\n"); + else + snd_iprintf (buffer, "SPDIF: %s\n", x ? "Sync" : "Lock"); + + x = status2 & HDSP_wc_sync; + if (status2 & HDSP_wc_lock) + snd_iprintf (buffer, "Word Clock: %s\n", x ? "Sync" : "Lock"); + else + snd_iprintf (buffer, "Word Clock: No Lock\n"); + + x = status & HDSP_TimecodeSync; + if (status & HDSP_TimecodeLock) + snd_iprintf(buffer, "ADAT Sync: %s\n", x ? "Sync" : "Lock"); + else + snd_iprintf(buffer, "ADAT Sync: No Lock\n"); + + snd_iprintf(buffer, "\n"); + + /* Informations about H9632 specific controls */ + if (hdsp->io_type == H9632) { + char *tmp; + + switch (hdsp_ad_gain(hdsp)) { + case 0: + tmp = "-10 dBV"; + break; + case 1: + tmp = "+4 dBu"; + break; + default: + tmp = "Lo Gain"; + break; + } + snd_iprintf(buffer, "AD Gain : %s\n", tmp); + + switch (hdsp_da_gain(hdsp)) { + case 0: + tmp = "Hi Gain"; + break; + case 1: + tmp = "+4 dBu"; + break; + default: + tmp = "-10 dBV"; + break; + } + snd_iprintf(buffer, "DA Gain : %s\n", tmp); + + switch (hdsp_phone_gain(hdsp)) { + case 0: + tmp = "0 dB"; + break; + case 1: + tmp = "-6 dB"; + break; + default: + tmp = "-12 dB"; + break; + } + snd_iprintf(buffer, "Phones Gain : %s\n", tmp); + + snd_iprintf(buffer, "XLR Breakout Cable : %s\n", + str_yes_no(hdsp_toggle_setting(hdsp, + HDSP_XLRBreakoutCable))); + + if (hdsp->control_register & HDSP_AnalogExtensionBoard) + snd_iprintf(buffer, "AEB : on (ADAT1 internal)\n"); + else + snd_iprintf(buffer, "AEB : off (ADAT1 external)\n"); + snd_iprintf(buffer, "\n"); + } + +} + +static void snd_hdsp_proc_init(struct hdsp *hdsp) +{ + snd_card_ro_proc_new(hdsp->card, "hdsp", hdsp, snd_hdsp_proc_read); +} + +static int snd_hdsp_initialize_memory(struct hdsp *hdsp) +{ + struct snd_dma_buffer *capture_dma, *playback_dma; + + capture_dma = snd_hammerfall_get_buffer(hdsp->pci, HDSP_DMA_AREA_BYTES); + playback_dma = snd_hammerfall_get_buffer(hdsp->pci, HDSP_DMA_AREA_BYTES); + if (!capture_dma || !playback_dma) { + dev_err(hdsp->card->dev, + "%s: no buffers available\n", hdsp->card_name); + return -ENOMEM; + } + + /* copy to the own data for alignment */ + hdsp->capture_dma_buf = *capture_dma; + hdsp->playback_dma_buf = *playback_dma; + + /* Align to bus-space 64K boundary */ + hdsp->capture_dma_buf.addr = ALIGN(capture_dma->addr, 0x10000ul); + hdsp->playback_dma_buf.addr = ALIGN(playback_dma->addr, 0x10000ul); + + /* Tell the card where it is */ + hdsp_write(hdsp, HDSP_inputBufferAddress, hdsp->capture_dma_buf.addr); + hdsp_write(hdsp, HDSP_outputBufferAddress, hdsp->playback_dma_buf.addr); + + hdsp->capture_dma_buf.area += hdsp->capture_dma_buf.addr - capture_dma->addr; + hdsp->playback_dma_buf.area += hdsp->playback_dma_buf.addr - playback_dma->addr; + hdsp->capture_buffer = hdsp->capture_dma_buf.area; + hdsp->playback_buffer = hdsp->playback_dma_buf.area; + + return 0; +} + +static int snd_hdsp_set_defaults(struct hdsp *hdsp) +{ + unsigned int i; + + /* ASSUMPTION: hdsp->lock is either held, or + there is no need to hold it (e.g. during module + initialization). + */ + + /* set defaults: + + SPDIF Input via Coax + Master clock mode + maximum latency (7 => 2^7 = 8192 samples, 64Kbyte buffer, + which implies 2 4096 sample, 32Kbyte periods). + Enable line out. + */ + + hdsp->control_register = HDSP_ClockModeMaster | + HDSP_SPDIFInputCoaxial | + hdsp_encode_latency(7) | + HDSP_LineOut; + + + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + +#ifdef SNDRV_BIG_ENDIAN + hdsp->control2_register = HDSP_BIGENDIAN_MODE; +#else + hdsp->control2_register = 0; +#endif + if (hdsp->io_type == H9652) + snd_hdsp_9652_enable_mixer (hdsp); + else + hdsp_write (hdsp, HDSP_control2Reg, hdsp->control2_register); + + hdsp_reset_hw_pointer(hdsp); + hdsp_compute_period_size(hdsp); + + /* silence everything */ + + for (i = 0; i < HDSP_MATRIX_MIXER_SIZE; ++i) + hdsp->mixer_matrix[i] = MINUS_INFINITY_GAIN; + + for (i = 0; i < ((hdsp->io_type == H9652 || hdsp->io_type == H9632) ? 1352 : HDSP_MATRIX_MIXER_SIZE); ++i) { + if (hdsp_write_gain (hdsp, i, MINUS_INFINITY_GAIN)) + return -EIO; + } + + /* H9632 specific defaults */ + if (hdsp->io_type == H9632) { + hdsp->control_register |= (HDSP_DAGainPlus4dBu | HDSP_ADGainPlus4dBu | HDSP_PhoneGain0dB); + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + } + + /* set a default rate so that the channel map is set up. + */ + + hdsp_set_rate(hdsp, 48000, 1); + + return 0; +} + +static void hdsp_midi_work(struct work_struct *work) +{ + struct hdsp *hdsp = container_of(work, struct hdsp, midi_work); + + if (hdsp->midi[0].pending) + snd_hdsp_midi_input_read (&hdsp->midi[0]); + if (hdsp->midi[1].pending) + snd_hdsp_midi_input_read (&hdsp->midi[1]); +} + +static irqreturn_t snd_hdsp_interrupt(int irq, void *dev_id) +{ + struct hdsp *hdsp = (struct hdsp *) dev_id; + unsigned int status; + int audio; + int midi0; + int midi1; + unsigned int midi0status; + unsigned int midi1status; + int schedule = 0; + + status = hdsp_read(hdsp, HDSP_statusRegister); + + audio = status & HDSP_audioIRQPending; + midi0 = status & HDSP_midi0IRQPending; + midi1 = status & HDSP_midi1IRQPending; + + if (!audio && !midi0 && !midi1) + return IRQ_NONE; + + hdsp_write(hdsp, HDSP_interruptConfirmation, 0); + + midi0status = hdsp_read (hdsp, HDSP_midiStatusIn0) & 0xff; + midi1status = hdsp_read (hdsp, HDSP_midiStatusIn1) & 0xff; + + if (!(hdsp->state & HDSP_InitializationComplete)) + return IRQ_HANDLED; + + if (audio) { + if (hdsp->capture_substream) + snd_pcm_period_elapsed(hdsp->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream); + + if (hdsp->playback_substream) + snd_pcm_period_elapsed(hdsp->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream); + } + + if (midi0 && midi0status) { + if (hdsp->use_midi_work) { + /* we disable interrupts for this input until processing is done */ + hdsp->control_register &= ~HDSP_Midi0InterruptEnable; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + hdsp->midi[0].pending = 1; + schedule = 1; + } else { + snd_hdsp_midi_input_read (&hdsp->midi[0]); + } + } + if (hdsp->io_type != Multiface && hdsp->io_type != RPM && hdsp->io_type != H9632 && midi1 && midi1status) { + if (hdsp->use_midi_work) { + /* we disable interrupts for this input until processing is done */ + hdsp->control_register &= ~HDSP_Midi1InterruptEnable; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + hdsp->midi[1].pending = 1; + schedule = 1; + } else { + snd_hdsp_midi_input_read (&hdsp->midi[1]); + } + } + if (hdsp->use_midi_work && schedule) + queue_work(system_highpri_wq, &hdsp->midi_work); + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t snd_hdsp_hw_pointer(struct snd_pcm_substream *substream) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + return hdsp_hw_pointer(hdsp); +} + +static signed char *hdsp_channel_buffer_location(struct hdsp *hdsp, + int stream, + int channel) + +{ + int mapped_channel; + + if (snd_BUG_ON(channel < 0 || channel >= hdsp->max_channels)) + return NULL; + + mapped_channel = hdsp->channel_map[channel]; + if (mapped_channel < 0) + return NULL; + + if (stream == SNDRV_PCM_STREAM_CAPTURE) + return hdsp->capture_buffer + (mapped_channel * HDSP_CHANNEL_BUFFER_BYTES); + else + return hdsp->playback_buffer + (mapped_channel * HDSP_CHANNEL_BUFFER_BYTES); +} + +static int snd_hdsp_playback_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *src, unsigned long count) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + signed char *channel_buf; + + if (snd_BUG_ON(pos + count > HDSP_CHANNEL_BUFFER_BYTES)) + return -EINVAL; + + channel_buf = hdsp_channel_buffer_location (hdsp, substream->pstr->stream, channel); + if (snd_BUG_ON(!channel_buf)) + return -EIO; + if (copy_from_iter(channel_buf + pos, count, src) != count) + return -EFAULT; + return 0; +} + +static int snd_hdsp_capture_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *dst, unsigned long count) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + signed char *channel_buf; + + if (snd_BUG_ON(pos + count > HDSP_CHANNEL_BUFFER_BYTES)) + return -EINVAL; + + channel_buf = hdsp_channel_buffer_location (hdsp, substream->pstr->stream, channel); + if (snd_BUG_ON(!channel_buf)) + return -EIO; + if (copy_to_iter(channel_buf + pos, count, dst) != count) + return -EFAULT; + return 0; +} + +static int snd_hdsp_hw_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + unsigned long count) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + signed char *channel_buf; + + channel_buf = hdsp_channel_buffer_location (hdsp, substream->pstr->stream, channel); + if (snd_BUG_ON(!channel_buf)) + return -EIO; + memset(channel_buf + pos, 0, count); + return 0; +} + +static int snd_hdsp_reset(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *other; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + other = hdsp->capture_substream; + else + other = hdsp->playback_substream; + if (hdsp->running) + runtime->status->hw_ptr = hdsp_hw_pointer(hdsp); + else + runtime->status->hw_ptr = 0; + if (other) { + struct snd_pcm_substream *s; + struct snd_pcm_runtime *oruntime = other->runtime; + snd_pcm_group_for_each_entry(s, substream) { + if (s == other) { + oruntime->status->hw_ptr = runtime->status->hw_ptr; + break; + } + } + } + return 0; +} + +static int snd_hdsp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + int err; + pid_t this_pid; + pid_t other_pid; + + if (hdsp_check_for_iobox (hdsp)) + return -EIO; + + if (hdsp_check_for_firmware(hdsp, 1)) + return -EIO; + + guard(spinlock_irq)(&hdsp->lock); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) { + hdsp->control_register &= ~(HDSP_SPDIFProfessional | HDSP_SPDIFNonAudio | HDSP_SPDIFEmphasis); + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register |= hdsp->creg_spdif_stream); + this_pid = hdsp->playback_pid; + other_pid = hdsp->capture_pid; + } else { + this_pid = hdsp->capture_pid; + other_pid = hdsp->playback_pid; + } + + if ((other_pid > 0) && (this_pid != other_pid)) { + + /* The other stream is open, and not by the same + task as this one. Make sure that the parameters + that matter are the same. + */ + + if (params_rate(params) != hdsp->system_sample_rate) { + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); + return -EBUSY; + } + + if (params_period_size(params) != hdsp->period_bytes / 4) { + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + return -EBUSY; + } + + /* We're fine. */ + + return 0; + + } + + /* how to make sure that the rate matches an externally-set one ? + */ + + if (! hdsp->clock_source_locked) { + err = hdsp_set_rate(hdsp, params_rate(params), 0); + if (err < 0) { + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); + return err; + } + } + + err = hdsp_set_interrupt_interval(hdsp, params_period_size(params)); + if (err < 0) { + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + return err; + } + + return 0; +} + +static int snd_hdsp_channel_info(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info *info) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + unsigned int channel = info->channel; + + if (snd_BUG_ON(channel >= hdsp->max_channels)) + return -EINVAL; + channel = array_index_nospec(channel, hdsp->max_channels); + + if (hdsp->channel_map[channel] < 0) + return -EINVAL; + + info->offset = hdsp->channel_map[channel] * HDSP_CHANNEL_BUFFER_BYTES; + info->first = 0; + info->step = 32; + return 0; +} + +static int snd_hdsp_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_RESET: + return snd_hdsp_reset(substream); + case SNDRV_PCM_IOCTL1_CHANNEL_INFO: + return snd_hdsp_channel_info(substream, arg); + default: + break; + } + + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int snd_hdsp_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *other; + int running; + + if (hdsp_check_for_iobox (hdsp)) + return -EIO; + + if (hdsp_check_for_firmware(hdsp, 0)) /* no auto-loading in trigger */ + return -EIO; + + guard(spinlock)(&hdsp->lock); + running = hdsp->running; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + running |= 1 << substream->stream; + break; + case SNDRV_PCM_TRIGGER_STOP: + running &= ~(1 << substream->stream); + break; + default: + snd_BUG(); + return -EINVAL; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + other = hdsp->capture_substream; + else + other = hdsp->playback_substream; + + if (other) { + struct snd_pcm_substream *s; + snd_pcm_group_for_each_entry(s, substream) { + if (s == other) { + snd_pcm_trigger_done(s, substream); + if (cmd == SNDRV_PCM_TRIGGER_START) + running |= 1 << s->stream; + else + running &= ~(1 << s->stream); + goto _ok; + } + } + if (cmd == SNDRV_PCM_TRIGGER_START) { + if (!(running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) && + substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hdsp_silence_playback(hdsp); + } else { + if (running && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + hdsp_silence_playback(hdsp); + } + } else { + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hdsp_silence_playback(hdsp); + } + _ok: + snd_pcm_trigger_done(substream, substream); + if (!hdsp->running && running) + hdsp_start_audio(hdsp); + else if (hdsp->running && !running) + hdsp_stop_audio(hdsp); + hdsp->running = running; + + return 0; +} + +static int snd_hdsp_prepare(struct snd_pcm_substream *substream) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + int result = 0; + + if (hdsp_check_for_iobox (hdsp)) + return -EIO; + + if (hdsp_check_for_firmware(hdsp, 1)) + return -EIO; + + guard(spinlock_irq)(&hdsp->lock); + if (!hdsp->running) + hdsp_reset_hw_pointer(hdsp); + return result; +} + +static const struct snd_pcm_hardware snd_hdsp_playback_subinfo = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_DOUBLE), +#ifdef SNDRV_BIG_ENDIAN + .formats = SNDRV_PCM_FMTBIT_S32_BE, +#else + .formats = SNDRV_PCM_FMTBIT_S32_LE, +#endif + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000), + .rate_min = 32000, + .rate_max = 96000, + .channels_min = 6, + .channels_max = HDSP_MAX_CHANNELS, + .buffer_bytes_max = HDSP_CHANNEL_BUFFER_BYTES * HDSP_MAX_CHANNELS, + .period_bytes_min = (64 * 4) * 10, + .period_bytes_max = (8192 * 4) * HDSP_MAX_CHANNELS, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0 +}; + +static const struct snd_pcm_hardware snd_hdsp_capture_subinfo = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_SYNC_START), +#ifdef SNDRV_BIG_ENDIAN + .formats = SNDRV_PCM_FMTBIT_S32_BE, +#else + .formats = SNDRV_PCM_FMTBIT_S32_LE, +#endif + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000), + .rate_min = 32000, + .rate_max = 96000, + .channels_min = 5, + .channels_max = HDSP_MAX_CHANNELS, + .buffer_bytes_max = HDSP_CHANNEL_BUFFER_BYTES * HDSP_MAX_CHANNELS, + .period_bytes_min = (64 * 4) * 10, + .period_bytes_max = (8192 * 4) * HDSP_MAX_CHANNELS, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0 +}; + +static const unsigned int hdsp_period_sizes[] = { 64, 128, 256, 512, 1024, 2048, 4096, 8192 }; + +static const struct snd_pcm_hw_constraint_list hdsp_hw_constraints_period_sizes = { + .count = ARRAY_SIZE(hdsp_period_sizes), + .list = hdsp_period_sizes, + .mask = 0 +}; + +static int snd_hdsp_hw_rule_in_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct hdsp *hdsp = rule->private; + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + if (hdsp->io_type == H9632) { + unsigned int list[3]; + list[0] = hdsp->qs_in_channels; + list[1] = hdsp->ds_in_channels; + list[2] = hdsp->ss_in_channels; + return snd_interval_list(c, 3, list, 0); + } else { + unsigned int list[2]; + list[0] = hdsp->ds_in_channels; + list[1] = hdsp->ss_in_channels; + return snd_interval_list(c, 2, list, 0); + } +} + +static int snd_hdsp_hw_rule_out_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned int list[3]; + struct hdsp *hdsp = rule->private; + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + if (hdsp->io_type == H9632) { + list[0] = hdsp->qs_out_channels; + list[1] = hdsp->ds_out_channels; + list[2] = hdsp->ss_out_channels; + return snd_interval_list(c, 3, list, 0); + } else { + list[0] = hdsp->ds_out_channels; + list[1] = hdsp->ss_out_channels; + } + return snd_interval_list(c, 2, list, 0); +} + +static int snd_hdsp_hw_rule_in_channels_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct hdsp *hdsp = rule->private; + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (r->min > 96000 && hdsp->io_type == H9632) { + struct snd_interval t = { + .min = hdsp->qs_in_channels, + .max = hdsp->qs_in_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else if (r->min > 48000 && r->max <= 96000) { + struct snd_interval t = { + .min = hdsp->ds_in_channels, + .max = hdsp->ds_in_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else if (r->max < 64000) { + struct snd_interval t = { + .min = hdsp->ss_in_channels, + .max = hdsp->ss_in_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } + return 0; +} + +static int snd_hdsp_hw_rule_out_channels_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct hdsp *hdsp = rule->private; + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (r->min > 96000 && hdsp->io_type == H9632) { + struct snd_interval t = { + .min = hdsp->qs_out_channels, + .max = hdsp->qs_out_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else if (r->min > 48000 && r->max <= 96000) { + struct snd_interval t = { + .min = hdsp->ds_out_channels, + .max = hdsp->ds_out_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else if (r->max < 64000) { + struct snd_interval t = { + .min = hdsp->ss_out_channels, + .max = hdsp->ss_out_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } + return 0; +} + +static int snd_hdsp_hw_rule_rate_out_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct hdsp *hdsp = rule->private; + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (c->min >= hdsp->ss_out_channels) { + struct snd_interval t = { + .min = 32000, + .max = 48000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } else if (c->max <= hdsp->qs_out_channels && hdsp->io_type == H9632) { + struct snd_interval t = { + .min = 128000, + .max = 192000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } else if (c->max <= hdsp->ds_out_channels) { + struct snd_interval t = { + .min = 64000, + .max = 96000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } + return 0; +} + +static int snd_hdsp_hw_rule_rate_in_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct hdsp *hdsp = rule->private; + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (c->min >= hdsp->ss_in_channels) { + struct snd_interval t = { + .min = 32000, + .max = 48000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } else if (c->max <= hdsp->qs_in_channels && hdsp->io_type == H9632) { + struct snd_interval t = { + .min = 128000, + .max = 192000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } else if (c->max <= hdsp->ds_in_channels) { + struct snd_interval t = { + .min = 64000, + .max = 96000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } + return 0; +} + +static int snd_hdsp_playback_open(struct snd_pcm_substream *substream) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (hdsp_check_for_iobox (hdsp)) + return -EIO; + + if (hdsp_check_for_firmware(hdsp, 1)) + return -EIO; + + scoped_guard(spinlock_irq, &hdsp->lock) { + snd_pcm_set_sync(substream); + + runtime->hw = snd_hdsp_playback_subinfo; + snd_pcm_set_runtime_buffer(substream, &hdsp->playback_dma_buf); + + hdsp->playback_pid = current->pid; + hdsp->playback_substream = substream; + } + + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hdsp_hw_constraints_period_sizes); + if (hdsp->clock_source_locked) { + runtime->hw.rate_min = runtime->hw.rate_max = hdsp->system_sample_rate; + } else if (hdsp->io_type == H9632) { + runtime->hw.rate_max = 192000; + runtime->hw.rates |= (SNDRV_PCM_RATE_128000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000); + } + if (hdsp->io_type == H9632) { + runtime->hw.channels_min = hdsp->qs_out_channels; + runtime->hw.channels_max = hdsp->ss_out_channels; + } + + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_hdsp_hw_rule_out_channels, hdsp, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_hdsp_hw_rule_out_channels_rate, hdsp, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_hdsp_hw_rule_rate_out_channels, hdsp, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + + if (RPM != hdsp->io_type) { + hdsp->creg_spdif_stream = hdsp->creg_spdif; + hdsp->spdif_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(hdsp->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &hdsp->spdif_ctl->id); + } + return 0; +} + +static int snd_hdsp_playback_release(struct snd_pcm_substream *substream) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + + scoped_guard(spinlock_irq, &hdsp->lock) { + hdsp->playback_pid = -1; + hdsp->playback_substream = NULL; + } + + if (RPM != hdsp->io_type) { + hdsp->spdif_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(hdsp->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &hdsp->spdif_ctl->id); + } + return 0; +} + + +static int snd_hdsp_capture_open(struct snd_pcm_substream *substream) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (hdsp_check_for_iobox (hdsp)) + return -EIO; + + if (hdsp_check_for_firmware(hdsp, 1)) + return -EIO; + + scoped_guard(spinlock_irq, &hdsp->lock) { + snd_pcm_set_sync(substream); + + runtime->hw = snd_hdsp_capture_subinfo; + snd_pcm_set_runtime_buffer(substream, &hdsp->capture_dma_buf); + + hdsp->capture_pid = current->pid; + hdsp->capture_substream = substream; + } + + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hdsp_hw_constraints_period_sizes); + if (hdsp->io_type == H9632) { + runtime->hw.channels_min = hdsp->qs_in_channels; + runtime->hw.channels_max = hdsp->ss_in_channels; + runtime->hw.rate_max = 192000; + runtime->hw.rates |= (SNDRV_PCM_RATE_128000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000); + } + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_hdsp_hw_rule_in_channels, hdsp, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_hdsp_hw_rule_in_channels_rate, hdsp, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_hdsp_hw_rule_rate_in_channels, hdsp, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + return 0; +} + +static int snd_hdsp_capture_release(struct snd_pcm_substream *substream) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&hdsp->lock); + + hdsp->capture_pid = -1; + hdsp->capture_substream = NULL; + + return 0; +} + +/* helper functions for copying meter values */ +static inline int copy_u32_le(void __user *dest, void __iomem *src) +{ + u32 val = readl(src); + return copy_to_user(dest, &val, 4); +} + +static inline int copy_u64_le(void __user *dest, void __iomem *src_low, void __iomem *src_high) +{ + u32 rms_low, rms_high; + u64 rms; + rms_low = readl(src_low); + rms_high = readl(src_high); + rms = ((u64)rms_high << 32) | rms_low; + return copy_to_user(dest, &rms, 8); +} + +static inline int copy_u48_le(void __user *dest, void __iomem *src_low, void __iomem *src_high) +{ + u32 rms_low, rms_high; + u64 rms; + rms_low = readl(src_low) & 0xffffff00; + rms_high = readl(src_high) & 0xffffff00; + rms = ((u64)rms_high << 32) | rms_low; + return copy_to_user(dest, &rms, 8); +} + +static int hdsp_9652_get_peak(struct hdsp *hdsp, struct hdsp_peak_rms __user *peak_rms) +{ + int doublespeed = 0; + int i, j, channels, ofs; + + if (hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DoubleSpeedStatus) + doublespeed = 1; + channels = doublespeed ? 14 : 26; + for (i = 0, j = 0; i < 26; ++i) { + if (doublespeed && (i & 4)) + continue; + ofs = HDSP_9652_peakBase - j * 4; + if (copy_u32_le(&peak_rms->input_peaks[i], hdsp->iobase + ofs)) + return -EFAULT; + ofs -= channels * 4; + if (copy_u32_le(&peak_rms->playback_peaks[i], hdsp->iobase + ofs)) + return -EFAULT; + ofs -= channels * 4; + if (copy_u32_le(&peak_rms->output_peaks[i], hdsp->iobase + ofs)) + return -EFAULT; + ofs = HDSP_9652_rmsBase + j * 8; + if (copy_u48_le(&peak_rms->input_rms[i], hdsp->iobase + ofs, + hdsp->iobase + ofs + 4)) + return -EFAULT; + ofs += channels * 8; + if (copy_u48_le(&peak_rms->playback_rms[i], hdsp->iobase + ofs, + hdsp->iobase + ofs + 4)) + return -EFAULT; + ofs += channels * 8; + if (copy_u48_le(&peak_rms->output_rms[i], hdsp->iobase + ofs, + hdsp->iobase + ofs + 4)) + return -EFAULT; + j++; + } + return 0; +} + +static int hdsp_9632_get_peak(struct hdsp *hdsp, struct hdsp_peak_rms __user *peak_rms) +{ + int i, j; + struct hdsp_9632_meters __iomem *m; + int doublespeed = 0; + + if (hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DoubleSpeedStatus) + doublespeed = 1; + m = (struct hdsp_9632_meters __iomem *)(hdsp->iobase+HDSP_9632_metersBase); + for (i = 0, j = 0; i < 16; ++i, ++j) { + if (copy_u32_le(&peak_rms->input_peaks[i], &m->input_peak[j])) + return -EFAULT; + if (copy_u32_le(&peak_rms->playback_peaks[i], &m->playback_peak[j])) + return -EFAULT; + if (copy_u32_le(&peak_rms->output_peaks[i], &m->output_peak[j])) + return -EFAULT; + if (copy_u64_le(&peak_rms->input_rms[i], &m->input_rms_low[j], + &m->input_rms_high[j])) + return -EFAULT; + if (copy_u64_le(&peak_rms->playback_rms[i], &m->playback_rms_low[j], + &m->playback_rms_high[j])) + return -EFAULT; + if (copy_u64_le(&peak_rms->output_rms[i], &m->output_rms_low[j], + &m->output_rms_high[j])) + return -EFAULT; + if (doublespeed && i == 3) i += 4; + } + return 0; +} + +static int hdsp_get_peak(struct hdsp *hdsp, struct hdsp_peak_rms __user *peak_rms) +{ + int i; + + for (i = 0; i < 26; i++) { + if (copy_u32_le(&peak_rms->playback_peaks[i], + hdsp->iobase + HDSP_playbackPeakLevel + i * 4)) + return -EFAULT; + if (copy_u32_le(&peak_rms->input_peaks[i], + hdsp->iobase + HDSP_inputPeakLevel + i * 4)) + return -EFAULT; + } + for (i = 0; i < 28; i++) { + if (copy_u32_le(&peak_rms->output_peaks[i], + hdsp->iobase + HDSP_outputPeakLevel + i * 4)) + return -EFAULT; + } + for (i = 0; i < 26; ++i) { + if (copy_u64_le(&peak_rms->playback_rms[i], + hdsp->iobase + HDSP_playbackRmsLevel + i * 8 + 4, + hdsp->iobase + HDSP_playbackRmsLevel + i * 8)) + return -EFAULT; + if (copy_u64_le(&peak_rms->input_rms[i], + hdsp->iobase + HDSP_inputRmsLevel + i * 8 + 4, + hdsp->iobase + HDSP_inputRmsLevel + i * 8)) + return -EFAULT; + } + return 0; +} + +static int snd_hdsp_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct hdsp *hdsp = hw->private_data; + void __user *argp = (void __user *)arg; + int err; + + switch (cmd) { + case SNDRV_HDSP_IOCTL_GET_PEAK_RMS: { + struct hdsp_peak_rms __user *peak_rms = (struct hdsp_peak_rms __user *)arg; + + err = hdsp_check_for_iobox(hdsp); + if (err < 0) + return err; + + err = hdsp_check_for_firmware(hdsp, 1); + if (err < 0) + return err; + + if (!(hdsp->state & HDSP_FirmwareLoaded)) { + dev_err(hdsp->card->dev, + "firmware needs to be uploaded to the card.\n"); + return -EINVAL; + } + + switch (hdsp->io_type) { + case H9652: + return hdsp_9652_get_peak(hdsp, peak_rms); + case H9632: + return hdsp_9632_get_peak(hdsp, peak_rms); + default: + return hdsp_get_peak(hdsp, peak_rms); + } + } + case SNDRV_HDSP_IOCTL_GET_CONFIG_INFO: { + struct hdsp_config_info info; + int i; + + err = hdsp_check_for_iobox(hdsp); + if (err < 0) + return err; + + err = hdsp_check_for_firmware(hdsp, 1); + if (err < 0) + return err; + + memset(&info, 0, sizeof(info)); + scoped_guard(spinlock_irqsave, &hdsp->lock) { + info.pref_sync_ref = (unsigned char)hdsp_pref_sync_ref(hdsp); + info.wordclock_sync_check = (unsigned char)hdsp_wc_sync_check(hdsp); + if (hdsp->io_type != H9632) + info.adatsync_sync_check = (unsigned char)hdsp_adatsync_sync_check(hdsp); + info.spdif_sync_check = (unsigned char)hdsp_spdif_sync_check(hdsp); + for (i = 0; i < ((hdsp->io_type != Multiface && hdsp->io_type != RPM && hdsp->io_type != H9632) ? 3 : 1); ++i) + info.adat_sync_check[i] = (unsigned char)hdsp_adat_sync_check(hdsp, i); + info.spdif_in = (unsigned char)hdsp_spdif_in(hdsp); + info.spdif_out = (unsigned char)hdsp_toggle_setting(hdsp, + HDSP_SPDIFOpticalOut); + info.spdif_professional = (unsigned char) + hdsp_toggle_setting(hdsp, HDSP_SPDIFProfessional); + info.spdif_emphasis = (unsigned char) + hdsp_toggle_setting(hdsp, HDSP_SPDIFEmphasis); + info.spdif_nonaudio = (unsigned char) + hdsp_toggle_setting(hdsp, HDSP_SPDIFNonAudio); + info.spdif_sample_rate = hdsp_spdif_sample_rate(hdsp); + info.system_sample_rate = hdsp->system_sample_rate; + info.autosync_sample_rate = hdsp_external_sample_rate(hdsp); + info.system_clock_mode = (unsigned char)hdsp_system_clock_mode(hdsp); + info.clock_source = (unsigned char)hdsp_clock_source(hdsp); + info.autosync_ref = (unsigned char)hdsp_autosync_ref(hdsp); + info.line_out = (unsigned char) + hdsp_toggle_setting(hdsp, HDSP_LineOut); + if (hdsp->io_type == H9632) { + info.da_gain = (unsigned char)hdsp_da_gain(hdsp); + info.ad_gain = (unsigned char)hdsp_ad_gain(hdsp); + info.phone_gain = (unsigned char)hdsp_phone_gain(hdsp); + info.xlr_breakout_cable = + (unsigned char)hdsp_toggle_setting(hdsp, + HDSP_XLRBreakoutCable); + + } else if (hdsp->io_type == RPM) { + info.da_gain = (unsigned char) hdsp_rpm_input12(hdsp); + info.ad_gain = (unsigned char) hdsp_rpm_input34(hdsp); + } + if (hdsp->io_type == H9632 || hdsp->io_type == H9652) + info.analog_extension_board = + (unsigned char)hdsp_toggle_setting(hdsp, + HDSP_AnalogExtensionBoard); + } + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + break; + } + case SNDRV_HDSP_IOCTL_GET_9632_AEB: { + struct hdsp_9632_aeb h9632_aeb; + + if (hdsp->io_type != H9632) return -EINVAL; + h9632_aeb.aebi = hdsp->ss_in_channels - H9632_SS_CHANNELS; + h9632_aeb.aebo = hdsp->ss_out_channels - H9632_SS_CHANNELS; + if (copy_to_user(argp, &h9632_aeb, sizeof(h9632_aeb))) + return -EFAULT; + break; + } + case SNDRV_HDSP_IOCTL_GET_VERSION: { + struct hdsp_version hdsp_version; + int err; + + if (hdsp->io_type == H9652 || hdsp->io_type == H9632) return -EINVAL; + if (hdsp->io_type == Undefined) { + err = hdsp_get_iobox_version(hdsp); + if (err < 0) + return err; + } + memset(&hdsp_version, 0, sizeof(hdsp_version)); + hdsp_version.io_type = hdsp->io_type; + hdsp_version.firmware_rev = hdsp->firmware_rev; + if (copy_to_user(argp, &hdsp_version, sizeof(hdsp_version))) + return -EFAULT; + break; + } + case SNDRV_HDSP_IOCTL_UPLOAD_FIRMWARE: { + struct hdsp_firmware firmware; + u32 __user *firmware_data; + int err; + + if (hdsp->io_type == H9652 || hdsp->io_type == H9632) return -EINVAL; + /* SNDRV_HDSP_IOCTL_GET_VERSION must have been called */ + if (hdsp->io_type == Undefined) return -EINVAL; + + if (hdsp->state & (HDSP_FirmwareCached | HDSP_FirmwareLoaded)) + return -EBUSY; + + dev_info(hdsp->card->dev, + "initializing firmware upload\n"); + if (copy_from_user(&firmware, argp, sizeof(firmware))) + return -EFAULT; + firmware_data = (u32 __user *)firmware.firmware_data; + + if (hdsp_check_for_iobox (hdsp)) + return -EIO; + + if (!hdsp->fw_uploaded) { + hdsp->fw_uploaded = vmalloc(HDSP_FIRMWARE_SIZE); + if (!hdsp->fw_uploaded) + return -ENOMEM; + } + + if (copy_from_user(hdsp->fw_uploaded, firmware_data, + HDSP_FIRMWARE_SIZE)) { + vfree(hdsp->fw_uploaded); + hdsp->fw_uploaded = NULL; + return -EFAULT; + } + + hdsp->state |= HDSP_FirmwareCached; + + err = snd_hdsp_load_firmware_from_cache(hdsp); + if (err < 0) + return err; + + if (!(hdsp->state & HDSP_InitializationComplete)) { + err = snd_hdsp_enable_io(hdsp); + if (err < 0) + return err; + + snd_hdsp_initialize_channels(hdsp); + snd_hdsp_initialize_midi_flush(hdsp); + + err = snd_hdsp_create_alsa_devices(hdsp->card, hdsp); + if (err < 0) { + dev_err(hdsp->card->dev, + "error creating alsa devices\n"); + return err; + } + } + break; + } + case SNDRV_HDSP_IOCTL_GET_MIXER: { + struct hdsp_mixer __user *mixer = (struct hdsp_mixer __user *)argp; + if (copy_to_user(mixer->matrix, hdsp->mixer_matrix, sizeof(unsigned short)*HDSP_MATRIX_MIXER_SIZE)) + return -EFAULT; + break; + } + default: + return -EINVAL; + } + return 0; +} + +static const struct snd_pcm_ops snd_hdsp_playback_ops = { + .open = snd_hdsp_playback_open, + .close = snd_hdsp_playback_release, + .ioctl = snd_hdsp_ioctl, + .hw_params = snd_hdsp_hw_params, + .prepare = snd_hdsp_prepare, + .trigger = snd_hdsp_trigger, + .pointer = snd_hdsp_hw_pointer, + .copy = snd_hdsp_playback_copy, + .fill_silence = snd_hdsp_hw_silence, +}; + +static const struct snd_pcm_ops snd_hdsp_capture_ops = { + .open = snd_hdsp_capture_open, + .close = snd_hdsp_capture_release, + .ioctl = snd_hdsp_ioctl, + .hw_params = snd_hdsp_hw_params, + .prepare = snd_hdsp_prepare, + .trigger = snd_hdsp_trigger, + .pointer = snd_hdsp_hw_pointer, + .copy = snd_hdsp_capture_copy, +}; + +static int snd_hdsp_create_hwdep(struct snd_card *card, struct hdsp *hdsp) +{ + struct snd_hwdep *hw; + int err; + + err = snd_hwdep_new(card, "HDSP hwdep", 0, &hw); + if (err < 0) + return err; + + hdsp->hwdep = hw; + hw->private_data = hdsp; + strscpy(hw->name, "HDSP hwdep interface"); + + hw->ops.ioctl = snd_hdsp_hwdep_ioctl; + hw->ops.ioctl_compat = snd_hdsp_hwdep_ioctl; + + return 0; +} + +static int snd_hdsp_create_pcm(struct snd_card *card, struct hdsp *hdsp) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(card, hdsp->card_name, 0, 1, 1, &pcm); + if (err < 0) + return err; + + hdsp->pcm = pcm; + pcm->private_data = hdsp; + strscpy(pcm->name, hdsp->card_name); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_hdsp_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_hdsp_capture_ops); + + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + + return 0; +} + +static void snd_hdsp_9652_enable_mixer (struct hdsp *hdsp) +{ + hdsp->control2_register |= HDSP_9652_ENABLE_MIXER; + hdsp_write (hdsp, HDSP_control2Reg, hdsp->control2_register); +} + +static int snd_hdsp_enable_io (struct hdsp *hdsp) +{ + int i; + + if (hdsp_fifo_wait (hdsp, 0, 100)) { + dev_err(hdsp->card->dev, + "enable_io fifo_wait failed\n"); + return -EIO; + } + + for (i = 0; i < hdsp->max_channels; ++i) { + hdsp_write (hdsp, HDSP_inputEnable + (4 * i), 1); + hdsp_write (hdsp, HDSP_outputEnable + (4 * i), 1); + } + + return 0; +} + +static void snd_hdsp_initialize_channels(struct hdsp *hdsp) +{ + int status, aebi_channels, aebo_channels, i; + + switch (hdsp->io_type) { + case Digiface: + hdsp->card_name = "RME Hammerfall DSP + Digiface"; + hdsp->ss_in_channels = hdsp->ss_out_channels = DIGIFACE_SS_CHANNELS; + hdsp->ds_in_channels = hdsp->ds_out_channels = DIGIFACE_DS_CHANNELS; + break; + + case H9652: + hdsp->card_name = "RME Hammerfall HDSP 9652"; + hdsp->ss_in_channels = hdsp->ss_out_channels = H9652_SS_CHANNELS; + hdsp->ds_in_channels = hdsp->ds_out_channels = H9652_DS_CHANNELS; + break; + + case H9632: + status = hdsp_read(hdsp, HDSP_statusRegister); + /* HDSP_AEBx bits are low when AEB are connected */ + aebi_channels = (status & HDSP_AEBI) ? 0 : 4; + aebo_channels = (status & HDSP_AEBO) ? 0 : 4; + hdsp->card_name = "RME Hammerfall HDSP 9632"; + hdsp->ss_in_channels = H9632_SS_CHANNELS+aebi_channels; + hdsp->ds_in_channels = H9632_DS_CHANNELS+aebi_channels; + hdsp->qs_in_channels = H9632_QS_CHANNELS+aebi_channels; + hdsp->ss_out_channels = H9632_SS_CHANNELS+aebo_channels; + hdsp->ds_out_channels = H9632_DS_CHANNELS+aebo_channels; + hdsp->qs_out_channels = H9632_QS_CHANNELS+aebo_channels; + /* Disable loopback of output channels, as the set function + * only sets on a change we fake all bits (channels) as enabled. + */ + hdsp->io_loopback = 0xffffffff; + for (i = 0; i < hdsp->max_channels; ++i) + hdsp_loopback_set(hdsp, i, false); + break; + + case Multiface: + hdsp->card_name = "RME Hammerfall DSP + Multiface"; + hdsp->ss_in_channels = hdsp->ss_out_channels = MULTIFACE_SS_CHANNELS; + hdsp->ds_in_channels = hdsp->ds_out_channels = MULTIFACE_DS_CHANNELS; + break; + + case RPM: + hdsp->card_name = "RME Hammerfall DSP + RPM"; + hdsp->ss_in_channels = RPM_CHANNELS-1; + hdsp->ss_out_channels = RPM_CHANNELS; + hdsp->ds_in_channels = RPM_CHANNELS-1; + hdsp->ds_out_channels = RPM_CHANNELS; + break; + + default: + /* should never get here */ + break; + } +} + +static void snd_hdsp_initialize_midi_flush (struct hdsp *hdsp) +{ + snd_hdsp_flush_midi_input (hdsp, 0); + snd_hdsp_flush_midi_input (hdsp, 1); +} + +static int snd_hdsp_create_alsa_devices(struct snd_card *card, struct hdsp *hdsp) +{ + int err; + + err = snd_hdsp_create_pcm(card, hdsp); + if (err < 0) { + dev_err(card->dev, + "Error creating pcm interface\n"); + return err; + } + + + err = snd_hdsp_create_midi(card, hdsp, 0); + if (err < 0) { + dev_err(card->dev, + "Error creating first midi interface\n"); + return err; + } + + if (hdsp->io_type == Digiface || hdsp->io_type == H9652) { + err = snd_hdsp_create_midi(card, hdsp, 1); + if (err < 0) { + dev_err(card->dev, + "Error creating second midi interface\n"); + return err; + } + } + + err = snd_hdsp_create_controls(card, hdsp); + if (err < 0) { + dev_err(card->dev, + "Error creating ctl interface\n"); + return err; + } + + snd_hdsp_proc_init(hdsp); + + hdsp->system_sample_rate = -1; + hdsp->playback_pid = -1; + hdsp->capture_pid = -1; + hdsp->capture_substream = NULL; + hdsp->playback_substream = NULL; + + err = snd_hdsp_set_defaults(hdsp); + if (err < 0) { + dev_err(card->dev, + "Error setting default values\n"); + return err; + } + + if (!(hdsp->state & HDSP_InitializationComplete)) { + strscpy(card->shortname, "Hammerfall DSP"); + sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name, + hdsp->port, hdsp->irq); + + err = snd_card_register(card); + if (err < 0) { + dev_err(card->dev, + "error registering card\n"); + return err; + } + hdsp->state |= HDSP_InitializationComplete; + } + + return 0; +} + +/* load firmware via hotplug fw loader */ +static int hdsp_request_fw_loader(struct hdsp *hdsp) +{ + const char *fwfile; + const struct firmware *fw; + int err; + + if (hdsp->io_type == H9652 || hdsp->io_type == H9632) + return 0; + if (hdsp->io_type == Undefined) { + err = hdsp_get_iobox_version(hdsp); + if (err < 0) + return err; + if (hdsp->io_type == H9652 || hdsp->io_type == H9632) + return 0; + } + + /* caution: max length of firmware filename is 30! */ + switch (hdsp->io_type) { + case RPM: + fwfile = "rpm_firmware.bin"; + break; + case Multiface: + if (hdsp->firmware_rev == 0xa) + fwfile = "multiface_firmware.bin"; + else + fwfile = "multiface_firmware_rev11.bin"; + break; + case Digiface: + if (hdsp->firmware_rev == 0xa) + fwfile = "digiface_firmware.bin"; + else + fwfile = "digiface_firmware_rev11.bin"; + break; + default: + dev_err(hdsp->card->dev, + "invalid io_type %d\n", hdsp->io_type); + return -EINVAL; + } + + if (request_firmware(&fw, fwfile, &hdsp->pci->dev)) { + dev_err(hdsp->card->dev, + "cannot load firmware %s\n", fwfile); + return -ENOENT; + } + if (fw->size < HDSP_FIRMWARE_SIZE) { + dev_err(hdsp->card->dev, + "too short firmware size %d (expected %d)\n", + (int)fw->size, HDSP_FIRMWARE_SIZE); + release_firmware(fw); + return -EINVAL; + } + + hdsp->firmware = fw; + + hdsp->state |= HDSP_FirmwareCached; + + err = snd_hdsp_load_firmware_from_cache(hdsp); + if (err < 0) + return err; + + if (!(hdsp->state & HDSP_InitializationComplete)) { + err = snd_hdsp_enable_io(hdsp); + if (err < 0) + return err; + + err = snd_hdsp_create_hwdep(hdsp->card, hdsp); + if (err < 0) { + dev_err(hdsp->card->dev, + "error creating hwdep device\n"); + return err; + } + snd_hdsp_initialize_channels(hdsp); + snd_hdsp_initialize_midi_flush(hdsp); + err = snd_hdsp_create_alsa_devices(hdsp->card, hdsp); + if (err < 0) { + dev_err(hdsp->card->dev, + "error creating alsa devices\n"); + return err; + } + } + return 0; +} + +static int snd_hdsp_create(struct snd_card *card, + struct hdsp *hdsp) +{ + struct pci_dev *pci = hdsp->pci; + int err; + int is_9652 = 0; + int is_9632 = 0; + + hdsp->irq = -1; + hdsp->state = 0; + hdsp->midi[0].rmidi = NULL; + hdsp->midi[1].rmidi = NULL; + hdsp->midi[0].input = NULL; + hdsp->midi[1].input = NULL; + hdsp->midi[0].output = NULL; + hdsp->midi[1].output = NULL; + hdsp->midi[0].pending = 0; + hdsp->midi[1].pending = 0; + spin_lock_init(&hdsp->midi[0].lock); + spin_lock_init(&hdsp->midi[1].lock); + hdsp->iobase = NULL; + hdsp->control_register = 0; + hdsp->control2_register = 0; + hdsp->io_type = Undefined; + hdsp->max_channels = 26; + + hdsp->card = card; + + spin_lock_init(&hdsp->lock); + + INIT_WORK(&hdsp->midi_work, hdsp_midi_work); + + pci_read_config_word(hdsp->pci, PCI_CLASS_REVISION, &hdsp->firmware_rev); + hdsp->firmware_rev &= 0xff; + + /* From Martin Bjoernsen : + "It is important that the card's latency timer register in + the PCI configuration space is set to a value much larger + than 0 by the computer's BIOS or the driver. + The windows driver always sets this 8 bit register [...] + to its maximum 255 to avoid problems with some computers." + */ + pci_write_config_byte(hdsp->pci, PCI_LATENCY_TIMER, 0xFF); + + strscpy(card->driver, "H-DSP"); + strscpy(card->mixername, "Xilinx FPGA"); + + if (hdsp->firmware_rev < 0xa) + return -ENODEV; + else if (hdsp->firmware_rev < 0x64) + hdsp->card_name = "RME Hammerfall DSP"; + else if (hdsp->firmware_rev < 0x96) { + hdsp->card_name = "RME HDSP 9652"; + is_9652 = 1; + } else { + hdsp->card_name = "RME HDSP 9632"; + hdsp->max_channels = 16; + is_9632 = 1; + } + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + pci_set_master(hdsp->pci); + + err = pcim_request_all_regions(pci, "hdsp"); + if (err < 0) + return err; + hdsp->port = pci_resource_start(pci, 0); + hdsp->iobase = devm_ioremap(&pci->dev, hdsp->port, HDSP_IO_EXTENT); + if (!hdsp->iobase) { + dev_err(hdsp->card->dev, "unable to remap region 0x%lx-0x%lx\n", + hdsp->port, hdsp->port + HDSP_IO_EXTENT - 1); + return -EBUSY; + } + + if (devm_request_irq(&pci->dev, pci->irq, snd_hdsp_interrupt, + IRQF_SHARED, KBUILD_MODNAME, hdsp)) { + dev_err(hdsp->card->dev, "unable to use IRQ %d\n", pci->irq); + return -EBUSY; + } + + hdsp->irq = pci->irq; + card->sync_irq = hdsp->irq; + hdsp->precise_ptr = 0; + hdsp->use_midi_work = 1; + hdsp->dds_value = 0; + + err = snd_hdsp_initialize_memory(hdsp); + if (err < 0) + return err; + + if (!is_9652 && !is_9632) { + /* we wait a maximum of 10 seconds to let freshly + * inserted cardbus cards do their hardware init */ + err = hdsp_wait_for_iobox(hdsp, 1000, 10); + + if (err < 0) + return err; + + if ((hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DllError) != 0) { + err = hdsp_request_fw_loader(hdsp); + if (err < 0) + /* we don't fail as this can happen + if userspace is not ready for + firmware upload + */ + dev_err(hdsp->card->dev, + "couldn't get firmware from userspace. try using hdsploader\n"); + else + /* init is complete, we return */ + return 0; + /* we defer initialization */ + dev_info(hdsp->card->dev, + "card initialization pending : waiting for firmware\n"); + err = snd_hdsp_create_hwdep(card, hdsp); + if (err < 0) + return err; + return 0; + } else { + dev_info(hdsp->card->dev, + "Firmware already present, initializing card.\n"); + if (hdsp_read(hdsp, HDSP_status2Register) & HDSP_version2) + hdsp->io_type = RPM; + else if (hdsp_read(hdsp, HDSP_status2Register) & HDSP_version1) + hdsp->io_type = Multiface; + else + hdsp->io_type = Digiface; + } + } + + err = snd_hdsp_enable_io(hdsp); + if (err) + return err; + + if (is_9652) + hdsp->io_type = H9652; + + if (is_9632) + hdsp->io_type = H9632; + + err = snd_hdsp_create_hwdep(card, hdsp); + if (err < 0) + return err; + + snd_hdsp_initialize_channels(hdsp); + snd_hdsp_initialize_midi_flush(hdsp); + + hdsp->state |= HDSP_FirmwareLoaded; + + err = snd_hdsp_create_alsa_devices(card, hdsp); + if (err < 0) + return err; + + return 0; +} + +static void snd_hdsp_card_free(struct snd_card *card) +{ + struct hdsp *hdsp = card->private_data; + + if (hdsp->port) { + /* stop the audio, and cancel all interrupts */ + cancel_work_sync(&hdsp->midi_work); + hdsp->control_register &= ~(HDSP_Start|HDSP_AudioInterruptEnable|HDSP_Midi0InterruptEnable|HDSP_Midi1InterruptEnable); + hdsp_write (hdsp, HDSP_controlRegister, hdsp->control_register); + } + + release_firmware(hdsp->firmware); + vfree(hdsp->fw_uploaded); +} + +static int snd_hdsp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct hdsp *hdsp; + struct snd_card *card; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct hdsp), &card); + if (err < 0) + return err; + + hdsp = card->private_data; + card->private_free = snd_hdsp_card_free; + hdsp->dev = dev; + hdsp->pci = pci; + err = snd_hdsp_create(card, hdsp); + if (err) + goto error; + + strscpy(card->shortname, "Hammerfall DSP"); + sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name, + hdsp->port, hdsp->irq); + err = snd_card_register(card); + if (err) + goto error; + pci_set_drvdata(pci, card); + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +static struct pci_driver hdsp_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_hdsp_ids, + .probe = snd_hdsp_probe, +}; + +module_pci_driver(hdsp_driver); diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c new file mode 100644 index 000000000..d8bbedbc8 --- /dev/null +++ b/sound/pci/rme9652/hdspm.c @@ -0,0 +1,6864 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for RME Hammerfall DSP MADI audio interface(s) + * + * Copyright (c) 2003 Winfried Ritsch (IEM) + * code based on hdsp.c Paul Davis + * Marcus Andersson + * Thomas Charbonnel + * Modified 2006-06-01 for AES32 support by Remy Bruno + * + * + * Modified 2009-04-13 for proper metering by Florian Faber + * + * + * Modified 2009-04-14 for native float support by Florian Faber + * + * + * Modified 2009-04-26 fixed bug in rms metering by Florian Faber + * + * + * Modified 2009-04-30 added hw serial number support by Florian Faber + * + * Modified 2011-01-14 added S/PDIF input on RayDATs by Adrian Knoth + * + * Modified 2011-01-25 variable period sizes on RayDAT/AIO by Adrian Knoth + * + * Modified 2019-05-23 fix AIO single speed ADAT capture and playback + * by Philippe.Bekaert@uhasselt.be + */ + +/* ************* Register Documentation ******************************************************* + * + * Work in progress! Documentation is based on the code in this file. + * + * --------- HDSPM_controlRegister --------- + * :7654.3210:7654.3210:7654.3210:7654.3210: bit number per byte + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :3322.2222:2222.1111:1111.1100:0000.0000: bit number + * :1098.7654:3210.9876:5432.1098:7654.3210: 0..31 + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :8421.8421:8421.8421:8421.8421:8421.8421: hex digit + * : . : . : . : x . : HDSPM_AudioInterruptEnable \_ setting both bits + * : . : . : . : . x: HDSPM_Start / enables audio IO + * : . : . : . : x. : HDSPM_ClockModeMaster - 1: Master, 0: Slave + * : . : . : . : .210 : HDSPM_LatencyMask - 3 Bit value for latency + * : . : . : . : . : 0:64, 1:128, 2:256, 3:512, + * : . : . : . : . : 4:1024, 5:2048, 6:4096, 7:8192 + * :x . : . : . x:xx . : HDSPM_FrequencyMask + * : . : . : . :10 . : HDSPM_Frequency1|HDSPM_Frequency0: 1=32K,2=44.1K,3=48K,0=?? + * : . : . : . x: . : HDSPM_DoubleSpeed + * :x . : . : . : . : HDSPM_QuadSpeed + * : . 3 : . 10: 2 . : . : HDSPM_SyncRefMask : + * : . : . x: . : . : HDSPM_SyncRef0 + * : . : . x : . : . : HDSPM_SyncRef1 + * : . : . : x . : . : HDSPM_SyncRef2 + * : . x : . : . : . : HDSPM_SyncRef3 + * : . : . 10: . : . : sync ref: 0:WC, 1:Madi, 2:TCO, 3:SyncIn + * : . 3 : . 10: 2 . : . : 0:WC, 1:AES1 ... 8:AES8, 9: TCO, 10:SyncIn? + * : . x : . : . : . : HDSPe_FLOAT_FORMAT + * : . : . : x . : . : HDSPM_InputSelect0 : 0=optical,1=coax + * : . : . :x . : . : HDSPM_InputSelect1 + * : . : .x : . : . : HDSPM_clr_tms + * : . : . : . x : . : HDSPM_TX_64ch + * : . : . : . x : . : HDSPM_Emphasis + * : . : . : .x : . : HDSPM_AutoInp + * : . : . x : . : . : HDSPM_SMUX + * : . : .x : . : . : HDSPM_clr_tms + * : . : x. : . : . : HDSPM_taxi_reset + * : . x: . : . : . : HDSPM_LineOut + * : . x: . : . : . : ?????????????????? + * : . : x. : . : . : HDSPM_WCK48 + * : . : . : .x : . : HDSPM_Dolby + * : . : x . : . : . : HDSPM_Midi0InterruptEnable + * : . :x . : . : . : HDSPM_Midi1InterruptEnable + * : . : x . : . : . : HDSPM_Midi2InterruptEnable + * : . x : . : . : . : HDSPM_Midi3InterruptEnable + * : . x : . : . : . : HDSPM_DS_DoubleWire + * : .x : . : . : . : HDSPM_QS_DoubleWire + * : x. : . : . : . : HDSPM_QS_QuadWire + * : . : . : . x : . : HDSPM_Professional + * : x . : . : . : . : HDSPM_wclk_sel + * : . : . : . : . : + * :7654.3210:7654.3210:7654.3210:7654.3210: bit number per byte + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :3322.2222:2222.1111:1111.1100:0000.0000: bit number + * :1098.7654:3210.9876:5432.1098:7654.3210: 0..31 + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :8421.8421:8421.8421:8421.8421:8421.8421:hex digit + * + * + * + * AIO / RayDAT only + * + * ------------ HDSPM_WR_SETTINGS ---------- + * :3322.2222:2222.1111:1111.1100:0000.0000: bit number per byte + * :1098.7654:3210.9876:5432.1098:7654.3210: + * :||||.||||:||||.||||:||||.||||:||||.||||: bit number + * :7654.3210:7654.3210:7654.3210:7654.3210: 0..31 + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :8421.8421:8421.8421:8421.8421:8421.8421: hex digit + * : . : . : . : . x: HDSPM_c0Master 1: Master, 0: Slave + * : . : . : . : . x : HDSPM_c0_SyncRef0 + * : . : . : . : . x : HDSPM_c0_SyncRef1 + * : . : . : . : .x : HDSPM_c0_SyncRef2 + * : . : . : . : x. : HDSPM_c0_SyncRef3 + * : . : . : . : 3.210 : HDSPM_c0_SyncRefMask: + * : . : . : . : . : RayDat: 0:WC, 1:AES, 2:SPDIF, 3..6: ADAT1..4, + * : . : . : . : . : 9:TCO, 10:SyncIn + * : . : . : . : . : AIO: 0:WC, 1:AES, 2: SPDIF, 3: ATAT, + * : . : . : . : . : 9:TCO, 10:SyncIn + * : . : . : . : . : + * : . : . : . : . : + * :3322.2222:2222.1111:1111.1100:0000.0000: bit number per byte + * :1098.7654:3210.9876:5432.1098:7654.3210: + * :||||.||||:||||.||||:||||.||||:||||.||||: bit number + * :7654.3210:7654.3210:7654.3210:7654.3210: 0..31 + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :8421.8421:8421.8421:8421.8421:8421.8421: hex digit + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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_PNP;/* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for RME HDSPM interface."); + +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for RME HDSPM interface."); + +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable/disable specific HDSPM soundcards."); + + +MODULE_AUTHOR +( + "Winfried Ritsch , " + "Paul Davis , " + "Marcus Andersson, Thomas Charbonnel , " + "Remy Bruno , " + "Florian Faber , " + "Adrian Knoth " +); +MODULE_DESCRIPTION("RME HDSPM"); +MODULE_LICENSE("GPL"); + +/* --- Write registers. --- + These are defined as byte-offsets from the iobase value. */ + +#define HDSPM_WR_SETTINGS 0 +#define HDSPM_outputBufferAddress 32 +#define HDSPM_inputBufferAddress 36 +#define HDSPM_controlRegister 64 +#define HDSPM_interruptConfirmation 96 +#define HDSPM_control2Reg 256 /* not in specs ???????? */ +#define HDSPM_freqReg 256 /* for setting arbitrary clock values (DDS feature) */ +#define HDSPM_midiDataOut0 352 /* just believe in old code */ +#define HDSPM_midiDataOut1 356 +#define HDSPM_eeprom_wr 384 /* for AES32 */ + +/* DMA enable for 64 channels, only Bit 0 is relevant */ +#define HDSPM_outputEnableBase 512 /* 512-767 input DMA */ +#define HDSPM_inputEnableBase 768 /* 768-1023 output DMA */ + +/* 16 page addresses for each of the 64 channels DMA buffer in and out + (each 64k=16*4k) Buffer must be 4k aligned (which is default i386 ????) */ +#define HDSPM_pageAddressBufferOut 8192 +#define HDSPM_pageAddressBufferIn (HDSPM_pageAddressBufferOut+64*16*4) + +#define HDSPM_MADI_mixerBase 32768 /* 32768-65535 for 2x64x64 Fader */ + +#define HDSPM_MATRIX_MIXER_SIZE 8192 /* = 2*64*64 * 4 Byte => 32kB */ + +/* --- Read registers. --- + These are defined as byte-offsets from the iobase value */ +#define HDSPM_statusRegister 0 +/*#define HDSPM_statusRegister2 96 */ +/* after RME Windows driver sources, status2 is 4-byte word # 48 = word at + * offset 192, for AES32 *and* MADI + * => need to check that offset 192 is working on MADI */ +#define HDSPM_statusRegister2 192 +#define HDSPM_timecodeRegister 128 + +/* AIO, RayDAT */ +#define HDSPM_RD_STATUS_0 0 +#define HDSPM_RD_STATUS_1 64 +#define HDSPM_RD_STATUS_2 128 +#define HDSPM_RD_STATUS_3 192 + +#define HDSPM_RD_TCO 256 +#define HDSPM_RD_PLL_FREQ 512 +#define HDSPM_WR_TCO 128 + +#define HDSPM_TCO1_TCO_lock 0x00000001 +#define HDSPM_TCO1_WCK_Input_Range_LSB 0x00000002 +#define HDSPM_TCO1_WCK_Input_Range_MSB 0x00000004 +#define HDSPM_TCO1_LTC_Input_valid 0x00000008 +#define HDSPM_TCO1_WCK_Input_valid 0x00000010 +#define HDSPM_TCO1_Video_Input_Format_NTSC 0x00000020 +#define HDSPM_TCO1_Video_Input_Format_PAL 0x00000040 + +#define HDSPM_TCO1_set_TC 0x00000100 +#define HDSPM_TCO1_set_drop_frame_flag 0x00000200 +#define HDSPM_TCO1_LTC_Format_LSB 0x00000400 +#define HDSPM_TCO1_LTC_Format_MSB 0x00000800 + +#define HDSPM_TCO2_TC_run 0x00010000 +#define HDSPM_TCO2_WCK_IO_ratio_LSB 0x00020000 +#define HDSPM_TCO2_WCK_IO_ratio_MSB 0x00040000 +#define HDSPM_TCO2_set_num_drop_frames_LSB 0x00080000 +#define HDSPM_TCO2_set_num_drop_frames_MSB 0x00100000 +#define HDSPM_TCO2_set_jam_sync 0x00200000 +#define HDSPM_TCO2_set_flywheel 0x00400000 + +#define HDSPM_TCO2_set_01_4 0x01000000 +#define HDSPM_TCO2_set_pull_down 0x02000000 +#define HDSPM_TCO2_set_pull_up 0x04000000 +#define HDSPM_TCO2_set_freq 0x08000000 +#define HDSPM_TCO2_set_term_75R 0x10000000 +#define HDSPM_TCO2_set_input_LSB 0x20000000 +#define HDSPM_TCO2_set_input_MSB 0x40000000 +#define HDSPM_TCO2_set_freq_from_app 0x80000000 + + +#define HDSPM_midiDataOut0 352 +#define HDSPM_midiDataOut1 356 +#define HDSPM_midiDataOut2 368 + +#define HDSPM_midiDataIn0 360 +#define HDSPM_midiDataIn1 364 +#define HDSPM_midiDataIn2 372 +#define HDSPM_midiDataIn3 376 + +/* status is data bytes in MIDI-FIFO (0-128) */ +#define HDSPM_midiStatusOut0 384 +#define HDSPM_midiStatusOut1 388 +#define HDSPM_midiStatusOut2 400 + +#define HDSPM_midiStatusIn0 392 +#define HDSPM_midiStatusIn1 396 +#define HDSPM_midiStatusIn2 404 +#define HDSPM_midiStatusIn3 408 + + +/* the meters are regular i/o-mapped registers, but offset + considerably from the rest. the peak registers are reset + when read; the least-significant 4 bits are full-scale counters; + the actual peak value is in the most-significant 24 bits. +*/ + +#define HDSPM_MADI_INPUT_PEAK 4096 +#define HDSPM_MADI_PLAYBACK_PEAK 4352 +#define HDSPM_MADI_OUTPUT_PEAK 4608 + +#define HDSPM_MADI_INPUT_RMS_L 6144 +#define HDSPM_MADI_PLAYBACK_RMS_L 6400 +#define HDSPM_MADI_OUTPUT_RMS_L 6656 + +#define HDSPM_MADI_INPUT_RMS_H 7168 +#define HDSPM_MADI_PLAYBACK_RMS_H 7424 +#define HDSPM_MADI_OUTPUT_RMS_H 7680 + +/* --- Control Register bits --------- */ +#define HDSPM_Start (1<<0) /* start engine */ + +#define HDSPM_Latency0 (1<<1) /* buffer size = 2^n */ +#define HDSPM_Latency1 (1<<2) /* where n is defined */ +#define HDSPM_Latency2 (1<<3) /* by Latency{2,1,0} */ + +#define HDSPM_ClockModeMaster (1<<4) /* 1=Master, 0=Autosync */ +#define HDSPM_c0Master 0x1 /* Master clock bit in settings + register [RayDAT, AIO] */ + +#define HDSPM_AudioInterruptEnable (1<<5) /* what do you think ? */ + +#define HDSPM_Frequency0 (1<<6) /* 0=44.1kHz/88.2kHz 1=48kHz/96kHz */ +#define HDSPM_Frequency1 (1<<7) /* 0=32kHz/64kHz */ +#define HDSPM_DoubleSpeed (1<<8) /* 0=normal speed, 1=double speed */ +#define HDSPM_QuadSpeed (1<<31) /* quad speed bit */ + +#define HDSPM_Professional (1<<9) /* Professional */ /* AES32 ONLY */ +#define HDSPM_TX_64ch (1<<10) /* Output 64channel MODE=1, + 56channelMODE=0 */ /* MADI ONLY*/ +#define HDSPM_Emphasis (1<<10) /* Emphasis */ /* AES32 ONLY */ + +#define HDSPM_AutoInp (1<<11) /* Auto Input (takeover) == Safe Mode, + 0=off, 1=on */ /* MADI ONLY */ +#define HDSPM_Dolby (1<<11) /* Dolby = "NonAudio" ?? */ /* AES32 ONLY */ + +#define HDSPM_InputSelect0 (1<<14) /* Input select 0= optical, 1=coax + * -- MADI ONLY + */ +#define HDSPM_InputSelect1 (1<<15) /* should be 0 */ + +#define HDSPM_SyncRef2 (1<<13) +#define HDSPM_SyncRef3 (1<<25) + +#define HDSPM_SMUX (1<<18) /* Frame ??? */ /* MADI ONY */ +#define HDSPM_clr_tms (1<<19) /* clear track marker, do not use + AES additional bits in + lower 5 Audiodatabits ??? */ +#define HDSPM_taxi_reset (1<<20) /* ??? */ /* MADI ONLY ? */ +#define HDSPM_WCK48 (1<<20) /* Frame ??? = HDSPM_SMUX */ /* AES32 ONLY */ + +#define HDSPM_Midi0InterruptEnable 0x0400000 +#define HDSPM_Midi1InterruptEnable 0x0800000 +#define HDSPM_Midi2InterruptEnable 0x0200000 +#define HDSPM_Midi3InterruptEnable 0x4000000 + +#define HDSPM_LineOut (1<<24) /* Analog Out on channel 63/64 on=1, mute=0 */ +#define HDSPe_FLOAT_FORMAT 0x2000000 + +#define HDSPM_DS_DoubleWire (1<<26) /* AES32 ONLY */ +#define HDSPM_QS_DoubleWire (1<<27) /* AES32 ONLY */ +#define HDSPM_QS_QuadWire (1<<28) /* AES32 ONLY */ + +#define HDSPM_wclk_sel (1<<30) + +/* additional control register bits for AIO*/ +#define HDSPM_c0_Wck48 0x20 /* also RayDAT */ +#define HDSPM_c0_Input0 0x1000 +#define HDSPM_c0_Input1 0x2000 +#define HDSPM_c0_Spdif_Opt 0x4000 +#define HDSPM_c0_Pro 0x8000 +#define HDSPM_c0_clr_tms 0x10000 +#define HDSPM_c0_AEB1 0x20000 +#define HDSPM_c0_AEB2 0x40000 +#define HDSPM_c0_LineOut 0x80000 +#define HDSPM_c0_AD_GAIN0 0x100000 +#define HDSPM_c0_AD_GAIN1 0x200000 +#define HDSPM_c0_DA_GAIN0 0x400000 +#define HDSPM_c0_DA_GAIN1 0x800000 +#define HDSPM_c0_PH_GAIN0 0x1000000 +#define HDSPM_c0_PH_GAIN1 0x2000000 +#define HDSPM_c0_Sym6db 0x4000000 + + +/* --- bit helper defines */ +#define HDSPM_LatencyMask (HDSPM_Latency0|HDSPM_Latency1|HDSPM_Latency2) +#define HDSPM_FrequencyMask (HDSPM_Frequency0|HDSPM_Frequency1|\ + HDSPM_DoubleSpeed|HDSPM_QuadSpeed) +#define HDSPM_InputMask (HDSPM_InputSelect0|HDSPM_InputSelect1) +#define HDSPM_InputOptical 0 +#define HDSPM_InputCoaxial (HDSPM_InputSelect0) +#define HDSPM_SyncRefMask (HDSPM_SyncRef0|HDSPM_SyncRef1|\ + HDSPM_SyncRef2|HDSPM_SyncRef3) + +#define HDSPM_c0_SyncRef0 0x2 +#define HDSPM_c0_SyncRef1 0x4 +#define HDSPM_c0_SyncRef2 0x8 +#define HDSPM_c0_SyncRef3 0x10 +#define HDSPM_c0_SyncRefMask (HDSPM_c0_SyncRef0 | HDSPM_c0_SyncRef1 |\ + HDSPM_c0_SyncRef2 | HDSPM_c0_SyncRef3) + +#define HDSPM_SYNC_FROM_WORD 0 /* Preferred sync reference */ +#define HDSPM_SYNC_FROM_MADI 1 /* choices - used by "pref_sync_ref" */ +#define HDSPM_SYNC_FROM_TCO 2 +#define HDSPM_SYNC_FROM_SYNC_IN 3 + +#define HDSPM_Frequency32KHz HDSPM_Frequency0 +#define HDSPM_Frequency44_1KHz HDSPM_Frequency1 +#define HDSPM_Frequency48KHz (HDSPM_Frequency1|HDSPM_Frequency0) +#define HDSPM_Frequency64KHz (HDSPM_DoubleSpeed|HDSPM_Frequency0) +#define HDSPM_Frequency88_2KHz (HDSPM_DoubleSpeed|HDSPM_Frequency1) +#define HDSPM_Frequency96KHz (HDSPM_DoubleSpeed|HDSPM_Frequency1|\ + HDSPM_Frequency0) +#define HDSPM_Frequency128KHz (HDSPM_QuadSpeed|HDSPM_Frequency0) +#define HDSPM_Frequency176_4KHz (HDSPM_QuadSpeed|HDSPM_Frequency1) +#define HDSPM_Frequency192KHz (HDSPM_QuadSpeed|HDSPM_Frequency1|\ + HDSPM_Frequency0) + + +/* Synccheck Status */ +#define HDSPM_SYNC_CHECK_NO_LOCK 0 +#define HDSPM_SYNC_CHECK_LOCK 1 +#define HDSPM_SYNC_CHECK_SYNC 2 + +/* AutoSync References - used by "autosync_ref" control switch */ +#define HDSPM_AUTOSYNC_FROM_WORD 0 +#define HDSPM_AUTOSYNC_FROM_MADI 1 +#define HDSPM_AUTOSYNC_FROM_TCO 2 +#define HDSPM_AUTOSYNC_FROM_SYNC_IN 3 +#define HDSPM_AUTOSYNC_FROM_NONE 4 + +/* Possible sources of MADI input */ +#define HDSPM_OPTICAL 0 /* optical */ +#define HDSPM_COAXIAL 1 /* BNC */ + +#define hdspm_encode_latency(x) (((x)<<1) & HDSPM_LatencyMask) +#define hdspm_decode_latency(x) ((((x) & HDSPM_LatencyMask)>>1)) + +#define hdspm_encode_in(x) (((x)&0x3)<<14) +#define hdspm_decode_in(x) (((x)>>14)&0x3) + +/* --- control2 register bits --- */ +#define HDSPM_TMS (1<<0) +#define HDSPM_TCK (1<<1) +#define HDSPM_TDI (1<<2) +#define HDSPM_JTAG (1<<3) +#define HDSPM_PWDN (1<<4) +#define HDSPM_PROGRAM (1<<5) +#define HDSPM_CONFIG_MODE_0 (1<<6) +#define HDSPM_CONFIG_MODE_1 (1<<7) +/*#define HDSPM_VERSION_BIT (1<<8) not defined any more*/ +#define HDSPM_BIGENDIAN_MODE (1<<9) +#define HDSPM_RD_MULTIPLE (1<<10) + +/* --- Status Register bits --- */ /* MADI ONLY */ /* Bits defined here and + that do not conflict with specific bits for AES32 seem to be valid also + for the AES32 + */ +#define HDSPM_audioIRQPending (1<<0) /* IRQ is high and pending */ +#define HDSPM_RX_64ch (1<<1) /* Input 64chan. MODE=1, 56chn MODE=0 */ +#define HDSPM_AB_int (1<<2) /* InputChannel Opt=0, Coax=1 + * (like inp0) + */ + +#define HDSPM_madiLock (1<<3) /* MADI Locked =1, no=0 */ +#define HDSPM_madiSync (1<<18) /* MADI is in sync */ + +#define HDSPM_tcoLockMadi 0x00000020 /* Optional TCO locked status for HDSPe MADI*/ +#define HDSPM_tcoSync 0x10000000 /* Optional TCO sync status for HDSPe MADI and AES32!*/ + +#define HDSPM_syncInLock 0x00010000 /* Sync In lock status for HDSPe MADI! */ +#define HDSPM_syncInSync 0x00020000 /* Sync In sync status for HDSPe MADI! */ + +#define HDSPM_BufferPositionMask 0x000FFC0 /* Bit 6..15 : h/w buffer pointer */ + /* since 64byte accurate, last 6 bits are not used */ + + + +#define HDSPM_DoubleSpeedStatus (1<<19) /* (input) card in double speed */ + +#define HDSPM_madiFreq0 (1<<22) /* system freq 0=error */ +#define HDSPM_madiFreq1 (1<<23) /* 1=32, 2=44.1 3=48 */ +#define HDSPM_madiFreq2 (1<<24) /* 4=64, 5=88.2 6=96 */ +#define HDSPM_madiFreq3 (1<<25) /* 7=128, 8=176.4 9=192 */ + +#define HDSPM_BufferID (1<<26) /* (Double)Buffer ID toggles with + * Interrupt + */ +#define HDSPM_tco_detect 0x08000000 +#define HDSPM_tcoLockAes 0x20000000 /* Optional TCO locked status for HDSPe AES */ + +#define HDSPM_s2_tco_detect 0x00000040 +#define HDSPM_s2_AEBO_D 0x00000080 +#define HDSPM_s2_AEBI_D 0x00000100 + + +#define HDSPM_midi0IRQPending 0x40000000 +#define HDSPM_midi1IRQPending 0x80000000 +#define HDSPM_midi2IRQPending 0x20000000 +#define HDSPM_midi2IRQPendingAES 0x00000020 +#define HDSPM_midi3IRQPending 0x00200000 + +/* --- status bit helpers */ +#define HDSPM_madiFreqMask (HDSPM_madiFreq0|HDSPM_madiFreq1|\ + HDSPM_madiFreq2|HDSPM_madiFreq3) +#define HDSPM_madiFreq32 (HDSPM_madiFreq0) +#define HDSPM_madiFreq44_1 (HDSPM_madiFreq1) +#define HDSPM_madiFreq48 (HDSPM_madiFreq0|HDSPM_madiFreq1) +#define HDSPM_madiFreq64 (HDSPM_madiFreq2) +#define HDSPM_madiFreq88_2 (HDSPM_madiFreq0|HDSPM_madiFreq2) +#define HDSPM_madiFreq96 (HDSPM_madiFreq1|HDSPM_madiFreq2) +#define HDSPM_madiFreq128 (HDSPM_madiFreq0|HDSPM_madiFreq1|HDSPM_madiFreq2) +#define HDSPM_madiFreq176_4 (HDSPM_madiFreq3) +#define HDSPM_madiFreq192 (HDSPM_madiFreq3|HDSPM_madiFreq0) + +/* Status2 Register bits */ /* MADI ONLY */ + +#define HDSPM_version0 (1<<0) /* not really defined but I guess */ +#define HDSPM_version1 (1<<1) /* in former cards it was ??? */ +#define HDSPM_version2 (1<<2) + +#define HDSPM_wcLock (1<<3) /* Wordclock is detected and locked */ +#define HDSPM_wcSync (1<<4) /* Wordclock is in sync with systemclock */ + +#define HDSPM_wc_freq0 (1<<5) /* input freq detected via autosync */ +#define HDSPM_wc_freq1 (1<<6) /* 001=32, 010==44.1, 011=48, */ +#define HDSPM_wc_freq2 (1<<7) /* 100=64, 101=88.2, 110=96, 111=128 */ +#define HDSPM_wc_freq3 0x800 /* 1000=176.4, 1001=192 */ + +#define HDSPM_SyncRef0 0x10000 /* Sync Reference */ +#define HDSPM_SyncRef1 0x20000 + +#define HDSPM_SelSyncRef0 (1<<8) /* AutoSync Source */ +#define HDSPM_SelSyncRef1 (1<<9) /* 000=word, 001=MADI, */ +#define HDSPM_SelSyncRef2 (1<<10) /* 111=no valid signal */ + +#define HDSPM_wc_valid (HDSPM_wcLock|HDSPM_wcSync) + +#define HDSPM_wcFreqMask (HDSPM_wc_freq0|HDSPM_wc_freq1|HDSPM_wc_freq2|\ + HDSPM_wc_freq3) +#define HDSPM_wcFreq32 (HDSPM_wc_freq0) +#define HDSPM_wcFreq44_1 (HDSPM_wc_freq1) +#define HDSPM_wcFreq48 (HDSPM_wc_freq0|HDSPM_wc_freq1) +#define HDSPM_wcFreq64 (HDSPM_wc_freq2) +#define HDSPM_wcFreq88_2 (HDSPM_wc_freq0|HDSPM_wc_freq2) +#define HDSPM_wcFreq96 (HDSPM_wc_freq1|HDSPM_wc_freq2) +#define HDSPM_wcFreq128 (HDSPM_wc_freq0|HDSPM_wc_freq1|HDSPM_wc_freq2) +#define HDSPM_wcFreq176_4 (HDSPM_wc_freq3) +#define HDSPM_wcFreq192 (HDSPM_wc_freq0|HDSPM_wc_freq3) + +#define HDSPM_status1_F_0 0x0400000 +#define HDSPM_status1_F_1 0x0800000 +#define HDSPM_status1_F_2 0x1000000 +#define HDSPM_status1_F_3 0x2000000 +#define HDSPM_status1_freqMask (HDSPM_status1_F_0|HDSPM_status1_F_1|HDSPM_status1_F_2|HDSPM_status1_F_3) + + +#define HDSPM_SelSyncRefMask (HDSPM_SelSyncRef0|HDSPM_SelSyncRef1|\ + HDSPM_SelSyncRef2) +#define HDSPM_SelSyncRef_WORD 0 +#define HDSPM_SelSyncRef_MADI (HDSPM_SelSyncRef0) +#define HDSPM_SelSyncRef_TCO (HDSPM_SelSyncRef1) +#define HDSPM_SelSyncRef_SyncIn (HDSPM_SelSyncRef0|HDSPM_SelSyncRef1) +#define HDSPM_SelSyncRef_NVALID (HDSPM_SelSyncRef0|HDSPM_SelSyncRef1|\ + HDSPM_SelSyncRef2) + +/* + For AES32, bits for status, status2 and timecode are different +*/ +/* status */ +#define HDSPM_AES32_wcLock 0x0200000 +#define HDSPM_AES32_wcSync 0x0100000 +#define HDSPM_AES32_wcFreq_bit 22 +/* (status >> HDSPM_AES32_wcFreq_bit) & 0xF gives WC frequency (cf function + HDSPM_bit2freq */ +#define HDSPM_AES32_syncref_bit 16 +/* (status >> HDSPM_AES32_syncref_bit) & 0xF gives sync source */ + +#define HDSPM_AES32_AUTOSYNC_FROM_WORD 0 +#define HDSPM_AES32_AUTOSYNC_FROM_AES1 1 +#define HDSPM_AES32_AUTOSYNC_FROM_AES2 2 +#define HDSPM_AES32_AUTOSYNC_FROM_AES3 3 +#define HDSPM_AES32_AUTOSYNC_FROM_AES4 4 +#define HDSPM_AES32_AUTOSYNC_FROM_AES5 5 +#define HDSPM_AES32_AUTOSYNC_FROM_AES6 6 +#define HDSPM_AES32_AUTOSYNC_FROM_AES7 7 +#define HDSPM_AES32_AUTOSYNC_FROM_AES8 8 +#define HDSPM_AES32_AUTOSYNC_FROM_TCO 9 +#define HDSPM_AES32_AUTOSYNC_FROM_SYNC_IN 10 +#define HDSPM_AES32_AUTOSYNC_FROM_NONE 11 + +/* status2 */ +/* HDSPM_LockAES_bit is given by HDSPM_LockAES >> (AES# - 1) */ +#define HDSPM_LockAES 0x80 +#define HDSPM_LockAES1 0x80 +#define HDSPM_LockAES2 0x40 +#define HDSPM_LockAES3 0x20 +#define HDSPM_LockAES4 0x10 +#define HDSPM_LockAES5 0x8 +#define HDSPM_LockAES6 0x4 +#define HDSPM_LockAES7 0x2 +#define HDSPM_LockAES8 0x1 +/* + Timecode + After windows driver sources, bits 4*i to 4*i+3 give the input frequency on + AES i+1 + bits 3210 + 0001 32kHz + 0010 44.1kHz + 0011 48kHz + 0100 64kHz + 0101 88.2kHz + 0110 96kHz + 0111 128kHz + 1000 176.4kHz + 1001 192kHz + NB: Timecode register doesn't seem to work on AES32 card revision 230 +*/ + +/* Mixer Values */ +#define UNITY_GAIN 32768 /* = 65536/2 */ +#define MINUS_INFINITY_GAIN 0 + +/* Number of channels for different Speed Modes */ +#define MADI_SS_CHANNELS 64 +#define MADI_DS_CHANNELS 32 +#define MADI_QS_CHANNELS 16 + +#define RAYDAT_SS_CHANNELS 36 +#define RAYDAT_DS_CHANNELS 20 +#define RAYDAT_QS_CHANNELS 12 + +#define AIO_IN_SS_CHANNELS 14 +#define AIO_IN_DS_CHANNELS 10 +#define AIO_IN_QS_CHANNELS 8 +#define AIO_OUT_SS_CHANNELS 16 +#define AIO_OUT_DS_CHANNELS 12 +#define AIO_OUT_QS_CHANNELS 10 + +#define AES32_CHANNELS 16 + +/* the size of a substream (1 mono data stream) */ +#define HDSPM_CHANNEL_BUFFER_SAMPLES (16*1024) +#define HDSPM_CHANNEL_BUFFER_BYTES (4*HDSPM_CHANNEL_BUFFER_SAMPLES) + +/* the size of the area we need to allocate for DMA transfers. the + size is the same regardless of the number of channels, and + also the latency to use. + for one direction !!! +*/ +#define HDSPM_DMA_AREA_BYTES (HDSPM_MAX_CHANNELS * HDSPM_CHANNEL_BUFFER_BYTES) +#define HDSPM_DMA_AREA_KILOBYTES (HDSPM_DMA_AREA_BYTES/1024) + +#define HDSPM_RAYDAT_REV 211 +#define HDSPM_AIO_REV 212 +#define HDSPM_MADIFACE_REV 213 + +/* speed factor modes */ +#define HDSPM_SPEED_SINGLE 0 +#define HDSPM_SPEED_DOUBLE 1 +#define HDSPM_SPEED_QUAD 2 + +/* names for speed modes */ +static const char * const hdspm_speed_names[] = { "single", "double", "quad" }; + +static const char *const texts_autosync_aes_tco[] = { "Word Clock", + "AES1", "AES2", "AES3", "AES4", + "AES5", "AES6", "AES7", "AES8", + "TCO", "Sync In" +}; +static const char *const texts_autosync_aes[] = { "Word Clock", + "AES1", "AES2", "AES3", "AES4", + "AES5", "AES6", "AES7", "AES8", + "Sync In" +}; +static const char *const texts_autosync_madi_tco[] = { "Word Clock", + "MADI", "TCO", "Sync In" }; +static const char *const texts_autosync_madi[] = { "Word Clock", + "MADI", "Sync In" }; + +static const char *const texts_autosync_raydat_tco[] = { + "Word Clock", + "ADAT 1", "ADAT 2", "ADAT 3", "ADAT 4", + "AES", "SPDIF", "TCO", "Sync In" +}; +static const char *const texts_autosync_raydat[] = { + "Word Clock", + "ADAT 1", "ADAT 2", "ADAT 3", "ADAT 4", + "AES", "SPDIF", "Sync In" +}; +static const char *const texts_autosync_aio_tco[] = { + "Word Clock", + "ADAT", "AES", "SPDIF", "TCO", "Sync In" +}; +static const char *const texts_autosync_aio[] = { "Word Clock", + "ADAT", "AES", "SPDIF", "Sync In" }; + +static const char *const texts_freq[] = { + "No Lock", + "32 kHz", + "44.1 kHz", + "48 kHz", + "64 kHz", + "88.2 kHz", + "96 kHz", + "128 kHz", + "176.4 kHz", + "192 kHz" +}; + +static const char * const texts_ports_madi[] = { + "MADI.1", "MADI.2", "MADI.3", "MADI.4", "MADI.5", "MADI.6", + "MADI.7", "MADI.8", "MADI.9", "MADI.10", "MADI.11", "MADI.12", + "MADI.13", "MADI.14", "MADI.15", "MADI.16", "MADI.17", "MADI.18", + "MADI.19", "MADI.20", "MADI.21", "MADI.22", "MADI.23", "MADI.24", + "MADI.25", "MADI.26", "MADI.27", "MADI.28", "MADI.29", "MADI.30", + "MADI.31", "MADI.32", "MADI.33", "MADI.34", "MADI.35", "MADI.36", + "MADI.37", "MADI.38", "MADI.39", "MADI.40", "MADI.41", "MADI.42", + "MADI.43", "MADI.44", "MADI.45", "MADI.46", "MADI.47", "MADI.48", + "MADI.49", "MADI.50", "MADI.51", "MADI.52", "MADI.53", "MADI.54", + "MADI.55", "MADI.56", "MADI.57", "MADI.58", "MADI.59", "MADI.60", + "MADI.61", "MADI.62", "MADI.63", "MADI.64", +}; + + +static const char * const texts_ports_raydat_ss[] = { + "ADAT1.1", "ADAT1.2", "ADAT1.3", "ADAT1.4", "ADAT1.5", "ADAT1.6", + "ADAT1.7", "ADAT1.8", "ADAT2.1", "ADAT2.2", "ADAT2.3", "ADAT2.4", + "ADAT2.5", "ADAT2.6", "ADAT2.7", "ADAT2.8", "ADAT3.1", "ADAT3.2", + "ADAT3.3", "ADAT3.4", "ADAT3.5", "ADAT3.6", "ADAT3.7", "ADAT3.8", + "ADAT4.1", "ADAT4.2", "ADAT4.3", "ADAT4.4", "ADAT4.5", "ADAT4.6", + "ADAT4.7", "ADAT4.8", + "AES.L", "AES.R", + "SPDIF.L", "SPDIF.R" +}; + +static const char * const texts_ports_raydat_ds[] = { + "ADAT1.1", "ADAT1.2", "ADAT1.3", "ADAT1.4", + "ADAT2.1", "ADAT2.2", "ADAT2.3", "ADAT2.4", + "ADAT3.1", "ADAT3.2", "ADAT3.3", "ADAT3.4", + "ADAT4.1", "ADAT4.2", "ADAT4.3", "ADAT4.4", + "AES.L", "AES.R", + "SPDIF.L", "SPDIF.R" +}; + +static const char * const texts_ports_raydat_qs[] = { + "ADAT1.1", "ADAT1.2", + "ADAT2.1", "ADAT2.2", + "ADAT3.1", "ADAT3.2", + "ADAT4.1", "ADAT4.2", + "AES.L", "AES.R", + "SPDIF.L", "SPDIF.R" +}; + + +static const char * const texts_ports_aio_in_ss[] = { + "Analogue.L", "Analogue.R", + "AES.L", "AES.R", + "SPDIF.L", "SPDIF.R", + "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", "ADAT.5", "ADAT.6", + "ADAT.7", "ADAT.8", + "AEB.1", "AEB.2", "AEB.3", "AEB.4" +}; + +static const char * const texts_ports_aio_out_ss[] = { + "Analogue.L", "Analogue.R", + "AES.L", "AES.R", + "SPDIF.L", "SPDIF.R", + "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", "ADAT.5", "ADAT.6", + "ADAT.7", "ADAT.8", + "Phone.L", "Phone.R", + "AEB.1", "AEB.2", "AEB.3", "AEB.4" +}; + +static const char * const texts_ports_aio_in_ds[] = { + "Analogue.L", "Analogue.R", + "AES.L", "AES.R", + "SPDIF.L", "SPDIF.R", + "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", + "AEB.1", "AEB.2", "AEB.3", "AEB.4" +}; + +static const char * const texts_ports_aio_out_ds[] = { + "Analogue.L", "Analogue.R", + "AES.L", "AES.R", + "SPDIF.L", "SPDIF.R", + "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", + "Phone.L", "Phone.R", + "AEB.1", "AEB.2", "AEB.3", "AEB.4" +}; + +static const char * const texts_ports_aio_in_qs[] = { + "Analogue.L", "Analogue.R", + "AES.L", "AES.R", + "SPDIF.L", "SPDIF.R", + "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", + "AEB.1", "AEB.2", "AEB.3", "AEB.4" +}; + +static const char * const texts_ports_aio_out_qs[] = { + "Analogue.L", "Analogue.R", + "AES.L", "AES.R", + "SPDIF.L", "SPDIF.R", + "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", + "Phone.L", "Phone.R", + "AEB.1", "AEB.2", "AEB.3", "AEB.4" +}; + +static const char * const texts_ports_aes32[] = { + "AES.1", "AES.2", "AES.3", "AES.4", "AES.5", "AES.6", "AES.7", + "AES.8", "AES.9.", "AES.10", "AES.11", "AES.12", "AES.13", "AES.14", + "AES.15", "AES.16" +}; + +/* These tables map the ALSA channels 1..N to the channels that we + need to use in order to find the relevant channel buffer. RME + refers to this kind of mapping as between "the ADAT channel and + the DMA channel." We index it using the logical audio channel, + and the value is the DMA channel (i.e. channel buffer number) + where the data for that channel can be read/written from/to. +*/ + +static const char channel_map_unity_ss[HDSPM_MAX_CHANNELS] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63 +}; + +static const char channel_map_raydat_ss[HDSPM_MAX_CHANNELS] = { + 4, 5, 6, 7, 8, 9, 10, 11, /* ADAT 1 */ + 12, 13, 14, 15, 16, 17, 18, 19, /* ADAT 2 */ + 20, 21, 22, 23, 24, 25, 26, 27, /* ADAT 3 */ + 28, 29, 30, 31, 32, 33, 34, 35, /* ADAT 4 */ + 0, 1, /* AES */ + 2, 3, /* SPDIF */ + -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, +}; + +static const char channel_map_raydat_ds[HDSPM_MAX_CHANNELS] = { + 4, 5, 6, 7, /* ADAT 1 */ + 8, 9, 10, 11, /* ADAT 2 */ + 12, 13, 14, 15, /* ADAT 3 */ + 16, 17, 18, 19, /* ADAT 4 */ + 0, 1, /* AES */ + 2, 3, /* SPDIF */ + -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, +}; + +static const char channel_map_raydat_qs[HDSPM_MAX_CHANNELS] = { + 4, 5, /* ADAT 1 */ + 6, 7, /* ADAT 2 */ + 8, 9, /* ADAT 3 */ + 10, 11, /* ADAT 4 */ + 0, 1, /* AES */ + 2, 3, /* SPDIF */ + -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, +}; + +static const char channel_map_aio_in_ss[HDSPM_MAX_CHANNELS] = { + 0, 1, /* line in */ + 8, 9, /* aes in, */ + 10, 11, /* spdif in */ + 12, 13, 14, 15, 16, 17, 18, 19, /* ADAT in */ + 2, 3, 4, 5, /* AEB */ + -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, +}; + +static const char channel_map_aio_out_ss[HDSPM_MAX_CHANNELS] = { + 0, 1, /* line out */ + 8, 9, /* aes out */ + 10, 11, /* spdif out */ + 12, 13, 14, 15, 16, 17, 18, 19, /* ADAT out */ + 6, 7, /* phone out */ + 2, 3, 4, 5, /* AEB */ + -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, +}; + +static const char channel_map_aio_in_ds[HDSPM_MAX_CHANNELS] = { + 0, 1, /* line in */ + 8, 9, /* aes in */ + 10, 11, /* spdif in */ + 12, 14, 16, 18, /* adat in */ + 2, 3, 4, 5, /* AEB */ + -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const char channel_map_aio_out_ds[HDSPM_MAX_CHANNELS] = { + 0, 1, /* line out */ + 8, 9, /* aes out */ + 10, 11, /* spdif out */ + 12, 14, 16, 18, /* adat out */ + 6, 7, /* phone out */ + 2, 3, 4, 5, /* AEB */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const char channel_map_aio_in_qs[HDSPM_MAX_CHANNELS] = { + 0, 1, /* line in */ + 8, 9, /* aes in */ + 10, 11, /* spdif in */ + 12, 16, /* adat in */ + 2, 3, 4, 5, /* AEB */ + -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const char channel_map_aio_out_qs[HDSPM_MAX_CHANNELS] = { + 0, 1, /* line out */ + 8, 9, /* aes out */ + 10, 11, /* spdif out */ + 12, 16, /* adat out */ + 6, 7, /* phone out */ + 2, 3, 4, 5, /* AEB */ + -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const char channel_map_aes32[HDSPM_MAX_CHANNELS] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 +}; + +struct hdspm_midi { + struct hdspm *hdspm; + int id; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *input; + struct snd_rawmidi_substream *output; + char istimer; /* timer in use */ + struct timer_list timer; + spinlock_t lock; + int pending; + int dataIn; + int statusIn; + int dataOut; + int statusOut; + int ie; + int irq; +}; + +struct hdspm_tco { + int input; /* 0: LTC, 1:Video, 2: WC*/ + int framerate; /* 0=24, 1=25, 2=29.97, 3=29.97d, 4=30, 5=30d */ + int wordclock; /* 0=1:1, 1=44.1->48, 2=48->44.1 */ + int samplerate; /* 0=44.1, 1=48, 2= freq from app */ + int pull; /* 0=0, 1=+0.1%, 2=-0.1%, 3=+4%, 4=-4%*/ + int term; /* 0 = off, 1 = on */ +}; + +struct hdspm { + spinlock_t lock; + /* only one playback and/or capture stream */ + struct snd_pcm_substream *capture_substream; + struct snd_pcm_substream *playback_substream; + + char *card_name; /* for procinfo */ + unsigned short firmware_rev; /* dont know if relevant (yes if AES32)*/ + + uint8_t io_type; + + int monitor_outs; /* set up monitoring outs init flag */ + + u32 control_register; /* cached value */ + u32 control2_register; /* cached value */ + u32 settings_register; /* cached value for AIO / RayDat (sync reference, master/slave) */ + + struct hdspm_midi midi[4]; + struct work_struct midi_work; + + size_t period_bytes; + unsigned char ss_in_channels; + unsigned char ds_in_channels; + unsigned char qs_in_channels; + unsigned char ss_out_channels; + unsigned char ds_out_channels; + unsigned char qs_out_channels; + + unsigned char max_channels_in; + unsigned char max_channels_out; + + const signed char *channel_map_in; + const signed char *channel_map_out; + + const signed char *channel_map_in_ss, *channel_map_in_ds, *channel_map_in_qs; + const signed char *channel_map_out_ss, *channel_map_out_ds, *channel_map_out_qs; + + const char * const *port_names_in; + const char * const *port_names_out; + + const char * const *port_names_in_ss; + const char * const *port_names_in_ds; + const char * const *port_names_in_qs; + const char * const *port_names_out_ss; + const char * const *port_names_out_ds; + const char * const *port_names_out_qs; + + unsigned char *playback_buffer; /* suitably aligned address */ + unsigned char *capture_buffer; /* suitably aligned address */ + + pid_t capture_pid; /* process id which uses capture */ + pid_t playback_pid; /* process id which uses capture */ + int running; /* running status */ + + int last_external_sample_rate; /* samplerate mystic ... */ + int last_internal_sample_rate; + int system_sample_rate; + + int dev; /* Hardware vars... */ + int irq; + unsigned long port; + void __iomem *iobase; + + int irq_count; /* for debug */ + int midiPorts; + + struct snd_card *card; /* one card */ + struct snd_pcm *pcm; /* has one pcm */ + struct snd_hwdep *hwdep; /* and a hwdep for additional ioctl */ + struct pci_dev *pci; /* and an pci info */ + + /* Mixer vars */ + /* fast alsa mixer */ + struct snd_kcontrol *playback_mixer_ctls[HDSPM_MAX_CHANNELS]; + /* but input to much, so not used */ + struct snd_kcontrol *input_mixer_ctls[HDSPM_MAX_CHANNELS]; + /* full mixer accessible over mixer ioctl or hwdep-device */ + struct hdspm_mixer *mixer; + + struct hdspm_tco *tco; /* NULL if no TCO detected */ + + const char *const *texts_autosync; + int texts_autosync_items; + + cycles_t last_interrupt; + + unsigned int serial; + + struct hdspm_peak_rms peak_rms; +}; + + +static const struct pci_device_id snd_hdspm_ids[] = { + { + .vendor = PCI_VENDOR_ID_XILINX, + .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = 0, + .class_mask = 0, + .driver_data = 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, snd_hdspm_ids); + +/* prototypes */ +static int snd_hdspm_create_alsa_devices(struct snd_card *card, + struct hdspm *hdspm); +static int snd_hdspm_create_pcm(struct snd_card *card, + struct hdspm *hdspm); + +static inline void snd_hdspm_initialize_midi_flush(struct hdspm *hdspm); +static inline int hdspm_get_pll_freq(struct hdspm *hdspm); +static int hdspm_update_simple_mixer_controls(struct hdspm *hdspm); +static int hdspm_autosync_ref(struct hdspm *hdspm); +static int hdspm_set_toggle_setting(struct hdspm *hdspm, u32 regmask, int out); +static int snd_hdspm_set_defaults(struct hdspm *hdspm); +static int hdspm_system_clock_mode(struct hdspm *hdspm); +static void hdspm_set_channel_dma_addr(struct hdspm *hdspm, + struct snd_pcm_substream *substream, + unsigned int reg, int channels); + +static int hdspm_aes_sync_check(struct hdspm *hdspm, int idx); +static int hdspm_wc_sync_check(struct hdspm *hdspm); +static int hdspm_tco_sync_check(struct hdspm *hdspm); +static int hdspm_sync_in_sync_check(struct hdspm *hdspm); + +static int hdspm_get_aes_sample_rate(struct hdspm *hdspm, int index); +static int hdspm_get_tco_sample_rate(struct hdspm *hdspm); +static int hdspm_get_wc_sample_rate(struct hdspm *hdspm); + + + +static inline int HDSPM_bit2freq(int n) +{ + static const int bit2freq_tab[] = { + 0, 32000, 44100, 48000, 64000, 88200, + 96000, 128000, 176400, 192000 }; + if (n < 1 || n > 9) + return 0; + return bit2freq_tab[n]; +} + +static bool hdspm_is_raydat_or_aio(struct hdspm *hdspm) +{ + return ((AIO == hdspm->io_type) || (RayDAT == hdspm->io_type)); +} + + +/* Write/read to/from HDSPM with Adresses in Bytes + not words but only 32Bit writes are allowed */ + +static inline void hdspm_write(struct hdspm * hdspm, unsigned int reg, + unsigned int val) +{ + writel(val, hdspm->iobase + reg); +} + +static inline unsigned int hdspm_read(struct hdspm * hdspm, unsigned int reg) +{ + return readl(hdspm->iobase + reg); +} + +/* for each output channel (chan) I have an Input (in) and Playback (pb) Fader + mixer is write only on hardware so we have to cache him for read + each fader is a u32, but uses only the first 16 bit */ + +static inline int hdspm_read_in_gain(struct hdspm * hdspm, unsigned int chan, + unsigned int in) +{ + if (chan >= HDSPM_MIXER_CHANNELS || in >= HDSPM_MIXER_CHANNELS) + return 0; + + return hdspm->mixer->ch[chan].in[in]; +} + +static inline int hdspm_read_pb_gain(struct hdspm * hdspm, unsigned int chan, + unsigned int pb) +{ + if (chan >= HDSPM_MIXER_CHANNELS || pb >= HDSPM_MIXER_CHANNELS) + return 0; + return hdspm->mixer->ch[chan].pb[pb]; +} + +static int hdspm_write_in_gain(struct hdspm *hdspm, unsigned int chan, + unsigned int in, unsigned short data) +{ + if (chan >= HDSPM_MIXER_CHANNELS || in >= HDSPM_MIXER_CHANNELS) + return -1; + + hdspm_write(hdspm, + HDSPM_MADI_mixerBase + + ((in + 128 * chan) * sizeof(u32)), + (hdspm->mixer->ch[chan].in[in] = data & 0xFFFF)); + return 0; +} + +static int hdspm_write_pb_gain(struct hdspm *hdspm, unsigned int chan, + unsigned int pb, unsigned short data) +{ + if (chan >= HDSPM_MIXER_CHANNELS || pb >= HDSPM_MIXER_CHANNELS) + return -1; + + hdspm_write(hdspm, + HDSPM_MADI_mixerBase + + ((64 + pb + 128 * chan) * sizeof(u32)), + (hdspm->mixer->ch[chan].pb[pb] = data & 0xFFFF)); + return 0; +} + + +/* enable DMA for specific channels, now available for DSP-MADI */ +static inline void snd_hdspm_enable_in(struct hdspm * hdspm, int i, int v) +{ + hdspm_write(hdspm, HDSPM_inputEnableBase + (4 * i), v); +} + +static inline void snd_hdspm_enable_out(struct hdspm * hdspm, int i, int v) +{ + hdspm_write(hdspm, HDSPM_outputEnableBase + (4 * i), v); +} + +/* check if same process is writing and reading */ +static int snd_hdspm_use_is_exclusive(struct hdspm *hdspm) +{ + guard(spinlock_irqsave)(&hdspm->lock); + if ((hdspm->playback_pid != hdspm->capture_pid) && + (hdspm->playback_pid >= 0) && (hdspm->capture_pid >= 0)) + return 0; + return 1; +} + +/* round arbitrary sample rates to commonly known rates */ +static int hdspm_round_frequency(int rate) +{ + if (rate < 38050) + return 32000; + if (rate < 46008) + return 44100; + else + return 48000; +} + +/* QS and DS rates normally can not be detected + * automatically by the card. Only exception is MADI + * in 96k frame mode. + * + * So if we read SS values (32 .. 48k), check for + * user-provided DS/QS bits in the control register + * and multiply the base frequency accordingly. + */ +static int hdspm_rate_multiplier(struct hdspm *hdspm, int rate) +{ + if (rate <= 48000) { + if (hdspm->control_register & HDSPM_QuadSpeed) + return rate * 4; + else if (hdspm->control_register & + HDSPM_DoubleSpeed) + return rate * 2; + } + return rate; +} + +/* check for external sample rate, returns the sample rate in Hz*/ +static int hdspm_external_sample_rate(struct hdspm *hdspm) +{ + unsigned int status, status2; + int syncref, rate = 0, rate_bits; + + switch (hdspm->io_type) { + case AES32: + status2 = hdspm_read(hdspm, HDSPM_statusRegister2); + status = hdspm_read(hdspm, HDSPM_statusRegister); + + syncref = hdspm_autosync_ref(hdspm); + switch (syncref) { + case HDSPM_AES32_AUTOSYNC_FROM_WORD: + /* Check WC sync and get sample rate */ + if (hdspm_wc_sync_check(hdspm)) + return HDSPM_bit2freq(hdspm_get_wc_sample_rate(hdspm)); + break; + + case HDSPM_AES32_AUTOSYNC_FROM_AES1: + case HDSPM_AES32_AUTOSYNC_FROM_AES2: + case HDSPM_AES32_AUTOSYNC_FROM_AES3: + case HDSPM_AES32_AUTOSYNC_FROM_AES4: + case HDSPM_AES32_AUTOSYNC_FROM_AES5: + case HDSPM_AES32_AUTOSYNC_FROM_AES6: + case HDSPM_AES32_AUTOSYNC_FROM_AES7: + case HDSPM_AES32_AUTOSYNC_FROM_AES8: + /* Check AES sync and get sample rate */ + if (hdspm_aes_sync_check(hdspm, syncref - HDSPM_AES32_AUTOSYNC_FROM_AES1)) + return HDSPM_bit2freq(hdspm_get_aes_sample_rate(hdspm, + syncref - HDSPM_AES32_AUTOSYNC_FROM_AES1)); + break; + + + case HDSPM_AES32_AUTOSYNC_FROM_TCO: + /* Check TCO sync and get sample rate */ + if (hdspm_tco_sync_check(hdspm)) + return HDSPM_bit2freq(hdspm_get_tco_sample_rate(hdspm)); + break; + default: + return 0; + } /* end switch(syncref) */ + break; + + case MADIface: + status = hdspm_read(hdspm, HDSPM_statusRegister); + + if (!(status & HDSPM_madiLock)) { + rate = 0; /* no lock */ + } else { + switch (status & (HDSPM_status1_freqMask)) { + case HDSPM_status1_F_0*1: + rate = 32000; break; + case HDSPM_status1_F_0*2: + rate = 44100; break; + case HDSPM_status1_F_0*3: + rate = 48000; break; + case HDSPM_status1_F_0*4: + rate = 64000; break; + case HDSPM_status1_F_0*5: + rate = 88200; break; + case HDSPM_status1_F_0*6: + rate = 96000; break; + case HDSPM_status1_F_0*7: + rate = 128000; break; + case HDSPM_status1_F_0*8: + rate = 176400; break; + case HDSPM_status1_F_0*9: + rate = 192000; break; + default: + rate = 0; break; + } + } + + break; + + case MADI: + case AIO: + case RayDAT: + status2 = hdspm_read(hdspm, HDSPM_statusRegister2); + status = hdspm_read(hdspm, HDSPM_statusRegister); + rate = 0; + + /* if wordclock has synced freq and wordclock is valid */ + if ((status2 & HDSPM_wcLock) != 0 && + (status2 & HDSPM_SelSyncRef0) == 0) { + + rate_bits = status2 & HDSPM_wcFreqMask; + + + switch (rate_bits) { + case HDSPM_wcFreq32: + rate = 32000; + break; + case HDSPM_wcFreq44_1: + rate = 44100; + break; + case HDSPM_wcFreq48: + rate = 48000; + break; + case HDSPM_wcFreq64: + rate = 64000; + break; + case HDSPM_wcFreq88_2: + rate = 88200; + break; + case HDSPM_wcFreq96: + rate = 96000; + break; + case HDSPM_wcFreq128: + rate = 128000; + break; + case HDSPM_wcFreq176_4: + rate = 176400; + break; + case HDSPM_wcFreq192: + rate = 192000; + break; + default: + rate = 0; + break; + } + } + + /* if rate detected and Syncref is Word than have it, + * word has priority to MADI + */ + if (rate != 0 && + (status2 & HDSPM_SelSyncRefMask) == HDSPM_SelSyncRef_WORD) + return hdspm_rate_multiplier(hdspm, rate); + + /* maybe a madi input (which is taken if sel sync is madi) */ + if (status & HDSPM_madiLock) { + rate_bits = status & HDSPM_madiFreqMask; + + switch (rate_bits) { + case HDSPM_madiFreq32: + rate = 32000; + break; + case HDSPM_madiFreq44_1: + rate = 44100; + break; + case HDSPM_madiFreq48: + rate = 48000; + break; + case HDSPM_madiFreq64: + rate = 64000; + break; + case HDSPM_madiFreq88_2: + rate = 88200; + break; + case HDSPM_madiFreq96: + rate = 96000; + break; + case HDSPM_madiFreq128: + rate = 128000; + break; + case HDSPM_madiFreq176_4: + rate = 176400; + break; + case HDSPM_madiFreq192: + rate = 192000; + break; + default: + rate = 0; + break; + } + + } /* endif HDSPM_madiLock */ + + /* check sample rate from TCO or SYNC_IN */ + { + bool is_valid_input = 0; + bool has_sync = 0; + + syncref = hdspm_autosync_ref(hdspm); + if (HDSPM_AUTOSYNC_FROM_TCO == syncref) { + is_valid_input = 1; + has_sync = (HDSPM_SYNC_CHECK_SYNC == + hdspm_tco_sync_check(hdspm)); + } else if (HDSPM_AUTOSYNC_FROM_SYNC_IN == syncref) { + is_valid_input = 1; + has_sync = (HDSPM_SYNC_CHECK_SYNC == + hdspm_sync_in_sync_check(hdspm)); + } + + if (is_valid_input && has_sync) { + rate = hdspm_round_frequency( + hdspm_get_pll_freq(hdspm)); + } + } + + rate = hdspm_rate_multiplier(hdspm, rate); + + break; + } + + return rate; +} + +/* return latency in samples per period */ +static int hdspm_get_latency(struct hdspm *hdspm) +{ + int n; + + n = hdspm_decode_latency(hdspm->control_register); + + /* Special case for new RME cards with 32 samples period size. + * The three latency bits in the control register + * (HDSP_LatencyMask) encode latency values of 64 samples as + * 0, 128 samples as 1 ... 4096 samples as 6. For old cards, 7 + * denotes 8192 samples, but on new cards like RayDAT or AIO, + * it corresponds to 32 samples. + */ + if ((7 == n) && (RayDAT == hdspm->io_type || AIO == hdspm->io_type)) + n = -1; + + return 1 << (n + 6); +} + +/* Latency function */ +static inline void hdspm_compute_period_size(struct hdspm *hdspm) +{ + hdspm->period_bytes = 4 * hdspm_get_latency(hdspm); +} + + +static snd_pcm_uframes_t hdspm_hw_pointer(struct hdspm *hdspm) +{ + int position; + + position = hdspm_read(hdspm, HDSPM_statusRegister); + + switch (hdspm->io_type) { + case RayDAT: + case AIO: + position &= HDSPM_BufferPositionMask; + position /= 4; /* Bytes per sample */ + break; + default: + position = (position & HDSPM_BufferID) ? + (hdspm->period_bytes / 4) : 0; + } + + return position; +} + + +static inline void hdspm_start_audio(struct hdspm * s) +{ + s->control_register |= (HDSPM_AudioInterruptEnable | HDSPM_Start); + hdspm_write(s, HDSPM_controlRegister, s->control_register); +} + +static inline void hdspm_stop_audio(struct hdspm * s) +{ + s->control_register &= ~(HDSPM_Start | HDSPM_AudioInterruptEnable); + hdspm_write(s, HDSPM_controlRegister, s->control_register); +} + +/* should I silence all or only opened ones ? doit all for first even is 4MB*/ +static void hdspm_silence_playback(struct hdspm *hdspm) +{ + int i; + int n = hdspm->period_bytes; + void *buf = hdspm->playback_buffer; + + if (!buf) + return; + + for (i = 0; i < HDSPM_MAX_CHANNELS; i++) { + memset(buf, 0, n); + buf += HDSPM_CHANNEL_BUFFER_BYTES; + } +} + +static int hdspm_set_interrupt_interval(struct hdspm *s, unsigned int frames) +{ + int n; + + guard(spinlock_irq)(&s->lock); + + if (32 == frames) { + /* Special case for new RME cards like RayDAT/AIO which + * support period sizes of 32 samples. Since latency is + * encoded in the three bits of HDSP_LatencyMask, we can only + * have values from 0 .. 7. While 0 still means 64 samples and + * 6 represents 4096 samples on all cards, 7 represents 8192 + * on older cards and 32 samples on new cards. + * + * In other words, period size in samples is calculated by + * 2^(n+6) with n ranging from 0 .. 7. + */ + n = 7; + } else { + frames >>= 7; + n = 0; + while (frames) { + n++; + frames >>= 1; + } + } + + s->control_register &= ~HDSPM_LatencyMask; + s->control_register |= hdspm_encode_latency(n); + + hdspm_write(s, HDSPM_controlRegister, s->control_register); + + hdspm_compute_period_size(s); + + return 0; +} + +static u64 hdspm_calc_dds_value(struct hdspm *hdspm, u64 period) +{ + u64 freq_const; + + if (period == 0) + return 0; + + switch (hdspm->io_type) { + case MADI: + case AES32: + freq_const = 110069313433624ULL; + break; + case RayDAT: + case AIO: + freq_const = 104857600000000ULL; + break; + case MADIface: + freq_const = 131072000000000ULL; + break; + default: + snd_BUG(); + return 0; + } + + return div_u64(freq_const, period); +} + + +static void hdspm_set_dds_value(struct hdspm *hdspm, int rate) +{ + u64 n; + + if (snd_BUG_ON(rate <= 0)) + return; + + if (rate >= 112000) + rate /= 4; + else if (rate >= 56000) + rate /= 2; + + switch (hdspm->io_type) { + case MADIface: + n = 131072000000000ULL; /* 125 MHz */ + break; + case MADI: + case AES32: + n = 110069313433624ULL; /* 105 MHz */ + break; + case RayDAT: + case AIO: + n = 104857600000000ULL; /* 100 MHz */ + break; + default: + snd_BUG(); + return; + } + + n = div_u64(n, rate); + /* n should be less than 2^32 for being written to FREQ register */ + snd_BUG_ON(n >> 32); + hdspm_write(hdspm, HDSPM_freqReg, (u32)n); +} + +/* dummy set rate lets see what happens */ +static int hdspm_set_rate(struct hdspm * hdspm, int rate, int called_internally) +{ + int current_rate; + int rate_bits; + int not_set = 0; + int current_speed, target_speed; + + /* ASSUMPTION: hdspm->lock is either set, or there is no need for + it (e.g. during module initialization). + */ + + if (!(hdspm->control_register & HDSPM_ClockModeMaster)) { + + /* SLAVE --- */ + if (called_internally) { + + /* request from ctl or card initialization + just make a warning an remember setting + for future master mode switching */ + + dev_warn(hdspm->card->dev, + "Warning: device is not running as a clock master.\n"); + not_set = 1; + } else { + + /* hw_param request while in AutoSync mode */ + int external_freq = + hdspm_external_sample_rate(hdspm); + + if (hdspm_autosync_ref(hdspm) == + HDSPM_AUTOSYNC_FROM_NONE) { + + dev_warn(hdspm->card->dev, + "Detected no External Sync\n"); + not_set = 1; + + } else if (rate != external_freq) { + + dev_warn(hdspm->card->dev, + "Warning: No AutoSync source for requested rate\n"); + not_set = 1; + } + } + } + + current_rate = hdspm->system_sample_rate; + + /* Changing between Singe, Double and Quad speed is not + allowed if any substreams are open. This is because such a change + causes a shift in the location of the DMA buffers and a reduction + in the number of available buffers. + + Note that a similar but essentially insoluble problem exists for + externally-driven rate changes. All we can do is to flag rate + changes in the read/write routines. + */ + + if (current_rate <= 48000) + current_speed = HDSPM_SPEED_SINGLE; + else if (current_rate <= 96000) + current_speed = HDSPM_SPEED_DOUBLE; + else + current_speed = HDSPM_SPEED_QUAD; + + if (rate <= 48000) + target_speed = HDSPM_SPEED_SINGLE; + else if (rate <= 96000) + target_speed = HDSPM_SPEED_DOUBLE; + else + target_speed = HDSPM_SPEED_QUAD; + + switch (rate) { + case 32000: + rate_bits = HDSPM_Frequency32KHz; + break; + case 44100: + rate_bits = HDSPM_Frequency44_1KHz; + break; + case 48000: + rate_bits = HDSPM_Frequency48KHz; + break; + case 64000: + rate_bits = HDSPM_Frequency64KHz; + break; + case 88200: + rate_bits = HDSPM_Frequency88_2KHz; + break; + case 96000: + rate_bits = HDSPM_Frequency96KHz; + break; + case 128000: + rate_bits = HDSPM_Frequency128KHz; + break; + case 176400: + rate_bits = HDSPM_Frequency176_4KHz; + break; + case 192000: + rate_bits = HDSPM_Frequency192KHz; + break; + default: + return -EINVAL; + } + + if (current_speed != target_speed + && (hdspm->capture_pid >= 0 || hdspm->playback_pid >= 0)) { + dev_err(hdspm->card->dev, + "cannot change from %s speed to %s speed mode (capture PID = %d, playback PID = %d)\n", + hdspm_speed_names[current_speed], + hdspm_speed_names[target_speed], + hdspm->capture_pid, hdspm->playback_pid); + return -EBUSY; + } + + hdspm->control_register &= ~HDSPM_FrequencyMask; + hdspm->control_register |= rate_bits; + hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register); + + /* For AES32, need to set DDS value in FREQ register + For MADI, also apparently */ + hdspm_set_dds_value(hdspm, rate); + + if (AES32 == hdspm->io_type && rate != current_rate) + hdspm_write(hdspm, HDSPM_eeprom_wr, 0); + + hdspm->system_sample_rate = rate; + + if (rate <= 48000) { + hdspm->channel_map_in = hdspm->channel_map_in_ss; + hdspm->channel_map_out = hdspm->channel_map_out_ss; + hdspm->max_channels_in = hdspm->ss_in_channels; + hdspm->max_channels_out = hdspm->ss_out_channels; + hdspm->port_names_in = hdspm->port_names_in_ss; + hdspm->port_names_out = hdspm->port_names_out_ss; + } else if (rate <= 96000) { + hdspm->channel_map_in = hdspm->channel_map_in_ds; + hdspm->channel_map_out = hdspm->channel_map_out_ds; + hdspm->max_channels_in = hdspm->ds_in_channels; + hdspm->max_channels_out = hdspm->ds_out_channels; + hdspm->port_names_in = hdspm->port_names_in_ds; + hdspm->port_names_out = hdspm->port_names_out_ds; + } else { + hdspm->channel_map_in = hdspm->channel_map_in_qs; + hdspm->channel_map_out = hdspm->channel_map_out_qs; + hdspm->max_channels_in = hdspm->qs_in_channels; + hdspm->max_channels_out = hdspm->qs_out_channels; + hdspm->port_names_in = hdspm->port_names_in_qs; + hdspm->port_names_out = hdspm->port_names_out_qs; + } + + if (not_set != 0) + return -1; + + return 0; +} + +/* mainly for init to 0 on load */ +static void all_in_all_mixer(struct hdspm * hdspm, int sgain) +{ + int i, j; + unsigned int gain; + + if (sgain > UNITY_GAIN) + gain = UNITY_GAIN; + else if (sgain < 0) + gain = 0; + else + gain = sgain; + + for (i = 0; i < HDSPM_MIXER_CHANNELS; i++) + for (j = 0; j < HDSPM_MIXER_CHANNELS; j++) { + hdspm_write_in_gain(hdspm, i, j, gain); + hdspm_write_pb_gain(hdspm, i, j, gain); + } +} + +/*---------------------------------------------------------------------------- + MIDI + ----------------------------------------------------------------------------*/ + +static inline unsigned char snd_hdspm_midi_read_byte (struct hdspm *hdspm, + int id) +{ + /* the hardware already does the relevant bit-mask with 0xff */ + return hdspm_read(hdspm, hdspm->midi[id].dataIn); +} + +static inline void snd_hdspm_midi_write_byte (struct hdspm *hdspm, int id, + int val) +{ + /* the hardware already does the relevant bit-mask with 0xff */ + return hdspm_write(hdspm, hdspm->midi[id].dataOut, val); +} + +static inline int snd_hdspm_midi_input_available (struct hdspm *hdspm, int id) +{ + return hdspm_read(hdspm, hdspm->midi[id].statusIn) & 0xFF; +} + +static inline int snd_hdspm_midi_output_possible (struct hdspm *hdspm, int id) +{ + int fifo_bytes_used; + + fifo_bytes_used = hdspm_read(hdspm, hdspm->midi[id].statusOut) & 0xFF; + + if (fifo_bytes_used < 128) + return 128 - fifo_bytes_used; + else + return 0; +} + +static void snd_hdspm_flush_midi_input(struct hdspm *hdspm, int id) +{ + int count = 256; + + while (snd_hdspm_midi_input_available(hdspm, id) && --count) + snd_hdspm_midi_read_byte(hdspm, id); +} + +static int snd_hdspm_midi_output_write (struct hdspm_midi *hmidi) +{ + int n_pending; + int to_write; + int i; + unsigned char buf[128]; + + /* Output is not interrupt driven */ + + guard(spinlock_irqsave)(&hmidi->lock); + if (hmidi->output && + !snd_rawmidi_transmit_empty (hmidi->output)) { + n_pending = snd_hdspm_midi_output_possible (hmidi->hdspm, + hmidi->id); + if (n_pending > 0) { + if (n_pending > (int)sizeof (buf)) + n_pending = sizeof (buf); + + to_write = snd_rawmidi_transmit (hmidi->output, buf, + n_pending); + if (to_write > 0) { + for (i = 0; i < to_write; ++i) + snd_hdspm_midi_write_byte (hmidi->hdspm, + hmidi->id, + buf[i]); + } + } + } + return 0; +} + +static int snd_hdspm_midi_input_read (struct hdspm_midi *hmidi) +{ + unsigned char buf[128]; /* this buffer is designed to match the MIDI + * input FIFO size + */ + int n_pending; + int i; + + scoped_guard(spinlock_irqsave, &hmidi->lock) { + n_pending = snd_hdspm_midi_input_available(hmidi->hdspm, hmidi->id); + if (n_pending > 0) { + if (hmidi->input) { + if (n_pending > (int)sizeof(buf)) + n_pending = sizeof(buf); + for (i = 0; i < n_pending; ++i) + buf[i] = snd_hdspm_midi_read_byte(hmidi->hdspm, + hmidi->id); + if (n_pending) + snd_rawmidi_receive(hmidi->input, buf, + n_pending); + } else { + /* flush the MIDI input FIFO */ + while (n_pending--) + snd_hdspm_midi_read_byte(hmidi->hdspm, + hmidi->id); + } + } + hmidi->pending = 0; + } + + scoped_guard(spinlock_irqsave, &hmidi->hdspm->lock) { + hmidi->hdspm->control_register |= hmidi->ie; + hdspm_write(hmidi->hdspm, HDSPM_controlRegister, + hmidi->hdspm->control_register); + } + + return snd_hdspm_midi_output_write (hmidi); +} + +static void +snd_hdspm_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct hdspm *hdspm; + struct hdspm_midi *hmidi; + + hmidi = substream->rmidi->private_data; + hdspm = hmidi->hdspm; + + guard(spinlock_irqsave)(&hdspm->lock); + if (up) { + if (!(hdspm->control_register & hmidi->ie)) { + snd_hdspm_flush_midi_input (hdspm, hmidi->id); + hdspm->control_register |= hmidi->ie; + } + } else { + hdspm->control_register &= ~hmidi->ie; + } + + hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register); +} + +static void snd_hdspm_midi_output_timer(struct timer_list *t) +{ + struct hdspm_midi *hmidi = timer_container_of(hmidi, t, timer); + + snd_hdspm_midi_output_write(hmidi); + guard(spinlock_irqsave)(&hmidi->lock); + + /* this does not bump hmidi->istimer, because the + kernel automatically removed the timer when it + expired, and we are now adding it back, thus + leaving istimer wherever it was set before. + */ + + if (hmidi->istimer) + mod_timer(&hmidi->timer, 1 + jiffies); +} + +static void +snd_hdspm_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct hdspm_midi *hmidi; + + hmidi = substream->rmidi->private_data; + scoped_guard(spinlock_irqsave, &hmidi->lock) { + if (up) { + if (!hmidi->istimer) { + timer_setup(&hmidi->timer, + snd_hdspm_midi_output_timer, 0); + mod_timer(&hmidi->timer, 1 + jiffies); + hmidi->istimer++; + } + } else { + if (hmidi->istimer && --hmidi->istimer <= 0) + timer_delete(&hmidi->timer); + } + } + if (up) + snd_hdspm_midi_output_write(hmidi); +} + +static int snd_hdspm_midi_input_open(struct snd_rawmidi_substream *substream) +{ + struct hdspm_midi *hmidi; + + hmidi = substream->rmidi->private_data; + guard(spinlock_irq)(&hmidi->lock); + snd_hdspm_flush_midi_input (hmidi->hdspm, hmidi->id); + hmidi->input = substream; + + return 0; +} + +static int snd_hdspm_midi_output_open(struct snd_rawmidi_substream *substream) +{ + struct hdspm_midi *hmidi; + + hmidi = substream->rmidi->private_data; + guard(spinlock_irq)(&hmidi->lock); + hmidi->output = substream; + + return 0; +} + +static int snd_hdspm_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct hdspm_midi *hmidi; + + snd_hdspm_midi_input_trigger (substream, 0); + + hmidi = substream->rmidi->private_data; + guard(spinlock_irq)(&hmidi->lock); + hmidi->input = NULL; + + return 0; +} + +static int snd_hdspm_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct hdspm_midi *hmidi; + + snd_hdspm_midi_output_trigger (substream, 0); + + hmidi = substream->rmidi->private_data; + guard(spinlock_irq)(&hmidi->lock); + hmidi->output = NULL; + + return 0; +} + +static const struct snd_rawmidi_ops snd_hdspm_midi_output = +{ + .open = snd_hdspm_midi_output_open, + .close = snd_hdspm_midi_output_close, + .trigger = snd_hdspm_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_hdspm_midi_input = +{ + .open = snd_hdspm_midi_input_open, + .close = snd_hdspm_midi_input_close, + .trigger = snd_hdspm_midi_input_trigger, +}; + +static int snd_hdspm_create_midi(struct snd_card *card, + struct hdspm *hdspm, int id) +{ + int err; + char buf[64]; + + hdspm->midi[id].id = id; + hdspm->midi[id].hdspm = hdspm; + spin_lock_init (&hdspm->midi[id].lock); + + if (0 == id) { + if (MADIface == hdspm->io_type) { + /* MIDI-over-MADI on HDSPe MADIface */ + hdspm->midi[0].dataIn = HDSPM_midiDataIn2; + hdspm->midi[0].statusIn = HDSPM_midiStatusIn2; + hdspm->midi[0].dataOut = HDSPM_midiDataOut2; + hdspm->midi[0].statusOut = HDSPM_midiStatusOut2; + hdspm->midi[0].ie = HDSPM_Midi2InterruptEnable; + hdspm->midi[0].irq = HDSPM_midi2IRQPending; + } else { + hdspm->midi[0].dataIn = HDSPM_midiDataIn0; + hdspm->midi[0].statusIn = HDSPM_midiStatusIn0; + hdspm->midi[0].dataOut = HDSPM_midiDataOut0; + hdspm->midi[0].statusOut = HDSPM_midiStatusOut0; + hdspm->midi[0].ie = HDSPM_Midi0InterruptEnable; + hdspm->midi[0].irq = HDSPM_midi0IRQPending; + } + } else if (1 == id) { + hdspm->midi[1].dataIn = HDSPM_midiDataIn1; + hdspm->midi[1].statusIn = HDSPM_midiStatusIn1; + hdspm->midi[1].dataOut = HDSPM_midiDataOut1; + hdspm->midi[1].statusOut = HDSPM_midiStatusOut1; + hdspm->midi[1].ie = HDSPM_Midi1InterruptEnable; + hdspm->midi[1].irq = HDSPM_midi1IRQPending; + } else if ((2 == id) && (MADI == hdspm->io_type)) { + /* MIDI-over-MADI on HDSPe MADI */ + hdspm->midi[2].dataIn = HDSPM_midiDataIn2; + hdspm->midi[2].statusIn = HDSPM_midiStatusIn2; + hdspm->midi[2].dataOut = HDSPM_midiDataOut2; + hdspm->midi[2].statusOut = HDSPM_midiStatusOut2; + hdspm->midi[2].ie = HDSPM_Midi2InterruptEnable; + hdspm->midi[2].irq = HDSPM_midi2IRQPending; + } else if (2 == id) { + /* TCO MTC, read only */ + hdspm->midi[2].dataIn = HDSPM_midiDataIn2; + hdspm->midi[2].statusIn = HDSPM_midiStatusIn2; + hdspm->midi[2].dataOut = -1; + hdspm->midi[2].statusOut = -1; + hdspm->midi[2].ie = HDSPM_Midi2InterruptEnable; + hdspm->midi[2].irq = HDSPM_midi2IRQPendingAES; + } else if (3 == id) { + /* TCO MTC on HDSPe MADI */ + hdspm->midi[3].dataIn = HDSPM_midiDataIn3; + hdspm->midi[3].statusIn = HDSPM_midiStatusIn3; + hdspm->midi[3].dataOut = -1; + hdspm->midi[3].statusOut = -1; + hdspm->midi[3].ie = HDSPM_Midi3InterruptEnable; + hdspm->midi[3].irq = HDSPM_midi3IRQPending; + } + + if ((id < 2) || ((2 == id) && ((MADI == hdspm->io_type) || + (MADIface == hdspm->io_type)))) { + if ((id == 0) && (MADIface == hdspm->io_type)) { + snprintf(buf, sizeof(buf), "%s MIDIoverMADI", + card->shortname); + } else if ((id == 2) && (MADI == hdspm->io_type)) { + snprintf(buf, sizeof(buf), "%s MIDIoverMADI", + card->shortname); + } else { + snprintf(buf, sizeof(buf), "%s MIDI %d", + card->shortname, id+1); + } + err = snd_rawmidi_new(card, buf, id, 1, 1, + &hdspm->midi[id].rmidi); + if (err < 0) + return err; + + snprintf(hdspm->midi[id].rmidi->name, + sizeof(hdspm->midi[id].rmidi->name), + "%s MIDI %d", card->id, id+1); + hdspm->midi[id].rmidi->private_data = &hdspm->midi[id]; + + snd_rawmidi_set_ops(hdspm->midi[id].rmidi, + SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_hdspm_midi_output); + snd_rawmidi_set_ops(hdspm->midi[id].rmidi, + SNDRV_RAWMIDI_STREAM_INPUT, + &snd_hdspm_midi_input); + + hdspm->midi[id].rmidi->info_flags |= + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + } else { + /* TCO MTC, read only */ + snprintf(buf, sizeof(buf), "%s MTC %d", + card->shortname, id+1); + err = snd_rawmidi_new(card, buf, id, 1, 1, + &hdspm->midi[id].rmidi); + if (err < 0) + return err; + + snprintf(hdspm->midi[id].rmidi->name, + sizeof(hdspm->midi[id].rmidi->name), + "%s MTC %d", card->id, id+1); + hdspm->midi[id].rmidi->private_data = &hdspm->midi[id]; + + snd_rawmidi_set_ops(hdspm->midi[id].rmidi, + SNDRV_RAWMIDI_STREAM_INPUT, + &snd_hdspm_midi_input); + + hdspm->midi[id].rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + } + + return 0; +} + + +static void hdspm_midi_work(struct work_struct *work) +{ + struct hdspm *hdspm = container_of(work, struct hdspm, midi_work); + int i = 0; + + while (i < hdspm->midiPorts) { + if (hdspm->midi[i].pending) + snd_hdspm_midi_input_read(&hdspm->midi[i]); + + i++; + } +} + + +/*----------------------------------------------------------------------------- + Status Interface + ----------------------------------------------------------------------------*/ + +/* get the system sample rate which is set */ + + +static inline int hdspm_get_pll_freq(struct hdspm *hdspm) +{ + unsigned int period, rate; + + period = hdspm_read(hdspm, HDSPM_RD_PLL_FREQ); + rate = hdspm_calc_dds_value(hdspm, period); + + return rate; +} + +/* + * Calculate the real sample rate from the + * current DDS value. + */ +static int hdspm_get_system_sample_rate(struct hdspm *hdspm) +{ + unsigned int rate; + + rate = hdspm_get_pll_freq(hdspm); + + if (rate > 207000) { + /* Unreasonable high sample rate as seen on PCI MADI cards. */ + if (0 == hdspm_system_clock_mode(hdspm)) { + /* master mode, return internal sample rate */ + rate = hdspm->system_sample_rate; + } else { + /* slave mode, return external sample rate */ + rate = hdspm_external_sample_rate(hdspm); + if (!rate) + rate = hdspm->system_sample_rate; + } + } + + return rate; +} + + +#define HDSPM_SYSTEM_SAMPLE_RATE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_system_sample_rate, \ + .put = snd_hdspm_put_system_sample_rate, \ + .get = snd_hdspm_get_system_sample_rate \ +} + +static int snd_hdspm_info_system_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 27000; + uinfo->value.integer.max = 207000; + uinfo->value.integer.step = 1; + return 0; +} + + +static int snd_hdspm_get_system_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value * + ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = hdspm_get_system_sample_rate(hdspm); + return 0; +} + +static int snd_hdspm_put_system_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value * + ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int rate = ucontrol->value.integer.value[0]; + + if (rate < 27000 || rate > 207000) + return -EINVAL; + hdspm_set_dds_value(hdspm, ucontrol->value.integer.value[0]); + return 0; +} + + +/* + * Returns the WordClock sample rate class for the given card. + */ +static int hdspm_get_wc_sample_rate(struct hdspm *hdspm) +{ + int status; + + switch (hdspm->io_type) { + case RayDAT: + case AIO: + status = hdspm_read(hdspm, HDSPM_RD_STATUS_1); + return (status >> 16) & 0xF; + case AES32: + status = hdspm_read(hdspm, HDSPM_statusRegister); + return (status >> HDSPM_AES32_wcFreq_bit) & 0xF; + default: + break; + } + + + return 0; +} + + +/* + * Returns the TCO sample rate class for the given card. + */ +static int hdspm_get_tco_sample_rate(struct hdspm *hdspm) +{ + int status; + + if (hdspm->tco) { + switch (hdspm->io_type) { + case RayDAT: + case AIO: + status = hdspm_read(hdspm, HDSPM_RD_STATUS_1); + return (status >> 20) & 0xF; + case AES32: + status = hdspm_read(hdspm, HDSPM_statusRegister); + return (status >> 1) & 0xF; + default: + break; + } + } + + return 0; +} + + +/* + * Returns the SYNC_IN sample rate class for the given card. + */ +static int hdspm_get_sync_in_sample_rate(struct hdspm *hdspm) +{ + int status; + + if (hdspm->tco) { + switch (hdspm->io_type) { + case RayDAT: + case AIO: + status = hdspm_read(hdspm, HDSPM_RD_STATUS_2); + return (status >> 12) & 0xF; + default: + break; + } + } + + return 0; +} + +/* + * Returns the AES sample rate class for the given card. + */ +static int hdspm_get_aes_sample_rate(struct hdspm *hdspm, int index) +{ + int timecode; + + switch (hdspm->io_type) { + case AES32: + timecode = hdspm_read(hdspm, HDSPM_timecodeRegister); + return (timecode >> (4*index)) & 0xF; + default: + break; + } + return 0; +} + +/* + * Returns the sample rate class for input source for + * 'new style' cards like the AIO and RayDAT. + */ +static int hdspm_get_s1_sample_rate(struct hdspm *hdspm, unsigned int idx) +{ + int status = hdspm_read(hdspm, HDSPM_RD_STATUS_2); + + return (status >> (idx*4)) & 0xF; +} + +#define ENUMERATED_CTL_INFO(info, texts) \ + snd_ctl_enum_info(info, 1, ARRAY_SIZE(texts), texts) + + +/* Helper function to query the external sample rate and return the + * corresponding enum to be returned to userspace. + */ +static int hdspm_external_rate_to_enum(struct hdspm *hdspm) +{ + int rate = hdspm_external_sample_rate(hdspm); + int i, selected_rate = 0; + for (i = 1; i < 10; i++) + if (HDSPM_bit2freq(i) == rate) { + selected_rate = i; + break; + } + return selected_rate; +} + + +#define HDSPM_AUTOSYNC_SAMPLE_RATE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .private_value = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ, \ + .info = snd_hdspm_info_autosync_sample_rate, \ + .get = snd_hdspm_get_autosync_sample_rate \ +} + + +static int snd_hdspm_info_autosync_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + ENUMERATED_CTL_INFO(uinfo, texts_freq); + return 0; +} + + +static int snd_hdspm_get_autosync_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value * + ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + switch (hdspm->io_type) { + case RayDAT: + switch (kcontrol->private_value) { + case 0: + ucontrol->value.enumerated.item[0] = + hdspm_get_wc_sample_rate(hdspm); + break; + case 7: + ucontrol->value.enumerated.item[0] = + hdspm_get_tco_sample_rate(hdspm); + break; + case 8: + ucontrol->value.enumerated.item[0] = + hdspm_get_sync_in_sample_rate(hdspm); + break; + default: + ucontrol->value.enumerated.item[0] = + hdspm_get_s1_sample_rate(hdspm, + kcontrol->private_value-1); + } + break; + + case AIO: + switch (kcontrol->private_value) { + case 0: /* WC */ + ucontrol->value.enumerated.item[0] = + hdspm_get_wc_sample_rate(hdspm); + break; + case 4: /* TCO */ + ucontrol->value.enumerated.item[0] = + hdspm_get_tco_sample_rate(hdspm); + break; + case 5: /* SYNC_IN */ + ucontrol->value.enumerated.item[0] = + hdspm_get_sync_in_sample_rate(hdspm); + break; + default: + ucontrol->value.enumerated.item[0] = + hdspm_get_s1_sample_rate(hdspm, + kcontrol->private_value-1); + } + break; + + case AES32: + + switch (kcontrol->private_value) { + case 0: /* WC */ + ucontrol->value.enumerated.item[0] = + hdspm_get_wc_sample_rate(hdspm); + break; + case 9: /* TCO */ + ucontrol->value.enumerated.item[0] = + hdspm_get_tco_sample_rate(hdspm); + break; + case 10: /* SYNC_IN */ + ucontrol->value.enumerated.item[0] = + hdspm_get_sync_in_sample_rate(hdspm); + break; + case 11: /* External Rate */ + ucontrol->value.enumerated.item[0] = + hdspm_external_rate_to_enum(hdspm); + break; + default: /* AES1 to AES8 */ + ucontrol->value.enumerated.item[0] = + hdspm_get_aes_sample_rate(hdspm, + kcontrol->private_value - + HDSPM_AES32_AUTOSYNC_FROM_AES1); + break; + } + break; + + case MADI: + case MADIface: + ucontrol->value.enumerated.item[0] = + hdspm_external_rate_to_enum(hdspm); + break; + default: + break; + } + + return 0; +} + + +#define HDSPM_SYSTEM_CLOCK_MODE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_system_clock_mode, \ + .get = snd_hdspm_get_system_clock_mode, \ + .put = snd_hdspm_put_system_clock_mode, \ +} + + +/* + * Returns the system clock mode for the given card. + * @returns 0 - master, 1 - slave + */ +static int hdspm_system_clock_mode(struct hdspm *hdspm) +{ + switch (hdspm->io_type) { + case AIO: + case RayDAT: + if (hdspm->settings_register & HDSPM_c0Master) + return 0; + break; + + default: + if (hdspm->control_register & HDSPM_ClockModeMaster) + return 0; + } + + return 1; +} + + +/* + * Sets the system clock mode. + * @param mode 0 - master, 1 - slave + */ +static void hdspm_set_system_clock_mode(struct hdspm *hdspm, int mode) +{ + hdspm_set_toggle_setting(hdspm, + (hdspm_is_raydat_or_aio(hdspm)) ? + HDSPM_c0Master : HDSPM_ClockModeMaster, + (0 == mode)); +} + + +static int snd_hdspm_info_system_clock_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "Master", "AutoSync" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_system_clock_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdspm_system_clock_mode(hdspm); + return 0; +} + +static int snd_hdspm_put_system_clock_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int val; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + + val = ucontrol->value.enumerated.item[0]; + if (val < 0) + val = 0; + else if (val > 1) + val = 1; + + hdspm_set_system_clock_mode(hdspm, val); + + return 0; +} + + +#define HDSPM_INTERNAL_CLOCK(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdspm_info_clock_source, \ + .get = snd_hdspm_get_clock_source, \ + .put = snd_hdspm_put_clock_source \ +} + + +static int hdspm_clock_source(struct hdspm * hdspm) +{ + switch (hdspm->system_sample_rate) { + case 32000: return 0; + case 44100: return 1; + case 48000: return 2; + case 64000: return 3; + case 88200: return 4; + case 96000: return 5; + case 128000: return 6; + case 176400: return 7; + case 192000: return 8; + } + + return -1; +} + +static int hdspm_set_clock_source(struct hdspm * hdspm, int mode) +{ + int rate; + switch (mode) { + case 0: + rate = 32000; break; + case 1: + rate = 44100; break; + case 2: + rate = 48000; break; + case 3: + rate = 64000; break; + case 4: + rate = 88200; break; + case 5: + rate = 96000; break; + case 6: + rate = 128000; break; + case 7: + rate = 176400; break; + case 8: + rate = 192000; break; + default: + rate = 48000; + } + hdspm_set_rate(hdspm, rate, 1); + return 0; +} + +static int snd_hdspm_info_clock_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 9, texts_freq + 1); +} + +static int snd_hdspm_get_clock_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdspm_clock_source(hdspm); + return 0; +} + +static int snd_hdspm_put_clock_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0]; + if (val < 0) + val = 0; + if (val > 9) + val = 9; + guard(spinlock_irq)(&hdspm->lock); + if (val != hdspm_clock_source(hdspm)) + change = (hdspm_set_clock_source(hdspm, val) == 0) ? 1 : 0; + else + change = 0; + return change; +} + + +#define HDSPM_PREF_SYNC_REF(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_pref_sync_ref, \ + .get = snd_hdspm_get_pref_sync_ref, \ + .put = snd_hdspm_put_pref_sync_ref \ +} + + +/* + * Returns the current preferred sync reference setting. + * The semantics of the return value are depending on the + * card, please see the comments for clarification. + */ +static int hdspm_pref_sync_ref(struct hdspm * hdspm) +{ + switch (hdspm->io_type) { + case AES32: + switch (hdspm->control_register & HDSPM_SyncRefMask) { + case 0: return 0; /* WC */ + case HDSPM_SyncRef0: return 1; /* AES 1 */ + case HDSPM_SyncRef1: return 2; /* AES 2 */ + case HDSPM_SyncRef1+HDSPM_SyncRef0: return 3; /* AES 3 */ + case HDSPM_SyncRef2: return 4; /* AES 4 */ + case HDSPM_SyncRef2+HDSPM_SyncRef0: return 5; /* AES 5 */ + case HDSPM_SyncRef2+HDSPM_SyncRef1: return 6; /* AES 6 */ + case HDSPM_SyncRef2+HDSPM_SyncRef1+HDSPM_SyncRef0: + return 7; /* AES 7 */ + case HDSPM_SyncRef3: return 8; /* AES 8 */ + case HDSPM_SyncRef3+HDSPM_SyncRef0: return 9; /* TCO */ + } + break; + + case MADI: + case MADIface: + if (hdspm->tco) { + switch (hdspm->control_register & HDSPM_SyncRefMask) { + case 0: return 0; /* WC */ + case HDSPM_SyncRef0: return 1; /* MADI */ + case HDSPM_SyncRef1: return 2; /* TCO */ + case HDSPM_SyncRef1+HDSPM_SyncRef0: + return 3; /* SYNC_IN */ + } + } else { + switch (hdspm->control_register & HDSPM_SyncRefMask) { + case 0: return 0; /* WC */ + case HDSPM_SyncRef0: return 1; /* MADI */ + case HDSPM_SyncRef1+HDSPM_SyncRef0: + return 2; /* SYNC_IN */ + } + } + break; + + case RayDAT: + if (hdspm->tco) { + switch ((hdspm->settings_register & + HDSPM_c0_SyncRefMask) / HDSPM_c0_SyncRef0) { + case 0: return 0; /* WC */ + case 3: return 1; /* ADAT 1 */ + case 4: return 2; /* ADAT 2 */ + case 5: return 3; /* ADAT 3 */ + case 6: return 4; /* ADAT 4 */ + case 1: return 5; /* AES */ + case 2: return 6; /* SPDIF */ + case 9: return 7; /* TCO */ + case 10: return 8; /* SYNC_IN */ + } + } else { + switch ((hdspm->settings_register & + HDSPM_c0_SyncRefMask) / HDSPM_c0_SyncRef0) { + case 0: return 0; /* WC */ + case 3: return 1; /* ADAT 1 */ + case 4: return 2; /* ADAT 2 */ + case 5: return 3; /* ADAT 3 */ + case 6: return 4; /* ADAT 4 */ + case 1: return 5; /* AES */ + case 2: return 6; /* SPDIF */ + case 10: return 7; /* SYNC_IN */ + } + } + + break; + + case AIO: + if (hdspm->tco) { + switch ((hdspm->settings_register & + HDSPM_c0_SyncRefMask) / HDSPM_c0_SyncRef0) { + case 0: return 0; /* WC */ + case 3: return 1; /* ADAT */ + case 1: return 2; /* AES */ + case 2: return 3; /* SPDIF */ + case 9: return 4; /* TCO */ + case 10: return 5; /* SYNC_IN */ + } + } else { + switch ((hdspm->settings_register & + HDSPM_c0_SyncRefMask) / HDSPM_c0_SyncRef0) { + case 0: return 0; /* WC */ + case 3: return 1; /* ADAT */ + case 1: return 2; /* AES */ + case 2: return 3; /* SPDIF */ + case 10: return 4; /* SYNC_IN */ + } + } + + break; + } + + return -1; +} + + +/* + * Set the preferred sync reference to . The semantics + * of are depending on the card type, see the comments + * for clarification. + */ +static int hdspm_set_pref_sync_ref(struct hdspm * hdspm, int pref) +{ + int p = 0; + + switch (hdspm->io_type) { + case AES32: + hdspm->control_register &= ~HDSPM_SyncRefMask; + switch (pref) { + case 0: /* WC */ + break; + case 1: /* AES 1 */ + hdspm->control_register |= HDSPM_SyncRef0; + break; + case 2: /* AES 2 */ + hdspm->control_register |= HDSPM_SyncRef1; + break; + case 3: /* AES 3 */ + hdspm->control_register |= + HDSPM_SyncRef1+HDSPM_SyncRef0; + break; + case 4: /* AES 4 */ + hdspm->control_register |= HDSPM_SyncRef2; + break; + case 5: /* AES 5 */ + hdspm->control_register |= + HDSPM_SyncRef2+HDSPM_SyncRef0; + break; + case 6: /* AES 6 */ + hdspm->control_register |= + HDSPM_SyncRef2+HDSPM_SyncRef1; + break; + case 7: /* AES 7 */ + hdspm->control_register |= + HDSPM_SyncRef2+HDSPM_SyncRef1+HDSPM_SyncRef0; + break; + case 8: /* AES 8 */ + hdspm->control_register |= HDSPM_SyncRef3; + break; + case 9: /* TCO */ + hdspm->control_register |= + HDSPM_SyncRef3+HDSPM_SyncRef0; + break; + default: + return -1; + } + + break; + + case MADI: + case MADIface: + hdspm->control_register &= ~HDSPM_SyncRefMask; + if (hdspm->tco) { + switch (pref) { + case 0: /* WC */ + break; + case 1: /* MADI */ + hdspm->control_register |= HDSPM_SyncRef0; + break; + case 2: /* TCO */ + hdspm->control_register |= HDSPM_SyncRef1; + break; + case 3: /* SYNC_IN */ + hdspm->control_register |= + HDSPM_SyncRef0+HDSPM_SyncRef1; + break; + default: + return -1; + } + } else { + switch (pref) { + case 0: /* WC */ + break; + case 1: /* MADI */ + hdspm->control_register |= HDSPM_SyncRef0; + break; + case 2: /* SYNC_IN */ + hdspm->control_register |= + HDSPM_SyncRef0+HDSPM_SyncRef1; + break; + default: + return -1; + } + } + + break; + + case RayDAT: + if (hdspm->tco) { + switch (pref) { + case 0: p = 0; break; /* WC */ + case 1: p = 3; break; /* ADAT 1 */ + case 2: p = 4; break; /* ADAT 2 */ + case 3: p = 5; break; /* ADAT 3 */ + case 4: p = 6; break; /* ADAT 4 */ + case 5: p = 1; break; /* AES */ + case 6: p = 2; break; /* SPDIF */ + case 7: p = 9; break; /* TCO */ + case 8: p = 10; break; /* SYNC_IN */ + default: return -1; + } + } else { + switch (pref) { + case 0: p = 0; break; /* WC */ + case 1: p = 3; break; /* ADAT 1 */ + case 2: p = 4; break; /* ADAT 2 */ + case 3: p = 5; break; /* ADAT 3 */ + case 4: p = 6; break; /* ADAT 4 */ + case 5: p = 1; break; /* AES */ + case 6: p = 2; break; /* SPDIF */ + case 7: p = 10; break; /* SYNC_IN */ + default: return -1; + } + } + break; + + case AIO: + if (hdspm->tco) { + switch (pref) { + case 0: p = 0; break; /* WC */ + case 1: p = 3; break; /* ADAT */ + case 2: p = 1; break; /* AES */ + case 3: p = 2; break; /* SPDIF */ + case 4: p = 9; break; /* TCO */ + case 5: p = 10; break; /* SYNC_IN */ + default: return -1; + } + } else { + switch (pref) { + case 0: p = 0; break; /* WC */ + case 1: p = 3; break; /* ADAT */ + case 2: p = 1; break; /* AES */ + case 3: p = 2; break; /* SPDIF */ + case 4: p = 10; break; /* SYNC_IN */ + default: return -1; + } + } + break; + } + + switch (hdspm->io_type) { + case RayDAT: + case AIO: + hdspm->settings_register &= ~HDSPM_c0_SyncRefMask; + hdspm->settings_register |= HDSPM_c0_SyncRef0 * p; + hdspm_write(hdspm, HDSPM_WR_SETTINGS, hdspm->settings_register); + break; + + case MADI: + case MADIface: + case AES32: + hdspm_write(hdspm, HDSPM_controlRegister, + hdspm->control_register); + } + + return 0; +} + + +static int snd_hdspm_info_pref_sync_ref(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + snd_ctl_enum_info(uinfo, 1, hdspm->texts_autosync_items, hdspm->texts_autosync); + + return 0; +} + +static int snd_hdspm_get_pref_sync_ref(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int psf = hdspm_pref_sync_ref(hdspm); + + if (psf >= 0) { + ucontrol->value.enumerated.item[0] = psf; + return 0; + } + + return -1; +} + +static int snd_hdspm_put_pref_sync_ref(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int val, change = 0; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + + val = ucontrol->value.enumerated.item[0]; + + if (val < 0) + val = 0; + else if (val >= hdspm->texts_autosync_items) + val = hdspm->texts_autosync_items-1; + + guard(spinlock_irq)(&hdspm->lock); + if (val != hdspm_pref_sync_ref(hdspm)) + change = (0 == hdspm_set_pref_sync_ref(hdspm, val)) ? 1 : 0; + + return change; +} + + +#define HDSPM_AUTOSYNC_REF(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ, \ + .info = snd_hdspm_info_autosync_ref, \ + .get = snd_hdspm_get_autosync_ref, \ +} + +static int hdspm_autosync_ref(struct hdspm *hdspm) +{ + /* This looks at the autosync selected sync reference */ + if (AES32 == hdspm->io_type) { + + unsigned int status = hdspm_read(hdspm, HDSPM_statusRegister); + unsigned int syncref = (status >> HDSPM_AES32_syncref_bit) & 0xF; + /* syncref >= HDSPM_AES32_AUTOSYNC_FROM_WORD is always true */ + if (syncref <= HDSPM_AES32_AUTOSYNC_FROM_SYNC_IN) { + return syncref; + } + return HDSPM_AES32_AUTOSYNC_FROM_NONE; + + } else if (MADI == hdspm->io_type) { + + unsigned int status2 = hdspm_read(hdspm, HDSPM_statusRegister2); + switch (status2 & HDSPM_SelSyncRefMask) { + case HDSPM_SelSyncRef_WORD: + return HDSPM_AUTOSYNC_FROM_WORD; + case HDSPM_SelSyncRef_MADI: + return HDSPM_AUTOSYNC_FROM_MADI; + case HDSPM_SelSyncRef_TCO: + return HDSPM_AUTOSYNC_FROM_TCO; + case HDSPM_SelSyncRef_SyncIn: + return HDSPM_AUTOSYNC_FROM_SYNC_IN; + case HDSPM_SelSyncRef_NVALID: + return HDSPM_AUTOSYNC_FROM_NONE; + default: + return HDSPM_AUTOSYNC_FROM_NONE; + } + + } + return 0; +} + + +static int snd_hdspm_info_autosync_ref(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + if (AES32 == hdspm->io_type) { + static const char *const texts[] = { "WordClock", "AES1", "AES2", "AES3", + "AES4", "AES5", "AES6", "AES7", "AES8", "TCO", "Sync In", "None"}; + + ENUMERATED_CTL_INFO(uinfo, texts); + } else if (MADI == hdspm->io_type) { + static const char *const texts[] = {"Word Clock", "MADI", "TCO", + "Sync In", "None" }; + + ENUMERATED_CTL_INFO(uinfo, texts); + } + return 0; +} + +static int snd_hdspm_get_autosync_ref(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdspm_autosync_ref(hdspm); + return 0; +} + + + +#define HDSPM_TCO_VIDEO_INPUT_FORMAT(xname) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_tco_video_input_format, \ + .get = snd_hdspm_get_tco_video_input_format, \ +} + +static int snd_hdspm_info_tco_video_input_format(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = {"No video", "NTSC", "PAL"}; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_tco_video_input_format(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 status; + int ret = 0; + + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + status = hdspm_read(hdspm, HDSPM_RD_TCO + 4); + switch (status & (HDSPM_TCO1_Video_Input_Format_NTSC | + HDSPM_TCO1_Video_Input_Format_PAL)) { + case HDSPM_TCO1_Video_Input_Format_NTSC: + /* ntsc */ + ret = 1; + break; + case HDSPM_TCO1_Video_Input_Format_PAL: + /* pal */ + ret = 2; + break; + default: + /* no video */ + ret = 0; + break; + } + ucontrol->value.enumerated.item[0] = ret; + return 0; +} + + + +#define HDSPM_TCO_LTC_FRAMES(xname) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_tco_ltc_frames, \ + .get = snd_hdspm_get_tco_ltc_frames, \ +} + +static int snd_hdspm_info_tco_ltc_frames(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = {"No lock", "24 fps", "25 fps", "29.97 fps", + "30 fps"}; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int hdspm_tco_ltc_frames(struct hdspm *hdspm) +{ + u32 status; + int ret = 0; + + status = hdspm_read(hdspm, HDSPM_RD_TCO + 4); + if (status & HDSPM_TCO1_LTC_Input_valid) { + switch (status & (HDSPM_TCO1_LTC_Format_LSB | + HDSPM_TCO1_LTC_Format_MSB)) { + case 0: + /* 24 fps */ + ret = fps_24; + break; + case HDSPM_TCO1_LTC_Format_LSB: + /* 25 fps */ + ret = fps_25; + break; + case HDSPM_TCO1_LTC_Format_MSB: + /* 29.97 fps */ + ret = fps_2997; + break; + default: + /* 30 fps */ + ret = fps_30; + break; + } + } + + return ret; +} + +static int snd_hdspm_get_tco_ltc_frames(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdspm_tco_ltc_frames(hdspm); + return 0; +} + +#define HDSPM_TOGGLE_SETTING(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .private_value = xindex, \ + .info = snd_hdspm_info_toggle_setting, \ + .get = snd_hdspm_get_toggle_setting, \ + .put = snd_hdspm_put_toggle_setting \ +} + +static int hdspm_toggle_setting(struct hdspm *hdspm, u32 regmask) +{ + u32 reg; + + if (hdspm_is_raydat_or_aio(hdspm)) + reg = hdspm->settings_register; + else + reg = hdspm->control_register; + + return (reg & regmask) ? 1 : 0; +} + +static int hdspm_set_toggle_setting(struct hdspm *hdspm, u32 regmask, int out) +{ + u32 *reg; + u32 target_reg; + + if (hdspm_is_raydat_or_aio(hdspm)) { + reg = &(hdspm->settings_register); + target_reg = HDSPM_WR_SETTINGS; + } else { + reg = &(hdspm->control_register); + target_reg = HDSPM_controlRegister; + } + + if (out) + *reg |= regmask; + else + *reg &= ~regmask; + + hdspm_write(hdspm, target_reg, *reg); + + return 0; +} + +#define snd_hdspm_info_toggle_setting snd_ctl_boolean_mono_info + +static int snd_hdspm_get_toggle_setting(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + u32 regmask = kcontrol->private_value; + + guard(spinlock_irq)(&hdspm->lock); + ucontrol->value.integer.value[0] = hdspm_toggle_setting(hdspm, regmask); + return 0; +} + +static int snd_hdspm_put_toggle_setting(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + u32 regmask = kcontrol->private_value; + int change; + unsigned int val; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&hdspm->lock); + change = (int) val != hdspm_toggle_setting(hdspm, regmask); + hdspm_set_toggle_setting(hdspm, regmask, val); + return change; +} + +#define HDSPM_INPUT_SELECT(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdspm_info_input_select, \ + .get = snd_hdspm_get_input_select, \ + .put = snd_hdspm_put_input_select \ +} + +static int hdspm_input_select(struct hdspm * hdspm) +{ + return (hdspm->control_register & HDSPM_InputSelect0) ? 1 : 0; +} + +static int hdspm_set_input_select(struct hdspm * hdspm, int out) +{ + if (out) + hdspm->control_register |= HDSPM_InputSelect0; + else + hdspm->control_register &= ~HDSPM_InputSelect0; + hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register); + + return 0; +} + +static int snd_hdspm_info_input_select(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "optical", "coaxial" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_input_select(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&hdspm->lock); + ucontrol->value.enumerated.item[0] = hdspm_input_select(hdspm); + return 0; +} + +static int snd_hdspm_put_input_select(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&hdspm->lock); + change = (int) val != hdspm_input_select(hdspm); + hdspm_set_input_select(hdspm, val); + return change; +} + + +#define HDSPM_DS_WIRE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdspm_info_ds_wire, \ + .get = snd_hdspm_get_ds_wire, \ + .put = snd_hdspm_put_ds_wire \ +} + +static int hdspm_ds_wire(struct hdspm * hdspm) +{ + return (hdspm->control_register & HDSPM_DS_DoubleWire) ? 1 : 0; +} + +static int hdspm_set_ds_wire(struct hdspm * hdspm, int ds) +{ + if (ds) + hdspm->control_register |= HDSPM_DS_DoubleWire; + else + hdspm->control_register &= ~HDSPM_DS_DoubleWire; + hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register); + + return 0; +} + +static int snd_hdspm_info_ds_wire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "Single", "Double" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_ds_wire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&hdspm->lock); + ucontrol->value.enumerated.item[0] = hdspm_ds_wire(hdspm); + return 0; +} + +static int snd_hdspm_put_ds_wire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&hdspm->lock); + change = (int) val != hdspm_ds_wire(hdspm); + hdspm_set_ds_wire(hdspm, val); + return change; +} + + +#define HDSPM_QS_WIRE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdspm_info_qs_wire, \ + .get = snd_hdspm_get_qs_wire, \ + .put = snd_hdspm_put_qs_wire \ +} + +static int hdspm_qs_wire(struct hdspm * hdspm) +{ + if (hdspm->control_register & HDSPM_QS_DoubleWire) + return 1; + if (hdspm->control_register & HDSPM_QS_QuadWire) + return 2; + return 0; +} + +static int hdspm_set_qs_wire(struct hdspm * hdspm, int mode) +{ + hdspm->control_register &= ~(HDSPM_QS_DoubleWire | HDSPM_QS_QuadWire); + switch (mode) { + case 0: + break; + case 1: + hdspm->control_register |= HDSPM_QS_DoubleWire; + break; + case 2: + hdspm->control_register |= HDSPM_QS_QuadWire; + break; + } + hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register); + + return 0; +} + +static int snd_hdspm_info_qs_wire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "Single", "Double", "Quad" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_qs_wire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&hdspm->lock); + ucontrol->value.enumerated.item[0] = hdspm_qs_wire(hdspm); + return 0; +} + +static int snd_hdspm_put_qs_wire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + val = ucontrol->value.integer.value[0]; + if (val < 0) + val = 0; + if (val > 2) + val = 2; + guard(spinlock_irq)(&hdspm->lock); + change = val != hdspm_qs_wire(hdspm); + hdspm_set_qs_wire(hdspm, val); + return change; +} + +#define HDSPM_CONTROL_TRISTATE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .private_value = xindex, \ + .info = snd_hdspm_info_tristate, \ + .get = snd_hdspm_get_tristate, \ + .put = snd_hdspm_put_tristate \ +} + +static int hdspm_tristate(struct hdspm *hdspm, u32 regmask) +{ + u32 reg = hdspm->settings_register & (regmask * 3); + return reg / regmask; +} + +static int hdspm_set_tristate(struct hdspm *hdspm, int mode, u32 regmask) +{ + hdspm->settings_register &= ~(regmask * 3); + hdspm->settings_register |= (regmask * mode); + hdspm_write(hdspm, HDSPM_WR_SETTINGS, hdspm->settings_register); + + return 0; +} + +static int snd_hdspm_info_tristate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 regmask = kcontrol->private_value; + + static const char *const texts_spdif[] = { "Optical", "Coaxial", "Internal" }; + static const char *const texts_levels[] = { "Hi Gain", "+4 dBu", "-10 dBV" }; + + switch (regmask) { + case HDSPM_c0_Input0: + ENUMERATED_CTL_INFO(uinfo, texts_spdif); + break; + default: + ENUMERATED_CTL_INFO(uinfo, texts_levels); + break; + } + return 0; +} + +static int snd_hdspm_get_tristate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + u32 regmask = kcontrol->private_value; + + guard(spinlock_irq)(&hdspm->lock); + ucontrol->value.enumerated.item[0] = hdspm_tristate(hdspm, regmask); + return 0; +} + +static int snd_hdspm_put_tristate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + u32 regmask = kcontrol->private_value; + int change; + int val; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + val = ucontrol->value.integer.value[0]; + if (val < 0) + val = 0; + if (val > 2) + val = 2; + + guard(spinlock_irq)(&hdspm->lock); + change = val != hdspm_tristate(hdspm, regmask); + hdspm_set_tristate(hdspm, val, regmask); + return change; +} + +#define HDSPM_MADI_SPEEDMODE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdspm_info_madi_speedmode, \ + .get = snd_hdspm_get_madi_speedmode, \ + .put = snd_hdspm_put_madi_speedmode \ +} + +static int hdspm_madi_speedmode(struct hdspm *hdspm) +{ + if (hdspm->control_register & HDSPM_QuadSpeed) + return 2; + if (hdspm->control_register & HDSPM_DoubleSpeed) + return 1; + return 0; +} + +static int hdspm_set_madi_speedmode(struct hdspm *hdspm, int mode) +{ + hdspm->control_register &= ~(HDSPM_DoubleSpeed | HDSPM_QuadSpeed); + switch (mode) { + case 0: + break; + case 1: + hdspm->control_register |= HDSPM_DoubleSpeed; + break; + case 2: + hdspm->control_register |= HDSPM_QuadSpeed; + break; + } + hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register); + + return 0; +} + +static int snd_hdspm_info_madi_speedmode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "Single", "Double", "Quad" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_madi_speedmode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&hdspm->lock); + ucontrol->value.enumerated.item[0] = hdspm_madi_speedmode(hdspm); + return 0; +} + +static int snd_hdspm_put_madi_speedmode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int change; + int val; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + val = ucontrol->value.integer.value[0]; + if (val < 0) + val = 0; + if (val > 2) + val = 2; + guard(spinlock_irq)(&hdspm->lock); + change = val != hdspm_madi_speedmode(hdspm); + hdspm_set_madi_speedmode(hdspm, val); + return change; +} + +#define HDSPM_MIXER(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \ + .name = xname, \ + .index = xindex, \ + .device = 0, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_mixer, \ + .get = snd_hdspm_get_mixer, \ + .put = snd_hdspm_put_mixer \ +} + +static int snd_hdspm_info_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 65535; + uinfo->value.integer.step = 1; + return 0; +} + +static int snd_hdspm_get_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int source; + int destination; + + source = ucontrol->value.integer.value[0]; + if (source < 0) + source = 0; + else if (source >= 2 * HDSPM_MAX_CHANNELS) + source = 2 * HDSPM_MAX_CHANNELS - 1; + + destination = ucontrol->value.integer.value[1]; + if (destination < 0) + destination = 0; + else if (destination >= HDSPM_MAX_CHANNELS) + destination = HDSPM_MAX_CHANNELS - 1; + + guard(spinlock_irq)(&hdspm->lock); + if (source >= HDSPM_MAX_CHANNELS) + ucontrol->value.integer.value[2] = + hdspm_read_pb_gain(hdspm, destination, + source - HDSPM_MAX_CHANNELS); + else + ucontrol->value.integer.value[2] = + hdspm_read_in_gain(hdspm, destination, source); + + return 0; +} + +static int snd_hdspm_put_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int change; + int source; + int destination; + int gain; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + + source = ucontrol->value.integer.value[0]; + destination = ucontrol->value.integer.value[1]; + + if (source < 0 || source >= 2 * HDSPM_MAX_CHANNELS) + return -1; + if (destination < 0 || destination >= HDSPM_MAX_CHANNELS) + return -1; + + gain = ucontrol->value.integer.value[2]; + + guard(spinlock_irq)(&hdspm->lock); + + if (source >= HDSPM_MAX_CHANNELS) + change = gain != hdspm_read_pb_gain(hdspm, destination, + source - + HDSPM_MAX_CHANNELS); + else + change = gain != hdspm_read_in_gain(hdspm, destination, + source); + + if (change) { + if (source >= HDSPM_MAX_CHANNELS) + hdspm_write_pb_gain(hdspm, destination, + source - HDSPM_MAX_CHANNELS, + gain); + else + hdspm_write_in_gain(hdspm, destination, source, + gain); + } + + return change; +} + +/* The simple mixer control(s) provide gain control for the + basic 1:1 mappings of playback streams to output + streams. +*/ + +#define HDSPM_PLAYBACK_MIXER \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_playback_mixer, \ + .get = snd_hdspm_get_playback_mixer, \ + .put = snd_hdspm_put_playback_mixer \ +} + +static int snd_hdspm_info_playback_mixer(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 = 64; + uinfo->value.integer.step = 1; + return 0; +} + +static int snd_hdspm_get_playback_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int channel; + + channel = ucontrol->id.index - 1; + + if (snd_BUG_ON(channel < 0 || channel >= HDSPM_MAX_CHANNELS)) + return -EINVAL; + + guard(spinlock_irq)(&hdspm->lock); + ucontrol->value.integer.value[0] = + (hdspm_read_pb_gain(hdspm, channel, channel)*64)/UNITY_GAIN; + + return 0; +} + +static int snd_hdspm_put_playback_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int change; + int channel; + int gain; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + + channel = ucontrol->id.index - 1; + + if (snd_BUG_ON(channel < 0 || channel >= HDSPM_MAX_CHANNELS)) + return -EINVAL; + + gain = ucontrol->value.integer.value[0]*UNITY_GAIN/64; + + guard(spinlock_irq)(&hdspm->lock); + change = + gain != hdspm_read_pb_gain(hdspm, channel, + channel); + if (change) + hdspm_write_pb_gain(hdspm, channel, channel, + gain); + return change; +} + +#define HDSPM_SYNC_CHECK(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .private_value = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_sync_check, \ + .get = snd_hdspm_get_sync_check \ +} + +#define HDSPM_TCO_LOCK_CHECK(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .private_value = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_tco_info_lock_check, \ + .get = snd_hdspm_get_sync_check \ +} + + + +static int snd_hdspm_info_sync_check(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "No Lock", "Lock", "Sync", "N/A" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_tco_info_lock_check(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "No Lock", "Lock" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int hdspm_wc_sync_check(struct hdspm *hdspm) +{ + int status, status2; + + switch (hdspm->io_type) { + case AES32: + status = hdspm_read(hdspm, HDSPM_statusRegister); + if (status & HDSPM_AES32_wcLock) { + if (status & HDSPM_AES32_wcSync) + return 2; + else + return 1; + } + return 0; + + case MADI: + status2 = hdspm_read(hdspm, HDSPM_statusRegister2); + if (status2 & HDSPM_wcLock) { + if (status2 & HDSPM_wcSync) + return 2; + else + return 1; + } + return 0; + + case RayDAT: + case AIO: + status = hdspm_read(hdspm, HDSPM_statusRegister); + + if (status & 0x2000000) + return 2; + else if (status & 0x1000000) + return 1; + return 0; + + case MADIface: + break; + } + + + return 3; +} + + +static int hdspm_madi_sync_check(struct hdspm *hdspm) +{ + int status = hdspm_read(hdspm, HDSPM_statusRegister); + if (status & HDSPM_madiLock) { + if (status & HDSPM_madiSync) + return 2; + else + return 1; + } + return 0; +} + + +static int hdspm_s1_sync_check(struct hdspm *hdspm, int idx) +{ + int status, lock, sync; + + status = hdspm_read(hdspm, HDSPM_RD_STATUS_1); + + lock = (status & (0x1<io_type) { + case RayDAT: + case AIO: + status = hdspm_read(hdspm, HDSPM_RD_STATUS_3); + lock = (status & 0x400) ? 1 : 0; + sync = (status & 0x800) ? 1 : 0; + break; + + case MADI: + status = hdspm_read(hdspm, HDSPM_statusRegister); + lock = (status & HDSPM_syncInLock) ? 1 : 0; + sync = (status & HDSPM_syncInSync) ? 1 : 0; + break; + + case AES32: + status = hdspm_read(hdspm, HDSPM_statusRegister2); + lock = (status & 0x100000) ? 1 : 0; + sync = (status & 0x200000) ? 1 : 0; + break; + + case MADIface: + break; + } + + if (lock && sync) + return 2; + else if (lock) + return 1; + + return 0; +} + +static int hdspm_aes_sync_check(struct hdspm *hdspm, int idx) +{ + int status2, lock, sync; + status2 = hdspm_read(hdspm, HDSPM_statusRegister2); + + lock = (status2 & (0x0080 >> idx)) ? 1 : 0; + sync = (status2 & (0x8000 >> idx)) ? 1 : 0; + + if (sync) + return 2; + else if (lock) + return 1; + return 0; +} + +static int hdspm_tco_input_check(struct hdspm *hdspm, u32 mask) +{ + u32 status; + status = hdspm_read(hdspm, HDSPM_RD_TCO + 4); + + return (status & mask) ? 1 : 0; +} + + +static int hdspm_tco_sync_check(struct hdspm *hdspm) +{ + int status; + + if (hdspm->tco) { + switch (hdspm->io_type) { + case MADI: + status = hdspm_read(hdspm, HDSPM_statusRegister); + if (status & HDSPM_tcoLockMadi) { + if (status & HDSPM_tcoSync) + return 2; + else + return 1; + } + return 0; + case AES32: + status = hdspm_read(hdspm, HDSPM_statusRegister); + if (status & HDSPM_tcoLockAes) { + if (status & HDSPM_tcoSync) + return 2; + else + return 1; + } + return 0; + case RayDAT: + case AIO: + status = hdspm_read(hdspm, HDSPM_RD_STATUS_1); + + if (status & 0x8000000) + return 2; /* Sync */ + if (status & 0x4000000) + return 1; /* Lock */ + return 0; /* No signal */ + + default: + break; + } + } + + return 3; /* N/A */ +} + + +static int snd_hdspm_get_sync_check(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int val = -1; + + switch (hdspm->io_type) { + case RayDAT: + switch (kcontrol->private_value) { + case 0: /* WC */ + val = hdspm_wc_sync_check(hdspm); break; + case 7: /* TCO */ + val = hdspm_tco_sync_check(hdspm); break; + case 8: /* SYNC IN */ + val = hdspm_sync_in_sync_check(hdspm); break; + default: + val = hdspm_s1_sync_check(hdspm, + kcontrol->private_value-1); + } + break; + + case AIO: + switch (kcontrol->private_value) { + case 0: /* WC */ + val = hdspm_wc_sync_check(hdspm); break; + case 4: /* TCO */ + val = hdspm_tco_sync_check(hdspm); break; + case 5: /* SYNC IN */ + val = hdspm_sync_in_sync_check(hdspm); break; + default: + val = hdspm_s1_sync_check(hdspm, + kcontrol->private_value-1); + } + break; + + case MADI: + switch (kcontrol->private_value) { + case 0: /* WC */ + val = hdspm_wc_sync_check(hdspm); break; + case 1: /* MADI */ + val = hdspm_madi_sync_check(hdspm); break; + case 2: /* TCO */ + val = hdspm_tco_sync_check(hdspm); break; + case 3: /* SYNC_IN */ + val = hdspm_sync_in_sync_check(hdspm); break; + } + break; + + case MADIface: + val = hdspm_madi_sync_check(hdspm); /* MADI */ + break; + + case AES32: + switch (kcontrol->private_value) { + case 0: /* WC */ + val = hdspm_wc_sync_check(hdspm); break; + case 9: /* TCO */ + val = hdspm_tco_sync_check(hdspm); break; + case 10 /* SYNC IN */: + val = hdspm_sync_in_sync_check(hdspm); break; + default: /* AES1 to AES8 */ + val = hdspm_aes_sync_check(hdspm, + kcontrol->private_value-1); + } + break; + + } + + if (hdspm->tco) { + switch (kcontrol->private_value) { + case 11: + /* Check TCO for lock state of its current input */ + val = hdspm_tco_input_check(hdspm, HDSPM_TCO1_TCO_lock); + break; + case 12: + /* Check TCO for valid time code on LTC input. */ + val = hdspm_tco_input_check(hdspm, + HDSPM_TCO1_LTC_Input_valid); + break; + default: + break; + } + } + + if (-1 == val) + val = 3; + + ucontrol->value.enumerated.item[0] = val; + return 0; +} + + + +/* + * TCO controls + */ +static void hdspm_tco_write(struct hdspm *hdspm) +{ + unsigned int tc[4] = { 0, 0, 0, 0}; + + switch (hdspm->tco->input) { + case 0: + tc[2] |= HDSPM_TCO2_set_input_MSB; + break; + case 1: + tc[2] |= HDSPM_TCO2_set_input_LSB; + break; + default: + break; + } + + switch (hdspm->tco->framerate) { + case 1: + tc[1] |= HDSPM_TCO1_LTC_Format_LSB; + break; + case 2: + tc[1] |= HDSPM_TCO1_LTC_Format_MSB; + break; + case 3: + tc[1] |= HDSPM_TCO1_LTC_Format_MSB + + HDSPM_TCO1_set_drop_frame_flag; + break; + case 4: + tc[1] |= HDSPM_TCO1_LTC_Format_LSB + + HDSPM_TCO1_LTC_Format_MSB; + break; + case 5: + tc[1] |= HDSPM_TCO1_LTC_Format_LSB + + HDSPM_TCO1_LTC_Format_MSB + + HDSPM_TCO1_set_drop_frame_flag; + break; + default: + break; + } + + switch (hdspm->tco->wordclock) { + case 1: + tc[2] |= HDSPM_TCO2_WCK_IO_ratio_LSB; + break; + case 2: + tc[2] |= HDSPM_TCO2_WCK_IO_ratio_MSB; + break; + default: + break; + } + + switch (hdspm->tco->samplerate) { + case 1: + tc[2] |= HDSPM_TCO2_set_freq; + break; + case 2: + tc[2] |= HDSPM_TCO2_set_freq_from_app; + break; + default: + break; + } + + switch (hdspm->tco->pull) { + case 1: + tc[2] |= HDSPM_TCO2_set_pull_up; + break; + case 2: + tc[2] |= HDSPM_TCO2_set_pull_down; + break; + case 3: + tc[2] |= HDSPM_TCO2_set_pull_up + HDSPM_TCO2_set_01_4; + break; + case 4: + tc[2] |= HDSPM_TCO2_set_pull_down + HDSPM_TCO2_set_01_4; + break; + default: + break; + } + + if (1 == hdspm->tco->term) { + tc[2] |= HDSPM_TCO2_set_term_75R; + } + + hdspm_write(hdspm, HDSPM_WR_TCO, tc[0]); + hdspm_write(hdspm, HDSPM_WR_TCO+4, tc[1]); + hdspm_write(hdspm, HDSPM_WR_TCO+8, tc[2]); + hdspm_write(hdspm, HDSPM_WR_TCO+12, tc[3]); +} + + +#define HDSPM_TCO_SAMPLE_RATE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_tco_sample_rate, \ + .get = snd_hdspm_get_tco_sample_rate, \ + .put = snd_hdspm_put_tco_sample_rate \ +} + +static int snd_hdspm_info_tco_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + /* TODO freq from app could be supported here, see tco->samplerate */ + static const char *const texts[] = { "44.1 kHz", "48 kHz" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_tco_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdspm->tco->samplerate; + + return 0; +} + +static int snd_hdspm_put_tco_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + if (hdspm->tco->samplerate != ucontrol->value.enumerated.item[0]) { + hdspm->tco->samplerate = ucontrol->value.enumerated.item[0]; + + hdspm_tco_write(hdspm); + + return 1; + } + + return 0; +} + + +#define HDSPM_TCO_PULL(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_tco_pull, \ + .get = snd_hdspm_get_tco_pull, \ + .put = snd_hdspm_put_tco_pull \ +} + +static int snd_hdspm_info_tco_pull(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "0", "+ 0.1 %", "- 0.1 %", + "+ 4 %", "- 4 %" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_tco_pull(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdspm->tco->pull; + + return 0; +} + +static int snd_hdspm_put_tco_pull(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + if (hdspm->tco->pull != ucontrol->value.enumerated.item[0]) { + hdspm->tco->pull = ucontrol->value.enumerated.item[0]; + + hdspm_tco_write(hdspm); + + return 1; + } + + return 0; +} + +#define HDSPM_TCO_WCK_CONVERSION(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_tco_wck_conversion, \ + .get = snd_hdspm_get_tco_wck_conversion, \ + .put = snd_hdspm_put_tco_wck_conversion \ +} + +static int snd_hdspm_info_tco_wck_conversion(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "1:1", "44.1 -> 48", "48 -> 44.1" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_tco_wck_conversion(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdspm->tco->wordclock; + + return 0; +} + +static int snd_hdspm_put_tco_wck_conversion(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + if (hdspm->tco->wordclock != ucontrol->value.enumerated.item[0]) { + hdspm->tco->wordclock = ucontrol->value.enumerated.item[0]; + + hdspm_tco_write(hdspm); + + return 1; + } + + return 0; +} + + +#define HDSPM_TCO_FRAME_RATE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_tco_frame_rate, \ + .get = snd_hdspm_get_tco_frame_rate, \ + .put = snd_hdspm_put_tco_frame_rate \ +} + +static int snd_hdspm_info_tco_frame_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "24 fps", "25 fps", "29.97fps", + "29.97 dfps", "30 fps", "30 dfps" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_tco_frame_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdspm->tco->framerate; + + return 0; +} + +static int snd_hdspm_put_tco_frame_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + if (hdspm->tco->framerate != ucontrol->value.enumerated.item[0]) { + hdspm->tco->framerate = ucontrol->value.enumerated.item[0]; + + hdspm_tco_write(hdspm); + + return 1; + } + + return 0; +} + + +#define HDSPM_TCO_SYNC_SOURCE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_tco_sync_source, \ + .get = snd_hdspm_get_tco_sync_source, \ + .put = snd_hdspm_put_tco_sync_source \ +} + +static int snd_hdspm_info_tco_sync_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[] = { "LTC", "Video", "WCK" }; + ENUMERATED_CTL_INFO(uinfo, texts); + return 0; +} + +static int snd_hdspm_get_tco_sync_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = hdspm->tco->input; + + return 0; +} + +static int snd_hdspm_put_tco_sync_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + if (hdspm->tco->input != ucontrol->value.enumerated.item[0]) { + hdspm->tco->input = ucontrol->value.enumerated.item[0]; + + hdspm_tco_write(hdspm); + + return 1; + } + + return 0; +} + + +#define HDSPM_TCO_WORD_TERM(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_hdspm_info_tco_word_term, \ + .get = snd_hdspm_get_tco_word_term, \ + .put = snd_hdspm_put_tco_word_term \ +} + +static int snd_hdspm_info_tco_word_term(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + + +static int snd_hdspm_get_tco_word_term(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = hdspm->tco->term; + + return 0; +} + + +static int snd_hdspm_put_tco_word_term(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + if (hdspm->tco->term != ucontrol->value.integer.value[0]) { + hdspm->tco->term = ucontrol->value.integer.value[0]; + + hdspm_tco_write(hdspm); + + return 1; + } + + return 0; +} + + + + +static const struct snd_kcontrol_new snd_hdspm_controls_madi[] = { + HDSPM_MIXER("Mixer", 0), + HDSPM_INTERNAL_CLOCK("Internal Clock", 0), + HDSPM_SYSTEM_CLOCK_MODE("System Clock Mode", 0), + HDSPM_PREF_SYNC_REF("Preferred Sync Reference", 0), + HDSPM_AUTOSYNC_REF("AutoSync Reference", 0), + HDSPM_SYSTEM_SAMPLE_RATE("System Sample Rate", 0), + HDSPM_AUTOSYNC_SAMPLE_RATE("External Rate", 0), + HDSPM_SYNC_CHECK("WC SyncCheck", 0), + HDSPM_SYNC_CHECK("MADI SyncCheck", 1), + HDSPM_SYNC_CHECK("TCO SyncCheck", 2), + HDSPM_SYNC_CHECK("SYNC IN SyncCheck", 3), + HDSPM_TOGGLE_SETTING("Line Out", HDSPM_LineOut), + HDSPM_TOGGLE_SETTING("TX 64 channels mode", HDSPM_TX_64ch), + HDSPM_TOGGLE_SETTING("Disable 96K frames", HDSPM_SMUX), + HDSPM_TOGGLE_SETTING("Clear Track Marker", HDSPM_clr_tms), + HDSPM_TOGGLE_SETTING("Safe Mode", HDSPM_AutoInp), + HDSPM_INPUT_SELECT("Input Select", 0), + HDSPM_MADI_SPEEDMODE("MADI Speed Mode", 0) +}; + + +static const struct snd_kcontrol_new snd_hdspm_controls_madiface[] = { + HDSPM_MIXER("Mixer", 0), + HDSPM_INTERNAL_CLOCK("Internal Clock", 0), + HDSPM_SYSTEM_CLOCK_MODE("System Clock Mode", 0), + HDSPM_SYSTEM_SAMPLE_RATE("System Sample Rate", 0), + HDSPM_AUTOSYNC_SAMPLE_RATE("External Rate", 0), + HDSPM_SYNC_CHECK("MADI SyncCheck", 0), + HDSPM_TOGGLE_SETTING("TX 64 channels mode", HDSPM_TX_64ch), + HDSPM_TOGGLE_SETTING("Clear Track Marker", HDSPM_clr_tms), + HDSPM_TOGGLE_SETTING("Safe Mode", HDSPM_AutoInp), + HDSPM_MADI_SPEEDMODE("MADI Speed Mode", 0) +}; + +static const struct snd_kcontrol_new snd_hdspm_controls_aio[] = { + HDSPM_MIXER("Mixer", 0), + HDSPM_INTERNAL_CLOCK("Internal Clock", 0), + HDSPM_SYSTEM_CLOCK_MODE("System Clock Mode", 0), + HDSPM_PREF_SYNC_REF("Preferred Sync Reference", 0), + HDSPM_SYSTEM_SAMPLE_RATE("System Sample Rate", 0), + HDSPM_AUTOSYNC_SAMPLE_RATE("External Rate", 0), + HDSPM_SYNC_CHECK("WC SyncCheck", 0), + HDSPM_SYNC_CHECK("AES SyncCheck", 1), + HDSPM_SYNC_CHECK("SPDIF SyncCheck", 2), + HDSPM_SYNC_CHECK("ADAT SyncCheck", 3), + HDSPM_SYNC_CHECK("TCO SyncCheck", 4), + HDSPM_SYNC_CHECK("SYNC IN SyncCheck", 5), + HDSPM_AUTOSYNC_SAMPLE_RATE("WC Frequency", 0), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES Frequency", 1), + HDSPM_AUTOSYNC_SAMPLE_RATE("SPDIF Frequency", 2), + HDSPM_AUTOSYNC_SAMPLE_RATE("ADAT Frequency", 3), + HDSPM_AUTOSYNC_SAMPLE_RATE("TCO Frequency", 4), + HDSPM_AUTOSYNC_SAMPLE_RATE("SYNC IN Frequency", 5), + HDSPM_CONTROL_TRISTATE("S/PDIF Input", HDSPM_c0_Input0), + HDSPM_TOGGLE_SETTING("S/PDIF Out Optical", HDSPM_c0_Spdif_Opt), + HDSPM_TOGGLE_SETTING("S/PDIF Out Professional", HDSPM_c0_Pro), + HDSPM_TOGGLE_SETTING("ADAT internal (AEB/TEB)", HDSPM_c0_AEB1), + HDSPM_TOGGLE_SETTING("XLR Breakout Cable", HDSPM_c0_Sym6db), + HDSPM_TOGGLE_SETTING("Single Speed WordClock Out", HDSPM_c0_Wck48), + HDSPM_CONTROL_TRISTATE("Input Level", HDSPM_c0_AD_GAIN0), + HDSPM_CONTROL_TRISTATE("Output Level", HDSPM_c0_DA_GAIN0), + HDSPM_CONTROL_TRISTATE("Phones Level", HDSPM_c0_PH_GAIN0) + + /* + HDSPM_INPUT_SELECT("Input Select", 0), + HDSPM_SPDIF_OPTICAL("SPDIF Out Optical", 0), + HDSPM_PROFESSIONAL("SPDIF Out Professional", 0); + HDSPM_SPDIF_IN("SPDIF In", 0); + HDSPM_BREAKOUT_CABLE("Breakout Cable", 0); + HDSPM_INPUT_LEVEL("Input Level", 0); + HDSPM_OUTPUT_LEVEL("Output Level", 0); + HDSPM_PHONES("Phones", 0); + */ +}; + +static const struct snd_kcontrol_new snd_hdspm_controls_raydat[] = { + HDSPM_MIXER("Mixer", 0), + HDSPM_INTERNAL_CLOCK("Internal Clock", 0), + HDSPM_SYSTEM_CLOCK_MODE("Clock Mode", 0), + HDSPM_PREF_SYNC_REF("Pref Sync Ref", 0), + HDSPM_SYSTEM_SAMPLE_RATE("System Sample Rate", 0), + HDSPM_SYNC_CHECK("WC SyncCheck", 0), + HDSPM_SYNC_CHECK("AES SyncCheck", 1), + HDSPM_SYNC_CHECK("SPDIF SyncCheck", 2), + HDSPM_SYNC_CHECK("ADAT1 SyncCheck", 3), + HDSPM_SYNC_CHECK("ADAT2 SyncCheck", 4), + HDSPM_SYNC_CHECK("ADAT3 SyncCheck", 5), + HDSPM_SYNC_CHECK("ADAT4 SyncCheck", 6), + HDSPM_SYNC_CHECK("TCO SyncCheck", 7), + HDSPM_SYNC_CHECK("SYNC IN SyncCheck", 8), + HDSPM_AUTOSYNC_SAMPLE_RATE("WC Frequency", 0), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES Frequency", 1), + HDSPM_AUTOSYNC_SAMPLE_RATE("SPDIF Frequency", 2), + HDSPM_AUTOSYNC_SAMPLE_RATE("ADAT1 Frequency", 3), + HDSPM_AUTOSYNC_SAMPLE_RATE("ADAT2 Frequency", 4), + HDSPM_AUTOSYNC_SAMPLE_RATE("ADAT3 Frequency", 5), + HDSPM_AUTOSYNC_SAMPLE_RATE("ADAT4 Frequency", 6), + HDSPM_AUTOSYNC_SAMPLE_RATE("TCO Frequency", 7), + HDSPM_AUTOSYNC_SAMPLE_RATE("SYNC IN Frequency", 8), + HDSPM_TOGGLE_SETTING("S/PDIF Out Professional", HDSPM_c0_Pro), + HDSPM_TOGGLE_SETTING("Single Speed WordClock Out", HDSPM_c0_Wck48) +}; + +static const struct snd_kcontrol_new snd_hdspm_controls_aes32[] = { + HDSPM_MIXER("Mixer", 0), + HDSPM_INTERNAL_CLOCK("Internal Clock", 0), + HDSPM_SYSTEM_CLOCK_MODE("System Clock Mode", 0), + HDSPM_PREF_SYNC_REF("Preferred Sync Reference", 0), + HDSPM_AUTOSYNC_REF("AutoSync Reference", 0), + HDSPM_SYSTEM_SAMPLE_RATE("System Sample Rate", 0), + HDSPM_AUTOSYNC_SAMPLE_RATE("External Rate", 11), + HDSPM_SYNC_CHECK("WC Sync Check", 0), + HDSPM_SYNC_CHECK("AES1 Sync Check", 1), + HDSPM_SYNC_CHECK("AES2 Sync Check", 2), + HDSPM_SYNC_CHECK("AES3 Sync Check", 3), + HDSPM_SYNC_CHECK("AES4 Sync Check", 4), + HDSPM_SYNC_CHECK("AES5 Sync Check", 5), + HDSPM_SYNC_CHECK("AES6 Sync Check", 6), + HDSPM_SYNC_CHECK("AES7 Sync Check", 7), + HDSPM_SYNC_CHECK("AES8 Sync Check", 8), + HDSPM_SYNC_CHECK("TCO Sync Check", 9), + HDSPM_SYNC_CHECK("SYNC IN Sync Check", 10), + HDSPM_AUTOSYNC_SAMPLE_RATE("WC Frequency", 0), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES1 Frequency", 1), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES2 Frequency", 2), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES3 Frequency", 3), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES4 Frequency", 4), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES5 Frequency", 5), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES6 Frequency", 6), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES7 Frequency", 7), + HDSPM_AUTOSYNC_SAMPLE_RATE("AES8 Frequency", 8), + HDSPM_AUTOSYNC_SAMPLE_RATE("TCO Frequency", 9), + HDSPM_AUTOSYNC_SAMPLE_RATE("SYNC IN Frequency", 10), + HDSPM_TOGGLE_SETTING("Line Out", HDSPM_LineOut), + HDSPM_TOGGLE_SETTING("Emphasis", HDSPM_Emphasis), + HDSPM_TOGGLE_SETTING("Non Audio", HDSPM_Dolby), + HDSPM_TOGGLE_SETTING("Professional", HDSPM_Professional), + HDSPM_TOGGLE_SETTING("Clear Track Marker", HDSPM_clr_tms), + HDSPM_DS_WIRE("Double Speed Wire Mode", 0), + HDSPM_QS_WIRE("Quad Speed Wire Mode", 0), +}; + + + +/* Control elements for the optional TCO module */ +static const struct snd_kcontrol_new snd_hdspm_controls_tco[] = { + HDSPM_TCO_SAMPLE_RATE("TCO Sample Rate", 0), + HDSPM_TCO_PULL("TCO Pull", 0), + HDSPM_TCO_WCK_CONVERSION("TCO WCK Conversion", 0), + HDSPM_TCO_FRAME_RATE("TCO Frame Rate", 0), + HDSPM_TCO_SYNC_SOURCE("TCO Sync Source", 0), + HDSPM_TCO_WORD_TERM("TCO Word Term", 0), + HDSPM_TCO_LOCK_CHECK("TCO Input Check", 11), + HDSPM_TCO_LOCK_CHECK("TCO LTC Valid", 12), + HDSPM_TCO_LTC_FRAMES("TCO Detected Frame Rate"), + HDSPM_TCO_VIDEO_INPUT_FORMAT("Video Input Format") +}; + + +static struct snd_kcontrol_new snd_hdspm_playback_mixer = HDSPM_PLAYBACK_MIXER; + + +static int hdspm_update_simple_mixer_controls(struct hdspm * hdspm) +{ + int i; + + for (i = hdspm->ds_out_channels; i < hdspm->ss_out_channels; ++i) { + if (hdspm->system_sample_rate > 48000) { + hdspm->playback_mixer_ctls[i]->vd[0].access = + SNDRV_CTL_ELEM_ACCESS_INACTIVE | + SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE; + } else { + hdspm->playback_mixer_ctls[i]->vd[0].access = + SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE; + } + snd_ctl_notify(hdspm->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, + &hdspm->playback_mixer_ctls[i]->id); + } + + return 0; +} + + +static int snd_hdspm_create_controls(struct snd_card *card, + struct hdspm *hdspm) +{ + unsigned int idx, limit; + int err; + struct snd_kcontrol *kctl; + const struct snd_kcontrol_new *list = NULL; + + switch (hdspm->io_type) { + case MADI: + list = snd_hdspm_controls_madi; + limit = ARRAY_SIZE(snd_hdspm_controls_madi); + break; + case MADIface: + list = snd_hdspm_controls_madiface; + limit = ARRAY_SIZE(snd_hdspm_controls_madiface); + break; + case AIO: + list = snd_hdspm_controls_aio; + limit = ARRAY_SIZE(snd_hdspm_controls_aio); + break; + case RayDAT: + list = snd_hdspm_controls_raydat; + limit = ARRAY_SIZE(snd_hdspm_controls_raydat); + break; + case AES32: + list = snd_hdspm_controls_aes32; + limit = ARRAY_SIZE(snd_hdspm_controls_aes32); + break; + } + + if (list) { + for (idx = 0; idx < limit; idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&list[idx], hdspm)); + if (err < 0) + return err; + } + } + + + /* create simple 1:1 playback mixer controls */ + snd_hdspm_playback_mixer.name = "Chn"; + if (hdspm->system_sample_rate >= 128000) { + limit = hdspm->qs_out_channels; + } else if (hdspm->system_sample_rate >= 64000) { + limit = hdspm->ds_out_channels; + } else { + limit = hdspm->ss_out_channels; + } + for (idx = 0; idx < limit; ++idx) { + snd_hdspm_playback_mixer.index = idx + 1; + kctl = snd_ctl_new1(&snd_hdspm_playback_mixer, hdspm); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + hdspm->playback_mixer_ctls[idx] = kctl; + } + + + if (hdspm->tco) { + /* add tco control elements */ + list = snd_hdspm_controls_tco; + limit = ARRAY_SIZE(snd_hdspm_controls_tco); + for (idx = 0; idx < limit; idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&list[idx], hdspm)); + if (err < 0) + return err; + } + } + + return 0; +} + +/*------------------------------------------------------------ + /proc interface + ------------------------------------------------------------*/ + +static void +snd_hdspm_proc_read_tco(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdspm *hdspm = entry->private_data; + unsigned int status, control; + int a, ltc, frames, seconds, minutes, hours; + unsigned int period; + u64 freq_const = 0; + u32 rate; + + snd_iprintf(buffer, "--- TCO ---\n"); + + status = hdspm_read(hdspm, HDSPM_statusRegister); + control = hdspm->control_register; + + + if (status & HDSPM_tco_detect) { + snd_iprintf(buffer, "TCO module detected.\n"); + a = hdspm_read(hdspm, HDSPM_RD_TCO+4); + if (a & HDSPM_TCO1_LTC_Input_valid) { + snd_iprintf(buffer, " LTC valid, "); + switch (a & (HDSPM_TCO1_LTC_Format_LSB | + HDSPM_TCO1_LTC_Format_MSB)) { + case 0: + snd_iprintf(buffer, "24 fps, "); + break; + case HDSPM_TCO1_LTC_Format_LSB: + snd_iprintf(buffer, "25 fps, "); + break; + case HDSPM_TCO1_LTC_Format_MSB: + snd_iprintf(buffer, "29.97 fps, "); + break; + default: + snd_iprintf(buffer, "30 fps, "); + break; + } + if (a & HDSPM_TCO1_set_drop_frame_flag) { + snd_iprintf(buffer, "drop frame\n"); + } else { + snd_iprintf(buffer, "full frame\n"); + } + } else { + snd_iprintf(buffer, " no LTC\n"); + } + if (a & HDSPM_TCO1_Video_Input_Format_NTSC) { + snd_iprintf(buffer, " Video: NTSC\n"); + } else if (a & HDSPM_TCO1_Video_Input_Format_PAL) { + snd_iprintf(buffer, " Video: PAL\n"); + } else { + snd_iprintf(buffer, " No video\n"); + } + if (a & HDSPM_TCO1_TCO_lock) { + snd_iprintf(buffer, " Sync: lock\n"); + } else { + snd_iprintf(buffer, " Sync: no lock\n"); + } + + switch (hdspm->io_type) { + case MADI: + case AES32: + freq_const = 110069313433624ULL; + break; + case RayDAT: + case AIO: + freq_const = 104857600000000ULL; + break; + case MADIface: + break; /* no TCO possible */ + } + + period = hdspm_read(hdspm, HDSPM_RD_PLL_FREQ); + snd_iprintf(buffer, " period: %u\n", period); + + + /* rate = freq_const/period; */ + rate = div_u64(freq_const, period); + + if (control & HDSPM_QuadSpeed) { + rate *= 4; + } else if (control & HDSPM_DoubleSpeed) { + rate *= 2; + } + + snd_iprintf(buffer, " Frequency: %u Hz\n", + (unsigned int) rate); + + ltc = hdspm_read(hdspm, HDSPM_RD_TCO); + frames = ltc & 0xF; + ltc >>= 4; + frames += (ltc & 0x3) * 10; + ltc >>= 4; + seconds = ltc & 0xF; + ltc >>= 4; + seconds += (ltc & 0x7) * 10; + ltc >>= 4; + minutes = ltc & 0xF; + ltc >>= 4; + minutes += (ltc & 0x7) * 10; + ltc >>= 4; + hours = ltc & 0xF; + ltc >>= 4; + hours += (ltc & 0x3) * 10; + snd_iprintf(buffer, + " LTC In: %02d:%02d:%02d:%02d\n", + hours, minutes, seconds, frames); + + } else { + snd_iprintf(buffer, "No TCO module detected.\n"); + } +} + +static void +snd_hdspm_proc_read_madi(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdspm *hdspm = entry->private_data; + unsigned int status, status2; + + char *pref_sync_ref; + char *autosync_ref; + char *system_clock_mode; + int x, x2; + + status = hdspm_read(hdspm, HDSPM_statusRegister); + status2 = hdspm_read(hdspm, HDSPM_statusRegister2); + + snd_iprintf(buffer, "%s (Card #%d) Rev.%x Status2first3bits: %x\n", + hdspm->card_name, hdspm->card->number + 1, + hdspm->firmware_rev, + (status2 & HDSPM_version0) | + (status2 & HDSPM_version1) | (status2 & + HDSPM_version2)); + + snd_iprintf(buffer, "HW Serial: 0x%06x%06x\n", + (hdspm_read(hdspm, HDSPM_midiStatusIn1)>>8) & 0xFFFFFF, + hdspm->serial); + + snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n", + hdspm->irq, hdspm->port, (unsigned long)hdspm->iobase); + + snd_iprintf(buffer, "--- System ---\n"); + + snd_iprintf(buffer, + "IRQ Pending: Audio=%d, MIDI0=%d, MIDI1=%d, IRQcount=%d\n", + status & HDSPM_audioIRQPending, + (status & HDSPM_midi0IRQPending) ? 1 : 0, + (status & HDSPM_midi1IRQPending) ? 1 : 0, + hdspm->irq_count); + snd_iprintf(buffer, + "HW pointer: id = %d, rawptr = %d (%d->%d) " + "estimated= %ld (bytes)\n", + ((status & HDSPM_BufferID) ? 1 : 0), + (status & HDSPM_BufferPositionMask), + (status & HDSPM_BufferPositionMask) % + (2 * (int)hdspm->period_bytes), + ((status & HDSPM_BufferPositionMask) - 64) % + (2 * (int)hdspm->period_bytes), + (long) hdspm_hw_pointer(hdspm) * 4); + + snd_iprintf(buffer, + "MIDI FIFO: Out1=0x%x, Out2=0x%x, In1=0x%x, In2=0x%x \n", + hdspm_read(hdspm, HDSPM_midiStatusOut0) & 0xFF, + hdspm_read(hdspm, HDSPM_midiStatusOut1) & 0xFF, + hdspm_read(hdspm, HDSPM_midiStatusIn0) & 0xFF, + hdspm_read(hdspm, HDSPM_midiStatusIn1) & 0xFF); + snd_iprintf(buffer, + "MIDIoverMADI FIFO: In=0x%x, Out=0x%x \n", + hdspm_read(hdspm, HDSPM_midiStatusIn2) & 0xFF, + hdspm_read(hdspm, HDSPM_midiStatusOut2) & 0xFF); + snd_iprintf(buffer, + "Register: ctrl1=0x%x, ctrl2=0x%x, status1=0x%x, " + "status2=0x%x\n", + hdspm->control_register, hdspm->control2_register, + status, status2); + + + snd_iprintf(buffer, "--- Settings ---\n"); + + x = hdspm_get_latency(hdspm); + + snd_iprintf(buffer, + "Size (Latency): %d samples (2 periods of %lu bytes)\n", + x, (unsigned long) hdspm->period_bytes); + + snd_iprintf(buffer, "Line out: %s\n", + str_on_off(hdspm->control_register & HDSPM_LineOut)); + + snd_iprintf(buffer, + "ClearTrackMarker = %s, Transmit in %s Channel Mode, " + "Auto Input %s\n", + str_on_off(hdspm->control_register & HDSPM_clr_tms), + (hdspm->control_register & HDSPM_TX_64ch) ? "64" : "56", + str_on_off(hdspm->control_register & HDSPM_AutoInp)); + + + if (!(hdspm->control_register & HDSPM_ClockModeMaster)) + system_clock_mode = "AutoSync"; + else + system_clock_mode = "Master"; + snd_iprintf(buffer, "AutoSync Reference: %s\n", system_clock_mode); + + switch (hdspm_pref_sync_ref(hdspm)) { + case HDSPM_SYNC_FROM_WORD: + pref_sync_ref = "Word Clock"; + break; + case HDSPM_SYNC_FROM_MADI: + pref_sync_ref = "MADI Sync"; + break; + case HDSPM_SYNC_FROM_TCO: + pref_sync_ref = "TCO"; + break; + case HDSPM_SYNC_FROM_SYNC_IN: + pref_sync_ref = "Sync In"; + break; + default: + pref_sync_ref = "XXXX Clock"; + break; + } + snd_iprintf(buffer, "Preferred Sync Reference: %s\n", + pref_sync_ref); + + snd_iprintf(buffer, "System Clock Frequency: %d\n", + hdspm->system_sample_rate); + + + snd_iprintf(buffer, "--- Status:\n"); + + x = status & HDSPM_madiSync; + x2 = status2 & HDSPM_wcSync; + + snd_iprintf(buffer, "Inputs MADI=%s, WordClock=%s\n", + (status & HDSPM_madiLock) ? (x ? "Sync" : "Lock") : + "NoLock", + (status2 & HDSPM_wcLock) ? (x2 ? "Sync" : "Lock") : + "NoLock"); + + switch (hdspm_autosync_ref(hdspm)) { + case HDSPM_AUTOSYNC_FROM_SYNC_IN: + autosync_ref = "Sync In"; + break; + case HDSPM_AUTOSYNC_FROM_TCO: + autosync_ref = "TCO"; + break; + case HDSPM_AUTOSYNC_FROM_WORD: + autosync_ref = "Word Clock"; + break; + case HDSPM_AUTOSYNC_FROM_MADI: + autosync_ref = "MADI Sync"; + break; + case HDSPM_AUTOSYNC_FROM_NONE: + autosync_ref = "Input not valid"; + break; + default: + autosync_ref = "---"; + break; + } + snd_iprintf(buffer, + "AutoSync: Reference= %s, Freq=%d (MADI = %d, Word = %d)\n", + autosync_ref, hdspm_external_sample_rate(hdspm), + (status & HDSPM_madiFreqMask) >> 22, + (status2 & HDSPM_wcFreqMask) >> 5); + + snd_iprintf(buffer, "Input: %s, Mode=%s\n", + (status & HDSPM_AB_int) ? "Coax" : "Optical", + (status & HDSPM_RX_64ch) ? "64 channels" : + "56 channels"); + + /* call readout function for TCO specific status */ + snd_hdspm_proc_read_tco(entry, buffer); + + snd_iprintf(buffer, "\n"); +} + +static void +snd_hdspm_proc_read_aes32(struct snd_info_entry * entry, + struct snd_info_buffer *buffer) +{ + struct hdspm *hdspm = entry->private_data; + unsigned int status; + unsigned int status2; + unsigned int timecode; + unsigned int wcLock, wcSync; + int pref_syncref; + char *autosync_ref; + int x; + + status = hdspm_read(hdspm, HDSPM_statusRegister); + status2 = hdspm_read(hdspm, HDSPM_statusRegister2); + timecode = hdspm_read(hdspm, HDSPM_timecodeRegister); + + snd_iprintf(buffer, "%s (Card #%d) Rev.%x\n", + hdspm->card_name, hdspm->card->number + 1, + hdspm->firmware_rev); + + snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n", + hdspm->irq, hdspm->port, (unsigned long)hdspm->iobase); + + snd_iprintf(buffer, "--- System ---\n"); + + snd_iprintf(buffer, + "IRQ Pending: Audio=%d, MIDI0=%d, MIDI1=%d, IRQcount=%d\n", + status & HDSPM_audioIRQPending, + (status & HDSPM_midi0IRQPending) ? 1 : 0, + (status & HDSPM_midi1IRQPending) ? 1 : 0, + hdspm->irq_count); + snd_iprintf(buffer, + "HW pointer: id = %d, rawptr = %d (%d->%d) " + "estimated= %ld (bytes)\n", + ((status & HDSPM_BufferID) ? 1 : 0), + (status & HDSPM_BufferPositionMask), + (status & HDSPM_BufferPositionMask) % + (2 * (int)hdspm->period_bytes), + ((status & HDSPM_BufferPositionMask) - 64) % + (2 * (int)hdspm->period_bytes), + (long) hdspm_hw_pointer(hdspm) * 4); + + snd_iprintf(buffer, + "MIDI FIFO: Out1=0x%x, Out2=0x%x, In1=0x%x, In2=0x%x \n", + hdspm_read(hdspm, HDSPM_midiStatusOut0) & 0xFF, + hdspm_read(hdspm, HDSPM_midiStatusOut1) & 0xFF, + hdspm_read(hdspm, HDSPM_midiStatusIn0) & 0xFF, + hdspm_read(hdspm, HDSPM_midiStatusIn1) & 0xFF); + snd_iprintf(buffer, + "MIDIoverMADI FIFO: In=0x%x, Out=0x%x \n", + hdspm_read(hdspm, HDSPM_midiStatusIn2) & 0xFF, + hdspm_read(hdspm, HDSPM_midiStatusOut2) & 0xFF); + snd_iprintf(buffer, + "Register: ctrl1=0x%x, ctrl2=0x%x, status1=0x%x, " + "status2=0x%x\n", + hdspm->control_register, hdspm->control2_register, + status, status2); + + snd_iprintf(buffer, "--- Settings ---\n"); + + x = hdspm_get_latency(hdspm); + + snd_iprintf(buffer, + "Size (Latency): %d samples (2 periods of %lu bytes)\n", + x, (unsigned long) hdspm->period_bytes); + + snd_iprintf(buffer, "Line out: %s\n", + (hdspm-> + control_register & HDSPM_LineOut) ? "on " : "off"); + + snd_iprintf(buffer, + "ClearTrackMarker %s, Emphasis %s, Dolby %s\n", + str_on_off(hdspm->control_register & HDSPM_clr_tms), + str_on_off(hdspm->control_register & HDSPM_Emphasis), + str_on_off(hdspm->control_register & HDSPM_Dolby)); + + + pref_syncref = hdspm_pref_sync_ref(hdspm); + if (pref_syncref == 0) + snd_iprintf(buffer, "Preferred Sync Reference: Word Clock\n"); + else + snd_iprintf(buffer, "Preferred Sync Reference: AES%d\n", + pref_syncref); + + snd_iprintf(buffer, "System Clock Frequency: %d\n", + hdspm->system_sample_rate); + + snd_iprintf(buffer, "Double speed: %s\n", + hdspm->control_register & HDSPM_DS_DoubleWire? + "Double wire" : "Single wire"); + snd_iprintf(buffer, "Quad speed: %s\n", + hdspm->control_register & HDSPM_QS_DoubleWire? + "Double wire" : + hdspm->control_register & HDSPM_QS_QuadWire? + "Quad wire" : "Single wire"); + + snd_iprintf(buffer, "--- Status:\n"); + + wcLock = status & HDSPM_AES32_wcLock; + wcSync = wcLock && (status & HDSPM_AES32_wcSync); + + snd_iprintf(buffer, "Word: %s Frequency: %d\n", + (wcLock) ? (wcSync ? "Sync " : "Lock ") : "No Lock", + HDSPM_bit2freq((status >> HDSPM_AES32_wcFreq_bit) & 0xF)); + + for (x = 0; x < 8; x++) { + snd_iprintf(buffer, "AES%d: %s Frequency: %d\n", + x+1, + (status2 & (HDSPM_LockAES >> x)) ? + "Sync " : "No Lock", + HDSPM_bit2freq((timecode >> (4*x)) & 0xF)); + } + + switch (hdspm_autosync_ref(hdspm)) { + case HDSPM_AES32_AUTOSYNC_FROM_NONE: + autosync_ref = "None"; break; + case HDSPM_AES32_AUTOSYNC_FROM_WORD: + autosync_ref = "Word Clock"; break; + case HDSPM_AES32_AUTOSYNC_FROM_AES1: + autosync_ref = "AES1"; break; + case HDSPM_AES32_AUTOSYNC_FROM_AES2: + autosync_ref = "AES2"; break; + case HDSPM_AES32_AUTOSYNC_FROM_AES3: + autosync_ref = "AES3"; break; + case HDSPM_AES32_AUTOSYNC_FROM_AES4: + autosync_ref = "AES4"; break; + case HDSPM_AES32_AUTOSYNC_FROM_AES5: + autosync_ref = "AES5"; break; + case HDSPM_AES32_AUTOSYNC_FROM_AES6: + autosync_ref = "AES6"; break; + case HDSPM_AES32_AUTOSYNC_FROM_AES7: + autosync_ref = "AES7"; break; + case HDSPM_AES32_AUTOSYNC_FROM_AES8: + autosync_ref = "AES8"; break; + case HDSPM_AES32_AUTOSYNC_FROM_TCO: + autosync_ref = "TCO"; break; + case HDSPM_AES32_AUTOSYNC_FROM_SYNC_IN: + autosync_ref = "Sync In"; break; + default: + autosync_ref = "---"; break; + } + snd_iprintf(buffer, "AutoSync ref = %s\n", autosync_ref); + + /* call readout function for TCO specific status */ + snd_hdspm_proc_read_tco(entry, buffer); + + snd_iprintf(buffer, "\n"); +} + +static void +snd_hdspm_proc_read_raydat(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdspm *hdspm = entry->private_data; + unsigned int status1, status2, status3, i; + unsigned int lock, sync; + + status1 = hdspm_read(hdspm, HDSPM_RD_STATUS_1); /* s1 */ + status2 = hdspm_read(hdspm, HDSPM_RD_STATUS_2); /* freq */ + status3 = hdspm_read(hdspm, HDSPM_RD_STATUS_3); /* s2 */ + + snd_iprintf(buffer, "STATUS1: 0x%08x\n", status1); + snd_iprintf(buffer, "STATUS2: 0x%08x\n", status2); + snd_iprintf(buffer, "STATUS3: 0x%08x\n", status3); + + + snd_iprintf(buffer, "\n*** CLOCK MODE\n\n"); + + snd_iprintf(buffer, "Clock mode : %s\n", + (hdspm_system_clock_mode(hdspm) == 0) ? "master" : "slave"); + snd_iprintf(buffer, "System frequency: %d Hz\n", + hdspm_get_system_sample_rate(hdspm)); + + snd_iprintf(buffer, "\n*** INPUT STATUS\n\n"); + + lock = 0x1; + sync = 0x100; + + for (i = 0; i < 8; i++) { + snd_iprintf(buffer, "s1_input %d: Lock %d, Sync %d, Freq %s\n", + i, + (status1 & lock) ? 1 : 0, + (status1 & sync) ? 1 : 0, + texts_freq[(status2 >> (i * 4)) & 0xF]); + + lock = lock<<1; + sync = sync<<1; + } + + snd_iprintf(buffer, "WC input: Lock %d, Sync %d, Freq %s\n", + (status1 & 0x1000000) ? 1 : 0, + (status1 & 0x2000000) ? 1 : 0, + texts_freq[(status1 >> 16) & 0xF]); + + snd_iprintf(buffer, "TCO input: Lock %d, Sync %d, Freq %s\n", + (status1 & 0x4000000) ? 1 : 0, + (status1 & 0x8000000) ? 1 : 0, + texts_freq[(status1 >> 20) & 0xF]); + + snd_iprintf(buffer, "SYNC IN: Lock %d, Sync %d, Freq %s\n", + (status3 & 0x400) ? 1 : 0, + (status3 & 0x800) ? 1 : 0, + texts_freq[(status2 >> 12) & 0xF]); + +} + +#ifdef CONFIG_SND_DEBUG +static void +snd_hdspm_proc_read_debug(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdspm *hdspm = entry->private_data; + + int j,i; + + for (i = 0; i < 256 /* 1024*64 */; i += j) { + snd_iprintf(buffer, "0x%08X: ", i); + for (j = 0; j < 16; j += 4) + snd_iprintf(buffer, "%08X ", hdspm_read(hdspm, i + j)); + snd_iprintf(buffer, "\n"); + } +} +#endif + + +static void snd_hdspm_proc_ports_in(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdspm *hdspm = entry->private_data; + int i; + + snd_iprintf(buffer, "# generated by hdspm\n"); + + for (i = 0; i < hdspm->max_channels_in; i++) { + snd_iprintf(buffer, "%d=%s\n", i+1, hdspm->port_names_in[i]); + } +} + +static void snd_hdspm_proc_ports_out(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdspm *hdspm = entry->private_data; + int i; + + snd_iprintf(buffer, "# generated by hdspm\n"); + + for (i = 0; i < hdspm->max_channels_out; i++) { + snd_iprintf(buffer, "%d=%s\n", i+1, hdspm->port_names_out[i]); + } +} + + +static void snd_hdspm_proc_init(struct hdspm *hdspm) +{ + void (*read)(struct snd_info_entry *, struct snd_info_buffer *) = NULL; + + switch (hdspm->io_type) { + case AES32: + read = snd_hdspm_proc_read_aes32; + break; + case MADI: + read = snd_hdspm_proc_read_madi; + break; + case MADIface: + /* read = snd_hdspm_proc_read_madiface; */ + break; + case RayDAT: + read = snd_hdspm_proc_read_raydat; + break; + case AIO: + break; + } + + snd_card_ro_proc_new(hdspm->card, "hdspm", hdspm, read); + snd_card_ro_proc_new(hdspm->card, "ports.in", hdspm, + snd_hdspm_proc_ports_in); + snd_card_ro_proc_new(hdspm->card, "ports.out", hdspm, + snd_hdspm_proc_ports_out); + +#ifdef CONFIG_SND_DEBUG + /* debug file to read all hdspm registers */ + snd_card_ro_proc_new(hdspm->card, "debug", hdspm, + snd_hdspm_proc_read_debug); +#endif +} + +/*------------------------------------------------------------ + hdspm intitialize + ------------------------------------------------------------*/ + +static int snd_hdspm_set_defaults(struct hdspm * hdspm) +{ + /* ASSUMPTION: hdspm->lock is either held, or there is no need to + hold it (e.g. during module initialization). + */ + + /* set defaults: */ + + hdspm->settings_register = 0; + + switch (hdspm->io_type) { + case MADI: + case MADIface: + hdspm->control_register = + 0x2 + 0x8 + 0x10 + 0x80 + 0x400 + 0x4000 + 0x1000000; + break; + + case RayDAT: + case AIO: + hdspm->settings_register = 0x1 + 0x1000; + /* Magic values are: LAT_0, LAT_2, Master, freq1, tx64ch, inp_0, + * line_out */ + hdspm->control_register = + 0x2 + 0x8 + 0x10 + 0x80 + 0x400 + 0x4000 + 0x1000000; + break; + + case AES32: + hdspm->control_register = + HDSPM_ClockModeMaster | /* Master Clock Mode on */ + hdspm_encode_latency(7) | /* latency max=8192samples */ + HDSPM_SyncRef0 | /* AES1 is syncclock */ + HDSPM_LineOut | /* Analog output in */ + HDSPM_Professional; /* Professional mode */ + break; + } + + hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register); + + if (AES32 == hdspm->io_type) { + /* No control2 register for AES32 */ +#ifdef SNDRV_BIG_ENDIAN + hdspm->control2_register = HDSPM_BIGENDIAN_MODE; +#else + hdspm->control2_register = 0; +#endif + + hdspm_write(hdspm, HDSPM_control2Reg, hdspm->control2_register); + } + hdspm_compute_period_size(hdspm); + + /* silence everything */ + + all_in_all_mixer(hdspm, 0 * UNITY_GAIN); + + if (hdspm_is_raydat_or_aio(hdspm)) + hdspm_write(hdspm, HDSPM_WR_SETTINGS, hdspm->settings_register); + + /* set a default rate so that the channel map is set up. */ + hdspm_set_rate(hdspm, 48000, 1); + + return 0; +} + + +/*------------------------------------------------------------ + interrupt + ------------------------------------------------------------*/ + +static irqreturn_t snd_hdspm_interrupt(int irq, void *dev_id) +{ + struct hdspm *hdspm = (struct hdspm *) dev_id; + unsigned int status; + int i, audio, midi, schedule = 0; + /* cycles_t now; */ + + status = hdspm_read(hdspm, HDSPM_statusRegister); + + audio = status & HDSPM_audioIRQPending; + midi = status & (HDSPM_midi0IRQPending | HDSPM_midi1IRQPending | + HDSPM_midi2IRQPending | HDSPM_midi3IRQPending); + + /* now = get_cycles(); */ + /* + * LAT_2..LAT_0 period counter (win) counter (mac) + * 6 4096 ~256053425 ~514672358 + * 5 2048 ~128024983 ~257373821 + * 4 1024 ~64023706 ~128718089 + * 3 512 ~32005945 ~64385999 + * 2 256 ~16003039 ~32260176 + * 1 128 ~7998738 ~16194507 + * 0 64 ~3998231 ~8191558 + */ + /* + dev_info(hdspm->card->dev, "snd_hdspm_interrupt %llu @ %llx\n", + now-hdspm->last_interrupt, status & 0xFFC0); + hdspm->last_interrupt = now; + */ + + if (!audio && !midi) + return IRQ_NONE; + + hdspm_write(hdspm, HDSPM_interruptConfirmation, 0); + hdspm->irq_count++; + + + if (audio) { + if (hdspm->capture_substream) + snd_pcm_period_elapsed(hdspm->capture_substream); + + if (hdspm->playback_substream) + snd_pcm_period_elapsed(hdspm->playback_substream); + } + + if (midi) { + i = 0; + while (i < hdspm->midiPorts) { + if ((hdspm_read(hdspm, + hdspm->midi[i].statusIn) & 0xff) && + (status & hdspm->midi[i].irq)) { + /* we disable interrupts for this input until + * processing is done + */ + hdspm->control_register &= ~hdspm->midi[i].ie; + hdspm_write(hdspm, HDSPM_controlRegister, + hdspm->control_register); + hdspm->midi[i].pending = 1; + schedule = 1; + } + + i++; + } + + if (schedule) + queue_work(system_highpri_wq, &hdspm->midi_work); + } + + return IRQ_HANDLED; +} + +/*------------------------------------------------------------ + pcm interface + ------------------------------------------------------------*/ + + +static snd_pcm_uframes_t snd_hdspm_hw_pointer(struct snd_pcm_substream + *substream) +{ + struct hdspm *hdspm = snd_pcm_substream_chip(substream); + return hdspm_hw_pointer(hdspm); +} + + +static int snd_hdspm_reset(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdspm *hdspm = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *other; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + other = hdspm->capture_substream; + else + other = hdspm->playback_substream; + + if (hdspm->running) + runtime->status->hw_ptr = hdspm_hw_pointer(hdspm); + else + runtime->status->hw_ptr = 0; + if (other) { + struct snd_pcm_substream *s; + struct snd_pcm_runtime *oruntime = other->runtime; + snd_pcm_group_for_each_entry(s, substream) { + if (s == other) { + oruntime->status->hw_ptr = + runtime->status->hw_ptr; + break; + } + } + } + return 0; +} + +static int snd_hdspm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct hdspm *hdspm = snd_pcm_substream_chip(substream); + int err; + int i; + pid_t this_pid; + pid_t other_pid; + + scoped_guard(spinlock_irq, &hdspm->lock) { + + if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) { + this_pid = hdspm->playback_pid; + other_pid = hdspm->capture_pid; + } else { + this_pid = hdspm->capture_pid; + other_pid = hdspm->playback_pid; + } + + if (other_pid > 0 && this_pid != other_pid) { + + /* The other stream is open, and not by the same + task as this one. Make sure that the parameters + that matter are the same. + */ + + if (params_rate(params) != hdspm->system_sample_rate) { + _snd_pcm_hw_param_setempty(params, + SNDRV_PCM_HW_PARAM_RATE); + return -EBUSY; + } + + if (params_period_size(params) != hdspm->period_bytes / 4) { + _snd_pcm_hw_param_setempty(params, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + return -EBUSY; + } + + } + } + /* We're fine. */ + + /* how to make sure that the rate matches an externally-set one ? */ + + scoped_guard(spinlock_irq, &hdspm->lock) { + err = hdspm_set_rate(hdspm, params_rate(params), 0); + if (err < 0) { + dev_info(hdspm->card->dev, "err on hdspm_set_rate: %d\n", err); + _snd_pcm_hw_param_setempty(params, + SNDRV_PCM_HW_PARAM_RATE); + return err; + } + } + + err = hdspm_set_interrupt_interval(hdspm, + params_period_size(params)); + if (err < 0) { + dev_info(hdspm->card->dev, + "err on hdspm_set_interrupt_interval: %d\n", err); + _snd_pcm_hw_param_setempty(params, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + return err; + } + + /* Memory allocation, takashi's method, dont know if we should + * spinlock + */ + /* malloc all buffer even if not enabled to get sure */ + /* Update for MADI rev 204: we need to allocate for all channels, + * otherwise it doesn't work at 96kHz */ + + err = + snd_pcm_lib_malloc_pages(substream, HDSPM_DMA_AREA_BYTES); + if (err < 0) { + dev_info(hdspm->card->dev, + "err on snd_pcm_lib_malloc_pages: %d\n", err); + return err; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + for (i = 0; i < params_channels(params); ++i) { + int c = hdspm->channel_map_out[i]; + + if (c < 0) + continue; /* just make sure */ + hdspm_set_channel_dma_addr(hdspm, substream, + HDSPM_pageAddressBufferOut, + c); + snd_hdspm_enable_out(hdspm, c, 1); + } + + hdspm->playback_buffer = + (unsigned char *) substream->runtime->dma_area; + dev_dbg(hdspm->card->dev, + "Allocated sample buffer for playback at %p\n", + hdspm->playback_buffer); + } else { + for (i = 0; i < params_channels(params); ++i) { + int c = hdspm->channel_map_in[i]; + + if (c < 0) + continue; + hdspm_set_channel_dma_addr(hdspm, substream, + HDSPM_pageAddressBufferIn, + c); + snd_hdspm_enable_in(hdspm, c, 1); + } + + hdspm->capture_buffer = + (unsigned char *) substream->runtime->dma_area; + dev_dbg(hdspm->card->dev, + "Allocated sample buffer for capture at %p\n", + hdspm->capture_buffer); + } + + /* + dev_dbg(hdspm->card->dev, + "Allocated sample buffer for %s at 0x%08X\n", + snd_pcm_direction_name(substream->stream), + snd_pcm_sgbuf_get_addr(substream, 0)); + */ + /* + dev_dbg(hdspm->card->dev, + "set_hwparams: %s %d Hz, %d channels, bs = %d\n", + snd_pcm_direction_name(substream->stream), + params_rate(params), params_channels(params), + params_buffer_size(params)); + */ + + + /* For AES cards, the float format bit is the same as the + * preferred sync reference. Since we don't want to break + * sync settings, we have to skip the remaining part of this + * function. + */ + if (hdspm->io_type == AES32) { + return 0; + } + + + /* Switch to native float format if requested */ + if (SNDRV_PCM_FORMAT_FLOAT_LE == params_format(params)) { + if (!(hdspm->control_register & HDSPe_FLOAT_FORMAT)) + dev_info(hdspm->card->dev, + "Switching to native 32bit LE float format.\n"); + + hdspm->control_register |= HDSPe_FLOAT_FORMAT; + } else if (SNDRV_PCM_FORMAT_S32_LE == params_format(params)) { + if (hdspm->control_register & HDSPe_FLOAT_FORMAT) + dev_info(hdspm->card->dev, + "Switching to native 32bit LE integer format.\n"); + + hdspm->control_register &= ~HDSPe_FLOAT_FORMAT; + } + hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register); + + return 0; +} + +static int snd_hdspm_hw_free(struct snd_pcm_substream *substream) +{ + int i; + struct hdspm *hdspm = snd_pcm_substream_chip(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Just disable all channels. The saving when disabling a */ + /* smaller set is not worth the trouble. */ + for (i = 0; i < HDSPM_MAX_CHANNELS; ++i) + snd_hdspm_enable_out(hdspm, i, 0); + + hdspm->playback_buffer = NULL; + } else { + for (i = 0; i < HDSPM_MAX_CHANNELS; ++i) + snd_hdspm_enable_in(hdspm, i, 0); + + hdspm->capture_buffer = NULL; + } + + snd_pcm_lib_free_pages(substream); + + return 0; +} + + +static int snd_hdspm_channel_info(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info *info) +{ + struct hdspm *hdspm = snd_pcm_substream_chip(substream); + unsigned int channel = info->channel; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (snd_BUG_ON(channel >= hdspm->max_channels_out)) { + dev_info(hdspm->card->dev, + "snd_hdspm_channel_info: output channel out of range (%d)\n", + channel); + return -EINVAL; + } + + channel = array_index_nospec(channel, hdspm->max_channels_out); + if (hdspm->channel_map_out[channel] < 0) { + dev_info(hdspm->card->dev, + "snd_hdspm_channel_info: output channel %d mapped out\n", + channel); + return -EINVAL; + } + + info->offset = hdspm->channel_map_out[channel] * + HDSPM_CHANNEL_BUFFER_BYTES; + } else { + if (snd_BUG_ON(channel >= hdspm->max_channels_in)) { + dev_info(hdspm->card->dev, + "snd_hdspm_channel_info: input channel out of range (%d)\n", + channel); + return -EINVAL; + } + + channel = array_index_nospec(channel, hdspm->max_channels_in); + if (hdspm->channel_map_in[channel] < 0) { + dev_info(hdspm->card->dev, + "snd_hdspm_channel_info: input channel %d mapped out\n", + channel); + return -EINVAL; + } + + info->offset = hdspm->channel_map_in[channel] * + HDSPM_CHANNEL_BUFFER_BYTES; + } + + info->first = 0; + info->step = 32; + return 0; +} + + +static int snd_hdspm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_RESET: + return snd_hdspm_reset(substream); + + case SNDRV_PCM_IOCTL1_CHANNEL_INFO: + { + struct snd_pcm_channel_info *info = arg; + return snd_hdspm_channel_info(substream, info); + } + default: + break; + } + + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int snd_hdspm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct hdspm *hdspm = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *other; + int running; + + guard(spinlock)(&hdspm->lock); + running = hdspm->running; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + running |= 1 << substream->stream; + break; + case SNDRV_PCM_TRIGGER_STOP: + running &= ~(1 << substream->stream); + break; + default: + snd_BUG(); + return -EINVAL; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + other = hdspm->capture_substream; + else + other = hdspm->playback_substream; + + if (other) { + struct snd_pcm_substream *s; + snd_pcm_group_for_each_entry(s, substream) { + if (s == other) { + snd_pcm_trigger_done(s, substream); + if (cmd == SNDRV_PCM_TRIGGER_START) + running |= 1 << s->stream; + else + running &= ~(1 << s->stream); + goto _ok; + } + } + if (cmd == SNDRV_PCM_TRIGGER_START) { + if (!(running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) + && substream->stream == + SNDRV_PCM_STREAM_CAPTURE) + hdspm_silence_playback(hdspm); + } else { + if (running && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + hdspm_silence_playback(hdspm); + } + } else { + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hdspm_silence_playback(hdspm); + } +_ok: + snd_pcm_trigger_done(substream, substream); + if (!hdspm->running && running) + hdspm_start_audio(hdspm); + else if (hdspm->running && !running) + hdspm_stop_audio(hdspm); + hdspm->running = running; + + return 0; +} + +static int snd_hdspm_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static const struct snd_pcm_hardware snd_hdspm_playback_subinfo = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_SYNC_START | SNDRV_PCM_INFO_DOUBLE), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000 ), + .rate_min = 32000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = HDSPM_MAX_CHANNELS, + .buffer_bytes_max = + HDSPM_CHANNEL_BUFFER_BYTES * HDSPM_MAX_CHANNELS, + .period_bytes_min = (32 * 4), + .period_bytes_max = (8192 * 4) * HDSPM_MAX_CHANNELS, + .periods_min = 2, + .periods_max = 512, + .fifo_size = 0 +}; + +static const struct snd_pcm_hardware snd_hdspm_capture_subinfo = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000), + .rate_min = 32000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = HDSPM_MAX_CHANNELS, + .buffer_bytes_max = + HDSPM_CHANNEL_BUFFER_BYTES * HDSPM_MAX_CHANNELS, + .period_bytes_min = (32 * 4), + .period_bytes_max = (8192 * 4) * HDSPM_MAX_CHANNELS, + .periods_min = 2, + .periods_max = 512, + .fifo_size = 0 +}; + +static int snd_hdspm_hw_rule_in_channels_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct hdspm *hdspm = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + if (r->min > 96000 && r->max <= 192000) { + struct snd_interval t = { + .min = hdspm->qs_in_channels, + .max = hdspm->qs_in_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else if (r->min > 48000 && r->max <= 96000) { + struct snd_interval t = { + .min = hdspm->ds_in_channels, + .max = hdspm->ds_in_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else if (r->max < 64000) { + struct snd_interval t = { + .min = hdspm->ss_in_channels, + .max = hdspm->ss_in_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } + + return 0; +} + +static int snd_hdspm_hw_rule_out_channels_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule * rule) +{ + struct hdspm *hdspm = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + if (r->min > 96000 && r->max <= 192000) { + struct snd_interval t = { + .min = hdspm->qs_out_channels, + .max = hdspm->qs_out_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else if (r->min > 48000 && r->max <= 96000) { + struct snd_interval t = { + .min = hdspm->ds_out_channels, + .max = hdspm->ds_out_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else if (r->max < 64000) { + struct snd_interval t = { + .min = hdspm->ss_out_channels, + .max = hdspm->ss_out_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else { + } + return 0; +} + +static int snd_hdspm_hw_rule_rate_in_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule * rule) +{ + struct hdspm *hdspm = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + if (c->min >= hdspm->ss_in_channels) { + struct snd_interval t = { + .min = 32000, + .max = 48000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } else if (c->max <= hdspm->qs_in_channels) { + struct snd_interval t = { + .min = 128000, + .max = 192000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } else if (c->max <= hdspm->ds_in_channels) { + struct snd_interval t = { + .min = 64000, + .max = 96000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } + + return 0; +} +static int snd_hdspm_hw_rule_rate_out_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct hdspm *hdspm = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + if (c->min >= hdspm->ss_out_channels) { + struct snd_interval t = { + .min = 32000, + .max = 48000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } else if (c->max <= hdspm->qs_out_channels) { + struct snd_interval t = { + .min = 128000, + .max = 192000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } else if (c->max <= hdspm->ds_out_channels) { + struct snd_interval t = { + .min = 64000, + .max = 96000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } + + return 0; +} + +static int snd_hdspm_hw_rule_in_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned int list[3]; + struct hdspm *hdspm = rule->private; + struct snd_interval *c = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + list[0] = hdspm->qs_in_channels; + list[1] = hdspm->ds_in_channels; + list[2] = hdspm->ss_in_channels; + return snd_interval_list(c, 3, list, 0); +} + +static int snd_hdspm_hw_rule_out_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned int list[3]; + struct hdspm *hdspm = rule->private; + struct snd_interval *c = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + list[0] = hdspm->qs_out_channels; + list[1] = hdspm->ds_out_channels; + list[2] = hdspm->ss_out_channels; + return snd_interval_list(c, 3, list, 0); +} + +static int snd_hdspm_open(struct snd_pcm_substream *substream) +{ + struct hdspm *hdspm = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + bool playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + scoped_guard(spinlock_irq, &hdspm->lock) { + snd_pcm_set_sync(substream); + runtime->hw = (playback) ? snd_hdspm_playback_subinfo : + snd_hdspm_capture_subinfo; + + if (playback) { + if (!hdspm->capture_substream) + hdspm_stop_audio(hdspm); + + hdspm->playback_pid = current->pid; + hdspm->playback_substream = substream; + } else { + if (!hdspm->playback_substream) + hdspm_stop_audio(hdspm); + + hdspm->capture_pid = current->pid; + hdspm->capture_substream = substream; + } + } + + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + snd_pcm_hw_constraint_pow2(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + + switch (hdspm->io_type) { + case AIO: + case RayDAT: + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 32, 4096); + /* RayDAT & AIO have a fixed buffer of 16384 samples per channel */ + snd_pcm_hw_constraint_single(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + 16384); + break; + + default: + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 64, 8192); + snd_pcm_hw_constraint_single(runtime, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + break; + } + + if (AES32 == hdspm->io_type) { + runtime->hw.rates |= SNDRV_PCM_RATE_128000; + } else { + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + (playback ? + snd_hdspm_hw_rule_rate_out_channels : + snd_hdspm_hw_rule_rate_in_channels), hdspm, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + } + + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + (playback ? snd_hdspm_hw_rule_out_channels : + snd_hdspm_hw_rule_in_channels), hdspm, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + (playback ? snd_hdspm_hw_rule_out_channels_rate : + snd_hdspm_hw_rule_in_channels_rate), hdspm, + SNDRV_PCM_HW_PARAM_RATE, -1); + + return 0; +} + +static int snd_hdspm_release(struct snd_pcm_substream *substream) +{ + struct hdspm *hdspm = snd_pcm_substream_chip(substream); + bool playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + guard(spinlock_irq)(&hdspm->lock); + + if (playback) { + hdspm->playback_pid = -1; + hdspm->playback_substream = NULL; + } else { + hdspm->capture_pid = -1; + hdspm->capture_substream = NULL; + } + + return 0; +} + +static int snd_hdspm_hwdep_dummy_op(struct snd_hwdep *hw, struct file *file) +{ + /* we have nothing to initialize but the call is required */ + return 0; +} + +static int snd_hdspm_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct hdspm *hdspm = hw->private_data; + struct hdspm_mixer_ioctl mixer; + struct hdspm_config info; + struct hdspm_status status; + struct hdspm_version hdspm_version; + struct hdspm_peak_rms *levels; + struct hdspm_ltc ltc; + unsigned int statusregister; + long unsigned int s; + int i = 0; + + switch (cmd) { + + case SNDRV_HDSPM_IOCTL_GET_PEAK_RMS: + levels = &hdspm->peak_rms; + for (i = 0; i < HDSPM_MAX_CHANNELS; i++) { + levels->input_peaks[i] = + readl(hdspm->iobase + + HDSPM_MADI_INPUT_PEAK + i*4); + levels->playback_peaks[i] = + readl(hdspm->iobase + + HDSPM_MADI_PLAYBACK_PEAK + i*4); + levels->output_peaks[i] = + readl(hdspm->iobase + + HDSPM_MADI_OUTPUT_PEAK + i*4); + + levels->input_rms[i] = + ((uint64_t) readl(hdspm->iobase + + HDSPM_MADI_INPUT_RMS_H + i*4) << 32) | + (uint64_t) readl(hdspm->iobase + + HDSPM_MADI_INPUT_RMS_L + i*4); + levels->playback_rms[i] = + ((uint64_t)readl(hdspm->iobase + + HDSPM_MADI_PLAYBACK_RMS_H+i*4) << 32) | + (uint64_t)readl(hdspm->iobase + + HDSPM_MADI_PLAYBACK_RMS_L + i*4); + levels->output_rms[i] = + ((uint64_t)readl(hdspm->iobase + + HDSPM_MADI_OUTPUT_RMS_H + i*4) << 32) | + (uint64_t)readl(hdspm->iobase + + HDSPM_MADI_OUTPUT_RMS_L + i*4); + } + + if (hdspm->system_sample_rate > 96000) { + levels->speed = qs; + } else if (hdspm->system_sample_rate > 48000) { + levels->speed = ds; + } else { + levels->speed = ss; + } + levels->status2 = hdspm_read(hdspm, HDSPM_statusRegister2); + + s = copy_to_user(argp, levels, sizeof(*levels)); + if (0 != s) { + /* dev_err(hdspm->card->dev, "copy_to_user(.., .., %lu): %lu + [Levels]\n", sizeof(struct hdspm_peak_rms), s); + */ + return -EFAULT; + } + break; + + case SNDRV_HDSPM_IOCTL_GET_LTC: + ltc.ltc = hdspm_read(hdspm, HDSPM_RD_TCO); + i = hdspm_read(hdspm, HDSPM_RD_TCO + 4); + if (i & HDSPM_TCO1_LTC_Input_valid) { + switch (i & (HDSPM_TCO1_LTC_Format_LSB | + HDSPM_TCO1_LTC_Format_MSB)) { + case 0: + ltc.format = fps_24; + break; + case HDSPM_TCO1_LTC_Format_LSB: + ltc.format = fps_25; + break; + case HDSPM_TCO1_LTC_Format_MSB: + ltc.format = fps_2997; + break; + default: + ltc.format = fps_30; + break; + } + if (i & HDSPM_TCO1_set_drop_frame_flag) { + ltc.frame = drop_frame; + } else { + ltc.frame = full_frame; + } + } else { + ltc.format = format_invalid; + ltc.frame = frame_invalid; + } + if (i & HDSPM_TCO1_Video_Input_Format_NTSC) { + ltc.input_format = ntsc; + } else if (i & HDSPM_TCO1_Video_Input_Format_PAL) { + ltc.input_format = pal; + } else { + ltc.input_format = no_video; + } + + s = copy_to_user(argp, <c, sizeof(ltc)); + if (0 != s) { + /* + dev_err(hdspm->card->dev, "copy_to_user(.., .., %lu): %lu [LTC]\n", sizeof(struct hdspm_ltc), s); */ + return -EFAULT; + } + + break; + + case SNDRV_HDSPM_IOCTL_GET_CONFIG: + + memset(&info, 0, sizeof(info)); + scoped_guard(spinlock_irq, &hdspm->lock) { + info.pref_sync_ref = hdspm_pref_sync_ref(hdspm); + info.wordclock_sync_check = hdspm_wc_sync_check(hdspm); + + info.system_sample_rate = hdspm->system_sample_rate; + info.autosync_sample_rate = + hdspm_external_sample_rate(hdspm); + info.system_clock_mode = hdspm_system_clock_mode(hdspm); + info.clock_source = hdspm_clock_source(hdspm); + info.autosync_ref = hdspm_autosync_ref(hdspm); + info.line_out = hdspm_toggle_setting(hdspm, HDSPM_LineOut); + info.passthru = 0; + } + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + break; + + case SNDRV_HDSPM_IOCTL_GET_STATUS: + memset(&status, 0, sizeof(status)); + + status.card_type = hdspm->io_type; + + status.autosync_source = hdspm_autosync_ref(hdspm); + + status.card_clock = 110069313433624ULL; + status.master_period = hdspm_read(hdspm, HDSPM_RD_PLL_FREQ); + + switch (hdspm->io_type) { + case MADI: + case MADIface: + status.card_specific.madi.sync_wc = + hdspm_wc_sync_check(hdspm); + status.card_specific.madi.sync_madi = + hdspm_madi_sync_check(hdspm); + status.card_specific.madi.sync_tco = + hdspm_tco_sync_check(hdspm); + status.card_specific.madi.sync_in = + hdspm_sync_in_sync_check(hdspm); + + statusregister = + hdspm_read(hdspm, HDSPM_statusRegister); + status.card_specific.madi.madi_input = + (statusregister & HDSPM_AB_int) ? 1 : 0; + status.card_specific.madi.channel_format = + (statusregister & HDSPM_RX_64ch) ? 1 : 0; + /* TODO: Mac driver sets it when f_s>48kHz */ + status.card_specific.madi.frame_format = 0; + break; + + default: + break; + } + + if (copy_to_user(argp, &status, sizeof(status))) + return -EFAULT; + + + break; + + case SNDRV_HDSPM_IOCTL_GET_VERSION: + memset(&hdspm_version, 0, sizeof(hdspm_version)); + + hdspm_version.card_type = hdspm->io_type; + strscpy(hdspm_version.cardname, hdspm->card_name, + sizeof(hdspm_version.cardname)); + hdspm_version.serial = hdspm->serial; + hdspm_version.firmware_rev = hdspm->firmware_rev; + hdspm_version.addons = 0; + if (hdspm->tco) + hdspm_version.addons |= HDSPM_ADDON_TCO; + + if (copy_to_user(argp, &hdspm_version, + sizeof(hdspm_version))) + return -EFAULT; + break; + + case SNDRV_HDSPM_IOCTL_GET_MIXER: + if (copy_from_user(&mixer, argp, sizeof(mixer))) + return -EFAULT; + if (copy_to_user((void __user *)mixer.mixer, hdspm->mixer, + sizeof(*mixer.mixer))) + return -EFAULT; + break; + + default: + return -EINVAL; + } + return 0; +} + +static const struct snd_pcm_ops snd_hdspm_ops = { + .open = snd_hdspm_open, + .close = snd_hdspm_release, + .ioctl = snd_hdspm_ioctl, + .hw_params = snd_hdspm_hw_params, + .hw_free = snd_hdspm_hw_free, + .prepare = snd_hdspm_prepare, + .trigger = snd_hdspm_trigger, + .pointer = snd_hdspm_hw_pointer, +}; + +static int snd_hdspm_create_hwdep(struct snd_card *card, + struct hdspm *hdspm) +{ + struct snd_hwdep *hw; + int err; + + err = snd_hwdep_new(card, "HDSPM hwdep", 0, &hw); + if (err < 0) + return err; + + hdspm->hwdep = hw; + hw->private_data = hdspm; + strscpy(hw->name, "HDSPM hwdep interface"); + + hw->ops.open = snd_hdspm_hwdep_dummy_op; + hw->ops.ioctl = snd_hdspm_hwdep_ioctl; + hw->ops.ioctl_compat = snd_hdspm_hwdep_ioctl; + hw->ops.release = snd_hdspm_hwdep_dummy_op; + + return 0; +} + + +/*------------------------------------------------------------ + memory interface + ------------------------------------------------------------*/ +static int snd_hdspm_preallocate_memory(struct hdspm *hdspm) +{ + struct snd_pcm *pcm; + size_t wanted; + + pcm = hdspm->pcm; + + wanted = HDSPM_DMA_AREA_BYTES; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &hdspm->pci->dev, + wanted, wanted); + dev_dbg(hdspm->card->dev, " Preallocated %zd Bytes\n", wanted); + return 0; +} + +/* Inform the card what DMA addresses to use for the indicated channel. */ +/* Each channel got 16 4K pages allocated for DMA transfers. */ +static void hdspm_set_channel_dma_addr(struct hdspm *hdspm, + struct snd_pcm_substream *substream, + unsigned int reg, int channel) +{ + int i; + + for (i = channel * 16; i < channel * 16 + 16; i++) + hdspm_write(hdspm, reg + 4 * i, + snd_pcm_sgbuf_get_addr(substream, 4096 * i)); +} + + +/* ------------- ALSA Devices ---------------------------- */ +static int snd_hdspm_create_pcm(struct snd_card *card, + struct hdspm *hdspm) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(card, hdspm->card_name, 0, 1, 1, &pcm); + if (err < 0) + return err; + + hdspm->pcm = pcm; + pcm->private_data = hdspm; + strscpy(pcm->name, hdspm->card_name); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_hdspm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_hdspm_ops); + + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + + err = snd_hdspm_preallocate_memory(hdspm); + if (err < 0) + return err; + + return 0; +} + +static inline void snd_hdspm_initialize_midi_flush(struct hdspm * hdspm) +{ + int i; + + for (i = 0; i < hdspm->midiPorts; i++) + snd_hdspm_flush_midi_input(hdspm, i); +} + +static int snd_hdspm_create_alsa_devices(struct snd_card *card, + struct hdspm *hdspm) +{ + int err, i; + + dev_dbg(card->dev, "Create card...\n"); + err = snd_hdspm_create_pcm(card, hdspm); + if (err < 0) + return err; + + i = 0; + while (i < hdspm->midiPorts) { + err = snd_hdspm_create_midi(card, hdspm, i); + if (err < 0) { + return err; + } + i++; + } + + err = snd_hdspm_create_controls(card, hdspm); + if (err < 0) + return err; + + err = snd_hdspm_create_hwdep(card, hdspm); + if (err < 0) + return err; + + dev_dbg(card->dev, "proc init...\n"); + snd_hdspm_proc_init(hdspm); + + hdspm->system_sample_rate = -1; + hdspm->last_external_sample_rate = -1; + hdspm->last_internal_sample_rate = -1; + hdspm->playback_pid = -1; + hdspm->capture_pid = -1; + hdspm->capture_substream = NULL; + hdspm->playback_substream = NULL; + + dev_dbg(card->dev, "Set defaults...\n"); + err = snd_hdspm_set_defaults(hdspm); + if (err < 0) + return err; + + dev_dbg(card->dev, "Update mixer controls...\n"); + hdspm_update_simple_mixer_controls(hdspm); + + dev_dbg(card->dev, "Initializing complete?\n"); + + err = snd_card_register(card); + if (err < 0) { + dev_err(card->dev, "error registering card\n"); + return err; + } + + dev_dbg(card->dev, "... yes now\n"); + + return 0; +} + +static int snd_hdspm_create(struct snd_card *card, + struct hdspm *hdspm) +{ + + struct pci_dev *pci = hdspm->pci; + int err; + unsigned long io_extent; + + hdspm->irq = -1; + hdspm->card = card; + + spin_lock_init(&hdspm->lock); + INIT_WORK(&hdspm->midi_work, hdspm_midi_work); + + pci_read_config_word(hdspm->pci, + PCI_CLASS_REVISION, &hdspm->firmware_rev); + + strscpy(card->mixername, "Xilinx FPGA"); + strscpy(card->driver, "HDSPM"); + + switch (hdspm->firmware_rev) { + case HDSPM_RAYDAT_REV: + hdspm->io_type = RayDAT; + hdspm->card_name = "RME RayDAT"; + hdspm->midiPorts = 2; + break; + case HDSPM_AIO_REV: + hdspm->io_type = AIO; + hdspm->card_name = "RME AIO"; + hdspm->midiPorts = 1; + break; + case HDSPM_MADIFACE_REV: + hdspm->io_type = MADIface; + hdspm->card_name = "RME MADIface"; + hdspm->midiPorts = 1; + break; + default: + if ((hdspm->firmware_rev == 0xf0) || + ((hdspm->firmware_rev >= 0xe6) && + (hdspm->firmware_rev <= 0xea))) { + hdspm->io_type = AES32; + hdspm->card_name = "RME AES32"; + hdspm->midiPorts = 2; + } else if ((hdspm->firmware_rev == 0xd2) || + ((hdspm->firmware_rev >= 0xc8) && + (hdspm->firmware_rev <= 0xcf))) { + hdspm->io_type = MADI; + hdspm->card_name = "RME MADI"; + hdspm->midiPorts = 3; + } else { + dev_err(card->dev, + "unknown firmware revision %x\n", + hdspm->firmware_rev); + return -ENODEV; + } + } + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + pci_set_master(hdspm->pci); + + hdspm->iobase = pcim_iomap_region(pci, 0, "hdspm"); + if (IS_ERR(hdspm->iobase)) + return PTR_ERR(hdspm->iobase); + + hdspm->port = pci_resource_start(pci, 0); + io_extent = pci_resource_len(pci, 0); + dev_dbg(card->dev, "remapped region (0x%lx) 0x%lx-0x%lx\n", + (unsigned long)hdspm->iobase, hdspm->port, + hdspm->port + io_extent - 1); + + if (devm_request_irq(&pci->dev, pci->irq, snd_hdspm_interrupt, + IRQF_SHARED, KBUILD_MODNAME, hdspm)) { + dev_err(card->dev, "unable to use IRQ %d\n", pci->irq); + return -EBUSY; + } + + dev_dbg(card->dev, "use IRQ %d\n", pci->irq); + + hdspm->irq = pci->irq; + card->sync_irq = hdspm->irq; + + dev_dbg(card->dev, "kmalloc Mixer memory of %zd Bytes\n", + sizeof(*hdspm->mixer)); + hdspm->mixer = devm_kzalloc(&pci->dev, sizeof(*hdspm->mixer), GFP_KERNEL); + if (!hdspm->mixer) + return -ENOMEM; + + hdspm->port_names_in = NULL; + hdspm->port_names_out = NULL; + + switch (hdspm->io_type) { + case AES32: + hdspm->ss_in_channels = hdspm->ss_out_channels = AES32_CHANNELS; + hdspm->ds_in_channels = hdspm->ds_out_channels = AES32_CHANNELS; + hdspm->qs_in_channels = hdspm->qs_out_channels = AES32_CHANNELS; + + hdspm->channel_map_in_ss = hdspm->channel_map_out_ss = + channel_map_aes32; + hdspm->channel_map_in_ds = hdspm->channel_map_out_ds = + channel_map_aes32; + hdspm->channel_map_in_qs = hdspm->channel_map_out_qs = + channel_map_aes32; + hdspm->port_names_in_ss = hdspm->port_names_out_ss = + texts_ports_aes32; + hdspm->port_names_in_ds = hdspm->port_names_out_ds = + texts_ports_aes32; + hdspm->port_names_in_qs = hdspm->port_names_out_qs = + texts_ports_aes32; + + hdspm->max_channels_out = hdspm->max_channels_in = + AES32_CHANNELS; + hdspm->port_names_in = hdspm->port_names_out = + texts_ports_aes32; + hdspm->channel_map_in = hdspm->channel_map_out = + channel_map_aes32; + + break; + + case MADI: + case MADIface: + hdspm->ss_in_channels = hdspm->ss_out_channels = + MADI_SS_CHANNELS; + hdspm->ds_in_channels = hdspm->ds_out_channels = + MADI_DS_CHANNELS; + hdspm->qs_in_channels = hdspm->qs_out_channels = + MADI_QS_CHANNELS; + + hdspm->channel_map_in_ss = hdspm->channel_map_out_ss = + channel_map_unity_ss; + hdspm->channel_map_in_ds = hdspm->channel_map_out_ds = + channel_map_unity_ss; + hdspm->channel_map_in_qs = hdspm->channel_map_out_qs = + channel_map_unity_ss; + + hdspm->port_names_in_ss = hdspm->port_names_out_ss = + texts_ports_madi; + hdspm->port_names_in_ds = hdspm->port_names_out_ds = + texts_ports_madi; + hdspm->port_names_in_qs = hdspm->port_names_out_qs = + texts_ports_madi; + break; + + case AIO: + hdspm->ss_in_channels = AIO_IN_SS_CHANNELS; + hdspm->ds_in_channels = AIO_IN_DS_CHANNELS; + hdspm->qs_in_channels = AIO_IN_QS_CHANNELS; + hdspm->ss_out_channels = AIO_OUT_SS_CHANNELS; + hdspm->ds_out_channels = AIO_OUT_DS_CHANNELS; + hdspm->qs_out_channels = AIO_OUT_QS_CHANNELS; + + if (0 == (hdspm_read(hdspm, HDSPM_statusRegister2) & HDSPM_s2_AEBI_D)) { + dev_info(card->dev, "AEB input board found\n"); + hdspm->ss_in_channels += 4; + hdspm->ds_in_channels += 4; + hdspm->qs_in_channels += 4; + } + + if (0 == (hdspm_read(hdspm, HDSPM_statusRegister2) & HDSPM_s2_AEBO_D)) { + dev_info(card->dev, "AEB output board found\n"); + hdspm->ss_out_channels += 4; + hdspm->ds_out_channels += 4; + hdspm->qs_out_channels += 4; + } + + hdspm->channel_map_out_ss = channel_map_aio_out_ss; + hdspm->channel_map_out_ds = channel_map_aio_out_ds; + hdspm->channel_map_out_qs = channel_map_aio_out_qs; + + hdspm->channel_map_in_ss = channel_map_aio_in_ss; + hdspm->channel_map_in_ds = channel_map_aio_in_ds; + hdspm->channel_map_in_qs = channel_map_aio_in_qs; + + hdspm->port_names_in_ss = texts_ports_aio_in_ss; + hdspm->port_names_out_ss = texts_ports_aio_out_ss; + hdspm->port_names_in_ds = texts_ports_aio_in_ds; + hdspm->port_names_out_ds = texts_ports_aio_out_ds; + hdspm->port_names_in_qs = texts_ports_aio_in_qs; + hdspm->port_names_out_qs = texts_ports_aio_out_qs; + + break; + + case RayDAT: + hdspm->ss_in_channels = hdspm->ss_out_channels = + RAYDAT_SS_CHANNELS; + hdspm->ds_in_channels = hdspm->ds_out_channels = + RAYDAT_DS_CHANNELS; + hdspm->qs_in_channels = hdspm->qs_out_channels = + RAYDAT_QS_CHANNELS; + + hdspm->max_channels_in = RAYDAT_SS_CHANNELS; + hdspm->max_channels_out = RAYDAT_SS_CHANNELS; + + hdspm->channel_map_in_ss = hdspm->channel_map_out_ss = + channel_map_raydat_ss; + hdspm->channel_map_in_ds = hdspm->channel_map_out_ds = + channel_map_raydat_ds; + hdspm->channel_map_in_qs = hdspm->channel_map_out_qs = + channel_map_raydat_qs; + hdspm->channel_map_in = hdspm->channel_map_out = + channel_map_raydat_ss; + + hdspm->port_names_in_ss = hdspm->port_names_out_ss = + texts_ports_raydat_ss; + hdspm->port_names_in_ds = hdspm->port_names_out_ds = + texts_ports_raydat_ds; + hdspm->port_names_in_qs = hdspm->port_names_out_qs = + texts_ports_raydat_qs; + + + break; + + } + + /* TCO detection */ + switch (hdspm->io_type) { + case AIO: + case RayDAT: + if (hdspm_read(hdspm, HDSPM_statusRegister2) & + HDSPM_s2_tco_detect) { + hdspm->midiPorts++; + hdspm->tco = kzalloc_obj(*hdspm->tco); + if (hdspm->tco) + hdspm_tco_write(hdspm); + + dev_info(card->dev, "AIO/RayDAT TCO module found\n"); + } else { + hdspm->tco = NULL; + } + break; + + case MADI: + case AES32: + if (hdspm_read(hdspm, HDSPM_statusRegister) & HDSPM_tco_detect) { + hdspm->midiPorts++; + hdspm->tco = kzalloc_obj(*hdspm->tco); + if (hdspm->tco) + hdspm_tco_write(hdspm); + + dev_info(card->dev, "MADI/AES TCO module found\n"); + } else { + hdspm->tco = NULL; + } + break; + + default: + hdspm->tco = NULL; + } + + /* texts */ + switch (hdspm->io_type) { + case AES32: + if (hdspm->tco) { + hdspm->texts_autosync = texts_autosync_aes_tco; + hdspm->texts_autosync_items = + ARRAY_SIZE(texts_autosync_aes_tco); + } else { + hdspm->texts_autosync = texts_autosync_aes; + hdspm->texts_autosync_items = + ARRAY_SIZE(texts_autosync_aes); + } + break; + + case MADI: + if (hdspm->tco) { + hdspm->texts_autosync = texts_autosync_madi_tco; + hdspm->texts_autosync_items = 4; + } else { + hdspm->texts_autosync = texts_autosync_madi; + hdspm->texts_autosync_items = 3; + } + break; + + case MADIface: + + break; + + case RayDAT: + if (hdspm->tco) { + hdspm->texts_autosync = texts_autosync_raydat_tco; + hdspm->texts_autosync_items = 9; + } else { + hdspm->texts_autosync = texts_autosync_raydat; + hdspm->texts_autosync_items = 8; + } + break; + + case AIO: + if (hdspm->tco) { + hdspm->texts_autosync = texts_autosync_aio_tco; + hdspm->texts_autosync_items = 6; + } else { + hdspm->texts_autosync = texts_autosync_aio; + hdspm->texts_autosync_items = 5; + } + break; + + } + + if (hdspm->io_type != MADIface) { + hdspm->serial = (hdspm_read(hdspm, + HDSPM_midiStatusIn0)>>8) & 0xFFFFFF; + /* id contains either a user-provided value or the default + * NULL. If it's the default, we're safe to + * fill card->id with the serial number. + * + * If the serial number is 0xFFFFFF, then we're dealing with + * an old PCI revision that comes without a sane number. In + * this case, we don't set card->id to avoid collisions + * when running with multiple cards. + */ + if (!id[hdspm->dev] && hdspm->serial != 0xFFFFFF) { + snprintf(card->id, sizeof(card->id), + "HDSPMx%06x", hdspm->serial); + snd_card_set_id(card, card->id); + } + } + + dev_dbg(card->dev, "create alsa devices.\n"); + err = snd_hdspm_create_alsa_devices(card, hdspm); + if (err < 0) + return err; + + snd_hdspm_initialize_midi_flush(hdspm); + + return 0; +} + + +static void snd_hdspm_card_free(struct snd_card *card) +{ + struct hdspm *hdspm = card->private_data; + + if (hdspm->port) { + cancel_work_sync(&hdspm->midi_work); + + /* stop th audio, and cancel all interrupts */ + hdspm->control_register &= + ~(HDSPM_Start | HDSPM_AudioInterruptEnable | + HDSPM_Midi0InterruptEnable | HDSPM_Midi1InterruptEnable | + HDSPM_Midi2InterruptEnable | HDSPM_Midi3InterruptEnable); + hdspm_write(hdspm, HDSPM_controlRegister, + hdspm->control_register); + } +} + + +static int snd_hdspm_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct hdspm *hdspm; + struct snd_card *card; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], + THIS_MODULE, sizeof(*hdspm), &card); + if (err < 0) + return err; + + hdspm = card->private_data; + card->private_free = snd_hdspm_card_free; + hdspm->dev = dev; + hdspm->pci = pci; + + err = snd_hdspm_create(card, hdspm); + if (err < 0) + goto error; + + if (hdspm->io_type != MADIface) { + snprintf(card->shortname, sizeof(card->shortname), "%s_%x", + hdspm->card_name, hdspm->serial); + snprintf(card->longname, sizeof(card->longname), + "%s S/N 0x%x at 0x%lx, irq %d", + hdspm->card_name, hdspm->serial, + hdspm->port, hdspm->irq); + } else { + snprintf(card->shortname, sizeof(card->shortname), "%s", + hdspm->card_name); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %d", + hdspm->card_name, hdspm->port, hdspm->irq); + } + + err = snd_card_register(card); + if (err < 0) + goto error; + + pci_set_drvdata(pci, card); + + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +static struct pci_driver hdspm_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_hdspm_ids, + .probe = snd_hdspm_probe, +}; + +module_pci_driver(hdspm_driver); diff --git a/sound/pci/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c new file mode 100644 index 000000000..3be30cb57 --- /dev/null +++ b/sound/pci/rme9652/rme9652.c @@ -0,0 +1,2521 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for RME Digi9652 audio interfaces + * + * Copyright (c) 1999 IEM - Winfried Ritsch + * Copyright (c) 1999-2001 Paul Davis + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +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_PNP; /* Enable this card */ +static bool precise_ptr[SNDRV_CARDS]; /* Enable precise pointer */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for RME Digi9652 (Hammerfall) soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for RME Digi9652 (Hammerfall) soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable/disable specific RME96{52,36} soundcards."); +module_param_array(precise_ptr, bool, NULL, 0444); +MODULE_PARM_DESC(precise_ptr, "Enable precise pointer (doesn't work reliably)."); +MODULE_AUTHOR("Paul Davis , Winfried Ritsch"); +MODULE_DESCRIPTION("RME Digi9652/Digi9636"); +MODULE_LICENSE("GPL"); + +/* The Hammerfall has two sets of 24 ADAT + 2 S/PDIF channels, one for + capture, one for playback. Both the ADAT and S/PDIF channels appear + to the host CPU in the same block of memory. There is no functional + difference between them in terms of access. + + The Hammerfall Light is identical to the Hammerfall, except that it + has 2 sets 18 channels (16 ADAT + 2 S/PDIF) for capture and playback. +*/ + +#define RME9652_NCHANNELS 26 +#define RME9636_NCHANNELS 18 + +/* Preferred sync source choices - used by "sync_pref" control switch */ + +#define RME9652_SYNC_FROM_SPDIF 0 +#define RME9652_SYNC_FROM_ADAT1 1 +#define RME9652_SYNC_FROM_ADAT2 2 +#define RME9652_SYNC_FROM_ADAT3 3 + +/* Possible sources of S/PDIF input */ + +#define RME9652_SPDIFIN_OPTICAL 0 /* optical (ADAT1) */ +#define RME9652_SPDIFIN_COAXIAL 1 /* coaxial (RCA) */ +#define RME9652_SPDIFIN_INTERN 2 /* internal (CDROM) */ + +/* ------------- Status-Register bits --------------------- */ + +#define RME9652_IRQ (1<<0) /* IRQ is High if not reset by irq_clear */ +#define RME9652_lock_2 (1<<1) /* ADAT 3-PLL: 1=locked, 0=unlocked */ +#define RME9652_lock_1 (1<<2) /* ADAT 2-PLL: 1=locked, 0=unlocked */ +#define RME9652_lock_0 (1<<3) /* ADAT 1-PLL: 1=locked, 0=unlocked */ +#define RME9652_fs48 (1<<4) /* sample rate is 0=44.1/88.2,1=48/96 Khz */ +#define RME9652_wsel_rd (1<<5) /* if Word-Clock is used and valid then 1 */ + /* bits 6-15 encode h/w buffer pointer position */ +#define RME9652_sync_2 (1<<16) /* if ADAT-IN 3 in sync to system clock */ +#define RME9652_sync_1 (1<<17) /* if ADAT-IN 2 in sync to system clock */ +#define RME9652_sync_0 (1<<18) /* if ADAT-IN 1 in sync to system clock */ +#define RME9652_DS_rd (1<<19) /* 1=Double Speed Mode, 0=Normal Speed */ +#define RME9652_tc_busy (1<<20) /* 1=time-code copy in progress (960ms) */ +#define RME9652_tc_out (1<<21) /* time-code out bit */ +#define RME9652_F_0 (1<<22) /* 000=64kHz, 100=88.2kHz, 011=96kHz */ +#define RME9652_F_1 (1<<23) /* 111=32kHz, 110=44.1kHz, 101=48kHz, */ +#define RME9652_F_2 (1<<24) /* external Crystal Chip if ERF=1 */ +#define RME9652_ERF (1<<25) /* Error-Flag of SDPIF Receiver (1=No Lock) */ +#define RME9652_buffer_id (1<<26) /* toggles by each interrupt on rec/play */ +#define RME9652_tc_valid (1<<27) /* 1 = a signal is detected on time-code input */ +#define RME9652_SPDIF_READ (1<<28) /* byte available from Rev 1.5+ S/PDIF interface */ + +#define RME9652_sync (RME9652_sync_0|RME9652_sync_1|RME9652_sync_2) +#define RME9652_lock (RME9652_lock_0|RME9652_lock_1|RME9652_lock_2) +#define RME9652_F (RME9652_F_0|RME9652_F_1|RME9652_F_2) +#define rme9652_decode_spdif_rate(x) ((x)>>22) + +/* Bit 6..15 : h/w buffer pointer */ + +#define RME9652_buf_pos 0x000FFC0 + +/* Bits 31,30,29 are bits 5,4,3 of h/w pointer position on later + Rev G EEPROMS and Rev 1.5 cards or later. +*/ + +#define RME9652_REV15_buf_pos(x) ((((x)&0xE0000000)>>26)|((x)&RME9652_buf_pos)) + +/* amount of io space we remap for register access. i'm not sure we + even need this much, but 1K is nice round number :) +*/ + +#define RME9652_IO_EXTENT 1024 + +#define RME9652_init_buffer 0 +#define RME9652_play_buffer 32 /* holds ptr to 26x64kBit host RAM */ +#define RME9652_rec_buffer 36 /* holds ptr to 26x64kBit host RAM */ +#define RME9652_control_register 64 +#define RME9652_irq_clear 96 +#define RME9652_time_code 100 /* useful if used with alesis adat */ +#define RME9652_thru_base 128 /* 132...228 Thru for 26 channels */ + +/* Read-only registers */ + +/* Writing to any of the register locations writes to the status + register. We'll use the first location as our point of access. +*/ + +#define RME9652_status_register 0 + +/* --------- Control-Register Bits ---------------- */ + + +#define RME9652_start_bit (1<<0) /* start record/play */ + /* bits 1-3 encode buffersize/latency */ +#define RME9652_Master (1<<4) /* Clock Mode Master=1,Slave/Auto=0 */ +#define RME9652_IE (1<<5) /* Interrupt Enable */ +#define RME9652_freq (1<<6) /* samplerate 0=44.1/88.2, 1=48/96 kHz */ +#define RME9652_freq1 (1<<7) /* if 0, 32kHz, else always 1 */ +#define RME9652_DS (1<<8) /* Doule Speed 0=44.1/48, 1=88.2/96 Khz */ +#define RME9652_PRO (1<<9) /* S/PDIF out: 0=consumer, 1=professional */ +#define RME9652_EMP (1<<10) /* Emphasis 0=None, 1=ON */ +#define RME9652_Dolby (1<<11) /* Non-audio bit 1=set, 0=unset */ +#define RME9652_opt_out (1<<12) /* Use 1st optical OUT as SPDIF: 1=yes,0=no */ +#define RME9652_wsel (1<<13) /* use Wordclock as sync (overwrites master) */ +#define RME9652_inp_0 (1<<14) /* SPDIF-IN: 00=optical (ADAT1), */ +#define RME9652_inp_1 (1<<15) /* 01=koaxial (Cinch), 10=Internal CDROM */ +#define RME9652_SyncPref_ADAT2 (1<<16) +#define RME9652_SyncPref_ADAT3 (1<<17) +#define RME9652_SPDIF_RESET (1<<18) /* Rev 1.5+: h/w S/PDIF receiver */ +#define RME9652_SPDIF_SELECT (1<<19) +#define RME9652_SPDIF_CLOCK (1<<20) +#define RME9652_SPDIF_WRITE (1<<21) +#define RME9652_ADAT1_INTERNAL (1<<22) /* Rev 1.5+: if set, internal CD connector carries ADAT */ + +/* buffersize = 512Bytes * 2^n, where n is made from Bit2 ... Bit0 */ + +#define RME9652_latency 0x0e +#define rme9652_encode_latency(x) (((x)&0x7)<<1) +#define rme9652_decode_latency(x) (((x)>>1)&0x7) +#define rme9652_running_double_speed(s) ((s)->control_register & RME9652_DS) +#define RME9652_inp (RME9652_inp_0|RME9652_inp_1) +#define rme9652_encode_spdif_in(x) (((x)&0x3)<<14) +#define rme9652_decode_spdif_in(x) (((x)>>14)&0x3) + +#define RME9652_SyncPref_Mask (RME9652_SyncPref_ADAT2|RME9652_SyncPref_ADAT3) +#define RME9652_SyncPref_ADAT1 0 +#define RME9652_SyncPref_SPDIF (RME9652_SyncPref_ADAT2|RME9652_SyncPref_ADAT3) + +/* the size of a substream (1 mono data stream) */ + +#define RME9652_CHANNEL_BUFFER_SAMPLES (16*1024) +#define RME9652_CHANNEL_BUFFER_BYTES (4*RME9652_CHANNEL_BUFFER_SAMPLES) + +/* the size of the area we need to allocate for DMA transfers. the + size is the same regardless of the number of channels - the + 9636 still uses the same memory area. + + Note that we allocate 1 more channel than is apparently needed + because the h/w seems to write 1 byte beyond the end of the last + page. Sigh. +*/ + +#define RME9652_DMA_AREA_BYTES ((RME9652_NCHANNELS+1) * RME9652_CHANNEL_BUFFER_BYTES) +#define RME9652_DMA_AREA_KILOBYTES (RME9652_DMA_AREA_BYTES/1024) + +struct snd_rme9652 { + int dev; + + spinlock_t lock; + int irq; + unsigned long port; + void __iomem *iobase; + + int precise_ptr; + + u32 control_register; /* cached value */ + u32 thru_bits; /* thru 1=on, 0=off channel 1=Bit1... channel 26= Bit26 */ + + u32 creg_spdif; + u32 creg_spdif_stream; + + char *card_name; /* hammerfall or hammerfall light names */ + + size_t hw_offsetmask; /* &-with status register to get real hw_offset */ + size_t prev_hw_offset; /* previous hw offset */ + size_t max_jitter; /* maximum jitter in frames for + hw pointer */ + size_t period_bytes; /* guess what this is */ + + unsigned char ds_channels; + unsigned char ss_channels; /* different for hammerfall/hammerfall-light */ + + /* DMA buffers; those are copied instances from the original snd_dma_buf + * objects (which are managed via devres) for the address alignments + */ + struct snd_dma_buffer playback_dma_buf; + struct snd_dma_buffer capture_dma_buf; + + unsigned char *capture_buffer; /* suitably aligned address */ + unsigned char *playback_buffer; /* suitably aligned address */ + + pid_t capture_pid; + pid_t playback_pid; + + struct snd_pcm_substream *capture_substream; + struct snd_pcm_substream *playback_substream; + int running; + + int passthru; /* non-zero if doing pass-thru */ + int hw_rev; /* h/w rev * 10 (i.e. 1.5 has hw_rev = 15) */ + + int last_spdif_sample_rate; /* so that we can catch externally ... */ + int last_adat_sample_rate; /* ... induced rate changes */ + + const signed char *channel_map; + + struct snd_card *card; + struct snd_pcm *pcm; + struct pci_dev *pci; + struct snd_kcontrol *spdif_ctl; + +}; + +/* These tables map the ALSA channels 1..N to the channels that we + need to use in order to find the relevant channel buffer. RME + refer to this kind of mapping as between "the ADAT channel and + the DMA channel." We index it using the logical audio channel, + and the value is the DMA channel (i.e. channel buffer number) + where the data for that channel can be read/written from/to. +*/ + +static const signed char channel_map_9652_ss[26] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25 +}; + +static const signed char channel_map_9636_ss[26] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + /* channels 16 and 17 are S/PDIF */ + 24, 25, + /* channels 18-25 don't exist */ + -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const signed char channel_map_9652_ds[26] = { + /* ADAT channels are remapped */ + 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, + /* channels 12 and 13 are S/PDIF */ + 24, 25, + /* others don't exist */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const signed char channel_map_9636_ds[26] = { + /* ADAT channels are remapped */ + 1, 3, 5, 7, 9, 11, 13, 15, + /* channels 8 and 9 are S/PDIF */ + 24, 25, + /* others don't exist */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static struct snd_dma_buffer * +snd_hammerfall_get_buffer(struct pci_dev *pci, size_t size) +{ + return snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, size); +} + +static const struct pci_device_id snd_rme9652_ids[] = { + { + .vendor = 0x10ee, + .device = 0x3fc4, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, /* RME Digi9652 */ + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, snd_rme9652_ids); + +static inline void rme9652_write(struct snd_rme9652 *rme9652, int reg, int val) +{ + writel(val, rme9652->iobase + reg); +} + +static inline unsigned int rme9652_read(struct snd_rme9652 *rme9652, int reg) +{ + return readl(rme9652->iobase + reg); +} + +static inline int snd_rme9652_use_is_exclusive(struct snd_rme9652 *rme9652) +{ + guard(spinlock_irqsave)(&rme9652->lock); + if ((rme9652->playback_pid != rme9652->capture_pid) && + (rme9652->playback_pid >= 0) && (rme9652->capture_pid >= 0)) + return 0; + return 1; +} + +static inline int rme9652_adat_sample_rate(struct snd_rme9652 *rme9652) +{ + if (rme9652_running_double_speed(rme9652)) { + return (rme9652_read(rme9652, RME9652_status_register) & + RME9652_fs48) ? 96000 : 88200; + } else { + return (rme9652_read(rme9652, RME9652_status_register) & + RME9652_fs48) ? 48000 : 44100; + } +} + +static inline void rme9652_compute_period_size(struct snd_rme9652 *rme9652) +{ + unsigned int i; + + i = rme9652->control_register & RME9652_latency; + rme9652->period_bytes = 1 << ((rme9652_decode_latency(i) + 8)); + rme9652->hw_offsetmask = + (rme9652->period_bytes * 2 - 1) & RME9652_buf_pos; + rme9652->max_jitter = 80; +} + +static snd_pcm_uframes_t rme9652_hw_pointer(struct snd_rme9652 *rme9652) +{ + int status; + unsigned int offset, frag; + snd_pcm_uframes_t period_size = rme9652->period_bytes / 4; + snd_pcm_sframes_t delta; + + status = rme9652_read(rme9652, RME9652_status_register); + if (!rme9652->precise_ptr) + return (status & RME9652_buffer_id) ? period_size : 0; + offset = status & RME9652_buf_pos; + + /* The hardware may give a backward movement for up to 80 frames + Martin Kirst knows the details. + */ + + delta = rme9652->prev_hw_offset - offset; + delta &= 0xffff; + if (delta <= (snd_pcm_sframes_t)rme9652->max_jitter * 4) + offset = rme9652->prev_hw_offset; + else + rme9652->prev_hw_offset = offset; + offset &= rme9652->hw_offsetmask; + offset /= 4; + frag = status & RME9652_buffer_id; + + if (offset < period_size) { + if (offset > rme9652->max_jitter) { + if (frag) + dev_err(rme9652->card->dev, + "Unexpected hw_pointer position (bufid == 0): status: %x offset: %d\n", + status, offset); + } else if (!frag) + return 0; + offset -= rme9652->max_jitter; + if ((int)offset < 0) + offset += period_size * 2; + } else { + if (offset > period_size + rme9652->max_jitter) { + if (!frag) + dev_err(rme9652->card->dev, + "Unexpected hw_pointer position (bufid == 1): status: %x offset: %d\n", + status, offset); + } else if (frag) + return period_size; + offset -= rme9652->max_jitter; + } + + return offset; +} + +static inline void rme9652_reset_hw_pointer(struct snd_rme9652 *rme9652) +{ + int i; + + /* reset the FIFO pointer to zero. We do this by writing to 8 + registers, each of which is a 32bit wide register, and set + them all to zero. Note that s->iobase is a pointer to + int32, not pointer to char. + */ + + for (i = 0; i < 8; i++) { + rme9652_write(rme9652, i * 4, 0); + udelay(10); + } + rme9652->prev_hw_offset = 0; +} + +static inline void rme9652_start(struct snd_rme9652 *s) +{ + s->control_register |= (RME9652_IE | RME9652_start_bit); + rme9652_write(s, RME9652_control_register, s->control_register); +} + +static inline void rme9652_stop(struct snd_rme9652 *s) +{ + s->control_register &= ~(RME9652_start_bit | RME9652_IE); + rme9652_write(s, RME9652_control_register, s->control_register); +} + +static int rme9652_set_interrupt_interval(struct snd_rme9652 *s, + unsigned int frames) +{ + int restart = 0; + int n; + + guard(spinlock_irq)(&s->lock); + + restart = s->running; + if (restart) + rme9652_stop(s); + + frames >>= 7; + n = 0; + while (frames) { + n++; + frames >>= 1; + } + + s->control_register &= ~RME9652_latency; + s->control_register |= rme9652_encode_latency(n); + + rme9652_write(s, RME9652_control_register, s->control_register); + + rme9652_compute_period_size(s); + + if (restart) + rme9652_start(s); + + return 0; +} + +static int rme9652_set_rate(struct snd_rme9652 *rme9652, int rate) +{ + int restart; + int reject_if_open = 0; + int xrate; + + if (!snd_rme9652_use_is_exclusive (rme9652)) { + return -EBUSY; + } + + /* Changing from a "single speed" to a "double speed" rate is + not allowed if any substreams are open. This is because + such a change causes a shift in the location of + the DMA buffers and a reduction in the number of available + buffers. + + Note that a similar but essentially insoluble problem + exists for externally-driven rate changes. All we can do + is to flag rate changes in the read/write routines. + */ + + guard(spinlock_irq)(&rme9652->lock); + xrate = rme9652_adat_sample_rate(rme9652); + + switch (rate) { + case 44100: + if (xrate > 48000) { + reject_if_open = 1; + } + rate = 0; + break; + case 48000: + if (xrate > 48000) { + reject_if_open = 1; + } + rate = RME9652_freq; + break; + case 88200: + if (xrate < 48000) { + reject_if_open = 1; + } + rate = RME9652_DS; + break; + case 96000: + if (xrate < 48000) { + reject_if_open = 1; + } + rate = RME9652_DS | RME9652_freq; + break; + default: + return -EINVAL; + } + + if (reject_if_open && (rme9652->capture_pid >= 0 || rme9652->playback_pid >= 0)) + return -EBUSY; + + restart = rme9652->running; + if (restart) + rme9652_stop(rme9652); + rme9652->control_register &= ~(RME9652_freq | RME9652_DS); + rme9652->control_register |= rate; + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register); + + if (restart) + rme9652_start(rme9652); + + if (rate & RME9652_DS) { + if (rme9652->ss_channels == RME9652_NCHANNELS) { + rme9652->channel_map = channel_map_9652_ds; + } else { + rme9652->channel_map = channel_map_9636_ds; + } + } else { + if (rme9652->ss_channels == RME9652_NCHANNELS) { + rme9652->channel_map = channel_map_9652_ss; + } else { + rme9652->channel_map = channel_map_9636_ss; + } + } + + return 0; +} + +static void rme9652_set_thru(struct snd_rme9652 *rme9652, int channel, int enable) +{ + int i; + + rme9652->passthru = 0; + + if (channel < 0) { + + /* set thru for all channels */ + + if (enable) { + for (i = 0; i < RME9652_NCHANNELS; i++) { + rme9652->thru_bits |= (1 << i); + rme9652_write(rme9652, RME9652_thru_base + i * 4, 1); + } + } else { + for (i = 0; i < RME9652_NCHANNELS; i++) { + rme9652->thru_bits &= ~(1 << i); + rme9652_write(rme9652, RME9652_thru_base + i * 4, 0); + } + } + + } else { + int mapped_channel; + + mapped_channel = rme9652->channel_map[channel]; + + if (enable) { + rme9652->thru_bits |= (1 << mapped_channel); + } else { + rme9652->thru_bits &= ~(1 << mapped_channel); + } + + rme9652_write(rme9652, + RME9652_thru_base + mapped_channel * 4, + enable ? 1 : 0); + } +} + +static int rme9652_set_passthru(struct snd_rme9652 *rme9652, int onoff) +{ + if (onoff) { + rme9652_set_thru(rme9652, -1, 1); + + /* we don't want interrupts, so do a + custom version of rme9652_start(). + */ + + rme9652->control_register = + RME9652_inp_0 | + rme9652_encode_latency(7) | + RME9652_start_bit; + + rme9652_reset_hw_pointer(rme9652); + + rme9652_write(rme9652, RME9652_control_register, + rme9652->control_register); + rme9652->passthru = 1; + } else { + rme9652_set_thru(rme9652, -1, 0); + rme9652_stop(rme9652); + rme9652->passthru = 0; + } + + return 0; +} + +static void rme9652_spdif_set_bit (struct snd_rme9652 *rme9652, int mask, int onoff) +{ + if (onoff) + rme9652->control_register |= mask; + else + rme9652->control_register &= ~mask; + + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register); +} + +static void rme9652_spdif_write_byte (struct snd_rme9652 *rme9652, const int val) +{ + long mask; + long i; + + for (i = 0, mask = 0x80; i < 8; i++, mask >>= 1) { + if (val & mask) + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_WRITE, 1); + else + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_WRITE, 0); + + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_CLOCK, 1); + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_CLOCK, 0); + } +} + +static int rme9652_spdif_read_byte (struct snd_rme9652 *rme9652) +{ + long mask; + long val; + long i; + + val = 0; + + for (i = 0, mask = 0x80; i < 8; i++, mask >>= 1) { + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_CLOCK, 1); + if (rme9652_read (rme9652, RME9652_status_register) & RME9652_SPDIF_READ) + val |= mask; + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_CLOCK, 0); + } + + return val; +} + +static void rme9652_write_spdif_codec (struct snd_rme9652 *rme9652, const int address, const int data) +{ + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 1); + rme9652_spdif_write_byte (rme9652, 0x20); + rme9652_spdif_write_byte (rme9652, address); + rme9652_spdif_write_byte (rme9652, data); + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 0); +} + + +static int rme9652_spdif_read_codec (struct snd_rme9652 *rme9652, const int address) +{ + int ret; + + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 1); + rme9652_spdif_write_byte (rme9652, 0x20); + rme9652_spdif_write_byte (rme9652, address); + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 0); + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 1); + + rme9652_spdif_write_byte (rme9652, 0x21); + ret = rme9652_spdif_read_byte (rme9652); + rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 0); + + return ret; +} + +static void rme9652_initialize_spdif_receiver (struct snd_rme9652 *rme9652) +{ + /* XXX what unsets this ? */ + + rme9652->control_register |= RME9652_SPDIF_RESET; + + rme9652_write_spdif_codec (rme9652, 4, 0x40); + rme9652_write_spdif_codec (rme9652, 17, 0x13); + rme9652_write_spdif_codec (rme9652, 6, 0x02); +} + +static inline int rme9652_spdif_sample_rate(struct snd_rme9652 *s) +{ + unsigned int rate_bits; + + if (rme9652_read(s, RME9652_status_register) & RME9652_ERF) { + return -1; /* error condition */ + } + + if (s->hw_rev == 15) { + + int x, y, ret; + + x = rme9652_spdif_read_codec (s, 30); + + if (x != 0) + y = 48000 * 64 / x; + else + y = 0; + + if (y > 30400 && y < 33600) ret = 32000; + else if (y > 41900 && y < 46000) ret = 44100; + else if (y > 46000 && y < 50400) ret = 48000; + else if (y > 60800 && y < 67200) ret = 64000; + else if (y > 83700 && y < 92000) ret = 88200; + else if (y > 92000 && y < 100000) ret = 96000; + else ret = 0; + return ret; + } + + rate_bits = rme9652_read(s, RME9652_status_register) & RME9652_F; + + switch (rme9652_decode_spdif_rate(rate_bits)) { + case 0x7: + return 32000; + + case 0x6: + return 44100; + + case 0x5: + return 48000; + + case 0x4: + return 88200; + + case 0x3: + return 96000; + + case 0x0: + return 64000; + + default: + dev_err(s->card->dev, + "%s: unknown S/PDIF input rate (bits = 0x%x)\n", + s->card_name, rate_bits); + return 0; + } +} + +/*----------------------------------------------------------------------------- + Control Interface + ----------------------------------------------------------------------------*/ + +static u32 snd_rme9652_convert_from_aes(struct snd_aes_iec958 *aes) +{ + u32 val = 0; + val |= (aes->status[0] & IEC958_AES0_PROFESSIONAL) ? RME9652_PRO : 0; + val |= (aes->status[0] & IEC958_AES0_NONAUDIO) ? RME9652_Dolby : 0; + if (val & RME9652_PRO) + val |= (aes->status[0] & IEC958_AES0_PRO_EMPHASIS_5015) ? RME9652_EMP : 0; + else + val |= (aes->status[0] & IEC958_AES0_CON_EMPHASIS_5015) ? RME9652_EMP : 0; + return val; +} + +static void snd_rme9652_convert_to_aes(struct snd_aes_iec958 *aes, u32 val) +{ + aes->status[0] = ((val & RME9652_PRO) ? IEC958_AES0_PROFESSIONAL : 0) | + ((val & RME9652_Dolby) ? IEC958_AES0_NONAUDIO : 0); + if (val & RME9652_PRO) + aes->status[0] |= (val & RME9652_EMP) ? IEC958_AES0_PRO_EMPHASIS_5015 : 0; + else + aes->status[0] |= (val & RME9652_EMP) ? IEC958_AES0_CON_EMPHASIS_5015 : 0; +} + +static int snd_rme9652_control_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_rme9652_control_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + snd_rme9652_convert_to_aes(&ucontrol->value.iec958, rme9652->creg_spdif); + return 0; +} + +static int snd_rme9652_control_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + int change; + u32 val; + + val = snd_rme9652_convert_from_aes(&ucontrol->value.iec958); + guard(spinlock_irq)(&rme9652->lock); + change = val != rme9652->creg_spdif; + rme9652->creg_spdif = val; + return change; +} + +static int snd_rme9652_control_spdif_stream_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_rme9652_control_spdif_stream_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + snd_rme9652_convert_to_aes(&ucontrol->value.iec958, rme9652->creg_spdif_stream); + return 0; +} + +static int snd_rme9652_control_spdif_stream_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + int change; + u32 val; + + val = snd_rme9652_convert_from_aes(&ucontrol->value.iec958); + guard(spinlock_irq)(&rme9652->lock); + change = val != rme9652->creg_spdif_stream; + rme9652->creg_spdif_stream = val; + rme9652->control_register &= ~(RME9652_PRO | RME9652_Dolby | RME9652_EMP); + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register |= val); + return change; +} + +static int snd_rme9652_control_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_rme9652_control_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = kcontrol->private_value; + return 0; +} + +#define RME9652_ADAT1_IN(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_rme9652_info_adat1_in, \ + .get = snd_rme9652_get_adat1_in, \ + .put = snd_rme9652_put_adat1_in } + +static unsigned int rme9652_adat1_in(struct snd_rme9652 *rme9652) +{ + if (rme9652->control_register & RME9652_ADAT1_INTERNAL) + return 1; + return 0; +} + +static int rme9652_set_adat1_input(struct snd_rme9652 *rme9652, int internal) +{ + int restart = 0; + + if (internal) { + rme9652->control_register |= RME9652_ADAT1_INTERNAL; + } else { + rme9652->control_register &= ~RME9652_ADAT1_INTERNAL; + } + + /* XXX do we actually need to stop the card when we do this ? */ + + restart = rme9652->running; + if (restart) + rme9652_stop(rme9652); + + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register); + + if (restart) + rme9652_start(rme9652); + + return 0; +} + +static int snd_rme9652_info_adat1_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = {"ADAT1", "Internal"}; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_rme9652_get_adat1_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme9652->lock); + ucontrol->value.enumerated.item[0] = rme9652_adat1_in(rme9652); + return 0; +} + +static int snd_rme9652_put_adat1_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_rme9652_use_is_exclusive(rme9652)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0] % 2; + guard(spinlock_irq)(&rme9652->lock); + change = val != rme9652_adat1_in(rme9652); + if (change) + rme9652_set_adat1_input(rme9652, val); + return change; +} + +#define RME9652_SPDIF_IN(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_rme9652_info_spdif_in, \ + .get = snd_rme9652_get_spdif_in, .put = snd_rme9652_put_spdif_in } + +static unsigned int rme9652_spdif_in(struct snd_rme9652 *rme9652) +{ + return rme9652_decode_spdif_in(rme9652->control_register & + RME9652_inp); +} + +static int rme9652_set_spdif_input(struct snd_rme9652 *rme9652, int in) +{ + int restart = 0; + + rme9652->control_register &= ~RME9652_inp; + rme9652->control_register |= rme9652_encode_spdif_in(in); + + restart = rme9652->running; + if (restart) + rme9652_stop(rme9652); + + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register); + + if (restart) + rme9652_start(rme9652); + + return 0; +} + +static int snd_rme9652_info_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = {"ADAT1", "Coaxial", "Internal"}; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_rme9652_get_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme9652->lock); + ucontrol->value.enumerated.item[0] = rme9652_spdif_in(rme9652); + return 0; +} + +static int snd_rme9652_put_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_rme9652_use_is_exclusive(rme9652)) + return -EBUSY; + val = ucontrol->value.enumerated.item[0] % 3; + guard(spinlock_irq)(&rme9652->lock); + change = val != rme9652_spdif_in(rme9652); + if (change) + rme9652_set_spdif_input(rme9652, val); + return change; +} + +#define RME9652_SPDIF_OUT(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_rme9652_info_spdif_out, \ + .get = snd_rme9652_get_spdif_out, .put = snd_rme9652_put_spdif_out } + +static int rme9652_spdif_out(struct snd_rme9652 *rme9652) +{ + return (rme9652->control_register & RME9652_opt_out) ? 1 : 0; +} + +static int rme9652_set_spdif_output(struct snd_rme9652 *rme9652, int out) +{ + int restart = 0; + + if (out) { + rme9652->control_register |= RME9652_opt_out; + } else { + rme9652->control_register &= ~RME9652_opt_out; + } + + restart = rme9652->running; + if (restart) + rme9652_stop(rme9652); + + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register); + + if (restart) + rme9652_start(rme9652); + + return 0; +} + +#define snd_rme9652_info_spdif_out snd_ctl_boolean_mono_info + +static int snd_rme9652_get_spdif_out(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme9652->lock); + ucontrol->value.integer.value[0] = rme9652_spdif_out(rme9652); + return 0; +} + +static int snd_rme9652_put_spdif_out(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + if (!snd_rme9652_use_is_exclusive(rme9652)) + return -EBUSY; + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&rme9652->lock); + change = (int)val != rme9652_spdif_out(rme9652); + rme9652_set_spdif_output(rme9652, val); + return change; +} + +#define RME9652_SYNC_MODE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_rme9652_info_sync_mode, \ + .get = snd_rme9652_get_sync_mode, .put = snd_rme9652_put_sync_mode } + +static int rme9652_sync_mode(struct snd_rme9652 *rme9652) +{ + if (rme9652->control_register & RME9652_wsel) { + return 2; + } else if (rme9652->control_register & RME9652_Master) { + return 1; + } else { + return 0; + } +} + +static int rme9652_set_sync_mode(struct snd_rme9652 *rme9652, int mode) +{ + int restart = 0; + + switch (mode) { + case 0: + rme9652->control_register &= + ~(RME9652_Master | RME9652_wsel); + break; + case 1: + rme9652->control_register = + (rme9652->control_register & ~RME9652_wsel) | RME9652_Master; + break; + case 2: + rme9652->control_register |= + (RME9652_Master | RME9652_wsel); + break; + } + + restart = rme9652->running; + if (restart) + rme9652_stop(rme9652); + + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register); + + if (restart) + rme9652_start(rme9652); + + return 0; +} + +static int snd_rme9652_info_sync_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "AutoSync", "Master", "Word Clock" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_rme9652_get_sync_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme9652->lock); + ucontrol->value.enumerated.item[0] = rme9652_sync_mode(rme9652); + return 0; +} + +static int snd_rme9652_put_sync_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + + val = ucontrol->value.enumerated.item[0] % 3; + guard(spinlock_irq)(&rme9652->lock); + change = (int)val != rme9652_sync_mode(rme9652); + rme9652_set_sync_mode(rme9652, val); + return change; +} + +#define RME9652_SYNC_PREF(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_rme9652_info_sync_pref, \ + .get = snd_rme9652_get_sync_pref, .put = snd_rme9652_put_sync_pref } + +static int rme9652_sync_pref(struct snd_rme9652 *rme9652) +{ + switch (rme9652->control_register & RME9652_SyncPref_Mask) { + case RME9652_SyncPref_ADAT1: + return RME9652_SYNC_FROM_ADAT1; + case RME9652_SyncPref_ADAT2: + return RME9652_SYNC_FROM_ADAT2; + case RME9652_SyncPref_ADAT3: + return RME9652_SYNC_FROM_ADAT3; + case RME9652_SyncPref_SPDIF: + return RME9652_SYNC_FROM_SPDIF; + } + /* Not reachable */ + return 0; +} + +static int rme9652_set_sync_pref(struct snd_rme9652 *rme9652, int pref) +{ + int restart; + + rme9652->control_register &= ~RME9652_SyncPref_Mask; + switch (pref) { + case RME9652_SYNC_FROM_ADAT1: + rme9652->control_register |= RME9652_SyncPref_ADAT1; + break; + case RME9652_SYNC_FROM_ADAT2: + rme9652->control_register |= RME9652_SyncPref_ADAT2; + break; + case RME9652_SYNC_FROM_ADAT3: + rme9652->control_register |= RME9652_SyncPref_ADAT3; + break; + case RME9652_SYNC_FROM_SPDIF: + rme9652->control_register |= RME9652_SyncPref_SPDIF; + break; + } + + restart = rme9652->running; + if (restart) + rme9652_stop(rme9652); + + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register); + + if (restart) + rme9652_start(rme9652); + + return 0; +} + +static int snd_rme9652_info_sync_pref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "IEC958 In", "ADAT1 In", "ADAT2 In", "ADAT3 In" + }; + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + return snd_ctl_enum_info(uinfo, 1, + rme9652->ss_channels == RME9652_NCHANNELS ? 4 : 3, + texts); +} + +static int snd_rme9652_get_sync_pref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme9652->lock); + ucontrol->value.enumerated.item[0] = rme9652_sync_pref(rme9652); + return 0; +} + +static int snd_rme9652_put_sync_pref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + int change, max; + unsigned int val; + + if (!snd_rme9652_use_is_exclusive(rme9652)) + return -EBUSY; + max = rme9652->ss_channels == RME9652_NCHANNELS ? 4 : 3; + val = ucontrol->value.enumerated.item[0] % max; + guard(spinlock_irq)(&rme9652->lock); + change = (int)val != rme9652_sync_pref(rme9652); + rme9652_set_sync_pref(rme9652, val); + return change; +} + +static int snd_rme9652_info_thru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = rme9652->ss_channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_rme9652_get_thru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + unsigned int k; + u32 thru_bits = rme9652->thru_bits; + + for (k = 0; k < rme9652->ss_channels; ++k) { + ucontrol->value.integer.value[k] = !!(thru_bits & (1 << k)); + } + return 0; +} + +static int snd_rme9652_put_thru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + int change; + unsigned int chn; + u32 thru_bits = 0; + + if (!snd_rme9652_use_is_exclusive(rme9652)) + return -EBUSY; + + for (chn = 0; chn < rme9652->ss_channels; ++chn) { + if (ucontrol->value.integer.value[chn]) + thru_bits |= 1 << chn; + } + + guard(spinlock_irq)(&rme9652->lock); + change = thru_bits ^ rme9652->thru_bits; + if (change) { + for (chn = 0; chn < rme9652->ss_channels; ++chn) { + if (!(change & (1 << chn))) + continue; + rme9652_set_thru(rme9652,chn,thru_bits&(1<lock); + ucontrol->value.integer.value[0] = rme9652->passthru; + return 0; +} + +static int snd_rme9652_put_passthru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + int change; + unsigned int val; + int err = 0; + + if (!snd_rme9652_use_is_exclusive(rme9652)) + return -EBUSY; + + val = ucontrol->value.integer.value[0] & 1; + guard(spinlock_irq)(&rme9652->lock); + change = (ucontrol->value.integer.value[0] != rme9652->passthru); + if (change) + err = rme9652_set_passthru(rme9652, val); + return err ? err : change; +} + +/* Read-only switches */ + +#define RME9652_SPDIF_RATE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_rme9652_info_spdif_rate, \ + .get = snd_rme9652_get_spdif_rate } + +static int snd_rme9652_info_spdif_rate(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 = 96000; + return 0; +} + +static int snd_rme9652_get_spdif_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&rme9652->lock); + ucontrol->value.integer.value[0] = rme9652_spdif_sample_rate(rme9652); + return 0; +} + +#define RME9652_ADAT_SYNC(xname, xindex, xidx) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_rme9652_info_adat_sync, \ + .get = snd_rme9652_get_adat_sync, .private_value = xidx } + +static int snd_rme9652_info_adat_sync(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "No Lock", "Lock", "No Lock Sync", "Lock Sync" + }; + + return snd_ctl_enum_info(uinfo, 1, 4, texts); +} + +static int snd_rme9652_get_adat_sync(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + unsigned int mask1, mask2, val; + + switch (kcontrol->private_value) { + case 0: mask1 = RME9652_lock_0; mask2 = RME9652_sync_0; break; + case 1: mask1 = RME9652_lock_1; mask2 = RME9652_sync_1; break; + case 2: mask1 = RME9652_lock_2; mask2 = RME9652_sync_2; break; + default: return -EINVAL; + } + val = rme9652_read(rme9652, RME9652_status_register); + ucontrol->value.enumerated.item[0] = (val & mask1) ? 1 : 0; + ucontrol->value.enumerated.item[0] |= (val & mask2) ? 2 : 0; + return 0; +} + +#define RME9652_TC_VALID(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_rme9652_info_tc_valid, \ + .get = snd_rme9652_get_tc_valid } + +#define snd_rme9652_info_tc_valid snd_ctl_boolean_mono_info + +static int snd_rme9652_get_tc_valid(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = + (rme9652_read(rme9652, RME9652_status_register) & RME9652_tc_valid) ? 1 : 0; + return 0; +} + +#ifdef ALSA_HAS_STANDARD_WAY_OF_RETURNING_TIMECODE + +/* FIXME: this routine needs a port to the new control API --jk */ + +static int snd_rme9652_get_tc_value(void *private_data, + snd_kswitch_t *kswitch, + snd_switch_t *uswitch) +{ + struct snd_rme9652 *s = (struct snd_rme9652 *) private_data; + u32 value; + int i; + + uswitch->type = SNDRV_SW_TYPE_DWORD; + + if ((rme9652_read(s, RME9652_status_register) & + RME9652_tc_valid) == 0) { + uswitch->value.data32[0] = 0; + return 0; + } + + /* timecode request */ + + rme9652_write(s, RME9652_time_code, 0); + + /* XXX bug alert: loop-based timing !!!! */ + + for (i = 0; i < 50; i++) { + if (!(rme9652_read(s, i * 4) & RME9652_tc_busy)) + break; + } + + if (!(rme9652_read(s, i * 4) & RME9652_tc_busy)) { + return -EIO; + } + + value = 0; + + for (i = 0; i < 32; i++) { + value >>= 1; + + if (rme9652_read(s, i * 4) & RME9652_tc_out) + value |= 0x80000000; + } + + if (value > 2 * 60 * 48000) { + value -= 2 * 60 * 48000; + } else { + value = 0; + } + + uswitch->value.data32[0] = value; + + return 0; +} + +#endif /* ALSA_HAS_STANDARD_WAY_OF_RETURNING_TIMECODE */ + +static const struct snd_kcontrol_new snd_rme9652_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_rme9652_control_spdif_info, + .get = snd_rme9652_control_spdif_get, + .put = snd_rme9652_control_spdif_put, +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .info = snd_rme9652_control_spdif_stream_info, + .get = snd_rme9652_control_spdif_stream_get, + .put = snd_rme9652_control_spdif_stream_put, +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK), + .info = snd_rme9652_control_spdif_mask_info, + .get = snd_rme9652_control_spdif_mask_get, + .private_value = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_CON_EMPHASIS, +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK), + .info = snd_rme9652_control_spdif_mask_info, + .get = snd_rme9652_control_spdif_mask_get, + .private_value = IEC958_AES0_NONAUDIO | + IEC958_AES0_PROFESSIONAL | + IEC958_AES0_PRO_EMPHASIS, +}, +RME9652_SPDIF_IN("IEC958 Input Connector", 0), +RME9652_SPDIF_OUT("IEC958 Output also on ADAT1", 0), +RME9652_SYNC_MODE("Sync Mode", 0), +RME9652_SYNC_PREF("Preferred Sync Source", 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channels Thru", + .index = 0, + .info = snd_rme9652_info_thru, + .get = snd_rme9652_get_thru, + .put = snd_rme9652_put_thru, +}, +RME9652_SPDIF_RATE("IEC958 Sample Rate", 0), +RME9652_ADAT_SYNC("ADAT1 Sync Check", 0, 0), +RME9652_ADAT_SYNC("ADAT2 Sync Check", 0, 1), +RME9652_TC_VALID("Timecode Valid", 0), +RME9652_PASSTHRU("Passthru", 0) +}; + +static const struct snd_kcontrol_new snd_rme9652_adat3_check = +RME9652_ADAT_SYNC("ADAT3 Sync Check", 0, 2); + +static const struct snd_kcontrol_new snd_rme9652_adat1_input = +RME9652_ADAT1_IN("ADAT1 Input Source", 0); + +static int snd_rme9652_create_controls(struct snd_card *card, struct snd_rme9652 *rme9652) +{ + unsigned int idx; + int err; + struct snd_kcontrol *kctl; + + for (idx = 0; idx < ARRAY_SIZE(snd_rme9652_controls); idx++) { + kctl = snd_ctl_new1(&snd_rme9652_controls[idx], rme9652); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + if (idx == 1) /* IEC958 (S/PDIF) Stream */ + rme9652->spdif_ctl = kctl; + } + + if (rme9652->ss_channels == RME9652_NCHANNELS) { + kctl = snd_ctl_new1(&snd_rme9652_adat3_check, rme9652); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + + if (rme9652->hw_rev >= 15) { + kctl = snd_ctl_new1(&snd_rme9652_adat1_input, rme9652); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + } + + return 0; +} + +/*------------------------------------------------------------ + /proc interface + ------------------------------------------------------------*/ + +static void +snd_rme9652_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_rme9652 *rme9652 = (struct snd_rme9652 *) entry->private_data; + u32 thru_bits = rme9652->thru_bits; + int show_auto_sync_source = 0; + int i; + unsigned int status; + int x; + + status = rme9652_read(rme9652, RME9652_status_register); + + snd_iprintf(buffer, "%s (Card #%d)\n", rme9652->card_name, rme9652->card->number + 1); + snd_iprintf(buffer, "Buffers: capture %p playback %p\n", + rme9652->capture_buffer, rme9652->playback_buffer); + snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n", + rme9652->irq, rme9652->port, (unsigned long)rme9652->iobase); + snd_iprintf(buffer, "Control register: %x\n", rme9652->control_register); + + snd_iprintf(buffer, "\n"); + + x = 1 << (6 + rme9652_decode_latency(rme9652->control_register & + RME9652_latency)); + + snd_iprintf(buffer, "Latency: %d samples (2 periods of %lu bytes)\n", + x, (unsigned long) rme9652->period_bytes); + snd_iprintf(buffer, "Hardware pointer (frames): %ld\n", + rme9652_hw_pointer(rme9652)); + snd_iprintf(buffer, "Passthru: %s\n", str_yes_no(rme9652->passthru)); + + if ((rme9652->control_register & (RME9652_Master | RME9652_wsel)) == 0) { + snd_iprintf(buffer, "Clock mode: autosync\n"); + show_auto_sync_source = 1; + } else if (rme9652->control_register & RME9652_wsel) { + if (status & RME9652_wsel_rd) { + snd_iprintf(buffer, "Clock mode: word clock\n"); + } else { + snd_iprintf(buffer, "Clock mode: word clock (no signal)\n"); + } + } else { + snd_iprintf(buffer, "Clock mode: master\n"); + } + + if (show_auto_sync_source) { + switch (rme9652->control_register & RME9652_SyncPref_Mask) { + case RME9652_SyncPref_ADAT1: + snd_iprintf(buffer, "Pref. sync source: ADAT1\n"); + break; + case RME9652_SyncPref_ADAT2: + snd_iprintf(buffer, "Pref. sync source: ADAT2\n"); + break; + case RME9652_SyncPref_ADAT3: + snd_iprintf(buffer, "Pref. sync source: ADAT3\n"); + break; + case RME9652_SyncPref_SPDIF: + snd_iprintf(buffer, "Pref. sync source: IEC958\n"); + break; + default: + snd_iprintf(buffer, "Pref. sync source: ???\n"); + } + } + + if (rme9652->hw_rev >= 15) + snd_iprintf(buffer, "\nADAT1 Input source: %s\n", + (rme9652->control_register & RME9652_ADAT1_INTERNAL) ? + "Internal" : "ADAT1 optical"); + + snd_iprintf(buffer, "\n"); + + switch (rme9652_decode_spdif_in(rme9652->control_register & + RME9652_inp)) { + case RME9652_SPDIFIN_OPTICAL: + snd_iprintf(buffer, "IEC958 input: ADAT1\n"); + break; + case RME9652_SPDIFIN_COAXIAL: + snd_iprintf(buffer, "IEC958 input: Coaxial\n"); + break; + case RME9652_SPDIFIN_INTERN: + snd_iprintf(buffer, "IEC958 input: Internal\n"); + break; + default: + snd_iprintf(buffer, "IEC958 input: ???\n"); + break; + } + + if (rme9652->control_register & RME9652_opt_out) { + snd_iprintf(buffer, "IEC958 output: Coaxial & ADAT1\n"); + } else { + snd_iprintf(buffer, "IEC958 output: Coaxial only\n"); + } + + if (rme9652->control_register & RME9652_PRO) { + snd_iprintf(buffer, "IEC958 quality: Professional\n"); + } else { + snd_iprintf(buffer, "IEC958 quality: Consumer\n"); + } + + if (rme9652->control_register & RME9652_EMP) { + snd_iprintf(buffer, "IEC958 emphasis: on\n"); + } else { + snd_iprintf(buffer, "IEC958 emphasis: off\n"); + } + + if (rme9652->control_register & RME9652_Dolby) { + snd_iprintf(buffer, "IEC958 Dolby: on\n"); + } else { + snd_iprintf(buffer, "IEC958 Dolby: off\n"); + } + + i = rme9652_spdif_sample_rate(rme9652); + + if (i < 0) { + snd_iprintf(buffer, + "IEC958 sample rate: error flag set\n"); + } else if (i == 0) { + snd_iprintf(buffer, "IEC958 sample rate: undetermined\n"); + } else { + snd_iprintf(buffer, "IEC958 sample rate: %d\n", i); + } + + snd_iprintf(buffer, "\n"); + + snd_iprintf(buffer, "ADAT Sample rate: %dHz\n", + rme9652_adat_sample_rate(rme9652)); + + /* Sync Check */ + + x = status & RME9652_sync_0; + if (status & RME9652_lock_0) { + snd_iprintf(buffer, "ADAT1: %s\n", x ? "Sync" : "Lock"); + } else { + snd_iprintf(buffer, "ADAT1: No Lock\n"); + } + + x = status & RME9652_sync_1; + if (status & RME9652_lock_1) { + snd_iprintf(buffer, "ADAT2: %s\n", x ? "Sync" : "Lock"); + } else { + snd_iprintf(buffer, "ADAT2: No Lock\n"); + } + + x = status & RME9652_sync_2; + if (status & RME9652_lock_2) { + snd_iprintf(buffer, "ADAT3: %s\n", x ? "Sync" : "Lock"); + } else { + snd_iprintf(buffer, "ADAT3: No Lock\n"); + } + + snd_iprintf(buffer, "\n"); + + snd_iprintf(buffer, "Timecode signal: %s\n", + str_yes_no(status & RME9652_tc_valid)); + + /* thru modes */ + + snd_iprintf(buffer, "Punch Status:\n\n"); + + for (i = 0; i < rme9652->ss_channels; i++) { + if (thru_bits & (1 << i)) { + snd_iprintf(buffer, "%2d: on ", i + 1); + } else { + snd_iprintf(buffer, "%2d: off ", i + 1); + } + + if (((i + 1) % 8) == 0) { + snd_iprintf(buffer, "\n"); + } + } + + snd_iprintf(buffer, "\n"); +} + +static void snd_rme9652_proc_init(struct snd_rme9652 *rme9652) +{ + snd_card_ro_proc_new(rme9652->card, "rme9652", rme9652, + snd_rme9652_proc_read); +} + +static void snd_rme9652_card_free(struct snd_card *card) +{ + struct snd_rme9652 *rme9652 = (struct snd_rme9652 *) card->private_data; + + if (rme9652->irq >= 0) + rme9652_stop(rme9652); +} + +static int snd_rme9652_initialize_memory(struct snd_rme9652 *rme9652) +{ + struct snd_dma_buffer *capture_dma, *playback_dma; + + capture_dma = snd_hammerfall_get_buffer(rme9652->pci, RME9652_DMA_AREA_BYTES); + playback_dma = snd_hammerfall_get_buffer(rme9652->pci, RME9652_DMA_AREA_BYTES); + if (!capture_dma || !playback_dma) { + dev_err(rme9652->card->dev, + "%s: no buffers available\n", rme9652->card_name); + return -ENOMEM; + } + + /* copy to the own data for alignment */ + rme9652->capture_dma_buf = *capture_dma; + rme9652->playback_dma_buf = *playback_dma; + + /* Align to bus-space 64K boundary */ + rme9652->capture_dma_buf.addr = ALIGN(capture_dma->addr, 0x10000ul); + rme9652->playback_dma_buf.addr = ALIGN(playback_dma->addr, 0x10000ul); + + /* Tell the card where it is */ + rme9652_write(rme9652, RME9652_rec_buffer, rme9652->capture_dma_buf.addr); + rme9652_write(rme9652, RME9652_play_buffer, rme9652->playback_dma_buf.addr); + + rme9652->capture_dma_buf.area += rme9652->capture_dma_buf.addr - capture_dma->addr; + rme9652->playback_dma_buf.area += rme9652->playback_dma_buf.addr - playback_dma->addr; + rme9652->capture_buffer = rme9652->capture_dma_buf.area; + rme9652->playback_buffer = rme9652->playback_dma_buf.area; + + return 0; +} + +static void snd_rme9652_set_defaults(struct snd_rme9652 *rme9652) +{ + unsigned int k; + + /* ASSUMPTION: rme9652->lock is either held, or + there is no need to hold it (e.g. during module + initialization). + */ + + /* set defaults: + + SPDIF Input via Coax + autosync clock mode + maximum latency (7 = 8192 samples, 64Kbyte buffer, + which implies 2 4096 sample, 32Kbyte periods). + + if rev 1.5, initialize the S/PDIF receiver. + + */ + + rme9652->control_register = + RME9652_inp_0 | rme9652_encode_latency(7); + + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register); + + rme9652_reset_hw_pointer(rme9652); + rme9652_compute_period_size(rme9652); + + /* default: thru off for all channels */ + + for (k = 0; k < RME9652_NCHANNELS; ++k) + rme9652_write(rme9652, RME9652_thru_base + k * 4, 0); + + rme9652->thru_bits = 0; + rme9652->passthru = 0; + + /* set a default rate so that the channel map is set up */ + + rme9652_set_rate(rme9652, 48000); +} + +static irqreturn_t snd_rme9652_interrupt(int irq, void *dev_id) +{ + struct snd_rme9652 *rme9652 = (struct snd_rme9652 *) dev_id; + + if (!(rme9652_read(rme9652, RME9652_status_register) & RME9652_IRQ)) { + return IRQ_NONE; + } + + rme9652_write(rme9652, RME9652_irq_clear, 0); + + if (rme9652->capture_substream) { + snd_pcm_period_elapsed(rme9652->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream); + } + + if (rme9652->playback_substream) { + snd_pcm_period_elapsed(rme9652->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream); + } + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t snd_rme9652_hw_pointer(struct snd_pcm_substream *substream) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + return rme9652_hw_pointer(rme9652); +} + +static signed char *rme9652_channel_buffer_location(struct snd_rme9652 *rme9652, + int stream, + int channel) + +{ + int mapped_channel; + + if (snd_BUG_ON(channel < 0 || channel >= RME9652_NCHANNELS)) + return NULL; + + mapped_channel = rme9652->channel_map[channel]; + if (mapped_channel < 0) + return NULL; + + if (stream == SNDRV_PCM_STREAM_CAPTURE) { + return rme9652->capture_buffer + + (mapped_channel * RME9652_CHANNEL_BUFFER_BYTES); + } else { + return rme9652->playback_buffer + + (mapped_channel * RME9652_CHANNEL_BUFFER_BYTES); + } +} + +static int snd_rme9652_playback_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *src, unsigned long count) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + signed char *channel_buf; + + if (snd_BUG_ON(pos + count > RME9652_CHANNEL_BUFFER_BYTES)) + return -EINVAL; + + channel_buf = rme9652_channel_buffer_location (rme9652, + substream->pstr->stream, + channel); + if (snd_BUG_ON(!channel_buf)) + return -EIO; + if (copy_from_iter(channel_buf + pos, count, src) != count) + return -EFAULT; + return 0; +} + +static int snd_rme9652_capture_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *dst, unsigned long count) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + signed char *channel_buf; + + if (snd_BUG_ON(pos + count > RME9652_CHANNEL_BUFFER_BYTES)) + return -EINVAL; + + channel_buf = rme9652_channel_buffer_location (rme9652, + substream->pstr->stream, + channel); + if (snd_BUG_ON(!channel_buf)) + return -EIO; + if (copy_to_iter(channel_buf + pos, count, dst) != count) + return -EFAULT; + return 0; +} + +static int snd_rme9652_hw_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + unsigned long count) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + signed char *channel_buf; + + channel_buf = rme9652_channel_buffer_location (rme9652, + substream->pstr->stream, + channel); + if (snd_BUG_ON(!channel_buf)) + return -EIO; + memset(channel_buf + pos, 0, count); + return 0; +} + +static int snd_rme9652_reset(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *other; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + other = rme9652->capture_substream; + else + other = rme9652->playback_substream; + if (rme9652->running) + runtime->status->hw_ptr = rme9652_hw_pointer(rme9652); + else + runtime->status->hw_ptr = 0; + if (other) { + struct snd_pcm_substream *s; + struct snd_pcm_runtime *oruntime = other->runtime; + snd_pcm_group_for_each_entry(s, substream) { + if (s == other) { + oruntime->status->hw_ptr = runtime->status->hw_ptr; + break; + } + } + } + return 0; +} + +static int snd_rme9652_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + int err; + pid_t this_pid; + pid_t other_pid; + + scoped_guard(spinlock_irq, &rme9652->lock) { + + if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) { + rme9652->control_register &= ~(RME9652_PRO | RME9652_Dolby | RME9652_EMP); + rme9652_write(rme9652, RME9652_control_register, rme9652->control_register |= rme9652->creg_spdif_stream); + this_pid = rme9652->playback_pid; + other_pid = rme9652->capture_pid; + } else { + this_pid = rme9652->capture_pid; + other_pid = rme9652->playback_pid; + } + + if ((other_pid > 0) && (this_pid != other_pid)) { + + /* The other stream is open, and not by the same + task as this one. Make sure that the parameters + that matter are the same. + */ + + if ((int)params_rate(params) != + rme9652_adat_sample_rate(rme9652)) { + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); + return -EBUSY; + } + + if (params_period_size(params) != rme9652->period_bytes / 4) { + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + return -EBUSY; + } + + /* We're fine. */ + return 0; + } + } + + /* how to make sure that the rate matches an externally-set one ? + */ + + err = rme9652_set_rate(rme9652, params_rate(params)); + if (err < 0) { + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); + return err; + } + + err = rme9652_set_interrupt_interval(rme9652, params_period_size(params)); + if (err < 0) { + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + return err; + } + + return 0; +} + +static int snd_rme9652_channel_info(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info *info) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + int chn; + + if (snd_BUG_ON(info->channel >= RME9652_NCHANNELS)) + return -EINVAL; + + chn = rme9652->channel_map[array_index_nospec(info->channel, + RME9652_NCHANNELS)]; + if (chn < 0) + return -EINVAL; + + info->offset = chn * RME9652_CHANNEL_BUFFER_BYTES; + info->first = 0; + info->step = 32; + return 0; +} + +static int snd_rme9652_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_RESET: + { + return snd_rme9652_reset(substream); + } + case SNDRV_PCM_IOCTL1_CHANNEL_INFO: + { + struct snd_pcm_channel_info *info = arg; + return snd_rme9652_channel_info(substream, info); + } + default: + break; + } + + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static void rme9652_silence_playback(struct snd_rme9652 *rme9652) +{ + memset(rme9652->playback_buffer, 0, RME9652_DMA_AREA_BYTES); +} + +static int snd_rme9652_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *other; + int running; + + guard(spinlock)(&rme9652->lock); + running = rme9652->running; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + running |= 1 << substream->stream; + break; + case SNDRV_PCM_TRIGGER_STOP: + running &= ~(1 << substream->stream); + break; + default: + snd_BUG(); + return -EINVAL; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + other = rme9652->capture_substream; + else + other = rme9652->playback_substream; + + if (other) { + struct snd_pcm_substream *s; + snd_pcm_group_for_each_entry(s, substream) { + if (s == other) { + snd_pcm_trigger_done(s, substream); + if (cmd == SNDRV_PCM_TRIGGER_START) + running |= 1 << s->stream; + else + running &= ~(1 << s->stream); + goto _ok; + } + } + if (cmd == SNDRV_PCM_TRIGGER_START) { + if (!(running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) && + substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rme9652_silence_playback(rme9652); + } else { + if (running && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rme9652_silence_playback(rme9652); + } + } else { + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rme9652_silence_playback(rme9652); + } + _ok: + snd_pcm_trigger_done(substream, substream); + if (!rme9652->running && running) + rme9652_start(rme9652); + else if (rme9652->running && !running) + rme9652_stop(rme9652); + rme9652->running = running; + + return 0; +} + +static int snd_rme9652_prepare(struct snd_pcm_substream *substream) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + + guard(spinlock_irqsave)(&rme9652->lock); + if (!rme9652->running) + rme9652_reset_hw_pointer(rme9652); + return 0; +} + +static const struct snd_pcm_hardware snd_rme9652_playback_subinfo = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_DOUBLE), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000), + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 10, + .channels_max = 26, + .buffer_bytes_max = RME9652_CHANNEL_BUFFER_BYTES * 26, + .period_bytes_min = (64 * 4) * 10, + .period_bytes_max = (8192 * 4) * 26, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_rme9652_capture_subinfo = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000), + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 10, + .channels_max = 26, + .buffer_bytes_max = RME9652_CHANNEL_BUFFER_BYTES *26, + .period_bytes_min = (64 * 4) * 10, + .period_bytes_max = (8192 * 4) * 26, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +static const unsigned int period_sizes[] = { 64, 128, 256, 512, 1024, 2048, 4096, 8192 }; + +static const struct snd_pcm_hw_constraint_list hw_constraints_period_sizes = { + .count = ARRAY_SIZE(period_sizes), + .list = period_sizes, + .mask = 0 +}; + +static int snd_rme9652_hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_rme9652 *rme9652 = rule->private; + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + unsigned int list[2] = { rme9652->ds_channels, rme9652->ss_channels }; + return snd_interval_list(c, 2, list, 0); +} + +static int snd_rme9652_hw_rule_channels_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_rme9652 *rme9652 = rule->private; + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (r->min > 48000) { + struct snd_interval t = { + .min = rme9652->ds_channels, + .max = rme9652->ds_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } else if (r->max < 88200) { + struct snd_interval t = { + .min = rme9652->ss_channels, + .max = rme9652->ss_channels, + .integer = 1, + }; + return snd_interval_refine(c, &t); + } + return 0; +} + +static int snd_rme9652_hw_rule_rate_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_rme9652 *rme9652 = rule->private; + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (c->min >= rme9652->ss_channels) { + struct snd_interval t = { + .min = 44100, + .max = 48000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } else if (c->max <= rme9652->ds_channels) { + struct snd_interval t = { + .min = 88200, + .max = 96000, + .integer = 1, + }; + return snd_interval_refine(r, &t); + } + return 0; +} + +static int snd_rme9652_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + scoped_guard(spinlock_irq, &rme9652->lock) { + snd_pcm_set_sync(substream); + + runtime->hw = snd_rme9652_playback_subinfo; + snd_pcm_set_runtime_buffer(substream, &rme9652->playback_dma_buf); + + if (rme9652->capture_substream == NULL) { + rme9652_stop(rme9652); + rme9652_set_thru(rme9652, -1, 0); + } + + rme9652->playback_pid = current->pid; + rme9652->playback_substream = substream; + } + + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_period_sizes); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_rme9652_hw_rule_channels, rme9652, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_rme9652_hw_rule_channels_rate, rme9652, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_rme9652_hw_rule_rate_channels, rme9652, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + + rme9652->creg_spdif_stream = rme9652->creg_spdif; + rme9652->spdif_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(rme9652->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &rme9652->spdif_ctl->id); + return 0; +} + +static int snd_rme9652_playback_release(struct snd_pcm_substream *substream) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + + scoped_guard(spinlock_irq, &rme9652->lock) { + rme9652->playback_pid = -1; + rme9652->playback_substream = NULL; + } + + rme9652->spdif_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(rme9652->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &rme9652->spdif_ctl->id); + return 0; +} + + +static int snd_rme9652_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + scoped_guard(spinlock_irq, &rme9652->lock) { + snd_pcm_set_sync(substream); + + runtime->hw = snd_rme9652_capture_subinfo; + snd_pcm_set_runtime_buffer(substream, &rme9652->capture_dma_buf); + + if (rme9652->playback_substream == NULL) { + rme9652_stop(rme9652); + rme9652_set_thru(rme9652, -1, 0); + } + + rme9652->capture_pid = current->pid; + rme9652->capture_substream = substream; + } + + snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_period_sizes); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_rme9652_hw_rule_channels, rme9652, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_rme9652_hw_rule_channels_rate, rme9652, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_rme9652_hw_rule_rate_channels, rme9652, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + return 0; +} + +static int snd_rme9652_capture_release(struct snd_pcm_substream *substream) +{ + struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&rme9652->lock); + + rme9652->capture_pid = -1; + rme9652->capture_substream = NULL; + + return 0; +} + +static const struct snd_pcm_ops snd_rme9652_playback_ops = { + .open = snd_rme9652_playback_open, + .close = snd_rme9652_playback_release, + .ioctl = snd_rme9652_ioctl, + .hw_params = snd_rme9652_hw_params, + .prepare = snd_rme9652_prepare, + .trigger = snd_rme9652_trigger, + .pointer = snd_rme9652_hw_pointer, + .copy = snd_rme9652_playback_copy, + .fill_silence = snd_rme9652_hw_silence, +}; + +static const struct snd_pcm_ops snd_rme9652_capture_ops = { + .open = snd_rme9652_capture_open, + .close = snd_rme9652_capture_release, + .ioctl = snd_rme9652_ioctl, + .hw_params = snd_rme9652_hw_params, + .prepare = snd_rme9652_prepare, + .trigger = snd_rme9652_trigger, + .pointer = snd_rme9652_hw_pointer, + .copy = snd_rme9652_capture_copy, +}; + +static int snd_rme9652_create_pcm(struct snd_card *card, + struct snd_rme9652 *rme9652) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(card, rme9652->card_name, 0, 1, 1, &pcm); + if (err < 0) + return err; + + rme9652->pcm = pcm; + pcm->private_data = rme9652; + strscpy(pcm->name, rme9652->card_name); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_rme9652_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_rme9652_capture_ops); + + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + + return 0; +} + +static int snd_rme9652_create(struct snd_card *card, + struct snd_rme9652 *rme9652, + int precise_ptr) +{ + struct pci_dev *pci = rme9652->pci; + int err; + int status; + unsigned short rev; + + rme9652->irq = -1; + rme9652->card = card; + + pci_read_config_word(rme9652->pci, PCI_CLASS_REVISION, &rev); + + switch (rev & 0xff) { + case 3: + case 4: + case 8: + case 9: + break; + + default: + /* who knows? */ + return -ENODEV; + } + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&rme9652->lock); + + err = pcim_request_all_regions(pci, "rme9652"); + if (err < 0) + return err; + rme9652->port = pci_resource_start(pci, 0); + rme9652->iobase = devm_ioremap(&pci->dev, rme9652->port, RME9652_IO_EXTENT); + if (rme9652->iobase == NULL) { + dev_err(card->dev, "unable to remap region 0x%lx-0x%lx\n", + rme9652->port, rme9652->port + RME9652_IO_EXTENT - 1); + return -EBUSY; + } + + if (devm_request_irq(&pci->dev, pci->irq, snd_rme9652_interrupt, + IRQF_SHARED, KBUILD_MODNAME, rme9652)) { + dev_err(card->dev, "unable to request IRQ %d\n", pci->irq); + return -EBUSY; + } + rme9652->irq = pci->irq; + card->sync_irq = rme9652->irq; + rme9652->precise_ptr = precise_ptr; + + /* Determine the h/w rev level of the card. This seems like + a particularly kludgy way to encode it, but its what RME + chose to do, so we follow them ... + */ + + status = rme9652_read(rme9652, RME9652_status_register); + if (rme9652_decode_spdif_rate(status&RME9652_F) == 1) { + rme9652->hw_rev = 15; + } else { + rme9652->hw_rev = 11; + } + + /* Differentiate between the standard Hammerfall, and the + "Light", which does not have the expansion board. This + method comes from information received from Mathhias + Clausen at RME. Display the EEPROM and h/w revID where + relevant. + */ + + switch (rev) { + case 8: /* original eprom */ + strscpy(card->driver, "RME9636"); + if (rme9652->hw_rev == 15) { + rme9652->card_name = "RME Digi9636 (Rev 1.5)"; + } else { + rme9652->card_name = "RME Digi9636"; + } + rme9652->ss_channels = RME9636_NCHANNELS; + break; + case 9: /* W36_G EPROM */ + strscpy(card->driver, "RME9636"); + rme9652->card_name = "RME Digi9636 (Rev G)"; + rme9652->ss_channels = RME9636_NCHANNELS; + break; + case 4: /* W52_G EPROM */ + strscpy(card->driver, "RME9652"); + rme9652->card_name = "RME Digi9652 (Rev G)"; + rme9652->ss_channels = RME9652_NCHANNELS; + break; + case 3: /* original eprom */ + strscpy(card->driver, "RME9652"); + if (rme9652->hw_rev == 15) { + rme9652->card_name = "RME Digi9652 (Rev 1.5)"; + } else { + rme9652->card_name = "RME Digi9652"; + } + rme9652->ss_channels = RME9652_NCHANNELS; + break; + } + + rme9652->ds_channels = (rme9652->ss_channels - 2) / 2 + 2; + + pci_set_master(rme9652->pci); + + err = snd_rme9652_initialize_memory(rme9652); + if (err < 0) + return err; + + err = snd_rme9652_create_pcm(card, rme9652); + if (err < 0) + return err; + + err = snd_rme9652_create_controls(card, rme9652); + if (err < 0) + return err; + + snd_rme9652_proc_init(rme9652); + + rme9652->last_spdif_sample_rate = -1; + rme9652->last_adat_sample_rate = -1; + rme9652->playback_pid = -1; + rme9652->capture_pid = -1; + rme9652->capture_substream = NULL; + rme9652->playback_substream = NULL; + + snd_rme9652_set_defaults(rme9652); + + if (rme9652->hw_rev == 15) { + rme9652_initialize_spdif_receiver (rme9652); + } + + return 0; +} + +static int snd_rme9652_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_rme9652 *rme9652; + struct snd_card *card; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_rme9652), &card); + + if (err < 0) + return err; + + rme9652 = (struct snd_rme9652 *) card->private_data; + card->private_free = snd_rme9652_card_free; + rme9652->dev = dev; + rme9652->pci = pci; + err = snd_rme9652_create(card, rme9652, precise_ptr[dev]); + if (err) + goto error; + + strscpy(card->shortname, rme9652->card_name); + + sprintf(card->longname, "%s at 0x%lx, irq %d", + card->shortname, rme9652->port, rme9652->irq); + err = snd_card_register(card); + if (err) + goto error; + pci_set_drvdata(pci, card); + dev++; + return 0; + + error: + snd_card_free(card); + return err; +} + +static struct pci_driver rme9652_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_rme9652_ids, + .probe = snd_rme9652_probe, +}; + +module_pci_driver(rme9652_driver); diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c new file mode 100644 index 000000000..55c1b11a2 --- /dev/null +++ b/sound/pci/sis7019.c @@ -0,0 +1,1382 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for SiS7019 Audio Accelerator + * + * Copyright (C) 2004-2007, David Dillow + * Written by David Dillow + * Inspired by the Trident 4D-WaveDX/NX driver. + * + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sis7019.h" + +MODULE_AUTHOR("David Dillow "); +MODULE_DESCRIPTION("SiS7019"); +MODULE_LICENSE("GPL"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static bool enable = 1; +static int codecs = 1; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for SiS7019 Audio Accelerator."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for SiS7019 Audio Accelerator."); +module_param(enable, bool, 0444); +MODULE_PARM_DESC(enable, "Enable SiS7019 Audio Accelerator."); +module_param(codecs, int, 0444); +MODULE_PARM_DESC(codecs, "Set bit to indicate that codec number is expected to be present (default 1)"); + +static const struct pci_device_id snd_sis7019_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_SI, 0x7019) }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, snd_sis7019_ids); + +/* There are three timing modes for the voices. + * + * For both playback and capture, when the buffer is one or two periods long, + * we use the hardware's built-in Mid-Loop Interrupt and End-Loop Interrupt + * to let us know when the periods have ended. + * + * When performing playback with more than two periods per buffer, we set + * the "Stop Sample Offset" and tell the hardware to interrupt us when we + * reach it. We then update the offset and continue on until we are + * interrupted for the next period. + * + * Capture channels do not have a SSO, so we allocate a playback channel to + * use as a timer for the capture periods. We use the SSO on the playback + * channel to clock out virtual periods, and adjust the virtual period length + * to maintain synchronization. This algorithm came from the Trident driver. + * + * FIXME: It'd be nice to make use of some of the synth features in the + * hardware, but a woeful lack of documentation is a significant roadblock. + */ +struct voice { + u16 flags; +#define VOICE_IN_USE 1 +#define VOICE_CAPTURE 2 +#define VOICE_SSO_TIMING 4 +#define VOICE_SYNC_TIMING 8 + u16 sync_cso; + u16 period_size; + u16 buffer_size; + u16 sync_period_size; + u16 sync_buffer_size; + u32 sso; + u32 vperiod; + struct snd_pcm_substream *substream; + struct voice *timing; + void __iomem *ctrl_base; + void __iomem *wave_base; + void __iomem *sync_base; + int num; +}; + +/* We need four pages to store our wave parameters during a suspend. If + * we're not doing power management, we still need to allocate a page + * for the silence buffer. + */ +#define SIS_SUSPEND_PAGES 4 + +struct sis7019 { + unsigned long ioport; + void __iomem *ioaddr; + int irq; + int codecs_present; + + struct pci_dev *pci; + struct snd_pcm *pcm; + struct snd_card *card; + struct snd_ac97 *ac97[3]; + + /* Protect against more than one thread hitting the AC97 + * registers (in a more polite manner than pounding the hardware + * semaphore) + */ + struct mutex ac97_mutex; + + /* voice_lock protects allocation/freeing of the voice descriptions + */ + spinlock_t voice_lock; + + struct voice voices[64]; + struct voice capture_voice; + + /* Allocate pages to store the internal wave state during + * suspends. When we're operating, this can be used as a silence + * buffer for a timing channel. + */ + void *suspend_state[SIS_SUSPEND_PAGES]; + + int silence_users; + dma_addr_t silence_dma_addr; +}; + +/* These values are also used by the module param 'codecs' to indicate + * which codecs should be present. + */ +#define SIS_PRIMARY_CODEC_PRESENT 0x0001 +#define SIS_SECONDARY_CODEC_PRESENT 0x0002 +#define SIS_TERTIARY_CODEC_PRESENT 0x0004 + +/* The HW offset parameters (Loop End, Stop Sample, End Sample) have a + * documented range of 8-0xfff8 samples. Given that they are 0-based, + * that places our period/buffer range at 9-0xfff9 samples. That makes the + * max buffer size 0xfff9 samples * 2 channels * 2 bytes per sample, and + * max samples / min samples gives us the max periods in a buffer. + * + * We'll add a constraint upon open that limits the period and buffer sample + * size to values that are legal for the hardware. + */ +static const struct snd_pcm_hardware sis_playback_hw_info = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (0xfff9 * 4), + .period_bytes_min = 9, + .period_bytes_max = (0xfff9 * 4), + .periods_min = 1, + .periods_max = (0xfff9 / 9), +}; + +static const struct snd_pcm_hardware sis_capture_hw_info = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (0xfff9 * 4), + .period_bytes_min = 9, + .period_bytes_max = (0xfff9 * 4), + .periods_min = 1, + .periods_max = (0xfff9 / 9), +}; + +static void sis_update_sso(struct voice *voice, u16 period) +{ + void __iomem *base = voice->ctrl_base; + + voice->sso += period; + if (voice->sso >= voice->buffer_size) + voice->sso -= voice->buffer_size; + + /* Enforce the documented hardware minimum offset */ + if (voice->sso < 8) + voice->sso = 8; + + /* The SSO is in the upper 16 bits of the register. */ + writew(voice->sso & 0xffff, base + SIS_PLAY_DMA_SSO_ESO + 2); +} + +static void sis_update_voice(struct voice *voice) +{ + if (voice->flags & VOICE_SSO_TIMING) { + sis_update_sso(voice, voice->period_size); + } else if (voice->flags & VOICE_SYNC_TIMING) { + int sync; + + /* If we've not hit the end of the virtual period, update + * our records and keep going. + */ + if (voice->vperiod > voice->period_size) { + voice->vperiod -= voice->period_size; + if (voice->vperiod < voice->period_size) + sis_update_sso(voice, voice->vperiod); + else + sis_update_sso(voice, voice->period_size); + return; + } + + /* Calculate our relative offset between the target and + * the actual CSO value. Since we're operating in a loop, + * if the value is more than half way around, we can + * consider ourselves wrapped. + */ + sync = voice->sync_cso; + sync -= readw(voice->sync_base + SIS_CAPTURE_DMA_FORMAT_CSO); + if (sync > (voice->sync_buffer_size / 2)) + sync -= voice->sync_buffer_size; + + /* If sync is positive, then we interrupted too early, and + * we'll need to come back in a few samples and try again. + * There's a minimum wait, as it takes some time for the DMA + * engine to startup, etc... + */ + if (sync > 0) { + if (sync < 16) + sync = 16; + sis_update_sso(voice, sync); + return; + } + + /* Ok, we interrupted right on time, or (hopefully) just + * a bit late. We'll adjst our next waiting period based + * on how close we got. + * + * We need to stay just behind the actual channel to ensure + * it really is past a period when we get our interrupt -- + * otherwise we'll fall into the early code above and have + * a minimum wait time, which makes us quite late here, + * eating into the user's time to refresh the buffer, esp. + * if using small periods. + * + * If we're less than 9 samples behind, we're on target. + * Otherwise, shorten the next vperiod by the amount we've + * been delayed. + */ + if (sync > -9) + voice->vperiod = voice->sync_period_size + 1; + else + voice->vperiod = voice->sync_period_size + sync + 10; + + if (voice->vperiod < voice->buffer_size) { + sis_update_sso(voice, voice->vperiod); + voice->vperiod = 0; + } else + sis_update_sso(voice, voice->period_size); + + sync = voice->sync_cso + voice->sync_period_size; + if (sync >= voice->sync_buffer_size) + sync -= voice->sync_buffer_size; + voice->sync_cso = sync; + } + + snd_pcm_period_elapsed(voice->substream); +} + +static void sis_voice_irq(u32 status, struct voice *voice) +{ + int bit; + + while (status) { + bit = __ffs(status); + status >>= bit + 1; + voice += bit; + sis_update_voice(voice); + voice++; + } +} + +static irqreturn_t sis_interrupt(int irq, void *dev) +{ + struct sis7019 *sis = dev; + unsigned long io = sis->ioport; + struct voice *voice; + u32 intr, status; + + /* We only use the DMA interrupts, and we don't enable any other + * source of interrupts. But, it is possible to see an interrupt + * status that didn't actually interrupt us, so eliminate anything + * we're not expecting to avoid falsely claiming an IRQ, and an + * ensuing endless loop. + */ + intr = inl(io + SIS_GISR); + intr &= SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS | + SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS; + if (!intr) + return IRQ_NONE; + + do { + status = inl(io + SIS_PISR_A); + if (status) { + sis_voice_irq(status, sis->voices); + outl(status, io + SIS_PISR_A); + } + + status = inl(io + SIS_PISR_B); + if (status) { + sis_voice_irq(status, &sis->voices[32]); + outl(status, io + SIS_PISR_B); + } + + status = inl(io + SIS_RISR); + if (status) { + voice = &sis->capture_voice; + if (!voice->timing) + snd_pcm_period_elapsed(voice->substream); + + outl(status, io + SIS_RISR); + } + + outl(intr, io + SIS_GISR); + intr = inl(io + SIS_GISR); + intr &= SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS | + SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS; + } while (intr); + + return IRQ_HANDLED; +} + +static u32 sis_rate_to_delta(unsigned int rate) +{ + u32 delta; + + /* This was copied from the trident driver, but it seems its gotten + * around a bit... nevertheless, it works well. + * + * We special case 44100 and 8000 since rounding with the equation + * does not give us an accurate enough value. For 11025 and 22050 + * the equation gives us the best answer. All other frequencies will + * also use the equation. JDW + */ + if (rate == 44100) + delta = 0xeb3; + else if (rate == 8000) + delta = 0x2ab; + else if (rate == 48000) + delta = 0x1000; + else + delta = DIV_ROUND_CLOSEST(rate << 12, 48000) & 0x0000ffff; + return delta; +} + +static void __sis_map_silence(struct sis7019 *sis) +{ + /* Helper function: must hold sis->voice_lock on entry */ + if (!sis->silence_users) + sis->silence_dma_addr = dma_map_single(&sis->pci->dev, + sis->suspend_state[0], + 4096, DMA_TO_DEVICE); + sis->silence_users++; +} + +static void __sis_unmap_silence(struct sis7019 *sis) +{ + /* Helper function: must hold sis->voice_lock on entry */ + sis->silence_users--; + if (!sis->silence_users) + dma_unmap_single(&sis->pci->dev, sis->silence_dma_addr, 4096, + DMA_TO_DEVICE); +} + +static void sis_free_voice(struct sis7019 *sis, struct voice *voice) +{ + guard(spinlock_irqsave)(&sis->voice_lock); + if (voice->timing) { + __sis_unmap_silence(sis); + voice->timing->flags &= ~(VOICE_IN_USE | VOICE_SSO_TIMING | + VOICE_SYNC_TIMING); + voice->timing = NULL; + } + voice->flags &= ~(VOICE_IN_USE | VOICE_SSO_TIMING | VOICE_SYNC_TIMING); +} + +static struct voice *__sis_alloc_playback_voice(struct sis7019 *sis) +{ + /* Must hold the voice_lock on entry */ + struct voice *voice; + int i; + + for (i = 0; i < 64; i++) { + voice = &sis->voices[i]; + if (voice->flags & VOICE_IN_USE) + continue; + voice->flags |= VOICE_IN_USE; + goto found_one; + } + voice = NULL; + +found_one: + return voice; +} + +static struct voice *sis_alloc_playback_voice(struct sis7019 *sis) +{ + guard(spinlock_irqsave)(&sis->voice_lock); + return __sis_alloc_playback_voice(sis); +} + +static int sis_alloc_timing_voice(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct sis7019 *sis = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct voice *voice = runtime->private_data; + unsigned int period_size, buffer_size; + int needed; + + /* If there are one or two periods per buffer, we don't need a + * timing voice, as we can use the capture channel's interrupts + * to clock out the periods. + */ + period_size = params_period_size(hw_params); + buffer_size = params_buffer_size(hw_params); + needed = (period_size != buffer_size && + period_size != (buffer_size / 2)); + + if (needed && !voice->timing) { + scoped_guard(spinlock_irqsave, &sis->voice_lock) { + voice->timing = __sis_alloc_playback_voice(sis); + if (voice->timing) + __sis_map_silence(sis); + } + if (!voice->timing) + return -ENOMEM; + voice->timing->substream = substream; + } else if (!needed && voice->timing) { + sis_free_voice(sis, voice); + voice->timing = NULL; + } + + return 0; +} + +static int sis_playback_open(struct snd_pcm_substream *substream) +{ + struct sis7019 *sis = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct voice *voice; + + voice = sis_alloc_playback_voice(sis); + if (!voice) + return -EAGAIN; + + voice->substream = substream; + runtime->private_data = voice; + runtime->hw = sis_playback_hw_info; + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 9, 0xfff9); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + 9, 0xfff9); + snd_pcm_set_sync(substream); + return 0; +} + +static int sis_substream_close(struct snd_pcm_substream *substream) +{ + struct sis7019 *sis = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct voice *voice = runtime->private_data; + + sis_free_voice(sis, voice); + return 0; +} + +static int sis_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voice *voice = runtime->private_data; + void __iomem *ctrl_base = voice->ctrl_base; + void __iomem *wave_base = voice->wave_base; + u32 format, dma_addr, control, sso_eso, delta, reg; + u16 leo; + + /* We rely on the PCM core to ensure that the parameters for this + * substream do not change on us while we're programming the HW. + */ + format = 0; + if (snd_pcm_format_width(runtime->format) == 8) + format |= SIS_PLAY_DMA_FORMAT_8BIT; + if (!snd_pcm_format_signed(runtime->format)) + format |= SIS_PLAY_DMA_FORMAT_UNSIGNED; + if (runtime->channels == 1) + format |= SIS_PLAY_DMA_FORMAT_MONO; + + /* The baseline setup is for a single period per buffer, and + * we add bells and whistles as needed from there. + */ + dma_addr = runtime->dma_addr; + leo = runtime->buffer_size - 1; + control = leo | SIS_PLAY_DMA_LOOP | SIS_PLAY_DMA_INTR_AT_LEO; + sso_eso = leo; + + if (runtime->period_size == (runtime->buffer_size / 2)) { + control |= SIS_PLAY_DMA_INTR_AT_MLP; + } else if (runtime->period_size != runtime->buffer_size) { + voice->flags |= VOICE_SSO_TIMING; + voice->sso = runtime->period_size - 1; + voice->period_size = runtime->period_size; + voice->buffer_size = runtime->buffer_size; + + control &= ~SIS_PLAY_DMA_INTR_AT_LEO; + control |= SIS_PLAY_DMA_INTR_AT_SSO; + sso_eso |= (runtime->period_size - 1) << 16; + } + + delta = sis_rate_to_delta(runtime->rate); + + /* Ok, we're ready to go, set up the channel. + */ + writel(format, ctrl_base + SIS_PLAY_DMA_FORMAT_CSO); + writel(dma_addr, ctrl_base + SIS_PLAY_DMA_BASE); + writel(control, ctrl_base + SIS_PLAY_DMA_CONTROL); + writel(sso_eso, ctrl_base + SIS_PLAY_DMA_SSO_ESO); + + for (reg = 0; reg < SIS_WAVE_SIZE; reg += 4) + writel(0, wave_base + reg); + + writel(SIS_WAVE_GENERAL_WAVE_VOLUME, wave_base + SIS_WAVE_GENERAL); + writel(delta << 16, wave_base + SIS_WAVE_GENERAL_ARTICULATION); + writel(SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE | + SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE | + SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE, + wave_base + SIS_WAVE_CHANNEL_CONTROL); + + /* Force PCI writes to post. */ + readl(ctrl_base); + + return 0; +} + +static int sis_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct sis7019 *sis = snd_pcm_substream_chip(substream); + unsigned long io = sis->ioport; + struct snd_pcm_substream *s; + struct voice *voice; + void *chip; + int starting; + u32 record = 0; + u32 play[2] = { 0, 0 }; + + /* No locks needed, as the PCM core will hold the locks on the + * substreams, and the HW will only start/stop the indicated voices + * without changing the state of the others. + */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + starting = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + starting = 0; + break; + default: + return -EINVAL; + } + + snd_pcm_group_for_each_entry(s, substream) { + /* Make sure it is for us... */ + chip = snd_pcm_substream_chip(s); + if (chip != sis) + continue; + + voice = s->runtime->private_data; + if (voice->flags & VOICE_CAPTURE) { + record |= 1 << voice->num; + voice = voice->timing; + } + + /* voice could be NULL if this a recording stream, and it + * doesn't have an external timing channel. + */ + if (voice) + play[voice->num / 32] |= 1 << (voice->num & 0x1f); + + snd_pcm_trigger_done(s, substream); + } + + if (starting) { + if (record) + outl(record, io + SIS_RECORD_START_REG); + if (play[0]) + outl(play[0], io + SIS_PLAY_START_A_REG); + if (play[1]) + outl(play[1], io + SIS_PLAY_START_B_REG); + } else { + if (record) + outl(record, io + SIS_RECORD_STOP_REG); + if (play[0]) + outl(play[0], io + SIS_PLAY_STOP_A_REG); + if (play[1]) + outl(play[1], io + SIS_PLAY_STOP_B_REG); + } + return 0; +} + +static snd_pcm_uframes_t sis_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voice *voice = runtime->private_data; + u32 cso; + + cso = readl(voice->ctrl_base + SIS_PLAY_DMA_FORMAT_CSO); + cso &= 0xffff; + return cso; +} + +static int sis_capture_open(struct snd_pcm_substream *substream) +{ + struct sis7019 *sis = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct voice *voice = &sis->capture_voice; + + /* FIXME: The driver only supports recording from one channel + * at the moment, but it could support more. + */ + scoped_guard(spinlock_irqsave, &sis->voice_lock) { + if (voice->flags & VOICE_IN_USE) + voice = NULL; + else + voice->flags |= VOICE_IN_USE; + } + + if (!voice) + return -EAGAIN; + + voice->substream = substream; + runtime->private_data = voice; + runtime->hw = sis_capture_hw_info; + runtime->hw.rates = sis->ac97[0]->rates[AC97_RATES_ADC]; + snd_pcm_limit_hw_rates(runtime); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 9, 0xfff9); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + 9, 0xfff9); + snd_pcm_set_sync(substream); + return 0; +} + +static int sis_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct sis7019 *sis = snd_pcm_substream_chip(substream); + int rc; + + rc = snd_ac97_set_rate(sis->ac97[0], AC97_PCM_LR_ADC_RATE, + params_rate(hw_params)); + if (rc) + goto out; + + rc = sis_alloc_timing_voice(substream, hw_params); + +out: + return rc; +} + +static void sis_prepare_timing_voice(struct voice *voice, + struct snd_pcm_substream *substream) +{ + struct sis7019 *sis = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct voice *timing = voice->timing; + void __iomem *play_base = timing->ctrl_base; + void __iomem *wave_base = timing->wave_base; + u16 buffer_size, period_size; + u32 format, control, sso_eso, delta; + u32 vperiod, sso, reg; + + /* Set our initial buffer and period as large as we can given a + * single page of silence. + */ + buffer_size = 4096 / runtime->channels; + buffer_size /= snd_pcm_format_size(runtime->format, 1); + period_size = buffer_size; + + /* Initially, we want to interrupt just a bit behind the end of + * the period we're clocking out. 12 samples seems to give a good + * delay. + * + * We want to spread our interrupts throughout the virtual period, + * so that we don't end up with two interrupts back to back at the + * end -- this helps minimize the effects of any jitter. Adjust our + * clocking period size so that the last period is at least a fourth + * of a full period. + * + * This is all moot if we don't need to use virtual periods. + */ + vperiod = runtime->period_size + 12; + if (vperiod > period_size) { + u16 tail = vperiod % period_size; + u16 quarter_period = period_size / 4; + + if (tail && tail < quarter_period) { + u16 loops = vperiod / period_size; + + tail = quarter_period - tail; + tail += loops - 1; + tail /= loops; + period_size -= tail; + } + + sso = period_size - 1; + } else { + /* The initial period will fit inside the buffer, so we + * don't need to use virtual periods -- disable them. + */ + period_size = runtime->period_size; + sso = vperiod - 1; + vperiod = 0; + } + + /* The interrupt handler implements the timing synchronization, so + * setup its state. + */ + timing->flags |= VOICE_SYNC_TIMING; + timing->sync_base = voice->ctrl_base; + timing->sync_cso = runtime->period_size; + timing->sync_period_size = runtime->period_size; + timing->sync_buffer_size = runtime->buffer_size; + timing->period_size = period_size; + timing->buffer_size = buffer_size; + timing->sso = sso; + timing->vperiod = vperiod; + + /* Using unsigned samples with the all-zero silence buffer + * forces the output to the lower rail, killing playback. + * So ignore unsigned vs signed -- it doesn't change the timing. + */ + format = 0; + if (snd_pcm_format_width(runtime->format) == 8) + format = SIS_CAPTURE_DMA_FORMAT_8BIT; + if (runtime->channels == 1) + format |= SIS_CAPTURE_DMA_FORMAT_MONO; + + control = timing->buffer_size - 1; + control |= SIS_PLAY_DMA_LOOP | SIS_PLAY_DMA_INTR_AT_SSO; + sso_eso = timing->buffer_size - 1; + sso_eso |= timing->sso << 16; + + delta = sis_rate_to_delta(runtime->rate); + + /* We've done the math, now configure the channel. + */ + writel(format, play_base + SIS_PLAY_DMA_FORMAT_CSO); + writel(sis->silence_dma_addr, play_base + SIS_PLAY_DMA_BASE); + writel(control, play_base + SIS_PLAY_DMA_CONTROL); + writel(sso_eso, play_base + SIS_PLAY_DMA_SSO_ESO); + + for (reg = 0; reg < SIS_WAVE_SIZE; reg += 4) + writel(0, wave_base + reg); + + writel(SIS_WAVE_GENERAL_WAVE_VOLUME, wave_base + SIS_WAVE_GENERAL); + writel(delta << 16, wave_base + SIS_WAVE_GENERAL_ARTICULATION); + writel(SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE | + SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE | + SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE, + wave_base + SIS_WAVE_CHANNEL_CONTROL); +} + +static int sis_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voice *voice = runtime->private_data; + void __iomem *rec_base = voice->ctrl_base; + u32 format, dma_addr, control; + u16 leo; + + /* We rely on the PCM core to ensure that the parameters for this + * substream do not change on us while we're programming the HW. + */ + format = 0; + if (snd_pcm_format_width(runtime->format) == 8) + format = SIS_CAPTURE_DMA_FORMAT_8BIT; + if (!snd_pcm_format_signed(runtime->format)) + format |= SIS_CAPTURE_DMA_FORMAT_UNSIGNED; + if (runtime->channels == 1) + format |= SIS_CAPTURE_DMA_FORMAT_MONO; + + dma_addr = runtime->dma_addr; + leo = runtime->buffer_size - 1; + control = leo | SIS_CAPTURE_DMA_LOOP; + + /* If we've got more than two periods per buffer, then we have + * use a timing voice to clock out the periods. Otherwise, we can + * use the capture channel's interrupts. + */ + if (voice->timing) { + sis_prepare_timing_voice(voice, substream); + } else { + control |= SIS_CAPTURE_DMA_INTR_AT_LEO; + if (runtime->period_size != runtime->buffer_size) + control |= SIS_CAPTURE_DMA_INTR_AT_MLP; + } + + writel(format, rec_base + SIS_CAPTURE_DMA_FORMAT_CSO); + writel(dma_addr, rec_base + SIS_CAPTURE_DMA_BASE); + writel(control, rec_base + SIS_CAPTURE_DMA_CONTROL); + + /* Force the writes to post. */ + readl(rec_base); + + return 0; +} + +static const struct snd_pcm_ops sis_playback_ops = { + .open = sis_playback_open, + .close = sis_substream_close, + .prepare = sis_pcm_playback_prepare, + .trigger = sis_pcm_trigger, + .pointer = sis_pcm_pointer, +}; + +static const struct snd_pcm_ops sis_capture_ops = { + .open = sis_capture_open, + .close = sis_substream_close, + .hw_params = sis_capture_hw_params, + .prepare = sis_pcm_capture_prepare, + .trigger = sis_pcm_trigger, + .pointer = sis_pcm_pointer, +}; + +static int sis_pcm_create(struct sis7019 *sis) +{ + struct snd_pcm *pcm; + int rc; + + /* We have 64 voices, and the driver currently records from + * only one channel, though that could change in the future. + */ + rc = snd_pcm_new(sis->card, "SiS7019", 0, 64, 1, &pcm); + if (rc) + return rc; + + pcm->private_data = sis; + strscpy(pcm->name, "SiS7019"); + sis->pcm = pcm; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &sis_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &sis_capture_ops); + + /* Try to preallocate some memory, but it's not the end of the + * world if this fails. + */ + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &sis->pci->dev, 64*1024, 128*1024); + + return 0; +} + +static unsigned short sis_ac97_rw(struct sis7019 *sis, int codec, u32 cmd) +{ + unsigned long io = sis->ioport; + unsigned short val = 0xffff; + u16 status; + u16 rdy; + int count; + static const u16 codec_ready[3] = { + SIS_AC97_STATUS_CODEC_READY, + SIS_AC97_STATUS_CODEC2_READY, + SIS_AC97_STATUS_CODEC3_READY, + }; + + rdy = codec_ready[codec]; + + + /* Get the AC97 semaphore -- software first, so we don't spin + * pounding out IO reads on the hardware semaphore... + */ + guard(mutex)(&sis->ac97_mutex); + + count = 0xffff; + while ((inw(io + SIS_AC97_SEMA) & SIS_AC97_SEMA_BUSY) && --count) + udelay(1); + + if (!count) + goto timeout; + + /* ... and wait for any outstanding commands to complete ... + */ + count = 0xffff; + do { + status = inw(io + SIS_AC97_STATUS); + if ((status & rdy) && !(status & SIS_AC97_STATUS_BUSY)) + break; + + udelay(1); + } while (--count); + + if (!count) + goto timeout_sema; + + /* ... before sending our command and waiting for it to finish ... + */ + outl(cmd, io + SIS_AC97_CMD); + udelay(10); + + count = 0xffff; + while ((inw(io + SIS_AC97_STATUS) & SIS_AC97_STATUS_BUSY) && --count) + udelay(1); + + /* ... and reading the results (if any). + */ + val = inl(io + SIS_AC97_CMD) >> 16; + +timeout_sema: + outl(SIS_AC97_SEMA_RELEASE, io + SIS_AC97_SEMA); +timeout: + if (!count) { + dev_err(&sis->pci->dev, "ac97 codec %d timeout cmd 0x%08x\n", + codec, cmd); + } + + return val; +} + +static void sis_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + static const u32 cmd[3] = { + SIS_AC97_CMD_CODEC_WRITE, + SIS_AC97_CMD_CODEC2_WRITE, + SIS_AC97_CMD_CODEC3_WRITE, + }; + sis_ac97_rw(ac97->private_data, ac97->num, + (val << 16) | (reg << 8) | cmd[ac97->num]); +} + +static unsigned short sis_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + static const u32 cmd[3] = { + SIS_AC97_CMD_CODEC_READ, + SIS_AC97_CMD_CODEC2_READ, + SIS_AC97_CMD_CODEC3_READ, + }; + return sis_ac97_rw(ac97->private_data, ac97->num, + (reg << 8) | cmd[ac97->num]); +} + +static int sis_mixer_create(struct sis7019 *sis) +{ + struct snd_ac97_bus *bus; + struct snd_ac97_template ac97; + static const struct snd_ac97_bus_ops ops = { + .write = sis_ac97_write, + .read = sis_ac97_read, + }; + int rc; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = sis; + + rc = snd_ac97_bus(sis->card, 0, &ops, NULL, &bus); + if (!rc && sis->codecs_present & SIS_PRIMARY_CODEC_PRESENT) + rc = snd_ac97_mixer(bus, &ac97, &sis->ac97[0]); + ac97.num = 1; + if (!rc && (sis->codecs_present & SIS_SECONDARY_CODEC_PRESENT)) + rc = snd_ac97_mixer(bus, &ac97, &sis->ac97[1]); + ac97.num = 2; + if (!rc && (sis->codecs_present & SIS_TERTIARY_CODEC_PRESENT)) + rc = snd_ac97_mixer(bus, &ac97, &sis->ac97[2]); + + /* If we return an error here, then snd_card_free() should + * free up any ac97 codecs that got created, as well as the bus. + */ + return rc; +} + +static void sis_chip_free(struct snd_card *card) +{ + struct sis7019 *sis = card->private_data; + + /* Reset the chip, and disable all interrputs. + */ + outl(SIS_GCR_SOFTWARE_RESET, sis->ioport + SIS_GCR); + udelay(25); + outl(0, sis->ioport + SIS_GCR); + outl(0, sis->ioport + SIS_GIER); + + /* Now, free everything we allocated. + */ + if (sis->irq >= 0) + free_irq(sis->irq, sis); +} + +static int sis_chip_init(struct sis7019 *sis) +{ + unsigned long io = sis->ioport; + void __iomem *ioaddr = sis->ioaddr; + unsigned long timeout; + u16 status; + int count; + int i; + + /* Reset the audio controller + */ + outl(SIS_GCR_SOFTWARE_RESET, io + SIS_GCR); + udelay(25); + outl(0, io + SIS_GCR); + + /* Get the AC-link semaphore, and reset the codecs + */ + count = 0xffff; + while ((inw(io + SIS_AC97_SEMA) & SIS_AC97_SEMA_BUSY) && --count) + udelay(1); + + if (!count) + return -EIO; + + outl(SIS_AC97_CMD_CODEC_COLD_RESET, io + SIS_AC97_CMD); + udelay(250); + + count = 0xffff; + while ((inw(io + SIS_AC97_STATUS) & SIS_AC97_STATUS_BUSY) && --count) + udelay(1); + + /* Command complete, we can let go of the semaphore now. + */ + outl(SIS_AC97_SEMA_RELEASE, io + SIS_AC97_SEMA); + if (!count) + return -EIO; + + /* Now that we've finished the reset, find out what's attached. + * There are some codec/board combinations that take an extremely + * long time to come up. 350+ ms has been observed in the field, + * so we'll give them up to 500ms. + */ + sis->codecs_present = 0; + timeout = msecs_to_jiffies(500) + jiffies; + while (time_before_eq(jiffies, timeout)) { + status = inl(io + SIS_AC97_STATUS); + if (status & SIS_AC97_STATUS_CODEC_READY) + sis->codecs_present |= SIS_PRIMARY_CODEC_PRESENT; + if (status & SIS_AC97_STATUS_CODEC2_READY) + sis->codecs_present |= SIS_SECONDARY_CODEC_PRESENT; + if (status & SIS_AC97_STATUS_CODEC3_READY) + sis->codecs_present |= SIS_TERTIARY_CODEC_PRESENT; + + if (sis->codecs_present == codecs) + break; + + msleep(1); + } + + /* All done, check for errors. + */ + if (!sis->codecs_present) { + dev_err(&sis->pci->dev, "could not find any codecs\n"); + return -EIO; + } + + if (sis->codecs_present != codecs) { + dev_warn(&sis->pci->dev, "missing codecs, found %0x, expected %0x\n", + sis->codecs_present, codecs); + } + + /* Let the hardware know that the audio driver is alive, + * and enable PCM slots on the AC-link for L/R playback (3 & 4) and + * record channels. We're going to want to use Variable Rate Audio + * for recording, to avoid needlessly resampling from 48kHZ. + */ + outl(SIS_AC97_CONF_AUDIO_ALIVE, io + SIS_AC97_CONF); + outl(SIS_AC97_CONF_AUDIO_ALIVE | SIS_AC97_CONF_PCM_LR_ENABLE | + SIS_AC97_CONF_PCM_CAP_MIC_ENABLE | + SIS_AC97_CONF_PCM_CAP_LR_ENABLE | + SIS_AC97_CONF_CODEC_VRA_ENABLE, io + SIS_AC97_CONF); + + /* All AC97 PCM slots should be sourced from sub-mixer 0. + */ + outl(0, io + SIS_AC97_PSR); + + /* There is only one valid DMA setup for a PCI environment. + */ + outl(SIS_DMA_CSR_PCI_SETTINGS, io + SIS_DMA_CSR); + + /* Reset the synchronization groups for all of the channels + * to be asynchronous. If we start doing SPDIF or 5.1 sound, etc. + * we'll need to change how we handle these. Until then, we just + * assign sub-mixer 0 to all playback channels, and avoid any + * attenuation on the audio. + */ + outl(0, io + SIS_PLAY_SYNC_GROUP_A); + outl(0, io + SIS_PLAY_SYNC_GROUP_B); + outl(0, io + SIS_PLAY_SYNC_GROUP_C); + outl(0, io + SIS_PLAY_SYNC_GROUP_D); + outl(0, io + SIS_MIXER_SYNC_GROUP); + + for (i = 0; i < 64; i++) { + writel(i, SIS_MIXER_START_ADDR(ioaddr, i)); + writel(SIS_MIXER_RIGHT_NO_ATTEN | SIS_MIXER_LEFT_NO_ATTEN | + SIS_MIXER_DEST_0, SIS_MIXER_ADDR(ioaddr, i)); + } + + /* Don't attenuate any audio set for the wave amplifier. + * + * FIXME: Maximum attenuation is set for the music amp, which will + * need to change if we start using the synth engine. + */ + outl(0xffff0000, io + SIS_WEVCR); + + /* Ensure that the wave engine is in normal operating mode. + */ + outl(0, io + SIS_WECCR); + + /* Go ahead and enable the DMA interrupts. They won't go live + * until we start a channel. + */ + outl(SIS_GIER_AUDIO_PLAY_DMA_IRQ_ENABLE | + SIS_GIER_AUDIO_RECORD_DMA_IRQ_ENABLE, io + SIS_GIER); + + return 0; +} + +static int sis_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct sis7019 *sis = card->private_data; + void __iomem *ioaddr = sis->ioaddr; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + if (sis->codecs_present & SIS_PRIMARY_CODEC_PRESENT) + snd_ac97_suspend(sis->ac97[0]); + if (sis->codecs_present & SIS_SECONDARY_CODEC_PRESENT) + snd_ac97_suspend(sis->ac97[1]); + if (sis->codecs_present & SIS_TERTIARY_CODEC_PRESENT) + snd_ac97_suspend(sis->ac97[2]); + + /* snd_pcm_suspend_all() stopped all channels, so we're quiescent. + */ + if (sis->irq >= 0) { + free_irq(sis->irq, sis); + sis->irq = -1; + } + + /* Save the internal state away + */ + for (i = 0; i < 4; i++) { + memcpy_fromio(sis->suspend_state[i], ioaddr, 4096); + ioaddr += 4096; + } + + return 0; +} + +static int sis_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct snd_card *card = dev_get_drvdata(dev); + struct sis7019 *sis = card->private_data; + void __iomem *ioaddr = sis->ioaddr; + int i; + + if (sis_chip_init(sis)) { + dev_err(&pci->dev, "unable to re-init controller\n"); + goto error; + } + + if (request_irq(pci->irq, sis_interrupt, IRQF_SHARED, + KBUILD_MODNAME, sis)) { + dev_err(&pci->dev, "unable to regain IRQ %d\n", pci->irq); + goto error; + } + + /* Restore saved state, then clear out the page we use for the + * silence buffer. + */ + for (i = 0; i < 4; i++) { + memcpy_toio(ioaddr, sis->suspend_state[i], 4096); + ioaddr += 4096; + } + + memset(sis->suspend_state[0], 0, 4096); + + sis->irq = pci->irq; + + if (sis->codecs_present & SIS_PRIMARY_CODEC_PRESENT) + snd_ac97_resume(sis->ac97[0]); + if (sis->codecs_present & SIS_SECONDARY_CODEC_PRESENT) + snd_ac97_resume(sis->ac97[1]); + if (sis->codecs_present & SIS_TERTIARY_CODEC_PRESENT) + snd_ac97_resume(sis->ac97[2]); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; + +error: + snd_card_disconnect(card); + return -EIO; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(sis_pm, sis_suspend, sis_resume); + +static int sis_alloc_suspend(struct sis7019 *sis) +{ + int i; + + /* We need 16K to store the internal wave engine state during a + * suspend, but we don't need it to be contiguous, so play nice + * with the memory system. We'll also use this area for a silence + * buffer. + */ + for (i = 0; i < SIS_SUSPEND_PAGES; i++) { + sis->suspend_state[i] = devm_kmalloc(&sis->pci->dev, 4096, + GFP_KERNEL); + if (!sis->suspend_state[i]) + return -ENOMEM; + } + memset(sis->suspend_state[0], 0, 4096); + + return 0; +} + +static int sis_chip_create(struct snd_card *card, + struct pci_dev *pci) +{ + struct sis7019 *sis = card->private_data; + struct voice *voice; + int rc; + int i; + + rc = pcim_enable_device(pci); + if (rc) + return rc; + + rc = dma_set_mask(&pci->dev, DMA_BIT_MASK(30)); + if (rc < 0) { + dev_err(&pci->dev, "architecture does not support 30-bit PCI busmaster DMA"); + return -ENXIO; + } + + mutex_init(&sis->ac97_mutex); + spin_lock_init(&sis->voice_lock); + sis->card = card; + sis->pci = pci; + sis->irq = -1; + sis->ioport = pci_resource_start(pci, 0); + + rc = pcim_request_region(pci, 0, "SiS7019"); + if (rc) { + dev_err(&pci->dev, "unable request I/O region\n"); + return rc; + } + + sis->ioaddr = pcim_iomap_region(pci, 1, "SiS7019"); + if (IS_ERR(sis->ioaddr)) { + dev_err(&pci->dev, "unable to remap MMIO, aborting\n"); + return PTR_ERR(sis->ioaddr); + } + + rc = sis_alloc_suspend(sis); + if (rc < 0) { + dev_err(&pci->dev, "unable to allocate state storage\n"); + return rc; + } + + rc = sis_chip_init(sis); + if (rc) + return rc; + card->private_free = sis_chip_free; + + rc = request_irq(pci->irq, sis_interrupt, IRQF_SHARED, KBUILD_MODNAME, + sis); + if (rc) { + dev_err(&pci->dev, "unable to allocate irq %d\n", sis->irq); + return rc; + } + + sis->irq = pci->irq; + card->sync_irq = sis->irq; + pci_set_master(pci); + + for (i = 0; i < 64; i++) { + voice = &sis->voices[i]; + voice->num = i; + voice->ctrl_base = SIS_PLAY_DMA_ADDR(sis->ioaddr, i); + voice->wave_base = SIS_WAVE_ADDR(sis->ioaddr, i); + } + + voice = &sis->capture_voice; + voice->flags = VOICE_CAPTURE; + voice->num = SIS_CAPTURE_CHAN_AC97_PCM_IN; + voice->ctrl_base = SIS_CAPTURE_DMA_ADDR(sis->ioaddr, voice->num); + + return 0; +} + +static int __snd_sis7019_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct sis7019 *sis; + int rc; + + if (!enable) + return -ENOENT; + + /* The user can specify which codecs should be present so that we + * can wait for them to show up if they are slow to recover from + * the AC97 cold reset. We default to a single codec, the primary. + * + * We assume that SIS_PRIMARY_*_PRESENT matches bits 0-2. + */ + codecs &= SIS_PRIMARY_CODEC_PRESENT | SIS_SECONDARY_CODEC_PRESENT | + SIS_TERTIARY_CODEC_PRESENT; + if (!codecs) + codecs = SIS_PRIMARY_CODEC_PRESENT; + + rc = snd_devm_card_new(&pci->dev, index, id, THIS_MODULE, + sizeof(*sis), &card); + if (rc < 0) + return rc; + + strscpy(card->driver, "SiS7019"); + strscpy(card->shortname, "SiS7019"); + rc = sis_chip_create(card, pci); + if (rc) + return rc; + + sis = card->private_data; + + rc = sis_mixer_create(sis); + if (rc) + return rc; + + rc = sis_pcm_create(sis); + if (rc) + return rc; + + snprintf(card->longname, sizeof(card->longname), + "%s Audio Accelerator with %s at 0x%lx, irq %d", + card->shortname, snd_ac97_get_short_name(sis->ac97[0]), + sis->ioport, sis->irq); + + rc = snd_card_register(card); + if (rc) + return rc; + + pci_set_drvdata(pci, card); + return 0; +} + +static int snd_sis7019_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_sis7019_probe(pci, pci_id)); +} + +static struct pci_driver sis7019_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_sis7019_ids, + .probe = snd_sis7019_probe, + .driver = { + .pm = &sis_pm, + }, +}; + +module_pci_driver(sis7019_driver); diff --git a/sound/pci/sis7019.h b/sound/pci/sis7019.h new file mode 100644 index 000000000..9141aad85 --- /dev/null +++ b/sound/pci/sis7019.h @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __sis7019_h__ +#define __sis7019_h__ + +/* + * Definitions for SiS7019 Audio Accelerator + * + * Copyright (C) 2004-2007, David Dillow + * Written by David Dillow + * Inspired by the Trident 4D-WaveDX/NX driver. + * + * All rights reserved. + */ + + +/* General Control Register */ +#define SIS_GCR 0x00 +#define SIS_GCR_MACRO_POWER_DOWN 0x80000000 +#define SIS_GCR_MODEM_ENABLE 0x00010000 +#define SIS_GCR_SOFTWARE_RESET 0x00000001 + +/* General Interrupt Enable Register */ +#define SIS_GIER 0x04 +#define SIS_GIER_MODEM_TIMER_IRQ_ENABLE 0x00100000 +#define SIS_GIER_MODEM_RX_DMA_IRQ_ENABLE 0x00080000 +#define SIS_GIER_MODEM_TX_DMA_IRQ_ENABLE 0x00040000 +#define SIS_GIER_AC97_GPIO1_IRQ_ENABLE 0x00020000 +#define SIS_GIER_AC97_GPIO0_IRQ_ENABLE 0x00010000 +#define SIS_GIER_AC97_SAMPLE_TIMER_IRQ_ENABLE 0x00000010 +#define SIS_GIER_AUDIO_GLOBAL_TIMER_IRQ_ENABLE 0x00000008 +#define SIS_GIER_AUDIO_RECORD_DMA_IRQ_ENABLE 0x00000004 +#define SIS_GIER_AUDIO_PLAY_DMA_IRQ_ENABLE 0x00000002 +#define SIS_GIER_AUDIO_WAVE_ENGINE_IRQ_ENABLE 0x00000001 + +/* General Interrupt Status Register */ +#define SIS_GISR 0x08 +#define SIS_GISR_MODEM_TIMER_IRQ_STATUS 0x00100000 +#define SIS_GISR_MODEM_RX_DMA_IRQ_STATUS 0x00080000 +#define SIS_GISR_MODEM_TX_DMA_IRQ_STATUS 0x00040000 +#define SIS_GISR_AC97_GPIO1_IRQ_STATUS 0x00020000 +#define SIS_GISR_AC97_GPIO0_IRQ_STATUS 0x00010000 +#define SIS_GISR_AC97_SAMPLE_TIMER_IRQ_STATUS 0x00000010 +#define SIS_GISR_AUDIO_GLOBAL_TIMER_IRQ_STATUS 0x00000008 +#define SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS 0x00000004 +#define SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS 0x00000002 +#define SIS_GISR_AUDIO_WAVE_ENGINE_IRQ_STATUS 0x00000001 + +/* DMA Control Register */ +#define SIS_DMA_CSR 0x10 +#define SIS_DMA_CSR_PCI_SETTINGS 0x0000001d +#define SIS_DMA_CSR_CONCURRENT_ENABLE 0x00000200 +#define SIS_DMA_CSR_PIPELINE_ENABLE 0x00000100 +#define SIS_DMA_CSR_RX_DRAIN_ENABLE 0x00000010 +#define SIS_DMA_CSR_RX_FILL_ENABLE 0x00000008 +#define SIS_DMA_CSR_TX_DRAIN_ENABLE 0x00000004 +#define SIS_DMA_CSR_TX_LOWPRI_FILL_ENABLE 0x00000002 +#define SIS_DMA_CSR_TX_HIPRI_FILL_ENABLE 0x00000001 + +/* Playback Channel Start Registers */ +#define SIS_PLAY_START_A_REG 0x14 +#define SIS_PLAY_START_B_REG 0x18 + +/* Playback Channel Stop Registers */ +#define SIS_PLAY_STOP_A_REG 0x1c +#define SIS_PLAY_STOP_B_REG 0x20 + +/* Recording Channel Start Register */ +#define SIS_RECORD_START_REG 0x24 + +/* Recording Channel Stop Register */ +#define SIS_RECORD_STOP_REG 0x28 + +/* Playback Interrupt Status Registers */ +#define SIS_PISR_A 0x2c +#define SIS_PISR_B 0x30 + +/* Recording Interrupt Status Register */ +#define SIS_RISR 0x34 + +/* AC97 AC-link Playback Source Register */ +#define SIS_AC97_PSR 0x40 +#define SIS_AC97_PSR_MODEM_HEADSET_SRC_MIXER 0x0f000000 +#define SIS_AC97_PSR_MODEM_LINE2_SRC_MIXER 0x00f00000 +#define SIS_AC97_PSR_MODEM_LINE1_SRC_MIXER 0x000f0000 +#define SIS_AC97_PSR_PCM_LFR_SRC_MIXER 0x0000f000 +#define SIS_AC97_PSR_PCM_SURROUND_SRC_MIXER 0x00000f00 +#define SIS_AC97_PSR_PCM_CENTER_SRC_MIXER 0x000000f0 +#define SIS_AC97_PSR_PCM_LR_SRC_MIXER 0x0000000f + +/* AC97 AC-link Command Register */ +#define SIS_AC97_CMD 0x50 +#define SIS_AC97_CMD_DATA_MASK 0xffff0000 +#define SIS_AC97_CMD_REG_MASK 0x0000ff00 +#define SIS_AC97_CMD_CODEC3_READ 0x0000000d +#define SIS_AC97_CMD_CODEC3_WRITE 0x0000000c +#define SIS_AC97_CMD_CODEC2_READ 0x0000000b +#define SIS_AC97_CMD_CODEC2_WRITE 0x0000000a +#define SIS_AC97_CMD_CODEC_READ 0x00000009 +#define SIS_AC97_CMD_CODEC_WRITE 0x00000008 +#define SIS_AC97_CMD_CODEC_WARM_RESET 0x00000005 +#define SIS_AC97_CMD_CODEC_COLD_RESET 0x00000004 +#define SIS_AC97_CMD_DONE 0x00000000 + +/* AC97 AC-link Semaphore Register */ +#define SIS_AC97_SEMA 0x54 +#define SIS_AC97_SEMA_BUSY 0x00000001 +#define SIS_AC97_SEMA_RELEASE 0x00000000 + +/* AC97 AC-link Status Register */ +#define SIS_AC97_STATUS 0x58 +#define SIS_AC97_STATUS_AUDIO_D2_INACT_SECS 0x03f00000 +#define SIS_AC97_STATUS_MODEM_ALIVE 0x00002000 +#define SIS_AC97_STATUS_AUDIO_ALIVE 0x00001000 +#define SIS_AC97_STATUS_CODEC3_READY 0x00000400 +#define SIS_AC97_STATUS_CODEC2_READY 0x00000200 +#define SIS_AC97_STATUS_CODEC_READY 0x00000100 +#define SIS_AC97_STATUS_WARM_RESET 0x00000080 +#define SIS_AC97_STATUS_COLD_RESET 0x00000040 +#define SIS_AC97_STATUS_POWERED_DOWN 0x00000020 +#define SIS_AC97_STATUS_NORMAL 0x00000010 +#define SIS_AC97_STATUS_READ_EXPIRED 0x00000004 +#define SIS_AC97_STATUS_SEMAPHORE 0x00000002 +#define SIS_AC97_STATUS_BUSY 0x00000001 + +/* AC97 AC-link Audio Configuration Register */ +#define SIS_AC97_CONF 0x5c +#define SIS_AC97_CONF_AUDIO_ALIVE 0x80000000 +#define SIS_AC97_CONF_WARM_RESET_ENABLE 0x40000000 +#define SIS_AC97_CONF_PR6_ENABLE 0x20000000 +#define SIS_AC97_CONF_PR5_ENABLE 0x10000000 +#define SIS_AC97_CONF_PR4_ENABLE 0x08000000 +#define SIS_AC97_CONF_PR3_ENABLE 0x04000000 +#define SIS_AC97_CONF_PR2_PR7_ENABLE 0x02000000 +#define SIS_AC97_CONF_PR0_PR1_ENABLE 0x01000000 +#define SIS_AC97_CONF_AUTO_PM_ENABLE 0x00800000 +#define SIS_AC97_CONF_PCM_LFE_ENABLE 0x00080000 +#define SIS_AC97_CONF_PCM_SURROUND_ENABLE 0x00040000 +#define SIS_AC97_CONF_PCM_CENTER_ENABLE 0x00020000 +#define SIS_AC97_CONF_PCM_LR_ENABLE 0x00010000 +#define SIS_AC97_CONF_PCM_CAP_MIC_ENABLE 0x00002000 +#define SIS_AC97_CONF_PCM_CAP_LR_ENABLE 0x00001000 +#define SIS_AC97_CONF_PCM_CAP_MIC_FROM_CODEC3 0x00000200 +#define SIS_AC97_CONF_PCM_CAP_LR_FROM_CODEC3 0x00000100 +#define SIS_AC97_CONF_CODEC3_PM_VRM 0x00000080 +#define SIS_AC97_CONF_CODEC_PM_VRM 0x00000040 +#define SIS_AC97_CONF_CODEC3_VRA_ENABLE 0x00000020 +#define SIS_AC97_CONF_CODEC_VRA_ENABLE 0x00000010 +#define SIS_AC97_CONF_CODEC3_PM_EAC 0x00000008 +#define SIS_AC97_CONF_CODEC_PM_EAC 0x00000004 +#define SIS_AC97_CONF_CODEC3_EXISTS 0x00000002 +#define SIS_AC97_CONF_CODEC_EXISTS 0x00000001 + +/* Playback Channel Sync Group registers */ +#define SIS_PLAY_SYNC_GROUP_A 0x80 +#define SIS_PLAY_SYNC_GROUP_B 0x84 +#define SIS_PLAY_SYNC_GROUP_C 0x88 +#define SIS_PLAY_SYNC_GROUP_D 0x8c +#define SIS_MIXER_SYNC_GROUP 0x90 + +/* Wave Engine Config and Control Register */ +#define SIS_WECCR 0xa0 +#define SIS_WECCR_TESTMODE_MASK 0x00300000 +#define SIS_WECCR_TESTMODE_NORMAL 0x00000000 +#define SIS_WECCR_TESTMODE_BYPASS_NSO_ALPHA 0x00100000 +#define SIS_WECCR_TESTMODE_BYPASS_FC 0x00200000 +#define SIS_WECCR_TESTMODE_BYPASS_WOL 0x00300000 +#define SIS_WECCR_RESONANCE_DELAY_MASK 0x00060000 +#define SIS_WECCR_RESONANCE_DELAY_NONE 0x00000000 +#define SIS_WECCR_RESONANCE_DELAY_FC_1F00 0x00020000 +#define SIS_WECCR_RESONANCE_DELAY_FC_1E00 0x00040000 +#define SIS_WECCR_RESONANCE_DELAY_FC_1C00 0x00060000 +#define SIS_WECCR_IGNORE_CHANNEL_PARMS 0x00010000 +#define SIS_WECCR_COMMAND_CHANNEL_ID_MASK 0x0003ff00 +#define SIS_WECCR_COMMAND_MASK 0x00000007 +#define SIS_WECCR_COMMAND_NONE 0x00000000 +#define SIS_WECCR_COMMAND_DONE 0x00000000 +#define SIS_WECCR_COMMAND_PAUSE 0x00000001 +#define SIS_WECCR_COMMAND_TOGGLE_VEG 0x00000002 +#define SIS_WECCR_COMMAND_TOGGLE_MEG 0x00000003 +#define SIS_WECCR_COMMAND_TOGGLE_VEG_MEG 0x00000004 + +/* Wave Engine Volume Control Register */ +#define SIS_WEVCR 0xa4 +#define SIS_WEVCR_LEFT_MUSIC_ATTENUATION_MASK 0xff000000 +#define SIS_WEVCR_RIGHT_MUSIC_ATTENUATION_MASK 0x00ff0000 +#define SIS_WEVCR_LEFT_WAVE_ATTENUATION_MASK 0x0000ff00 +#define SIS_WEVCR_RIGHT_WAVE_ATTENUATION_MASK 0x000000ff + +/* Wave Engine Interrupt Status Registers */ +#define SIS_WEISR_A 0xa8 +#define SIS_WEISR_B 0xac + + +/* Playback DMA parameters (parameter RAM) */ +#define SIS_PLAY_DMA_OFFSET 0x0000 +#define SIS_PLAY_DMA_SIZE 0x10 +#define SIS_PLAY_DMA_ADDR(addr, num) \ + ((num * SIS_PLAY_DMA_SIZE) + (addr) + SIS_PLAY_DMA_OFFSET) + +#define SIS_PLAY_DMA_FORMAT_CSO 0x00 +#define SIS_PLAY_DMA_FORMAT_UNSIGNED 0x00080000 +#define SIS_PLAY_DMA_FORMAT_8BIT 0x00040000 +#define SIS_PLAY_DMA_FORMAT_MONO 0x00020000 +#define SIS_PLAY_DMA_CSO_MASK 0x0000ffff +#define SIS_PLAY_DMA_BASE 0x04 +#define SIS_PLAY_DMA_CONTROL 0x08 +#define SIS_PLAY_DMA_STOP_AT_SSO 0x04000000 +#define SIS_PLAY_DMA_RELEASE 0x02000000 +#define SIS_PLAY_DMA_LOOP 0x01000000 +#define SIS_PLAY_DMA_INTR_AT_SSO 0x00080000 +#define SIS_PLAY_DMA_INTR_AT_ESO 0x00040000 +#define SIS_PLAY_DMA_INTR_AT_LEO 0x00020000 +#define SIS_PLAY_DMA_INTR_AT_MLP 0x00010000 +#define SIS_PLAY_DMA_LEO_MASK 0x0000ffff +#define SIS_PLAY_DMA_SSO_ESO 0x0c +#define SIS_PLAY_DMA_SSO_MASK 0xffff0000 +#define SIS_PLAY_DMA_ESO_MASK 0x0000ffff + +/* Capture DMA parameters (parameter RAM) */ +#define SIS_CAPTURE_DMA_OFFSET 0x0800 +#define SIS_CAPTURE_DMA_SIZE 0x10 +#define SIS_CAPTURE_DMA_ADDR(addr, num) \ + ((num * SIS_CAPTURE_DMA_SIZE) + (addr) + SIS_CAPTURE_DMA_OFFSET) + +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_0 0 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_1 1 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_2 2 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_3 3 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_4 4 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_5 5 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_6 6 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_7 7 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_8 8 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_9 9 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_10 10 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_11 11 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_12 12 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_13 13 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_14 14 +#define SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_15 15 +#define SIS_CAPTURE_CHAN_AC97_PCM_IN 16 +#define SIS_CAPTURE_CHAN_AC97_MIC_IN 17 +#define SIS_CAPTURE_CHAN_AC97_LINE1_IN 18 +#define SIS_CAPTURE_CHAN_AC97_LINE2_IN 19 +#define SIS_CAPTURE_CHAN_AC97_HANDSE_IN 20 + +#define SIS_CAPTURE_DMA_FORMAT_CSO 0x00 +#define SIS_CAPTURE_DMA_MONO_MODE_MASK 0xc0000000 +#define SIS_CAPTURE_DMA_MONO_MODE_AVG 0x00000000 +#define SIS_CAPTURE_DMA_MONO_MODE_LEFT 0x40000000 +#define SIS_CAPTURE_DMA_MONO_MODE_RIGHT 0x80000000 +#define SIS_CAPTURE_DMA_FORMAT_UNSIGNED 0x00080000 +#define SIS_CAPTURE_DMA_FORMAT_8BIT 0x00040000 +#define SIS_CAPTURE_DMA_FORMAT_MONO 0x00020000 +#define SIS_CAPTURE_DMA_CSO_MASK 0x0000ffff +#define SIS_CAPTURE_DMA_BASE 0x04 +#define SIS_CAPTURE_DMA_CONTROL 0x08 +#define SIS_CAPTURE_DMA_STOP_AT_SSO 0x04000000 +#define SIS_CAPTURE_DMA_RELEASE 0x02000000 +#define SIS_CAPTURE_DMA_LOOP 0x01000000 +#define SIS_CAPTURE_DMA_INTR_AT_LEO 0x00020000 +#define SIS_CAPTURE_DMA_INTR_AT_MLP 0x00010000 +#define SIS_CAPTURE_DMA_LEO_MASK 0x0000ffff +#define SIS_CAPTURE_DMA_RESERVED 0x0c + + +/* Mixer routing list start pointer (parameter RAM) */ +#define SIS_MIXER_START_OFFSET 0x1000 +#define SIS_MIXER_START_SIZE 0x04 +#define SIS_MIXER_START_ADDR(addr, num) \ + ((num * SIS_MIXER_START_SIZE) + (addr) + SIS_MIXER_START_OFFSET) + +#define SIS_MIXER_START_MASK 0x0000007f + +/* Mixer routing table (parameter RAM) */ +#define SIS_MIXER_OFFSET 0x1400 +#define SIS_MIXER_SIZE 0x04 +#define SIS_MIXER_ADDR(addr, num) \ + ((num * SIS_MIXER_SIZE) + (addr) + SIS_MIXER_OFFSET) + +#define SIS_MIXER_RIGHT_ATTENUTATION_MASK 0xff000000 +#define SIS_MIXER_RIGHT_NO_ATTEN 0xff000000 +#define SIS_MIXER_LEFT_ATTENUTATION_MASK 0x00ff0000 +#define SIS_MIXER_LEFT_NO_ATTEN 0x00ff0000 +#define SIS_MIXER_NEXT_ENTRY_MASK 0x00007f00 +#define SIS_MIXER_NEXT_ENTRY_NONE 0x00000000 +#define SIS_MIXER_DEST_MASK 0x0000007f +#define SIS_MIXER_DEST_0 0x00000020 +#define SIS_MIXER_DEST_1 0x00000021 +#define SIS_MIXER_DEST_2 0x00000022 +#define SIS_MIXER_DEST_3 0x00000023 +#define SIS_MIXER_DEST_4 0x00000024 +#define SIS_MIXER_DEST_5 0x00000025 +#define SIS_MIXER_DEST_6 0x00000026 +#define SIS_MIXER_DEST_7 0x00000027 +#define SIS_MIXER_DEST_8 0x00000028 +#define SIS_MIXER_DEST_9 0x00000029 +#define SIS_MIXER_DEST_10 0x0000002a +#define SIS_MIXER_DEST_11 0x0000002b +#define SIS_MIXER_DEST_12 0x0000002c +#define SIS_MIXER_DEST_13 0x0000002d +#define SIS_MIXER_DEST_14 0x0000002e +#define SIS_MIXER_DEST_15 0x0000002f + +/* Wave Engine Control Parameters (parameter RAM) */ +#define SIS_WAVE_OFFSET 0x2000 +#define SIS_WAVE_SIZE 0x40 +#define SIS_WAVE_ADDR(addr, num) \ + ((num * SIS_WAVE_SIZE) + (addr) + SIS_WAVE_OFFSET) + +#define SIS_WAVE_GENERAL 0x00 +#define SIS_WAVE_GENERAL_WAVE_VOLUME 0x80000000 +#define SIS_WAVE_GENERAL_MUSIC_VOLUME 0x00000000 +#define SIS_WAVE_GENERAL_VOLUME_MASK 0x7f000000 +#define SIS_WAVE_GENERAL_ARTICULATION 0x04 +#define SIS_WAVE_GENERAL_ARTICULATION_DELTA_MASK 0x3fff0000 +#define SIS_WAVE_ARTICULATION 0x08 +#define SIS_WAVE_TIMER 0x0c +#define SIS_WAVE_GENERATOR 0x10 +#define SIS_WAVE_CHANNEL_CONTROL 0x14 +#define SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE 0x80000000 +#define SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE 0x40000000 +#define SIS_WAVE_CHANNEL_CONTROL_FILTER_ENABLE 0x20000000 +#define SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE 0x10000000 +#define SIS_WAVE_LFO_EG_CONTROL 0x18 +#define SIS_WAVE_LFO_EG_CONTROL_2 0x1c +#define SIS_WAVE_LFO_EG_CONTROL_3 0x20 +#define SIS_WAVE_LFO_EG_CONTROL_4 0x24 + +#endif /* __sis7019_h__ */ diff --git a/sound/pci/sonicvibes.c b/sound/pci/sonicvibes.c new file mode 100644 index 000000000..a885d544a --- /dev/null +++ b/sound/pci/sonicvibes.c @@ -0,0 +1,1449 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for S3 SonicVibes soundcard + * Copyright (c) by Jaroslav Kysela + * + * BUGS: + * It looks like 86c617 rev 3 doesn't supports DDMA buffers above 16MB? + * Driver sometimes hangs... Nobody knows why at this moment... + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("S3 SonicVibes PCI"); +MODULE_LICENSE("GPL"); + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK 1 +#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_PNP; /* Enable this card */ +static bool reverb[SNDRV_CARDS]; +static bool mge[SNDRV_CARDS]; +static unsigned int dmaio = 0x7a00; /* DDMA i/o address */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for S3 SonicVibes soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for S3 SonicVibes soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable S3 SonicVibes soundcard."); +module_param_array(reverb, bool, NULL, 0444); +MODULE_PARM_DESC(reverb, "Enable reverb (SRAM is present) for S3 SonicVibes soundcard."); +module_param_array(mge, bool, NULL, 0444); +MODULE_PARM_DESC(mge, "MIC Gain Enable for S3 SonicVibes soundcard."); +module_param_hw(dmaio, uint, ioport, 0444); +MODULE_PARM_DESC(dmaio, "DDMA i/o base address for S3 SonicVibes soundcard."); + +/* + * Enhanced port direct registers + */ + +#define SV_REG(sonic, x) ((sonic)->enh_port + SV_REG_##x) + +#define SV_REG_CONTROL 0x00 /* R/W: CODEC/Mixer control register */ +#define SV_ENHANCED 0x01 /* audio mode select - enhanced mode */ +#define SV_TEST 0x02 /* test bit */ +#define SV_REVERB 0x04 /* reverb enable */ +#define SV_WAVETABLE 0x08 /* wavetable active / FM active if not set */ +#define SV_INTA 0x20 /* INTA driving - should be always 1 */ +#define SV_RESET 0x80 /* reset chip */ +#define SV_REG_IRQMASK 0x01 /* R/W: CODEC/Mixer interrupt mask register */ +#define SV_DMAA_MASK 0x01 /* mask DMA-A interrupt */ +#define SV_DMAC_MASK 0x04 /* mask DMA-C interrupt */ +#define SV_SPEC_MASK 0x08 /* special interrupt mask - should be always masked */ +#define SV_UD_MASK 0x40 /* Up/Down button interrupt mask */ +#define SV_MIDI_MASK 0x80 /* mask MIDI interrupt */ +#define SV_REG_STATUS 0x02 /* R/O: CODEC/Mixer status register */ +#define SV_DMAA_IRQ 0x01 /* DMA-A interrupt */ +#define SV_DMAC_IRQ 0x04 /* DMA-C interrupt */ +#define SV_SPEC_IRQ 0x08 /* special interrupt */ +#define SV_UD_IRQ 0x40 /* Up/Down interrupt */ +#define SV_MIDI_IRQ 0x80 /* MIDI interrupt */ +#define SV_REG_INDEX 0x04 /* R/W: CODEC/Mixer index address register */ +#define SV_MCE 0x40 /* mode change enable */ +#define SV_TRD 0x80 /* DMA transfer request disabled */ +#define SV_REG_DATA 0x05 /* R/W: CODEC/Mixer index data register */ + +/* + * Enhanced port indirect registers + */ + +#define SV_IREG_LEFT_ADC 0x00 /* Left ADC Input Control */ +#define SV_IREG_RIGHT_ADC 0x01 /* Right ADC Input Control */ +#define SV_IREG_LEFT_AUX1 0x02 /* Left AUX1 Input Control */ +#define SV_IREG_RIGHT_AUX1 0x03 /* Right AUX1 Input Control */ +#define SV_IREG_LEFT_CD 0x04 /* Left CD Input Control */ +#define SV_IREG_RIGHT_CD 0x05 /* Right CD Input Control */ +#define SV_IREG_LEFT_LINE 0x06 /* Left Line Input Control */ +#define SV_IREG_RIGHT_LINE 0x07 /* Right Line Input Control */ +#define SV_IREG_MIC 0x08 /* MIC Input Control */ +#define SV_IREG_GAME_PORT 0x09 /* Game Port Control */ +#define SV_IREG_LEFT_SYNTH 0x0a /* Left Synth Input Control */ +#define SV_IREG_RIGHT_SYNTH 0x0b /* Right Synth Input Control */ +#define SV_IREG_LEFT_AUX2 0x0c /* Left AUX2 Input Control */ +#define SV_IREG_RIGHT_AUX2 0x0d /* Right AUX2 Input Control */ +#define SV_IREG_LEFT_ANALOG 0x0e /* Left Analog Mixer Output Control */ +#define SV_IREG_RIGHT_ANALOG 0x0f /* Right Analog Mixer Output Control */ +#define SV_IREG_LEFT_PCM 0x10 /* Left PCM Input Control */ +#define SV_IREG_RIGHT_PCM 0x11 /* Right PCM Input Control */ +#define SV_IREG_DMA_DATA_FMT 0x12 /* DMA Data Format */ +#define SV_IREG_PC_ENABLE 0x13 /* Playback/Capture Enable Register */ +#define SV_IREG_UD_BUTTON 0x14 /* Up/Down Button Register */ +#define SV_IREG_REVISION 0x15 /* Revision */ +#define SV_IREG_ADC_OUTPUT_CTRL 0x16 /* ADC Output Control */ +#define SV_IREG_DMA_A_UPPER 0x18 /* DMA A Upper Base Count */ +#define SV_IREG_DMA_A_LOWER 0x19 /* DMA A Lower Base Count */ +#define SV_IREG_DMA_C_UPPER 0x1c /* DMA C Upper Base Count */ +#define SV_IREG_DMA_C_LOWER 0x1d /* DMA C Lower Base Count */ +#define SV_IREG_PCM_RATE_LOW 0x1e /* PCM Sampling Rate Low Byte */ +#define SV_IREG_PCM_RATE_HIGH 0x1f /* PCM Sampling Rate High Byte */ +#define SV_IREG_SYNTH_RATE_LOW 0x20 /* Synthesizer Sampling Rate Low Byte */ +#define SV_IREG_SYNTH_RATE_HIGH 0x21 /* Synthesizer Sampling Rate High Byte */ +#define SV_IREG_ADC_CLOCK 0x22 /* ADC Clock Source Selection */ +#define SV_IREG_ADC_ALT_RATE 0x23 /* ADC Alternative Sampling Rate Selection */ +#define SV_IREG_ADC_PLL_M 0x24 /* ADC PLL M Register */ +#define SV_IREG_ADC_PLL_N 0x25 /* ADC PLL N Register */ +#define SV_IREG_SYNTH_PLL_M 0x26 /* Synthesizer PLL M Register */ +#define SV_IREG_SYNTH_PLL_N 0x27 /* Synthesizer PLL N Register */ +#define SV_IREG_MPU401 0x2a /* MPU-401 UART Operation */ +#define SV_IREG_DRIVE_CTRL 0x2b /* Drive Control */ +#define SV_IREG_SRS_SPACE 0x2c /* SRS Space Control */ +#define SV_IREG_SRS_CENTER 0x2d /* SRS Center Control */ +#define SV_IREG_WAVE_SOURCE 0x2e /* Wavetable Sample Source Select */ +#define SV_IREG_ANALOG_POWER 0x30 /* Analog Power Down Control */ +#define SV_IREG_DIGITAL_POWER 0x31 /* Digital Power Down Control */ + +#define SV_IREG_ADC_PLL SV_IREG_ADC_PLL_M +#define SV_IREG_SYNTH_PLL SV_IREG_SYNTH_PLL_M + +/* + * DMA registers + */ + +#define SV_DMA_ADDR0 0x00 +#define SV_DMA_ADDR1 0x01 +#define SV_DMA_ADDR2 0x02 +#define SV_DMA_ADDR3 0x03 +#define SV_DMA_COUNT0 0x04 +#define SV_DMA_COUNT1 0x05 +#define SV_DMA_COUNT2 0x06 +#define SV_DMA_MODE 0x0b +#define SV_DMA_RESET 0x0d +#define SV_DMA_MASK 0x0f + +/* + * Record sources + */ + +#define SV_RECSRC_RESERVED (0x00<<5) +#define SV_RECSRC_CD (0x01<<5) +#define SV_RECSRC_DAC (0x02<<5) +#define SV_RECSRC_AUX2 (0x03<<5) +#define SV_RECSRC_LINE (0x04<<5) +#define SV_RECSRC_AUX1 (0x05<<5) +#define SV_RECSRC_MIC (0x06<<5) +#define SV_RECSRC_OUT (0x07<<5) + +/* + * constants + */ + +#define SV_FULLRATE 48000 +#define SV_REFFREQUENCY 24576000 +#define SV_ADCMULT 512 + +#define SV_MODE_PLAY 1 +#define SV_MODE_CAPTURE 2 + +/* + + */ + +struct sonicvibes { + unsigned long dma1size; + unsigned long dma2size; + int irq; + + unsigned long sb_port; + unsigned long enh_port; + unsigned long synth_port; + unsigned long midi_port; + unsigned long game_port; + unsigned int dmaa_port; + struct resource *res_dmaa; + unsigned int dmac_port; + struct resource *res_dmac; + + unsigned char enable; + unsigned char irqmask; + unsigned char revision; + unsigned char format; + unsigned char srs_space; + unsigned char srs_center; + unsigned char mpu_switch; + unsigned char wave_source; + + unsigned int mode; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + struct snd_rawmidi *rmidi; + struct snd_hwdep *fmsynth; /* S3FM */ + + spinlock_t reg_lock; + + unsigned int p_dma_size; + unsigned int c_dma_size; + + struct snd_kcontrol *master_mute; + struct snd_kcontrol *master_volume; + +#ifdef SUPPORT_JOYSTICK + struct gameport *gameport; +#endif +}; + +static const struct pci_device_id snd_sonic_ids[] = { + { PCI_VDEVICE(S3, 0xca00) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_sonic_ids); + +static const struct snd_ratden sonicvibes_adc_clock = { + .num_min = 4000 * 65536, + .num_max = 48000UL * 65536, + .num_step = 1, + .den = 65536, +}; +static const struct snd_pcm_hw_constraint_ratdens snd_sonicvibes_hw_constraints_adc_clock = { + .nrats = 1, + .rats = &sonicvibes_adc_clock, +}; + +/* + * common I/O routines + */ + +static inline void snd_sonicvibes_setdmaa(struct sonicvibes * sonic, + unsigned int addr, + unsigned int count) +{ + count--; + outl(addr, sonic->dmaa_port + SV_DMA_ADDR0); + outl(count, sonic->dmaa_port + SV_DMA_COUNT0); + outb(0x18, sonic->dmaa_port + SV_DMA_MODE); +#if 0 + dev_dbg(sonic->card->dev, "program dmaa: addr = 0x%x, paddr = 0x%x\n", + addr, inl(sonic->dmaa_port + SV_DMA_ADDR0)); +#endif +} + +static inline void snd_sonicvibes_setdmac(struct sonicvibes * sonic, + unsigned int addr, + unsigned int count) +{ + /* note: dmac is working in word mode!!! */ + count >>= 1; + count--; + outl(addr, sonic->dmac_port + SV_DMA_ADDR0); + outl(count, sonic->dmac_port + SV_DMA_COUNT0); + outb(0x14, sonic->dmac_port + SV_DMA_MODE); +#if 0 + dev_dbg(sonic->card->dev, "program dmac: addr = 0x%x, paddr = 0x%x\n", + addr, inl(sonic->dmac_port + SV_DMA_ADDR0)); +#endif +} + +static inline unsigned int snd_sonicvibes_getdmaa(struct sonicvibes * sonic) +{ + return (inl(sonic->dmaa_port + SV_DMA_COUNT0) & 0xffffff) + 1; +} + +static inline unsigned int snd_sonicvibes_getdmac(struct sonicvibes * sonic) +{ + /* note: dmac is working in word mode!!! */ + return ((inl(sonic->dmac_port + SV_DMA_COUNT0) & 0xffffff) + 1) << 1; +} + +static void snd_sonicvibes_out1(struct sonicvibes * sonic, + unsigned char reg, + unsigned char value) +{ + outb(reg, SV_REG(sonic, INDEX)); + udelay(10); + outb(value, SV_REG(sonic, DATA)); + udelay(10); +} + +static void snd_sonicvibes_out(struct sonicvibes * sonic, + unsigned char reg, + unsigned char value) +{ + guard(spinlock_irqsave)(&sonic->reg_lock); + outb(reg, SV_REG(sonic, INDEX)); + udelay(10); + outb(value, SV_REG(sonic, DATA)); + udelay(10); +} + +static unsigned char snd_sonicvibes_in1(struct sonicvibes * sonic, unsigned char reg) +{ + unsigned char value; + + outb(reg, SV_REG(sonic, INDEX)); + udelay(10); + value = inb(SV_REG(sonic, DATA)); + udelay(10); + return value; +} + +static unsigned char snd_sonicvibes_in(struct sonicvibes * sonic, unsigned char reg) +{ + unsigned char value; + + guard(spinlock_irqsave)(&sonic->reg_lock); + outb(reg, SV_REG(sonic, INDEX)); + udelay(10); + value = inb(SV_REG(sonic, DATA)); + udelay(10); + return value; +} + +#if 0 +static void snd_sonicvibes_debug(struct sonicvibes * sonic) +{ + dev_dbg(sonic->card->dev, + "SV REGS: INDEX = 0x%02x STATUS = 0x%02x\n", + inb(SV_REG(sonic, INDEX)), inb(SV_REG(sonic, STATUS))); + dev_dbg(sonic->card->dev, + " 0x00: left input = 0x%02x 0x20: synth rate low = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x00), snd_sonicvibes_in(sonic, 0x20)); + dev_dbg(sonic->card->dev, + " 0x01: right input = 0x%02x 0x21: synth rate high = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x01), snd_sonicvibes_in(sonic, 0x21)); + dev_dbg(sonic->card->dev, + " 0x02: left AUX1 = 0x%02x 0x22: ADC clock = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x02), snd_sonicvibes_in(sonic, 0x22)); + dev_dbg(sonic->card->dev, + " 0x03: right AUX1 = 0x%02x 0x23: ADC alt rate = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x03), snd_sonicvibes_in(sonic, 0x23)); + dev_dbg(sonic->card->dev, + " 0x04: left CD = 0x%02x 0x24: ADC pll M = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x04), snd_sonicvibes_in(sonic, 0x24)); + dev_dbg(sonic->card->dev, + " 0x05: right CD = 0x%02x 0x25: ADC pll N = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x05), snd_sonicvibes_in(sonic, 0x25)); + dev_dbg(sonic->card->dev, + " 0x06: left line = 0x%02x 0x26: Synth pll M = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x06), snd_sonicvibes_in(sonic, 0x26)); + dev_dbg(sonic->card->dev, + " 0x07: right line = 0x%02x 0x27: Synth pll N = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x07), snd_sonicvibes_in(sonic, 0x27)); + dev_dbg(sonic->card->dev, + " 0x08: MIC = 0x%02x 0x28: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x08), snd_sonicvibes_in(sonic, 0x28)); + dev_dbg(sonic->card->dev, + " 0x09: Game port = 0x%02x 0x29: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x09), snd_sonicvibes_in(sonic, 0x29)); + dev_dbg(sonic->card->dev, + " 0x0a: left synth = 0x%02x 0x2a: MPU401 = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x0a), snd_sonicvibes_in(sonic, 0x2a)); + dev_dbg(sonic->card->dev, + " 0x0b: right synth = 0x%02x 0x2b: drive ctrl = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x0b), snd_sonicvibes_in(sonic, 0x2b)); + dev_dbg(sonic->card->dev, + " 0x0c: left AUX2 = 0x%02x 0x2c: SRS space = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x0c), snd_sonicvibes_in(sonic, 0x2c)); + dev_dbg(sonic->card->dev, + " 0x0d: right AUX2 = 0x%02x 0x2d: SRS center = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x0d), snd_sonicvibes_in(sonic, 0x2d)); + dev_dbg(sonic->card->dev, + " 0x0e: left analog = 0x%02x 0x2e: wave source = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x0e), snd_sonicvibes_in(sonic, 0x2e)); + dev_dbg(sonic->card->dev, + " 0x0f: right analog = 0x%02x 0x2f: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x0f), snd_sonicvibes_in(sonic, 0x2f)); + dev_dbg(sonic->card->dev, + " 0x10: left PCM = 0x%02x 0x30: analog power = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x10), snd_sonicvibes_in(sonic, 0x30)); + dev_dbg(sonic->card->dev, + " 0x11: right PCM = 0x%02x 0x31: analog power = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x11), snd_sonicvibes_in(sonic, 0x31)); + dev_dbg(sonic->card->dev, + " 0x12: DMA data format = 0x%02x 0x32: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x12), snd_sonicvibes_in(sonic, 0x32)); + dev_dbg(sonic->card->dev, + " 0x13: P/C enable = 0x%02x 0x33: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x13), snd_sonicvibes_in(sonic, 0x33)); + dev_dbg(sonic->card->dev, + " 0x14: U/D button = 0x%02x 0x34: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x14), snd_sonicvibes_in(sonic, 0x34)); + dev_dbg(sonic->card->dev, + " 0x15: revision = 0x%02x 0x35: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x15), snd_sonicvibes_in(sonic, 0x35)); + dev_dbg(sonic->card->dev, + " 0x16: ADC output ctrl = 0x%02x 0x36: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x16), snd_sonicvibes_in(sonic, 0x36)); + dev_dbg(sonic->card->dev, + " 0x17: --- = 0x%02x 0x37: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x17), snd_sonicvibes_in(sonic, 0x37)); + dev_dbg(sonic->card->dev, + " 0x18: DMA A upper cnt = 0x%02x 0x38: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x18), snd_sonicvibes_in(sonic, 0x38)); + dev_dbg(sonic->card->dev, + " 0x19: DMA A lower cnt = 0x%02x 0x39: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x19), snd_sonicvibes_in(sonic, 0x39)); + dev_dbg(sonic->card->dev, + " 0x1a: --- = 0x%02x 0x3a: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x1a), snd_sonicvibes_in(sonic, 0x3a)); + dev_dbg(sonic->card->dev, + " 0x1b: --- = 0x%02x 0x3b: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x1b), snd_sonicvibes_in(sonic, 0x3b)); + dev_dbg(sonic->card->dev, + " 0x1c: DMA C upper cnt = 0x%02x 0x3c: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x1c), snd_sonicvibes_in(sonic, 0x3c)); + dev_dbg(sonic->card->dev, + " 0x1d: DMA C upper cnt = 0x%02x 0x3d: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x1d), snd_sonicvibes_in(sonic, 0x3d)); + dev_dbg(sonic->card->dev, + " 0x1e: PCM rate low = 0x%02x 0x3e: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x1e), snd_sonicvibes_in(sonic, 0x3e)); + dev_dbg(sonic->card->dev, + " 0x1f: PCM rate high = 0x%02x 0x3f: --- = 0x%02x\n", + snd_sonicvibes_in(sonic, 0x1f), snd_sonicvibes_in(sonic, 0x3f)); +} + +#endif + +static void snd_sonicvibes_setfmt(struct sonicvibes * sonic, + unsigned char mask, + unsigned char value) +{ + guard(spinlock_irqsave)(&sonic->reg_lock); + outb(SV_MCE | SV_IREG_DMA_DATA_FMT, SV_REG(sonic, INDEX)); + if (mask) { + sonic->format = inb(SV_REG(sonic, DATA)); + udelay(10); + } + sonic->format = (sonic->format & mask) | value; + outb(sonic->format, SV_REG(sonic, DATA)); + udelay(10); + outb(0, SV_REG(sonic, INDEX)); + udelay(10); +} + +static void snd_sonicvibes_pll(unsigned int rate, + unsigned int *res_r, + unsigned int *res_m, + unsigned int *res_n) +{ + unsigned int r, m = 0, n = 0; + unsigned int xm, xn, xr, xd, metric = ~0U; + + if (rate < 625000 / SV_ADCMULT) + rate = 625000 / SV_ADCMULT; + if (rate > 150000000 / SV_ADCMULT) + rate = 150000000 / SV_ADCMULT; + /* slight violation of specs, needed for continuous sampling rates */ + for (r = 0; rate < 75000000 / SV_ADCMULT; r += 0x20, rate <<= 1); + for (xn = 3; xn < 33; xn++) /* 35 */ + for (xm = 3; xm < 257; xm++) { + xr = ((SV_REFFREQUENCY / SV_ADCMULT) * xm) / xn; + if (xr >= rate) + xd = xr - rate; + else + xd = rate - xr; + if (xd < metric) { + metric = xd; + m = xm - 2; + n = xn - 2; + } + } + *res_r = r; + *res_m = m; + *res_n = n; +#if 0 + dev_dbg(sonic->card->dev, + "metric = %i, xm = %i, xn = %i\n", metric, xm, xn); + dev_dbg(sonic->card->dev, + "pll: m = 0x%x, r = 0x%x, n = 0x%x\n", reg, m, r, n); +#endif +} + +static void snd_sonicvibes_setpll(struct sonicvibes * sonic, + unsigned char reg, + unsigned int rate) +{ + unsigned int r, m, n; + + snd_sonicvibes_pll(rate, &r, &m, &n); + if (sonic != NULL) { + guard(spinlock_irqsave)(&sonic->reg_lock); + snd_sonicvibes_out1(sonic, reg, m); + snd_sonicvibes_out1(sonic, reg + 1, r | n); + } +} + +static void snd_sonicvibes_set_adc_rate(struct sonicvibes * sonic, unsigned int rate) +{ + unsigned int div; + unsigned char clock; + + div = 48000 / rate; + if (div > 8) + div = 8; + if ((48000 / div) == rate) { /* use the alternate clock */ + clock = 0x10; + } else { /* use the PLL source */ + clock = 0x00; + snd_sonicvibes_setpll(sonic, SV_IREG_ADC_PLL, rate); + } + guard(spinlock_irqsave)(&sonic->reg_lock); + snd_sonicvibes_out1(sonic, SV_IREG_ADC_ALT_RATE, (div - 1) << 4); + snd_sonicvibes_out1(sonic, SV_IREG_ADC_CLOCK, clock); +} + +static int snd_sonicvibes_hw_constraint_dac_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned int rate, div, r, m, n; + + if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min == + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->max) { + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min; + div = 48000 / rate; + if (div > 8) + div = 8; + if ((48000 / div) == rate) { + params->rate_num = rate; + params->rate_den = 1; + } else { + snd_sonicvibes_pll(rate, &r, &m, &n); + snd_BUG_ON(SV_REFFREQUENCY % 16); + snd_BUG_ON(SV_ADCMULT % 512); + params->rate_num = (SV_REFFREQUENCY/16) * (n+2) * r; + params->rate_den = (SV_ADCMULT/512) * (m+2); + } + } + return 0; +} + +static void snd_sonicvibes_set_dac_rate(struct sonicvibes * sonic, unsigned int rate) +{ + unsigned int div; + + div = DIV_ROUND_CLOSEST(rate * 65536, SV_FULLRATE); + if (div > 65535) + div = 65535; + guard(spinlock_irqsave)(&sonic->reg_lock); + snd_sonicvibes_out1(sonic, SV_IREG_PCM_RATE_HIGH, div >> 8); + snd_sonicvibes_out1(sonic, SV_IREG_PCM_RATE_LOW, div); +} + +static int snd_sonicvibes_trigger(struct sonicvibes * sonic, int what, int cmd) +{ + guard(spinlock)(&sonic->reg_lock); + if (cmd == SNDRV_PCM_TRIGGER_START) { + if (!(sonic->enable & what)) { + sonic->enable |= what; + snd_sonicvibes_out1(sonic, SV_IREG_PC_ENABLE, sonic->enable); + } + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + if (sonic->enable & what) { + sonic->enable &= ~what; + snd_sonicvibes_out1(sonic, SV_IREG_PC_ENABLE, sonic->enable); + } + } else { + return -EINVAL; + } + return 0; +} + +static irqreturn_t snd_sonicvibes_interrupt(int irq, void *dev_id) +{ + struct sonicvibes *sonic = dev_id; + unsigned char status; + + status = inb(SV_REG(sonic, STATUS)); + if (!(status & (SV_DMAA_IRQ | SV_DMAC_IRQ | SV_MIDI_IRQ))) + return IRQ_NONE; + if (status == 0xff) { /* failure */ + outb(sonic->irqmask = ~0, SV_REG(sonic, IRQMASK)); + dev_err(sonic->card->dev, + "IRQ failure - interrupts disabled!!\n"); + return IRQ_HANDLED; + } + if (sonic->pcm) { + if (status & SV_DMAA_IRQ) + snd_pcm_period_elapsed(sonic->playback_substream); + if (status & SV_DMAC_IRQ) + snd_pcm_period_elapsed(sonic->capture_substream); + } + if (sonic->rmidi) { + if (status & SV_MIDI_IRQ) + snd_mpu401_uart_interrupt(irq, sonic->rmidi->private_data); + } + if (status & SV_UD_IRQ) { + unsigned char udreg; + int vol, oleft, oright, mleft, mright; + + scoped_guard(spinlock, &sonic->reg_lock) { + udreg = snd_sonicvibes_in1(sonic, SV_IREG_UD_BUTTON); + vol = udreg & 0x3f; + if (!(udreg & 0x40)) + vol = -vol; + oleft = mleft = snd_sonicvibes_in1(sonic, SV_IREG_LEFT_ANALOG); + oright = mright = snd_sonicvibes_in1(sonic, SV_IREG_RIGHT_ANALOG); + oleft &= 0x1f; + oright &= 0x1f; + oleft += vol; + if (oleft < 0) + oleft = 0; + if (oleft > 0x1f) + oleft = 0x1f; + oright += vol; + if (oright < 0) + oright = 0; + if (oright > 0x1f) + oright = 0x1f; + if (udreg & 0x80) { + mleft ^= 0x80; + mright ^= 0x80; + } + oleft |= mleft & 0x80; + oright |= mright & 0x80; + snd_sonicvibes_out1(sonic, SV_IREG_LEFT_ANALOG, oleft); + snd_sonicvibes_out1(sonic, SV_IREG_RIGHT_ANALOG, oright); + } + snd_ctl_notify(sonic->card, SNDRV_CTL_EVENT_MASK_VALUE, &sonic->master_mute->id); + snd_ctl_notify(sonic->card, SNDRV_CTL_EVENT_MASK_VALUE, &sonic->master_volume->id); + } + return IRQ_HANDLED; +} + +/* + * PCM part + */ + +static int snd_sonicvibes_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + return snd_sonicvibes_trigger(sonic, 1, cmd); +} + +static int snd_sonicvibes_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + return snd_sonicvibes_trigger(sonic, 2, cmd); +} + +static int snd_sonicvibes_playback_prepare(struct snd_pcm_substream *substream) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned char fmt = 0; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + sonic->p_dma_size = size; + count--; + if (runtime->channels > 1) + fmt |= 1; + if (snd_pcm_format_width(runtime->format) == 16) + fmt |= 2; + snd_sonicvibes_setfmt(sonic, ~3, fmt); + snd_sonicvibes_set_dac_rate(sonic, runtime->rate); + guard(spinlock_irq)(&sonic->reg_lock); + snd_sonicvibes_setdmaa(sonic, runtime->dma_addr, size); + snd_sonicvibes_out1(sonic, SV_IREG_DMA_A_UPPER, count >> 8); + snd_sonicvibes_out1(sonic, SV_IREG_DMA_A_LOWER, count); + return 0; +} + +static int snd_sonicvibes_capture_prepare(struct snd_pcm_substream *substream) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned char fmt = 0; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + sonic->c_dma_size = size; + count >>= 1; + count--; + if (runtime->channels > 1) + fmt |= 0x10; + if (snd_pcm_format_width(runtime->format) == 16) + fmt |= 0x20; + snd_sonicvibes_setfmt(sonic, ~0x30, fmt); + snd_sonicvibes_set_adc_rate(sonic, runtime->rate); + guard(spinlock_irq)(&sonic->reg_lock); + snd_sonicvibes_setdmac(sonic, runtime->dma_addr, size); + snd_sonicvibes_out1(sonic, SV_IREG_DMA_C_UPPER, count >> 8); + snd_sonicvibes_out1(sonic, SV_IREG_DMA_C_LOWER, count); + return 0; +} + +static snd_pcm_uframes_t snd_sonicvibes_playback_pointer(struct snd_pcm_substream *substream) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(sonic->enable & 1)) + return 0; + ptr = sonic->p_dma_size - snd_sonicvibes_getdmaa(sonic); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_sonicvibes_capture_pointer(struct snd_pcm_substream *substream) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + size_t ptr; + if (!(sonic->enable & 2)) + return 0; + ptr = sonic->c_dma_size - snd_sonicvibes_getdmac(sonic); + return bytes_to_frames(substream->runtime, ptr); +} + +static const struct snd_pcm_hardware snd_sonicvibes_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + 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 = (128*1024), + .period_bytes_min = 32, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_sonicvibes_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + 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 = (128*1024), + .period_bytes_min = 32, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_sonicvibes_playback_open(struct snd_pcm_substream *substream) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + sonic->mode |= SV_MODE_PLAY; + sonic->playback_substream = substream; + runtime->hw = snd_sonicvibes_playback; + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, snd_sonicvibes_hw_constraint_dac_rate, NULL, SNDRV_PCM_HW_PARAM_RATE, -1); + return 0; +} + +static int snd_sonicvibes_capture_open(struct snd_pcm_substream *substream) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + sonic->mode |= SV_MODE_CAPTURE; + sonic->capture_substream = substream; + runtime->hw = snd_sonicvibes_capture; + snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &snd_sonicvibes_hw_constraints_adc_clock); + return 0; +} + +static int snd_sonicvibes_playback_close(struct snd_pcm_substream *substream) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + + sonic->playback_substream = NULL; + sonic->mode &= ~SV_MODE_PLAY; + return 0; +} + +static int snd_sonicvibes_capture_close(struct snd_pcm_substream *substream) +{ + struct sonicvibes *sonic = snd_pcm_substream_chip(substream); + + sonic->capture_substream = NULL; + sonic->mode &= ~SV_MODE_CAPTURE; + return 0; +} + +static const struct snd_pcm_ops snd_sonicvibes_playback_ops = { + .open = snd_sonicvibes_playback_open, + .close = snd_sonicvibes_playback_close, + .prepare = snd_sonicvibes_playback_prepare, + .trigger = snd_sonicvibes_playback_trigger, + .pointer = snd_sonicvibes_playback_pointer, +}; + +static const struct snd_pcm_ops snd_sonicvibes_capture_ops = { + .open = snd_sonicvibes_capture_open, + .close = snd_sonicvibes_capture_close, + .prepare = snd_sonicvibes_capture_prepare, + .trigger = snd_sonicvibes_capture_trigger, + .pointer = snd_sonicvibes_capture_pointer, +}; + +static int snd_sonicvibes_pcm(struct sonicvibes *sonic, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(sonic->card, "s3_86c617", device, 1, 1, &pcm); + if (err < 0) + return err; + if (snd_BUG_ON(!pcm)) + return -EINVAL; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sonicvibes_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sonicvibes_capture_ops); + + pcm->private_data = sonic; + pcm->info_flags = 0; + strscpy(pcm->name, "S3 SonicVibes"); + sonic->pcm = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &sonic->pci->dev, 64*1024, 128*1024); + + return 0; +} + +/* + * Mixer part + */ + +#define SONICVIBES_MUX(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_sonicvibes_info_mux, \ + .get = snd_sonicvibes_get_mux, .put = snd_sonicvibes_put_mux } + +static int snd_sonicvibes_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[7] = { + "CD", "PCM", "Aux1", "Line", "Aux0", "Mic", "Mix" + }; + + return snd_ctl_enum_info(uinfo, 2, 7, texts); +} + +static int snd_sonicvibes_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&sonic->reg_lock); + ucontrol->value.enumerated.item[0] = ((snd_sonicvibes_in1(sonic, SV_IREG_LEFT_ADC) & SV_RECSRC_OUT) >> 5) - 1; + ucontrol->value.enumerated.item[1] = ((snd_sonicvibes_in1(sonic, SV_IREG_RIGHT_ADC) & SV_RECSRC_OUT) >> 5) - 1; + return 0; +} + +static int snd_sonicvibes_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol); + unsigned short left, right, oval1, oval2; + int change; + + if (ucontrol->value.enumerated.item[0] >= 7 || + ucontrol->value.enumerated.item[1] >= 7) + return -EINVAL; + left = (ucontrol->value.enumerated.item[0] + 1) << 5; + right = (ucontrol->value.enumerated.item[1] + 1) << 5; + guard(spinlock_irq)(&sonic->reg_lock); + oval1 = snd_sonicvibes_in1(sonic, SV_IREG_LEFT_ADC); + oval2 = snd_sonicvibes_in1(sonic, SV_IREG_RIGHT_ADC); + left = (oval1 & ~SV_RECSRC_OUT) | left; + right = (oval2 & ~SV_RECSRC_OUT) | right; + change = left != oval1 || right != oval2; + snd_sonicvibes_out1(sonic, SV_IREG_LEFT_ADC, left); + snd_sonicvibes_out1(sonic, SV_IREG_RIGHT_ADC, right); + return change; +} + +#define SONICVIBES_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_sonicvibes_info_single, \ + .get = snd_sonicvibes_get_single, .put = snd_sonicvibes_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_sonicvibes_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_sonicvibes_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct sonicvibes *sonic = 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) & 0xff; + + guard(spinlock_irq)(&sonic->reg_lock); + ucontrol->value.integer.value[0] = (snd_sonicvibes_in1(sonic, reg)>> shift) & mask; + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_sonicvibes_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct sonicvibes *sonic = 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) & 0xff; + int change; + unsigned short val, oval; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + guard(spinlock_irq)(&sonic->reg_lock); + oval = snd_sonicvibes_in1(sonic, reg); + val = (oval & ~(mask << shift)) | val; + change = val != oval; + snd_sonicvibes_out1(sonic, reg, val); + return change; +} + +#define SONICVIBES_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_sonicvibes_info_double, \ + .get = snd_sonicvibes_get_double, .put = snd_sonicvibes_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +static int snd_sonicvibes_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_sonicvibes_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct sonicvibes *sonic = 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; + + guard(spinlock_irq)(&sonic->reg_lock); + ucontrol->value.integer.value[0] = (snd_sonicvibes_in1(sonic, left_reg) >> shift_left) & mask; + ucontrol->value.integer.value[1] = (snd_sonicvibes_in1(sonic, right_reg) >> 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_sonicvibes_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct sonicvibes *sonic = 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 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; + guard(spinlock_irq)(&sonic->reg_lock); + oval1 = snd_sonicvibes_in1(sonic, left_reg); + oval2 = snd_sonicvibes_in1(sonic, right_reg); + val1 = (oval1 & ~(mask << shift_left)) | val1; + val2 = (oval2 & ~(mask << shift_right)) | val2; + change = val1 != oval1 || val2 != oval2; + snd_sonicvibes_out1(sonic, left_reg, val1); + snd_sonicvibes_out1(sonic, right_reg, val2); + return change; +} + +static const struct snd_kcontrol_new snd_sonicvibes_controls[] = { +SONICVIBES_DOUBLE("Capture Volume", 0, SV_IREG_LEFT_ADC, SV_IREG_RIGHT_ADC, 0, 0, 15, 0), +SONICVIBES_DOUBLE("Aux Playback Switch", 0, SV_IREG_LEFT_AUX1, SV_IREG_RIGHT_AUX1, 7, 7, 1, 1), +SONICVIBES_DOUBLE("Aux Playback Volume", 0, SV_IREG_LEFT_AUX1, SV_IREG_RIGHT_AUX1, 0, 0, 31, 1), +SONICVIBES_DOUBLE("CD Playback Switch", 0, SV_IREG_LEFT_CD, SV_IREG_RIGHT_CD, 7, 7, 1, 1), +SONICVIBES_DOUBLE("CD Playback Volume", 0, SV_IREG_LEFT_CD, SV_IREG_RIGHT_CD, 0, 0, 31, 1), +SONICVIBES_DOUBLE("Line Playback Switch", 0, SV_IREG_LEFT_LINE, SV_IREG_RIGHT_LINE, 7, 7, 1, 1), +SONICVIBES_DOUBLE("Line Playback Volume", 0, SV_IREG_LEFT_LINE, SV_IREG_RIGHT_LINE, 0, 0, 31, 1), +SONICVIBES_SINGLE("Mic Playback Switch", 0, SV_IREG_MIC, 7, 1, 1), +SONICVIBES_SINGLE("Mic Playback Volume", 0, SV_IREG_MIC, 0, 15, 1), +SONICVIBES_SINGLE("Mic Boost", 0, SV_IREG_LEFT_ADC, 4, 1, 0), +SONICVIBES_DOUBLE("Synth Playback Switch", 0, SV_IREG_LEFT_SYNTH, SV_IREG_RIGHT_SYNTH, 7, 7, 1, 1), +SONICVIBES_DOUBLE("Synth Playback Volume", 0, SV_IREG_LEFT_SYNTH, SV_IREG_RIGHT_SYNTH, 0, 0, 31, 1), +SONICVIBES_DOUBLE("Aux Playback Switch", 1, SV_IREG_LEFT_AUX2, SV_IREG_RIGHT_AUX2, 7, 7, 1, 1), +SONICVIBES_DOUBLE("Aux Playback Volume", 1, SV_IREG_LEFT_AUX2, SV_IREG_RIGHT_AUX2, 0, 0, 31, 1), +SONICVIBES_DOUBLE("Master Playback Switch", 0, SV_IREG_LEFT_ANALOG, SV_IREG_RIGHT_ANALOG, 7, 7, 1, 1), +SONICVIBES_DOUBLE("Master Playback Volume", 0, SV_IREG_LEFT_ANALOG, SV_IREG_RIGHT_ANALOG, 0, 0, 31, 1), +SONICVIBES_DOUBLE("PCM Playback Switch", 0, SV_IREG_LEFT_PCM, SV_IREG_RIGHT_PCM, 7, 7, 1, 1), +SONICVIBES_DOUBLE("PCM Playback Volume", 0, SV_IREG_LEFT_PCM, SV_IREG_RIGHT_PCM, 0, 0, 63, 1), +SONICVIBES_SINGLE("Loopback Capture Switch", 0, SV_IREG_ADC_OUTPUT_CTRL, 0, 1, 0), +SONICVIBES_SINGLE("Loopback Capture Volume", 0, SV_IREG_ADC_OUTPUT_CTRL, 2, 63, 1), +SONICVIBES_MUX("Capture Source", 0) +}; + +static void snd_sonicvibes_master_free(struct snd_kcontrol *kcontrol) +{ + struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol); + sonic->master_mute = NULL; + sonic->master_volume = NULL; +} + +static int snd_sonicvibes_mixer(struct sonicvibes *sonic) +{ + struct snd_card *card; + struct snd_kcontrol *kctl; + unsigned int idx; + int err; + + if (snd_BUG_ON(!sonic || !sonic->card)) + return -EINVAL; + card = sonic->card; + strscpy(card->mixername, "S3 SonicVibes"); + + for (idx = 0; idx < ARRAY_SIZE(snd_sonicvibes_controls); idx++) { + kctl = snd_ctl_new1(&snd_sonicvibes_controls[idx], sonic); + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + switch (idx) { + case 0: + case 1: kctl->private_free = snd_sonicvibes_master_free; break; + } + } + return 0; +} + +/* + + */ + +static void snd_sonicvibes_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct sonicvibes *sonic = entry->private_data; + unsigned char tmp; + + tmp = sonic->srs_space & 0x0f; + snd_iprintf(buffer, "SRS 3D : %s\n", + str_off_on(sonic->srs_space & 0x80)); + snd_iprintf(buffer, "SRS Space : %s\n", + tmp == 0x00 ? "100%" : + tmp == 0x01 ? "75%" : + tmp == 0x02 ? "50%" : + tmp == 0x03 ? "25%" : "0%"); + tmp = sonic->srs_center & 0x0f; + snd_iprintf(buffer, "SRS Center : %s\n", + tmp == 0x00 ? "100%" : + tmp == 0x01 ? "75%" : + tmp == 0x02 ? "50%" : + tmp == 0x03 ? "25%" : "0%"); + tmp = sonic->wave_source & 0x03; + snd_iprintf(buffer, "WaveTable Source : %s\n", + tmp == 0x00 ? "on-board ROM" : + tmp == 0x01 ? "PCI bus" : "on-board ROM + PCI bus"); + tmp = sonic->mpu_switch; + snd_iprintf(buffer, "Onboard synth : %s\n", str_on_off(tmp & 0x01)); + snd_iprintf(buffer, "Ext. Rx to synth : %s\n", str_on_off(tmp & 0x02)); + snd_iprintf(buffer, "MIDI to ext. Tx : %s\n", str_on_off(tmp & 0x04)); +} + +static void snd_sonicvibes_proc_init(struct sonicvibes *sonic) +{ + snd_card_ro_proc_new(sonic->card, "sonicvibes", sonic, + snd_sonicvibes_proc_read); +} + +/* + + */ + +#ifdef SUPPORT_JOYSTICK +static const struct snd_kcontrol_new snd_sonicvibes_game_control = +SONICVIBES_SINGLE("Joystick Speed", 0, SV_IREG_GAME_PORT, 1, 15, 0); + +static int snd_sonicvibes_create_gameport(struct sonicvibes *sonic) +{ + struct gameport *gp; + int err; + + sonic->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(sonic->card->dev, + "sonicvibes: cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "SonicVibes Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(sonic->pci)); + gameport_set_dev_parent(gp, &sonic->pci->dev); + gp->io = sonic->game_port; + + gameport_register_port(gp); + + err = snd_ctl_add(sonic->card, + snd_ctl_new1(&snd_sonicvibes_game_control, sonic)); + if (err < 0) + return err; + + return 0; +} + +static void snd_sonicvibes_free_gameport(struct sonicvibes *sonic) +{ + if (sonic->gameport) { + gameport_unregister_port(sonic->gameport); + sonic->gameport = NULL; + } +} +#else +static inline int snd_sonicvibes_create_gameport(struct sonicvibes *sonic) { return -ENOSYS; } +static inline void snd_sonicvibes_free_gameport(struct sonicvibes *sonic) { } +#endif + +static void snd_sonicvibes_free(struct snd_card *card) +{ + struct sonicvibes *sonic = card->private_data; + + snd_sonicvibes_free_gameport(sonic); + pci_write_config_dword(sonic->pci, 0x40, sonic->dmaa_port); + pci_write_config_dword(sonic->pci, 0x48, sonic->dmac_port); +} + +static int snd_sonicvibes_create(struct snd_card *card, + struct pci_dev *pci, + int reverb, + int mge) +{ + struct sonicvibes *sonic = card->private_data; + unsigned int dmaa, dmac; + int err; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + /* check, if we can restrict PCI DMA transfers to 24 bits */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(24))) { + dev_err(card->dev, + "architecture does not support 24bit PCI busmaster DMA\n"); + return -ENXIO; + } + + spin_lock_init(&sonic->reg_lock); + sonic->card = card; + sonic->pci = pci; + sonic->irq = -1; + + err = pcim_request_all_regions(pci, "S3 SonicVibes"); + if (err < 0) + return err; + + sonic->sb_port = pci_resource_start(pci, 0); + sonic->enh_port = pci_resource_start(pci, 1); + sonic->synth_port = pci_resource_start(pci, 2); + sonic->midi_port = pci_resource_start(pci, 3); + sonic->game_port = pci_resource_start(pci, 4); + + if (devm_request_irq(&pci->dev, pci->irq, snd_sonicvibes_interrupt, + IRQF_SHARED, KBUILD_MODNAME, sonic)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + sonic->irq = pci->irq; + card->sync_irq = sonic->irq; + card->private_free = snd_sonicvibes_free; + + pci_read_config_dword(pci, 0x40, &dmaa); + pci_read_config_dword(pci, 0x48, &dmac); + dmaio &= ~0x0f; + dmaa &= ~0x0f; + dmac &= ~0x0f; + if (!dmaa) { + dmaa = dmaio; + dmaio += 0x10; + dev_info(card->dev, + "BIOS did not allocate DDMA channel A i/o, allocated at 0x%x\n", + dmaa); + } + if (!dmac) { + dmac = dmaio; + dmaio += 0x10; + dev_info(card->dev, + "BIOS did not allocate DDMA channel C i/o, allocated at 0x%x\n", + dmac); + } + pci_write_config_dword(pci, 0x40, dmaa); + pci_write_config_dword(pci, 0x48, dmac); + + sonic->res_dmaa = devm_request_region(&pci->dev, dmaa, 0x10, + "S3 SonicVibes DDMA-A"); + if (!sonic->res_dmaa) { + dev_err(card->dev, + "unable to grab DDMA-A port at 0x%x-0x%x\n", + dmaa, dmaa + 0x10 - 1); + return -EBUSY; + } + sonic->res_dmac = devm_request_region(&pci->dev, dmac, 0x10, + "S3 SonicVibes DDMA-C"); + if (!sonic->res_dmac) { + dev_err(card->dev, + "unable to grab DDMA-C port at 0x%x-0x%x\n", + dmac, dmac + 0x10 - 1); + return -EBUSY; + } + + pci_read_config_dword(pci, 0x40, &sonic->dmaa_port); + pci_read_config_dword(pci, 0x48, &sonic->dmac_port); + sonic->dmaa_port &= ~0x0f; + sonic->dmac_port &= ~0x0f; + pci_write_config_dword(pci, 0x40, sonic->dmaa_port | 9); /* enable + enhanced */ + pci_write_config_dword(pci, 0x48, sonic->dmac_port | 9); /* enable */ + /* ok.. initialize S3 SonicVibes chip */ + outb(SV_RESET, SV_REG(sonic, CONTROL)); /* reset chip */ + udelay(100); + outb(0, SV_REG(sonic, CONTROL)); /* release reset */ + udelay(100); + outb(SV_ENHANCED | SV_INTA | (reverb ? SV_REVERB : 0), SV_REG(sonic, CONTROL)); + inb(SV_REG(sonic, STATUS)); /* clear IRQs */ +#if 1 + snd_sonicvibes_out(sonic, SV_IREG_DRIVE_CTRL, 0); /* drive current 16mA */ +#else + snd_sonicvibes_out(sonic, SV_IREG_DRIVE_CTRL, 0x40); /* drive current 8mA */ +#endif + snd_sonicvibes_out(sonic, SV_IREG_PC_ENABLE, sonic->enable = 0); /* disable playback & capture */ + outb(sonic->irqmask = ~(SV_DMAA_MASK | SV_DMAC_MASK | SV_UD_MASK), SV_REG(sonic, IRQMASK)); + inb(SV_REG(sonic, STATUS)); /* clear IRQs */ + snd_sonicvibes_out(sonic, SV_IREG_ADC_CLOCK, 0); /* use PLL as clock source */ + snd_sonicvibes_out(sonic, SV_IREG_ANALOG_POWER, 0); /* power up analog parts */ + snd_sonicvibes_out(sonic, SV_IREG_DIGITAL_POWER, 0); /* power up digital parts */ + snd_sonicvibes_setpll(sonic, SV_IREG_ADC_PLL, 8000); + snd_sonicvibes_out(sonic, SV_IREG_SRS_SPACE, sonic->srs_space = 0x80); /* SRS space off */ + snd_sonicvibes_out(sonic, SV_IREG_SRS_CENTER, sonic->srs_center = 0x00);/* SRS center off */ + snd_sonicvibes_out(sonic, SV_IREG_MPU401, sonic->mpu_switch = 0x05); /* MPU-401 switch */ + snd_sonicvibes_out(sonic, SV_IREG_WAVE_SOURCE, sonic->wave_source = 0x00); /* onboard ROM */ + snd_sonicvibes_out(sonic, SV_IREG_PCM_RATE_LOW, (8000 * 65536 / SV_FULLRATE) & 0xff); + snd_sonicvibes_out(sonic, SV_IREG_PCM_RATE_HIGH, ((8000 * 65536 / SV_FULLRATE) >> 8) & 0xff); + snd_sonicvibes_out(sonic, SV_IREG_LEFT_ADC, mge ? 0xd0 : 0xc0); + snd_sonicvibes_out(sonic, SV_IREG_RIGHT_ADC, 0xc0); + snd_sonicvibes_out(sonic, SV_IREG_LEFT_AUX1, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_RIGHT_AUX1, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_LEFT_CD, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_RIGHT_CD, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_LEFT_LINE, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_RIGHT_LINE, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_MIC, 0x8f); + snd_sonicvibes_out(sonic, SV_IREG_LEFT_SYNTH, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_RIGHT_SYNTH, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_LEFT_AUX2, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_RIGHT_AUX2, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_LEFT_ANALOG, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_RIGHT_ANALOG, 0x9f); + snd_sonicvibes_out(sonic, SV_IREG_LEFT_PCM, 0xbf); + snd_sonicvibes_out(sonic, SV_IREG_RIGHT_PCM, 0xbf); + snd_sonicvibes_out(sonic, SV_IREG_ADC_OUTPUT_CTRL, 0xfc); +#if 0 + snd_sonicvibes_debug(sonic); +#endif + sonic->revision = snd_sonicvibes_in(sonic, SV_IREG_REVISION); + + snd_sonicvibes_proc_init(sonic); + return 0; +} + +/* + * MIDI section + */ + +static const struct snd_kcontrol_new snd_sonicvibes_midi_controls[] = { +SONICVIBES_SINGLE("SonicVibes Wave Source RAM", 0, SV_IREG_WAVE_SOURCE, 0, 1, 0), +SONICVIBES_SINGLE("SonicVibes Wave Source RAM+ROM", 0, SV_IREG_WAVE_SOURCE, 1, 1, 0), +SONICVIBES_SINGLE("SonicVibes Onboard Synth", 0, SV_IREG_MPU401, 0, 1, 0), +SONICVIBES_SINGLE("SonicVibes External Rx to Synth", 0, SV_IREG_MPU401, 1, 1, 0), +SONICVIBES_SINGLE("SonicVibes External Tx", 0, SV_IREG_MPU401, 2, 1, 0) +}; + +static int snd_sonicvibes_midi_input_open(struct snd_mpu401 * mpu) +{ + struct sonicvibes *sonic = mpu->private_data; + outb(sonic->irqmask &= ~SV_MIDI_MASK, SV_REG(sonic, IRQMASK)); + return 0; +} + +static void snd_sonicvibes_midi_input_close(struct snd_mpu401 * mpu) +{ + struct sonicvibes *sonic = mpu->private_data; + outb(sonic->irqmask |= SV_MIDI_MASK, SV_REG(sonic, IRQMASK)); +} + +static int snd_sonicvibes_midi(struct sonicvibes *sonic, + struct snd_rawmidi *rmidi) +{ + struct snd_mpu401 * mpu = rmidi->private_data; + struct snd_card *card = sonic->card; + unsigned int idx; + int err; + + mpu->private_data = sonic; + mpu->open_input = snd_sonicvibes_midi_input_open; + mpu->close_input = snd_sonicvibes_midi_input_close; + for (idx = 0; idx < ARRAY_SIZE(snd_sonicvibes_midi_controls); idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_sonicvibes_midi_controls[idx], sonic)); + if (err < 0) + return err; + } + return 0; +} + +static int __snd_sonic_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct sonicvibes *sonic; + struct snd_rawmidi *midi_uart; + struct snd_opl3 *opl3; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*sonic), &card); + if (err < 0) + return err; + sonic = card->private_data; + err = snd_sonicvibes_create(card, pci, + reverb[dev] ? 1 : 0, + mge[dev] ? 1 : 0); + if (err < 0) + return err; + + strscpy(card->driver, "SonicVibes"); + strscpy(card->shortname, "S3 SonicVibes"); + sprintf(card->longname, "%s rev %i at 0x%llx, irq %i", + card->shortname, + sonic->revision, + (unsigned long long)pci_resource_start(pci, 1), + sonic->irq); + + err = snd_sonicvibes_pcm(sonic, 0); + if (err < 0) + return err; + err = snd_sonicvibes_mixer(sonic); + if (err < 0) + return err; + err = snd_mpu401_uart_new(card, 0, MPU401_HW_SONICVIBES, + sonic->midi_port, + MPU401_INFO_INTEGRATED | + MPU401_INFO_IRQ_HOOK, + -1, &midi_uart); + if (err < 0) + return err; + snd_sonicvibes_midi(sonic, midi_uart); + err = snd_opl3_create(card, sonic->synth_port, + sonic->synth_port + 2, + OPL3_HW_OPL3_SV, 1, &opl3); + if (err < 0) + return err; + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + return err; + + err = snd_sonicvibes_create_gameport(sonic); + if (err < 0) + return err; + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_sonic_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_sonic_probe(pci, pci_id)); +} + +static struct pci_driver sonicvibes_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_sonic_ids, + .probe = snd_sonic_probe, +}; + +module_pci_driver(sonicvibes_driver); diff --git a/sound/pci/trident/Makefile b/sound/pci/trident/Makefile new file mode 100644 index 000000000..476d16abc --- /dev/null +++ b/sound/pci/trident/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-trident-y := trident.o trident_main.o trident_memory.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_TRIDENT) += snd-trident.o diff --git a/sound/pci/trident/trident.c b/sound/pci/trident/trident.c new file mode 100644 index 000000000..8dcbd022c --- /dev/null +++ b/sound/pci/trident/trident.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Trident 4DWave DX/NX & SiS SI7018 Audio PCI soundcard + * + * Driver was originated by Trident + * Fri Feb 19 15:55:28 MST 1999 + */ + +#include +#include +#include +#include +#include +#include "trident.h" +#include + +MODULE_AUTHOR("Jaroslav Kysela , "); +MODULE_DESCRIPTION("Trident 4D-WaveDX/NX & SiS SI7018"); +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_PNP; /* Enable this card */ +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 32}; +static int wavetable_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8192}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Trident 4DWave PCI soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Trident 4DWave PCI soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Trident 4DWave PCI soundcard."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Number of hardware channels assigned for PCM."); +module_param_array(wavetable_size, int, NULL, 0444); +MODULE_PARM_DESC(wavetable_size, "Maximum memory size in kB for wavetable synth."); + +static const struct pci_device_id snd_trident_ids[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_DX), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00, + }, { + PCI_DEVICE(PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_NX), + }, { + PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_7018) + }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_trident_ids); + +static int snd_trident_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_trident *trident; + const char *str; + int err, pcm_dev = 0; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*trident), &card); + if (err < 0) + return err; + trident = card->private_data; + + err = snd_trident_create(card, pci, + pcm_channels[dev], + ((pci->vendor << 16) | pci->device) == TRIDENT_DEVICE_ID_SI7018 ? 1 : 2, + wavetable_size[dev]); + if (err < 0) + return err; + + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: + str = "TRID4DWAVEDX"; + break; + case TRIDENT_DEVICE_ID_NX: + str = "TRID4DWAVENX"; + break; + case TRIDENT_DEVICE_ID_SI7018: + str = "SI7018"; + break; + default: + str = "Unknown"; + } + strscpy(card->driver, str); + if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + strscpy(card->shortname, "SiS "); + } else { + strscpy(card->shortname, "Trident "); + } + strcat(card->shortname, str); + sprintf(card->longname, "%s PCI Audio at 0x%lx, irq %d", + card->shortname, trident->port, trident->irq); + + err = snd_trident_pcm(trident, pcm_dev++); + if (err < 0) + return err; + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: + case TRIDENT_DEVICE_ID_NX: + err = snd_trident_foldback_pcm(trident, pcm_dev++); + if (err < 0) + return err; + break; + } + if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) { + err = snd_trident_spdif_pcm(trident, pcm_dev++); + if (err < 0) + return err; + } + if (trident->device != TRIDENT_DEVICE_ID_SI7018) { + err = snd_mpu401_uart_new(card, 0, MPU401_HW_TRID4DWAVE, + trident->midi_port, + MPU401_INFO_INTEGRATED | + MPU401_INFO_IRQ_HOOK, + -1, &trident->rmidi); + if (err < 0) + return err; + } + + snd_trident_create_gameport(trident); + + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static struct pci_driver trident_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_trident_ids, + .probe = snd_trident_probe, +#ifdef CONFIG_PM_SLEEP + .driver = { + .pm = &snd_trident_pm, + }, +#endif +}; + +module_pci_driver(trident_driver); diff --git a/sound/pci/trident/trident.h b/sound/pci/trident/trident.h new file mode 100644 index 000000000..ed2d4eecc --- /dev/null +++ b/sound/pci/trident/trident.h @@ -0,0 +1,422 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_TRIDENT_H +#define __SOUND_TRIDENT_H + +/* + * audio@tridentmicro.com + * Fri Feb 19 15:55:28 MST 1999 + * Definitions for Trident 4DWave DX/NX chips + */ + +#include +#include +#include +#include + +#define TRIDENT_DEVICE_ID_DX ((PCI_VENDOR_ID_TRIDENT<<16)|PCI_DEVICE_ID_TRIDENT_4DWAVE_DX) +#define TRIDENT_DEVICE_ID_NX ((PCI_VENDOR_ID_TRIDENT<<16)|PCI_DEVICE_ID_TRIDENT_4DWAVE_NX) +#define TRIDENT_DEVICE_ID_SI7018 ((PCI_VENDOR_ID_SI<<16)|PCI_DEVICE_ID_SI_7018) + +#define SNDRV_TRIDENT_VOICE_TYPE_PCM 0 +#define SNDRV_TRIDENT_VOICE_TYPE_SYNTH 1 +#define SNDRV_TRIDENT_VOICE_TYPE_MIDI 2 + +#define SNDRV_TRIDENT_VFLG_RUNNING (1<<0) + +/* TLB code constants */ +#define SNDRV_TRIDENT_PAGE_SIZE 4096 +#define SNDRV_TRIDENT_PAGE_SHIFT 12 +#define SNDRV_TRIDENT_PAGE_MASK ((1<port + (x)) + +#define ID_4DWAVE_DX 0x2000 +#define ID_4DWAVE_NX 0x2001 + +/* Bank definitions */ + +#define T4D_BANK_A 0 +#define T4D_BANK_B 1 +#define T4D_NUM_BANKS 2 + +/* Register definitions */ + +/* Global registers */ + +enum global_control_bits { + CHANNEL_IDX = 0x0000003f, + OVERRUN_IE = 0x00000400, /* interrupt enable: capture overrun */ + UNDERRUN_IE = 0x00000800, /* interrupt enable: playback underrun */ + ENDLP_IE = 0x00001000, /* interrupt enable: end of buffer */ + MIDLP_IE = 0x00002000, /* interrupt enable: middle buffer */ + ETOG_IE = 0x00004000, /* interrupt enable: envelope toggling */ + EDROP_IE = 0x00008000, /* interrupt enable: envelope drop */ + BANK_B_EN = 0x00010000, /* SiS: enable bank B (64 channels) */ + PCMIN_B_MIX = 0x00020000, /* SiS: PCM IN B mixing enable */ + I2S_OUT_ASSIGN = 0x00040000, /* SiS: I2S Out contains surround PCM */ + SPDIF_OUT_ASSIGN= 0x00080000, /* SiS: 0=S/PDIF L/R | 1=PCM Out FIFO */ + MAIN_OUT_ASSIGN = 0x00100000, /* SiS: 0=PCM Out FIFO | 1=MMC Out buffer */ +}; + +enum miscint_bits { + PB_UNDERRUN_IRQ = 0x00000001, REC_OVERRUN_IRQ = 0x00000002, + SB_IRQ = 0x00000004, MPU401_IRQ = 0x00000008, + OPL3_IRQ = 0x00000010, ADDRESS_IRQ = 0x00000020, + ENVELOPE_IRQ = 0x00000040, PB_UNDERRUN = 0x00000100, + REC_OVERRUN = 0x00000200, MIXER_UNDERFLOW = 0x00000400, + MIXER_OVERFLOW = 0x00000800, NX_SB_IRQ_DISABLE = 0x00001000, + ST_TARGET_REACHED = 0x00008000, + PB_24K_MODE = 0x00010000, ST_IRQ_EN = 0x00800000, + ACGPIO_IRQ = 0x01000000 +}; + +/* T2 legacy dma control registers. */ +#define LEGACY_DMAR0 0x00 // ADR0 +#define LEGACY_DMAR4 0x04 // CNT0 +#define LEGACY_DMAR6 0x06 // CNT0 - High bits +#define LEGACY_DMAR11 0x0b // MOD +#define LEGACY_DMAR15 0x0f // MMR + +#define T4D_START_A 0x80 +#define T4D_STOP_A 0x84 +#define T4D_DLY_A 0x88 +#define T4D_SIGN_CSO_A 0x8c +#define T4D_CSPF_A 0x90 +#define T4D_CSPF_B 0xbc +#define T4D_CEBC_A 0x94 +#define T4D_AINT_A 0x98 +#define T4D_AINTEN_A 0x9c +#define T4D_LFO_GC_CIR 0xa0 +#define T4D_MUSICVOL_WAVEVOL 0xa8 +#define T4D_SBDELTA_DELTA_R 0xac +#define T4D_MISCINT 0xb0 +#define T4D_START_B 0xb4 +#define T4D_STOP_B 0xb8 +#define T4D_SBBL_SBCL 0xc0 +#define T4D_SBCTRL_SBE2R_SBDD 0xc4 +#define T4D_STIMER 0xc8 +#define T4D_AINT_B 0xd8 +#define T4D_AINTEN_B 0xdc +#define T4D_RCI 0x70 + +/* MPU-401 UART */ +#define T4D_MPU401_BASE 0x20 +#define T4D_MPUR0 0x20 +#define T4D_MPUR1 0x21 +#define T4D_MPUR2 0x22 +#define T4D_MPUR3 0x23 + +/* S/PDIF Registers */ +#define NX_SPCTRL_SPCSO 0x24 +#define NX_SPLBA 0x28 +#define NX_SPESO 0x2c +#define NX_SPCSTATUS 0x64 + +/* Joystick */ +#define GAMEPORT_GCR 0x30 +#define GAMEPORT_MODE_ADC 0x80 +#define GAMEPORT_LEGACY 0x31 +#define GAMEPORT_AXES 0x34 + +/* NX Specific Registers */ +#define NX_TLBC 0x6c + +/* Channel Registers */ + +#define CH_START 0xe0 + +#define CH_DX_CSO_ALPHA_FMS 0xe0 +#define CH_DX_ESO_DELTA 0xe8 +#define CH_DX_FMC_RVOL_CVOL 0xec + +#define CH_NX_DELTA_CSO 0xe0 +#define CH_NX_DELTA_ESO 0xe8 +#define CH_NX_ALPHA_FMS_FMC_RVOL_CVOL 0xec + +#define CH_LBA 0xe4 +#define CH_GVSEL_PAN_VOL_CTRL_EC 0xf0 +#define CH_EBUF1 0xf4 +#define CH_EBUF2 0xf8 + +/* AC-97 Registers */ + +#define DX_ACR0_AC97_W 0x40 +#define DX_ACR1_AC97_R 0x44 +#define DX_ACR2_AC97_COM_STAT 0x48 + +#define NX_ACR0_AC97_COM_STAT 0x40 +#define NX_ACR1_AC97_W 0x44 +#define NX_ACR2_AC97_R_PRIMARY 0x48 +#define NX_ACR3_AC97_R_SECONDARY 0x4c + +#define SI_AC97_WRITE 0x40 +#define SI_AC97_READ 0x44 +#define SI_SERIAL_INTF_CTRL 0x48 +#define SI_AC97_GPIO 0x4c +#define SI_ASR0 0x50 +#define SI_SPDIF_CS 0x70 +#define SI_GPIO 0x7c + +enum trident_nx_ac97_bits { + /* ACR1-3 */ + NX_AC97_BUSY_WRITE = 0x0800, + NX_AC97_BUSY_READ = 0x0800, + NX_AC97_BUSY_DATA = 0x0400, + NX_AC97_WRITE_SECONDARY = 0x0100, + /* ACR0 */ + NX_AC97_SECONDARY_READY = 0x0040, + NX_AC97_SECONDARY_RECORD = 0x0020, + NX_AC97_SURROUND_OUTPUT = 0x0010, + NX_AC97_PRIMARY_READY = 0x0008, + NX_AC97_PRIMARY_RECORD = 0x0004, + NX_AC97_PCM_OUTPUT = 0x0002, + NX_AC97_WARM_RESET = 0x0001 +}; + +enum trident_dx_ac97_bits { + DX_AC97_BUSY_WRITE = 0x8000, + DX_AC97_BUSY_READ = 0x8000, + DX_AC97_READY = 0x0010, + DX_AC97_RECORD = 0x0008, + DX_AC97_PLAYBACK = 0x0002 +}; + +enum sis7018_ac97_bits { + SI_AC97_BUSY_WRITE = 0x00008000, + SI_AC97_AUDIO_BUSY = 0x00004000, + SI_AC97_MODEM_BUSY = 0x00002000, + SI_AC97_BUSY_READ = 0x00008000, + SI_AC97_SECONDARY = 0x00000080, +}; + +enum serial_intf_ctrl_bits { + WARM_RESET = 0x00000001, + COLD_RESET = 0x00000002, + I2S_CLOCK = 0x00000004, + PCM_SEC_AC97 = 0x00000008, + AC97_DBL_RATE = 0x00000010, + SPDIF_EN = 0x00000020, + I2S_OUTPUT_EN = 0x00000040, + I2S_INPUT_EN = 0x00000080, + PCMIN = 0x00000100, + LINE1IN = 0x00000200, + MICIN = 0x00000400, + LINE2IN = 0x00000800, + HEAD_SET_IN = 0x00001000, + GPIOIN = 0x00002000, + /* 7018 spec says id = 01 but the demo board routed to 10 + SECONDARY_ID= 0x00004000, */ + SECONDARY_ID = 0x00004000, + PCMOUT = 0x00010000, + SURROUT = 0x00020000, + CENTEROUT = 0x00040000, + LFEOUT = 0x00080000, + LINE1OUT = 0x00100000, + LINE2OUT = 0x00200000, + GPIOOUT = 0x00400000, + SI_AC97_PRIMARY_READY = 0x01000000, + SI_AC97_SECONDARY_READY = 0x02000000, + SI_AC97_POWERDOWN = 0x04000000, +}; + +/* PCM defaults */ + +#define T4D_DEFAULT_PCM_VOL 10 /* 0 - 255 */ +#define T4D_DEFAULT_PCM_PAN 0 /* 0 - 127 */ +#define T4D_DEFAULT_PCM_RVOL 127 /* 0 - 127 */ +#define T4D_DEFAULT_PCM_CVOL 127 /* 0 - 127 */ + +struct snd_trident; +struct snd_trident_voice; +struct snd_trident_pcm_mixer; + +struct snd_trident_port { + struct snd_midi_channel_set * chset; + struct snd_trident * trident; + int mode; /* operation mode */ + int client; /* sequencer client number */ + int port; /* sequencer port number */ + unsigned int midi_has_voices: 1; +}; + +struct snd_trident_memblk_arg { + short first_page, last_page; +}; + +struct snd_trident_tlb { + __le32 *entries; /* 16k-aligned TLB table */ + dma_addr_t entries_dmaaddr; /* 16k-aligned PCI address to TLB table */ + struct snd_dma_buffer *buffer; + struct snd_util_memhdr * memhdr; /* page allocation list */ + struct snd_dma_buffer *silent_page; +}; + +struct snd_trident_voice { + unsigned int number; + unsigned int use: 1, + pcm: 1, + synth:1, + midi: 1; + unsigned int flags; + unsigned char client; + unsigned char port; + unsigned char index; + + struct snd_trident_sample_ops *sample_ops; + + /* channel parameters */ + unsigned int CSO; /* 24 bits (16 on DX) */ + unsigned int ESO; /* 24 bits (16 on DX) */ + unsigned int LBA; /* 30 bits */ + unsigned short EC; /* 12 bits */ + unsigned short Alpha; /* 12 bits */ + unsigned short Delta; /* 16 bits */ + unsigned short Attribute; /* 16 bits - SiS 7018 */ + unsigned short Vol; /* 12 bits (6.6) */ + unsigned char Pan; /* 7 bits (1.4.2) */ + unsigned char GVSel; /* 1 bit */ + unsigned char RVol; /* 7 bits (5.2) */ + unsigned char CVol; /* 7 bits (5.2) */ + unsigned char FMC; /* 2 bits */ + unsigned char CTRL; /* 4 bits */ + unsigned char FMS; /* 4 bits */ + unsigned char LFO; /* 8 bits */ + + unsigned int negCSO; /* nonzero - use negative CSO */ + + struct snd_util_memblk *memblk; /* memory block if TLB enabled */ + + /* PCM data */ + + struct snd_trident *trident; + struct snd_pcm_substream *substream; + struct snd_trident_voice *extra; /* extra PCM voice (acts as interrupt generator) */ + unsigned int running: 1, + capture: 1, + spdif: 1, + foldback: 1, + isync: 1, + isync2: 1, + isync3: 1; + int foldback_chan; /* foldback subdevice number */ + unsigned int stimer; /* global sample timer (to detect spurious interrupts) */ + unsigned int spurious_threshold; /* spurious threshold */ + unsigned int isync_mark; + unsigned int isync_max; + unsigned int isync_ESO; + + /* --- */ + + void *private_data; + void (*private_free)(struct snd_trident_voice *voice); +}; + +struct snd_4dwave { + int seq_client; + + struct snd_trident_port seq_ports[4]; + struct snd_trident_voice voices[64]; + + int ChanSynthCount; /* number of allocated synth channels */ + int max_size; /* maximum synth memory size in bytes */ + int current_size; /* current allocated synth mem in bytes */ +}; + +struct snd_trident_pcm_mixer { + struct snd_trident_voice *voice; /* active voice */ + unsigned short vol; /* front volume */ + unsigned char pan; /* pan control */ + unsigned char rvol; /* rear volume */ + unsigned char cvol; /* center volume */ + unsigned char pad; +}; + +struct snd_trident { + int irq; + + unsigned int device; /* device ID */ + + unsigned char bDMAStart; + + unsigned long port; + unsigned long midi_port; + + unsigned int spurious_irq_count; + unsigned int spurious_irq_max_delta; + + struct snd_trident_tlb tlb; /* TLB entries for NX cards */ + + unsigned char spdif_ctrl; + unsigned char spdif_pcm_ctrl; + unsigned int spdif_bits; + unsigned int spdif_pcm_bits; + struct snd_kcontrol *spdif_pcm_ctl; /* S/PDIF settings */ + unsigned int ac97_ctrl; + + unsigned int ChanMap[2]; /* allocation map for hardware channels */ + + int ChanPCM; /* max number of PCM channels */ + int ChanPCMcnt; /* actual number of PCM channels */ + + unsigned int ac97_detect: 1; /* 1 = AC97 in detection phase */ + unsigned int in_suspend: 1; /* 1 during suspend/resume */ + + struct snd_4dwave synth; /* synth specific variables */ + + spinlock_t event_lock; + spinlock_t voice_alloc; + + struct snd_dma_device dma_dev; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; /* ADC/DAC PCM */ + struct snd_pcm *foldback; /* Foldback PCM */ + struct snd_pcm *spdif; /* SPDIF PCM */ + struct snd_rawmidi *rmidi; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + struct snd_ac97 *ac97_sec; + + unsigned int musicvol_wavevol; + struct snd_trident_pcm_mixer pcm_mixer[32]; + struct snd_kcontrol *ctl_vol; /* front volume */ + struct snd_kcontrol *ctl_pan; /* pan */ + struct snd_kcontrol *ctl_rvol; /* rear volume */ + struct snd_kcontrol *ctl_cvol; /* center volume */ + + spinlock_t reg_lock; + + struct gameport *gameport; +}; + +int snd_trident_create(struct snd_card *card, + struct pci_dev *pci, + int pcm_streams, + int pcm_spdif_device, + int max_wavetable_size); +int snd_trident_create_gameport(struct snd_trident *trident); + +int snd_trident_pcm(struct snd_trident *trident, int device); +int snd_trident_foldback_pcm(struct snd_trident *trident, int device); +int snd_trident_spdif_pcm(struct snd_trident *trident, int device); +struct snd_trident_voice *snd_trident_alloc_voice(struct snd_trident * trident, int type, + int client, int port); +void snd_trident_free_voice(struct snd_trident * trident, struct snd_trident_voice *voice); +void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice); +void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice); +void snd_trident_write_voice_regs(struct snd_trident * trident, struct snd_trident_voice *voice); +extern const struct dev_pm_ops snd_trident_pm; + +/* TLB memory allocation */ +struct snd_util_memblk *snd_trident_alloc_pages(struct snd_trident *trident, + struct snd_pcm_substream *substream); +int snd_trident_free_pages(struct snd_trident *trident, struct snd_util_memblk *blk); + +#endif /* __SOUND_TRIDENT_H */ diff --git a/sound/pci/trident/trident_main.c b/sound/pci/trident/trident_main.c new file mode 100644 index 000000000..fa8d7f271 --- /dev/null +++ b/sound/pci/trident/trident_main.c @@ -0,0 +1,3841 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Maintained by Jaroslav Kysela + * Originated by audio@tridentmicro.com + * Fri Feb 19 15:55:28 MST 1999 + * Routines for control of Trident 4DWave (DX and NX) chip + * + * BUGS: + * + * TODO: + * --- + * + * SiS7018 S/PDIF support by Thomas Winischhofer + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "trident.h" +#include + +static int snd_trident_pcm_mixer_build(struct snd_trident *trident, + struct snd_trident_voice * voice, + struct snd_pcm_substream *substream); +static int snd_trident_pcm_mixer_free(struct snd_trident *trident, + struct snd_trident_voice * voice, + struct snd_pcm_substream *substream); +static irqreturn_t snd_trident_interrupt(int irq, void *dev_id); +static int snd_trident_sis_reset(struct snd_trident *trident); + +static void snd_trident_clear_voices(struct snd_trident * trident, + unsigned short v_min, unsigned short v_max); +static void snd_trident_free(struct snd_card *card); + +/* + * common I/O routines + */ + + +#if 0 +static void snd_trident_print_voice_regs(struct snd_trident *trident, int voice) +{ + unsigned int val, tmp; + + dev_dbg(trident->card->dev, "Trident voice %i:\n", voice); + outb(voice, TRID_REG(trident, T4D_LFO_GC_CIR)); + val = inl(TRID_REG(trident, CH_LBA)); + dev_dbg(trident->card->dev, "LBA: 0x%x\n", val); + val = inl(TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC)); + dev_dbg(trident->card->dev, "GVSel: %i\n", val >> 31); + dev_dbg(trident->card->dev, "Pan: 0x%x\n", (val >> 24) & 0x7f); + dev_dbg(trident->card->dev, "Vol: 0x%x\n", (val >> 16) & 0xff); + dev_dbg(trident->card->dev, "CTRL: 0x%x\n", (val >> 12) & 0x0f); + dev_dbg(trident->card->dev, "EC: 0x%x\n", val & 0x0fff); + if (trident->device != TRIDENT_DEVICE_ID_NX) { + val = inl(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS)); + dev_dbg(trident->card->dev, "CSO: 0x%x\n", val >> 16); + dev_dbg(trident->card->dev, "Alpha: 0x%x\n", (val >> 4) & 0x0fff); + dev_dbg(trident->card->dev, "FMS: 0x%x\n", val & 0x0f); + val = inl(TRID_REG(trident, CH_DX_ESO_DELTA)); + dev_dbg(trident->card->dev, "ESO: 0x%x\n", val >> 16); + dev_dbg(trident->card->dev, "Delta: 0x%x\n", val & 0xffff); + val = inl(TRID_REG(trident, CH_DX_FMC_RVOL_CVOL)); + } else { // TRIDENT_DEVICE_ID_NX + val = inl(TRID_REG(trident, CH_NX_DELTA_CSO)); + tmp = (val >> 24) & 0xff; + dev_dbg(trident->card->dev, "CSO: 0x%x\n", val & 0x00ffffff); + val = inl(TRID_REG(trident, CH_NX_DELTA_ESO)); + tmp |= (val >> 16) & 0xff00; + dev_dbg(trident->card->dev, "Delta: 0x%x\n", tmp); + dev_dbg(trident->card->dev, "ESO: 0x%x\n", val & 0x00ffffff); + val = inl(TRID_REG(trident, CH_NX_ALPHA_FMS_FMC_RVOL_CVOL)); + dev_dbg(trident->card->dev, "Alpha: 0x%x\n", val >> 20); + dev_dbg(trident->card->dev, "FMS: 0x%x\n", (val >> 16) & 0x0f); + } + dev_dbg(trident->card->dev, "FMC: 0x%x\n", (val >> 14) & 3); + dev_dbg(trident->card->dev, "RVol: 0x%x\n", (val >> 7) & 0x7f); + dev_dbg(trident->card->dev, "CVol: 0x%x\n", val & 0x7f); +} +#endif + +/*--------------------------------------------------------------------------- + unsigned short snd_trident_codec_read(struct snd_ac97 *ac97, unsigned short reg) + + Description: This routine will do all of the reading from the external + CODEC (AC97). + + Parameters: ac97 - ac97 codec structure + reg - CODEC register index, from AC97 Hal. + + returns: 16 bit value read from the AC97. + + ---------------------------------------------------------------------------*/ +static unsigned short snd_trident_codec_read(struct snd_ac97 *ac97, unsigned short reg) +{ + unsigned int data = 0, treg; + unsigned short count = 0xffff; + struct snd_trident *trident = ac97->private_data; + + guard(spinlock_irqsave)(&trident->reg_lock); + if (trident->device == TRIDENT_DEVICE_ID_DX) { + data = (DX_AC97_BUSY_READ | (reg & 0x000000ff)); + outl(data, TRID_REG(trident, DX_ACR1_AC97_R)); + do { + data = inl(TRID_REG(trident, DX_ACR1_AC97_R)); + if ((data & DX_AC97_BUSY_READ) == 0) + break; + } while (--count); + } else if (trident->device == TRIDENT_DEVICE_ID_NX) { + data = (NX_AC97_BUSY_READ | (reg & 0x000000ff)); + treg = ac97->num == 0 ? NX_ACR2_AC97_R_PRIMARY : NX_ACR3_AC97_R_SECONDARY; + outl(data, TRID_REG(trident, treg)); + do { + data = inl(TRID_REG(trident, treg)); + if ((data & 0x00000C00) == 0) + break; + } while (--count); + } else if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + data = SI_AC97_BUSY_READ | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff); + if (ac97->num == 1) + data |= SI_AC97_SECONDARY; + outl(data, TRID_REG(trident, SI_AC97_READ)); + do { + data = inl(TRID_REG(trident, SI_AC97_READ)); + if ((data & (SI_AC97_BUSY_READ)) == 0) + break; + } while (--count); + } + + if (count == 0 && !trident->ac97_detect) { + dev_err(trident->card->dev, + "ac97 codec read TIMEOUT [0x%x/0x%x]!!!\n", + reg, data); + data = 0; + } + + return ((unsigned short) (data >> 16)); +} + +/*--------------------------------------------------------------------------- + void snd_trident_codec_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short wdata) + + Description: This routine will do all of the writing to the external + CODEC (AC97). + + Parameters: ac97 - ac97 codec structure + reg - CODEC register index, from AC97 Hal. + data - Lower 16 bits are the data to write to CODEC. + + returns: TRUE if everything went ok, else FALSE. + + ---------------------------------------------------------------------------*/ +static void snd_trident_codec_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short wdata) +{ + unsigned int address, data; + unsigned short count = 0xffff; + struct snd_trident *trident = ac97->private_data; + + data = ((unsigned long) wdata) << 16; + + guard(spinlock_irqsave)(&trident->reg_lock); + if (trident->device == TRIDENT_DEVICE_ID_DX) { + address = DX_ACR0_AC97_W; + + /* read AC-97 write register status */ + do { + if ((inw(TRID_REG(trident, address)) & DX_AC97_BUSY_WRITE) == 0) + break; + } while (--count); + + data |= (DX_AC97_BUSY_WRITE | (reg & 0x000000ff)); + } else if (trident->device == TRIDENT_DEVICE_ID_NX) { + address = NX_ACR1_AC97_W; + + /* read AC-97 write register status */ + do { + if ((inw(TRID_REG(trident, address)) & NX_AC97_BUSY_WRITE) == 0) + break; + } while (--count); + + data |= (NX_AC97_BUSY_WRITE | (ac97->num << 8) | (reg & 0x000000ff)); + } else if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + address = SI_AC97_WRITE; + + /* read AC-97 write register status */ + do { + if ((inw(TRID_REG(trident, address)) & (SI_AC97_BUSY_WRITE)) == 0) + break; + } while (--count); + + data |= SI_AC97_BUSY_WRITE | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff); + if (ac97->num == 1) + data |= SI_AC97_SECONDARY; + } else { + address = 0; /* keep GCC happy */ + count = 0; /* return */ + } + + if (count == 0) + return; + outl(data, TRID_REG(trident, address)); +} + +/*--------------------------------------------------------------------------- + void snd_trident_enable_eso(struct snd_trident *trident) + + Description: This routine will enable end of loop interrupts. + End of loop interrupts will occur when a running + channel reaches ESO. + Also enables middle of loop interrupts. + + Parameters: trident - pointer to target device class for 4DWave. + + ---------------------------------------------------------------------------*/ + +static void snd_trident_enable_eso(struct snd_trident * trident) +{ + unsigned int val; + + val = inl(TRID_REG(trident, T4D_LFO_GC_CIR)); + val |= ENDLP_IE; + val |= MIDLP_IE; + if (trident->device == TRIDENT_DEVICE_ID_SI7018) + val |= BANK_B_EN; + outl(val, TRID_REG(trident, T4D_LFO_GC_CIR)); +} + +/*--------------------------------------------------------------------------- + void snd_trident_disable_eso(struct snd_trident *trident) + + Description: This routine will disable end of loop interrupts. + End of loop interrupts will occur when a running + channel reaches ESO. + Also disables middle of loop interrupts. + + Parameters: + trident - pointer to target device class for 4DWave. + + returns: TRUE if everything went ok, else FALSE. + + ---------------------------------------------------------------------------*/ + +static void snd_trident_disable_eso(struct snd_trident * trident) +{ + unsigned int tmp; + + tmp = inl(TRID_REG(trident, T4D_LFO_GC_CIR)); + tmp &= ~ENDLP_IE; + tmp &= ~MIDLP_IE; + outl(tmp, TRID_REG(trident, T4D_LFO_GC_CIR)); +} + +/*--------------------------------------------------------------------------- + void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice) + + Description: Start a voice, any channel 0 thru 63. + This routine automatically handles the fact that there are + more than 32 channels available. + + Parameters : voice - Voice number 0 thru n. + trident - pointer to target device class for 4DWave. + + Return Value: None. + + ---------------------------------------------------------------------------*/ + +void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice) +{ + unsigned int mask = 1 << (voice & 0x1f); + unsigned int reg = (voice & 0x20) ? T4D_START_B : T4D_START_A; + + outl(mask, TRID_REG(trident, reg)); +} + +EXPORT_SYMBOL(snd_trident_start_voice); + +/*--------------------------------------------------------------------------- + void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice) + + Description: Stop a voice, any channel 0 thru 63. + This routine automatically handles the fact that there are + more than 32 channels available. + + Parameters : voice - Voice number 0 thru n. + trident - pointer to target device class for 4DWave. + + Return Value: None. + + ---------------------------------------------------------------------------*/ + +void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice) +{ + unsigned int mask = 1 << (voice & 0x1f); + unsigned int reg = (voice & 0x20) ? T4D_STOP_B : T4D_STOP_A; + + outl(mask, TRID_REG(trident, reg)); +} + +EXPORT_SYMBOL(snd_trident_stop_voice); + +/*--------------------------------------------------------------------------- + int snd_trident_allocate_pcm_channel(struct snd_trident *trident) + + Description: Allocate hardware channel in Bank B (32-63). + + Parameters : trident - pointer to target device class for 4DWave. + + Return Value: hardware channel - 32-63 or -1 when no channel is available + + ---------------------------------------------------------------------------*/ + +static int snd_trident_allocate_pcm_channel(struct snd_trident * trident) +{ + int idx; + + if (trident->ChanPCMcnt >= trident->ChanPCM) + return -1; + for (idx = 31; idx >= 0; idx--) { + if (!(trident->ChanMap[T4D_BANK_B] & (1 << idx))) { + trident->ChanMap[T4D_BANK_B] |= 1 << idx; + trident->ChanPCMcnt++; + return idx + 32; + } + } + return -1; +} + +/*--------------------------------------------------------------------------- + void snd_trident_free_pcm_channel(int channel) + + Description: Free hardware channel in Bank B (32-63) + + Parameters : trident - pointer to target device class for 4DWave. + channel - hardware channel number 0-63 + + Return Value: none + + ---------------------------------------------------------------------------*/ + +static void snd_trident_free_pcm_channel(struct snd_trident *trident, int channel) +{ + if (channel < 32 || channel > 63) + return; + channel &= 0x1f; + if (trident->ChanMap[T4D_BANK_B] & (1 << channel)) { + trident->ChanMap[T4D_BANK_B] &= ~(1 << channel); + trident->ChanPCMcnt--; + } +} + +/*--------------------------------------------------------------------------- + unsigned int snd_trident_allocate_synth_channel(void) + + Description: Allocate hardware channel in Bank A (0-31). + + Parameters : trident - pointer to target device class for 4DWave. + + Return Value: hardware channel - 0-31 or -1 when no channel is available + + ---------------------------------------------------------------------------*/ + +static int snd_trident_allocate_synth_channel(struct snd_trident * trident) +{ + int idx; + + for (idx = 31; idx >= 0; idx--) { + if (!(trident->ChanMap[T4D_BANK_A] & (1 << idx))) { + trident->ChanMap[T4D_BANK_A] |= 1 << idx; + trident->synth.ChanSynthCount++; + return idx; + } + } + return -1; +} + +/*--------------------------------------------------------------------------- + void snd_trident_free_synth_channel( int channel ) + + Description: Free hardware channel in Bank B (0-31). + + Parameters : trident - pointer to target device class for 4DWave. + channel - hardware channel number 0-63 + + Return Value: none + + ---------------------------------------------------------------------------*/ + +static void snd_trident_free_synth_channel(struct snd_trident *trident, int channel) +{ + if (channel < 0 || channel > 31) + return; + channel &= 0x1f; + if (trident->ChanMap[T4D_BANK_A] & (1 << channel)) { + trident->ChanMap[T4D_BANK_A] &= ~(1 << channel); + trident->synth.ChanSynthCount--; + } +} + +/*--------------------------------------------------------------------------- + snd_trident_write_voice_regs + + Description: This routine will complete and write the 5 hardware channel + registers to hardware. + + Parameters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + Each register field. + + ---------------------------------------------------------------------------*/ + +void snd_trident_write_voice_regs(struct snd_trident * trident, + struct snd_trident_voice * voice) +{ + unsigned int FmcRvolCvol; + unsigned int regs[5]; + + regs[1] = voice->LBA; + regs[4] = (voice->GVSel << 31) | + ((voice->Pan & 0x0000007f) << 24) | + ((voice->CTRL & 0x0000000f) << 12); + FmcRvolCvol = ((voice->FMC & 3) << 14) | + ((voice->RVol & 0x7f) << 7) | + (voice->CVol & 0x7f); + + switch (trident->device) { + case TRIDENT_DEVICE_ID_SI7018: + regs[4] |= voice->number > 31 ? + (voice->Vol & 0x000003ff) : + ((voice->Vol & 0x00003fc) << (16-2)) | + (voice->EC & 0x00000fff); + regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) | + (voice->FMS & 0x0000000f); + regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff); + regs[3] = (voice->Attribute << 16) | FmcRvolCvol; + break; + case TRIDENT_DEVICE_ID_DX: + regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) | + (voice->EC & 0x00000fff); + regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) | + (voice->FMS & 0x0000000f); + regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff); + regs[3] = FmcRvolCvol; + break; + case TRIDENT_DEVICE_ID_NX: + regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) | + (voice->EC & 0x00000fff); + regs[0] = (voice->Delta << 24) | (voice->CSO & 0x00ffffff); + regs[2] = ((voice->Delta << 16) & 0xff000000) | + (voice->ESO & 0x00ffffff); + regs[3] = (voice->Alpha << 20) | + ((voice->FMS & 0x0000000f) << 16) | FmcRvolCvol; + break; + default: + snd_BUG(); + return; + } + + outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + outl(regs[0], TRID_REG(trident, CH_START + 0)); + outl(regs[1], TRID_REG(trident, CH_START + 4)); + outl(regs[2], TRID_REG(trident, CH_START + 8)); + outl(regs[3], TRID_REG(trident, CH_START + 12)); + outl(regs[4], TRID_REG(trident, CH_START + 16)); + +#if 0 + dev_dbg(trident->card->dev, "written %i channel:\n", voice->number); + dev_dbg(trident->card->dev, " regs[0] = 0x%x/0x%x\n", + regs[0], inl(TRID_REG(trident, CH_START + 0))); + dev_dbg(trident->card->dev, " regs[1] = 0x%x/0x%x\n", + regs[1], inl(TRID_REG(trident, CH_START + 4))); + dev_dbg(trident->card->dev, " regs[2] = 0x%x/0x%x\n", + regs[2], inl(TRID_REG(trident, CH_START + 8))); + dev_dbg(trident->card->dev, " regs[3] = 0x%x/0x%x\n", + regs[3], inl(TRID_REG(trident, CH_START + 12))); + dev_dbg(trident->card->dev, " regs[4] = 0x%x/0x%x\n", + regs[4], inl(TRID_REG(trident, CH_START + 16))); +#endif +} + +EXPORT_SYMBOL(snd_trident_write_voice_regs); + +/*--------------------------------------------------------------------------- + snd_trident_write_cso_reg + + Description: This routine will write the new CSO offset + register to hardware. + + Parameters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + CSO - new CSO value + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_cso_reg(struct snd_trident * trident, + struct snd_trident_voice * voice, + unsigned int CSO) +{ + voice->CSO = CSO; + outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + if (trident->device != TRIDENT_DEVICE_ID_NX) { + outw(voice->CSO, TRID_REG(trident, CH_DX_CSO_ALPHA_FMS) + 2); + } else { + outl((voice->Delta << 24) | + (voice->CSO & 0x00ffffff), TRID_REG(trident, CH_NX_DELTA_CSO)); + } +} + +/*--------------------------------------------------------------------------- + snd_trident_write_eso_reg + + Description: This routine will write the new ESO offset + register to hardware. + + Parameters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + ESO - new ESO value + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_eso_reg(struct snd_trident * trident, + struct snd_trident_voice * voice, + unsigned int ESO) +{ + voice->ESO = ESO; + outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + if (trident->device != TRIDENT_DEVICE_ID_NX) { + outw(voice->ESO, TRID_REG(trident, CH_DX_ESO_DELTA) + 2); + } else { + outl(((voice->Delta << 16) & 0xff000000) | (voice->ESO & 0x00ffffff), + TRID_REG(trident, CH_NX_DELTA_ESO)); + } +} + +/*--------------------------------------------------------------------------- + snd_trident_write_vol_reg + + Description: This routine will write the new voice volume + register to hardware. + + Parameters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + Vol - new voice volume + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_vol_reg(struct snd_trident * trident, + struct snd_trident_voice * voice, + unsigned int Vol) +{ + voice->Vol = Vol; + outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: + case TRIDENT_DEVICE_ID_NX: + outb(voice->Vol >> 2, TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 2)); + break; + case TRIDENT_DEVICE_ID_SI7018: + /* dev_dbg(trident->card->dev, "voice->Vol = 0x%x\n", voice->Vol); */ + outw((voice->CTRL << 12) | voice->Vol, + TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC)); + break; + } +} + +/*--------------------------------------------------------------------------- + snd_trident_write_pan_reg + + Description: This routine will write the new voice pan + register to hardware. + + Parameters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + Pan - new pan value + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_pan_reg(struct snd_trident * trident, + struct snd_trident_voice * voice, + unsigned int Pan) +{ + voice->Pan = Pan; + outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + outb(((voice->GVSel & 0x01) << 7) | (voice->Pan & 0x7f), + TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 3)); +} + +/*--------------------------------------------------------------------------- + snd_trident_write_rvol_reg + + Description: This routine will write the new reverb volume + register to hardware. + + Parameters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + RVol - new reverb volume + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_rvol_reg(struct snd_trident * trident, + struct snd_trident_voice * voice, + unsigned int RVol) +{ + voice->RVol = RVol; + outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) | + (voice->CVol & 0x007f), + TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ? + CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL)); +} + +/*--------------------------------------------------------------------------- + snd_trident_write_cvol_reg + + Description: This routine will write the new chorus volume + register to hardware. + + Parameters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + CVol - new chorus volume + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_cvol_reg(struct snd_trident * trident, + struct snd_trident_voice * voice, + unsigned int CVol) +{ + voice->CVol = CVol; + outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) | + (voice->CVol & 0x007f), + TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ? + CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL)); +} + +/*--------------------------------------------------------------------------- + snd_trident_convert_rate + + Description: This routine converts rate in HZ to hardware delta value. + + Parameters: trident - pointer to target device class for 4DWave. + rate - Real or Virtual channel number. + + Returns: Delta value. + + ---------------------------------------------------------------------------*/ +static unsigned int snd_trident_convert_rate(unsigned int rate) +{ + unsigned int delta; + + // We special case 44100 and 8000 since rounding with the equation + // does not give us an accurate enough value. For 11025 and 22050 + // the equation gives us the best answer. All other frequencies will + // also use the equation. JDW + if (rate == 44100) + delta = 0xeb3; + else if (rate == 8000) + delta = 0x2ab; + else if (rate == 48000) + delta = 0x1000; + else + delta = DIV_ROUND_CLOSEST(rate << 12, 48000) & 0x0000ffff; + return delta; +} + +/*--------------------------------------------------------------------------- + snd_trident_convert_adc_rate + + Description: This routine converts rate in HZ to hardware delta value. + + Parameters: trident - pointer to target device class for 4DWave. + rate - Real or Virtual channel number. + + Returns: Delta value. + + ---------------------------------------------------------------------------*/ +static unsigned int snd_trident_convert_adc_rate(unsigned int rate) +{ + unsigned int delta; + + // We special case 44100 and 8000 since rounding with the equation + // does not give us an accurate enough value. For 11025 and 22050 + // the equation gives us the best answer. All other frequencies will + // also use the equation. JDW + if (rate == 44100) + delta = 0x116a; + else if (rate == 8000) + delta = 0x6000; + else if (rate == 48000) + delta = 0x1000; + else + delta = ((48000 << 12) / rate) & 0x0000ffff; + return delta; +} + +/*--------------------------------------------------------------------------- + snd_trident_spurious_threshold + + Description: This routine converts rate in HZ to spurious threshold. + + Parameters: trident - pointer to target device class for 4DWave. + rate - Real or Virtual channel number. + + Returns: Delta value. + + ---------------------------------------------------------------------------*/ +static unsigned int snd_trident_spurious_threshold(unsigned int rate, + unsigned int period_size) +{ + unsigned int res = (rate * period_size) / 48000; + if (res < 64) + res = res / 2; + else + res -= 32; + return res; +} + +/*--------------------------------------------------------------------------- + snd_trident_control_mode + + Description: This routine returns a control mode for a PCM channel. + + Parameters: trident - pointer to target device class for 4DWave. + substream - PCM substream + + Returns: Control value. + + ---------------------------------------------------------------------------*/ +static unsigned int snd_trident_control_mode(struct snd_pcm_substream *substream) +{ + unsigned int CTRL; + struct snd_pcm_runtime *runtime = substream->runtime; + + /* set ctrl mode + CTRL default: 8-bit (unsigned) mono, loop mode enabled + */ + CTRL = 0x00000001; + if (snd_pcm_format_width(runtime->format) == 16) + CTRL |= 0x00000008; // 16-bit data + if (snd_pcm_format_signed(runtime->format)) + CTRL |= 0x00000002; // signed data + if (runtime->channels > 1) + CTRL |= 0x00000004; // stereo data + return CTRL; +} + +/* + * PCM part + */ + +/*--------------------------------------------------------------------------- + snd_trident_allocate_pcm_mem + + Description: Allocate PCM ring buffer for given substream + + Parameters: substream - PCM substream class + hw_params - hardware parameters + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_allocate_pcm_mem(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + + if (trident->tlb.entries) { + if (runtime->buffer_changed) { + if (voice->memblk) + snd_trident_free_pages(trident, voice->memblk); + voice->memblk = snd_trident_alloc_pages(trident, substream); + if (voice->memblk == NULL) + return -ENOMEM; + } + } + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_allocate_evoice + + Description: Allocate extra voice as interrupt generator + + Parameters: substream - PCM substream class + hw_params - hardware parameters + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_allocate_evoice(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + struct snd_trident_voice *evoice = voice->extra; + + /* voice management */ + + if (params_buffer_size(hw_params) / 2 != params_period_size(hw_params)) { + if (evoice == NULL) { + evoice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0); + if (evoice == NULL) + return -ENOMEM; + voice->extra = evoice; + evoice->substream = substream; + } + } else { + if (evoice != NULL) { + snd_trident_free_voice(trident, evoice); + voice->extra = evoice = NULL; + } + } + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_hw_params + + Description: Set the hardware parameters for the playback device. + + Parameters: substream - PCM substream class + hw_params - hardware parameters + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int err; + + err = snd_trident_allocate_pcm_mem(substream, hw_params); + if (err >= 0) + err = snd_trident_allocate_evoice(substream, hw_params); + return err; +} + +/*--------------------------------------------------------------------------- + snd_trident_playback_hw_free + + Description: Release the hardware resources for the playback device. + + Parameters: substream - PCM substream class + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + struct snd_trident_voice *evoice = voice ? voice->extra : NULL; + + if (trident->tlb.entries) { + if (voice && voice->memblk) { + snd_trident_free_pages(trident, voice->memblk); + voice->memblk = NULL; + } + } + if (evoice != NULL) { + snd_trident_free_voice(trident, evoice); + voice->extra = NULL; + } + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_playback_prepare + + Description: Prepare playback device for playback. + + Parameters: substream - PCM substream class + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + struct snd_trident_voice *evoice = voice->extra; + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[substream->number]; + + guard(spinlock_irq)(&trident->reg_lock); + + /* set delta (rate) value */ + voice->Delta = snd_trident_convert_rate(runtime->rate); + voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size); + + /* set Loop Begin Address */ + if (voice->memblk) + voice->LBA = voice->memblk->offset; + else + voice->LBA = runtime->dma_addr; + + voice->CSO = 0; + voice->ESO = runtime->buffer_size - 1; /* in samples */ + voice->CTRL = snd_trident_control_mode(substream); + voice->FMC = 3; + voice->GVSel = 1; + voice->EC = 0; + voice->Alpha = 0; + voice->FMS = 0; + voice->Vol = mix->vol; + voice->RVol = mix->rvol; + voice->CVol = mix->cvol; + voice->Pan = mix->pan; + voice->Attribute = 0; +#if 0 + voice->Attribute = (1<<(30-16))|(2<<(26-16))| + (0<<(24-16))|(0x1f<<(19-16)); +#else + voice->Attribute = 0; +#endif + + snd_trident_write_voice_regs(trident, voice); + + if (evoice != NULL) { + evoice->Delta = voice->Delta; + evoice->spurious_threshold = voice->spurious_threshold; + evoice->LBA = voice->LBA; + evoice->CSO = 0; + evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */ + evoice->CTRL = voice->CTRL; + evoice->FMC = 3; + evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1; + evoice->EC = 0; + evoice->Alpha = 0; + evoice->FMS = 0; + evoice->Vol = 0x3ff; /* mute */ + evoice->RVol = evoice->CVol = 0x7f; /* mute */ + evoice->Pan = 0x7f; /* mute */ +#if 0 + evoice->Attribute = (1<<(30-16))|(2<<(26-16))| + (0<<(24-16))|(0x1f<<(19-16)); +#else + evoice->Attribute = 0; +#endif + snd_trident_write_voice_regs(trident, evoice); + evoice->isync2 = 1; + evoice->isync_mark = runtime->period_size; + evoice->ESO = (runtime->period_size * 2) - 1; + } + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_capture_hw_params + + Description: Set the hardware parameters for the capture device. + + Parameters: substream - PCM substream class + hw_params - hardware parameters + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_trident_allocate_pcm_mem(substream, hw_params); +} + +/*--------------------------------------------------------------------------- + snd_trident_capture_prepare + + Description: Prepare capture device for playback. + + Parameters: substream - PCM substream class + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + unsigned int val, ESO_bytes; + + guard(spinlock_irq)(&trident->reg_lock); + + // Initialize the channel and set channel Mode + outb(0, TRID_REG(trident, LEGACY_DMAR15)); + + // Set DMA channel operation mode register + outb(0x54, TRID_REG(trident, LEGACY_DMAR11)); + + // Set channel buffer Address, DMAR0 expects contiguous PCI memory area + voice->LBA = runtime->dma_addr; + outl(voice->LBA, TRID_REG(trident, LEGACY_DMAR0)); + if (voice->memblk) + voice->LBA = voice->memblk->offset; + + // set ESO + ESO_bytes = snd_pcm_lib_buffer_bytes(substream) - 1; + outb((ESO_bytes & 0x00ff0000) >> 16, TRID_REG(trident, LEGACY_DMAR6)); + outw((ESO_bytes & 0x0000ffff), TRID_REG(trident, LEGACY_DMAR4)); + ESO_bytes++; + + // Set channel sample rate, 4.12 format + val = DIV_ROUND_CLOSEST(48000U << 12, runtime->rate); + outw(val, TRID_REG(trident, T4D_SBDELTA_DELTA_R)); + + // Set channel interrupt blk length + if (snd_pcm_format_width(runtime->format) == 16) { + val = (unsigned short) ((ESO_bytes >> 1) - 1); + } else { + val = (unsigned short) (ESO_bytes - 1); + } + + outl((val << 16) | val, TRID_REG(trident, T4D_SBBL_SBCL)); + + // Right now, set format and start to run captureing, + // continuous run loop enable. + trident->bDMAStart = 0x19; // 0001 1001b + + if (snd_pcm_format_width(runtime->format) == 16) + trident->bDMAStart |= 0x80; + if (snd_pcm_format_signed(runtime->format)) + trident->bDMAStart |= 0x20; + if (runtime->channels > 1) + trident->bDMAStart |= 0x40; + + // Prepare capture intr channel + + voice->Delta = snd_trident_convert_rate(runtime->rate); + voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size); + voice->isync = 1; + voice->isync_mark = runtime->period_size; + voice->isync_max = runtime->buffer_size; + + // Set voice parameters + voice->CSO = 0; + voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1; + voice->CTRL = snd_trident_control_mode(substream); + voice->FMC = 3; + voice->RVol = 0x7f; + voice->CVol = 0x7f; + voice->GVSel = 1; + voice->Pan = 0x7f; /* mute */ + voice->Vol = 0x3ff; /* mute */ + voice->EC = 0; + voice->Alpha = 0; + voice->FMS = 0; + voice->Attribute = 0; + + snd_trident_write_voice_regs(trident, voice); + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_si7018_capture_hw_params + + Description: Set the hardware parameters for the capture device. + + Parameters: substream - PCM substream class + hw_params - hardware parameters + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_si7018_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_trident_allocate_evoice(substream, hw_params); +} + +/*--------------------------------------------------------------------------- + snd_trident_si7018_capture_hw_free + + Description: Release the hardware resources for the capture device. + + Parameters: substream - PCM substream class + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_si7018_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + struct snd_trident_voice *evoice = voice ? voice->extra : NULL; + + if (evoice != NULL) { + snd_trident_free_voice(trident, evoice); + voice->extra = NULL; + } + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_si7018_capture_prepare + + Description: Prepare capture device for playback. + + Parameters: substream - PCM substream class + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_si7018_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + struct snd_trident_voice *evoice = voice->extra; + + guard(spinlock_irq)(&trident->reg_lock); + + voice->LBA = runtime->dma_addr; + voice->Delta = snd_trident_convert_adc_rate(runtime->rate); + voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size); + + // Set voice parameters + voice->CSO = 0; + voice->ESO = runtime->buffer_size - 1; /* in samples */ + voice->CTRL = snd_trident_control_mode(substream); + voice->FMC = 0; + voice->RVol = 0; + voice->CVol = 0; + voice->GVSel = 1; + voice->Pan = T4D_DEFAULT_PCM_PAN; + voice->Vol = 0; + voice->EC = 0; + voice->Alpha = 0; + voice->FMS = 0; + + voice->Attribute = (2 << (30-16)) | + (2 << (26-16)) | + (2 << (24-16)) | + (1 << (23-16)); + + snd_trident_write_voice_regs(trident, voice); + + if (evoice != NULL) { + evoice->Delta = snd_trident_convert_rate(runtime->rate); + evoice->spurious_threshold = voice->spurious_threshold; + evoice->LBA = voice->LBA; + evoice->CSO = 0; + evoice->ESO = (runtime->period_size * 2) + 20 - 1; /* in samples, 20 means correction */ + evoice->CTRL = voice->CTRL; + evoice->FMC = 3; + evoice->GVSel = 0; + evoice->EC = 0; + evoice->Alpha = 0; + evoice->FMS = 0; + evoice->Vol = 0x3ff; /* mute */ + evoice->RVol = evoice->CVol = 0x7f; /* mute */ + evoice->Pan = 0x7f; /* mute */ + evoice->Attribute = 0; + snd_trident_write_voice_regs(trident, evoice); + evoice->isync2 = 1; + evoice->isync_mark = runtime->period_size; + evoice->ESO = (runtime->period_size * 2) - 1; + } + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_foldback_prepare + + Description: Prepare foldback capture device for playback. + + Parameters: substream - PCM substream class + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_foldback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + struct snd_trident_voice *evoice = voice->extra; + + guard(spinlock_irq)(&trident->reg_lock); + + /* Set channel buffer Address */ + if (voice->memblk) + voice->LBA = voice->memblk->offset; + else + voice->LBA = runtime->dma_addr; + + /* set target ESO for channel */ + voice->ESO = runtime->buffer_size - 1; /* in samples */ + + /* set sample rate */ + voice->Delta = 0x1000; + voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size); + + voice->CSO = 0; + voice->CTRL = snd_trident_control_mode(substream); + voice->FMC = 3; + voice->RVol = 0x7f; + voice->CVol = 0x7f; + voice->GVSel = 1; + voice->Pan = 0x7f; /* mute */ + voice->Vol = 0x3ff; /* mute */ + voice->EC = 0; + voice->Alpha = 0; + voice->FMS = 0; + voice->Attribute = 0; + + /* set up capture channel */ + outb(((voice->number & 0x3f) | 0x80), TRID_REG(trident, T4D_RCI + voice->foldback_chan)); + + snd_trident_write_voice_regs(trident, voice); + + if (evoice != NULL) { + evoice->Delta = voice->Delta; + evoice->spurious_threshold = voice->spurious_threshold; + evoice->LBA = voice->LBA; + evoice->CSO = 0; + evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */ + evoice->CTRL = voice->CTRL; + evoice->FMC = 3; + evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1; + evoice->EC = 0; + evoice->Alpha = 0; + evoice->FMS = 0; + evoice->Vol = 0x3ff; /* mute */ + evoice->RVol = evoice->CVol = 0x7f; /* mute */ + evoice->Pan = 0x7f; /* mute */ + evoice->Attribute = 0; + snd_trident_write_voice_regs(trident, evoice); + evoice->isync2 = 1; + evoice->isync_mark = runtime->period_size; + evoice->ESO = (runtime->period_size * 2) - 1; + } + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_spdif_hw_params + + Description: Set the hardware parameters for the spdif device. + + Parameters: substream - PCM substream class + hw_params - hardware parameters + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + unsigned int old_bits = 0, change = 0; + int err; + + err = snd_trident_allocate_pcm_mem(substream, hw_params); + if (err < 0) + return err; + + if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + err = snd_trident_allocate_evoice(substream, hw_params); + if (err < 0) + return err; + } + + /* prepare SPDIF channel */ + scoped_guard(spinlock_irq, &trident->reg_lock) { + old_bits = trident->spdif_pcm_bits; + if (old_bits & IEC958_AES0_PROFESSIONAL) + trident->spdif_pcm_bits &= ~IEC958_AES0_PRO_FS; + else + trident->spdif_pcm_bits &= ~(IEC958_AES3_CON_FS << 24); + if (params_rate(hw_params) >= 48000) { + trident->spdif_pcm_ctrl = 0x3c; // 48000 Hz + trident->spdif_pcm_bits |= + trident->spdif_bits & IEC958_AES0_PROFESSIONAL ? + IEC958_AES0_PRO_FS_48000 : + (IEC958_AES3_CON_FS_48000 << 24); + } else if (params_rate(hw_params) >= 44100) { + trident->spdif_pcm_ctrl = 0x3e; // 44100 Hz + trident->spdif_pcm_bits |= + trident->spdif_bits & IEC958_AES0_PROFESSIONAL ? + IEC958_AES0_PRO_FS_44100 : + (IEC958_AES3_CON_FS_44100 << 24); + } else { + trident->spdif_pcm_ctrl = 0x3d; // 32000 Hz + trident->spdif_pcm_bits |= + trident->spdif_bits & IEC958_AES0_PROFESSIONAL ? + IEC958_AES0_PRO_FS_32000 : + (IEC958_AES3_CON_FS_32000 << 24); + } + change = old_bits != trident->spdif_pcm_bits; + } + + if (change) + snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE, &trident->spdif_pcm_ctl->id); + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_spdif_prepare + + Description: Prepare SPDIF device for playback. + + Parameters: substream - PCM substream class + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_spdif_prepare(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + struct snd_trident_voice *evoice = voice->extra; + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[substream->number]; + unsigned int RESO, LBAO; + unsigned int temp; + + guard(spinlock_irq)(&trident->reg_lock); + + if (trident->device != TRIDENT_DEVICE_ID_SI7018) { + + /* set delta (rate) value */ + voice->Delta = snd_trident_convert_rate(runtime->rate); + voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size); + + /* set Loop Back Address */ + LBAO = runtime->dma_addr; + if (voice->memblk) + voice->LBA = voice->memblk->offset; + else + voice->LBA = LBAO; + + voice->isync = 1; + voice->isync3 = 1; + voice->isync_mark = runtime->period_size; + voice->isync_max = runtime->buffer_size; + + /* set target ESO for channel */ + RESO = runtime->buffer_size - 1; + voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1; + + /* set ctrl mode */ + voice->CTRL = snd_trident_control_mode(substream); + + voice->FMC = 3; + voice->RVol = 0x7f; + voice->CVol = 0x7f; + voice->GVSel = 1; + voice->Pan = 0x7f; + voice->Vol = 0x3ff; + voice->EC = 0; + voice->CSO = 0; + voice->Alpha = 0; + voice->FMS = 0; + voice->Attribute = 0; + + /* prepare surrogate IRQ channel */ + snd_trident_write_voice_regs(trident, voice); + + outw((RESO & 0xffff), TRID_REG(trident, NX_SPESO)); + outb((RESO >> 16), TRID_REG(trident, NX_SPESO + 2)); + outl((LBAO & 0xfffffffc), TRID_REG(trident, NX_SPLBA)); + outw((voice->CSO & 0xffff), TRID_REG(trident, NX_SPCTRL_SPCSO)); + outb((voice->CSO >> 16), TRID_REG(trident, NX_SPCTRL_SPCSO + 2)); + + /* set SPDIF setting */ + outb(trident->spdif_pcm_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3)); + outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS)); + + } else { /* SiS */ + + /* set delta (rate) value */ + voice->Delta = 0x800; + voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size); + + /* set Loop Begin Address */ + if (voice->memblk) + voice->LBA = voice->memblk->offset; + else + voice->LBA = runtime->dma_addr; + + voice->CSO = 0; + voice->ESO = runtime->buffer_size - 1; /* in samples */ + voice->CTRL = snd_trident_control_mode(substream); + voice->FMC = 3; + voice->GVSel = 1; + voice->EC = 0; + voice->Alpha = 0; + voice->FMS = 0; + voice->Vol = mix->vol; + voice->RVol = mix->rvol; + voice->CVol = mix->cvol; + voice->Pan = mix->pan; + voice->Attribute = (1<<(30-16))|(7<<(26-16))| + (0<<(24-16))|(0<<(19-16)); + + snd_trident_write_voice_regs(trident, voice); + + if (evoice != NULL) { + evoice->Delta = voice->Delta; + evoice->spurious_threshold = voice->spurious_threshold; + evoice->LBA = voice->LBA; + evoice->CSO = 0; + evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */ + evoice->CTRL = voice->CTRL; + evoice->FMC = 3; + evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1; + evoice->EC = 0; + evoice->Alpha = 0; + evoice->FMS = 0; + evoice->Vol = 0x3ff; /* mute */ + evoice->RVol = evoice->CVol = 0x7f; /* mute */ + evoice->Pan = 0x7f; /* mute */ + evoice->Attribute = 0; + snd_trident_write_voice_regs(trident, evoice); + evoice->isync2 = 1; + evoice->isync_mark = runtime->period_size; + evoice->ESO = (runtime->period_size * 2) - 1; + } + + outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS)); + temp = inl(TRID_REG(trident, T4D_LFO_GC_CIR)); + temp &= ~(1<<19); + outl(temp, TRID_REG(trident, T4D_LFO_GC_CIR)); + temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)); + temp |= SPDIF_EN; + outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL)); + } + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_trigger + + Description: Start/stop devices + + Parameters: substream - PCM substream class + cmd - trigger command (STOP, GO) + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_trigger(struct snd_pcm_substream *substream, + int cmd) + +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + unsigned int what, whati, capture_flag, spdif_flag; + struct snd_trident_voice *voice, *evoice; + unsigned int val, go; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + go = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + go = 0; + break; + default: + return -EINVAL; + } + what = whati = capture_flag = spdif_flag = 0; + guard(spinlock)(&trident->reg_lock); + val = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff; + snd_pcm_group_for_each_entry(s, substream) { + if ((struct snd_trident *) snd_pcm_substream_chip(s) == trident) { + voice = s->runtime->private_data; + evoice = voice->extra; + what |= 1 << (voice->number & 0x1f); + if (evoice == NULL) { + whati |= 1 << (voice->number & 0x1f); + } else { + what |= 1 << (evoice->number & 0x1f); + whati |= 1 << (evoice->number & 0x1f); + if (go) + evoice->stimer = val; + } + if (go) { + voice->running = 1; + voice->stimer = val; + } else { + voice->running = 0; + } + snd_pcm_trigger_done(s, substream); + if (voice->capture) + capture_flag = 1; + if (voice->spdif) + spdif_flag = 1; + } + } + if (spdif_flag) { + if (trident->device != TRIDENT_DEVICE_ID_SI7018) { + outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS)); + val = trident->spdif_pcm_ctrl; + if (!go) + val &= ~(0x28); + outb(val, TRID_REG(trident, NX_SPCTRL_SPCSO + 3)); + } else { + outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS)); + val = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) | SPDIF_EN; + outl(val, TRID_REG(trident, SI_SERIAL_INTF_CTRL)); + } + } + if (!go) + outl(what, TRID_REG(trident, T4D_STOP_B)); + val = inl(TRID_REG(trident, T4D_AINTEN_B)); + if (go) { + val |= whati; + } else { + val &= ~whati; + } + outl(val, TRID_REG(trident, T4D_AINTEN_B)); + if (go) { + outl(what, TRID_REG(trident, T4D_START_B)); + + if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018) + outb(trident->bDMAStart, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD)); + } else { + if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018) + outb(0x00, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD)); + } + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_playback_pointer + + Description: This routine return the playback position + + Parameters: substream - PCM substream class + + Returns: position of buffer + + ---------------------------------------------------------------------------*/ + +static snd_pcm_uframes_t snd_trident_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + unsigned int cso; + + if (!voice->running) + return 0; + + guard(spinlock)(&trident->reg_lock); + + outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + + if (trident->device != TRIDENT_DEVICE_ID_NX) { + cso = inw(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2)); + } else { // ID_4DWAVE_NX + cso = (unsigned int) inl(TRID_REG(trident, CH_NX_DELTA_CSO)) & 0x00ffffff; + } + + if (cso >= runtime->buffer_size) + cso = 0; + + return cso; +} + +/*--------------------------------------------------------------------------- + snd_trident_capture_pointer + + Description: This routine return the capture position + + Parameters: pcm1 - PCM device class + + Returns: position of buffer + + ---------------------------------------------------------------------------*/ + +static snd_pcm_uframes_t snd_trident_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + unsigned int result; + + if (!voice->running) + return 0; + + result = inw(TRID_REG(trident, T4D_SBBL_SBCL)); + if (runtime->channels > 1) + result >>= 1; + if (result > 0) + result = runtime->buffer_size - result; + + return result; +} + +/*--------------------------------------------------------------------------- + snd_trident_spdif_pointer + + Description: This routine return the SPDIF playback position + + Parameters: substream - PCM substream class + + Returns: position of buffer + + ---------------------------------------------------------------------------*/ + +static snd_pcm_uframes_t snd_trident_spdif_pointer(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + unsigned int result; + + if (!voice->running) + return 0; + + result = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff; + + return result; +} + +/* + * Playback support device description + */ + +static const struct snd_pcm_hardware snd_trident_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S8 | 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 = (256*1024), + .period_bytes_min = 64, + .period_bytes_max = (256*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + * Capture support device description + */ + +static const struct snd_pcm_hardware snd_trident_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S8 | 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 = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + * Foldback capture support device description + */ + +static const struct snd_pcm_hardware snd_trident_foldback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .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, +}; + +/* + * SPDIF playback support device description + */ + +static const struct snd_pcm_hardware snd_trident_spdif = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .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_trident_spdif_7018 = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .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_trident_pcm_free_substream(struct snd_pcm_runtime *runtime) +{ + struct snd_trident_voice *voice = runtime->private_data; + struct snd_trident *trident; + + if (voice) { + trident = voice->trident; + snd_trident_free_voice(trident, voice); + } +} + +static int snd_trident_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice; + + voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0); + if (voice == NULL) + return -EAGAIN; + snd_trident_pcm_mixer_build(trident, voice, substream); + voice->substream = substream; + runtime->private_data = voice; + runtime->private_free = snd_trident_pcm_free_substream; + runtime->hw = snd_trident_playback; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024); + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_playback_close + + Description: This routine will close the 4DWave playback device. For now + we will simply free the dma transfer buffer. + + Parameters: substream - PCM substream class + + ---------------------------------------------------------------------------*/ +static int snd_trident_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_trident_voice *voice = runtime->private_data; + + snd_trident_pcm_mixer_free(trident, voice, substream); + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_spdif_open + + Description: This routine will open the 4DWave SPDIF device. + + Parameters: substream - PCM substream class + + Returns: status - success or failure flag + + ---------------------------------------------------------------------------*/ + +static int snd_trident_spdif_open(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_trident_voice *voice; + struct snd_pcm_runtime *runtime = substream->runtime; + + voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0); + if (voice == NULL) + return -EAGAIN; + voice->spdif = 1; + voice->substream = substream; + scoped_guard(spinlock_irq, &trident->reg_lock) { + trident->spdif_pcm_bits = trident->spdif_bits; + } + + runtime->private_data = voice; + runtime->private_free = snd_trident_pcm_free_substream; + if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + runtime->hw = snd_trident_spdif; + } else { + runtime->hw = snd_trident_spdif_7018; + } + + trident->spdif_pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id); + + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024); + return 0; +} + + +/*--------------------------------------------------------------------------- + snd_trident_spdif_close + + Description: This routine will close the 4DWave SPDIF device. + + Parameters: substream - PCM substream class + + ---------------------------------------------------------------------------*/ + +static int snd_trident_spdif_close(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + unsigned int temp; + + scoped_guard(spinlock_irq, &trident->reg_lock) { + // restore default SPDIF setting + if (trident->device != TRIDENT_DEVICE_ID_SI7018) { + outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3)); + outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS)); + } else { + outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS)); + temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)); + if (trident->spdif_ctrl) { + temp |= SPDIF_EN; + } else { + temp &= ~SPDIF_EN; + } + outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL)); + } + } + trident->spdif_pcm_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id); + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_capture_open + + Description: This routine will open the 4DWave capture device. + + Parameters: substream - PCM substream class + + Returns: status - success or failure flag + + ---------------------------------------------------------------------------*/ + +static int snd_trident_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_trident_voice *voice; + struct snd_pcm_runtime *runtime = substream->runtime; + + voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0); + if (voice == NULL) + return -EAGAIN; + voice->capture = 1; + voice->substream = substream; + runtime->private_data = voice; + runtime->private_free = snd_trident_pcm_free_substream; + runtime->hw = snd_trident_capture; + snd_pcm_set_sync(substream); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024); + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_capture_close + + Description: This routine will close the 4DWave capture device. For now + we will simply free the dma transfer buffer. + + Parameters: substream - PCM substream class + + ---------------------------------------------------------------------------*/ +static int snd_trident_capture_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_foldback_open + + Description: This routine will open the 4DWave foldback capture device. + + Parameters: substream - PCM substream class + + Returns: status - success or failure flag + + ---------------------------------------------------------------------------*/ + +static int snd_trident_foldback_open(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_trident_voice *voice; + struct snd_pcm_runtime *runtime = substream->runtime; + + voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0); + if (voice == NULL) + return -EAGAIN; + voice->foldback_chan = substream->number; + voice->substream = substream; + runtime->private_data = voice; + runtime->private_free = snd_trident_pcm_free_substream; + runtime->hw = snd_trident_foldback; + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024); + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_foldback_close + + Description: This routine will close the 4DWave foldback capture device. + For now we will simply free the dma transfer buffer. + + Parameters: substream - PCM substream class + + ---------------------------------------------------------------------------*/ +static int snd_trident_foldback_close(struct snd_pcm_substream *substream) +{ + struct snd_trident *trident = snd_pcm_substream_chip(substream); + struct snd_trident_voice *voice; + struct snd_pcm_runtime *runtime = substream->runtime; + voice = runtime->private_data; + + /* stop capture channel */ + guard(spinlock_irq)(&trident->reg_lock); + outb(0x00, TRID_REG(trident, T4D_RCI + voice->foldback_chan)); + return 0; +} + +/*--------------------------------------------------------------------------- + PCM operations + ---------------------------------------------------------------------------*/ + +static const struct snd_pcm_ops snd_trident_playback_ops = { + .open = snd_trident_playback_open, + .close = snd_trident_playback_close, + .hw_params = snd_trident_hw_params, + .hw_free = snd_trident_hw_free, + .prepare = snd_trident_playback_prepare, + .trigger = snd_trident_trigger, + .pointer = snd_trident_playback_pointer, +}; + +static const struct snd_pcm_ops snd_trident_nx_playback_ops = { + .open = snd_trident_playback_open, + .close = snd_trident_playback_close, + .hw_params = snd_trident_hw_params, + .hw_free = snd_trident_hw_free, + .prepare = snd_trident_playback_prepare, + .trigger = snd_trident_trigger, + .pointer = snd_trident_playback_pointer, +}; + +static const struct snd_pcm_ops snd_trident_capture_ops = { + .open = snd_trident_capture_open, + .close = snd_trident_capture_close, + .hw_params = snd_trident_capture_hw_params, + .hw_free = snd_trident_hw_free, + .prepare = snd_trident_capture_prepare, + .trigger = snd_trident_trigger, + .pointer = snd_trident_capture_pointer, +}; + +static const struct snd_pcm_ops snd_trident_si7018_capture_ops = { + .open = snd_trident_capture_open, + .close = snd_trident_capture_close, + .hw_params = snd_trident_si7018_capture_hw_params, + .hw_free = snd_trident_si7018_capture_hw_free, + .prepare = snd_trident_si7018_capture_prepare, + .trigger = snd_trident_trigger, + .pointer = snd_trident_playback_pointer, +}; + +static const struct snd_pcm_ops snd_trident_foldback_ops = { + .open = snd_trident_foldback_open, + .close = snd_trident_foldback_close, + .hw_params = snd_trident_hw_params, + .hw_free = snd_trident_hw_free, + .prepare = snd_trident_foldback_prepare, + .trigger = snd_trident_trigger, + .pointer = snd_trident_playback_pointer, +}; + +static const struct snd_pcm_ops snd_trident_nx_foldback_ops = { + .open = snd_trident_foldback_open, + .close = snd_trident_foldback_close, + .hw_params = snd_trident_hw_params, + .hw_free = snd_trident_hw_free, + .prepare = snd_trident_foldback_prepare, + .trigger = snd_trident_trigger, + .pointer = snd_trident_playback_pointer, +}; + +static const struct snd_pcm_ops snd_trident_spdif_ops = { + .open = snd_trident_spdif_open, + .close = snd_trident_spdif_close, + .hw_params = snd_trident_spdif_hw_params, + .hw_free = snd_trident_hw_free, + .prepare = snd_trident_spdif_prepare, + .trigger = snd_trident_trigger, + .pointer = snd_trident_spdif_pointer, +}; + +static const struct snd_pcm_ops snd_trident_spdif_7018_ops = { + .open = snd_trident_spdif_open, + .close = snd_trident_spdif_close, + .hw_params = snd_trident_spdif_hw_params, + .hw_free = snd_trident_hw_free, + .prepare = snd_trident_spdif_prepare, + .trigger = snd_trident_trigger, + .pointer = snd_trident_playback_pointer, +}; + +/*--------------------------------------------------------------------------- + snd_trident_pcm + + Description: This routine registers the 4DWave device for PCM support. + + Parameters: trident - pointer to target device class for 4DWave. + + Returns: None + + ---------------------------------------------------------------------------*/ + +int snd_trident_pcm(struct snd_trident *trident, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(trident->card, "trident_dx_nx", device, trident->ChanPCM, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = trident; + + if (trident->tlb.entries) { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_nx_playback_ops); + } else { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_playback_ops); + } + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + trident->device != TRIDENT_DEVICE_ID_SI7018 ? + &snd_trident_capture_ops : + &snd_trident_si7018_capture_ops); + + pcm->info_flags = 0; + pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + strscpy(pcm->name, "Trident 4DWave"); + trident->pcm = pcm; + + if (trident->tlb.entries) { + struct snd_pcm_substream *substream; + for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) + snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV_SG, + &trident->pci->dev, + 64*1024, 128*1024); + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_DEV, + &trident->pci->dev, + 64*1024, 128*1024); + } else { + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &trident->pci->dev, + 64*1024, 128*1024); + } + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_foldback_pcm + + Description: This routine registers the 4DWave device for foldback PCM support. + + Parameters: trident - pointer to target device class for 4DWave. + + Returns: None + + ---------------------------------------------------------------------------*/ + +int snd_trident_foldback_pcm(struct snd_trident *trident, int device) +{ + struct snd_pcm *foldback; + int err; + int num_chan = 3; + struct snd_pcm_substream *substream; + + if (trident->device == TRIDENT_DEVICE_ID_NX) + num_chan = 4; + err = snd_pcm_new(trident->card, "trident_dx_nx", device, 0, num_chan, &foldback); + if (err < 0) + return err; + + foldback->private_data = trident; + if (trident->tlb.entries) + snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_nx_foldback_ops); + else + snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_foldback_ops); + foldback->info_flags = 0; + strscpy(foldback->name, "Trident 4DWave"); + substream = foldback->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + strscpy(substream->name, "Front Mixer"); + substream = substream->next; + strscpy(substream->name, "Reverb Mixer"); + substream = substream->next; + strscpy(substream->name, "Chorus Mixer"); + if (num_chan == 4) { + substream = substream->next; + strscpy(substream->name, "Second AC'97 ADC"); + } + trident->foldback = foldback; + + if (trident->tlb.entries) + snd_pcm_set_managed_buffer_all(foldback, SNDRV_DMA_TYPE_DEV_SG, + &trident->pci->dev, + 0, 128*1024); + else + snd_pcm_set_managed_buffer_all(foldback, SNDRV_DMA_TYPE_DEV, + &trident->pci->dev, + 64*1024, 128*1024); + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_spdif + + Description: This routine registers the 4DWave-NX device for SPDIF support. + + Parameters: trident - pointer to target device class for 4DWave-NX. + + Returns: None + + ---------------------------------------------------------------------------*/ + +int snd_trident_spdif_pcm(struct snd_trident *trident, int device) +{ + struct snd_pcm *spdif; + int err; + + err = snd_pcm_new(trident->card, "trident_dx_nx IEC958", device, 1, 0, &spdif); + if (err < 0) + return err; + + spdif->private_data = trident; + if (trident->device != TRIDENT_DEVICE_ID_SI7018) { + snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_ops); + } else { + snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_7018_ops); + } + spdif->info_flags = 0; + strscpy(spdif->name, "Trident 4DWave IEC958"); + trident->spdif = spdif; + + snd_pcm_set_managed_buffer_all(spdif, SNDRV_DMA_TYPE_DEV, + &trident->pci->dev, 64*1024, 128*1024); + + return 0; +} + +/* + * Mixer part + */ + + +/*--------------------------------------------------------------------------- + snd_trident_spdif_control + + Description: enable/disable S/PDIF out from ac97 mixer + ---------------------------------------------------------------------------*/ + +#define snd_trident_spdif_control_info snd_ctl_boolean_mono_info + +static int snd_trident_spdif_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + unsigned char val; + + guard(spinlock_irq)(&trident->reg_lock); + val = trident->spdif_ctrl; + ucontrol->value.integer.value[0] = val == kcontrol->private_value; + return 0; +} + +static int snd_trident_spdif_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + unsigned char val; + int change; + + val = ucontrol->value.integer.value[0] ? (unsigned char) kcontrol->private_value : 0x00; + guard(spinlock_irq)(&trident->reg_lock); + /* S/PDIF C Channel bits 0-31 : 48khz, SCMS disabled */ + change = trident->spdif_ctrl != val; + trident->spdif_ctrl = val; + if (trident->device != TRIDENT_DEVICE_ID_SI7018) { + if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0) { + outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS)); + outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3)); + } + } else { + if (trident->spdif == NULL) { + unsigned int temp; + outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS)); + temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & ~SPDIF_EN; + if (val) + temp |= SPDIF_EN; + outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL)); + } + } + return change; +} + +static const struct snd_kcontrol_new snd_trident_spdif_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), + .info = snd_trident_spdif_control_info, + .get = snd_trident_spdif_control_get, + .put = snd_trident_spdif_control_put, + .private_value = 0x28, +}; + +/*--------------------------------------------------------------------------- + snd_trident_spdif_default + + Description: put/get the S/PDIF default settings + ---------------------------------------------------------------------------*/ + +static int snd_trident_spdif_default_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_trident_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&trident->reg_lock); + ucontrol->value.iec958.status[0] = (trident->spdif_bits >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (trident->spdif_bits >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (trident->spdif_bits >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (trident->spdif_bits >> 24) & 0xff; + return 0; +} + +static int snd_trident_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = (ucontrol->value.iec958.status[0] << 0) | + (ucontrol->value.iec958.status[1] << 8) | + (ucontrol->value.iec958.status[2] << 16) | + (ucontrol->value.iec958.status[3] << 24); + guard(spinlock_irq)(&trident->reg_lock); + change = trident->spdif_bits != val; + trident->spdif_bits = val; + if (trident->device != TRIDENT_DEVICE_ID_SI7018) { + if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0) + outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS)); + } else { + if (trident->spdif == NULL) + outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS)); + } + return change; +} + +static const struct snd_kcontrol_new snd_trident_spdif_default = +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_trident_spdif_default_info, + .get = snd_trident_spdif_default_get, + .put = snd_trident_spdif_default_put +}; + +/*--------------------------------------------------------------------------- + snd_trident_spdif_mask + + Description: put/get the S/PDIF mask + ---------------------------------------------------------------------------*/ + +static int snd_trident_spdif_mask_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_trident_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static const struct snd_kcontrol_new snd_trident_spdif_mask = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = snd_trident_spdif_mask_info, + .get = snd_trident_spdif_mask_get, +}; + +/*--------------------------------------------------------------------------- + snd_trident_spdif_stream + + Description: put/get the S/PDIF stream settings + ---------------------------------------------------------------------------*/ + +static int snd_trident_spdif_stream_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_trident_spdif_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&trident->reg_lock); + ucontrol->value.iec958.status[0] = (trident->spdif_pcm_bits >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (trident->spdif_pcm_bits >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (trident->spdif_pcm_bits >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (trident->spdif_pcm_bits >> 24) & 0xff; + return 0; +} + +static int snd_trident_spdif_stream_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = (ucontrol->value.iec958.status[0] << 0) | + (ucontrol->value.iec958.status[1] << 8) | + (ucontrol->value.iec958.status[2] << 16) | + (ucontrol->value.iec958.status[3] << 24); + guard(spinlock_irq)(&trident->reg_lock); + change = trident->spdif_pcm_bits != val; + trident->spdif_pcm_bits = val; + if (trident->spdif != NULL) { + if (trident->device != TRIDENT_DEVICE_ID_SI7018) { + outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS)); + } else { + outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS)); + } + } + return change; +} + +static const struct snd_kcontrol_new snd_trident_spdif_stream = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .info = snd_trident_spdif_stream_info, + .get = snd_trident_spdif_stream_get, + .put = snd_trident_spdif_stream_put +}; + +/*--------------------------------------------------------------------------- + snd_trident_ac97_control + + Description: enable/disable rear path for ac97 + ---------------------------------------------------------------------------*/ + +#define snd_trident_ac97_control_info snd_ctl_boolean_mono_info + +static int snd_trident_ac97_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + unsigned char val; + + guard(spinlock_irq)(&trident->reg_lock); + val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)); + ucontrol->value.integer.value[0] = (val & (1 << kcontrol->private_value)) ? 1 : 0; + return 0; +} + +static int snd_trident_ac97_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + unsigned char val; + int change = 0; + + guard(spinlock_irq)(&trident->reg_lock); + val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)); + val &= ~(1 << kcontrol->private_value); + if (ucontrol->value.integer.value[0]) + val |= 1 << kcontrol->private_value; + change = val != trident->ac97_ctrl; + trident->ac97_ctrl = val; + outl(trident->ac97_ctrl = val, TRID_REG(trident, NX_ACR0_AC97_COM_STAT)); + return change; +} + +static const struct snd_kcontrol_new snd_trident_ac97_rear_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Rear Path", + .info = snd_trident_ac97_control_info, + .get = snd_trident_ac97_control_get, + .put = snd_trident_ac97_control_put, + .private_value = 4, +}; + +/*--------------------------------------------------------------------------- + snd_trident_vol_control + + Description: wave & music volume control + ---------------------------------------------------------------------------*/ + +static int snd_trident_vol_control_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 = 255; + return 0; +} + +static int snd_trident_vol_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + unsigned int val; + + val = trident->musicvol_wavevol; + ucontrol->value.integer.value[0] = 255 - ((val >> kcontrol->private_value) & 0xff); + ucontrol->value.integer.value[1] = 255 - ((val >> (kcontrol->private_value + 8)) & 0xff); + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_gvol, -6375, 25, 0); + +static int snd_trident_vol_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + + guard(spinlock_irq)(&trident->reg_lock); + val = trident->musicvol_wavevol; + val &= ~(0xffff << kcontrol->private_value); + val |= ((255 - (ucontrol->value.integer.value[0] & 0xff)) | + ((255 - (ucontrol->value.integer.value[1] & 0xff)) << 8)) << kcontrol->private_value; + change = val != trident->musicvol_wavevol; + outl(trident->musicvol_wavevol = val, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL)); + return change; +} + +static const struct snd_kcontrol_new snd_trident_vol_music_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Music Playback Volume", + .info = snd_trident_vol_control_info, + .get = snd_trident_vol_control_get, + .put = snd_trident_vol_control_put, + .private_value = 16, + .tlv = { .p = db_scale_gvol }, +}; + +static const struct snd_kcontrol_new snd_trident_vol_wave_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Wave Playback Volume", + .info = snd_trident_vol_control_info, + .get = snd_trident_vol_control_get, + .put = snd_trident_vol_control_put, + .private_value = 0, + .tlv = { .p = db_scale_gvol }, +}; + +/*--------------------------------------------------------------------------- + snd_trident_pcm_vol_control + + Description: PCM front volume control + ---------------------------------------------------------------------------*/ + +static int snd_trident_pcm_vol_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + if (trident->device == TRIDENT_DEVICE_ID_SI7018) + uinfo->value.integer.max = 1023; + return 0; +} + +static int snd_trident_pcm_vol_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)]; + + if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + ucontrol->value.integer.value[0] = 1023 - mix->vol; + } else { + ucontrol->value.integer.value[0] = 255 - (mix->vol>>2); + } + return 0; +} + +static int snd_trident_pcm_vol_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)]; + unsigned int val; + int change = 0; + + if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + val = 1023 - (ucontrol->value.integer.value[0] & 1023); + } else { + val = (255 - (ucontrol->value.integer.value[0] & 255)) << 2; + } + guard(spinlock_irq)(&trident->reg_lock); + change = val != mix->vol; + mix->vol = val; + if (mix->voice != NULL) + snd_trident_write_vol_reg(trident, mix->voice, val); + return change; +} + +static const struct snd_kcontrol_new snd_trident_pcm_vol_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Front Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .count = 32, + .info = snd_trident_pcm_vol_control_info, + .get = snd_trident_pcm_vol_control_get, + .put = snd_trident_pcm_vol_control_put, + /* FIXME: no tlv yet */ +}; + +/*--------------------------------------------------------------------------- + snd_trident_pcm_pan_control + + Description: PCM front pan control + ---------------------------------------------------------------------------*/ + +static int snd_trident_pcm_pan_control_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 = 127; + return 0; +} + +static int snd_trident_pcm_pan_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)]; + + ucontrol->value.integer.value[0] = mix->pan; + if (ucontrol->value.integer.value[0] & 0x40) { + ucontrol->value.integer.value[0] = (0x3f - (ucontrol->value.integer.value[0] & 0x3f)); + } else { + ucontrol->value.integer.value[0] |= 0x40; + } + return 0; +} + +static int snd_trident_pcm_pan_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)]; + unsigned char val; + int change = 0; + + if (ucontrol->value.integer.value[0] & 0x40) + val = ucontrol->value.integer.value[0] & 0x3f; + else + val = (0x3f - (ucontrol->value.integer.value[0] & 0x3f)) | 0x40; + guard(spinlock_irq)(&trident->reg_lock); + change = val != mix->pan; + mix->pan = val; + if (mix->voice != NULL) + snd_trident_write_pan_reg(trident, mix->voice, val); + return change; +} + +static const struct snd_kcontrol_new snd_trident_pcm_pan_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Pan Playback Control", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .count = 32, + .info = snd_trident_pcm_pan_control_info, + .get = snd_trident_pcm_pan_control_get, + .put = snd_trident_pcm_pan_control_put, +}; + +/*--------------------------------------------------------------------------- + snd_trident_pcm_rvol_control + + Description: PCM reverb volume control + ---------------------------------------------------------------------------*/ + +static int snd_trident_pcm_rvol_control_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 = 127; + return 0; +} + +static int snd_trident_pcm_rvol_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)]; + + ucontrol->value.integer.value[0] = 127 - mix->rvol; + return 0; +} + +static int snd_trident_pcm_rvol_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)]; + unsigned short val; + int change = 0; + + val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f); + guard(spinlock_irq)(&trident->reg_lock); + change = val != mix->rvol; + mix->rvol = val; + if (mix->voice != NULL) + snd_trident_write_rvol_reg(trident, mix->voice, val); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_crvol, -3175, 25, 1); + +static const struct snd_kcontrol_new snd_trident_pcm_rvol_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Reverb Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .count = 32, + .info = snd_trident_pcm_rvol_control_info, + .get = snd_trident_pcm_rvol_control_get, + .put = snd_trident_pcm_rvol_control_put, + .tlv = { .p = db_scale_crvol }, +}; + +/*--------------------------------------------------------------------------- + snd_trident_pcm_cvol_control + + Description: PCM chorus volume control + ---------------------------------------------------------------------------*/ + +static int snd_trident_pcm_cvol_control_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 = 127; + return 0; +} + +static int snd_trident_pcm_cvol_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)]; + + ucontrol->value.integer.value[0] = 127 - mix->cvol; + return 0; +} + +static int snd_trident_pcm_cvol_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_trident *trident = snd_kcontrol_chip(kcontrol); + struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)]; + unsigned short val; + int change = 0; + + val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f); + guard(spinlock_irq)(&trident->reg_lock); + change = val != mix->cvol; + mix->cvol = val; + if (mix->voice != NULL) + snd_trident_write_cvol_reg(trident, mix->voice, val); + return change; +} + +static const struct snd_kcontrol_new snd_trident_pcm_cvol_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Chorus Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .count = 32, + .info = snd_trident_pcm_cvol_control_info, + .get = snd_trident_pcm_cvol_control_get, + .put = snd_trident_pcm_cvol_control_put, + .tlv = { .p = db_scale_crvol }, +}; + +static void snd_trident_notify_pcm_change1(struct snd_card *card, + struct snd_kcontrol *kctl, + int num, int activate) +{ + struct snd_ctl_elem_id id; + + if (! kctl) + return; + if (activate) + kctl->vd[num].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + else + kctl->vd[num].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, + snd_ctl_build_ioff(&id, kctl, num)); +} + +static void snd_trident_notify_pcm_change(struct snd_trident *trident, + struct snd_trident_pcm_mixer *tmix, + int num, int activate) +{ + snd_trident_notify_pcm_change1(trident->card, trident->ctl_vol, num, activate); + snd_trident_notify_pcm_change1(trident->card, trident->ctl_pan, num, activate); + snd_trident_notify_pcm_change1(trident->card, trident->ctl_rvol, num, activate); + snd_trident_notify_pcm_change1(trident->card, trident->ctl_cvol, num, activate); +} + +static int snd_trident_pcm_mixer_build(struct snd_trident *trident, + struct snd_trident_voice *voice, + struct snd_pcm_substream *substream) +{ + struct snd_trident_pcm_mixer *tmix; + + if (snd_BUG_ON(!trident || !voice || !substream)) + return -EINVAL; + tmix = &trident->pcm_mixer[substream->number]; + tmix->voice = voice; + tmix->vol = T4D_DEFAULT_PCM_VOL; + tmix->pan = T4D_DEFAULT_PCM_PAN; + tmix->rvol = T4D_DEFAULT_PCM_RVOL; + tmix->cvol = T4D_DEFAULT_PCM_CVOL; + snd_trident_notify_pcm_change(trident, tmix, substream->number, 1); + return 0; +} + +static int snd_trident_pcm_mixer_free(struct snd_trident *trident, struct snd_trident_voice *voice, struct snd_pcm_substream *substream) +{ + struct snd_trident_pcm_mixer *tmix; + + if (snd_BUG_ON(!trident || !substream)) + return -EINVAL; + tmix = &trident->pcm_mixer[substream->number]; + tmix->voice = NULL; + snd_trident_notify_pcm_change(trident, tmix, substream->number, 0); + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_mixer + + Description: This routine registers the 4DWave device for mixer support. + + Parameters: trident - pointer to target device class for 4DWave. + + Returns: None + + ---------------------------------------------------------------------------*/ + +static int snd_trident_mixer(struct snd_trident *trident, int pcm_spdif_device) +{ + struct snd_ac97_template _ac97; + struct snd_card *card = trident->card; + struct snd_kcontrol *kctl; + struct snd_ctl_elem_value *uctl; + int idx, err, retries = 2; + static const struct snd_ac97_bus_ops ops = { + .write = snd_trident_codec_write, + .read = snd_trident_codec_read, + }; + + uctl = kzalloc_obj(*uctl); + if (!uctl) + return -ENOMEM; + + err = snd_ac97_bus(trident->card, 0, &ops, NULL, &trident->ac97_bus); + if (err < 0) + goto __out; + + memset(&_ac97, 0, sizeof(_ac97)); + _ac97.private_data = trident; + trident->ac97_detect = 1; + + __again: + err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97); + if (err < 0) { + if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + err = snd_trident_sis_reset(trident); + if (err < 0) + goto __out; + if (retries-- > 0) + goto __again; + err = -EIO; + } + goto __out; + } + + /* secondary codec? */ + if (trident->device == TRIDENT_DEVICE_ID_SI7018 && + (inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0) { + _ac97.num = 1; + err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97_sec); + if (err < 0) + dev_err(trident->card->dev, + "SI7018: the secondary codec - invalid access\n"); +#if 0 // only for my testing purpose --jk + { + struct snd_ac97 *mc97; + err = snd_ac97_modem(trident->card, &_ac97, &mc97); + if (err < 0) + dev_err(trident->card->dev, + "snd_ac97_modem returned error %i\n", err); + } +#endif + } + + trident->ac97_detect = 0; + + if (trident->device != TRIDENT_DEVICE_ID_SI7018) { + kctl = snd_ctl_new1(&snd_trident_vol_wave_control, trident); + err = snd_ctl_add(card, kctl); + if (err < 0) + goto __out; + kctl->put(kctl, uctl); + kctl = snd_ctl_new1(&snd_trident_vol_music_control, trident); + err = snd_ctl_add(card, kctl); + if (err < 0) + goto __out; + kctl->put(kctl, uctl); + outl(trident->musicvol_wavevol = 0x00000000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL)); + } else { + outl(trident->musicvol_wavevol = 0xffff0000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL)); + } + + for (idx = 0; idx < 32; idx++) { + struct snd_trident_pcm_mixer *tmix; + + tmix = &trident->pcm_mixer[idx]; + tmix->voice = NULL; + } + trident->ctl_vol = snd_ctl_new1(&snd_trident_pcm_vol_control, trident); + if (!trident->ctl_vol) + goto __nomem; + err = snd_ctl_add(card, trident->ctl_vol); + if (err) + goto __out; + + trident->ctl_pan = snd_ctl_new1(&snd_trident_pcm_pan_control, trident); + if (!trident->ctl_pan) + goto __nomem; + err = snd_ctl_add(card, trident->ctl_pan); + if (err) + goto __out; + + trident->ctl_rvol = snd_ctl_new1(&snd_trident_pcm_rvol_control, trident); + if (!trident->ctl_rvol) + goto __nomem; + err = snd_ctl_add(card, trident->ctl_rvol); + if (err) + goto __out; + + trident->ctl_cvol = snd_ctl_new1(&snd_trident_pcm_cvol_control, trident); + if (!trident->ctl_cvol) + goto __nomem; + err = snd_ctl_add(card, trident->ctl_cvol); + if (err) + goto __out; + + if (trident->device == TRIDENT_DEVICE_ID_NX) { + kctl = snd_ctl_new1(&snd_trident_ac97_rear_control, trident); + err = snd_ctl_add(card, kctl); + if (err < 0) + goto __out; + kctl->put(kctl, uctl); + } + if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) { + + kctl = snd_ctl_new1(&snd_trident_spdif_control, trident); + if (kctl == NULL) { + err = -ENOMEM; + goto __out; + } + if (trident->ac97->ext_id & AC97_EI_SPDIF) + kctl->id.index++; + if (trident->ac97_sec && (trident->ac97_sec->ext_id & AC97_EI_SPDIF)) + kctl->id.index++; + idx = kctl->id.index; + err = snd_ctl_add(card, kctl); + if (err < 0) + goto __out; + kctl->put(kctl, uctl); + + kctl = snd_ctl_new1(&snd_trident_spdif_default, trident); + if (kctl == NULL) { + err = -ENOMEM; + goto __out; + } + kctl->id.index = idx; + kctl->id.device = pcm_spdif_device; + err = snd_ctl_add(card, kctl); + if (err < 0) + goto __out; + + kctl = snd_ctl_new1(&snd_trident_spdif_mask, trident); + if (kctl == NULL) { + err = -ENOMEM; + goto __out; + } + kctl->id.index = idx; + kctl->id.device = pcm_spdif_device; + err = snd_ctl_add(card, kctl); + if (err < 0) + goto __out; + + kctl = snd_ctl_new1(&snd_trident_spdif_stream, trident); + if (kctl == NULL) { + err = -ENOMEM; + goto __out; + } + kctl->id.index = idx; + kctl->id.device = pcm_spdif_device; + err = snd_ctl_add(card, kctl); + if (err < 0) + goto __out; + trident->spdif_pcm_ctl = kctl; + } + + err = 0; + goto __out; + + __nomem: + err = -ENOMEM; + + __out: + kfree(uctl); + + return err; +} + +/* + * gameport interface + */ + +#if IS_REACHABLE(CONFIG_GAMEPORT) + +static unsigned char snd_trident_gameport_read(struct gameport *gameport) +{ + struct snd_trident *chip = gameport_get_port_data(gameport); + + if (snd_BUG_ON(!chip)) + return 0; + return inb(TRID_REG(chip, GAMEPORT_LEGACY)); +} + +static void snd_trident_gameport_trigger(struct gameport *gameport) +{ + struct snd_trident *chip = gameport_get_port_data(gameport); + + if (snd_BUG_ON(!chip)) + return; + outb(0xff, TRID_REG(chip, GAMEPORT_LEGACY)); +} + +static int snd_trident_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + struct snd_trident *chip = gameport_get_port_data(gameport); + int i; + + if (snd_BUG_ON(!chip)) + return 0; + + *buttons = (~inb(TRID_REG(chip, GAMEPORT_LEGACY)) >> 4) & 0xf; + + for (i = 0; i < 4; i++) { + axes[i] = inw(TRID_REG(chip, GAMEPORT_AXES + i * 2)); + if (axes[i] == 0xffff) axes[i] = -1; + } + + return 0; +} + +static int snd_trident_gameport_open(struct gameport *gameport, int mode) +{ + struct snd_trident *chip = gameport_get_port_data(gameport); + + if (snd_BUG_ON(!chip)) + return 0; + + switch (mode) { + case GAMEPORT_MODE_COOKED: + outb(GAMEPORT_MODE_ADC, TRID_REG(chip, GAMEPORT_GCR)); + msleep(20); + return 0; + case GAMEPORT_MODE_RAW: + outb(0, TRID_REG(chip, GAMEPORT_GCR)); + return 0; + default: + return -1; + } +} + +int snd_trident_create_gameport(struct snd_trident *chip) +{ + struct gameport *gp; + + chip->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(chip->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "Trident 4DWave"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci)); + gameport_set_dev_parent(gp, &chip->pci->dev); + + gameport_set_port_data(gp, chip); + gp->fuzz = 64; + gp->read = snd_trident_gameport_read; + gp->trigger = snd_trident_gameport_trigger; + gp->cooked_read = snd_trident_gameport_cooked_read; + gp->open = snd_trident_gameport_open; + + gameport_register_port(gp); + + return 0; +} + +static inline void snd_trident_free_gameport(struct snd_trident *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } +} +#else +int snd_trident_create_gameport(struct snd_trident *chip) { return -ENOSYS; } +static inline void snd_trident_free_gameport(struct snd_trident *chip) { } +#endif /* CONFIG_GAMEPORT */ + +/* + * delay for 1 tick + */ +static inline void do_delay(struct snd_trident *chip) +{ + schedule_timeout_uninterruptible(1); +} + +/* + * SiS reset routine + */ + +static int snd_trident_sis_reset(struct snd_trident *trident) +{ + unsigned long end_time; + unsigned int i; + int r; + + r = trident->in_suspend ? 0 : 2; /* count of retries */ + __si7018_retry: + pci_write_config_byte(trident->pci, 0x46, 0x04); /* SOFTWARE RESET */ + udelay(100); + pci_write_config_byte(trident->pci, 0x46, 0x00); + udelay(100); + /* disable AC97 GPIO interrupt */ + outb(0x00, TRID_REG(trident, SI_AC97_GPIO)); + /* initialize serial interface, force cold reset */ + i = PCMOUT|SURROUT|CENTEROUT|LFEOUT|SECONDARY_ID|COLD_RESET; + outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL)); + udelay(1000); + /* remove cold reset */ + i &= ~COLD_RESET; + outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL)); + udelay(2000); + /* wait, until the codec is ready */ + end_time = (jiffies + (HZ * 3) / 4) + 1; + do { + if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0) + goto __si7018_ok; + do_delay(trident); + } while (time_after_eq(end_time, jiffies)); + dev_err(trident->card->dev, "AC'97 codec ready error [0x%x]\n", + inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL))); + if (r-- > 0) { + end_time = jiffies + HZ; + do { + do_delay(trident); + } while (time_after_eq(end_time, jiffies)); + goto __si7018_retry; + } + __si7018_ok: + /* wait for the second codec */ + do { + if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_SECONDARY_READY) != 0) + break; + do_delay(trident); + } while (time_after_eq(end_time, jiffies)); + /* enable 64 channel mode */ + outl(BANK_B_EN, TRID_REG(trident, T4D_LFO_GC_CIR)); + return 0; +} + +/* + * /proc interface + */ + +static void snd_trident_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_trident *trident = entry->private_data; + char *s; + + switch (trident->device) { + case TRIDENT_DEVICE_ID_SI7018: + s = "SiS 7018 Audio"; + break; + case TRIDENT_DEVICE_ID_DX: + s = "Trident 4DWave PCI DX"; + break; + case TRIDENT_DEVICE_ID_NX: + s = "Trident 4DWave PCI NX"; + break; + default: + s = "???"; + } + snd_iprintf(buffer, "%s\n\n", s); + snd_iprintf(buffer, "Spurious IRQs : %d\n", trident->spurious_irq_count); + snd_iprintf(buffer, "Spurious IRQ dlta: %d\n", trident->spurious_irq_max_delta); + if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) + snd_iprintf(buffer, "IEC958 Mixer Out : %s\n", str_on_off(trident->spdif_ctrl == 0x28)); + if (trident->device == TRIDENT_DEVICE_ID_NX) { + snd_iprintf(buffer, "Rear Speakers : %s\n", str_on_off(trident->ac97_ctrl & 0x00000010)); + if (trident->tlb.entries) { + snd_iprintf(buffer,"\nVirtual Memory\n"); + snd_iprintf(buffer, "Memory Maximum : %d\n", trident->tlb.memhdr->size); + snd_iprintf(buffer, "Memory Used : %d\n", trident->tlb.memhdr->used); + snd_iprintf(buffer, "Memory Free : %d\n", snd_util_mem_avail(trident->tlb.memhdr)); + } + } +} + +static void snd_trident_proc_init(struct snd_trident *trident) +{ + const char *s = "trident"; + + if (trident->device == TRIDENT_DEVICE_ID_SI7018) + s = "sis7018"; + snd_card_ro_proc_new(trident->card, s, trident, snd_trident_proc_read); +} + +/*--------------------------------------------------------------------------- + snd_trident_tlb_alloc + + Description: Allocate and set up the TLB page table on 4D NX. + Each entry has 4 bytes (physical PCI address). + + Parameters: trident - pointer to target device class for 4DWave. + + Returns: 0 or negative error code + + ---------------------------------------------------------------------------*/ + +static int snd_trident_tlb_alloc(struct snd_trident *trident) +{ + int i; + + /* TLB array must be aligned to 16kB !!! so we allocate + 32kB region and correct offset when necessary */ + + trident->tlb.buffer = + snd_devm_alloc_pages(&trident->pci->dev, SNDRV_DMA_TYPE_DEV, + 2 * SNDRV_TRIDENT_MAX_PAGES * 4); + if (!trident->tlb.buffer) { + dev_err(trident->card->dev, "unable to allocate TLB buffer\n"); + return -ENOMEM; + } + trident->tlb.entries = (__le32 *)ALIGN((unsigned long)trident->tlb.buffer->area, SNDRV_TRIDENT_MAX_PAGES * 4); + trident->tlb.entries_dmaaddr = ALIGN(trident->tlb.buffer->addr, SNDRV_TRIDENT_MAX_PAGES * 4); + + /* allocate and setup silent page and initialise TLB entries */ + trident->tlb.silent_page = + snd_devm_alloc_pages(&trident->pci->dev, SNDRV_DMA_TYPE_DEV, + SNDRV_TRIDENT_PAGE_SIZE); + if (!trident->tlb.silent_page) { + dev_err(trident->card->dev, "unable to allocate silent page\n"); + return -ENOMEM; + } + memset(trident->tlb.silent_page->area, 0, SNDRV_TRIDENT_PAGE_SIZE); + for (i = 0; i < SNDRV_TRIDENT_MAX_PAGES; i++) + trident->tlb.entries[i] = cpu_to_le32(trident->tlb.silent_page->addr & ~(SNDRV_TRIDENT_PAGE_SIZE-1)); + + /* use emu memory block manager code to manage tlb page allocation */ + trident->tlb.memhdr = snd_util_memhdr_new(SNDRV_TRIDENT_PAGE_SIZE * SNDRV_TRIDENT_MAX_PAGES); + if (trident->tlb.memhdr == NULL) + return -ENOMEM; + + trident->tlb.memhdr->block_extra_size = sizeof(struct snd_trident_memblk_arg); + return 0; +} + +/* + * initialize 4D DX chip + */ + +static void snd_trident_stop_all_voices(struct snd_trident *trident) +{ + outl(0xffffffff, TRID_REG(trident, T4D_STOP_A)); + outl(0xffffffff, TRID_REG(trident, T4D_STOP_B)); + outl(0, TRID_REG(trident, T4D_AINTEN_A)); + outl(0, TRID_REG(trident, T4D_AINTEN_B)); +} + +static int snd_trident_4d_dx_init(struct snd_trident *trident) +{ + struct pci_dev *pci = trident->pci; + unsigned long end_time; + + /* reset the legacy configuration and whole audio/wavetable block */ + pci_write_config_dword(pci, 0x40, 0); /* DDMA */ + pci_write_config_byte(pci, 0x44, 0); /* ports */ + pci_write_config_byte(pci, 0x45, 0); /* Legacy DMA */ + pci_write_config_byte(pci, 0x46, 4); /* reset */ + udelay(100); + pci_write_config_byte(pci, 0x46, 0); /* release reset */ + udelay(100); + + /* warm reset of the AC'97 codec */ + outl(0x00000001, TRID_REG(trident, DX_ACR2_AC97_COM_STAT)); + udelay(100); + outl(0x00000000, TRID_REG(trident, DX_ACR2_AC97_COM_STAT)); + /* DAC on, disable SB IRQ and try to force ADC valid signal */ + trident->ac97_ctrl = 0x0000004a; + outl(trident->ac97_ctrl, TRID_REG(trident, DX_ACR2_AC97_COM_STAT)); + /* wait, until the codec is ready */ + end_time = (jiffies + (HZ * 3) / 4) + 1; + do { + if ((inl(TRID_REG(trident, DX_ACR2_AC97_COM_STAT)) & 0x0010) != 0) + goto __dx_ok; + do_delay(trident); + } while (time_after_eq(end_time, jiffies)); + dev_err(trident->card->dev, "AC'97 codec ready error\n"); + return -EIO; + + __dx_ok: + snd_trident_stop_all_voices(trident); + + return 0; +} + +/* + * initialize 4D NX chip + */ +static int snd_trident_4d_nx_init(struct snd_trident *trident) +{ + struct pci_dev *pci = trident->pci; + unsigned long end_time; + + /* reset the legacy configuration and whole audio/wavetable block */ + pci_write_config_dword(pci, 0x40, 0); /* DDMA */ + pci_write_config_byte(pci, 0x44, 0); /* ports */ + pci_write_config_byte(pci, 0x45, 0); /* Legacy DMA */ + + pci_write_config_byte(pci, 0x46, 1); /* reset */ + udelay(100); + pci_write_config_byte(pci, 0x46, 0); /* release reset */ + udelay(100); + + /* warm reset of the AC'97 codec */ + outl(0x00000001, TRID_REG(trident, NX_ACR0_AC97_COM_STAT)); + udelay(100); + outl(0x00000000, TRID_REG(trident, NX_ACR0_AC97_COM_STAT)); + /* wait, until the codec is ready */ + end_time = (jiffies + (HZ * 3) / 4) + 1; + do { + if ((inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)) & 0x0008) != 0) + goto __nx_ok; + do_delay(trident); + } while (time_after_eq(end_time, jiffies)); + dev_err(trident->card->dev, "AC'97 codec ready error [0x%x]\n", + inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT))); + return -EIO; + + __nx_ok: + /* DAC on */ + trident->ac97_ctrl = 0x00000002; + outl(trident->ac97_ctrl, TRID_REG(trident, NX_ACR0_AC97_COM_STAT)); + /* disable SB IRQ */ + outl(NX_SB_IRQ_DISABLE, TRID_REG(trident, T4D_MISCINT)); + + snd_trident_stop_all_voices(trident); + + if (trident->tlb.entries != NULL) { + unsigned int i; + /* enable virtual addressing via TLB */ + i = trident->tlb.entries_dmaaddr; + i |= 0x00000001; + outl(i, TRID_REG(trident, NX_TLBC)); + } else { + outl(0, TRID_REG(trident, NX_TLBC)); + } + /* initialize S/PDIF */ + outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS)); + outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3)); + + return 0; +} + +/* + * initialize sis7018 chip + */ +static int snd_trident_sis_init(struct snd_trident *trident) +{ + int err; + + err = snd_trident_sis_reset(trident); + if (err < 0) + return err; + + snd_trident_stop_all_voices(trident); + + /* initialize S/PDIF */ + outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS)); + + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_create + + Description: This routine will create the device specific class for + the 4DWave card. It will also perform basic initialization. + + Parameters: card - which card to create + pci - interface to PCI bus resource info + dma1ptr - playback dma buffer + dma2ptr - capture dma buffer + irqptr - interrupt resource info + + Returns: 4DWave device class private data + + ---------------------------------------------------------------------------*/ + +int snd_trident_create(struct snd_card *card, + struct pci_dev *pci, + int pcm_streams, + int pcm_spdif_device, + int max_wavetable_size) +{ + struct snd_trident *trident = card->private_data; + int i, err; + struct snd_trident_voice *voice; + struct snd_trident_pcm_mixer *tmix; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + /* check, if we can restrict PCI DMA transfers to 30 bits */ + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(30))) { + dev_err(card->dev, + "architecture does not support 30bit PCI busmaster DMA\n"); + return -ENXIO; + } + + trident->device = (pci->vendor << 16) | pci->device; + trident->card = card; + trident->pci = pci; + spin_lock_init(&trident->reg_lock); + spin_lock_init(&trident->event_lock); + spin_lock_init(&trident->voice_alloc); + if (pcm_streams < 1) + pcm_streams = 1; + if (pcm_streams > 32) + pcm_streams = 32; + trident->ChanPCM = pcm_streams; + if (max_wavetable_size < 0 ) + max_wavetable_size = 0; + trident->synth.max_size = max_wavetable_size * 1024; + trident->irq = -1; + card->private_free = snd_trident_free; + + trident->midi_port = TRID_REG(trident, T4D_MPU401_BASE); + pci_set_master(pci); + + err = pcim_request_all_regions(pci, "Trident Audio"); + if (err < 0) + return err; + trident->port = pci_resource_start(pci, 0); + + if (devm_request_irq(&pci->dev, pci->irq, snd_trident_interrupt, + IRQF_SHARED, KBUILD_MODNAME, trident)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + trident->irq = pci->irq; + card->sync_irq = trident->irq; + + /* allocate 16k-aligned TLB for NX cards */ + trident->tlb.entries = NULL; + if (trident->device == TRIDENT_DEVICE_ID_NX) { + err = snd_trident_tlb_alloc(trident); + if (err < 0) + return err; + } + + trident->spdif_bits = trident->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF; + + /* initialize chip */ + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: + err = snd_trident_4d_dx_init(trident); + break; + case TRIDENT_DEVICE_ID_NX: + err = snd_trident_4d_nx_init(trident); + break; + case TRIDENT_DEVICE_ID_SI7018: + err = snd_trident_sis_init(trident); + break; + default: + snd_BUG(); + break; + } + if (err < 0) + return err; + + err = snd_trident_mixer(trident, pcm_spdif_device); + if (err < 0) + return err; + + /* initialise synth voices */ + for (i = 0; i < 64; i++) { + voice = &trident->synth.voices[i]; + voice->number = i; + voice->trident = trident; + } + /* initialize pcm mixer entries */ + for (i = 0; i < 32; i++) { + tmix = &trident->pcm_mixer[i]; + tmix->vol = T4D_DEFAULT_PCM_VOL; + tmix->pan = T4D_DEFAULT_PCM_PAN; + tmix->rvol = T4D_DEFAULT_PCM_RVOL; + tmix->cvol = T4D_DEFAULT_PCM_CVOL; + } + + snd_trident_enable_eso(trident); + + snd_trident_proc_init(trident); + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_free + + Description: This routine will free the device specific class for + the 4DWave card. + + Parameters: card - card to release + + Returns: None. + + ---------------------------------------------------------------------------*/ + +static void snd_trident_free(struct snd_card *card) +{ + struct snd_trident *trident = card->private_data; + + snd_trident_free_gameport(trident); + snd_trident_disable_eso(trident); + // Disable S/PDIF out + if (trident->device == TRIDENT_DEVICE_ID_NX) + outb(0x00, TRID_REG(trident, NX_SPCTRL_SPCSO + 3)); + else if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + outl(0, TRID_REG(trident, SI_SERIAL_INTF_CTRL)); + } + if (trident->tlb.buffer) { + outl(0, TRID_REG(trident, NX_TLBC)); + snd_util_memhdr_free(trident->tlb.memhdr); + } +} + +/*--------------------------------------------------------------------------- + snd_trident_interrupt + + Description: ISR for Trident 4DWave device + + Parameters: trident - device specific private data for 4DWave card + + Problems: It seems that Trident chips generates interrupts more than + one time in special cases. The spurious interrupts are + detected via sample timer (T4D_STIMER) and computing + corresponding delta value. The limits are detected with + the method try & fail so it is possible that it won't + work on all computers. [jaroslav] + + Returns: None. + + ---------------------------------------------------------------------------*/ + +static irqreturn_t snd_trident_interrupt(int irq, void *dev_id) +{ + struct snd_trident *trident = dev_id; + unsigned int audio_int, chn_int, stimer, channel, mask, tmp; + int delta; + struct snd_trident_voice *voice; + + audio_int = inl(TRID_REG(trident, T4D_MISCINT)); + if ((audio_int & (ADDRESS_IRQ|MPU401_IRQ)) == 0) + return IRQ_NONE; + if (audio_int & ADDRESS_IRQ) { + // get interrupt status for all channels + scoped_guard(spinlock, &trident->reg_lock) { + stimer = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff; + chn_int = inl(TRID_REG(trident, T4D_AINT_A)); + if (chn_int) + outl(chn_int, TRID_REG(trident, T4D_AINT_A)); /* ack */ + chn_int = inl(TRID_REG(trident, T4D_AINT_B)); + if (chn_int == 0) + break; + for (channel = 63; channel >= 32; channel--) { + mask = 1 << (channel&0x1f); + if ((chn_int & mask) == 0) + continue; + voice = &trident->synth.voices[channel]; + if (!voice->pcm || voice->substream == NULL) { + outl(mask, TRID_REG(trident, T4D_STOP_B)); + continue; + } + delta = (int)stimer - (int)voice->stimer; + if (delta < 0) + delta = -delta; + if ((unsigned int)delta < voice->spurious_threshold) { + /* do some statistics here */ + trident->spurious_irq_count++; + if (trident->spurious_irq_max_delta < (unsigned int)delta) + trident->spurious_irq_max_delta = delta; + continue; + } + voice->stimer = stimer; + if (voice->isync) { + if (!voice->isync3) { + tmp = inw(TRID_REG(trident, T4D_SBBL_SBCL)); + if (trident->bDMAStart & 0x40) + tmp >>= 1; + if (tmp > 0) + tmp = voice->isync_max - tmp; + } else { + tmp = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff; + } + if (tmp < voice->isync_mark) { + if (tmp > 0x10) + tmp = voice->isync_ESO - 7; + else + tmp = voice->isync_ESO + 2; + /* update ESO for IRQ voice to preserve sync */ + snd_trident_stop_voice(trident, voice->number); + snd_trident_write_eso_reg(trident, voice, tmp); + snd_trident_start_voice(trident, voice->number); + } + } else if (voice->isync2) { + voice->isync2 = 0; + /* write original ESO and update CSO for IRQ voice to preserve sync */ + snd_trident_stop_voice(trident, voice->number); + snd_trident_write_cso_reg(trident, voice, voice->isync_mark); + snd_trident_write_eso_reg(trident, voice, voice->ESO); + snd_trident_start_voice(trident, voice->number); + } +#if 0 + if (voice->extra) { + /* update CSO for extra voice to preserve sync */ + snd_trident_stop_voice(trident, voice->extra->number); + snd_trident_write_cso_reg(trident, voice->extra, 0); + snd_trident_start_voice(trident, voice->extra->number); + } +#endif + spin_unlock(&trident->reg_lock); + snd_pcm_period_elapsed(voice->substream); + spin_lock(&trident->reg_lock); + } + outl(chn_int, TRID_REG(trident, T4D_AINT_B)); /* ack */ + } + } + if (audio_int & MPU401_IRQ) { + if (trident->rmidi) { + snd_mpu401_uart_interrupt(irq, trident->rmidi->private_data); + } else { + inb(TRID_REG(trident, T4D_MPUR0)); + } + } + // outl((ST_TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), TRID_REG(trident, T4D_MISCINT)); + return IRQ_HANDLED; +} + +struct snd_trident_voice *snd_trident_alloc_voice(struct snd_trident * trident, int type, int client, int port) +{ + struct snd_trident_voice *pvoice; + int idx; + + guard(spinlock_irqsave)(&trident->voice_alloc); + if (type == SNDRV_TRIDENT_VOICE_TYPE_PCM) { + idx = snd_trident_allocate_pcm_channel(trident); + if (idx < 0) + return NULL; + pvoice = &trident->synth.voices[idx]; + pvoice->use = 1; + pvoice->pcm = 1; + pvoice->capture = 0; + pvoice->spdif = 0; + pvoice->memblk = NULL; + pvoice->substream = NULL; + return pvoice; + } + if (type == SNDRV_TRIDENT_VOICE_TYPE_SYNTH) { + idx = snd_trident_allocate_synth_channel(trident); + if (idx < 0) + return NULL; + pvoice = &trident->synth.voices[idx]; + pvoice->use = 1; + pvoice->synth = 1; + pvoice->client = client; + pvoice->port = port; + pvoice->memblk = NULL; + return pvoice; + } + if (type == SNDRV_TRIDENT_VOICE_TYPE_MIDI) { + } + return NULL; +} + +EXPORT_SYMBOL(snd_trident_alloc_voice); + +void snd_trident_free_voice(struct snd_trident * trident, struct snd_trident_voice *voice) +{ + void (*private_free)(struct snd_trident_voice *); + + if (voice == NULL || !voice->use) + return; + snd_trident_clear_voices(trident, voice->number, voice->number); + scoped_guard(spinlock_irqsave, &trident->voice_alloc) { + private_free = voice->private_free; + voice->private_free = NULL; + voice->private_data = NULL; + if (voice->pcm) + snd_trident_free_pcm_channel(trident, voice->number); + if (voice->synth) + snd_trident_free_synth_channel(trident, voice->number); + voice->use = voice->pcm = voice->synth = voice->midi = 0; + voice->capture = voice->spdif = 0; + voice->sample_ops = NULL; + voice->substream = NULL; + voice->extra = NULL; + } + if (private_free) + private_free(voice); +} + +EXPORT_SYMBOL(snd_trident_free_voice); + +static void snd_trident_clear_voices(struct snd_trident * trident, unsigned short v_min, unsigned short v_max) +{ + unsigned int i, val, mask[2] = { 0, 0 }; + + if (snd_BUG_ON(v_min > 63 || v_max > 63)) + return; + for (i = v_min; i <= v_max; i++) + mask[i >> 5] |= 1 << (i & 0x1f); + if (mask[0]) { + outl(mask[0], TRID_REG(trident, T4D_STOP_A)); + val = inl(TRID_REG(trident, T4D_AINTEN_A)); + outl(val & ~mask[0], TRID_REG(trident, T4D_AINTEN_A)); + } + if (mask[1]) { + outl(mask[1], TRID_REG(trident, T4D_STOP_B)); + val = inl(TRID_REG(trident, T4D_AINTEN_B)); + outl(val & ~mask[1], TRID_REG(trident, T4D_AINTEN_B)); + } +} + +#ifdef CONFIG_PM_SLEEP +static int snd_trident_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_trident *trident = card->private_data; + + trident->in_suspend = 1; + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(trident->ac97); + snd_ac97_suspend(trident->ac97_sec); + return 0; +} + +static int snd_trident_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_trident *trident = card->private_data; + + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: + snd_trident_4d_dx_init(trident); + break; + case TRIDENT_DEVICE_ID_NX: + snd_trident_4d_nx_init(trident); + break; + case TRIDENT_DEVICE_ID_SI7018: + snd_trident_sis_init(trident); + break; + } + + snd_ac97_resume(trident->ac97); + snd_ac97_resume(trident->ac97_sec); + + /* restore some registers */ + outl(trident->musicvol_wavevol, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL)); + + snd_trident_enable_eso(trident); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + trident->in_suspend = 0; + return 0; +} + +SIMPLE_DEV_PM_OPS(snd_trident_pm, snd_trident_suspend, snd_trident_resume); +#endif /* CONFIG_PM_SLEEP */ diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c new file mode 100644 index 000000000..81f634819 --- /dev/null +++ b/sound/pci/trident/trident_memory.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Copyright (c) by Takashi Iwai + * Copyright (c) by Scott McNab + * + * Trident 4DWave-NX memory page allocation (TLB area) + * Trident chip can handle only 16MByte of the memory at the same time. + */ + +#include +#include +#include +#include + +#include +#include "trident.h" + +/* page arguments of these two macros are Trident page (4096 bytes), not like + * aligned pages in others + */ +#define __set_tlb_bus(trident,page,addr) \ + (trident)->tlb.entries[page] = cpu_to_le32((addr) & ~(SNDRV_TRIDENT_PAGE_SIZE-1)) +#define __tlb_to_addr(trident,page) \ + (dma_addr_t)le32_to_cpu((trident->tlb.entries[page]) & ~(SNDRV_TRIDENT_PAGE_SIZE - 1)) + +#if PAGE_SIZE == 4096 +/* page size == SNDRV_TRIDENT_PAGE_SIZE */ +#define ALIGN_PAGE_SIZE PAGE_SIZE /* minimum page size for allocation */ +#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maxmium aligned pages */ +/* fill TLB entrie(s) corresponding to page with ptr */ +#define set_tlb_bus(trident,page,addr) __set_tlb_bus(trident,page,addr) +/* fill TLB entrie(s) corresponding to page with silence pointer */ +#define set_silent_tlb(trident,page) __set_tlb_bus(trident, page, trident->tlb.silent_page->addr) +/* get aligned page from offset address */ +#define get_aligned_page(offset) ((offset) >> 12) +/* get offset address from aligned page */ +#define aligned_page_offset(page) ((page) << 12) +/* get PCI physical address from aligned page */ +#define page_to_addr(trident,page) __tlb_to_addr(trident, page) + +#elif PAGE_SIZE == 8192 +/* page size == SNDRV_TRIDENT_PAGE_SIZE x 2*/ +#define ALIGN_PAGE_SIZE PAGE_SIZE +#define MAX_ALIGN_PAGES (SNDRV_TRIDENT_MAX_PAGES / 2) +#define get_aligned_page(offset) ((offset) >> 13) +#define aligned_page_offset(page) ((page) << 13) +#define page_to_addr(trident,page) __tlb_to_addr(trident, (page) << 1) + +/* fill TLB entries -- we need to fill two entries */ +static inline void set_tlb_bus(struct snd_trident *trident, int page, + dma_addr_t addr) +{ + page <<= 1; + __set_tlb_bus(trident, page, addr); + __set_tlb_bus(trident, page+1, addr + SNDRV_TRIDENT_PAGE_SIZE); +} +static inline void set_silent_tlb(struct snd_trident *trident, int page) +{ + page <<= 1; + __set_tlb_bus(trident, page, trident->tlb.silent_page->addr); + __set_tlb_bus(trident, page+1, trident->tlb.silent_page->addr); +} + +#else +/* arbitrary size */ +#define UNIT_PAGES (PAGE_SIZE / SNDRV_TRIDENT_PAGE_SIZE) +#define ALIGN_PAGE_SIZE (SNDRV_TRIDENT_PAGE_SIZE * UNIT_PAGES) +#define MAX_ALIGN_PAGES (SNDRV_TRIDENT_MAX_PAGES / UNIT_PAGES) +/* Note: if alignment doesn't match to the maximum size, the last few blocks + * become unusable. To use such blocks, you'll need to check the validity + * of accessing page in set_tlb_bus and set_silent_tlb. search_empty() + * should also check it, too. + */ +#define get_aligned_page(offset) ((offset) / ALIGN_PAGE_SIZE) +#define aligned_page_offset(page) ((page) * ALIGN_PAGE_SIZE) +#define page_to_addr(trident,page) __tlb_to_addr(trident, (page) * UNIT_PAGES) + +/* fill TLB entries -- UNIT_PAGES entries must be filled */ +static inline void set_tlb_bus(struct snd_trident *trident, int page, + dma_addr_t addr) +{ + int i; + page *= UNIT_PAGES; + for (i = 0; i < UNIT_PAGES; i++, page++) { + __set_tlb_bus(trident, page, addr); + addr += SNDRV_TRIDENT_PAGE_SIZE; + } +} +static inline void set_silent_tlb(struct snd_trident *trident, int page) +{ + int i; + page *= UNIT_PAGES; + for (i = 0; i < UNIT_PAGES; i++, page++) + __set_tlb_bus(trident, page, trident->tlb.silent_page->addr); +} + +#endif /* PAGE_SIZE */ + +/* first and last (aligned) pages of memory block */ +#define firstpg(blk) (((struct snd_trident_memblk_arg *)snd_util_memblk_argptr(blk))->first_page) +#define lastpg(blk) (((struct snd_trident_memblk_arg *)snd_util_memblk_argptr(blk))->last_page) + +/* + * search empty pages which may contain given size + */ +static struct snd_util_memblk * +search_empty(struct snd_util_memhdr *hdr, int size) +{ + struct snd_util_memblk *blk; + int page, psize; + struct list_head *p; + + psize = get_aligned_page(size + ALIGN_PAGE_SIZE -1); + page = 0; + list_for_each(p, &hdr->block) { + blk = list_entry(p, struct snd_util_memblk, list); + if (page + psize <= firstpg(blk)) + goto __found_pages; + page = lastpg(blk) + 1; + } + if (page + psize > MAX_ALIGN_PAGES) + return NULL; + +__found_pages: + /* create a new memory block */ + blk = __snd_util_memblk_new(hdr, psize * ALIGN_PAGE_SIZE, p->prev); + if (blk == NULL) + return NULL; + blk->offset = aligned_page_offset(page); /* set aligned offset */ + firstpg(blk) = page; + lastpg(blk) = page + psize - 1; + return blk; +} + + +/* + * check if the given pointer is valid for pages + */ +static int is_valid_page(struct snd_trident *trident, unsigned long ptr) +{ + if (ptr & ~0x3fffffffUL) { + dev_err(trident->card->dev, "max memory size is 1GB!!\n"); + return 0; + } + if (ptr & (SNDRV_TRIDENT_PAGE_SIZE-1)) { + dev_err(trident->card->dev, "page is not aligned\n"); + return 0; + } + return 1; +} + +/* + * page allocation for DMA (Scatter-Gather version) + */ +static struct snd_util_memblk * +snd_trident_alloc_sg_pages(struct snd_trident *trident, + struct snd_pcm_substream *substream) +{ + struct snd_util_memhdr *hdr; + struct snd_util_memblk *blk; + struct snd_pcm_runtime *runtime = substream->runtime; + int idx, page; + + if (snd_BUG_ON(runtime->dma_bytes <= 0 || + runtime->dma_bytes > SNDRV_TRIDENT_MAX_PAGES * + SNDRV_TRIDENT_PAGE_SIZE)) + return NULL; + hdr = trident->tlb.memhdr; + if (snd_BUG_ON(!hdr)) + return NULL; + + + + guard(mutex)(&hdr->block_mutex); + blk = search_empty(hdr, runtime->dma_bytes); + if (blk == NULL) + return NULL; + + /* set TLB entries */ + idx = 0; + for (page = firstpg(blk); page <= lastpg(blk); page++, idx++) { + unsigned long ofs = idx << PAGE_SHIFT; + dma_addr_t addr = snd_pcm_sgbuf_get_addr(substream, ofs); + if (!is_valid_page(trident, addr)) { + __snd_util_mem_free(hdr, blk); + return NULL; + } + set_tlb_bus(trident, page, addr); + } + return blk; +} + +/* + * page allocation for DMA (contiguous version) + */ +static struct snd_util_memblk * +snd_trident_alloc_cont_pages(struct snd_trident *trident, + struct snd_pcm_substream *substream) +{ + struct snd_util_memhdr *hdr; + struct snd_util_memblk *blk; + int page; + struct snd_pcm_runtime *runtime = substream->runtime; + dma_addr_t addr; + + if (snd_BUG_ON(runtime->dma_bytes <= 0 || + runtime->dma_bytes > SNDRV_TRIDENT_MAX_PAGES * + SNDRV_TRIDENT_PAGE_SIZE)) + return NULL; + hdr = trident->tlb.memhdr; + if (snd_BUG_ON(!hdr)) + return NULL; + + guard(mutex)(&hdr->block_mutex); + blk = search_empty(hdr, runtime->dma_bytes); + if (blk == NULL) + return NULL; + + /* set TLB entries */ + addr = runtime->dma_addr; + for (page = firstpg(blk); page <= lastpg(blk); page++, + addr += SNDRV_TRIDENT_PAGE_SIZE) { + if (!is_valid_page(trident, addr)) { + __snd_util_mem_free(hdr, blk); + return NULL; + } + set_tlb_bus(trident, page, addr); + } + return blk; +} + +/* + * page allocation for DMA + */ +struct snd_util_memblk * +snd_trident_alloc_pages(struct snd_trident *trident, + struct snd_pcm_substream *substream) +{ + if (snd_BUG_ON(!trident || !substream)) + return NULL; + if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_SG) + return snd_trident_alloc_sg_pages(trident, substream); + else + return snd_trident_alloc_cont_pages(trident, substream); +} + + +/* + * release DMA buffer from page table + */ +int snd_trident_free_pages(struct snd_trident *trident, + struct snd_util_memblk *blk) +{ + struct snd_util_memhdr *hdr; + int page; + + if (snd_BUG_ON(!trident || !blk)) + return -EINVAL; + + hdr = trident->tlb.memhdr; + guard(mutex)(&hdr->block_mutex); + /* reset TLB entries */ + for (page = firstpg(blk); page <= lastpg(blk); page++) + set_silent_tlb(trident, page); + /* free memory block */ + __snd_util_mem_free(hdr, blk); + return 0; +} diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c new file mode 100644 index 000000000..24ee1302f --- /dev/null +++ b/sound/pci/via82xx.c @@ -0,0 +1,2564 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for VIA VT82xx (South Bridge) + * + * VT82C686A/B/C, VT8233A/C, VT8235 + * + * Copyright (c) 2000 Jaroslav Kysela + * Tjeerd.Mulder + * 2002 Takashi Iwai + */ + +/* + * Changes: + * + * Dec. 19, 2002 Takashi Iwai + * - use the DSX channels for the first pcm playback. + * (on VIA8233, 8233C and 8235 only) + * this will allow you play simultaneously up to 4 streams. + * multi-channel playback is assigned to the second device + * on these chips. + * - support the secondary capture (on VIA8233/C,8235) + * - SPDIF support + * the DSX3 channel can be used for SPDIF output. + * on VIA8233A, this channel is assigned to the second pcm + * playback. + * the card config of alsa-lib will assign the correct + * device for applications. + * - clean up the code, separate low-level initialization + * routines for each chipset. + * + * Sep. 26, 2005 Karsten Wiese + * - Optimize position calculation for the 823x chips. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define POINTER_DEBUG +#endif + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("VIA VT82xx audio"); +MODULE_LICENSE("GPL"); + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK 1 +#endif + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static long mpu_port; +#ifdef SUPPORT_JOYSTICK +static bool joystick; +#endif +static int ac97_clock = 48000; +static char *ac97_quirk; +static int dxs_support; +static int dxs_init_volume = 31; +static int nodelay; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for VIA 82xx bridge."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for VIA 82xx bridge."); +module_param_hw(mpu_port, long, ioport, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port. (VT82C686x only)"); +#ifdef SUPPORT_JOYSTICK +module_param(joystick, bool, 0444); +MODULE_PARM_DESC(joystick, "Enable joystick. (VT82C686x only)"); +#endif +module_param(ac97_clock, int, 0444); +MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz)."); +module_param(ac97_quirk, charp, 0444); +MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware."); +module_param(dxs_support, int, 0444); +MODULE_PARM_DESC(dxs_support, "Support for DXS channels (0 = auto, 1 = enable, 2 = disable, 3 = 48k only, 4 = no VRA, 5 = enable any sample rate)"); +module_param(dxs_init_volume, int, 0644); +MODULE_PARM_DESC(dxs_init_volume, "initial DXS volume (0-31)"); +module_param(nodelay, int, 0444); +MODULE_PARM_DESC(nodelay, "Disable 500ms init delay"); + +/* just for backward compatibility */ +static bool enable; +module_param(enable, bool, 0444); + + +/* revision numbers for via686 */ +#define VIA_REV_686_A 0x10 +#define VIA_REV_686_B 0x11 +#define VIA_REV_686_C 0x12 +#define VIA_REV_686_D 0x13 +#define VIA_REV_686_E 0x14 +#define VIA_REV_686_H 0x20 + +/* revision numbers for via8233 */ +#define VIA_REV_PRE_8233 0x10 /* not in market */ +#define VIA_REV_8233C 0x20 /* 2 rec, 4 pb, 1 multi-pb */ +#define VIA_REV_8233 0x30 /* 2 rec, 4 pb, 1 multi-pb, spdif */ +#define VIA_REV_8233A 0x40 /* 1 rec, 1 multi-pb, spdf */ +#define VIA_REV_8235 0x50 /* 2 rec, 4 pb, 1 multi-pb, spdif */ +#define VIA_REV_8237 0x60 +#define VIA_REV_8251 0x70 + +/* + * Direct registers + */ + +#define VIAREG(via, x) ((via)->port + VIA_REG_##x) +#define VIADEV_REG(viadev, x) ((viadev)->port + VIA_REG_##x) + +/* common offsets */ +#define VIA_REG_OFFSET_STATUS 0x00 /* byte - channel status */ +#define VIA_REG_STAT_ACTIVE 0x80 /* RO */ +#define VIA8233_SHADOW_STAT_ACTIVE 0x08 /* RO */ +#define VIA_REG_STAT_PAUSED 0x40 /* RO */ +#define VIA_REG_STAT_TRIGGER_QUEUED 0x08 /* RO */ +#define VIA_REG_STAT_STOPPED 0x04 /* RWC */ +#define VIA_REG_STAT_EOL 0x02 /* RWC */ +#define VIA_REG_STAT_FLAG 0x01 /* RWC */ +#define VIA_REG_OFFSET_CONTROL 0x01 /* byte - channel control */ +#define VIA_REG_CTRL_START 0x80 /* WO */ +#define VIA_REG_CTRL_TERMINATE 0x40 /* WO */ +#define VIA_REG_CTRL_AUTOSTART 0x20 +#define VIA_REG_CTRL_PAUSE 0x08 /* RW */ +#define VIA_REG_CTRL_INT_STOP 0x04 +#define VIA_REG_CTRL_INT_EOL 0x02 +#define VIA_REG_CTRL_INT_FLAG 0x01 +#define VIA_REG_CTRL_RESET 0x01 /* RW - probably reset? undocumented */ +#define VIA_REG_CTRL_INT (VIA_REG_CTRL_INT_FLAG | VIA_REG_CTRL_INT_EOL | VIA_REG_CTRL_AUTOSTART) +#define VIA_REG_OFFSET_TYPE 0x02 /* byte - channel type (686 only) */ +#define VIA_REG_TYPE_AUTOSTART 0x80 /* RW - autostart at EOL */ +#define VIA_REG_TYPE_16BIT 0x20 /* RW */ +#define VIA_REG_TYPE_STEREO 0x10 /* RW */ +#define VIA_REG_TYPE_INT_LLINE 0x00 +#define VIA_REG_TYPE_INT_LSAMPLE 0x04 +#define VIA_REG_TYPE_INT_LESSONE 0x08 +#define VIA_REG_TYPE_INT_MASK 0x0c +#define VIA_REG_TYPE_INT_EOL 0x02 +#define VIA_REG_TYPE_INT_FLAG 0x01 +#define VIA_REG_OFFSET_TABLE_PTR 0x04 /* dword - channel table pointer */ +#define VIA_REG_OFFSET_CURR_PTR 0x04 /* dword - channel current pointer */ +#define VIA_REG_OFFSET_STOP_IDX 0x08 /* dword - stop index, channel type, sample rate */ +#define VIA8233_REG_TYPE_16BIT 0x00200000 /* RW */ +#define VIA8233_REG_TYPE_STEREO 0x00100000 /* RW */ +#define VIA_REG_OFFSET_CURR_COUNT 0x0c /* dword - channel current count (24 bit) */ +#define VIA_REG_OFFSET_CURR_INDEX 0x0f /* byte - channel current index (for via8233 only) */ + +#define DEFINE_VIA_REGSET(name,val) \ +enum {\ + VIA_REG_##name##_STATUS = (val),\ + VIA_REG_##name##_CONTROL = (val) + 0x01,\ + VIA_REG_##name##_TYPE = (val) + 0x02,\ + VIA_REG_##name##_TABLE_PTR = (val) + 0x04,\ + VIA_REG_##name##_CURR_PTR = (val) + 0x04,\ + VIA_REG_##name##_STOP_IDX = (val) + 0x08,\ + VIA_REG_##name##_CURR_COUNT = (val) + 0x0c,\ +} + +/* playback block */ +DEFINE_VIA_REGSET(PLAYBACK, 0x00); +DEFINE_VIA_REGSET(CAPTURE, 0x10); +DEFINE_VIA_REGSET(FM, 0x20); + +/* AC'97 */ +#define VIA_REG_AC97 0x80 /* dword */ +#define VIA_REG_AC97_CODEC_ID_MASK (3<<30) +#define VIA_REG_AC97_CODEC_ID_SHIFT 30 +#define VIA_REG_AC97_CODEC_ID_PRIMARY 0x00 +#define VIA_REG_AC97_CODEC_ID_SECONDARY 0x01 +#define VIA_REG_AC97_SECONDARY_VALID (1<<27) +#define VIA_REG_AC97_PRIMARY_VALID (1<<25) +#define VIA_REG_AC97_BUSY (1<<24) +#define VIA_REG_AC97_READ (1<<23) +#define VIA_REG_AC97_CMD_SHIFT 16 +#define VIA_REG_AC97_CMD_MASK 0x7e +#define VIA_REG_AC97_DATA_SHIFT 0 +#define VIA_REG_AC97_DATA_MASK 0xffff + +#define VIA_REG_SGD_SHADOW 0x84 /* dword */ +/* via686 */ +#define VIA_REG_SGD_STAT_PB_FLAG (1<<0) +#define VIA_REG_SGD_STAT_CP_FLAG (1<<1) +#define VIA_REG_SGD_STAT_FM_FLAG (1<<2) +#define VIA_REG_SGD_STAT_PB_EOL (1<<4) +#define VIA_REG_SGD_STAT_CP_EOL (1<<5) +#define VIA_REG_SGD_STAT_FM_EOL (1<<6) +#define VIA_REG_SGD_STAT_PB_STOP (1<<8) +#define VIA_REG_SGD_STAT_CP_STOP (1<<9) +#define VIA_REG_SGD_STAT_FM_STOP (1<<10) +#define VIA_REG_SGD_STAT_PB_ACTIVE (1<<12) +#define VIA_REG_SGD_STAT_CP_ACTIVE (1<<13) +#define VIA_REG_SGD_STAT_FM_ACTIVE (1<<14) +/* via8233 */ +#define VIA8233_REG_SGD_STAT_FLAG (1<<0) +#define VIA8233_REG_SGD_STAT_EOL (1<<1) +#define VIA8233_REG_SGD_STAT_STOP (1<<2) +#define VIA8233_REG_SGD_STAT_ACTIVE (1<<3) +#define VIA8233_INTR_MASK(chan) ((VIA8233_REG_SGD_STAT_FLAG|VIA8233_REG_SGD_STAT_EOL) << ((chan) * 4)) +#define VIA8233_REG_SGD_CHAN_SDX 0 +#define VIA8233_REG_SGD_CHAN_MULTI 4 +#define VIA8233_REG_SGD_CHAN_REC 6 +#define VIA8233_REG_SGD_CHAN_REC1 7 + +#define VIA_REG_GPI_STATUS 0x88 +#define VIA_REG_GPI_INTR 0x8c + +/* multi-channel and capture registers for via8233 */ +DEFINE_VIA_REGSET(MULTPLAY, 0x40); +DEFINE_VIA_REGSET(CAPTURE_8233, 0x60); + +/* via8233-specific registers */ +#define VIA_REG_OFS_PLAYBACK_VOLUME_L 0x02 /* byte */ +#define VIA_REG_OFS_PLAYBACK_VOLUME_R 0x03 /* byte */ +#define VIA_REG_OFS_MULTPLAY_FORMAT 0x02 /* byte - format and channels */ +#define VIA_REG_MULTPLAY_FMT_8BIT 0x00 +#define VIA_REG_MULTPLAY_FMT_16BIT 0x80 +#define VIA_REG_MULTPLAY_FMT_CH_MASK 0x70 /* # channels << 4 (valid = 1,2,4,6) */ +#define VIA_REG_OFS_CAPTURE_FIFO 0x02 /* byte - bit 6 = fifo enable */ +#define VIA_REG_CAPTURE_FIFO_ENABLE 0x40 + +#define VIA_DXS_MAX_VOLUME 31 /* max. volume (attenuation) of reg 0x32/33 */ + +#define VIA_REG_CAPTURE_CHANNEL 0x63 /* byte - input select */ +#define VIA_REG_CAPTURE_CHANNEL_MIC 0x4 +#define VIA_REG_CAPTURE_CHANNEL_LINE 0 +#define VIA_REG_CAPTURE_SELECT_CODEC 0x03 /* recording source codec (0 = primary) */ + +#define VIA_TBL_BIT_FLAG 0x40000000 +#define VIA_TBL_BIT_EOL 0x80000000 + +/* pci space */ +#define VIA_ACLINK_STAT 0x40 +#define VIA_ACLINK_C11_READY 0x20 +#define VIA_ACLINK_C10_READY 0x10 +#define VIA_ACLINK_C01_READY 0x04 /* secondary codec ready */ +#define VIA_ACLINK_LOWPOWER 0x02 /* low-power state */ +#define VIA_ACLINK_C00_READY 0x01 /* primary codec ready */ +#define VIA_ACLINK_CTRL 0x41 +#define VIA_ACLINK_CTRL_ENABLE 0x80 /* 0: disable, 1: enable */ +#define VIA_ACLINK_CTRL_RESET 0x40 /* 0: assert, 1: de-assert */ +#define VIA_ACLINK_CTRL_SYNC 0x20 /* 0: release SYNC, 1: force SYNC hi */ +#define VIA_ACLINK_CTRL_SDO 0x10 /* 0: release SDO, 1: force SDO hi */ +#define VIA_ACLINK_CTRL_VRA 0x08 /* 0: disable VRA, 1: enable VRA */ +#define VIA_ACLINK_CTRL_PCM 0x04 /* 0: disable PCM, 1: enable PCM */ +#define VIA_ACLINK_CTRL_FM 0x02 /* via686 only */ +#define VIA_ACLINK_CTRL_SB 0x01 /* via686 only */ +#define VIA_ACLINK_CTRL_INIT (VIA_ACLINK_CTRL_ENABLE|\ + VIA_ACLINK_CTRL_RESET|\ + VIA_ACLINK_CTRL_PCM|\ + VIA_ACLINK_CTRL_VRA) +#define VIA_FUNC_ENABLE 0x42 +#define VIA_FUNC_MIDI_PNP 0x80 /* FIXME: it's 0x40 in the datasheet! */ +#define VIA_FUNC_MIDI_IRQMASK 0x40 /* FIXME: not documented! */ +#define VIA_FUNC_RX2C_WRITE 0x20 +#define VIA_FUNC_SB_FIFO_EMPTY 0x10 +#define VIA_FUNC_ENABLE_GAME 0x08 +#define VIA_FUNC_ENABLE_FM 0x04 +#define VIA_FUNC_ENABLE_MIDI 0x02 +#define VIA_FUNC_ENABLE_SB 0x01 +#define VIA_PNP_CONTROL 0x43 +#define VIA_FM_NMI_CTRL 0x48 +#define VIA8233_VOLCHG_CTRL 0x48 +#define VIA8233_SPDIF_CTRL 0x49 +#define VIA8233_SPDIF_DX3 0x08 +#define VIA8233_SPDIF_SLOT_MASK 0x03 +#define VIA8233_SPDIF_SLOT_1011 0x00 +#define VIA8233_SPDIF_SLOT_34 0x01 +#define VIA8233_SPDIF_SLOT_78 0x02 +#define VIA8233_SPDIF_SLOT_69 0x03 + +/* + */ + +#define VIA_DXS_AUTO 0 +#define VIA_DXS_ENABLE 1 +#define VIA_DXS_DISABLE 2 +#define VIA_DXS_48K 3 +#define VIA_DXS_NO_VRA 4 +#define VIA_DXS_SRC 5 + + +/* + * pcm stream + */ + +struct snd_via_sg_table { + unsigned int offset; + unsigned int size; +} ; + +#define VIA_TABLE_SIZE 255 +#define VIA_MAX_BUFSIZE (1<<24) + +struct viadev { + unsigned int reg_offset; + unsigned long port; + int direction; /* playback = 0, capture = 1 */ + struct snd_pcm_substream *substream; + int running; + unsigned int tbl_entries; /* # descriptors */ + struct snd_dma_buffer table; + struct snd_via_sg_table *idx_table; + /* for recovery from the unexpected pointer */ + unsigned int lastpos; + unsigned int fragsize; + unsigned int bufsize; + unsigned int bufsize2; + int hwptr_done; /* processed frame position in the buffer */ + int in_interrupt; + int shadow_shift; +}; + + +enum { TYPE_CARD_VIA686 = 1, TYPE_CARD_VIA8233 }; +enum { TYPE_VIA686, TYPE_VIA8233, TYPE_VIA8233A }; + +#define VIA_MAX_DEVS 7 /* 4 playback, 1 multi, 2 capture */ + +struct via_rate_lock { + spinlock_t lock; + int rate; + int used; +}; + +struct via82xx { + int irq; + + unsigned long port; + struct resource *mpu_res; + int chip_type; + unsigned char revision; + + unsigned char old_legacy; + unsigned char old_legacy_cfg; + unsigned char legacy_saved; + unsigned char legacy_cfg_saved; + unsigned char spdif_ctrl_saved; + unsigned char capture_src_saved[2]; + unsigned int mpu_port_saved; + + unsigned char playback_volume[4][2]; /* for VIA8233/C/8235; default = 0 */ + unsigned char playback_volume_c[2]; /* for VIA8233/C/8235; default = 0 */ + + unsigned int intr_mask; /* SGD_SHADOW mask to check interrupts */ + + struct pci_dev *pci; + struct snd_card *card; + + unsigned int num_devs; + unsigned int playback_devno, multi_devno, capture_devno; + struct viadev devs[VIA_MAX_DEVS]; + struct via_rate_lock rates[2]; /* playback and capture */ + unsigned int dxs_fixed: 1; /* DXS channel accepts only 48kHz */ + unsigned int no_vra: 1; /* no need to set VRA on DXS channels */ + unsigned int dxs_src: 1; /* use full SRC capabilities of DXS */ + unsigned int spdif_on: 1; /* only spdif rates work to external DACs */ + + struct snd_pcm *pcms[2]; + struct snd_rawmidi *rmidi; + struct snd_kcontrol *dxs_controls[4]; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + unsigned int ac97_clock; + unsigned int ac97_secondary; /* secondary AC'97 codec is present */ + + spinlock_t reg_lock; + struct snd_info_entry *proc_entry; + +#ifdef SUPPORT_JOYSTICK + struct gameport *gameport; +#endif +}; + +static const struct pci_device_id snd_via82xx_ids[] = { + /* 0x1106, 0x3058 */ + { PCI_VDEVICE(VIA, PCI_DEVICE_ID_VIA_82C686_5), .driver_data = TYPE_CARD_VIA686 }, /* 686A */ + /* 0x1106, 0x3059 */ + { PCI_VDEVICE(VIA, PCI_DEVICE_ID_VIA_8233_5), .driver_data = TYPE_CARD_VIA8233 }, /* VT8233 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_via82xx_ids); + +/* + */ + +/* + * allocate and initialize the descriptor buffers + * periods = number of periods + * fragsize = period size in bytes + */ +static int build_via_table(struct viadev *dev, struct snd_pcm_substream *substream, + struct pci_dev *pci, + unsigned int periods, unsigned int fragsize) +{ + unsigned int i, idx, ofs, rest; + struct via82xx *chip = snd_pcm_substream_chip(substream); + __le32 *pgtbl; + + if (dev->table.area == NULL) { + /* the start of each lists must be aligned to 8 bytes, + * but the kernel pages are much bigger, so we don't care + */ + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + PAGE_ALIGN(VIA_TABLE_SIZE * 2 * 8), + &dev->table) < 0) + return -ENOMEM; + } + if (! dev->idx_table) { + dev->idx_table = kmalloc_objs(*dev->idx_table, VIA_TABLE_SIZE); + if (! dev->idx_table) + return -ENOMEM; + } + + /* fill the entries */ + idx = 0; + ofs = 0; + pgtbl = (__le32 *)dev->table.area; + for (i = 0; i < periods; i++) { + rest = fragsize; + /* fill descriptors for a period. + * a period can be split to several descriptors if it's + * over page boundary. + */ + do { + unsigned int r; + unsigned int flag; + unsigned int addr; + + if (idx >= VIA_TABLE_SIZE) { + dev_err(&pci->dev, "too much table size!\n"); + return -EINVAL; + } + addr = snd_pcm_sgbuf_get_addr(substream, ofs); + pgtbl[idx << 1] = cpu_to_le32(addr); + r = snd_pcm_sgbuf_get_chunk_size(substream, ofs, rest); + rest -= r; + if (! rest) { + if (i == periods - 1) + flag = VIA_TBL_BIT_EOL; /* buffer boundary */ + else + flag = VIA_TBL_BIT_FLAG; /* period boundary */ + } else + flag = 0; /* period continues to the next */ + /* + dev_dbg(&pci->dev, + "tbl %d: at %d size %d (rest %d)\n", + idx, ofs, r, rest); + */ + pgtbl[(idx<<1) + 1] = cpu_to_le32(r | flag); + dev->idx_table[idx].offset = ofs; + dev->idx_table[idx].size = r; + ofs += r; + idx++; + } while (rest > 0); + } + dev->tbl_entries = idx; + dev->bufsize = periods * fragsize; + dev->bufsize2 = dev->bufsize / 2; + dev->fragsize = fragsize; + return 0; +} + + +static int clean_via_table(struct viadev *dev, struct snd_pcm_substream *substream, + struct pci_dev *pci) +{ + if (dev->table.area) { + snd_dma_free_pages(&dev->table); + dev->table.area = NULL; + } + kfree(dev->idx_table); + dev->idx_table = NULL; + return 0; +} + +/* + * Basic I/O + */ + +static inline unsigned int snd_via82xx_codec_xread(struct via82xx *chip) +{ + return inl(VIAREG(chip, AC97)); +} + +static inline void snd_via82xx_codec_xwrite(struct via82xx *chip, unsigned int val) +{ + outl(val, VIAREG(chip, AC97)); +} + +static int snd_via82xx_codec_ready(struct via82xx *chip, int secondary) +{ + unsigned int timeout = 1000; /* 1ms */ + unsigned int val; + + while (timeout-- > 0) { + udelay(1); + val = snd_via82xx_codec_xread(chip); + if (!(val & VIA_REG_AC97_BUSY)) + return val & 0xffff; + } + dev_err(chip->card->dev, "codec_ready: codec %i is not ready [0x%x]\n", + secondary, snd_via82xx_codec_xread(chip)); + return -EIO; +} + +static int snd_via82xx_codec_valid(struct via82xx *chip, int secondary) +{ + unsigned int timeout = 1000; /* 1ms */ + unsigned int val, val1; + unsigned int stat = !secondary ? VIA_REG_AC97_PRIMARY_VALID : + VIA_REG_AC97_SECONDARY_VALID; + + while (timeout-- > 0) { + val = snd_via82xx_codec_xread(chip); + val1 = val & (VIA_REG_AC97_BUSY | stat); + if (val1 == stat) + return val & 0xffff; + udelay(1); + } + return -EIO; +} + +static void snd_via82xx_codec_wait(struct snd_ac97 *ac97) +{ + struct via82xx *chip = ac97->private_data; + __always_unused int err; + err = snd_via82xx_codec_ready(chip, ac97->num); + /* here we need to wait fairly for long time.. */ + if (!nodelay) + msleep(500); +} + +static void snd_via82xx_codec_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct via82xx *chip = ac97->private_data; + unsigned int xval; + + xval = !ac97->num ? VIA_REG_AC97_CODEC_ID_PRIMARY : VIA_REG_AC97_CODEC_ID_SECONDARY; + xval <<= VIA_REG_AC97_CODEC_ID_SHIFT; + xval |= reg << VIA_REG_AC97_CMD_SHIFT; + xval |= val << VIA_REG_AC97_DATA_SHIFT; + snd_via82xx_codec_xwrite(chip, xval); + snd_via82xx_codec_ready(chip, ac97->num); +} + +static unsigned short snd_via82xx_codec_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct via82xx *chip = ac97->private_data; + unsigned int xval, val = 0xffff; + int again = 0; + + xval = ac97->num << VIA_REG_AC97_CODEC_ID_SHIFT; + xval |= ac97->num ? VIA_REG_AC97_SECONDARY_VALID : VIA_REG_AC97_PRIMARY_VALID; + xval |= VIA_REG_AC97_READ; + xval |= (reg & 0x7f) << VIA_REG_AC97_CMD_SHIFT; + while (1) { + if (again++ > 3) { + dev_err(chip->card->dev, + "codec_read: codec %i is not valid [0x%x]\n", + ac97->num, snd_via82xx_codec_xread(chip)); + return 0xffff; + } + snd_via82xx_codec_xwrite(chip, xval); + udelay (20); + if (snd_via82xx_codec_valid(chip, ac97->num) >= 0) { + udelay(25); + val = snd_via82xx_codec_xread(chip); + break; + } + } + return val & 0xffff; +} + +static void snd_via82xx_channel_reset(struct via82xx *chip, struct viadev *viadev) +{ + outb(VIA_REG_CTRL_PAUSE | VIA_REG_CTRL_TERMINATE | VIA_REG_CTRL_RESET, + VIADEV_REG(viadev, OFFSET_CONTROL)); + inb(VIADEV_REG(viadev, OFFSET_CONTROL)); + udelay(50); + /* disable interrupts */ + outb(0x00, VIADEV_REG(viadev, OFFSET_CONTROL)); + /* clear interrupts */ + outb(0x03, VIADEV_REG(viadev, OFFSET_STATUS)); + outb(0x00, VIADEV_REG(viadev, OFFSET_TYPE)); /* for via686 */ + // outl(0, VIADEV_REG(viadev, OFFSET_CURR_PTR)); + viadev->lastpos = 0; + viadev->hwptr_done = 0; +} + + +/* + * Interrupt handler + * Used for 686 and 8233A + */ +static irqreturn_t snd_via686_interrupt(int irq, void *dev_id) +{ + struct via82xx *chip = dev_id; + unsigned int status; + unsigned int i; + + status = inl(VIAREG(chip, SGD_SHADOW)); + if (! (status & chip->intr_mask)) { + if (chip->rmidi) + /* check mpu401 interrupt */ + return snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); + return IRQ_NONE; + } + + /* check status for each stream */ + guard(spinlock)(&chip->reg_lock); + for (i = 0; i < chip->num_devs; i++) { + struct viadev *viadev = &chip->devs[i]; + unsigned char c_status = inb(VIADEV_REG(viadev, OFFSET_STATUS)); + if (! (c_status & (VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG|VIA_REG_STAT_STOPPED))) + continue; + if (viadev->substream && viadev->running) { + /* + * Update hwptr_done based on 'period elapsed' + * interrupts. We'll use it, when the chip returns 0 + * for OFFSET_CURR_COUNT. + */ + if (c_status & VIA_REG_STAT_EOL) + viadev->hwptr_done = 0; + else + viadev->hwptr_done += viadev->fragsize; + viadev->in_interrupt = c_status; + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(viadev->substream); + spin_lock(&chip->reg_lock); + viadev->in_interrupt = 0; + } + outb(c_status, VIADEV_REG(viadev, OFFSET_STATUS)); /* ack */ + } + return IRQ_HANDLED; +} + +/* + * Interrupt handler + */ +static irqreturn_t snd_via8233_interrupt(int irq, void *dev_id) +{ + struct via82xx *chip = dev_id; + unsigned int status; + unsigned int i; + int irqreturn = 0; + + /* check status for each stream */ + guard(spinlock)(&chip->reg_lock); + status = inl(VIAREG(chip, SGD_SHADOW)); + + for (i = 0; i < chip->num_devs; i++) { + struct viadev *viadev = &chip->devs[i]; + struct snd_pcm_substream *substream; + unsigned char c_status, shadow_status; + + shadow_status = (status >> viadev->shadow_shift) & + (VIA8233_SHADOW_STAT_ACTIVE|VIA_REG_STAT_EOL| + VIA_REG_STAT_FLAG); + c_status = shadow_status & (VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG); + if (!c_status) + continue; + + substream = viadev->substream; + if (substream && viadev->running) { + /* + * Update hwptr_done based on 'period elapsed' + * interrupts. We'll use it, when the chip returns 0 + * for OFFSET_CURR_COUNT. + */ + if (c_status & VIA_REG_STAT_EOL) + viadev->hwptr_done = 0; + else + viadev->hwptr_done += viadev->fragsize; + viadev->in_interrupt = c_status; + if (shadow_status & VIA8233_SHADOW_STAT_ACTIVE) + viadev->in_interrupt |= VIA_REG_STAT_ACTIVE; + spin_unlock(&chip->reg_lock); + + snd_pcm_period_elapsed(substream); + + spin_lock(&chip->reg_lock); + viadev->in_interrupt = 0; + } + outb(c_status, VIADEV_REG(viadev, OFFSET_STATUS)); /* ack */ + irqreturn = 1; + } + return IRQ_RETVAL(irqreturn); +} + +/* + * PCM callbacks + */ + +/* + * trigger callback + */ +static int snd_via82xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + unsigned char val; + + if (chip->chip_type != TYPE_VIA686) + val = VIA_REG_CTRL_INT; + else + val = 0; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + val |= VIA_REG_CTRL_START; + viadev->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + val = VIA_REG_CTRL_TERMINATE; + viadev->running = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val |= VIA_REG_CTRL_PAUSE; + viadev->running = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + viadev->running = 1; + break; + default: + return -EINVAL; + } + outb(val, VIADEV_REG(viadev, OFFSET_CONTROL)); + if (cmd == SNDRV_PCM_TRIGGER_STOP) + snd_via82xx_channel_reset(chip, viadev); + return 0; +} + + +/* + * pointer callbacks + */ + +/* + * calculate the linear position at the given sg-buffer index and the rest count + */ + +#define check_invalid_pos(viadev,pos) \ + ((pos) < viadev->lastpos && ((pos) >= viadev->bufsize2 ||\ + viadev->lastpos < viadev->bufsize2)) + +static inline unsigned int calc_linear_pos(struct via82xx *chip, + struct viadev *viadev, + unsigned int idx, + unsigned int count) +{ + unsigned int size, base, res; + + size = viadev->idx_table[idx].size; + base = viadev->idx_table[idx].offset; + res = base + size - count; + if (res >= viadev->bufsize) + res -= viadev->bufsize; + + /* check the validity of the calculated position */ + if (size < count) { + dev_dbg(chip->card->dev, + "invalid via82xx_cur_ptr (size = %d, count = %d)\n", + (int)size, (int)count); + res = viadev->lastpos; + } else { + if (! count) { + /* Some mobos report count = 0 on the DMA boundary, + * i.e. count = size indeed. + * Let's check whether this step is above the expected size. + */ + int delta = res - viadev->lastpos; + if (delta < 0) + delta += viadev->bufsize; + if ((unsigned int)delta > viadev->fragsize) + res = base; + } + if (check_invalid_pos(viadev, res)) { +#ifdef POINTER_DEBUG + dev_dbg(chip->card->dev, + "fail: idx = %i/%i, lastpos = 0x%x, bufsize2 = 0x%x, offsize = 0x%x, size = 0x%x, count = 0x%x\n", + idx, viadev->tbl_entries, + viadev->lastpos, viadev->bufsize2, + viadev->idx_table[idx].offset, + viadev->idx_table[idx].size, count); +#endif + /* count register returns full size when end of buffer is reached */ + res = base + size; + if (check_invalid_pos(viadev, res)) { + dev_dbg(chip->card->dev, + "invalid via82xx_cur_ptr (2), using last valid pointer\n"); + res = viadev->lastpos; + } + } + } + return res; +} + +/* + * get the current pointer on via686 + */ +static snd_pcm_uframes_t snd_via686_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + unsigned int idx, ptr, count, res; + + if (snd_BUG_ON(!viadev->tbl_entries)) + return 0; + if (!(inb(VIADEV_REG(viadev, OFFSET_STATUS)) & VIA_REG_STAT_ACTIVE)) + return 0; + + guard(spinlock)(&chip->reg_lock); + count = inl(VIADEV_REG(viadev, OFFSET_CURR_COUNT)) & 0xffffff; + /* The via686a does not have the current index register, + * so we need to calculate the index from CURR_PTR. + */ + ptr = inl(VIADEV_REG(viadev, OFFSET_CURR_PTR)); + if (ptr <= (unsigned int)viadev->table.addr) + idx = 0; + else /* CURR_PTR holds the address + 8 */ + idx = ((ptr - (unsigned int)viadev->table.addr) / 8 - 1) % viadev->tbl_entries; + res = calc_linear_pos(chip, viadev, idx, count); + viadev->lastpos = res; /* remember the last position */ + + return bytes_to_frames(substream->runtime, res); +} + +/* + * get the current pointer on via823x + */ +static snd_pcm_uframes_t snd_via8233_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + unsigned int idx, count, res; + int status; + + if (snd_BUG_ON(!viadev->tbl_entries)) + return 0; + + guard(spinlock)(&chip->reg_lock); + count = inl(VIADEV_REG(viadev, OFFSET_CURR_COUNT)); + status = viadev->in_interrupt; + if (!status) + status = inb(VIADEV_REG(viadev, OFFSET_STATUS)); + + /* An apparent bug in the 8251 is worked around by sending a + * REG_CTRL_START. */ + if (chip->revision == VIA_REV_8251 && (status & VIA_REG_STAT_EOL)) + snd_via82xx_pcm_trigger(substream, SNDRV_PCM_TRIGGER_START); + + if (!(status & VIA_REG_STAT_ACTIVE)) { + res = 0; + goto unlock; + } + if (count & 0xffffff) { + idx = count >> 24; + if (idx >= viadev->tbl_entries) { +#ifdef POINTER_DEBUG + dev_dbg(chip->card->dev, + "fail: invalid idx = %i/%i\n", idx, + viadev->tbl_entries); +#endif + res = viadev->lastpos; + } else { + count &= 0xffffff; + res = calc_linear_pos(chip, viadev, idx, count); + } + } else { + res = viadev->hwptr_done; + if (!viadev->in_interrupt) { + if (status & VIA_REG_STAT_EOL) { + res = 0; + } else + if (status & VIA_REG_STAT_FLAG) { + res += viadev->fragsize; + } + } + } +unlock: + viadev->lastpos = res; + + return bytes_to_frames(substream->runtime, res); +} + + +/* + * hw_params callback: + * allocate the buffer and build up the buffer description table + */ +static int snd_via82xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + + return build_via_table(viadev, substream, chip->pci, + params_periods(hw_params), + params_period_bytes(hw_params)); +} + +/* + * hw_free callback: + * clean up the buffer description table and release the buffer + */ +static int snd_via82xx_hw_free(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + + clean_via_table(viadev, substream, chip->pci); + return 0; +} + + +/* + * set up the table pointer + */ +static void snd_via82xx_set_table_ptr(struct via82xx *chip, struct viadev *viadev) +{ + snd_via82xx_codec_ready(chip, 0); + outl((u32)viadev->table.addr, VIADEV_REG(viadev, OFFSET_TABLE_PTR)); + udelay(20); + snd_via82xx_codec_ready(chip, 0); +} + +/* + * prepare callback for playback and capture on via686 + */ +static void via686_setup_format(struct via82xx *chip, struct viadev *viadev, + struct snd_pcm_runtime *runtime) +{ + snd_via82xx_channel_reset(chip, viadev); + /* this must be set after channel_reset */ + snd_via82xx_set_table_ptr(chip, viadev); + outb(VIA_REG_TYPE_AUTOSTART | + (runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA_REG_TYPE_16BIT : 0) | + (runtime->channels > 1 ? VIA_REG_TYPE_STEREO : 0) | + ((viadev->reg_offset & 0x10) == 0 ? VIA_REG_TYPE_INT_LSAMPLE : 0) | + VIA_REG_TYPE_INT_EOL | + VIA_REG_TYPE_INT_FLAG, VIADEV_REG(viadev, OFFSET_TYPE)); +} + +static int snd_via686_playback_prepare(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate); + snd_ac97_set_rate(chip->ac97, AC97_SPDIF, runtime->rate); + via686_setup_format(chip, viadev, runtime); + return 0; +} + +static int snd_via686_capture_prepare(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_ac97_set_rate(chip->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate); + via686_setup_format(chip, viadev, runtime); + return 0; +} + +/* + * lock the current rate + */ +static int via_lock_rate(struct via_rate_lock *rec, int rate) +{ + int changed = 0; + + guard(spinlock_irq)(&rec->lock); + if (rec->rate != rate) { + if (rec->rate && rec->used > 1) /* already set */ + changed = -EINVAL; + else { + rec->rate = rate; + changed = 1; + } + } + return changed; +} + +/* + * prepare callback for DSX playback on via823x + */ +static int snd_via8233_playback_prepare(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int ac97_rate = chip->dxs_src ? 48000 : runtime->rate; + int rate_changed; + u32 rbits; + + rate_changed = via_lock_rate(&chip->rates[0], ac97_rate); + if (rate_changed < 0) + return rate_changed; + if (rate_changed) + snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE, + chip->no_vra ? 48000 : runtime->rate); + if (chip->spdif_on && viadev->reg_offset == 0x30) + snd_ac97_set_rate(chip->ac97, AC97_SPDIF, runtime->rate); + + if (runtime->rate == 48000) + rbits = 0xfffff; + else + rbits = (0x100000 / 48000) * runtime->rate + + ((0x100000 % 48000) * runtime->rate) / 48000; + snd_BUG_ON(rbits & ~0xfffff); + snd_via82xx_channel_reset(chip, viadev); + snd_via82xx_set_table_ptr(chip, viadev); + outb(chip->playback_volume[viadev->reg_offset / 0x10][0], + VIADEV_REG(viadev, OFS_PLAYBACK_VOLUME_L)); + outb(chip->playback_volume[viadev->reg_offset / 0x10][1], + VIADEV_REG(viadev, OFS_PLAYBACK_VOLUME_R)); + outl((runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA8233_REG_TYPE_16BIT : 0) | /* format */ + (runtime->channels > 1 ? VIA8233_REG_TYPE_STEREO : 0) | /* stereo */ + rbits | /* rate */ + 0xff000000, /* STOP index is never reached */ + VIADEV_REG(viadev, OFFSET_STOP_IDX)); + udelay(20); + snd_via82xx_codec_ready(chip, 0); + return 0; +} + +/* + * prepare callback for multi-channel playback on via823x + */ +static int snd_via8233_multi_prepare(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int slots; + int fmt; + + if (via_lock_rate(&chip->rates[0], runtime->rate) < 0) + return -EINVAL; + snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate); + snd_ac97_set_rate(chip->ac97, AC97_PCM_SURR_DAC_RATE, runtime->rate); + snd_ac97_set_rate(chip->ac97, AC97_PCM_LFE_DAC_RATE, runtime->rate); + snd_ac97_set_rate(chip->ac97, AC97_SPDIF, runtime->rate); + snd_via82xx_channel_reset(chip, viadev); + snd_via82xx_set_table_ptr(chip, viadev); + + fmt = (runtime->format == SNDRV_PCM_FORMAT_S16_LE) ? + VIA_REG_MULTPLAY_FMT_16BIT : VIA_REG_MULTPLAY_FMT_8BIT; + fmt |= runtime->channels << 4; + outb(fmt, VIADEV_REG(viadev, OFS_MULTPLAY_FORMAT)); +#if 0 + if (chip->revision == VIA_REV_8233A) + slots = 0; + else +#endif + { + /* set sample number to slot 3, 4, 7, 8, 6, 9 (for VIA8233/C,8235) */ + /* corresponding to FL, FR, RL, RR, C, LFE ?? */ + switch (runtime->channels) { + case 1: slots = (1<<0) | (1<<4); break; + case 2: slots = (1<<0) | (2<<4); break; + case 3: slots = (1<<0) | (2<<4) | (5<<8); break; + case 4: slots = (1<<0) | (2<<4) | (3<<8) | (4<<12); break; + case 5: slots = (1<<0) | (2<<4) | (3<<8) | (4<<12) | (5<<16); break; + case 6: slots = (1<<0) | (2<<4) | (3<<8) | (4<<12) | (5<<16) | (6<<20); break; + default: slots = 0; break; + } + } + /* STOP index is never reached */ + outl(0xff000000 | slots, VIADEV_REG(viadev, OFFSET_STOP_IDX)); + udelay(20); + snd_via82xx_codec_ready(chip, 0); + return 0; +} + +/* + * prepare callback for capture on via823x + */ +static int snd_via8233_capture_prepare(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (via_lock_rate(&chip->rates[1], runtime->rate) < 0) + return -EINVAL; + snd_ac97_set_rate(chip->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate); + snd_via82xx_channel_reset(chip, viadev); + snd_via82xx_set_table_ptr(chip, viadev); + outb(VIA_REG_CAPTURE_FIFO_ENABLE, VIADEV_REG(viadev, OFS_CAPTURE_FIFO)); + outl((runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA8233_REG_TYPE_16BIT : 0) | + (runtime->channels > 1 ? VIA8233_REG_TYPE_STEREO : 0) | + 0xff000000, /* STOP index is never reached */ + VIADEV_REG(viadev, OFFSET_STOP_IDX)); + udelay(20); + snd_via82xx_codec_ready(chip, 0); + return 0; +} + + +/* + * pcm hardware definition, identical for both playback and capture + */ +static const struct snd_pcm_hardware snd_via82xx_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + /* SNDRV_PCM_INFO_RESUME | */ + SNDRV_PCM_INFO_PAUSE), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = VIA_MAX_BUFSIZE, + .period_bytes_min = 32, + .period_bytes_max = VIA_MAX_BUFSIZE / 2, + .periods_min = 2, + .periods_max = VIA_TABLE_SIZE / 2, + .fifo_size = 0, +}; + + +/* + * open callback skeleton + */ +static int snd_via82xx_pcm_open(struct via82xx *chip, struct viadev *viadev, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + struct via_rate_lock *ratep; + bool use_src = false; + + runtime->hw = snd_via82xx_hw; + + /* set the hw rate condition */ + ratep = &chip->rates[viadev->direction]; + scoped_guard(spinlock_irq, &ratep->lock) { + ratep->used++; + if (chip->spdif_on && viadev->reg_offset == 0x30) { + /* DXS#3 and spdif is on */ + runtime->hw.rates = chip->ac97->rates[AC97_RATES_SPDIF]; + snd_pcm_limit_hw_rates(runtime); + } else if (chip->dxs_fixed && viadev->reg_offset < 0x40) { + /* fixed DXS playback rate */ + runtime->hw.rates = SNDRV_PCM_RATE_48000; + runtime->hw.rate_min = runtime->hw.rate_max = 48000; + } else if (chip->dxs_src && viadev->reg_offset < 0x40) { + /* use full SRC capabilities of DXS */ + runtime->hw.rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_48000); + runtime->hw.rate_min = 8000; + runtime->hw.rate_max = 48000; + use_src = true; + } else if (!ratep->rate) { + int idx = viadev->direction ? AC97_RATES_ADC : AC97_RATES_FRONT_DAC; + runtime->hw.rates = chip->ac97->rates[idx]; + snd_pcm_limit_hw_rates(runtime); + } else { + /* a fixed rate */ + runtime->hw.rates = SNDRV_PCM_RATE_KNOT; + runtime->hw.rate_max = runtime->hw.rate_min = ratep->rate; + } + } + + /* we may remove following constaint when we modify table entries + in interrupt */ + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + if (use_src) { + err = snd_pcm_hw_rule_noresample(runtime, 48000); + if (err < 0) + return err; + } + + runtime->private_data = viadev; + viadev->substream = substream; + + return 0; +} + + +/* + * open callback for playback on via686 + */ +static int snd_via686_playback_open(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = &chip->devs[chip->playback_devno + substream->number]; + int err; + + err = snd_via82xx_pcm_open(chip, viadev, substream); + if (err < 0) + return err; + return 0; +} + +/* + * open callback for playback on via823x DXS + */ +static int snd_via8233_playback_open(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev; + unsigned int stream; + int err; + + viadev = &chip->devs[chip->playback_devno + substream->number]; + err = snd_via82xx_pcm_open(chip, viadev, substream); + if (err < 0) + return err; + stream = viadev->reg_offset / 0x10; + if (chip->dxs_controls[stream]) { + chip->playback_volume[stream][0] = + VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); + chip->playback_volume[stream][1] = + VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); + chip->dxs_controls[stream]->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, + &chip->dxs_controls[stream]->id); + } + return 0; +} + +/* + * open callback for playback on via823x multi-channel + */ +static int snd_via8233_multi_open(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = &chip->devs[chip->multi_devno]; + int err; + /* channels constraint for VIA8233A + * 3 and 5 channels are not supported + */ + static const unsigned int channels[] = { + 1, 2, 4, 6 + }; + static const struct snd_pcm_hw_constraint_list hw_constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, + }; + + err = snd_via82xx_pcm_open(chip, viadev, substream); + if (err < 0) + return err; + substream->runtime->hw.channels_max = 6; + if (chip->revision == VIA_REV_8233A) + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_constraints_channels); + return 0; +} + +/* + * open callback for capture on via686 and via823x + */ +static int snd_via82xx_capture_open(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = &chip->devs[chip->capture_devno + substream->pcm->device]; + + return snd_via82xx_pcm_open(chip, viadev, substream); +} + +/* + * close callback + */ +static int snd_via82xx_pcm_close(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + struct via_rate_lock *ratep; + + /* release the rate lock */ + ratep = &chip->rates[viadev->direction]; + scoped_guard(spinlock_irq, &ratep->lock) { + ratep->used--; + if (!ratep->used) + ratep->rate = 0; + } + if (! ratep->rate) { + if (! viadev->direction) { + snd_ac97_update_power(chip->ac97, + AC97_PCM_FRONT_DAC_RATE, 0); + snd_ac97_update_power(chip->ac97, + AC97_PCM_SURR_DAC_RATE, 0); + snd_ac97_update_power(chip->ac97, + AC97_PCM_LFE_DAC_RATE, 0); + } else + snd_ac97_update_power(chip->ac97, + AC97_PCM_LR_ADC_RATE, 0); + } + viadev->substream = NULL; + return 0; +} + +static int snd_via8233_playback_close(struct snd_pcm_substream *substream) +{ + struct via82xx *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + unsigned int stream; + + stream = viadev->reg_offset / 0x10; + if (chip->dxs_controls[stream]) { + chip->dxs_controls[stream]->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, + &chip->dxs_controls[stream]->id); + } + return snd_via82xx_pcm_close(substream); +} + + +/* via686 playback callbacks */ +static const struct snd_pcm_ops snd_via686_playback_ops = { + .open = snd_via686_playback_open, + .close = snd_via82xx_pcm_close, + .hw_params = snd_via82xx_hw_params, + .hw_free = snd_via82xx_hw_free, + .prepare = snd_via686_playback_prepare, + .trigger = snd_via82xx_pcm_trigger, + .pointer = snd_via686_pcm_pointer, +}; + +/* via686 capture callbacks */ +static const struct snd_pcm_ops snd_via686_capture_ops = { + .open = snd_via82xx_capture_open, + .close = snd_via82xx_pcm_close, + .hw_params = snd_via82xx_hw_params, + .hw_free = snd_via82xx_hw_free, + .prepare = snd_via686_capture_prepare, + .trigger = snd_via82xx_pcm_trigger, + .pointer = snd_via686_pcm_pointer, +}; + +/* via823x DSX playback callbacks */ +static const struct snd_pcm_ops snd_via8233_playback_ops = { + .open = snd_via8233_playback_open, + .close = snd_via8233_playback_close, + .hw_params = snd_via82xx_hw_params, + .hw_free = snd_via82xx_hw_free, + .prepare = snd_via8233_playback_prepare, + .trigger = snd_via82xx_pcm_trigger, + .pointer = snd_via8233_pcm_pointer, +}; + +/* via823x multi-channel playback callbacks */ +static const struct snd_pcm_ops snd_via8233_multi_ops = { + .open = snd_via8233_multi_open, + .close = snd_via82xx_pcm_close, + .hw_params = snd_via82xx_hw_params, + .hw_free = snd_via82xx_hw_free, + .prepare = snd_via8233_multi_prepare, + .trigger = snd_via82xx_pcm_trigger, + .pointer = snd_via8233_pcm_pointer, +}; + +/* via823x capture callbacks */ +static const struct snd_pcm_ops snd_via8233_capture_ops = { + .open = snd_via82xx_capture_open, + .close = snd_via82xx_pcm_close, + .hw_params = snd_via82xx_hw_params, + .hw_free = snd_via82xx_hw_free, + .prepare = snd_via8233_capture_prepare, + .trigger = snd_via82xx_pcm_trigger, + .pointer = snd_via8233_pcm_pointer, +}; + + +static void init_viadev(struct via82xx *chip, int idx, unsigned int reg_offset, + int shadow_pos, int direction) +{ + chip->devs[idx].reg_offset = reg_offset; + chip->devs[idx].shadow_shift = shadow_pos * 4; + chip->devs[idx].direction = direction; + chip->devs[idx].port = chip->port + reg_offset; +} + +/* + * create pcm instances for VIA8233, 8233C and 8235 (not 8233A) + */ +static int snd_via8233_pcm_new(struct via82xx *chip) +{ + struct snd_pcm *pcm; + struct snd_pcm_chmap *chmap; + int i, err; + + chip->playback_devno = 0; /* x 4 */ + chip->multi_devno = 4; /* x 1 */ + chip->capture_devno = 5; /* x 2 */ + chip->num_devs = 7; + chip->intr_mask = 0x33033333; /* FLAG|EOL for rec0-1, mc, sdx0-3 */ + + /* PCM #0: 4 DSX playbacks and 1 capture */ + err = snd_pcm_new(chip->card, chip->card->shortname, 0, 4, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via8233_capture_ops); + pcm->private_data = chip; + strscpy(pcm->name, chip->card->shortname); + chip->pcms[0] = pcm; + /* set up playbacks */ + for (i = 0; i < 4; i++) + init_viadev(chip, i, 0x10 * i, i, 0); + /* capture */ + init_viadev(chip, chip->capture_devno, VIA_REG_CAPTURE_8233_STATUS, 6, 1); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci->dev, + 64*1024, VIA_MAX_BUFSIZE); + + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_std_chmaps, 2, 0, + &chmap); + if (err < 0) + return err; + + /* PCM #1: multi-channel playback and 2nd capture */ + err = snd_pcm_new(chip->card, chip->card->shortname, 1, 1, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_multi_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via8233_capture_ops); + pcm->private_data = chip; + strscpy(pcm->name, chip->card->shortname); + chip->pcms[1] = pcm; + /* set up playback */ + init_viadev(chip, chip->multi_devno, VIA_REG_MULTPLAY_STATUS, 4, 0); + /* set up capture */ + init_viadev(chip, chip->capture_devno + 1, VIA_REG_CAPTURE_8233_STATUS + 0x10, 7, 1); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci->dev, + 64*1024, VIA_MAX_BUFSIZE); + + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_alt_chmaps, 6, 0, + &chmap); + if (err < 0) + return err; + chip->ac97->chmaps[SNDRV_PCM_STREAM_PLAYBACK] = chmap; + + return 0; +} + +/* + * create pcm instances for VIA8233A + */ +static int snd_via8233a_pcm_new(struct via82xx *chip) +{ + struct snd_pcm *pcm; + struct snd_pcm_chmap *chmap; + int err; + + chip->multi_devno = 0; + chip->playback_devno = 1; + chip->capture_devno = 2; + chip->num_devs = 3; + chip->intr_mask = 0x03033000; /* FLAG|EOL for rec0, mc, sdx3 */ + + /* PCM #0: multi-channel playback and capture */ + err = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_multi_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via8233_capture_ops); + pcm->private_data = chip; + strscpy(pcm->name, chip->card->shortname); + chip->pcms[0] = pcm; + /* set up playback */ + init_viadev(chip, chip->multi_devno, VIA_REG_MULTPLAY_STATUS, 4, 0); + /* capture */ + init_viadev(chip, chip->capture_devno, VIA_REG_CAPTURE_8233_STATUS, 6, 1); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci->dev, + 64*1024, VIA_MAX_BUFSIZE); + + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_alt_chmaps, 6, 0, + &chmap); + if (err < 0) + return err; + chip->ac97->chmaps[SNDRV_PCM_STREAM_PLAYBACK] = chmap; + + /* SPDIF supported? */ + if (! ac97_can_spdif(chip->ac97)) + return 0; + + /* PCM #1: DXS3 playback (for spdif) */ + err = snd_pcm_new(chip->card, chip->card->shortname, 1, 1, 0, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_playback_ops); + pcm->private_data = chip; + strscpy(pcm->name, chip->card->shortname); + chip->pcms[1] = pcm; + /* set up playback */ + init_viadev(chip, chip->playback_devno, 0x30, 3, 0); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci->dev, + 64*1024, VIA_MAX_BUFSIZE); + return 0; +} + +/* + * create a pcm instance for via686a/b + */ +static int snd_via686_pcm_new(struct via82xx *chip) +{ + struct snd_pcm *pcm; + int err; + + chip->playback_devno = 0; + chip->capture_devno = 1; + chip->num_devs = 2; + chip->intr_mask = 0x77; /* FLAG | EOL for PB, CP, FM */ + + err = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via686_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via686_capture_ops); + pcm->private_data = chip; + strscpy(pcm->name, chip->card->shortname); + chip->pcms[0] = pcm; + init_viadev(chip, 0, VIA_REG_PLAYBACK_STATUS, 0, 0); + init_viadev(chip, 1, VIA_REG_CAPTURE_STATUS, 0, 1); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci->dev, + 64*1024, VIA_MAX_BUFSIZE); + return 0; +} + + +/* + * Mixer part + */ + +static int snd_via8233_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + /* formerly they were "Line" and "Mic", but it looks like that they + * have nothing to do with the actual physical connections... + */ + static const char * const texts[2] = { + "Input1", "Input2" + }; + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_via8233_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct via82xx *chip = snd_kcontrol_chip(kcontrol); + unsigned long port = chip->port + (kcontrol->id.index ? (VIA_REG_CAPTURE_CHANNEL + 0x10) : VIA_REG_CAPTURE_CHANNEL); + ucontrol->value.enumerated.item[0] = inb(port) & VIA_REG_CAPTURE_CHANNEL_MIC ? 1 : 0; + return 0; +} + +static int snd_via8233_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct via82xx *chip = snd_kcontrol_chip(kcontrol); + unsigned long port = chip->port + (kcontrol->id.index ? (VIA_REG_CAPTURE_CHANNEL + 0x10) : VIA_REG_CAPTURE_CHANNEL); + u8 val, oval; + + guard(spinlock_irq)(&chip->reg_lock); + oval = inb(port); + val = oval & ~VIA_REG_CAPTURE_CHANNEL_MIC; + if (ucontrol->value.enumerated.item[0]) + val |= VIA_REG_CAPTURE_CHANNEL_MIC; + if (val != oval) + outb(val, port); + return val != oval; +} + +static struct snd_kcontrol_new snd_via8233_capture_source = { + .name = "Input Source Select", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_via8233_capture_source_info, + .get = snd_via8233_capture_source_get, + .put = snd_via8233_capture_source_put, +}; + +#define snd_via8233_dxs3_spdif_info snd_ctl_boolean_mono_info + +static int snd_via8233_dxs3_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct via82xx *chip = snd_kcontrol_chip(kcontrol); + u8 val; + + pci_read_config_byte(chip->pci, VIA8233_SPDIF_CTRL, &val); + ucontrol->value.integer.value[0] = (val & VIA8233_SPDIF_DX3) ? 1 : 0; + return 0; +} + +static int snd_via8233_dxs3_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct via82xx *chip = snd_kcontrol_chip(kcontrol); + u8 val, oval; + + pci_read_config_byte(chip->pci, VIA8233_SPDIF_CTRL, &oval); + val = oval & ~VIA8233_SPDIF_DX3; + if (ucontrol->value.integer.value[0]) + val |= VIA8233_SPDIF_DX3; + /* save the spdif flag for rate filtering */ + chip->spdif_on = ucontrol->value.integer.value[0] ? 1 : 0; + if (val != oval) { + pci_write_config_byte(chip->pci, VIA8233_SPDIF_CTRL, val); + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = { + .name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH), + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_via8233_dxs3_spdif_info, + .get = snd_via8233_dxs3_spdif_get, + .put = snd_via8233_dxs3_spdif_put, +}; + +static int snd_via8233_dxs_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 = VIA_DXS_MAX_VOLUME; + return 0; +} + +static int snd_via8233_dxs_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct via82xx *chip = snd_kcontrol_chip(kcontrol); + unsigned int idx = kcontrol->id.subdevice; + + ucontrol->value.integer.value[0] = VIA_DXS_MAX_VOLUME - chip->playback_volume[idx][0]; + ucontrol->value.integer.value[1] = VIA_DXS_MAX_VOLUME - chip->playback_volume[idx][1]; + return 0; +} + +static int snd_via8233_pcmdxs_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct via82xx *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = VIA_DXS_MAX_VOLUME - chip->playback_volume_c[0]; + ucontrol->value.integer.value[1] = VIA_DXS_MAX_VOLUME - chip->playback_volume_c[1]; + return 0; +} + +static int snd_via8233_dxs_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct via82xx *chip = snd_kcontrol_chip(kcontrol); + unsigned int idx = kcontrol->id.subdevice; + unsigned long port = chip->port + 0x10 * idx; + unsigned char val; + int i, change = 0; + + for (i = 0; i < 2; i++) { + val = ucontrol->value.integer.value[i]; + if (val > VIA_DXS_MAX_VOLUME) + val = VIA_DXS_MAX_VOLUME; + val = VIA_DXS_MAX_VOLUME - val; + change |= val != chip->playback_volume[idx][i]; + if (change) { + chip->playback_volume[idx][i] = val; + outb(val, port + VIA_REG_OFS_PLAYBACK_VOLUME_L + i); + } + } + return change; +} + +static int snd_via8233_pcmdxs_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct via82xx *chip = snd_kcontrol_chip(kcontrol); + unsigned int idx; + unsigned char val; + int i, change = 0; + + for (i = 0; i < 2; i++) { + val = ucontrol->value.integer.value[i]; + if (val > VIA_DXS_MAX_VOLUME) + val = VIA_DXS_MAX_VOLUME; + val = VIA_DXS_MAX_VOLUME - val; + if (val != chip->playback_volume_c[i]) { + change = 1; + chip->playback_volume_c[i] = val; + for (idx = 0; idx < 4; idx++) { + unsigned long port = chip->port + 0x10 * idx; + chip->playback_volume[idx][i] = val; + outb(val, port + VIA_REG_OFS_PLAYBACK_VOLUME_L + i); + } + } + } + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_dxs, -4650, 150, 1); + +static const struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { + .name = "PCM Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = snd_via8233_dxs_volume_info, + .get = snd_via8233_pcmdxs_volume_get, + .put = snd_via8233_pcmdxs_volume_put, + .tlv = { .p = db_scale_dxs } +}; + +static const struct snd_kcontrol_new snd_via8233_dxs_volume_control = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .device = 0, + /* .subdevice set later */ + .name = "PCM Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .info = snd_via8233_dxs_volume_info, + .get = snd_via8233_dxs_volume_get, + .put = snd_via8233_dxs_volume_put, + .tlv = { .p = db_scale_dxs } +}; + +/* + */ + +static void snd_via82xx_mixer_free_ac97_bus(struct snd_ac97_bus *bus) +{ + struct via82xx *chip = bus->private_data; + chip->ac97_bus = NULL; +} + +static void snd_via82xx_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct via82xx *chip = ac97->private_data; + chip->ac97 = NULL; +} + +static const struct ac97_quirk ac97_quirks[] = { + { + .subvendor = 0x1106, + .subdevice = 0x4161, + .codec_id = 0x56494161, /* VT1612A */ + .name = "Soltek SL-75DRV5", + .type = AC97_TUNE_NONE + }, + { /* FIXME: which codec? */ + .subvendor = 0x1106, + .subdevice = 0x4161, + .name = "ASRock K7VT2", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x110a, + .subdevice = 0x0079, + .name = "Fujitsu Siemens D1289", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1019, + .subdevice = 0x0a81, + .name = "ECS K7VTA3", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1019, + .subdevice = 0x0a85, + .name = "ECS L7VMM2", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1019, + .subdevice = 0x1841, + .name = "ECS K7VTA3", + .type = AC97_TUNE_HP_ONLY + }, + { + .subvendor = 0x1849, + .subdevice = 0x3059, + .name = "ASRock K7VM2", + .type = AC97_TUNE_HP_ONLY /* VT1616 */ + }, + { + .subvendor = 0x14cd, + .subdevice = 0x7002, + .name = "Unknown", + .type = AC97_TUNE_ALC_JACK + }, + { + .subvendor = 0x1071, + .subdevice = 0x8590, + .name = "Mitac Mobo", + .type = AC97_TUNE_ALC_JACK + }, + { + .subvendor = 0x161f, + .subdevice = 0x202b, + .name = "Arima Notebook", + .type = AC97_TUNE_HP_ONLY, + }, + { + .subvendor = 0x161f, + .subdevice = 0x2032, + .name = "Targa Traveller 811", + .type = AC97_TUNE_HP_ONLY, + }, + { + .subvendor = 0x161f, + .subdevice = 0x2032, + .name = "m680x", + .type = AC97_TUNE_HP_ONLY, /* http://launchpad.net/bugs/38546 */ + }, + { + .subvendor = 0x1297, + .subdevice = 0xa232, + .name = "Shuttle AK32VN", + .type = AC97_TUNE_HP_ONLY + }, + { } /* terminator */ +}; + +static int snd_via82xx_mixer_new(struct via82xx *chip, const char *quirk_override) +{ + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_via82xx_codec_write, + .read = snd_via82xx_codec_read, + .wait = snd_via82xx_codec_wait, + }; + + err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus); + if (err < 0) + return err; + chip->ac97_bus->private_free = snd_via82xx_mixer_free_ac97_bus; + chip->ac97_bus->clock = chip->ac97_clock; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.private_free = snd_via82xx_mixer_free_ac97; + ac97.pci = chip->pci; + ac97.scaps = AC97_SCAP_SKIP_MODEM | AC97_SCAP_POWER_SAVE; + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97); + if (err < 0) + return err; + + snd_ac97_tune_hardware(chip->ac97, ac97_quirks, quirk_override); + + if (chip->chip_type != TYPE_VIA686) { + /* use slot 10/11 */ + snd_ac97_update_bits(chip->ac97, AC97_EXTENDED_STATUS, 0x03 << 4, 0x03 << 4); + } + + return 0; +} + +#ifdef SUPPORT_JOYSTICK +#define JOYSTICK_ADDR 0x200 +static int snd_via686_create_gameport(struct via82xx *chip, unsigned char *legacy) +{ + struct gameport *gp; + + if (!joystick) + return -ENODEV; + + if (!devm_request_region(chip->card->dev, JOYSTICK_ADDR, 8, + "VIA686 gameport")) { + dev_warn(chip->card->dev, "cannot reserve joystick port %#x\n", + JOYSTICK_ADDR); + return -EBUSY; + } + + chip->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(chip->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "VIA686 Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci)); + gameport_set_dev_parent(gp, &chip->pci->dev); + gp->io = JOYSTICK_ADDR; + + /* Enable legacy joystick port */ + *legacy |= VIA_FUNC_ENABLE_GAME; + pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, *legacy); + + gameport_register_port(chip->gameport); + + return 0; +} + +static void snd_via686_free_gameport(struct via82xx *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } +} +#else +static inline int snd_via686_create_gameport(struct via82xx *chip, unsigned char *legacy) +{ + return -ENOSYS; +} +static inline void snd_via686_free_gameport(struct via82xx *chip) { } +#endif + + +/* + * + */ + +static int snd_via8233_init_misc(struct via82xx *chip) +{ + int i, err, caps; + unsigned char val; + + caps = chip->chip_type == TYPE_VIA8233A ? 1 : 2; + for (i = 0; i < caps; i++) { + snd_via8233_capture_source.index = i; + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_via8233_capture_source, chip)); + if (err < 0) + return err; + } + if (ac97_can_spdif(chip->ac97)) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_via8233_dxs3_spdif_control, chip)); + if (err < 0) + return err; + } + if (chip->chip_type != TYPE_VIA8233A) { + /* when no h/w PCM volume control is found, use DXS volume control + * as the PCM vol control + */ + if (!snd_ctl_find_id_mixer(chip->card, "PCM Playback Volume")) { + dev_info(chip->card->dev, + "Using DXS as PCM Playback\n"); + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_via8233_pcmdxs_volume_control, chip)); + if (err < 0) + return err; + } + else /* Using DXS when PCM emulation is enabled is really weird */ + { + for (i = 0; i < 4; ++i) { + struct snd_kcontrol *kctl; + + kctl = snd_ctl_new1( + &snd_via8233_dxs_volume_control, chip); + if (!kctl) + return -ENOMEM; + kctl->id.subdevice = i; + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + chip->dxs_controls[i] = kctl; + } + } + } + /* select spdif data slot 10/11 */ + pci_read_config_byte(chip->pci, VIA8233_SPDIF_CTRL, &val); + val = (val & ~VIA8233_SPDIF_SLOT_MASK) | VIA8233_SPDIF_SLOT_1011; + val &= ~VIA8233_SPDIF_DX3; /* SPDIF off as default */ + pci_write_config_byte(chip->pci, VIA8233_SPDIF_CTRL, val); + + return 0; +} + +static int snd_via686_init_misc(struct via82xx *chip) +{ + unsigned char legacy, legacy_cfg; + int rev_h = 0; + + legacy = chip->old_legacy; + legacy_cfg = chip->old_legacy_cfg; + legacy |= VIA_FUNC_MIDI_IRQMASK; /* FIXME: correct? (disable MIDI) */ + legacy &= ~VIA_FUNC_ENABLE_GAME; /* disable joystick */ + if (chip->revision >= VIA_REV_686_H) { + rev_h = 1; + if (mpu_port >= 0x200) { /* force MIDI */ + mpu_port &= 0xfffc; + pci_write_config_dword(chip->pci, 0x18, mpu_port | 0x01); + chip->mpu_port_saved = mpu_port; + } else { + mpu_port = pci_resource_start(chip->pci, 2); + } + } else { + switch (mpu_port) { /* force MIDI */ + case 0x300: + case 0x310: + case 0x320: + case 0x330: + legacy_cfg &= ~(3 << 2); + legacy_cfg |= (mpu_port & 0x0030) >> 2; + break; + default: /* no, use BIOS settings */ + if (legacy & VIA_FUNC_ENABLE_MIDI) + mpu_port = 0x300 + ((legacy_cfg & 0x000c) << 2); + break; + } + } + if (mpu_port >= 0x200) + chip->mpu_res = devm_request_region(&chip->pci->dev, mpu_port, + 2, "VIA82xx MPU401"); + if (chip->mpu_res) { + if (rev_h) + legacy |= VIA_FUNC_MIDI_PNP; /* enable PCI I/O 2 */ + legacy |= VIA_FUNC_ENABLE_MIDI; + } else { + if (rev_h) + legacy &= ~VIA_FUNC_MIDI_PNP; /* disable PCI I/O 2 */ + legacy &= ~VIA_FUNC_ENABLE_MIDI; + mpu_port = 0; + } + + pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, legacy); + pci_write_config_byte(chip->pci, VIA_PNP_CONTROL, legacy_cfg); + if (chip->mpu_res) { + if (snd_mpu401_uart_new(chip->card, 0, MPU401_HW_VIA686A, + mpu_port, MPU401_INFO_INTEGRATED | + MPU401_INFO_IRQ_HOOK, -1, + &chip->rmidi) < 0) { + dev_warn(chip->card->dev, + "unable to initialize MPU-401 at 0x%lx, skipping\n", + mpu_port); + legacy &= ~VIA_FUNC_ENABLE_MIDI; + } else { + legacy &= ~VIA_FUNC_MIDI_IRQMASK; /* enable MIDI interrupt */ + } + pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, legacy); + } + + snd_via686_create_gameport(chip, &legacy); + + chip->legacy_saved = legacy; + chip->legacy_cfg_saved = legacy_cfg; + + return 0; +} + + +/* + * proc interface + */ +static void snd_via82xx_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct via82xx *chip = entry->private_data; + int i; + + snd_iprintf(buffer, "%s\n\n", chip->card->longname); + for (i = 0; i < 0xa0; i += 4) { + snd_iprintf(buffer, "%02x: %08x\n", i, inl(chip->port + i)); + } +} + +static void snd_via82xx_proc_init(struct via82xx *chip) +{ + snd_card_ro_proc_new(chip->card, "via82xx", chip, + snd_via82xx_proc_read); +} + +/* + * + */ + +static int snd_via82xx_chip_init(struct via82xx *chip) +{ + unsigned int val; + unsigned long end_time; + unsigned char pval; + +#if 0 /* broken on K7M? */ + if (chip->chip_type == TYPE_VIA686) + /* disable all legacy ports */ + pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, 0); +#endif + pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval); + if (! (pval & VIA_ACLINK_C00_READY)) { /* codec not ready? */ + /* deassert ACLink reset, force SYNC */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, + VIA_ACLINK_CTRL_ENABLE | + VIA_ACLINK_CTRL_RESET | + VIA_ACLINK_CTRL_SYNC); + udelay(100); +#if 1 /* FIXME: should we do full reset here for all chip models? */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, 0x00); + udelay(100); +#else + /* deassert ACLink reset, force SYNC (warm AC'97 reset) */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, + VIA_ACLINK_CTRL_RESET|VIA_ACLINK_CTRL_SYNC); + udelay(2); +#endif + /* ACLink on, deassert ACLink reset, VSR, SGD data out */ + /* note - FM data out has trouble with non VRA codecs !! */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT); + udelay(100); + } + + /* Make sure VRA is enabled, in case we didn't do a + * complete codec reset, above */ + pci_read_config_byte(chip->pci, VIA_ACLINK_CTRL, &pval); + if ((pval & VIA_ACLINK_CTRL_INIT) != VIA_ACLINK_CTRL_INIT) { + /* ACLink on, deassert ACLink reset, VSR, SGD data out */ + /* note - FM data out has trouble with non VRA codecs !! */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT); + udelay(100); + } + + /* wait until codec ready */ + end_time = jiffies + msecs_to_jiffies(750); + do { + pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval); + if (pval & VIA_ACLINK_C00_READY) /* primary codec ready */ + break; + schedule_timeout_uninterruptible(1); + } while (time_before(jiffies, end_time)); + + val = snd_via82xx_codec_xread(chip); + if (val & VIA_REG_AC97_BUSY) + dev_err(chip->card->dev, + "AC'97 codec is not ready [0x%x]\n", val); + +#if 0 /* FIXME: we don't support the second codec yet so skip the detection now.. */ + snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ | + VIA_REG_AC97_SECONDARY_VALID | + (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT)); + end_time = jiffies + msecs_to_jiffies(750); + snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ | + VIA_REG_AC97_SECONDARY_VALID | + (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT)); + do { + val = snd_via82xx_codec_xread(chip); + if (val & VIA_REG_AC97_SECONDARY_VALID) { + chip->ac97_secondary = 1; + goto __ac97_ok2; + } + schedule_timeout_uninterruptible(1); + } while (time_before(jiffies, end_time)); + /* This is ok, the most of motherboards have only one codec */ + + __ac97_ok2: +#endif + + if (chip->chip_type == TYPE_VIA686) { + /* route FM trap to IRQ, disable FM trap */ + pci_write_config_byte(chip->pci, VIA_FM_NMI_CTRL, 0); + /* disable all GPI interrupts */ + outl(0, VIAREG(chip, GPI_INTR)); + } + + if (chip->chip_type != TYPE_VIA686) { + /* Workaround for Award BIOS bug: + * DXS channels don't work properly with VRA if MC97 is disabled. + */ + struct pci_dev *pci; + pci = pci_get_device(0x1106, 0x3068, NULL); /* MC97 */ + if (pci) { + unsigned char data; + pci_read_config_byte(pci, 0x44, &data); + pci_write_config_byte(pci, 0x44, data | 0x40); + pci_dev_put(pci); + } + } + + if (chip->chip_type != TYPE_VIA8233A) { + int i, idx; + for (idx = 0; idx < 4; idx++) { + unsigned long port = chip->port + 0x10 * idx; + for (i = 0; i < 2; i++) { + chip->playback_volume[idx][i]=chip->playback_volume_c[i]; + outb(chip->playback_volume_c[i], + port + VIA_REG_OFS_PLAYBACK_VOLUME_L + i); + } + } + } + + return 0; +} + +/* + * power management + */ +static int snd_via82xx_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct via82xx *chip = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + snd_ac97_suspend(chip->ac97); + + /* save misc values */ + if (chip->chip_type != TYPE_VIA686) { + pci_read_config_byte(chip->pci, VIA8233_SPDIF_CTRL, &chip->spdif_ctrl_saved); + chip->capture_src_saved[0] = inb(chip->port + VIA_REG_CAPTURE_CHANNEL); + chip->capture_src_saved[1] = inb(chip->port + VIA_REG_CAPTURE_CHANNEL + 0x10); + } + + return 0; +} + +static int snd_via82xx_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct via82xx *chip = card->private_data; + int i; + + snd_via82xx_chip_init(chip); + + if (chip->chip_type == TYPE_VIA686) { + if (chip->mpu_port_saved) + pci_write_config_dword(chip->pci, 0x18, chip->mpu_port_saved | 0x01); + pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, chip->legacy_saved); + pci_write_config_byte(chip->pci, VIA_PNP_CONTROL, chip->legacy_cfg_saved); + } else { + pci_write_config_byte(chip->pci, VIA8233_SPDIF_CTRL, chip->spdif_ctrl_saved); + outb(chip->capture_src_saved[0], chip->port + VIA_REG_CAPTURE_CHANNEL); + outb(chip->capture_src_saved[1], chip->port + VIA_REG_CAPTURE_CHANNEL + 0x10); + } + + snd_ac97_resume(chip->ac97); + + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_via82xx_pm, snd_via82xx_suspend, snd_via82xx_resume); + +static void snd_via82xx_free(struct snd_card *card) +{ + struct via82xx *chip = card->private_data; + unsigned int i; + + /* disable interrupts */ + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + + if (chip->chip_type == TYPE_VIA686) { + snd_via686_free_gameport(chip); + pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, chip->old_legacy); + pci_write_config_byte(chip->pci, VIA_PNP_CONTROL, chip->old_legacy_cfg); + } +} + +static int snd_via82xx_create(struct snd_card *card, + struct pci_dev *pci, + int chip_type, + int revision, + unsigned int ac97_clock) +{ + struct via82xx *chip = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + chip->chip_type = chip_type; + chip->revision = revision; + + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->rates[0].lock); + spin_lock_init(&chip->rates[1].lock); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + pci_read_config_byte(pci, VIA_FUNC_ENABLE, &chip->old_legacy); + pci_read_config_byte(pci, VIA_PNP_CONTROL, &chip->old_legacy_cfg); + pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, + chip->old_legacy & ~(VIA_FUNC_ENABLE_SB|VIA_FUNC_ENABLE_FM)); + + err = pcim_request_all_regions(pci, card->driver); + if (err < 0) + return err; + chip->port = pci_resource_start(pci, 0); + if (devm_request_irq(&pci->dev, pci->irq, + chip_type == TYPE_VIA8233 ? + snd_via8233_interrupt : snd_via686_interrupt, + IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_via82xx_free; + if (ac97_clock >= 8000 && ac97_clock <= 48000) + chip->ac97_clock = ac97_clock; + + err = snd_via82xx_chip_init(chip); + if (err < 0) + return err; + + /* The 8233 ac97 controller does not implement the master bit + * in the pci command register. IMHO this is a violation of the PCI spec. + * We call pci_set_master here because it does not hurt. */ + pci_set_master(pci); + return 0; +} + +struct via823x_info { + int revision; + char *name; + int type; +}; +static const struct via823x_info via823x_cards[] = { + { VIA_REV_PRE_8233, "VIA 8233-Pre", TYPE_VIA8233 }, + { VIA_REV_8233C, "VIA 8233C", TYPE_VIA8233 }, + { VIA_REV_8233, "VIA 8233", TYPE_VIA8233 }, + { VIA_REV_8233A, "VIA 8233A", TYPE_VIA8233A }, + { VIA_REV_8235, "VIA 8235", TYPE_VIA8233 }, + { VIA_REV_8237, "VIA 8237", TYPE_VIA8233 }, + { VIA_REV_8251, "VIA 8251", TYPE_VIA8233 }, +}; + +/* + * auto detection of DXS channel supports. + */ + +static const struct snd_pci_quirk dxs_allowlist[] = { + SND_PCI_QUIRK(0x1005, 0x4710, "Avance Logic Mobo", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x1019, 0x0996, "ESC Mobo", VIA_DXS_48K), + SND_PCI_QUIRK(0x1019, 0x0a81, "ECS K7VTA3 v8.0", VIA_DXS_NO_VRA), + SND_PCI_QUIRK(0x1019, 0x0a85, "ECS L7VMM2", VIA_DXS_NO_VRA), + SND_PCI_QUIRK_VENDOR(0x1019, "ESC K8", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1019, 0xaa01, "ESC K8T890-A", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1025, 0x0033, "Acer Inspire 1353LM", VIA_DXS_NO_VRA), + SND_PCI_QUIRK(0x1025, 0x0046, "Acer Aspire 1524 WLMi", VIA_DXS_SRC), + SND_PCI_QUIRK_VENDOR(0x1043, "ASUS A7/A8", VIA_DXS_NO_VRA), + SND_PCI_QUIRK_VENDOR(0x1071, "Diverse Notebook", VIA_DXS_NO_VRA), + SND_PCI_QUIRK(0x10cf, 0x118e, "FSC Laptop", VIA_DXS_ENABLE), + SND_PCI_QUIRK_VENDOR(0x1106, "ASRock", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1297, 0xa231, "Shuttle AK31v2", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1297, 0xa232, "Shuttle", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1297, 0xc160, "Shuttle Sk41G", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte GA-7VAXP", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x1462, 0x3800, "MSI KT266", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x1462, 0x7120, "MSI KT4V", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x1462, 0x7142, "MSI K8MM-V", VIA_DXS_ENABLE), + SND_PCI_QUIRK_VENDOR(0x1462, "MSI Mobo", VIA_DXS_SRC), + SND_PCI_QUIRK(0x147b, 0x1401, "ABIT KD7(-RAID)", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x147b, 0x1411, "ABIT VA-20", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x147b, 0x1413, "ABIT KV8 Pro", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x147b, 0x1415, "ABIT AV8", VIA_DXS_NO_VRA), + SND_PCI_QUIRK(0x14ff, 0x0403, "Twinhead mobo", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x14ff, 0x0408, "Twinhead laptop", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1558, 0x4701, "Clevo D470", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1584, 0x8120, "Diverse Laptop", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x1584, 0x8123, "Targa/Uniwill", VIA_DXS_NO_VRA), + SND_PCI_QUIRK(0x161f, 0x202b, "Amira Notebook", VIA_DXS_NO_VRA), + SND_PCI_QUIRK(0x161f, 0x2032, "m680x machines", VIA_DXS_48K), + SND_PCI_QUIRK(0x1631, 0xe004, "PB EasyNote 3174", VIA_DXS_ENABLE), + SND_PCI_QUIRK(0x1695, 0x3005, "EPoX EP-8K9A", VIA_DXS_ENABLE), + SND_PCI_QUIRK_VENDOR(0x1695, "EPoX mobo", VIA_DXS_SRC), + SND_PCI_QUIRK_VENDOR(0x16f3, "Jetway K8", VIA_DXS_SRC), + SND_PCI_QUIRK_VENDOR(0x1734, "FSC Laptop", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1849, 0x3059, "ASRock K7VM2", VIA_DXS_NO_VRA), + SND_PCI_QUIRK_VENDOR(0x1849, "ASRock mobo", VIA_DXS_SRC), + SND_PCI_QUIRK(0x1919, 0x200a, "Soltek SL-K8", VIA_DXS_NO_VRA), + SND_PCI_QUIRK(0x4005, 0x4710, "MSI K7T266", VIA_DXS_SRC), + { } /* terminator */ +}; + +static int check_dxs_list(struct pci_dev *pci, int revision) +{ + const struct snd_pci_quirk *w; + + w = snd_pci_quirk_lookup(pci, dxs_allowlist); + if (w) { + dev_dbg(&pci->dev, "DXS allow list for %s found\n", + snd_pci_quirk_name(w)); + return w->value; + } + + /* for newer revision, default to DXS_SRC */ + if (revision >= VIA_REV_8235) + return VIA_DXS_SRC; + + /* + * not detected, try 48k rate only to be sure. + */ + dev_info(&pci->dev, "Assuming DXS channels with 48k fixed sample rate.\n"); + dev_info(&pci->dev, " Please try dxs_support=5 option\n"); + dev_info(&pci->dev, " and report if it works on your machine.\n"); + dev_info(&pci->dev, " For more details, read ALSA-Configuration.txt.\n"); + return VIA_DXS_48K; +}; + +static int __snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct via82xx *chip; + int chip_type = 0, card_type; + unsigned int i; + int err; + + err = snd_devm_card_new(&pci->dev, index, id, THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + card_type = pci_id->driver_data; + switch (card_type) { + case TYPE_CARD_VIA686: + strscpy(card->driver, "VIA686A"); + sprintf(card->shortname, "VIA 82C686A/B rev%x", pci->revision); + chip_type = TYPE_VIA686; + break; + case TYPE_CARD_VIA8233: + chip_type = TYPE_VIA8233; + sprintf(card->shortname, "VIA 823x rev%x", pci->revision); + for (i = 0; i < ARRAY_SIZE(via823x_cards); i++) { + if (pci->revision == via823x_cards[i].revision) { + chip_type = via823x_cards[i].type; + strscpy(card->shortname, via823x_cards[i].name); + break; + } + } + if (chip_type != TYPE_VIA8233A) { + if (dxs_support == VIA_DXS_AUTO) + dxs_support = check_dxs_list(pci, pci->revision); + /* force to use VIA8233 or 8233A model according to + * dxs_support module option + */ + if (dxs_support == VIA_DXS_DISABLE) + chip_type = TYPE_VIA8233A; + else + chip_type = TYPE_VIA8233; + } + if (chip_type == TYPE_VIA8233A) + strscpy(card->driver, "VIA8233A"); + else if (pci->revision >= VIA_REV_8237) + strscpy(card->driver, "VIA8237"); /* no slog assignment */ + else + strscpy(card->driver, "VIA8233"); + break; + default: + dev_err(card->dev, "invalid card type %d\n", card_type); + return -EINVAL; + } + + err = snd_via82xx_create(card, pci, chip_type, pci->revision, + ac97_clock); + if (err < 0) + return err; + err = snd_via82xx_mixer_new(chip, ac97_quirk); + if (err < 0) + return err; + + if (chip_type == TYPE_VIA686) { + err = snd_via686_pcm_new(chip); + if (err < 0) + return err; + err = snd_via686_init_misc(chip); + if (err < 0) + return err; + } else { + if (chip_type == TYPE_VIA8233A) { + err = snd_via8233a_pcm_new(chip); + if (err < 0) + return err; + // chip->dxs_fixed = 1; /* FIXME: use 48k for DXS #3? */ + } else { + err = snd_via8233_pcm_new(chip); + if (err < 0) + return err; + if (dxs_support == VIA_DXS_48K) + chip->dxs_fixed = 1; + else if (dxs_support == VIA_DXS_NO_VRA) + chip->no_vra = 1; + else if (dxs_support == VIA_DXS_SRC) { + chip->no_vra = 1; + chip->dxs_src = 1; + } + } + err = snd_via8233_init_misc(chip); + if (err < 0) + return err; + } + + /* disable interrupts */ + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + + snprintf(card->longname, sizeof(card->longname), + "%s with %s at %#lx, irq %d", card->shortname, + snd_ac97_get_short_name(chip->ac97), chip->port, chip->irq); + + snd_via82xx_proc_init(chip); + + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + return 0; +} + +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); +} + +static struct pci_driver via82xx_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_via82xx_ids, + .probe = snd_via82xx_probe, + .driver = { + .pm = &snd_via82xx_pm, + }, +}; + +module_pci_driver(via82xx_driver); diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c new file mode 100644 index 000000000..b32f84ac1 --- /dev/null +++ b/sound/pci/via82xx_modem.c @@ -0,0 +1,1160 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA modem driver for VIA VT82xx (South Bridge) + * + * VT82C686A/B/C, VT8233A/C, VT8235 + * + * Copyright (c) 2000 Jaroslav Kysela + * Tjeerd.Mulder + * 2002 Takashi Iwai + */ + +/* + * Changes: + * + * Sep. 2, 2004 Sasha Khapyorsky + * Modified from original audio driver 'via82xx.c' to support AC97 + * modems. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define POINTER_DEBUG +#endif + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("VIA VT82xx modem"); +MODULE_LICENSE("GPL"); + +static int index = -2; /* Exclude the first card */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static int ac97_clock = 48000; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for VIA 82xx bridge."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for VIA 82xx bridge."); +module_param(ac97_clock, int, 0444); +MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz)."); + +/* just for backward compatibility */ +static bool enable; +module_param(enable, bool, 0444); + + +/* + * Direct registers + */ + +#define VIAREG(via, x) ((via)->port + VIA_REG_##x) +#define VIADEV_REG(viadev, x) ((viadev)->port + VIA_REG_##x) + +/* common offsets */ +#define VIA_REG_OFFSET_STATUS 0x00 /* byte - channel status */ +#define VIA_REG_STAT_ACTIVE 0x80 /* RO */ +#define VIA_REG_STAT_PAUSED 0x40 /* RO */ +#define VIA_REG_STAT_TRIGGER_QUEUED 0x08 /* RO */ +#define VIA_REG_STAT_STOPPED 0x04 /* RWC */ +#define VIA_REG_STAT_EOL 0x02 /* RWC */ +#define VIA_REG_STAT_FLAG 0x01 /* RWC */ +#define VIA_REG_OFFSET_CONTROL 0x01 /* byte - channel control */ +#define VIA_REG_CTRL_START 0x80 /* WO */ +#define VIA_REG_CTRL_TERMINATE 0x40 /* WO */ +#define VIA_REG_CTRL_AUTOSTART 0x20 +#define VIA_REG_CTRL_PAUSE 0x08 /* RW */ +#define VIA_REG_CTRL_INT_STOP 0x04 +#define VIA_REG_CTRL_INT_EOL 0x02 +#define VIA_REG_CTRL_INT_FLAG 0x01 +#define VIA_REG_CTRL_RESET 0x01 /* RW - probably reset? undocumented */ +#define VIA_REG_CTRL_INT (VIA_REG_CTRL_INT_FLAG | VIA_REG_CTRL_INT_EOL | VIA_REG_CTRL_AUTOSTART) +#define VIA_REG_OFFSET_TYPE 0x02 /* byte - channel type (686 only) */ +#define VIA_REG_TYPE_AUTOSTART 0x80 /* RW - autostart at EOL */ +#define VIA_REG_TYPE_16BIT 0x20 /* RW */ +#define VIA_REG_TYPE_STEREO 0x10 /* RW */ +#define VIA_REG_TYPE_INT_LLINE 0x00 +#define VIA_REG_TYPE_INT_LSAMPLE 0x04 +#define VIA_REG_TYPE_INT_LESSONE 0x08 +#define VIA_REG_TYPE_INT_MASK 0x0c +#define VIA_REG_TYPE_INT_EOL 0x02 +#define VIA_REG_TYPE_INT_FLAG 0x01 +#define VIA_REG_OFFSET_TABLE_PTR 0x04 /* dword - channel table pointer */ +#define VIA_REG_OFFSET_CURR_PTR 0x04 /* dword - channel current pointer */ +#define VIA_REG_OFFSET_STOP_IDX 0x08 /* dword - stop index, channel type, sample rate */ +#define VIA_REG_OFFSET_CURR_COUNT 0x0c /* dword - channel current count (24 bit) */ +#define VIA_REG_OFFSET_CURR_INDEX 0x0f /* byte - channel current index (for via8233 only) */ + +#define DEFINE_VIA_REGSET(name,val) \ +enum {\ + VIA_REG_##name##_STATUS = (val),\ + VIA_REG_##name##_CONTROL = (val) + 0x01,\ + VIA_REG_##name##_TYPE = (val) + 0x02,\ + VIA_REG_##name##_TABLE_PTR = (val) + 0x04,\ + VIA_REG_##name##_CURR_PTR = (val) + 0x04,\ + VIA_REG_##name##_STOP_IDX = (val) + 0x08,\ + VIA_REG_##name##_CURR_COUNT = (val) + 0x0c,\ +} + +/* modem block */ +DEFINE_VIA_REGSET(MO, 0x40); +DEFINE_VIA_REGSET(MI, 0x50); + +/* AC'97 */ +#define VIA_REG_AC97 0x80 /* dword */ +#define VIA_REG_AC97_CODEC_ID_MASK (3<<30) +#define VIA_REG_AC97_CODEC_ID_SHIFT 30 +#define VIA_REG_AC97_CODEC_ID_PRIMARY 0x00 +#define VIA_REG_AC97_CODEC_ID_SECONDARY 0x01 +#define VIA_REG_AC97_SECONDARY_VALID (1<<27) +#define VIA_REG_AC97_PRIMARY_VALID (1<<25) +#define VIA_REG_AC97_BUSY (1<<24) +#define VIA_REG_AC97_READ (1<<23) +#define VIA_REG_AC97_CMD_SHIFT 16 +#define VIA_REG_AC97_CMD_MASK 0x7e +#define VIA_REG_AC97_DATA_SHIFT 0 +#define VIA_REG_AC97_DATA_MASK 0xffff + +#define VIA_REG_SGD_SHADOW 0x84 /* dword */ +#define VIA_REG_SGD_STAT_PB_FLAG (1<<0) +#define VIA_REG_SGD_STAT_CP_FLAG (1<<1) +#define VIA_REG_SGD_STAT_FM_FLAG (1<<2) +#define VIA_REG_SGD_STAT_PB_EOL (1<<4) +#define VIA_REG_SGD_STAT_CP_EOL (1<<5) +#define VIA_REG_SGD_STAT_FM_EOL (1<<6) +#define VIA_REG_SGD_STAT_PB_STOP (1<<8) +#define VIA_REG_SGD_STAT_CP_STOP (1<<9) +#define VIA_REG_SGD_STAT_FM_STOP (1<<10) +#define VIA_REG_SGD_STAT_PB_ACTIVE (1<<12) +#define VIA_REG_SGD_STAT_CP_ACTIVE (1<<13) +#define VIA_REG_SGD_STAT_FM_ACTIVE (1<<14) +#define VIA_REG_SGD_STAT_MR_FLAG (1<<16) +#define VIA_REG_SGD_STAT_MW_FLAG (1<<17) +#define VIA_REG_SGD_STAT_MR_EOL (1<<20) +#define VIA_REG_SGD_STAT_MW_EOL (1<<21) +#define VIA_REG_SGD_STAT_MR_STOP (1<<24) +#define VIA_REG_SGD_STAT_MW_STOP (1<<25) +#define VIA_REG_SGD_STAT_MR_ACTIVE (1<<28) +#define VIA_REG_SGD_STAT_MW_ACTIVE (1<<29) + +#define VIA_REG_GPI_STATUS 0x88 +#define VIA_REG_GPI_INTR 0x8c + +#define VIA_TBL_BIT_FLAG 0x40000000 +#define VIA_TBL_BIT_EOL 0x80000000 + +/* pci space */ +#define VIA_ACLINK_STAT 0x40 +#define VIA_ACLINK_C11_READY 0x20 +#define VIA_ACLINK_C10_READY 0x10 +#define VIA_ACLINK_C01_READY 0x04 /* secondary codec ready */ +#define VIA_ACLINK_LOWPOWER 0x02 /* low-power state */ +#define VIA_ACLINK_C00_READY 0x01 /* primary codec ready */ +#define VIA_ACLINK_CTRL 0x41 +#define VIA_ACLINK_CTRL_ENABLE 0x80 /* 0: disable, 1: enable */ +#define VIA_ACLINK_CTRL_RESET 0x40 /* 0: assert, 1: de-assert */ +#define VIA_ACLINK_CTRL_SYNC 0x20 /* 0: release SYNC, 1: force SYNC hi */ +#define VIA_ACLINK_CTRL_SDO 0x10 /* 0: release SDO, 1: force SDO hi */ +#define VIA_ACLINK_CTRL_VRA 0x08 /* 0: disable VRA, 1: enable VRA */ +#define VIA_ACLINK_CTRL_PCM 0x04 /* 0: disable PCM, 1: enable PCM */ +#define VIA_ACLINK_CTRL_FM 0x02 /* via686 only */ +#define VIA_ACLINK_CTRL_SB 0x01 /* via686 only */ +#define VIA_ACLINK_CTRL_INIT (VIA_ACLINK_CTRL_ENABLE|\ + VIA_ACLINK_CTRL_RESET|\ + VIA_ACLINK_CTRL_PCM) +#define VIA_FUNC_ENABLE 0x42 +#define VIA_FUNC_MIDI_PNP 0x80 /* FIXME: it's 0x40 in the datasheet! */ +#define VIA_FUNC_MIDI_IRQMASK 0x40 /* FIXME: not documented! */ +#define VIA_FUNC_RX2C_WRITE 0x20 +#define VIA_FUNC_SB_FIFO_EMPTY 0x10 +#define VIA_FUNC_ENABLE_GAME 0x08 +#define VIA_FUNC_ENABLE_FM 0x04 +#define VIA_FUNC_ENABLE_MIDI 0x02 +#define VIA_FUNC_ENABLE_SB 0x01 +#define VIA_PNP_CONTROL 0x43 +#define VIA_MC97_CTRL 0x44 +#define VIA_MC97_CTRL_ENABLE 0x80 +#define VIA_MC97_CTRL_SECONDARY 0x40 +#define VIA_MC97_CTRL_INIT (VIA_MC97_CTRL_ENABLE|\ + VIA_MC97_CTRL_SECONDARY) + + +/* + * pcm stream + */ + +struct snd_via_sg_table { + unsigned int offset; + unsigned int size; +} ; + +#define VIA_TABLE_SIZE 255 + +struct viadev { + unsigned int reg_offset; + unsigned long port; + int direction; /* playback = 0, capture = 1 */ + struct snd_pcm_substream *substream; + int running; + unsigned int tbl_entries; /* # descriptors */ + struct snd_dma_buffer table; + struct snd_via_sg_table *idx_table; + /* for recovery from the unexpected pointer */ + unsigned int lastpos; + unsigned int bufsize; + unsigned int bufsize2; +}; + +enum { TYPE_CARD_VIA82XX_MODEM = 1 }; + +#define VIA_MAX_MODEM_DEVS 2 + +struct via82xx_modem { + int irq; + + unsigned long port; + + unsigned int intr_mask; /* SGD_SHADOW mask to check interrupts */ + + struct pci_dev *pci; + struct snd_card *card; + + unsigned int num_devs; + unsigned int playback_devno, capture_devno; + struct viadev devs[VIA_MAX_MODEM_DEVS]; + + struct snd_pcm *pcms[2]; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + unsigned int ac97_clock; + unsigned int ac97_secondary; /* secondary AC'97 codec is present */ + + spinlock_t reg_lock; + struct snd_info_entry *proc_entry; +}; + +static const struct pci_device_id snd_via82xx_modem_ids[] = { + { PCI_VDEVICE(VIA, 0x3068), .driver_data = TYPE_CARD_VIA82XX_MODEM }, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_via82xx_modem_ids); + +/* + */ + +/* + * allocate and initialize the descriptor buffers + * periods = number of periods + * fragsize = period size in bytes + */ +static int build_via_table(struct viadev *dev, struct snd_pcm_substream *substream, + struct pci_dev *pci, + unsigned int periods, unsigned int fragsize) +{ + unsigned int i, idx, ofs, rest; + struct via82xx_modem *chip = snd_pcm_substream_chip(substream); + __le32 *pgtbl; + + if (dev->table.area == NULL) { + /* the start of each lists must be aligned to 8 bytes, + * but the kernel pages are much bigger, so we don't care + */ + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + PAGE_ALIGN(VIA_TABLE_SIZE * 2 * 8), + &dev->table) < 0) + return -ENOMEM; + } + if (! dev->idx_table) { + dev->idx_table = kmalloc_objs(*dev->idx_table, VIA_TABLE_SIZE); + if (! dev->idx_table) + return -ENOMEM; + } + + /* fill the entries */ + idx = 0; + ofs = 0; + pgtbl = (__le32 *)dev->table.area; + for (i = 0; i < periods; i++) { + rest = fragsize; + /* fill descriptors for a period. + * a period can be split to several descriptors if it's + * over page boundary. + */ + do { + unsigned int r; + unsigned int flag; + unsigned int addr; + + if (idx >= VIA_TABLE_SIZE) { + dev_err(&pci->dev, "too much table size!\n"); + return -EINVAL; + } + addr = snd_pcm_sgbuf_get_addr(substream, ofs); + pgtbl[idx << 1] = cpu_to_le32(addr); + r = PAGE_SIZE - (ofs % PAGE_SIZE); + if (rest < r) + r = rest; + rest -= r; + if (! rest) { + if (i == periods - 1) + flag = VIA_TBL_BIT_EOL; /* buffer boundary */ + else + flag = VIA_TBL_BIT_FLAG; /* period boundary */ + } else + flag = 0; /* period continues to the next */ + /* + dev_dbg(&pci->dev, + "tbl %d: at %d size %d (rest %d)\n", + idx, ofs, r, rest); + */ + pgtbl[(idx<<1) + 1] = cpu_to_le32(r | flag); + dev->idx_table[idx].offset = ofs; + dev->idx_table[idx].size = r; + ofs += r; + idx++; + } while (rest > 0); + } + dev->tbl_entries = idx; + dev->bufsize = periods * fragsize; + dev->bufsize2 = dev->bufsize / 2; + return 0; +} + + +static int clean_via_table(struct viadev *dev, struct snd_pcm_substream *substream, + struct pci_dev *pci) +{ + if (dev->table.area) { + snd_dma_free_pages(&dev->table); + dev->table.area = NULL; + } + kfree(dev->idx_table); + dev->idx_table = NULL; + return 0; +} + +/* + * Basic I/O + */ + +static inline unsigned int snd_via82xx_codec_xread(struct via82xx_modem *chip) +{ + return inl(VIAREG(chip, AC97)); +} + +static inline void snd_via82xx_codec_xwrite(struct via82xx_modem *chip, unsigned int val) +{ + outl(val, VIAREG(chip, AC97)); +} + +static int snd_via82xx_codec_ready(struct via82xx_modem *chip, int secondary) +{ + unsigned int timeout = 1000; /* 1ms */ + unsigned int val; + + while (timeout-- > 0) { + udelay(1); + val = snd_via82xx_codec_xread(chip); + if (!(val & VIA_REG_AC97_BUSY)) + return val & 0xffff; + } + dev_err(chip->card->dev, "codec_ready: codec %i is not ready [0x%x]\n", + secondary, snd_via82xx_codec_xread(chip)); + return -EIO; +} + +static int snd_via82xx_codec_valid(struct via82xx_modem *chip, int secondary) +{ + unsigned int timeout = 1000; /* 1ms */ + unsigned int val, val1; + unsigned int stat = !secondary ? VIA_REG_AC97_PRIMARY_VALID : + VIA_REG_AC97_SECONDARY_VALID; + + while (timeout-- > 0) { + val = snd_via82xx_codec_xread(chip); + val1 = val & (VIA_REG_AC97_BUSY | stat); + if (val1 == stat) + return val & 0xffff; + udelay(1); + } + return -EIO; +} + +static void snd_via82xx_codec_wait(struct snd_ac97 *ac97) +{ + struct via82xx_modem *chip = ac97->private_data; + __always_unused int err; + err = snd_via82xx_codec_ready(chip, ac97->num); + /* here we need to wait fairly for long time.. */ + msleep(500); +} + +static void snd_via82xx_codec_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct via82xx_modem *chip = ac97->private_data; + unsigned int xval; + if(reg == AC97_GPIO_STATUS) { + outl(val, VIAREG(chip, GPI_STATUS)); + return; + } + xval = !ac97->num ? VIA_REG_AC97_CODEC_ID_PRIMARY : VIA_REG_AC97_CODEC_ID_SECONDARY; + xval <<= VIA_REG_AC97_CODEC_ID_SHIFT; + xval |= reg << VIA_REG_AC97_CMD_SHIFT; + xval |= val << VIA_REG_AC97_DATA_SHIFT; + snd_via82xx_codec_xwrite(chip, xval); + snd_via82xx_codec_ready(chip, ac97->num); +} + +static unsigned short snd_via82xx_codec_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct via82xx_modem *chip = ac97->private_data; + unsigned int xval, val = 0xffff; + int again = 0; + + xval = ac97->num << VIA_REG_AC97_CODEC_ID_SHIFT; + xval |= ac97->num ? VIA_REG_AC97_SECONDARY_VALID : VIA_REG_AC97_PRIMARY_VALID; + xval |= VIA_REG_AC97_READ; + xval |= (reg & 0x7f) << VIA_REG_AC97_CMD_SHIFT; + while (1) { + if (again++ > 3) { + dev_err(chip->card->dev, + "codec_read: codec %i is not valid [0x%x]\n", + ac97->num, snd_via82xx_codec_xread(chip)); + return 0xffff; + } + snd_via82xx_codec_xwrite(chip, xval); + udelay (20); + if (snd_via82xx_codec_valid(chip, ac97->num) >= 0) { + udelay(25); + val = snd_via82xx_codec_xread(chip); + break; + } + } + return val & 0xffff; +} + +static void snd_via82xx_channel_reset(struct via82xx_modem *chip, struct viadev *viadev) +{ + outb(VIA_REG_CTRL_PAUSE | VIA_REG_CTRL_TERMINATE | VIA_REG_CTRL_RESET, + VIADEV_REG(viadev, OFFSET_CONTROL)); + inb(VIADEV_REG(viadev, OFFSET_CONTROL)); + udelay(50); + /* disable interrupts */ + outb(0x00, VIADEV_REG(viadev, OFFSET_CONTROL)); + /* clear interrupts */ + outb(0x03, VIADEV_REG(viadev, OFFSET_STATUS)); + outb(0x00, VIADEV_REG(viadev, OFFSET_TYPE)); /* for via686 */ + // outl(0, VIADEV_REG(viadev, OFFSET_CURR_PTR)); + viadev->lastpos = 0; +} + + +/* + * Interrupt handler + */ + +static irqreturn_t snd_via82xx_interrupt(int irq, void *dev_id) +{ + struct via82xx_modem *chip = dev_id; + unsigned int status; + unsigned int i; + + status = inl(VIAREG(chip, SGD_SHADOW)); + if (! (status & chip->intr_mask)) { + return IRQ_NONE; + } +// _skip_sgd: + + /* check status for each stream */ + guard(spinlock)(&chip->reg_lock); + for (i = 0; i < chip->num_devs; i++) { + struct viadev *viadev = &chip->devs[i]; + unsigned char c_status = inb(VIADEV_REG(viadev, OFFSET_STATUS)); + c_status &= (VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG|VIA_REG_STAT_STOPPED); + if (! c_status) + continue; + if (viadev->substream && viadev->running) { + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(viadev->substream); + spin_lock(&chip->reg_lock); + } + outb(c_status, VIADEV_REG(viadev, OFFSET_STATUS)); /* ack */ + } + return IRQ_HANDLED; +} + +/* + * PCM callbacks + */ + +/* + * trigger callback + */ +static int snd_via82xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct via82xx_modem *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + unsigned char val = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_SUSPEND: + val |= VIA_REG_CTRL_START; + viadev->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + val = VIA_REG_CTRL_TERMINATE; + viadev->running = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val |= VIA_REG_CTRL_PAUSE; + viadev->running = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + viadev->running = 1; + break; + default: + return -EINVAL; + } + outb(val, VIADEV_REG(viadev, OFFSET_CONTROL)); + if (cmd == SNDRV_PCM_TRIGGER_STOP) + snd_via82xx_channel_reset(chip, viadev); + return 0; +} + +/* + * pointer callbacks + */ + +/* + * calculate the linear position at the given sg-buffer index and the rest count + */ + +#define check_invalid_pos(viadev,pos) \ + ((pos) < viadev->lastpos && ((pos) >= viadev->bufsize2 ||\ + viadev->lastpos < viadev->bufsize2)) + +static inline unsigned int calc_linear_pos(struct via82xx_modem *chip, + struct viadev *viadev, + unsigned int idx, + unsigned int count) +{ + unsigned int size, res; + + size = viadev->idx_table[idx].size; + res = viadev->idx_table[idx].offset + size - count; + + /* check the validity of the calculated position */ + if (size < count) { + dev_err(chip->card->dev, + "invalid via82xx_cur_ptr (size = %d, count = %d)\n", + (int)size, (int)count); + res = viadev->lastpos; + } else if (check_invalid_pos(viadev, res)) { +#ifdef POINTER_DEBUG + dev_dbg(chip->card->dev, + "fail: idx = %i/%i, lastpos = 0x%x, bufsize2 = 0x%x, offsize = 0x%x, size = 0x%x, count = 0x%x\n", + idx, viadev->tbl_entries, viadev->lastpos, + viadev->bufsize2, viadev->idx_table[idx].offset, + viadev->idx_table[idx].size, count); +#endif + if (! count) + /* bogus count 0 on the DMA boundary? */ + res = viadev->idx_table[idx].offset; + else + /* count register returns full size + * when end of buffer is reached + */ + res = viadev->idx_table[idx].offset + size; + if (check_invalid_pos(viadev, res)) { + dev_dbg(chip->card->dev, + "invalid via82xx_cur_ptr (2), using last valid pointer\n"); + res = viadev->lastpos; + } + } + viadev->lastpos = res; /* remember the last position */ + if (res >= viadev->bufsize) + res -= viadev->bufsize; + return res; +} + +/* + * get the current pointer on via686 + */ +static snd_pcm_uframes_t snd_via686_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct via82xx_modem *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + unsigned int idx, ptr, count, res; + + if (snd_BUG_ON(!viadev->tbl_entries)) + return 0; + if (!(inb(VIADEV_REG(viadev, OFFSET_STATUS)) & VIA_REG_STAT_ACTIVE)) + return 0; + + guard(spinlock)(&chip->reg_lock); + count = inl(VIADEV_REG(viadev, OFFSET_CURR_COUNT)) & 0xffffff; + /* The via686a does not have the current index register, + * so we need to calculate the index from CURR_PTR. + */ + ptr = inl(VIADEV_REG(viadev, OFFSET_CURR_PTR)); + if (ptr <= (unsigned int)viadev->table.addr) + idx = 0; + else /* CURR_PTR holds the address + 8 */ + idx = ((ptr - (unsigned int)viadev->table.addr) / 8 - 1) % + viadev->tbl_entries; + res = calc_linear_pos(chip, viadev, idx, count); + + return bytes_to_frames(substream->runtime, res); +} + +/* + * hw_params callback: + * allocate the buffer and build up the buffer description table + */ +static int snd_via82xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct via82xx_modem *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + int err; + + err = build_via_table(viadev, substream, chip->pci, + params_periods(hw_params), + params_period_bytes(hw_params)); + if (err < 0) + return err; + + snd_ac97_write(chip->ac97, AC97_LINE1_RATE, params_rate(hw_params)); + snd_ac97_write(chip->ac97, AC97_LINE1_LEVEL, 0); + + return 0; +} + +/* + * hw_free callback: + * clean up the buffer description table and release the buffer + */ +static int snd_via82xx_hw_free(struct snd_pcm_substream *substream) +{ + struct via82xx_modem *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + + clean_via_table(viadev, substream, chip->pci); + return 0; +} + + +/* + * set up the table pointer + */ +static void snd_via82xx_set_table_ptr(struct via82xx_modem *chip, struct viadev *viadev) +{ + snd_via82xx_codec_ready(chip, chip->ac97_secondary); + outl((u32)viadev->table.addr, VIADEV_REG(viadev, OFFSET_TABLE_PTR)); + udelay(20); + snd_via82xx_codec_ready(chip, chip->ac97_secondary); +} + +/* + * prepare callback for playback and capture + */ +static int snd_via82xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct via82xx_modem *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + + snd_via82xx_channel_reset(chip, viadev); + /* this must be set after channel_reset */ + snd_via82xx_set_table_ptr(chip, viadev); + outb(VIA_REG_TYPE_AUTOSTART|VIA_REG_TYPE_INT_EOL|VIA_REG_TYPE_INT_FLAG, + VIADEV_REG(viadev, OFFSET_TYPE)); + return 0; +} + +/* + * pcm hardware definition, identical for both playback and capture + */ +static const struct snd_pcm_hardware snd_via82xx_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + /* SNDRV_PCM_INFO_RESUME | */ + SNDRV_PCM_INFO_PAUSE), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = VIA_TABLE_SIZE / 2, + .fifo_size = 0, +}; + + +/* + * open callback skeleton + */ +static int snd_via82xx_modem_pcm_open(struct via82xx_modem *chip, struct viadev *viadev, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + static const unsigned int rates[] = { 8000, 9600, 12000, 16000 }; + static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + + runtime->hw = snd_via82xx_hw; + + err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_rates); + if (err < 0) + return err; + + /* we may remove following constaint when we modify table entries + in interrupt */ + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + runtime->private_data = viadev; + viadev->substream = substream; + + return 0; +} + + +/* + * open callback for playback + */ +static int snd_via82xx_playback_open(struct snd_pcm_substream *substream) +{ + struct via82xx_modem *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = &chip->devs[chip->playback_devno + substream->number]; + + return snd_via82xx_modem_pcm_open(chip, viadev, substream); +} + +/* + * open callback for capture + */ +static int snd_via82xx_capture_open(struct snd_pcm_substream *substream) +{ + struct via82xx_modem *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = &chip->devs[chip->capture_devno + substream->pcm->device]; + + return snd_via82xx_modem_pcm_open(chip, viadev, substream); +} + +/* + * close callback + */ +static int snd_via82xx_pcm_close(struct snd_pcm_substream *substream) +{ + struct viadev *viadev = substream->runtime->private_data; + + viadev->substream = NULL; + return 0; +} + + +/* via686 playback callbacks */ +static const struct snd_pcm_ops snd_via686_playback_ops = { + .open = snd_via82xx_playback_open, + .close = snd_via82xx_pcm_close, + .hw_params = snd_via82xx_hw_params, + .hw_free = snd_via82xx_hw_free, + .prepare = snd_via82xx_pcm_prepare, + .trigger = snd_via82xx_pcm_trigger, + .pointer = snd_via686_pcm_pointer, +}; + +/* via686 capture callbacks */ +static const struct snd_pcm_ops snd_via686_capture_ops = { + .open = snd_via82xx_capture_open, + .close = snd_via82xx_pcm_close, + .hw_params = snd_via82xx_hw_params, + .hw_free = snd_via82xx_hw_free, + .prepare = snd_via82xx_pcm_prepare, + .trigger = snd_via82xx_pcm_trigger, + .pointer = snd_via686_pcm_pointer, +}; + + +static void init_viadev(struct via82xx_modem *chip, int idx, unsigned int reg_offset, + int direction) +{ + chip->devs[idx].reg_offset = reg_offset; + chip->devs[idx].direction = direction; + chip->devs[idx].port = chip->port + reg_offset; +} + +/* + * create a pcm instance for via686a/b + */ +static int snd_via686_pcm_new(struct via82xx_modem *chip) +{ + struct snd_pcm *pcm; + int err; + + chip->playback_devno = 0; + chip->capture_devno = 1; + chip->num_devs = 2; + chip->intr_mask = 0x330000; /* FLAGS | EOL for MR, MW */ + + err = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via686_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via686_capture_ops); + pcm->dev_class = SNDRV_PCM_CLASS_MODEM; + pcm->private_data = chip; + strscpy(pcm->name, chip->card->shortname); + chip->pcms[0] = pcm; + init_viadev(chip, 0, VIA_REG_MO_STATUS, 0); + init_viadev(chip, 1, VIA_REG_MI_STATUS, 1); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + &chip->pci->dev, 64*1024, 128*1024); + return 0; +} + + +/* + * Mixer part + */ + + +static void snd_via82xx_mixer_free_ac97_bus(struct snd_ac97_bus *bus) +{ + struct via82xx_modem *chip = bus->private_data; + chip->ac97_bus = NULL; +} + +static void snd_via82xx_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct via82xx_modem *chip = ac97->private_data; + chip->ac97 = NULL; +} + + +static int snd_via82xx_mixer_new(struct via82xx_modem *chip) +{ + struct snd_ac97_template ac97; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_via82xx_codec_write, + .read = snd_via82xx_codec_read, + .wait = snd_via82xx_codec_wait, + }; + + err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus); + if (err < 0) + return err; + chip->ac97_bus->private_free = snd_via82xx_mixer_free_ac97_bus; + chip->ac97_bus->clock = chip->ac97_clock; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.private_free = snd_via82xx_mixer_free_ac97; + ac97.pci = chip->pci; + ac97.scaps = AC97_SCAP_SKIP_AUDIO | AC97_SCAP_POWER_SAVE; + ac97.num = chip->ac97_secondary; + + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97); + if (err < 0) + return err; + + return 0; +} + + +/* + * proc interface + */ +static void snd_via82xx_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct via82xx_modem *chip = entry->private_data; + int i; + + snd_iprintf(buffer, "%s\n\n", chip->card->longname); + for (i = 0; i < 0xa0; i += 4) { + snd_iprintf(buffer, "%02x: %08x\n", i, inl(chip->port + i)); + } +} + +static void snd_via82xx_proc_init(struct via82xx_modem *chip) +{ + snd_card_ro_proc_new(chip->card, "via82xx", chip, + snd_via82xx_proc_read); +} + +/* + * + */ + +static int snd_via82xx_chip_init(struct via82xx_modem *chip) +{ + unsigned int val; + unsigned long end_time; + unsigned char pval; + + pci_read_config_byte(chip->pci, VIA_MC97_CTRL, &pval); + if((pval & VIA_MC97_CTRL_INIT) != VIA_MC97_CTRL_INIT) { + pci_write_config_byte(chip->pci, 0x44, pval|VIA_MC97_CTRL_INIT); + udelay(100); + } + + pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval); + if (! (pval & VIA_ACLINK_C00_READY)) { /* codec not ready? */ + /* deassert ACLink reset, force SYNC */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, + VIA_ACLINK_CTRL_ENABLE | + VIA_ACLINK_CTRL_RESET | + VIA_ACLINK_CTRL_SYNC); + udelay(100); +#if 1 /* FIXME: should we do full reset here for all chip models? */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, 0x00); + udelay(100); +#else + /* deassert ACLink reset, force SYNC (warm AC'97 reset) */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, + VIA_ACLINK_CTRL_RESET|VIA_ACLINK_CTRL_SYNC); + udelay(2); +#endif + /* ACLink on, deassert ACLink reset, VSR, SGD data out */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT); + udelay(100); + } + + pci_read_config_byte(chip->pci, VIA_ACLINK_CTRL, &pval); + if ((pval & VIA_ACLINK_CTRL_INIT) != VIA_ACLINK_CTRL_INIT) { + /* ACLink on, deassert ACLink reset, VSR, SGD data out */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT); + udelay(100); + } + + /* wait until codec ready */ + end_time = jiffies + msecs_to_jiffies(750); + do { + pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval); + if (pval & VIA_ACLINK_C00_READY) /* primary codec ready */ + break; + schedule_timeout_uninterruptible(1); + } while (time_before(jiffies, end_time)); + + val = snd_via82xx_codec_xread(chip); + if (val & VIA_REG_AC97_BUSY) + dev_err(chip->card->dev, + "AC'97 codec is not ready [0x%x]\n", val); + + snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ | + VIA_REG_AC97_SECONDARY_VALID | + (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT)); + end_time = jiffies + msecs_to_jiffies(750); + snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ | + VIA_REG_AC97_SECONDARY_VALID | + (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT)); + do { + val = snd_via82xx_codec_xread(chip); + if (val & VIA_REG_AC97_SECONDARY_VALID) { + chip->ac97_secondary = 1; + goto __ac97_ok2; + } + schedule_timeout_uninterruptible(1); + } while (time_before(jiffies, end_time)); + /* This is ok, the most of motherboards have only one codec */ + + __ac97_ok2: + + /* route FM trap to IRQ, disable FM trap */ + // pci_write_config_byte(chip->pci, VIA_FM_NMI_CTRL, 0); + /* disable all GPI interrupts */ + outl(0, VIAREG(chip, GPI_INTR)); + + return 0; +} + +/* + * power management + */ +static int snd_via82xx_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct via82xx_modem *chip = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + snd_ac97_suspend(chip->ac97); + return 0; +} + +static int snd_via82xx_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct via82xx_modem *chip = card->private_data; + int i; + + snd_via82xx_chip_init(chip); + + snd_ac97_resume(chip->ac97); + + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_via82xx_pm, snd_via82xx_suspend, snd_via82xx_resume); + +static void snd_via82xx_free(struct snd_card *card) +{ + struct via82xx_modem *chip = card->private_data; + unsigned int i; + + /* disable interrupts */ + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); +} + +static int snd_via82xx_create(struct snd_card *card, + struct pci_dev *pci, + int chip_type, + int revision, + unsigned int ac97_clock) +{ + struct via82xx_modem *chip = card->private_data; + int err; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + err = pcim_request_all_regions(pci, card->driver); + if (err < 0) + return err; + chip->port = pci_resource_start(pci, 0); + if (devm_request_irq(&pci->dev, pci->irq, snd_via82xx_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_via82xx_free; + if (ac97_clock >= 8000 && ac97_clock <= 48000) + chip->ac97_clock = ac97_clock; + + err = snd_via82xx_chip_init(chip); + if (err < 0) + return err; + + /* The 8233 ac97 controller does not implement the master bit + * in the pci command register. IMHO this is a violation of the PCI spec. + * We call pci_set_master here because it does not hurt. */ + pci_set_master(pci); + return 0; +} + + +static int __snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct via82xx_modem *chip; + int chip_type = 0, card_type; + unsigned int i; + int err; + + err = snd_devm_card_new(&pci->dev, index, id, THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + card_type = pci_id->driver_data; + switch (card_type) { + case TYPE_CARD_VIA82XX_MODEM: + strscpy(card->driver, "VIA82XX-MODEM"); + sprintf(card->shortname, "VIA 82XX modem"); + break; + default: + dev_err(card->dev, "invalid card type %d\n", card_type); + return -EINVAL; + } + + err = snd_via82xx_create(card, pci, chip_type, pci->revision, + ac97_clock); + if (err < 0) + return err; + err = snd_via82xx_mixer_new(chip); + if (err < 0) + return err; + + err = snd_via686_pcm_new(chip); + if (err < 0) + return err; + + /* disable interrupts */ + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + + sprintf(card->longname, "%s at 0x%lx, irq %d", + card->shortname, chip->port, chip->irq); + + snd_via82xx_proc_init(chip); + + err = snd_card_register(card); + if (err < 0) + return err; + pci_set_drvdata(pci, card); + return 0; +} + +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); +} + +static struct pci_driver via82xx_modem_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_via82xx_modem_ids, + .probe = snd_via82xx_probe, + .driver = { + .pm = &snd_via82xx_pm, + }, +}; + +module_pci_driver(via82xx_modem_driver); diff --git a/sound/pci/vx222/Makefile b/sound/pci/vx222/Makefile new file mode 100644 index 000000000..6889137eb --- /dev/null +++ b/sound/pci/vx222/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-vx222-y := vx222.o vx222_ops.o + +obj-$(CONFIG_SND_VX222) += snd-vx222.o diff --git a/sound/pci/vx222/vx222.c b/sound/pci/vx222/vx222.c new file mode 100644 index 000000000..0b89ca859 --- /dev/null +++ b/sound/pci/vx222/vx222.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX222 V2/Mic PCI soundcards + * + * Copyright (c) 2002 by Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "vx222.h" + +#define CARD_NAME "VX222" + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("Digigram VX222 V2/Mic"); +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_PNP; /* Enable this card */ +static bool mic[SNDRV_CARDS]; /* microphone */ +static int ibl[SNDRV_CARDS]; /* microphone */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Digigram " CARD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Digigram " CARD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Digigram " CARD_NAME " soundcard."); +module_param_array(mic, bool, NULL, 0444); +MODULE_PARM_DESC(mic, "Enable Microphone."); +module_param_array(ibl, int, NULL, 0444); +MODULE_PARM_DESC(ibl, "Capture IBL size."); + +/* + */ + +enum { + VX_PCI_VX222_OLD, + VX_PCI_VX222_NEW +}; + +static const struct pci_device_id snd_vx222_ids[] = { + { PCI_DEVICE_SUB(0x10b5, 0x9050, 0x1369, PCI_ANY_ID), .driver_data = VX_PCI_VX222_OLD }, /* PLX */ + { PCI_DEVICE_SUB(0x10b5, 0x9030, 0x1369, PCI_ANY_ID), .driver_data = VX_PCI_VX222_NEW }, /* PLX */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_vx222_ids); + + +/* + */ + +static const DECLARE_TLV_DB_SCALE(db_scale_old_vol, -11350, 50, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_akm, -7350, 50, 0); + +static const struct snd_vx_hardware vx222_old_hw = { + + .name = "VX222/Old", + .type = VX_TYPE_BOARD, + /* hw specs */ + .num_codecs = 1, + .num_ins = 1, + .num_outs = 1, + .output_level_max = VX_ANALOG_OUT_LEVEL_MAX, + .output_level_db_scale = db_scale_old_vol, +}; + +static const struct snd_vx_hardware vx222_v2_hw = { + + .name = "VX222/v2", + .type = VX_TYPE_V2, + /* hw specs */ + .num_codecs = 1, + .num_ins = 1, + .num_outs = 1, + .output_level_max = VX2_AKM_LEVEL_MAX, + .output_level_db_scale = db_scale_akm, +}; + +static const struct snd_vx_hardware vx222_mic_hw = { + + .name = "VX222/Mic", + .type = VX_TYPE_MIC, + /* hw specs */ + .num_codecs = 1, + .num_ins = 1, + .num_outs = 1, + .output_level_max = VX2_AKM_LEVEL_MAX, + .output_level_db_scale = db_scale_akm, +}; + + +/* + */ +static int snd_vx222_create(struct snd_card *card, struct pci_dev *pci, + const struct snd_vx_hardware *hw, + struct snd_vx222 **rchip) +{ + struct vx_core *chip; + struct snd_vx222 *vx; + int i, err; + const struct snd_vx_ops *vx_ops; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + pci_set_master(pci); + + vx_ops = hw->type == VX_TYPE_BOARD ? &vx222_old_ops : &vx222_ops; + chip = snd_vx_create(card, hw, vx_ops, + sizeof(struct snd_vx222) - sizeof(struct vx_core)); + if (!chip) + return -ENOMEM; + vx = to_vx222(chip); + vx->pci = pci; + + err = pcim_request_all_regions(pci, KBUILD_MODNAME); + if (err < 0) + return err; + for (i = 0; i < 2; i++) + vx->port[i] = pci_resource_start(pci, i + 1); + + if (devm_request_threaded_irq(&pci->dev, pci->irq, snd_vx_irq_handler, + snd_vx_threaded_irq_handler, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + *rchip = vx; + + return 0; +} + + +static int snd_vx222_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + const struct snd_vx_hardware *hw; + struct snd_vx222 *vx; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) + return err; + + switch ((int)pci_id->driver_data) { + case VX_PCI_VX222_OLD: + hw = &vx222_old_hw; + break; + case VX_PCI_VX222_NEW: + default: + if (mic[dev]) + hw = &vx222_mic_hw; + else + hw = &vx222_v2_hw; + break; + } + err = snd_vx222_create(card, pci, hw, &vx); + if (err < 0) + return err; + card->private_data = vx; + vx->core.ibl.size = ibl[dev]; + + sprintf(card->longname, "%s at 0x%lx & 0x%lx, irq %i", + card->shortname, vx->port[0], vx->port[1], vx->core.irq); + dev_dbg(card->dev, "%s at 0x%lx & 0x%lx, irq %i\n", + card->shortname, vx->port[0], vx->port[1], vx->core.irq); + +#ifdef SND_VX_FW_LOADER + vx->core.dev = &pci->dev; +#endif + + err = snd_vx_setup_firmware(&vx->core); + if (err < 0) + return err; + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_vx222_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_vx222 *vx = card->private_data; + + return snd_vx_suspend(&vx->core); +} + +static int snd_vx222_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_vx222 *vx = card->private_data; + + return snd_vx_resume(&vx->core); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(snd_vx222_pm, snd_vx222_suspend, snd_vx222_resume); + +static struct pci_driver vx222_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_vx222_ids, + .probe = snd_vx222_probe, + .driver = { + .pm = pm_ptr(&snd_vx222_pm), + }, +}; + +module_pci_driver(vx222_driver); diff --git a/sound/pci/vx222/vx222.h b/sound/pci/vx222/vx222.h new file mode 100644 index 000000000..46ddc6858 --- /dev/null +++ b/sound/pci/vx222/vx222.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram VX222 PCI soundcards + * + * Copyright (c) 2002 by Takashi Iwai + */ + +#ifndef __VX222_H +#define __VX222_H + +#include + +struct snd_vx222 { + + struct vx_core core; + + /* h/w config; for PLX and for DSP */ + struct pci_dev *pci; + unsigned long port[2]; + + unsigned int regCDSP; /* current CDSP register */ + unsigned int regCFG; /* current CFG register */ + unsigned int regSELMIC; /* current SELMIC reg. (for VX222 Mic) */ + + int input_level[2]; /* input level for vx222 mic */ + int mic_level; /* mic level for vx222 mic */ +}; + +#define to_vx222(x) container_of(x, struct snd_vx222, core) + +/* we use a lookup table with 148 values, see vx_mixer.c */ +#define VX2_AKM_LEVEL_MAX 0x93 + +extern const struct snd_vx_ops vx222_ops; +extern const struct snd_vx_ops vx222_old_ops; + +/* Offset of registers with base equal to portDSP. */ +#define VX_RESET_DMA_REGISTER_OFFSET 0x00000008 + +/* Constants used to access the INTCSR register. */ +#define VX_INTCSR_VALUE 0x00000001 +#define VX_PCI_INTERRUPT_MASK 0x00000040 + +/* Constants used to access the CDSP register (0x20). */ +#define VX_CDSP_TEST1_MASK 0x00000080 +#define VX_CDSP_TOR1_MASK 0x00000040 +#define VX_CDSP_TOR2_MASK 0x00000020 +#define VX_CDSP_RESERVED0_0_MASK 0x00000010 +#define VX_CDSP_CODEC_RESET_MASK 0x00000008 +#define VX_CDSP_VALID_IRQ_MASK 0x00000004 +#define VX_CDSP_TEST0_MASK 0x00000002 +#define VX_CDSP_DSP_RESET_MASK 0x00000001 + +#define VX_CDSP_GPIO_OUT_MASK 0x00000060 +#define VX_GPIO_OUT_BIT_OFFSET 5 // transform output to bit 0 and 1 + +/* Constants used to access the CFG register (0x24). */ +#define VX_CFG_SYNCDSP_MASK 0x00000080 +#define VX_CFG_RESERVED0_0_MASK 0x00000040 +#define VX_CFG_RESERVED1_0_MASK 0x00000020 +#define VX_CFG_RESERVED2_0_MASK 0x00000010 +#define VX_CFG_DATAIN_SEL_MASK 0x00000008 // 0 (ana), 1 (UER) +#define VX_CFG_RESERVED3_0_MASK 0x00000004 +#define VX_CFG_RESERVED4_0_MASK 0x00000002 +#define VX_CFG_CLOCKIN_SEL_MASK 0x00000001 // 0 (internal), 1 (AES/EBU) + +/* Constants used to access the STATUS register (0x30). */ +#define VX_STATUS_DATA_XICOR_MASK 0x00000080 +#define VX_STATUS_VAL_TEST1_MASK 0x00000040 +#define VX_STATUS_VAL_TEST0_MASK 0x00000020 +#define VX_STATUS_RESERVED0_MASK 0x00000010 +#define VX_STATUS_VAL_TOR1_MASK 0x00000008 +#define VX_STATUS_VAL_TOR0_MASK 0x00000004 +#define VX_STATUS_LEVEL_IN_MASK 0x00000002 // 6 dBu (0), 22 dBu (1) +#define VX_STATUS_MEMIRQ_MASK 0x00000001 + +#define VX_STATUS_GPIO_IN_MASK 0x0000000C +#define VX_GPIO_IN_BIT_OFFSET 0 // leave input as bit 2 and 3 + +/* Constants used to access the MICRO INPUT SELECT register (0x40). */ +#define MICRO_SELECT_INPUT_NORM 0x00 +#define MICRO_SELECT_INPUT_MUTE 0x01 +#define MICRO_SELECT_INPUT_LIMIT 0x02 +#define MICRO_SELECT_INPUT_MASK 0x03 + +#define MICRO_SELECT_PREAMPLI_G_0 0x00 +#define MICRO_SELECT_PREAMPLI_G_1 0x04 +#define MICRO_SELECT_PREAMPLI_G_2 0x08 +#define MICRO_SELECT_PREAMPLI_G_3 0x0C +#define MICRO_SELECT_PREAMPLI_MASK 0x0C +#define MICRO_SELECT_PREAMPLI_OFFSET 2 + +#define MICRO_SELECT_RAISE_COMPR 0x10 + +#define MICRO_SELECT_NOISE_T_52DB 0x00 +#define MICRO_SELECT_NOISE_T_42DB 0x20 +#define MICRO_SELECT_NOISE_T_32DB 0x40 +#define MICRO_SELECT_NOISE_T_MASK 0x60 + +#define MICRO_SELECT_PHANTOM_ALIM 0x80 + + +#endif /* __VX222_H */ diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c new file mode 100644 index 000000000..b6459dbdb --- /dev/null +++ b/sound/pci/vx222/vx222_ops.c @@ -0,0 +1,1024 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX222 V2/Mic soundcards + * + * VX222-specific low-level routines + * + * Copyright (c) 2002 by Takashi Iwai + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "vx222.h" + + +static const int vx2_reg_offset[VX_REG_MAX] = { + [VX_ICR] = 0x00, + [VX_CVR] = 0x04, + [VX_ISR] = 0x08, + [VX_IVR] = 0x0c, + [VX_RXH] = 0x14, + [VX_RXM] = 0x18, + [VX_RXL] = 0x1c, + [VX_DMA] = 0x10, + [VX_CDSP] = 0x20, + [VX_CFG] = 0x24, + [VX_RUER] = 0x28, + [VX_DATA] = 0x2c, + [VX_STATUS] = 0x30, + [VX_LOFREQ] = 0x34, + [VX_HIFREQ] = 0x38, + [VX_CSUER] = 0x3c, + [VX_SELMIC] = 0x40, + [VX_COMPOT] = 0x44, // Write: POTENTIOMETER ; Read: COMPRESSION LEVEL activate + [VX_SCOMPR] = 0x48, // Read: COMPRESSION THRESHOLD activate + [VX_GLIMIT] = 0x4c, // Read: LEVEL LIMITATION activate + [VX_INTCSR] = 0x4c, // VX_INTCSR_REGISTER_OFFSET + [VX_CNTRL] = 0x50, // VX_CNTRL_REGISTER_OFFSET + [VX_GPIOC] = 0x54, // VX_GPIOC (new with PLX9030) +}; + +static const int vx2_reg_index[VX_REG_MAX] = { + [VX_ICR] = 1, + [VX_CVR] = 1, + [VX_ISR] = 1, + [VX_IVR] = 1, + [VX_RXH] = 1, + [VX_RXM] = 1, + [VX_RXL] = 1, + [VX_DMA] = 1, + [VX_CDSP] = 1, + [VX_CFG] = 1, + [VX_RUER] = 1, + [VX_DATA] = 1, + [VX_STATUS] = 1, + [VX_LOFREQ] = 1, + [VX_HIFREQ] = 1, + [VX_CSUER] = 1, + [VX_SELMIC] = 1, + [VX_COMPOT] = 1, + [VX_SCOMPR] = 1, + [VX_GLIMIT] = 1, + [VX_INTCSR] = 0, /* on the PLX */ + [VX_CNTRL] = 0, /* on the PLX */ + [VX_GPIOC] = 0, /* on the PLX */ +}; + +static inline unsigned long vx2_reg_addr(struct vx_core *_chip, int reg) +{ + struct snd_vx222 *chip = to_vx222(_chip); + return chip->port[vx2_reg_index[reg]] + vx2_reg_offset[reg]; +} + +/** + * vx2_inb - read a byte from the register + * @chip: VX core instance + * @offset: register enum + */ +static unsigned char vx2_inb(struct vx_core *chip, int offset) +{ + return inb(vx2_reg_addr(chip, offset)); +} + +/** + * vx2_outb - write a byte on the register + * @chip: VX core instance + * @offset: the register offset + * @val: the value to write + */ +static void vx2_outb(struct vx_core *chip, int offset, unsigned char val) +{ + outb(val, vx2_reg_addr(chip, offset)); + /* + dev_dbg(chip->card->dev, "outb: %x -> %x\n", val, vx2_reg_addr(chip, offset)); + */ +} + +/** + * vx2_inl - read a 32bit word from the register + * @chip: VX core instance + * @offset: register enum + */ +static unsigned int vx2_inl(struct vx_core *chip, int offset) +{ + return inl(vx2_reg_addr(chip, offset)); +} + +/** + * vx2_outl - write a 32bit word on the register + * @chip: VX core instance + * @offset: the register enum + * @val: the value to write + */ +static void vx2_outl(struct vx_core *chip, int offset, unsigned int val) +{ + /* + dev_dbg(chip->card->dev, "outl: %x -> %x\n", val, vx2_reg_addr(chip, offset)); + */ + outl(val, vx2_reg_addr(chip, offset)); +} + +/* + * redefine macros to call directly + */ +#undef vx_inb +#define vx_inb(chip,reg) vx2_inb((struct vx_core*)(chip), VX_##reg) +#undef vx_outb +#define vx_outb(chip,reg,val) vx2_outb((struct vx_core*)(chip), VX_##reg, val) +#undef vx_inl +#define vx_inl(chip,reg) vx2_inl((struct vx_core*)(chip), VX_##reg) +#undef vx_outl +#define vx_outl(chip,reg,val) vx2_outl((struct vx_core*)(chip), VX_##reg, val) + + +/* + * vx_reset_dsp - reset the DSP + */ + +#define XX_DSP_RESET_WAIT_TIME 2 /* ms */ + +static void vx2_reset_dsp(struct vx_core *_chip) +{ + struct snd_vx222 *chip = to_vx222(_chip); + + /* set the reset dsp bit to 0 */ + vx_outl(chip, CDSP, chip->regCDSP & ~VX_CDSP_DSP_RESET_MASK); + + mdelay(XX_DSP_RESET_WAIT_TIME); + + chip->regCDSP |= VX_CDSP_DSP_RESET_MASK; + /* set the reset dsp bit to 1 */ + vx_outl(chip, CDSP, chip->regCDSP); +} + + +static int vx2_test_xilinx(struct vx_core *_chip) +{ + struct snd_vx222 *chip = to_vx222(_chip); + unsigned int data; + + dev_dbg(_chip->card->dev, "testing xilinx...\n"); + /* This test uses several write/read sequences on TEST0 and TEST1 bits + * to figure out whever or not the xilinx was correctly loaded + */ + + /* We write 1 on CDSP.TEST0. We should get 0 on STATUS.TEST0. */ + vx_outl(chip, CDSP, chip->regCDSP | VX_CDSP_TEST0_MASK); + vx_inl(chip, ISR); + data = vx_inl(chip, STATUS); + if ((data & VX_STATUS_VAL_TEST0_MASK) == VX_STATUS_VAL_TEST0_MASK) { + dev_dbg(_chip->card->dev, "bad!\n"); + return -ENODEV; + } + + /* We write 0 on CDSP.TEST0. We should get 1 on STATUS.TEST0. */ + vx_outl(chip, CDSP, chip->regCDSP & ~VX_CDSP_TEST0_MASK); + vx_inl(chip, ISR); + data = vx_inl(chip, STATUS); + if (! (data & VX_STATUS_VAL_TEST0_MASK)) { + dev_dbg(_chip->card->dev, "bad! #2\n"); + return -ENODEV; + } + + if (_chip->type == VX_TYPE_BOARD) { + /* not implemented on VX_2_BOARDS */ + /* We write 1 on CDSP.TEST1. We should get 0 on STATUS.TEST1. */ + vx_outl(chip, CDSP, chip->regCDSP | VX_CDSP_TEST1_MASK); + vx_inl(chip, ISR); + data = vx_inl(chip, STATUS); + if ((data & VX_STATUS_VAL_TEST1_MASK) == VX_STATUS_VAL_TEST1_MASK) { + dev_dbg(_chip->card->dev, "bad! #3\n"); + return -ENODEV; + } + + /* We write 0 on CDSP.TEST1. We should get 1 on STATUS.TEST1. */ + vx_outl(chip, CDSP, chip->regCDSP & ~VX_CDSP_TEST1_MASK); + vx_inl(chip, ISR); + data = vx_inl(chip, STATUS); + if (! (data & VX_STATUS_VAL_TEST1_MASK)) { + dev_dbg(_chip->card->dev, "bad! #4\n"); + return -ENODEV; + } + } + dev_dbg(_chip->card->dev, "ok, xilinx fine.\n"); + return 0; +} + + +/** + * vx2_setup_pseudo_dma - set up the pseudo dma read/write mode. + * @chip: VX core instance + * @do_write: 0 = read, 1 = set up for DMA write + */ +static void vx2_setup_pseudo_dma(struct vx_core *chip, int do_write) +{ + /* Interrupt mode and HREQ pin enabled for host transmit data transfers + * (in case of the use of the pseudo-dma facility). + */ + vx_outl(chip, ICR, do_write ? ICR_TREQ : ICR_RREQ); + + /* Reset the pseudo-dma register (in case of the use of the + * pseudo-dma facility). + */ + vx_outl(chip, RESET_DMA, 0); +} + +/* + * vx_release_pseudo_dma - disable the pseudo-DMA mode + */ +static inline void vx2_release_pseudo_dma(struct vx_core *chip) +{ + /* HREQ pin disabled. */ + vx_outl(chip, ICR, 0); +} + + + +/* pseudo-dma write */ +static void vx2_dma_write(struct vx_core *chip, struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe, int count) +{ + unsigned long port = vx2_reg_addr(chip, VX_DMA); + int offset = pipe->hw_ptr; + u32 *addr = (u32 *)(runtime->dma_area + offset); + + if (snd_BUG_ON(count % 4)) + return; + + vx2_setup_pseudo_dma(chip, 1); + + /* Transfer using pseudo-dma. + */ + if (offset + count >= pipe->buffer_bytes) { + int length = pipe->buffer_bytes - offset; + count -= length; + length >>= 2; /* in 32bit words */ + /* Transfer using pseudo-dma. */ + for (; length > 0; length--) { + outl(*addr, port); + addr++; + } + addr = (u32 *)runtime->dma_area; + pipe->hw_ptr = 0; + } + pipe->hw_ptr += count; + count >>= 2; /* in 32bit words */ + /* Transfer using pseudo-dma. */ + for (; count > 0; count--) { + outl(*addr, port); + addr++; + } + + vx2_release_pseudo_dma(chip); +} + + +/* pseudo dma read */ +static void vx2_dma_read(struct vx_core *chip, struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe, int count) +{ + int offset = pipe->hw_ptr; + u32 *addr = (u32 *)(runtime->dma_area + offset); + unsigned long port = vx2_reg_addr(chip, VX_DMA); + + if (snd_BUG_ON(count % 4)) + return; + + vx2_setup_pseudo_dma(chip, 0); + /* Transfer using pseudo-dma. + */ + if (offset + count >= pipe->buffer_bytes) { + int length = pipe->buffer_bytes - offset; + count -= length; + length >>= 2; /* in 32bit words */ + /* Transfer using pseudo-dma. */ + for (; length > 0; length--) + *addr++ = inl(port); + addr = (u32 *)runtime->dma_area; + pipe->hw_ptr = 0; + } + pipe->hw_ptr += count; + count >>= 2; /* in 32bit words */ + /* Transfer using pseudo-dma. */ + for (; count > 0; count--) + *addr++ = inl(port); + + vx2_release_pseudo_dma(chip); +} + +#define VX_XILINX_RESET_MASK 0x40000000 +#define VX_USERBIT0_MASK 0x00000004 +#define VX_USERBIT1_MASK 0x00000020 +#define VX_CNTRL_REGISTER_VALUE 0x00172012 + +/* + * transfer counts bits to PLX + */ +static int put_xilinx_data(struct vx_core *chip, unsigned int port, unsigned int counts, unsigned char data) +{ + unsigned int i; + + for (i = 0; i < counts; i++) { + unsigned int val; + + /* set the clock bit to 0. */ + val = VX_CNTRL_REGISTER_VALUE & ~VX_USERBIT0_MASK; + vx2_outl(chip, port, val); + vx2_inl(chip, port); + udelay(1); + + if (data & (1 << i)) + val |= VX_USERBIT1_MASK; + else + val &= ~VX_USERBIT1_MASK; + vx2_outl(chip, port, val); + vx2_inl(chip, port); + + /* set the clock bit to 1. */ + val |= VX_USERBIT0_MASK; + vx2_outl(chip, port, val); + vx2_inl(chip, port); + udelay(1); + } + return 0; +} + +/* + * load the xilinx image + */ +static int vx2_load_xilinx_binary(struct vx_core *chip, const struct firmware *xilinx) +{ + unsigned int i; + unsigned int port; + const unsigned char *image; + + /* XILINX reset (wait at least 1 millisecond between reset on and off). */ + vx_outl(chip, CNTRL, VX_CNTRL_REGISTER_VALUE | VX_XILINX_RESET_MASK); + vx_inl(chip, CNTRL); + msleep(10); + vx_outl(chip, CNTRL, VX_CNTRL_REGISTER_VALUE); + vx_inl(chip, CNTRL); + msleep(10); + + if (chip->type == VX_TYPE_BOARD) + port = VX_CNTRL; + else + port = VX_GPIOC; /* VX222 V2 and VX222_MIC_BOARD with new PLX9030 use this register */ + + image = xilinx->data; + for (i = 0; i < xilinx->size; i++, image++) { + if (put_xilinx_data(chip, port, 8, *image) < 0) + return -EINVAL; + /* don't take too much time in this loop... */ + cond_resched(); + } + put_xilinx_data(chip, port, 4, 0xff); /* end signature */ + + msleep(200); + + /* test after loading (is buggy with VX222) */ + if (chip->type != VX_TYPE_BOARD) { + /* Test if load successful: test bit 8 of register GPIOC (VX222: use CNTRL) ! */ + i = vx_inl(chip, GPIOC); + if (i & 0x0100) + return 0; + dev_err(chip->card->dev, + "xilinx test failed after load, GPIOC=0x%x\n", i); + return -EINVAL; + } + + return 0; +} + + +/* + * load the boot/dsp images + */ +static int vx2_load_dsp(struct vx_core *vx, int index, const struct firmware *dsp) +{ + int err; + + switch (index) { + case 1: + /* xilinx image */ + err = vx2_load_xilinx_binary(vx, dsp); + if (err < 0) + return err; + err = vx2_test_xilinx(vx); + if (err < 0) + return err; + return 0; + case 2: + /* DSP boot */ + return snd_vx_dsp_boot(vx, dsp); + case 3: + /* DSP image */ + return snd_vx_dsp_load(vx, dsp); + default: + snd_BUG(); + return -EINVAL; + } +} + + +/* + * vx_test_and_ack - test and acknowledge interrupt + * + * called from irq hander, too + * + * spinlock held! + */ +static int vx2_test_and_ack(struct vx_core *chip) +{ + /* not booted yet? */ + if (! (chip->chip_status & VX_STAT_XILINX_LOADED)) + return -ENXIO; + + if (! (vx_inl(chip, STATUS) & VX_STATUS_MEMIRQ_MASK)) + return -EIO; + + /* ok, interrupts generated, now ack it */ + /* set ACQUIT bit up and down */ + vx_outl(chip, STATUS, 0); + /* useless read just to spend some time and maintain + * the ACQUIT signal up for a while ( a bus cycle ) + */ + vx_inl(chip, STATUS); + /* ack */ + vx_outl(chip, STATUS, VX_STATUS_MEMIRQ_MASK); + /* useless read just to spend some time and maintain + * the ACQUIT signal up for a while ( a bus cycle ) */ + vx_inl(chip, STATUS); + /* clear */ + vx_outl(chip, STATUS, 0); + + return 0; +} + + +/* + * vx_validate_irq - enable/disable IRQ + */ +static void vx2_validate_irq(struct vx_core *_chip, int enable) +{ + struct snd_vx222 *chip = to_vx222(_chip); + + /* Set the interrupt enable bit to 1 in CDSP register */ + if (enable) { + /* Set the PCI interrupt enable bit to 1.*/ + vx_outl(chip, INTCSR, VX_INTCSR_VALUE|VX_PCI_INTERRUPT_MASK); + chip->regCDSP |= VX_CDSP_VALID_IRQ_MASK; + } else { + /* Set the PCI interrupt enable bit to 0. */ + vx_outl(chip, INTCSR, VX_INTCSR_VALUE&~VX_PCI_INTERRUPT_MASK); + chip->regCDSP &= ~VX_CDSP_VALID_IRQ_MASK; + } + vx_outl(chip, CDSP, chip->regCDSP); +} + + +/* + * write an AKM codec data (24bit) + */ +static void vx2_write_codec_reg(struct vx_core *chip, unsigned int data) +{ + unsigned int i; + + vx_inl(chip, HIFREQ); + + /* We have to send 24 bits (3 x 8 bits). Start with most signif. Bit */ + for (i = 0; i < 24; i++, data <<= 1) + vx_outl(chip, DATA, ((data & 0x800000) ? VX_DATA_CODEC_MASK : 0)); + /* Terminate access to codec registers */ + vx_inl(chip, RUER); +} + + +#define AKM_CODEC_POWER_CONTROL_CMD 0xA007 +#define AKM_CODEC_RESET_ON_CMD 0xA100 +#define AKM_CODEC_RESET_OFF_CMD 0xA103 +#define AKM_CODEC_CLOCK_FORMAT_CMD 0xA240 +#define AKM_CODEC_MUTE_CMD 0xA38D +#define AKM_CODEC_UNMUTE_CMD 0xA30D +#define AKM_CODEC_LEFT_LEVEL_CMD 0xA400 +#define AKM_CODEC_RIGHT_LEVEL_CMD 0xA500 + +static const u8 vx2_akm_gains_lut[VX2_AKM_LEVEL_MAX+1] = { + 0x7f, // [000] = +0.000 dB -> AKM(0x7f) = +0.000 dB error(+0.000 dB) + 0x7d, // [001] = -0.500 dB -> AKM(0x7d) = -0.572 dB error(-0.072 dB) + 0x7c, // [002] = -1.000 dB -> AKM(0x7c) = -0.873 dB error(+0.127 dB) + 0x7a, // [003] = -1.500 dB -> AKM(0x7a) = -1.508 dB error(-0.008 dB) + 0x79, // [004] = -2.000 dB -> AKM(0x79) = -1.844 dB error(+0.156 dB) + 0x77, // [005] = -2.500 dB -> AKM(0x77) = -2.557 dB error(-0.057 dB) + 0x76, // [006] = -3.000 dB -> AKM(0x76) = -2.937 dB error(+0.063 dB) + 0x75, // [007] = -3.500 dB -> AKM(0x75) = -3.334 dB error(+0.166 dB) + 0x73, // [008] = -4.000 dB -> AKM(0x73) = -4.188 dB error(-0.188 dB) + 0x72, // [009] = -4.500 dB -> AKM(0x72) = -4.648 dB error(-0.148 dB) + 0x71, // [010] = -5.000 dB -> AKM(0x71) = -5.134 dB error(-0.134 dB) + 0x70, // [011] = -5.500 dB -> AKM(0x70) = -5.649 dB error(-0.149 dB) + 0x6f, // [012] = -6.000 dB -> AKM(0x6f) = -6.056 dB error(-0.056 dB) + 0x6d, // [013] = -6.500 dB -> AKM(0x6d) = -6.631 dB error(-0.131 dB) + 0x6c, // [014] = -7.000 dB -> AKM(0x6c) = -6.933 dB error(+0.067 dB) + 0x6a, // [015] = -7.500 dB -> AKM(0x6a) = -7.571 dB error(-0.071 dB) + 0x69, // [016] = -8.000 dB -> AKM(0x69) = -7.909 dB error(+0.091 dB) + 0x67, // [017] = -8.500 dB -> AKM(0x67) = -8.626 dB error(-0.126 dB) + 0x66, // [018] = -9.000 dB -> AKM(0x66) = -9.008 dB error(-0.008 dB) + 0x65, // [019] = -9.500 dB -> AKM(0x65) = -9.407 dB error(+0.093 dB) + 0x64, // [020] = -10.000 dB -> AKM(0x64) = -9.826 dB error(+0.174 dB) + 0x62, // [021] = -10.500 dB -> AKM(0x62) = -10.730 dB error(-0.230 dB) + 0x61, // [022] = -11.000 dB -> AKM(0x61) = -11.219 dB error(-0.219 dB) + 0x60, // [023] = -11.500 dB -> AKM(0x60) = -11.738 dB error(-0.238 dB) + 0x5f, // [024] = -12.000 dB -> AKM(0x5f) = -12.149 dB error(-0.149 dB) + 0x5e, // [025] = -12.500 dB -> AKM(0x5e) = -12.434 dB error(+0.066 dB) + 0x5c, // [026] = -13.000 dB -> AKM(0x5c) = -13.033 dB error(-0.033 dB) + 0x5b, // [027] = -13.500 dB -> AKM(0x5b) = -13.350 dB error(+0.150 dB) + 0x59, // [028] = -14.000 dB -> AKM(0x59) = -14.018 dB error(-0.018 dB) + 0x58, // [029] = -14.500 dB -> AKM(0x58) = -14.373 dB error(+0.127 dB) + 0x56, // [030] = -15.000 dB -> AKM(0x56) = -15.130 dB error(-0.130 dB) + 0x55, // [031] = -15.500 dB -> AKM(0x55) = -15.534 dB error(-0.034 dB) + 0x54, // [032] = -16.000 dB -> AKM(0x54) = -15.958 dB error(+0.042 dB) + 0x53, // [033] = -16.500 dB -> AKM(0x53) = -16.404 dB error(+0.096 dB) + 0x52, // [034] = -17.000 dB -> AKM(0x52) = -16.874 dB error(+0.126 dB) + 0x51, // [035] = -17.500 dB -> AKM(0x51) = -17.371 dB error(+0.129 dB) + 0x50, // [036] = -18.000 dB -> AKM(0x50) = -17.898 dB error(+0.102 dB) + 0x4e, // [037] = -18.500 dB -> AKM(0x4e) = -18.605 dB error(-0.105 dB) + 0x4d, // [038] = -19.000 dB -> AKM(0x4d) = -18.905 dB error(+0.095 dB) + 0x4b, // [039] = -19.500 dB -> AKM(0x4b) = -19.538 dB error(-0.038 dB) + 0x4a, // [040] = -20.000 dB -> AKM(0x4a) = -19.872 dB error(+0.128 dB) + 0x48, // [041] = -20.500 dB -> AKM(0x48) = -20.583 dB error(-0.083 dB) + 0x47, // [042] = -21.000 dB -> AKM(0x47) = -20.961 dB error(+0.039 dB) + 0x46, // [043] = -21.500 dB -> AKM(0x46) = -21.356 dB error(+0.144 dB) + 0x44, // [044] = -22.000 dB -> AKM(0x44) = -22.206 dB error(-0.206 dB) + 0x43, // [045] = -22.500 dB -> AKM(0x43) = -22.664 dB error(-0.164 dB) + 0x42, // [046] = -23.000 dB -> AKM(0x42) = -23.147 dB error(-0.147 dB) + 0x41, // [047] = -23.500 dB -> AKM(0x41) = -23.659 dB error(-0.159 dB) + 0x40, // [048] = -24.000 dB -> AKM(0x40) = -24.203 dB error(-0.203 dB) + 0x3f, // [049] = -24.500 dB -> AKM(0x3f) = -24.635 dB error(-0.135 dB) + 0x3e, // [050] = -25.000 dB -> AKM(0x3e) = -24.935 dB error(+0.065 dB) + 0x3c, // [051] = -25.500 dB -> AKM(0x3c) = -25.569 dB error(-0.069 dB) + 0x3b, // [052] = -26.000 dB -> AKM(0x3b) = -25.904 dB error(+0.096 dB) + 0x39, // [053] = -26.500 dB -> AKM(0x39) = -26.615 dB error(-0.115 dB) + 0x38, // [054] = -27.000 dB -> AKM(0x38) = -26.994 dB error(+0.006 dB) + 0x37, // [055] = -27.500 dB -> AKM(0x37) = -27.390 dB error(+0.110 dB) + 0x36, // [056] = -28.000 dB -> AKM(0x36) = -27.804 dB error(+0.196 dB) + 0x34, // [057] = -28.500 dB -> AKM(0x34) = -28.699 dB error(-0.199 dB) + 0x33, // [058] = -29.000 dB -> AKM(0x33) = -29.183 dB error(-0.183 dB) + 0x32, // [059] = -29.500 dB -> AKM(0x32) = -29.696 dB error(-0.196 dB) + 0x31, // [060] = -30.000 dB -> AKM(0x31) = -30.241 dB error(-0.241 dB) + 0x31, // [061] = -30.500 dB -> AKM(0x31) = -30.241 dB error(+0.259 dB) + 0x30, // [062] = -31.000 dB -> AKM(0x30) = -30.823 dB error(+0.177 dB) + 0x2e, // [063] = -31.500 dB -> AKM(0x2e) = -31.610 dB error(-0.110 dB) + 0x2d, // [064] = -32.000 dB -> AKM(0x2d) = -31.945 dB error(+0.055 dB) + 0x2b, // [065] = -32.500 dB -> AKM(0x2b) = -32.659 dB error(-0.159 dB) + 0x2a, // [066] = -33.000 dB -> AKM(0x2a) = -33.038 dB error(-0.038 dB) + 0x29, // [067] = -33.500 dB -> AKM(0x29) = -33.435 dB error(+0.065 dB) + 0x28, // [068] = -34.000 dB -> AKM(0x28) = -33.852 dB error(+0.148 dB) + 0x27, // [069] = -34.500 dB -> AKM(0x27) = -34.289 dB error(+0.211 dB) + 0x25, // [070] = -35.000 dB -> AKM(0x25) = -35.235 dB error(-0.235 dB) + 0x24, // [071] = -35.500 dB -> AKM(0x24) = -35.750 dB error(-0.250 dB) + 0x24, // [072] = -36.000 dB -> AKM(0x24) = -35.750 dB error(+0.250 dB) + 0x23, // [073] = -36.500 dB -> AKM(0x23) = -36.297 dB error(+0.203 dB) + 0x22, // [074] = -37.000 dB -> AKM(0x22) = -36.881 dB error(+0.119 dB) + 0x21, // [075] = -37.500 dB -> AKM(0x21) = -37.508 dB error(-0.008 dB) + 0x20, // [076] = -38.000 dB -> AKM(0x20) = -38.183 dB error(-0.183 dB) + 0x1f, // [077] = -38.500 dB -> AKM(0x1f) = -38.726 dB error(-0.226 dB) + 0x1e, // [078] = -39.000 dB -> AKM(0x1e) = -39.108 dB error(-0.108 dB) + 0x1d, // [079] = -39.500 dB -> AKM(0x1d) = -39.507 dB error(-0.007 dB) + 0x1c, // [080] = -40.000 dB -> AKM(0x1c) = -39.926 dB error(+0.074 dB) + 0x1b, // [081] = -40.500 dB -> AKM(0x1b) = -40.366 dB error(+0.134 dB) + 0x1a, // [082] = -41.000 dB -> AKM(0x1a) = -40.829 dB error(+0.171 dB) + 0x19, // [083] = -41.500 dB -> AKM(0x19) = -41.318 dB error(+0.182 dB) + 0x18, // [084] = -42.000 dB -> AKM(0x18) = -41.837 dB error(+0.163 dB) + 0x17, // [085] = -42.500 dB -> AKM(0x17) = -42.389 dB error(+0.111 dB) + 0x16, // [086] = -43.000 dB -> AKM(0x16) = -42.978 dB error(+0.022 dB) + 0x15, // [087] = -43.500 dB -> AKM(0x15) = -43.610 dB error(-0.110 dB) + 0x14, // [088] = -44.000 dB -> AKM(0x14) = -44.291 dB error(-0.291 dB) + 0x14, // [089] = -44.500 dB -> AKM(0x14) = -44.291 dB error(+0.209 dB) + 0x13, // [090] = -45.000 dB -> AKM(0x13) = -45.031 dB error(-0.031 dB) + 0x12, // [091] = -45.500 dB -> AKM(0x12) = -45.840 dB error(-0.340 dB) + 0x12, // [092] = -46.000 dB -> AKM(0x12) = -45.840 dB error(+0.160 dB) + 0x11, // [093] = -46.500 dB -> AKM(0x11) = -46.731 dB error(-0.231 dB) + 0x11, // [094] = -47.000 dB -> AKM(0x11) = -46.731 dB error(+0.269 dB) + 0x10, // [095] = -47.500 dB -> AKM(0x10) = -47.725 dB error(-0.225 dB) + 0x10, // [096] = -48.000 dB -> AKM(0x10) = -47.725 dB error(+0.275 dB) + 0x0f, // [097] = -48.500 dB -> AKM(0x0f) = -48.553 dB error(-0.053 dB) + 0x0e, // [098] = -49.000 dB -> AKM(0x0e) = -49.152 dB error(-0.152 dB) + 0x0d, // [099] = -49.500 dB -> AKM(0x0d) = -49.796 dB error(-0.296 dB) + 0x0d, // [100] = -50.000 dB -> AKM(0x0d) = -49.796 dB error(+0.204 dB) + 0x0c, // [101] = -50.500 dB -> AKM(0x0c) = -50.491 dB error(+0.009 dB) + 0x0b, // [102] = -51.000 dB -> AKM(0x0b) = -51.247 dB error(-0.247 dB) + 0x0b, // [103] = -51.500 dB -> AKM(0x0b) = -51.247 dB error(+0.253 dB) + 0x0a, // [104] = -52.000 dB -> AKM(0x0a) = -52.075 dB error(-0.075 dB) + 0x0a, // [105] = -52.500 dB -> AKM(0x0a) = -52.075 dB error(+0.425 dB) + 0x09, // [106] = -53.000 dB -> AKM(0x09) = -52.990 dB error(+0.010 dB) + 0x09, // [107] = -53.500 dB -> AKM(0x09) = -52.990 dB error(+0.510 dB) + 0x08, // [108] = -54.000 dB -> AKM(0x08) = -54.013 dB error(-0.013 dB) + 0x08, // [109] = -54.500 dB -> AKM(0x08) = -54.013 dB error(+0.487 dB) + 0x07, // [110] = -55.000 dB -> AKM(0x07) = -55.173 dB error(-0.173 dB) + 0x07, // [111] = -55.500 dB -> AKM(0x07) = -55.173 dB error(+0.327 dB) + 0x06, // [112] = -56.000 dB -> AKM(0x06) = -56.512 dB error(-0.512 dB) + 0x06, // [113] = -56.500 dB -> AKM(0x06) = -56.512 dB error(-0.012 dB) + 0x06, // [114] = -57.000 dB -> AKM(0x06) = -56.512 dB error(+0.488 dB) + 0x05, // [115] = -57.500 dB -> AKM(0x05) = -58.095 dB error(-0.595 dB) + 0x05, // [116] = -58.000 dB -> AKM(0x05) = -58.095 dB error(-0.095 dB) + 0x05, // [117] = -58.500 dB -> AKM(0x05) = -58.095 dB error(+0.405 dB) + 0x05, // [118] = -59.000 dB -> AKM(0x05) = -58.095 dB error(+0.905 dB) + 0x04, // [119] = -59.500 dB -> AKM(0x04) = -60.034 dB error(-0.534 dB) + 0x04, // [120] = -60.000 dB -> AKM(0x04) = -60.034 dB error(-0.034 dB) + 0x04, // [121] = -60.500 dB -> AKM(0x04) = -60.034 dB error(+0.466 dB) + 0x04, // [122] = -61.000 dB -> AKM(0x04) = -60.034 dB error(+0.966 dB) + 0x03, // [123] = -61.500 dB -> AKM(0x03) = -62.532 dB error(-1.032 dB) + 0x03, // [124] = -62.000 dB -> AKM(0x03) = -62.532 dB error(-0.532 dB) + 0x03, // [125] = -62.500 dB -> AKM(0x03) = -62.532 dB error(-0.032 dB) + 0x03, // [126] = -63.000 dB -> AKM(0x03) = -62.532 dB error(+0.468 dB) + 0x03, // [127] = -63.500 dB -> AKM(0x03) = -62.532 dB error(+0.968 dB) + 0x03, // [128] = -64.000 dB -> AKM(0x03) = -62.532 dB error(+1.468 dB) + 0x02, // [129] = -64.500 dB -> AKM(0x02) = -66.054 dB error(-1.554 dB) + 0x02, // [130] = -65.000 dB -> AKM(0x02) = -66.054 dB error(-1.054 dB) + 0x02, // [131] = -65.500 dB -> AKM(0x02) = -66.054 dB error(-0.554 dB) + 0x02, // [132] = -66.000 dB -> AKM(0x02) = -66.054 dB error(-0.054 dB) + 0x02, // [133] = -66.500 dB -> AKM(0x02) = -66.054 dB error(+0.446 dB) + 0x02, // [134] = -67.000 dB -> AKM(0x02) = -66.054 dB error(+0.946 dB) + 0x02, // [135] = -67.500 dB -> AKM(0x02) = -66.054 dB error(+1.446 dB) + 0x02, // [136] = -68.000 dB -> AKM(0x02) = -66.054 dB error(+1.946 dB) + 0x02, // [137] = -68.500 dB -> AKM(0x02) = -66.054 dB error(+2.446 dB) + 0x02, // [138] = -69.000 dB -> AKM(0x02) = -66.054 dB error(+2.946 dB) + 0x01, // [139] = -69.500 dB -> AKM(0x01) = -72.075 dB error(-2.575 dB) + 0x01, // [140] = -70.000 dB -> AKM(0x01) = -72.075 dB error(-2.075 dB) + 0x01, // [141] = -70.500 dB -> AKM(0x01) = -72.075 dB error(-1.575 dB) + 0x01, // [142] = -71.000 dB -> AKM(0x01) = -72.075 dB error(-1.075 dB) + 0x01, // [143] = -71.500 dB -> AKM(0x01) = -72.075 dB error(-0.575 dB) + 0x01, // [144] = -72.000 dB -> AKM(0x01) = -72.075 dB error(-0.075 dB) + 0x01, // [145] = -72.500 dB -> AKM(0x01) = -72.075 dB error(+0.425 dB) + 0x01, // [146] = -73.000 dB -> AKM(0x01) = -72.075 dB error(+0.925 dB) + 0x00}; // [147] = -73.500 dB -> AKM(0x00) = mute error(+infini) + +/* + * pseudo-codec write entry + */ +static void vx2_write_akm(struct vx_core *chip, int reg, unsigned int data) +{ + unsigned int val; + + if (reg == XX_CODEC_DAC_CONTROL_REGISTER) { + vx2_write_codec_reg(chip, data ? AKM_CODEC_MUTE_CMD : AKM_CODEC_UNMUTE_CMD); + return; + } + + /* `data' is a value between 0x0 and VX2_AKM_LEVEL_MAX = 0x093, in the case of the AKM codecs, we need + a look up table, as there is no linear matching between the driver codec values + and the real dBu value + */ + if (snd_BUG_ON(data >= sizeof(vx2_akm_gains_lut))) + return; + + switch (reg) { + case XX_CODEC_LEVEL_LEFT_REGISTER: + val = AKM_CODEC_LEFT_LEVEL_CMD; + break; + case XX_CODEC_LEVEL_RIGHT_REGISTER: + val = AKM_CODEC_RIGHT_LEVEL_CMD; + break; + default: + snd_BUG(); + return; + } + val |= vx2_akm_gains_lut[data]; + + vx2_write_codec_reg(chip, val); +} + + +/* + * write codec bit for old VX222 board + */ +static void vx2_old_write_codec_bit(struct vx_core *chip, int codec, unsigned int data) +{ + int i; + + /* activate access to codec registers */ + vx_inl(chip, HIFREQ); + + for (i = 0; i < 24; i++, data <<= 1) + vx_outl(chip, DATA, ((data & 0x800000) ? VX_DATA_CODEC_MASK : 0)); + + /* Terminate access to codec registers */ + vx_inl(chip, RUER); +} + + +/* + * reset codec bit + */ +static void vx2_reset_codec(struct vx_core *_chip) +{ + struct snd_vx222 *chip = to_vx222(_chip); + + /* Set the reset CODEC bit to 0. */ + vx_outl(chip, CDSP, chip->regCDSP &~ VX_CDSP_CODEC_RESET_MASK); + vx_inl(chip, CDSP); + msleep(10); + /* Set the reset CODEC bit to 1. */ + chip->regCDSP |= VX_CDSP_CODEC_RESET_MASK; + vx_outl(chip, CDSP, chip->regCDSP); + vx_inl(chip, CDSP); + if (_chip->type == VX_TYPE_BOARD) { + msleep(1); + return; + } + + msleep(5); /* additionnel wait time for AKM's */ + + vx2_write_codec_reg(_chip, AKM_CODEC_POWER_CONTROL_CMD); /* DAC power up, ADC power up, Vref power down */ + + vx2_write_codec_reg(_chip, AKM_CODEC_CLOCK_FORMAT_CMD); /* default */ + vx2_write_codec_reg(_chip, AKM_CODEC_MUTE_CMD); /* Mute = ON ,Deemphasis = OFF */ + vx2_write_codec_reg(_chip, AKM_CODEC_RESET_OFF_CMD); /* DAC and ADC normal operation */ + + if (_chip->type == VX_TYPE_MIC) { + /* set up the micro input selector */ + chip->regSELMIC = MICRO_SELECT_INPUT_NORM | + MICRO_SELECT_PREAMPLI_G_0 | + MICRO_SELECT_NOISE_T_52DB; + + /* reset phantom power supply */ + chip->regSELMIC &= ~MICRO_SELECT_PHANTOM_ALIM; + + vx_outl(_chip, SELMIC, chip->regSELMIC); + } +} + + +/* + * change the audio source + */ +static void vx2_change_audio_source(struct vx_core *_chip, int src) +{ + struct snd_vx222 *chip = to_vx222(_chip); + + switch (src) { + case VX_AUDIO_SRC_DIGITAL: + chip->regCFG |= VX_CFG_DATAIN_SEL_MASK; + break; + default: + chip->regCFG &= ~VX_CFG_DATAIN_SEL_MASK; + break; + } + vx_outl(chip, CFG, chip->regCFG); +} + + +/* + * set the clock source + */ +static void vx2_set_clock_source(struct vx_core *_chip, int source) +{ + struct snd_vx222 *chip = to_vx222(_chip); + + if (source == INTERNAL_QUARTZ) + chip->regCFG &= ~VX_CFG_CLOCKIN_SEL_MASK; + else + chip->regCFG |= VX_CFG_CLOCKIN_SEL_MASK; + vx_outl(chip, CFG, chip->regCFG); +} + +/* + * reset the board + */ +static void vx2_reset_board(struct vx_core *_chip, int cold_reset) +{ + struct snd_vx222 *chip = to_vx222(_chip); + + /* initialize the register values */ + chip->regCDSP = VX_CDSP_CODEC_RESET_MASK | VX_CDSP_DSP_RESET_MASK ; + chip->regCFG = 0; +} + + + +/* + * input level controls for VX222 Mic + */ + +/* Micro level is specified to be adjustable from -96dB to 63 dB (board coded 0x00 ... 318), + * 318 = 210 + 36 + 36 + 36 (210 = +9dB variable) (3 * 36 = 3 steps of 18dB pre ampli) + * as we will mute if less than -110dB, so let's simply use line input coded levels and add constant offset ! + */ +#define V2_MICRO_LEVEL_RANGE (318 - 255) + +static void vx2_set_input_level(struct snd_vx222 *chip) +{ + int i, miclevel, preamp; + unsigned int data; + + miclevel = chip->mic_level; + miclevel += V2_MICRO_LEVEL_RANGE; /* add 318 - 0xff */ + preamp = 0; + while (miclevel > 210) { /* limitation to +9dB of 3310 real gain */ + preamp++; /* raise pre ampli + 18dB */ + miclevel -= (18 * 2); /* lower level 18 dB (*2 because of 0.5 dB steps !) */ + } + if (snd_BUG_ON(preamp >= 4)) + return; + + /* set pre-amp level */ + chip->regSELMIC &= ~MICRO_SELECT_PREAMPLI_MASK; + chip->regSELMIC |= (preamp << MICRO_SELECT_PREAMPLI_OFFSET) & MICRO_SELECT_PREAMPLI_MASK; + vx_outl(chip, SELMIC, chip->regSELMIC); + + data = (unsigned int)miclevel << 16 | + (unsigned int)chip->input_level[1] << 8 | + (unsigned int)chip->input_level[0]; + vx_inl(chip, DATA); /* Activate input level programming */ + + /* We have to send 32 bits (4 x 8 bits) */ + for (i = 0; i < 32; i++, data <<= 1) + vx_outl(chip, DATA, ((data & 0x80000000) ? VX_DATA_CODEC_MASK : 0)); + + vx_inl(chip, RUER); /* Terminate input level programming */ +} + + +#define MIC_LEVEL_MAX 0xff + +static const DECLARE_TLV_DB_SCALE(db_scale_mic, -6450, 50, 0); + +/* + * controls API for input levels + */ + +/* input levels */ +static int vx_input_level_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 = MIC_LEVEL_MAX; + return 0; +} + +static int vx_input_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *_chip = snd_kcontrol_chip(kcontrol); + struct snd_vx222 *chip = to_vx222(_chip); + + guard(mutex)(&_chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->input_level[0]; + ucontrol->value.integer.value[1] = chip->input_level[1]; + return 0; +} + +static int vx_input_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *_chip = snd_kcontrol_chip(kcontrol); + struct snd_vx222 *chip = to_vx222(_chip); + if (ucontrol->value.integer.value[0] < 0 || + ucontrol->value.integer.value[0] > MIC_LEVEL_MAX) + return -EINVAL; + if (ucontrol->value.integer.value[1] < 0 || + ucontrol->value.integer.value[1] > MIC_LEVEL_MAX) + return -EINVAL; + guard(mutex)(&_chip->mixer_mutex); + if (chip->input_level[0] != ucontrol->value.integer.value[0] || + chip->input_level[1] != ucontrol->value.integer.value[1]) { + chip->input_level[0] = ucontrol->value.integer.value[0]; + chip->input_level[1] = ucontrol->value.integer.value[1]; + vx2_set_input_level(chip); + return 1; + } + return 0; +} + +/* mic level */ +static int vx_mic_level_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 = MIC_LEVEL_MAX; + return 0; +} + +static int vx_mic_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *_chip = snd_kcontrol_chip(kcontrol); + struct snd_vx222 *chip = to_vx222(_chip); + ucontrol->value.integer.value[0] = chip->mic_level; + return 0; +} + +static int vx_mic_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *_chip = snd_kcontrol_chip(kcontrol); + struct snd_vx222 *chip = to_vx222(_chip); + if (ucontrol->value.integer.value[0] < 0 || + ucontrol->value.integer.value[0] > MIC_LEVEL_MAX) + return -EINVAL; + guard(mutex)(&_chip->mixer_mutex); + if (chip->mic_level != ucontrol->value.integer.value[0]) { + chip->mic_level = ucontrol->value.integer.value[0]; + vx2_set_input_level(chip); + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new vx_control_input_level = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Capture Volume", + .info = vx_input_level_info, + .get = vx_input_level_get, + .put = vx_input_level_put, + .tlv = { .p = db_scale_mic }, +}; + +static const struct snd_kcontrol_new vx_control_mic_level = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Mic Capture Volume", + .info = vx_mic_level_info, + .get = vx_mic_level_get, + .put = vx_mic_level_put, + .tlv = { .p = db_scale_mic }, +}; + +/* + * FIXME: compressor/limiter implementation is missing yet... + */ + +static int vx2_add_mic_controls(struct vx_core *_chip) +{ + struct snd_vx222 *chip = to_vx222(_chip); + int err; + + if (_chip->type != VX_TYPE_MIC) + return 0; + + /* mute input levels */ + chip->input_level[0] = chip->input_level[1] = 0; + chip->mic_level = 0; + vx2_set_input_level(chip); + + /* controls */ + err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_input_level, chip)); + if (err < 0) + return err; + err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_mic_level, chip)); + if (err < 0) + return err; + + return 0; +} + + +/* + * callbacks + */ +const struct snd_vx_ops vx222_ops = { + .in8 = vx2_inb, + .in32 = vx2_inl, + .out8 = vx2_outb, + .out32 = vx2_outl, + .test_and_ack = vx2_test_and_ack, + .validate_irq = vx2_validate_irq, + .akm_write = vx2_write_akm, + .reset_codec = vx2_reset_codec, + .change_audio_source = vx2_change_audio_source, + .set_clock_source = vx2_set_clock_source, + .load_dsp = vx2_load_dsp, + .reset_dsp = vx2_reset_dsp, + .reset_board = vx2_reset_board, + .dma_write = vx2_dma_write, + .dma_read = vx2_dma_read, + .add_controls = vx2_add_mic_controls, +}; + +/* for old VX222 board */ +const struct snd_vx_ops vx222_old_ops = { + .in8 = vx2_inb, + .in32 = vx2_inl, + .out8 = vx2_outb, + .out32 = vx2_outl, + .test_and_ack = vx2_test_and_ack, + .validate_irq = vx2_validate_irq, + .write_codec = vx2_old_write_codec_bit, + .reset_codec = vx2_reset_codec, + .change_audio_source = vx2_change_audio_source, + .set_clock_source = vx2_set_clock_source, + .load_dsp = vx2_load_dsp, + .reset_dsp = vx2_reset_dsp, + .reset_board = vx2_reset_board, + .dma_write = vx2_dma_write, + .dma_read = vx2_dma_read, +}; + diff --git a/sound/pci/ymfpci/Makefile b/sound/pci/ymfpci/Makefile new file mode 100644 index 000000000..2d7856403 --- /dev/null +++ b/sound/pci/ymfpci/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-ymfpci-y := ymfpci.o ymfpci_main.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_YMFPCI) += snd-ymfpci.o diff --git a/sound/pci/ymfpci/ymfpci.c b/sound/pci/ymfpci/ymfpci.c new file mode 100644 index 000000000..d3fb047c9 --- /dev/null +++ b/sound/pci/ymfpci/ymfpci.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The driver for the Yamaha's DS1/DS1E cards + * Copyright (c) by Jaroslav Kysela + */ + +#include +#include +#include +#include +#include +#include "ymfpci.h" +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Yamaha DS-1 PCI"); +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_PNP; /* Enable this card */ +static long fm_port[SNDRV_CARDS]; +static long mpu_port[SNDRV_CARDS]; +#ifdef SUPPORT_JOYSTICK +static long joystick_port[SNDRV_CARDS]; +#endif +static bool rear_switch[SNDRV_CARDS]; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the Yamaha DS-1 PCI soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the Yamaha DS-1 PCI soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Yamaha DS-1 soundcard."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 Port."); +module_param_hw_array(fm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM OPL-3 Port."); +#ifdef SUPPORT_JOYSTICK +module_param_hw_array(joystick_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(joystick_port, "Joystick port address"); +#endif +module_param_array(rear_switch, bool, NULL, 0444); +MODULE_PARM_DESC(rear_switch, "Enable shared rear/line-in switch"); + +static const struct pci_device_id snd_ymfpci_ids[] = { + { PCI_VDEVICE(YAMAHA, 0x0004) }, /* YMF724 */ + { PCI_VDEVICE(YAMAHA, 0x000d) }, /* YMF724F */ + { PCI_VDEVICE(YAMAHA, 0x000a) }, /* YMF740 */ + { PCI_VDEVICE(YAMAHA, 0x000c) }, /* YMF740C */ + { PCI_VDEVICE(YAMAHA, 0x0010) }, /* YMF744 */ + { PCI_VDEVICE(YAMAHA, 0x0012) }, /* YMF754 */ + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_ymfpci_ids); + +#ifdef SUPPORT_JOYSTICK +static int snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev, + int legacy_ctrl, int legacy_ctrl2) +{ + struct gameport *gp; + struct resource *r = NULL; + int io_port = joystick_port[dev]; + + if (!io_port) + return -ENODEV; + + if (chip->pci->device >= 0x0010) { /* YMF 744/754 */ + + if (io_port == 1) { + /* auto-detect */ + io_port = pci_resource_start(chip->pci, 2); + if (!io_port) + return -ENODEV; + } + } else { + if (io_port == 1) { + /* auto-detect */ + for (io_port = 0x201; io_port <= 0x205; io_port++) { + if (io_port == 0x203) + continue; + r = request_region(io_port, 1, "YMFPCI gameport"); + if (r) + break; + } + if (!r) { + dev_err(chip->card->dev, + "no gameport ports available\n"); + return -EBUSY; + } + } + switch (io_port) { + case 0x201: legacy_ctrl2 |= 0 << 6; break; + case 0x202: legacy_ctrl2 |= 1 << 6; break; + case 0x204: legacy_ctrl2 |= 2 << 6; break; + case 0x205: legacy_ctrl2 |= 3 << 6; break; + default: + if (io_port > 0) + dev_err(chip->card->dev, + "The %s does not support arbitrary IO ports for the game port (requested 0x%x)\n", + chip->card->shortname, (unsigned int)io_port); + return -EINVAL; + } + } + + if (!r) { + r = devm_request_region(&chip->pci->dev, io_port, 1, + "YMFPCI gameport"); + if (!r) { + dev_err(chip->card->dev, + "joystick port %#x is in use.\n", io_port); + return -EBUSY; + } + } + + chip->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(chip->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + + gameport_set_name(gp, "Yamaha YMF Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci)); + gameport_set_dev_parent(gp, &chip->pci->dev); + gp->io = io_port; + + if (chip->pci->device >= 0x0010) /* YMF 744/754 */ + pci_write_config_word(chip->pci, PCIR_DSXG_JOYBASE, io_port); + + pci_write_config_word(chip->pci, PCIR_DSXG_LEGACY, legacy_ctrl | YMFPCI_LEGACY_JPEN); + pci_write_config_word(chip->pci, PCIR_DSXG_ELEGACY, legacy_ctrl2); + + gameport_register_port(chip->gameport); + + return 0; +} + +void snd_ymfpci_free_gameport(struct snd_ymfpci *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } +} +#else +static inline int snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev, int l, int l2) { return -ENOSYS; } +void snd_ymfpci_free_gameport(struct snd_ymfpci *chip) { } +#endif /* SUPPORT_JOYSTICK */ + +static int __snd_card_ymfpci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct resource *fm_res = NULL; + struct resource *mpu_res = NULL; + struct snd_ymfpci *chip; + struct snd_opl3 *opl3; + const char *str, *model; + int err; + u16 legacy_ctrl, legacy_ctrl2, old_legacy_ctrl; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) + return err; + chip = card->private_data; + + switch (pci_id->device) { + case 0x0004: str = "YMF724"; model = "DS-1"; break; + case 0x000d: str = "YMF724F"; model = "DS-1"; break; + case 0x000a: str = "YMF740"; model = "DS-1L"; break; + case 0x000c: str = "YMF740C"; model = "DS-1L"; break; + case 0x0010: str = "YMF744"; model = "DS-1S"; break; + case 0x0012: str = "YMF754"; model = "DS-1E"; break; + default: model = str = "???"; break; + } + + strscpy(card->driver, str); + sprintf(card->shortname, "Yamaha %s (%s)", model, str); + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, + chip->reg_area_phys, + chip->irq); + + legacy_ctrl = 0; + legacy_ctrl2 = 0x0800; /* SBEN = 0, SMOD = 01, LAD = 0 */ + + if (pci_id->device >= 0x0010) { /* YMF 744/754 */ + if (fm_port[dev] == 1) { + /* auto-detect */ + fm_port[dev] = pci_resource_start(pci, 1); + } + if (fm_port[dev] > 0) + fm_res = devm_request_region(&pci->dev, fm_port[dev], + 4, "YMFPCI OPL3"); + if (fm_res) { + legacy_ctrl |= YMFPCI_LEGACY_FMEN; + pci_write_config_word(pci, PCIR_DSXG_FMBASE, fm_port[dev]); + } + if (mpu_port[dev] == 1) { + /* auto-detect */ + mpu_port[dev] = pci_resource_start(pci, 1) + 0x20; + } + if (mpu_port[dev] > 0) + mpu_res = devm_request_region(&pci->dev, mpu_port[dev], + 2, "YMFPCI MPU401"); + if (mpu_res) { + legacy_ctrl |= YMFPCI_LEGACY_MEN; + pci_write_config_word(pci, PCIR_DSXG_MPU401BASE, mpu_port[dev]); + } + } else { + switch (fm_port[dev]) { + case 0x388: legacy_ctrl2 |= 0; break; + case 0x398: legacy_ctrl2 |= 1; break; + case 0x3a0: legacy_ctrl2 |= 2; break; + case 0x3a8: legacy_ctrl2 |= 3; break; + default: + if (fm_port[dev] > 0) + dev_err(card->dev, + "The %s does not support arbitrary IO ports for FM (requested 0x%x)\n", + card->shortname, (unsigned int)fm_port[dev]); + fm_port[dev] = 0; + break; + } + if (fm_port[dev] > 0) + fm_res = devm_request_region(&pci->dev, fm_port[dev], + 4, "YMFPCI OPL3"); + if (fm_res) { + legacy_ctrl |= YMFPCI_LEGACY_FMEN; + } else { + legacy_ctrl2 &= ~YMFPCI_LEGACY2_FMIO; + fm_port[dev] = 0; + } + switch (mpu_port[dev]) { + case 0x330: legacy_ctrl2 |= 0 << 4; break; + case 0x300: legacy_ctrl2 |= 1 << 4; break; + case 0x332: legacy_ctrl2 |= 2 << 4; break; + case 0x334: legacy_ctrl2 |= 3 << 4; break; + default: + if (mpu_port[dev] > 0) + dev_err(card->dev, + "The %s does not support arbitrary IO ports for MPU-401 (requested 0x%x)\n", + card->shortname, (unsigned int)mpu_port[dev]); + mpu_port[dev] = 0; + break; + } + if (mpu_port[dev] > 0) + mpu_res = devm_request_region(&pci->dev, mpu_port[dev], + 2, "YMFPCI MPU401"); + if (mpu_res) { + legacy_ctrl |= YMFPCI_LEGACY_MEN; + } else { + legacy_ctrl2 &= ~YMFPCI_LEGACY2_MPUIO; + mpu_port[dev] = 0; + } + } + if (mpu_res) { + legacy_ctrl |= YMFPCI_LEGACY_MIEN; + legacy_ctrl2 |= YMFPCI_LEGACY2_IMOD; + } + pci_read_config_word(pci, PCIR_DSXG_LEGACY, &old_legacy_ctrl); + pci_write_config_word(pci, PCIR_DSXG_LEGACY, legacy_ctrl); + pci_write_config_word(pci, PCIR_DSXG_ELEGACY, legacy_ctrl2); + err = snd_ymfpci_create(card, pci, old_legacy_ctrl); + if (err < 0) + return err; + + err = snd_ymfpci_pcm(chip, 0); + if (err < 0) + return err; + + err = snd_ymfpci_pcm_spdif(chip, 1); + if (err < 0) + return err; + + err = snd_ymfpci_mixer(chip, rear_switch[dev]); + if (err < 0) + return err; + + if (chip->ac97->ext_id & AC97_EI_SDAC) { + err = snd_ymfpci_pcm_4ch(chip, 2); + if (err < 0) + return err; + + err = snd_ymfpci_pcm2(chip, 3); + if (err < 0) + return err; + } + err = snd_ymfpci_timer(chip, 0); + if (err < 0) + return err; + + if (mpu_res) { + err = snd_mpu401_uart_new(card, 0, MPU401_HW_YMFPCI, + mpu_port[dev], + MPU401_INFO_INTEGRATED | + MPU401_INFO_IRQ_HOOK, + -1, &chip->rawmidi); + if (err < 0) { + dev_warn(card->dev, + "cannot initialize MPU401 at 0x%lx, skipping...\n", + mpu_port[dev]); + legacy_ctrl &= ~YMFPCI_LEGACY_MIEN; /* disable MPU401 irq */ + pci_write_config_word(pci, PCIR_DSXG_LEGACY, legacy_ctrl); + } + } + if (fm_res) { + err = snd_opl3_create(card, + fm_port[dev], + fm_port[dev] + 2, + OPL3_HW_OPL3, 1, &opl3); + if (err < 0) { + dev_warn(card->dev, + "cannot initialize FM OPL3 at 0x%lx, skipping...\n", + fm_port[dev]); + legacy_ctrl &= ~YMFPCI_LEGACY_FMEN; + pci_write_config_word(pci, PCIR_DSXG_LEGACY, legacy_ctrl); + } else { + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) { + dev_err(card->dev, "cannot create opl3 hwdep\n"); + return err; + } + } + } + + snd_ymfpci_create_gameport(chip, dev, legacy_ctrl, legacy_ctrl2); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int snd_card_ymfpci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __snd_card_ymfpci_probe(pci, pci_id)); +} + +static struct pci_driver ymfpci_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_ymfpci_ids, + .probe = snd_card_ymfpci_probe, + .driver = { + .pm = pm_sleep_ptr(&snd_ymfpci_pm), + }, +}; + +module_pci_driver(ymfpci_driver); diff --git a/sound/pci/ymfpci/ymfpci.h b/sound/pci/ymfpci/ymfpci.h new file mode 100644 index 000000000..a408785cf --- /dev/null +++ b/sound/pci/ymfpci/ymfpci.h @@ -0,0 +1,410 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __SOUND_YMFPCI_H +#define __SOUND_YMFPCI_H + +/* + * Copyright (c) by Jaroslav Kysela + * Definitions for Yahama YMF724/740/744/754 chips + */ + +#include +#include +#include +#include +#include + +/* + * Direct registers + */ + +#define YMFREG(chip, reg) (chip->port + YDSXGR_##reg) + +#define YDSXGR_INTFLAG 0x0004 +#define YDSXGR_ACTIVITY 0x0006 +#define YDSXGR_GLOBALCTRL 0x0008 +#define YDSXGR_ZVCTRL 0x000A +#define YDSXGR_TIMERCTRL 0x0010 +#define YDSXGR_TIMERCOUNT 0x0012 +#define YDSXGR_SPDIFOUTCTRL 0x0018 +#define YDSXGR_SPDIFOUTSTATUS 0x001C +#define YDSXGR_EEPROMCTRL 0x0020 +#define YDSXGR_SPDIFINCTRL 0x0034 +#define YDSXGR_SPDIFINSTATUS 0x0038 +#define YDSXGR_DSPPROGRAMDL 0x0048 +#define YDSXGR_DLCNTRL 0x004C +#define YDSXGR_GPIOININTFLAG 0x0050 +#define YDSXGR_GPIOININTENABLE 0x0052 +#define YDSXGR_GPIOINSTATUS 0x0054 +#define YDSXGR_GPIOOUTCTRL 0x0056 +#define YDSXGR_GPIOFUNCENABLE 0x0058 +#define YDSXGR_GPIOTYPECONFIG 0x005A +#define YDSXGR_AC97CMDDATA 0x0060 +#define YDSXGR_AC97CMDADR 0x0062 +#define YDSXGR_PRISTATUSDATA 0x0064 +#define YDSXGR_PRISTATUSADR 0x0066 +#define YDSXGR_SECSTATUSDATA 0x0068 +#define YDSXGR_SECSTATUSADR 0x006A +#define YDSXGR_SECCONFIG 0x0070 +#define YDSXGR_LEGACYOUTVOL 0x0080 +#define YDSXGR_LEGACYOUTVOLL 0x0080 +#define YDSXGR_LEGACYOUTVOLR 0x0082 +#define YDSXGR_NATIVEDACOUTVOL 0x0084 +#define YDSXGR_NATIVEDACOUTVOLL 0x0084 +#define YDSXGR_NATIVEDACOUTVOLR 0x0086 +#define YDSXGR_ZVOUTVOL 0x0088 +#define YDSXGR_ZVOUTVOLL 0x0088 +#define YDSXGR_ZVOUTVOLR 0x008A +#define YDSXGR_SECADCOUTVOL 0x008C +#define YDSXGR_SECADCOUTVOLL 0x008C +#define YDSXGR_SECADCOUTVOLR 0x008E +#define YDSXGR_PRIADCOUTVOL 0x0090 +#define YDSXGR_PRIADCOUTVOLL 0x0090 +#define YDSXGR_PRIADCOUTVOLR 0x0092 +#define YDSXGR_LEGACYLOOPVOL 0x0094 +#define YDSXGR_LEGACYLOOPVOLL 0x0094 +#define YDSXGR_LEGACYLOOPVOLR 0x0096 +#define YDSXGR_NATIVEDACLOOPVOL 0x0098 +#define YDSXGR_NATIVEDACLOOPVOLL 0x0098 +#define YDSXGR_NATIVEDACLOOPVOLR 0x009A +#define YDSXGR_ZVLOOPVOL 0x009C +#define YDSXGR_ZVLOOPVOLL 0x009E +#define YDSXGR_ZVLOOPVOLR 0x009E +#define YDSXGR_SECADCLOOPVOL 0x00A0 +#define YDSXGR_SECADCLOOPVOLL 0x00A0 +#define YDSXGR_SECADCLOOPVOLR 0x00A2 +#define YDSXGR_PRIADCLOOPVOL 0x00A4 +#define YDSXGR_PRIADCLOOPVOLL 0x00A4 +#define YDSXGR_PRIADCLOOPVOLR 0x00A6 +#define YDSXGR_NATIVEADCINVOL 0x00A8 +#define YDSXGR_NATIVEADCINVOLL 0x00A8 +#define YDSXGR_NATIVEADCINVOLR 0x00AA +#define YDSXGR_NATIVEDACINVOL 0x00AC +#define YDSXGR_NATIVEDACINVOLL 0x00AC +#define YDSXGR_NATIVEDACINVOLR 0x00AE +#define YDSXGR_BUF441OUTVOL 0x00B0 +#define YDSXGR_BUF441OUTVOLL 0x00B0 +#define YDSXGR_BUF441OUTVOLR 0x00B2 +#define YDSXGR_BUF441LOOPVOL 0x00B4 +#define YDSXGR_BUF441LOOPVOLL 0x00B4 +#define YDSXGR_BUF441LOOPVOLR 0x00B6 +#define YDSXGR_SPDIFOUTVOL 0x00B8 +#define YDSXGR_SPDIFOUTVOLL 0x00B8 +#define YDSXGR_SPDIFOUTVOLR 0x00BA +#define YDSXGR_SPDIFLOOPVOL 0x00BC +#define YDSXGR_SPDIFLOOPVOLL 0x00BC +#define YDSXGR_SPDIFLOOPVOLR 0x00BE +#define YDSXGR_ADCSLOTSR 0x00C0 +#define YDSXGR_RECSLOTSR 0x00C4 +#define YDSXGR_ADCFORMAT 0x00C8 +#define YDSXGR_RECFORMAT 0x00CC +#define YDSXGR_P44SLOTSR 0x00D0 +#define YDSXGR_STATUS 0x0100 +#define YDSXGR_CTRLSELECT 0x0104 +#define YDSXGR_MODE 0x0108 +#define YDSXGR_SAMPLECOUNT 0x010C +#define YDSXGR_NUMOFSAMPLES 0x0110 +#define YDSXGR_CONFIG 0x0114 +#define YDSXGR_PLAYCTRLSIZE 0x0140 +#define YDSXGR_RECCTRLSIZE 0x0144 +#define YDSXGR_EFFCTRLSIZE 0x0148 +#define YDSXGR_WORKSIZE 0x014C +#define YDSXGR_MAPOFREC 0x0150 +#define YDSXGR_MAPOFEFFECT 0x0154 +#define YDSXGR_PLAYCTRLBASE 0x0158 +#define YDSXGR_RECCTRLBASE 0x015C +#define YDSXGR_EFFCTRLBASE 0x0160 +#define YDSXGR_WORKBASE 0x0164 +#define YDSXGR_DSPINSTRAM 0x1000 +#define YDSXGR_CTRLINSTRAM 0x4000 + +#define YDSXG_AC97READCMD 0x8000 +#define YDSXG_AC97WRITECMD 0x0000 + +#define PCIR_DSXG_LEGACY 0x40 +#define PCIR_DSXG_ELEGACY 0x42 +#define PCIR_DSXG_CTRL 0x48 +#define PCIR_DSXG_PWRCTRL1 0x4a +#define PCIR_DSXG_PWRCTRL2 0x4e +#define PCIR_DSXG_FMBASE 0x60 +#define PCIR_DSXG_SBBASE 0x62 +#define PCIR_DSXG_MPU401BASE 0x64 +#define PCIR_DSXG_JOYBASE 0x66 + +#define YDSXG_DSPLENGTH 0x0080 +#define YDSXG_CTRLLENGTH 0x3000 + +#define YDSXG_DEFAULT_WORK_SIZE 0x0400 + +#define YDSXG_PLAYBACK_VOICES 64 +#define YDSXG_CAPTURE_VOICES 2 +#define YDSXG_EFFECT_VOICES 5 + +#define YMFPCI_LEGACY_SBEN (1 << 0) /* soundblaster enable */ +#define YMFPCI_LEGACY_FMEN (1 << 1) /* OPL3 enable */ +#define YMFPCI_LEGACY_JPEN (1 << 2) /* joystick enable */ +#define YMFPCI_LEGACY_MEN (1 << 3) /* MPU401 enable */ +#define YMFPCI_LEGACY_MIEN (1 << 4) /* MPU RX irq enable */ +#define YMFPCI_LEGACY_IOBITS (1 << 5) /* i/o bits range, 0 = 16bit, 1 =10bit */ +#define YMFPCI_LEGACY_SDMA (3 << 6) /* SB DMA select */ +#define YMFPCI_LEGACY_SBIRQ (7 << 8) /* SB IRQ select */ +#define YMFPCI_LEGACY_MPUIRQ (7 << 11) /* MPU IRQ select */ +#define YMFPCI_LEGACY_SIEN (1 << 14) /* serialized IRQ */ +#define YMFPCI_LEGACY_LAD (1 << 15) /* legacy audio disable */ + +#define YMFPCI_LEGACY2_FMIO (3 << 0) /* OPL3 i/o address (724/740) */ +#define YMFPCI_LEGACY2_SBIO (3 << 2) /* SB i/o address (724/740) */ +#define YMFPCI_LEGACY2_MPUIO (3 << 4) /* MPU401 i/o address (724/740) */ +#define YMFPCI_LEGACY2_JSIO (3 << 6) /* joystick i/o address (724/740) */ +#define YMFPCI_LEGACY2_MAIM (1 << 8) /* MPU401 ack intr mask */ +#define YMFPCI_LEGACY2_SMOD (3 << 11) /* SB DMA mode */ +#define YMFPCI_LEGACY2_SBVER (3 << 13) /* SB version select */ +#define YMFPCI_LEGACY2_IMOD (1 << 15) /* legacy IRQ mode */ +/* SIEN:IMOD 0:0 = legacy irq, 0:1 = INTA, 1:0 = serialized IRQ */ + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK +#endif + +/* + * + */ + +struct snd_ymfpci_playback_bank { + __le32 format; + __le32 loop_default; + __le32 base; /* 32-bit address */ + __le32 loop_start; /* 32-bit offset */ + __le32 loop_end; /* 32-bit offset */ + __le32 loop_frac; /* 8-bit fraction - loop_start */ + __le32 delta_end; /* pitch delta end */ + __le32 lpfK_end; + __le32 eg_gain_end; + __le32 left_gain_end; + __le32 right_gain_end; + __le32 eff1_gain_end; + __le32 eff2_gain_end; + __le32 eff3_gain_end; + __le32 lpfQ; + __le32 status; + __le32 num_of_frames; + __le32 loop_count; + __le32 start; + __le32 start_frac; + __le32 delta; + __le32 lpfK; + __le32 eg_gain; + __le32 left_gain; + __le32 right_gain; + __le32 eff1_gain; + __le32 eff2_gain; + __le32 eff3_gain; + __le32 lpfD1; + __le32 lpfD2; + }; + +struct snd_ymfpci_capture_bank { + __le32 base; /* 32-bit address */ + __le32 loop_end; /* 32-bit offset */ + __le32 start; /* 32-bit offset */ + __le32 num_of_loops; /* counter */ +}; + +struct snd_ymfpci_effect_bank { + __le32 base; /* 32-bit address */ + __le32 loop_end; /* 32-bit offset */ + __le32 start; /* 32-bit offset */ + __le32 temp; +}; + +struct snd_ymfpci_pcm; +struct snd_ymfpci; + +enum snd_ymfpci_voice_type { + YMFPCI_PCM, + YMFPCI_SYNTH, + YMFPCI_MIDI +}; + +struct snd_ymfpci_voice { + struct snd_ymfpci *chip; + int number; + unsigned int use: 1, + pcm: 1, + synth: 1, + midi: 1; + struct snd_ymfpci_playback_bank *bank; + dma_addr_t bank_addr; + void (*interrupt)(struct snd_ymfpci *chip, struct snd_ymfpci_voice *voice); + struct snd_ymfpci_pcm *ypcm; +}; + +enum snd_ymfpci_pcm_type { + PLAYBACK_VOICE, + CAPTURE_REC, + CAPTURE_AC97, + EFFECT_DRY_LEFT, + EFFECT_DRY_RIGHT, + EFFECT_EFF1, + EFFECT_EFF2, + EFFECT_EFF3 +}; + +struct snd_ymfpci_pcm { + struct snd_ymfpci *chip; + enum snd_ymfpci_pcm_type type; + struct snd_pcm_substream *substream; + struct snd_ymfpci_voice *voices[2]; /* playback only */ + unsigned int running: 1, + use_441_slot: 1, + output_front: 1, + output_rear: 1, + swap_rear: 1; + unsigned int update_pcm_vol; + u32 period_size; /* cached from runtime->period_size */ + u32 buffer_size; /* cached from runtime->buffer_size */ + u32 period_pos; + u32 last_pos; + u32 capture_bank_number; + u32 shift; +}; + +static const int saved_regs_index[] = { + /* spdif */ + YDSXGR_SPDIFOUTCTRL, + YDSXGR_SPDIFOUTSTATUS, + YDSXGR_SPDIFINCTRL, + /* volumes */ + YDSXGR_PRIADCLOOPVOL, + YDSXGR_NATIVEDACINVOL, + YDSXGR_NATIVEDACOUTVOL, + YDSXGR_BUF441OUTVOL, + YDSXGR_NATIVEADCINVOL, + YDSXGR_SPDIFLOOPVOL, + YDSXGR_SPDIFOUTVOL, + YDSXGR_ZVOUTVOL, + YDSXGR_LEGACYOUTVOL, + /* address bases */ + YDSXGR_PLAYCTRLBASE, + YDSXGR_RECCTRLBASE, + YDSXGR_EFFCTRLBASE, + YDSXGR_WORKBASE, + /* capture set up */ + YDSXGR_MAPOFREC, + YDSXGR_RECFORMAT, + YDSXGR_RECSLOTSR, + YDSXGR_ADCFORMAT, + YDSXGR_ADCSLOTSR, +}; +#define YDSXGR_NUM_SAVED_REGS ARRAY_SIZE(saved_regs_index) + +static const int pci_saved_regs_index[] = { + /* All Chips */ + PCIR_DSXG_LEGACY, + PCIR_DSXG_ELEGACY, + /* YMF 744/754 */ + PCIR_DSXG_FMBASE, + PCIR_DSXG_SBBASE, + PCIR_DSXG_MPU401BASE, + PCIR_DSXG_JOYBASE, +}; +#define DSXG_PCI_NUM_SAVED_REGS ARRAY_SIZE(pci_saved_regs_index) +#define DSXG_PCI_NUM_SAVED_LEGACY_REGS 2 +static_assert(DSXG_PCI_NUM_SAVED_LEGACY_REGS <= DSXG_PCI_NUM_SAVED_REGS); + +struct snd_ymfpci { + int irq; + + unsigned int device_id; /* PCI device ID */ + unsigned char rev; /* PCI revision */ + unsigned long reg_area_phys; + void __iomem *reg_area_virt; + + u16 old_legacy_ctrl; +#ifdef SUPPORT_JOYSTICK + struct gameport *gameport; +#endif + + struct snd_dma_buffer *work_ptr; + + unsigned int bank_size_playback; + unsigned int bank_size_capture; + unsigned int bank_size_effect; + unsigned int work_size; + + void *bank_base_playback; + void *bank_base_capture; + void *bank_base_effect; + void *work_base; + dma_addr_t bank_base_playback_addr; + dma_addr_t bank_base_capture_addr; + dma_addr_t bank_base_effect_addr; + dma_addr_t work_base_addr; + struct snd_dma_buffer ac3_tmp_base; + + __le32 *ctrl_playback; + struct snd_ymfpci_playback_bank *bank_playback[YDSXG_PLAYBACK_VOICES][2]; + struct snd_ymfpci_capture_bank *bank_capture[YDSXG_CAPTURE_VOICES][2]; + struct snd_ymfpci_effect_bank *bank_effect[YDSXG_EFFECT_VOICES][2]; + + int start_count; + + u32 active_bank; + struct snd_ymfpci_voice voices[64]; + int src441_used; + + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + struct snd_rawmidi *rawmidi; + struct snd_timer *timer; + unsigned int timer_ticks; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm *pcm2; + struct snd_pcm *pcm_spdif; + struct snd_pcm *pcm_4ch; + struct snd_pcm_substream *capture_substream[YDSXG_CAPTURE_VOICES]; + struct snd_pcm_substream *effect_substream[YDSXG_EFFECT_VOICES]; + struct snd_kcontrol *ctl_vol_recsrc; + struct snd_kcontrol *ctl_vol_adcrec; + struct snd_kcontrol *ctl_vol_spdifrec; + unsigned short spdif_bits, spdif_pcm_bits; + struct snd_kcontrol *spdif_pcm_ctl; + int mode_dup4ch; + int rear_opened; + int spdif_opened; + struct snd_ymfpci_pcm_mixer { + u16 left; + u16 right; + struct snd_kcontrol *ctl; + } pcm_mixer[32]; + + spinlock_t reg_lock; + spinlock_t voice_lock; + wait_queue_head_t interrupt_sleep; + atomic_t interrupt_sleep_count; + struct snd_info_entry *proc_entry; + const struct firmware *dsp_microcode; + const struct firmware *controller_microcode; + + u32 saved_regs[YDSXGR_NUM_SAVED_REGS]; + u32 saved_ydsxgr_mode; + u16 saved_dsxg_pci_regs[DSXG_PCI_NUM_SAVED_REGS]; +}; + +int snd_ymfpci_create(struct snd_card *card, + struct pci_dev *pci, + u16 old_legacy_ctrl); +void snd_ymfpci_free_gameport(struct snd_ymfpci *chip); + +extern const struct dev_pm_ops snd_ymfpci_pm; + +int snd_ymfpci_pcm(struct snd_ymfpci *chip, int device); +int snd_ymfpci_pcm2(struct snd_ymfpci *chip, int device); +int snd_ymfpci_pcm_spdif(struct snd_ymfpci *chip, int device); +int snd_ymfpci_pcm_4ch(struct snd_ymfpci *chip, int device); +int snd_ymfpci_mixer(struct snd_ymfpci *chip, int rear_switch); +int snd_ymfpci_timer(struct snd_ymfpci *chip, int device); + +#endif /* __SOUND_YMFPCI_H */ diff --git a/sound/pci/ymfpci/ymfpci_main.c b/sound/pci/ymfpci/ymfpci_main.c new file mode 100644 index 000000000..2ccb976e6 --- /dev/null +++ b/sound/pci/ymfpci/ymfpci_main.c @@ -0,0 +1,2317 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of YMF724/740/744/754 chips + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "ymfpci.h" +#include +#include + +#include + +/* + * common I/O routines + */ + +static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip); + +static inline void snd_ymfpci_writeb(struct snd_ymfpci *chip, u32 offset, u8 val) +{ + writeb(val, chip->reg_area_virt + offset); +} + +static inline u16 snd_ymfpci_readw(struct snd_ymfpci *chip, u32 offset) +{ + return readw(chip->reg_area_virt + offset); +} + +static inline void snd_ymfpci_writew(struct snd_ymfpci *chip, u32 offset, u16 val) +{ + writew(val, chip->reg_area_virt + offset); +} + +static inline u32 snd_ymfpci_readl(struct snd_ymfpci *chip, u32 offset) +{ + return readl(chip->reg_area_virt + offset); +} + +static inline void snd_ymfpci_writel(struct snd_ymfpci *chip, u32 offset, u32 val) +{ + writel(val, chip->reg_area_virt + offset); +} + +static int snd_ymfpci_codec_ready(struct snd_ymfpci *chip, int secondary) +{ + unsigned long end_time; + u32 reg = secondary ? YDSXGR_SECSTATUSADR : YDSXGR_PRISTATUSADR; + + end_time = jiffies + msecs_to_jiffies(750); + do { + if ((snd_ymfpci_readw(chip, reg) & 0x8000) == 0) + return 0; + schedule_timeout_uninterruptible(1); + } while (time_before(jiffies, end_time)); + dev_err(chip->card->dev, + "codec_ready: codec %i is not ready [0x%x]\n", + secondary, snd_ymfpci_readw(chip, reg)); + return -EBUSY; +} + +static void snd_ymfpci_codec_write(struct snd_ac97 *ac97, u16 reg, u16 val) +{ + struct snd_ymfpci *chip = ac97->private_data; + u32 cmd; + + snd_ymfpci_codec_ready(chip, 0); + cmd = ((YDSXG_AC97WRITECMD | reg) << 16) | val; + snd_ymfpci_writel(chip, YDSXGR_AC97CMDDATA, cmd); +} + +static u16 snd_ymfpci_codec_read(struct snd_ac97 *ac97, u16 reg) +{ + struct snd_ymfpci *chip = ac97->private_data; + + if (snd_ymfpci_codec_ready(chip, 0)) + return ~0; + snd_ymfpci_writew(chip, YDSXGR_AC97CMDADR, YDSXG_AC97READCMD | reg); + if (snd_ymfpci_codec_ready(chip, 0)) + return ~0; + if (chip->device_id == PCI_DEVICE_ID_YAMAHA_744 && chip->rev < 2) { + int i; + for (i = 0; i < 600; i++) + snd_ymfpci_readw(chip, YDSXGR_PRISTATUSDATA); + } + return snd_ymfpci_readw(chip, YDSXGR_PRISTATUSDATA); +} + +/* + * Misc routines + */ + +static u32 snd_ymfpci_calc_delta(u32 rate) +{ + switch (rate) { + case 8000: return 0x02aaab00; + case 11025: return 0x03accd00; + case 16000: return 0x05555500; + case 22050: return 0x07599a00; + case 32000: return 0x0aaaab00; + case 44100: return 0x0eb33300; + default: return ((rate << 16) / 375) << 5; + } +} + +static const u32 def_rate[8] = { + 100, 2000, 8000, 11025, 16000, 22050, 32000, 48000 +}; + +static u32 snd_ymfpci_calc_lpfK(u32 rate) +{ + u32 i; + static const u32 val[8] = { + 0x00570000, 0x06AA0000, 0x18B20000, 0x20930000, + 0x2B9A0000, 0x35A10000, 0x3EAA0000, 0x40000000 + }; + + if (rate == 44100) + return 0x40000000; /* FIXME: What's the right value? */ + for (i = 0; i < 8; i++) + if (rate <= def_rate[i]) + return val[i]; + return val[0]; +} + +static u32 snd_ymfpci_calc_lpfQ(u32 rate) +{ + u32 i; + static const u32 val[8] = { + 0x35280000, 0x34A70000, 0x32020000, 0x31770000, + 0x31390000, 0x31C90000, 0x33D00000, 0x40000000 + }; + + if (rate == 44100) + return 0x370A0000; + for (i = 0; i < 8; i++) + if (rate <= def_rate[i]) + return val[i]; + return val[0]; +} + +/* + * Hardware start management + */ + +static void snd_ymfpci_hw_start(struct snd_ymfpci *chip) +{ + guard(spinlock_irqsave)(&chip->reg_lock); + if (chip->start_count++ > 0) + return; + snd_ymfpci_writel(chip, YDSXGR_MODE, + snd_ymfpci_readl(chip, YDSXGR_MODE) | 3); + chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT) & 1; +} + +static void snd_ymfpci_hw_stop(struct snd_ymfpci *chip) +{ + long timeout = 1000; + + guard(spinlock_irqsave)(&chip->reg_lock); + if (--chip->start_count > 0) + return; + snd_ymfpci_writel(chip, YDSXGR_MODE, + snd_ymfpci_readl(chip, YDSXGR_MODE) & ~3); + while (timeout-- > 0) { + if ((snd_ymfpci_readl(chip, YDSXGR_STATUS) & 2) == 0) + break; + } + if (atomic_read(&chip->interrupt_sleep_count)) { + atomic_set(&chip->interrupt_sleep_count, 0); + wake_up(&chip->interrupt_sleep); + } +} + +/* + * Playback voice management + */ + +static int voice_alloc(struct snd_ymfpci *chip, + enum snd_ymfpci_voice_type type, int pair, + struct snd_ymfpci_voice **rvoice) +{ + struct snd_ymfpci_voice *voice, *voice2; + int idx; + + *rvoice = NULL; + for (idx = 0; idx < YDSXG_PLAYBACK_VOICES; idx += pair ? 2 : 1) { + voice = &chip->voices[idx]; + voice2 = pair ? &chip->voices[idx+1] : NULL; + if (voice->use || (voice2 && voice2->use)) + continue; + voice->use = 1; + if (voice2) + voice2->use = 1; + switch (type) { + case YMFPCI_PCM: + voice->pcm = 1; + if (voice2) + voice2->pcm = 1; + break; + case YMFPCI_SYNTH: + voice->synth = 1; + break; + case YMFPCI_MIDI: + voice->midi = 1; + break; + } + snd_ymfpci_hw_start(chip); + if (voice2) + snd_ymfpci_hw_start(chip); + *rvoice = voice; + return 0; + } + return -ENOMEM; +} + +static int snd_ymfpci_voice_alloc(struct snd_ymfpci *chip, + enum snd_ymfpci_voice_type type, int pair, + struct snd_ymfpci_voice **rvoice) +{ + int result; + + if (snd_BUG_ON(!rvoice)) + return -EINVAL; + if (snd_BUG_ON(pair && type != YMFPCI_PCM)) + return -EINVAL; + + guard(spinlock_irqsave)(&chip->voice_lock); + for (;;) { + result = voice_alloc(chip, type, pair, rvoice); + if (result == 0 || type != YMFPCI_PCM) + break; + /* TODO: synth/midi voice deallocation */ + break; + } + return result; +} + +static int snd_ymfpci_voice_free(struct snd_ymfpci *chip, struct snd_ymfpci_voice *pvoice) +{ + if (snd_BUG_ON(!pvoice)) + return -EINVAL; + snd_ymfpci_hw_stop(chip); + guard(spinlock_irqsave)(&chip->voice_lock); + if (pvoice->number == chip->src441_used) { + chip->src441_used = -1; + pvoice->ypcm->use_441_slot = 0; + } + pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0; + pvoice->ypcm = NULL; + pvoice->interrupt = NULL; + return 0; +} + +/* + * PCM part + */ + +static void snd_ymfpci_pcm_interrupt(struct snd_ymfpci *chip, struct snd_ymfpci_voice *voice) +{ + struct snd_ymfpci_pcm *ypcm; + u32 pos, delta; + + ypcm = voice->ypcm; + if (!ypcm) + return; + if (ypcm->substream == NULL) + return; + guard(spinlock)(&chip->reg_lock); + if (ypcm->running) { + pos = le32_to_cpu(voice->bank[chip->active_bank].start); + if (pos < ypcm->last_pos) + delta = pos + (ypcm->buffer_size - ypcm->last_pos); + else + delta = pos - ypcm->last_pos; + ypcm->period_pos += delta; + ypcm->last_pos = pos; + if (ypcm->period_pos >= ypcm->period_size) { + /* + dev_dbg(chip->card->dev, + "done - active_bank = 0x%x, start = 0x%x\n", + chip->active_bank, + voice->bank[chip->active_bank].start); + */ + ypcm->period_pos %= ypcm->period_size; + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(ypcm->substream); + spin_lock(&chip->reg_lock); + } + + if (unlikely(ypcm->update_pcm_vol)) { + unsigned int subs = ypcm->substream->number; + unsigned int next_bank = 1 - chip->active_bank; + struct snd_ymfpci_playback_bank *bank; + __le32 volume; + + bank = &voice->bank[next_bank]; + volume = cpu_to_le32(chip->pcm_mixer[subs].left << 15); + bank->left_gain_end = volume; + if (ypcm->output_rear) + bank->eff2_gain_end = volume; + if (ypcm->voices[1]) + bank = &ypcm->voices[1]->bank[next_bank]; + volume = cpu_to_le32(chip->pcm_mixer[subs].right << 15); + bank->right_gain_end = volume; + if (ypcm->output_rear) + bank->eff3_gain_end = volume; + ypcm->update_pcm_vol--; + } + } +} + +static void snd_ymfpci_pcm_capture_interrupt(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm = runtime->private_data; + struct snd_ymfpci *chip = ypcm->chip; + u32 pos, delta; + + guard(spinlock)(&chip->reg_lock); + if (ypcm->running) { + pos = le32_to_cpu(chip->bank_capture[ypcm->capture_bank_number][chip->active_bank]->start) >> ypcm->shift; + if (pos < ypcm->last_pos) + delta = pos + (ypcm->buffer_size - ypcm->last_pos); + else + delta = pos - ypcm->last_pos; + ypcm->period_pos += delta; + ypcm->last_pos = pos; + if (ypcm->period_pos >= ypcm->period_size) { + ypcm->period_pos %= ypcm->period_size; + /* + dev_dbg(chip->card->dev, + "done - active_bank = 0x%x, start = 0x%x\n", + chip->active_bank, + voice->bank[chip->active_bank].start); + */ + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(substream); + spin_lock(&chip->reg_lock); + } + } +} + +static int snd_ymfpci_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data; + struct snd_kcontrol *kctl = NULL; + int result = 0; + + guard(spinlock)(&chip->reg_lock); + if (ypcm->voices[0] == NULL) + return -EINVAL; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + chip->ctrl_playback[ypcm->voices[0]->number + 1] = cpu_to_le32(ypcm->voices[0]->bank_addr); + if (ypcm->voices[1] != NULL && !ypcm->use_441_slot) + chip->ctrl_playback[ypcm->voices[1]->number + 1] = cpu_to_le32(ypcm->voices[1]->bank_addr); + ypcm->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->pcm == chip->pcm && !ypcm->use_441_slot) { + kctl = chip->pcm_mixer[substream->number].ctl; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } + fallthrough; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + chip->ctrl_playback[ypcm->voices[0]->number + 1] = 0; + if (ypcm->voices[1] != NULL && !ypcm->use_441_slot) + chip->ctrl_playback[ypcm->voices[1]->number + 1] = 0; + ypcm->running = 0; + break; + default: + return -EINVAL; + } + if (kctl) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id); + return result; +} +static int snd_ymfpci_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data; + int result = 0; + u32 tmp; + + guard(spinlock)(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + tmp = snd_ymfpci_readl(chip, YDSXGR_MAPOFREC) | (1 << ypcm->capture_bank_number); + snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, tmp); + ypcm->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + tmp = snd_ymfpci_readl(chip, YDSXGR_MAPOFREC) & ~(1 << ypcm->capture_bank_number); + snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, tmp); + ypcm->running = 0; + break; + default: + result = -EINVAL; + break; + } + return result; +} + +static int snd_ymfpci_pcm_voice_alloc(struct snd_ymfpci_pcm *ypcm, int voices) +{ + int err; + + if (ypcm->voices[1] != NULL && voices < 2) { + snd_ymfpci_voice_free(ypcm->chip, ypcm->voices[1]); + ypcm->voices[1] = NULL; + } + if (voices == 1 && ypcm->voices[0] != NULL) + return 0; /* already allocated */ + if (voices == 2 && ypcm->voices[0] != NULL && ypcm->voices[1] != NULL) + return 0; /* already allocated */ + if (voices > 1) { + if (ypcm->voices[0] != NULL && ypcm->voices[1] == NULL) { + snd_ymfpci_voice_free(ypcm->chip, ypcm->voices[0]); + ypcm->voices[0] = NULL; + } + } + err = snd_ymfpci_voice_alloc(ypcm->chip, YMFPCI_PCM, voices > 1, &ypcm->voices[0]); + if (err < 0) + return err; + ypcm->voices[0]->ypcm = ypcm; + ypcm->voices[0]->interrupt = snd_ymfpci_pcm_interrupt; + if (voices > 1) { + ypcm->voices[1] = &ypcm->chip->voices[ypcm->voices[0]->number + 1]; + ypcm->voices[1]->ypcm = ypcm; + } + return 0; +} + +static void snd_ymfpci_pcm_init_voice(struct snd_ymfpci_pcm *ypcm, unsigned int voiceidx, + struct snd_pcm_runtime *runtime, + int has_pcm_volume) +{ + struct snd_ymfpci_voice *voice = ypcm->voices[voiceidx]; + u32 format; + u32 delta = snd_ymfpci_calc_delta(runtime->rate); + u32 lpfQ = snd_ymfpci_calc_lpfQ(runtime->rate); + u32 lpfK = snd_ymfpci_calc_lpfK(runtime->rate); + struct snd_ymfpci_playback_bank *bank; + unsigned int nbank; + __le32 vol_left, vol_right; + u8 use_left, use_right; + + if (snd_BUG_ON(!voice)) + return; + if (runtime->channels == 1) { + use_left = 1; + use_right = 1; + } else { + use_left = (voiceidx & 1) == 0; + use_right = !use_left; + } + if (has_pcm_volume) { + vol_left = cpu_to_le32(ypcm->chip->pcm_mixer + [ypcm->substream->number].left << 15); + vol_right = cpu_to_le32(ypcm->chip->pcm_mixer + [ypcm->substream->number].right << 15); + } else { + vol_left = cpu_to_le32(0x40000000); + vol_right = cpu_to_le32(0x40000000); + } + scoped_guard(spinlock_irqsave, &ypcm->chip->voice_lock) { + format = runtime->channels == 2 ? 0x00010000 : 0; + if (snd_pcm_format_width(runtime->format) == 8) + format |= 0x80000000; + else if (ypcm->chip->device_id == PCI_DEVICE_ID_YAMAHA_754 && + runtime->rate == 44100 && runtime->channels == 2 && + voiceidx == 0 && (ypcm->chip->src441_used == -1 || + ypcm->chip->src441_used == voice->number)) { + ypcm->chip->src441_used = voice->number; + ypcm->use_441_slot = 1; + format |= 0x10000000; + } + if (ypcm->chip->src441_used == voice->number && + (format & 0x10000000) == 0) { + ypcm->chip->src441_used = -1; + ypcm->use_441_slot = 0; + } + if (runtime->channels == 2 && (voiceidx & 1) != 0) + format |= 1; + } + for (nbank = 0; nbank < 2; nbank++) { + bank = &voice->bank[nbank]; + memset(bank, 0, sizeof(*bank)); + bank->format = cpu_to_le32(format); + bank->base = cpu_to_le32(runtime->dma_addr); + bank->loop_end = cpu_to_le32(ypcm->buffer_size); + bank->lpfQ = cpu_to_le32(lpfQ); + bank->delta = + bank->delta_end = cpu_to_le32(delta); + bank->lpfK = + bank->lpfK_end = cpu_to_le32(lpfK); + bank->eg_gain = + bank->eg_gain_end = cpu_to_le32(0x40000000); + + if (ypcm->output_front) { + if (use_left) { + bank->left_gain = + bank->left_gain_end = vol_left; + } + if (use_right) { + bank->right_gain = + bank->right_gain_end = vol_right; + } + } + if (ypcm->output_rear) { + if (!ypcm->swap_rear) { + if (use_left) { + bank->eff2_gain = + bank->eff2_gain_end = vol_left; + } + if (use_right) { + bank->eff3_gain = + bank->eff3_gain_end = vol_right; + } + } else { + /* The SPDIF out channels seem to be swapped, so we have + * to swap them here, too. The rear analog out channels + * will be wrong, but otherwise AC3 would not work. + */ + if (use_left) { + bank->eff3_gain = + bank->eff3_gain_end = vol_left; + } + if (use_right) { + bank->eff2_gain = + bank->eff2_gain_end = vol_right; + } + } + } + } +} + +static int snd_ymfpci_ac3_init(struct snd_ymfpci *chip) +{ + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev, + 4096, &chip->ac3_tmp_base) < 0) + return -ENOMEM; + + chip->bank_effect[3][0]->base = + chip->bank_effect[3][1]->base = cpu_to_le32(chip->ac3_tmp_base.addr); + chip->bank_effect[3][0]->loop_end = + chip->bank_effect[3][1]->loop_end = cpu_to_le32(1024); + chip->bank_effect[4][0]->base = + chip->bank_effect[4][1]->base = cpu_to_le32(chip->ac3_tmp_base.addr + 2048); + chip->bank_effect[4][0]->loop_end = + chip->bank_effect[4][1]->loop_end = cpu_to_le32(1024); + + guard(spinlock_irq)(&chip->reg_lock); + snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT, + snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) | 3 << 3); + return 0; +} + +static int snd_ymfpci_ac3_done(struct snd_ymfpci *chip) +{ + scoped_guard(spinlock_irq, &chip->reg_lock) { + snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT, + snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) & ~(3 << 3)); + } + // snd_ymfpci_irq_wait(chip); + if (chip->ac3_tmp_base.area) { + snd_dma_free_pages(&chip->ac3_tmp_base); + chip->ac3_tmp_base.area = NULL; + } + return 0; +} + +static int snd_ymfpci_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm = runtime->private_data; + int err; + + err = snd_ymfpci_pcm_voice_alloc(ypcm, params_channels(hw_params)); + if (err < 0) + return err; + return 0; +} + +static int snd_ymfpci_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm; + + if (runtime->private_data == NULL) + return 0; + ypcm = runtime->private_data; + + /* wait, until the PCI operations are not finished */ + snd_ymfpci_irq_wait(chip); + if (ypcm->voices[1]) { + snd_ymfpci_voice_free(chip, ypcm->voices[1]); + ypcm->voices[1] = NULL; + } + if (ypcm->voices[0]) { + snd_ymfpci_voice_free(chip, ypcm->voices[0]); + ypcm->voices[0] = NULL; + } + return 0; +} + +static int snd_ymfpci_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm = runtime->private_data; + struct snd_kcontrol *kctl; + unsigned int nvoice; + + ypcm->period_size = runtime->period_size; + ypcm->buffer_size = runtime->buffer_size; + ypcm->period_pos = 0; + ypcm->last_pos = 0; + for (nvoice = 0; nvoice < runtime->channels; nvoice++) + snd_ymfpci_pcm_init_voice(ypcm, nvoice, runtime, + substream->pcm == chip->pcm); + + if (substream->pcm == chip->pcm && !ypcm->use_441_slot) { + kctl = chip->pcm_mixer[substream->number].ctl; + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id); + } + return 0; +} + +static int snd_ymfpci_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + + /* wait, until the PCI operations are not finished */ + snd_ymfpci_irq_wait(chip); + return 0; +} + +static int snd_ymfpci_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm = runtime->private_data; + struct snd_ymfpci_capture_bank * bank; + int nbank; + u32 rate, format; + + ypcm->period_size = runtime->period_size; + ypcm->buffer_size = runtime->buffer_size; + ypcm->period_pos = 0; + ypcm->last_pos = 0; + ypcm->shift = 0; + rate = ((48000 * 4096) / runtime->rate) - 1; + format = 0; + if (runtime->channels == 2) { + format |= 2; + ypcm->shift++; + } + if (snd_pcm_format_width(runtime->format) == 8) + format |= 1; + else + ypcm->shift++; + switch (ypcm->capture_bank_number) { + case 0: + snd_ymfpci_writel(chip, YDSXGR_RECFORMAT, format); + snd_ymfpci_writel(chip, YDSXGR_RECSLOTSR, rate); + break; + case 1: + snd_ymfpci_writel(chip, YDSXGR_ADCFORMAT, format); + snd_ymfpci_writel(chip, YDSXGR_ADCSLOTSR, rate); + break; + } + for (nbank = 0; nbank < 2; nbank++) { + bank = chip->bank_capture[ypcm->capture_bank_number][nbank]; + bank->base = cpu_to_le32(runtime->dma_addr); + bank->loop_end = cpu_to_le32(ypcm->buffer_size << ypcm->shift); + bank->start = 0; + bank->num_of_loops = 0; + } + return 0; +} + +static snd_pcm_uframes_t snd_ymfpci_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm = runtime->private_data; + struct snd_ymfpci_voice *voice = ypcm->voices[0]; + + if (!(ypcm->running && voice)) + return 0; + return le32_to_cpu(voice->bank[chip->active_bank].start); +} + +static snd_pcm_uframes_t snd_ymfpci_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm = runtime->private_data; + + if (!ypcm->running) + return 0; + return le32_to_cpu(chip->bank_capture[ypcm->capture_bank_number][chip->active_bank]->start) >> ypcm->shift; +} + +static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip) +{ + wait_queue_entry_t wait; + int loops = 4; + + while (loops-- > 0) { + if ((snd_ymfpci_readl(chip, YDSXGR_MODE) & 3) == 0) + continue; + init_waitqueue_entry(&wait, current); + add_wait_queue(&chip->interrupt_sleep, &wait); + atomic_inc(&chip->interrupt_sleep_count); + schedule_timeout_uninterruptible(msecs_to_jiffies(50)); + remove_wait_queue(&chip->interrupt_sleep, &wait); + } +} + +static irqreturn_t snd_ymfpci_interrupt(int irq, void *dev_id) +{ + struct snd_ymfpci *chip = dev_id; + u32 status, nvoice, mode; + struct snd_ymfpci_voice *voice; + + status = snd_ymfpci_readl(chip, YDSXGR_STATUS); + if (status & 0x80000000) { + chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT) & 1; + scoped_guard(spinlock, &chip->voice_lock) { + for (nvoice = 0; nvoice < YDSXG_PLAYBACK_VOICES; nvoice++) { + voice = &chip->voices[nvoice]; + if (voice->interrupt) + voice->interrupt(chip, voice); + } + for (nvoice = 0; nvoice < YDSXG_CAPTURE_VOICES; nvoice++) { + if (chip->capture_substream[nvoice]) + snd_ymfpci_pcm_capture_interrupt(chip->capture_substream[nvoice]); + } +#if 0 + for (nvoice = 0; nvoice < YDSXG_EFFECT_VOICES; nvoice++) { + if (chip->effect_substream[nvoice]) + snd_ymfpci_pcm_effect_interrupt(chip->effect_substream[nvoice]); + } +#endif + } + scoped_guard(spinlock, &chip->reg_lock) { + snd_ymfpci_writel(chip, YDSXGR_STATUS, 0x80000000); + mode = snd_ymfpci_readl(chip, YDSXGR_MODE) | 2; + snd_ymfpci_writel(chip, YDSXGR_MODE, mode); + } + + if (atomic_read(&chip->interrupt_sleep_count)) { + atomic_set(&chip->interrupt_sleep_count, 0); + wake_up(&chip->interrupt_sleep); + } + } + + status = snd_ymfpci_readw(chip, YDSXGR_INTFLAG); + if (status & 1) { + if (chip->timer) + snd_timer_interrupt(chip->timer, chip->timer_ticks); + } + snd_ymfpci_writew(chip, YDSXGR_INTFLAG, status); + + if (chip->rawmidi) + snd_mpu401_uart_interrupt(irq, chip->rawmidi->private_data); + return IRQ_HANDLED; +} + +static const struct snd_pcm_hardware snd_ymfpci_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 256 * 1024, /* FIXME: enough? */ + .period_bytes_min = 64, + .period_bytes_max = 256 * 1024, /* FIXME: enough? */ + .periods_min = 3, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_ymfpci_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 256 * 1024, /* FIXME: enough? */ + .period_bytes_min = 64, + .period_bytes_max = 256 * 1024, /* FIXME: enough? */ + .periods_min = 3, + .periods_max = 1024, + .fifo_size = 0, +}; + +static void snd_ymfpci_pcm_free_substream(struct snd_pcm_runtime *runtime) +{ + kfree(runtime->private_data); +} + +static int snd_ymfpci_playback_open_1(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm; + int err; + + runtime->hw = snd_ymfpci_playback; + /* FIXME? True value is 256/48 = 5.33333 ms */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 5334, UINT_MAX); + if (err < 0) + return err; + err = snd_pcm_hw_rule_noresample(runtime, 48000); + if (err < 0) + return err; + + ypcm = kzalloc_obj(*ypcm); + if (ypcm == NULL) + return -ENOMEM; + ypcm->chip = chip; + ypcm->type = PLAYBACK_VOICE; + ypcm->substream = substream; + runtime->private_data = ypcm; + runtime->private_free = snd_ymfpci_pcm_free_substream; + return 0; +} + +/* call with spinlock held */ +static void ymfpci_open_extension(struct snd_ymfpci *chip) +{ + if (! chip->rear_opened) { + if (! chip->spdif_opened) /* set AC3 */ + snd_ymfpci_writel(chip, YDSXGR_MODE, + snd_ymfpci_readl(chip, YDSXGR_MODE) | (1 << 30)); + /* enable second codec (4CHEN) */ + snd_ymfpci_writew(chip, YDSXGR_SECCONFIG, + (snd_ymfpci_readw(chip, YDSXGR_SECCONFIG) & ~0x0330) | 0x0010); + } +} + +/* call with spinlock held */ +static void ymfpci_close_extension(struct snd_ymfpci *chip) +{ + if (! chip->rear_opened) { + if (! chip->spdif_opened) + snd_ymfpci_writel(chip, YDSXGR_MODE, + snd_ymfpci_readl(chip, YDSXGR_MODE) & ~(1 << 30)); + snd_ymfpci_writew(chip, YDSXGR_SECCONFIG, + (snd_ymfpci_readw(chip, YDSXGR_SECCONFIG) & ~0x0330) & ~0x0010); + } +} + +static int snd_ymfpci_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm; + int err; + + err = snd_ymfpci_playback_open_1(substream); + if (err < 0) + return err; + ypcm = runtime->private_data; + ypcm->output_front = 1; + ypcm->output_rear = chip->mode_dup4ch ? 1 : 0; + ypcm->swap_rear = 0; + guard(spinlock_irq)(&chip->reg_lock); + if (ypcm->output_rear) { + ymfpci_open_extension(chip); + chip->rear_opened++; + } + return 0; +} + +static int snd_ymfpci_playback_spdif_open(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm; + int err; + + err = snd_ymfpci_playback_open_1(substream); + if (err < 0) + return err; + ypcm = runtime->private_data; + ypcm->output_front = 0; + ypcm->output_rear = 1; + ypcm->swap_rear = 1; + scoped_guard(spinlock_irq, &chip->reg_lock) { + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL, + snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) | 2); + ymfpci_open_extension(chip); + chip->spdif_pcm_bits = chip->spdif_bits; + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_pcm_bits); + chip->spdif_opened++; + } + + chip->spdif_pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &chip->spdif_pcm_ctl->id); + return 0; +} + +static int snd_ymfpci_playback_4ch_open(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm; + int err; + + err = snd_ymfpci_playback_open_1(substream); + if (err < 0) + return err; + ypcm = runtime->private_data; + ypcm->output_front = 0; + ypcm->output_rear = 1; + ypcm->swap_rear = 0; + guard(spinlock_irq)(&chip->reg_lock); + ymfpci_open_extension(chip); + chip->rear_opened++; + return 0; +} + +static int snd_ymfpci_capture_open(struct snd_pcm_substream *substream, + u32 capture_bank_number) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm; + int err; + + runtime->hw = snd_ymfpci_capture; + /* FIXME? True value is 256/48 = 5.33333 ms */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 5334, UINT_MAX); + if (err < 0) + return err; + err = snd_pcm_hw_rule_noresample(runtime, 48000); + if (err < 0) + return err; + + ypcm = kzalloc_obj(*ypcm); + if (ypcm == NULL) + return -ENOMEM; + ypcm->chip = chip; + ypcm->type = capture_bank_number + CAPTURE_REC; + ypcm->substream = substream; + ypcm->capture_bank_number = capture_bank_number; + chip->capture_substream[capture_bank_number] = substream; + runtime->private_data = ypcm; + runtime->private_free = snd_ymfpci_pcm_free_substream; + snd_ymfpci_hw_start(chip); + return 0; +} + +static int snd_ymfpci_capture_rec_open(struct snd_pcm_substream *substream) +{ + return snd_ymfpci_capture_open(substream, 0); +} + +static int snd_ymfpci_capture_ac97_open(struct snd_pcm_substream *substream) +{ + return snd_ymfpci_capture_open(substream, 1); +} + +static int snd_ymfpci_playback_close_1(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int snd_ymfpci_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data; + + scoped_guard(spinlock_irq, &chip->reg_lock) { + if (ypcm->output_rear && chip->rear_opened > 0) { + chip->rear_opened--; + ymfpci_close_extension(chip); + } + } + return snd_ymfpci_playback_close_1(substream); +} + +static int snd_ymfpci_playback_spdif_close(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + + scoped_guard(spinlock_irq, &chip->reg_lock) { + chip->spdif_opened = 0; + ymfpci_close_extension(chip); + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL, + snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & ~2); + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits); + } + chip->spdif_pcm_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &chip->spdif_pcm_ctl->id); + return snd_ymfpci_playback_close_1(substream); +} + +static int snd_ymfpci_playback_4ch_close(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + + scoped_guard(spinlock_irq, &chip->reg_lock) { + if (chip->rear_opened > 0) { + chip->rear_opened--; + ymfpci_close_extension(chip); + } + } + return snd_ymfpci_playback_close_1(substream); +} + +static int snd_ymfpci_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_ymfpci *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ymfpci_pcm *ypcm = runtime->private_data; + + if (ypcm != NULL) { + chip->capture_substream[ypcm->capture_bank_number] = NULL; + snd_ymfpci_hw_stop(chip); + } + return 0; +} + +static const struct snd_pcm_ops snd_ymfpci_playback_ops = { + .open = snd_ymfpci_playback_open, + .close = snd_ymfpci_playback_close, + .hw_params = snd_ymfpci_playback_hw_params, + .hw_free = snd_ymfpci_playback_hw_free, + .prepare = snd_ymfpci_playback_prepare, + .trigger = snd_ymfpci_playback_trigger, + .pointer = snd_ymfpci_playback_pointer, +}; + +static const struct snd_pcm_ops snd_ymfpci_capture_rec_ops = { + .open = snd_ymfpci_capture_rec_open, + .close = snd_ymfpci_capture_close, + .hw_free = snd_ymfpci_capture_hw_free, + .prepare = snd_ymfpci_capture_prepare, + .trigger = snd_ymfpci_capture_trigger, + .pointer = snd_ymfpci_capture_pointer, +}; + +int snd_ymfpci_pcm(struct snd_ymfpci *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "YMFPCI", device, 32, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ymfpci_capture_rec_ops); + + /* global setup */ + pcm->info_flags = 0; + strscpy(pcm->name, "YMFPCI"); + chip->pcm = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 256*1024); + + return snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_std_chmaps, 2, 0, NULL); +} + +static const struct snd_pcm_ops snd_ymfpci_capture_ac97_ops = { + .open = snd_ymfpci_capture_ac97_open, + .close = snd_ymfpci_capture_close, + .hw_free = snd_ymfpci_capture_hw_free, + .prepare = snd_ymfpci_capture_prepare, + .trigger = snd_ymfpci_capture_trigger, + .pointer = snd_ymfpci_capture_pointer, +}; + +int snd_ymfpci_pcm2(struct snd_ymfpci *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "YMFPCI - PCM2", device, 0, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ymfpci_capture_ac97_ops); + + /* global setup */ + pcm->info_flags = 0; + sprintf(pcm->name, "YMFPCI - %s", + chip->device_id == PCI_DEVICE_ID_YAMAHA_754 ? "Direct Recording" : "AC'97"); + chip->pcm2 = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 256*1024); + + return 0; +} + +static const struct snd_pcm_ops snd_ymfpci_playback_spdif_ops = { + .open = snd_ymfpci_playback_spdif_open, + .close = snd_ymfpci_playback_spdif_close, + .hw_params = snd_ymfpci_playback_hw_params, + .hw_free = snd_ymfpci_playback_hw_free, + .prepare = snd_ymfpci_playback_prepare, + .trigger = snd_ymfpci_playback_trigger, + .pointer = snd_ymfpci_playback_pointer, +}; + +int snd_ymfpci_pcm_spdif(struct snd_ymfpci *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "YMFPCI - IEC958", device, 1, 0, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_spdif_ops); + + /* global setup */ + pcm->info_flags = 0; + strscpy(pcm->name, "YMFPCI - IEC958"); + chip->pcm_spdif = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 256*1024); + + return 0; +} + +static const struct snd_pcm_ops snd_ymfpci_playback_4ch_ops = { + .open = snd_ymfpci_playback_4ch_open, + .close = snd_ymfpci_playback_4ch_close, + .hw_params = snd_ymfpci_playback_hw_params, + .hw_free = snd_ymfpci_playback_hw_free, + .prepare = snd_ymfpci_playback_prepare, + .trigger = snd_ymfpci_playback_trigger, + .pointer = snd_ymfpci_playback_pointer, +}; + +static const struct snd_pcm_chmap_elem surround_map[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, + .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +int snd_ymfpci_pcm_4ch(struct snd_ymfpci *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "YMFPCI - Rear", device, 1, 0, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_4ch_ops); + + /* global setup */ + pcm->info_flags = 0; + strscpy(pcm->name, "YMFPCI - Rear PCM"); + chip->pcm_4ch = pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pci->dev, 64*1024, 256*1024); + + return snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + surround_map, 2, 0, NULL); +} + +static int snd_ymfpci_spdif_default_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_ymfpci_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&chip->reg_lock); + ucontrol->value.iec958.status[0] = (chip->spdif_bits >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (chip->spdif_bits >> 8) & 0xff; + ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000; + return 0; +} + +static int snd_ymfpci_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ((ucontrol->value.iec958.status[0] & 0x3e) << 0) | + (ucontrol->value.iec958.status[1] << 8); + guard(spinlock_irq)(&chip->reg_lock); + change = chip->spdif_bits != val; + chip->spdif_bits = val; + if ((snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & 1) && chip->pcm_spdif == NULL) + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits); + return change; +} + +static const struct snd_kcontrol_new snd_ymfpci_spdif_default = +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_ymfpci_spdif_default_info, + .get = snd_ymfpci_spdif_default_get, + .put = snd_ymfpci_spdif_default_put +}; + +static int snd_ymfpci_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_ymfpci_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&chip->reg_lock); + ucontrol->value.iec958.status[0] = 0x3e; + ucontrol->value.iec958.status[1] = 0xff; + return 0; +} + +static const struct snd_kcontrol_new snd_ymfpci_spdif_mask = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK), + .info = snd_ymfpci_spdif_mask_info, + .get = snd_ymfpci_spdif_mask_get, +}; + +static int snd_ymfpci_spdif_stream_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_ymfpci_spdif_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irq)(&chip->reg_lock); + ucontrol->value.iec958.status[0] = (chip->spdif_pcm_bits >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (chip->spdif_pcm_bits >> 8) & 0xff; + ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000; + return 0; +} + +static int snd_ymfpci_spdif_stream_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change; + + val = ((ucontrol->value.iec958.status[0] & 0x3e) << 0) | + (ucontrol->value.iec958.status[1] << 8); + guard(spinlock_irq)(&chip->reg_lock); + change = chip->spdif_pcm_bits != val; + chip->spdif_pcm_bits = val; + if ((snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & 2)) + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_pcm_bits); + return change; +} + +static const struct snd_kcontrol_new snd_ymfpci_spdif_stream = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM), + .info = snd_ymfpci_spdif_stream_info, + .get = snd_ymfpci_spdif_stream_get, + .put = snd_ymfpci_spdif_stream_put +}; + +static int snd_ymfpci_drec_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *info) +{ + static const char *const texts[3] = {"AC'97", "IEC958", "ZV Port"}; + + return snd_ctl_enum_info(info, 1, 3, texts); +} + +static int snd_ymfpci_drec_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + u16 reg; + + guard(spinlock_irq)(&chip->reg_lock); + reg = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL); + if (!(reg & 0x100)) + value->value.enumerated.item[0] = 0; + else + value->value.enumerated.item[0] = 1 + ((reg & 0x200) != 0); + return 0; +} + +static int snd_ymfpci_drec_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + u16 reg, old_reg; + + guard(spinlock_irq)(&chip->reg_lock); + old_reg = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL); + if (value->value.enumerated.item[0] == 0) + reg = old_reg & ~0x100; + else + reg = (old_reg & ~0x300) | 0x100 | ((value->value.enumerated.item[0] == 2) << 9); + snd_ymfpci_writew(chip, YDSXGR_GLOBALCTRL, reg); + return reg != old_reg; +} + +static const struct snd_kcontrol_new snd_ymfpci_drec_source = { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Direct Recording Source", + .info = snd_ymfpci_drec_source_info, + .get = snd_ymfpci_drec_source_get, + .put = snd_ymfpci_drec_source_put +}; + +/* + * Mixer controls + */ + +#define YMFPCI_SINGLE(xname, xindex, reg, shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_ymfpci_info_single, \ + .get = snd_ymfpci_get_single, .put = snd_ymfpci_put_single, \ + .private_value = ((reg) | ((shift) << 16)) } + +#define snd_ymfpci_info_single snd_ctl_boolean_mono_info + +static int snd_ymfpci_get_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xffff; + unsigned int shift = (kcontrol->private_value >> 16) & 0xff; + unsigned int mask = 1; + + switch (reg) { + case YDSXGR_SPDIFOUTCTRL: break; + case YDSXGR_SPDIFINCTRL: break; + default: return -EINVAL; + } + ucontrol->value.integer.value[0] = + (snd_ymfpci_readl(chip, reg) >> shift) & mask; + return 0; +} + +static int snd_ymfpci_put_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xffff; + unsigned int shift = (kcontrol->private_value >> 16) & 0xff; + unsigned int mask = 1; + int change; + unsigned int val, oval; + + switch (reg) { + case YDSXGR_SPDIFOUTCTRL: break; + case YDSXGR_SPDIFINCTRL: break; + default: return -EINVAL; + } + val = (ucontrol->value.integer.value[0] & mask); + val <<= shift; + guard(spinlock_irq)(&chip->reg_lock); + oval = snd_ymfpci_readl(chip, reg); + val = (oval & ~(mask << shift)) | val; + change = val != oval; + snd_ymfpci_writel(chip, reg, val); + return change; +} + +static const DECLARE_TLV_DB_LINEAR(db_scale_native, TLV_DB_GAIN_MUTE, 0); + +#define YMFPCI_DOUBLE(xname, xindex, reg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = snd_ymfpci_info_double, \ + .get = snd_ymfpci_get_double, .put = snd_ymfpci_put_double, \ + .private_value = reg, \ + .tlv = { .p = db_scale_native } } + +static int snd_ymfpci_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + unsigned int reg = kcontrol->private_value; + + if (reg < 0x80 || reg >= 0xc0) + return -EINVAL; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 16383; + return 0; +} + +static int snd_ymfpci_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + unsigned int reg = kcontrol->private_value; + unsigned int shift_left = 0, shift_right = 16, mask = 16383; + unsigned int val; + + if (reg < 0x80 || reg >= 0xc0) + return -EINVAL; + guard(spinlock_irq)(&chip->reg_lock); + val = snd_ymfpci_readl(chip, reg); + ucontrol->value.integer.value[0] = (val >> shift_left) & mask; + ucontrol->value.integer.value[1] = (val >> shift_right) & mask; + return 0; +} + +static int snd_ymfpci_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + unsigned int reg = kcontrol->private_value; + unsigned int shift_left = 0, shift_right = 16, mask = 16383; + int change; + unsigned int val1, val2, oval; + + if (reg < 0x80 || reg >= 0xc0) + return -EINVAL; + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + val1 <<= shift_left; + val2 <<= shift_right; + guard(spinlock_irq)(&chip->reg_lock); + oval = snd_ymfpci_readl(chip, reg); + val1 = (oval & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != oval; + snd_ymfpci_writel(chip, reg, val1); + return change; +} + +static int snd_ymfpci_put_nativedacvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + unsigned int reg = YDSXGR_NATIVEDACOUTVOL; + unsigned int reg2 = YDSXGR_BUF441OUTVOL; + int change; + unsigned int value, oval; + + value = ucontrol->value.integer.value[0] & 0x3fff; + value |= (ucontrol->value.integer.value[1] & 0x3fff) << 16; + guard(spinlock_irq)(&chip->reg_lock); + oval = snd_ymfpci_readl(chip, reg); + change = value != oval; + snd_ymfpci_writel(chip, reg, value); + snd_ymfpci_writel(chip, reg2, value); + return change; +} + +/* + * 4ch duplication + */ +#define snd_ymfpci_info_dup4ch snd_ctl_boolean_mono_info + +static int snd_ymfpci_get_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->mode_dup4ch; + return 0; +} + +static int snd_ymfpci_put_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + int change; + change = (ucontrol->value.integer.value[0] != chip->mode_dup4ch); + if (change) + chip->mode_dup4ch = !!ucontrol->value.integer.value[0]; + return change; +} + +static const struct snd_kcontrol_new snd_ymfpci_dup4ch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "4ch Duplication", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ymfpci_info_dup4ch, + .get = snd_ymfpci_get_dup4ch, + .put = snd_ymfpci_put_dup4ch, +}; + +static const struct snd_kcontrol_new snd_ymfpci_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Wave Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_ymfpci_info_double, + .get = snd_ymfpci_get_double, + .put = snd_ymfpci_put_nativedacvol, + .private_value = YDSXGR_NATIVEDACOUTVOL, + .tlv = { .p = db_scale_native }, +}, +YMFPCI_DOUBLE("Wave Capture Volume", 0, YDSXGR_NATIVEDACLOOPVOL), +YMFPCI_DOUBLE("Digital Capture Volume", 0, YDSXGR_NATIVEDACINVOL), +YMFPCI_DOUBLE("Digital Capture Volume", 1, YDSXGR_NATIVEADCINVOL), +YMFPCI_DOUBLE("ADC Playback Volume", 0, YDSXGR_PRIADCOUTVOL), +YMFPCI_DOUBLE("ADC Capture Volume", 0, YDSXGR_PRIADCLOOPVOL), +YMFPCI_DOUBLE("ADC Playback Volume", 1, YDSXGR_SECADCOUTVOL), +YMFPCI_DOUBLE("ADC Capture Volume", 1, YDSXGR_SECADCLOOPVOL), +YMFPCI_DOUBLE("FM Legacy Playback Volume", 0, YDSXGR_LEGACYOUTVOL), +YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("AC97 ", PLAYBACK,VOLUME), 0, YDSXGR_ZVOUTVOL), +YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("", CAPTURE,VOLUME), 0, YDSXGR_ZVLOOPVOL), +YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("AC97 ",PLAYBACK,VOLUME), 1, YDSXGR_SPDIFOUTVOL), +YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,VOLUME), 1, YDSXGR_SPDIFLOOPVOL), +YMFPCI_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), 0, YDSXGR_SPDIFOUTCTRL, 0), +YMFPCI_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), 0, YDSXGR_SPDIFINCTRL, 0), +YMFPCI_SINGLE(SNDRV_CTL_NAME_IEC958("Loop",NONE,NONE), 0, YDSXGR_SPDIFINCTRL, 4), +}; + + +/* + * GPIO + */ + +static int snd_ymfpci_get_gpio_out(struct snd_ymfpci *chip, int pin) +{ + u16 reg, mode; + + guard(spinlock_irqsave)(&chip->reg_lock); + reg = snd_ymfpci_readw(chip, YDSXGR_GPIOFUNCENABLE); + reg &= ~(1 << (pin + 8)); + reg |= (1 << pin); + snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg); + /* set the level mode for input line */ + mode = snd_ymfpci_readw(chip, YDSXGR_GPIOTYPECONFIG); + mode &= ~(3 << (pin * 2)); + snd_ymfpci_writew(chip, YDSXGR_GPIOTYPECONFIG, mode); + snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg | (1 << (pin + 8))); + mode = snd_ymfpci_readw(chip, YDSXGR_GPIOINSTATUS); + return (mode >> pin) & 1; +} + +static int snd_ymfpci_set_gpio_out(struct snd_ymfpci *chip, int pin, int enable) +{ + u16 reg; + + guard(spinlock_irqsave)(&chip->reg_lock); + reg = snd_ymfpci_readw(chip, YDSXGR_GPIOFUNCENABLE); + reg &= ~(1 << pin); + reg &= ~(1 << (pin + 8)); + snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg); + snd_ymfpci_writew(chip, YDSXGR_GPIOOUTCTRL, enable << pin); + snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg | (1 << (pin + 8))); + + return 0; +} + +#define snd_ymfpci_gpio_sw_info snd_ctl_boolean_mono_info + +static int snd_ymfpci_gpio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + int pin = (int)kcontrol->private_value; + ucontrol->value.integer.value[0] = snd_ymfpci_get_gpio_out(chip, pin); + return 0; +} + +static int snd_ymfpci_gpio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + int pin = (int)kcontrol->private_value; + + if (snd_ymfpci_get_gpio_out(chip, pin) != ucontrol->value.integer.value[0]) { + snd_ymfpci_set_gpio_out(chip, pin, !!ucontrol->value.integer.value[0]); + ucontrol->value.integer.value[0] = snd_ymfpci_get_gpio_out(chip, pin); + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new snd_ymfpci_rear_shared = { + .name = "Shared Rear/Line-In Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_ymfpci_gpio_sw_info, + .get = snd_ymfpci_gpio_sw_get, + .put = snd_ymfpci_gpio_sw_put, + .private_value = 2, +}; + +/* + * PCM voice volume + */ + +static int snd_ymfpci_pcm_vol_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 = 0x8000; + return 0; +} + +static int snd_ymfpci_pcm_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + unsigned int subs = kcontrol->id.subdevice; + + ucontrol->value.integer.value[0] = chip->pcm_mixer[subs].left; + ucontrol->value.integer.value[1] = chip->pcm_mixer[subs].right; + return 0; +} + +static int snd_ymfpci_pcm_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol); + unsigned int subs = kcontrol->id.subdevice; + struct snd_pcm_substream *substream; + + if (ucontrol->value.integer.value[0] != chip->pcm_mixer[subs].left || + ucontrol->value.integer.value[1] != chip->pcm_mixer[subs].right) { + chip->pcm_mixer[subs].left = ucontrol->value.integer.value[0]; + chip->pcm_mixer[subs].right = ucontrol->value.integer.value[1]; + if (chip->pcm_mixer[subs].left > 0x8000) + chip->pcm_mixer[subs].left = 0x8000; + if (chip->pcm_mixer[subs].right > 0x8000) + chip->pcm_mixer[subs].right = 0x8000; + + substream = (struct snd_pcm_substream *)kcontrol->private_value; + guard(spinlock_irqsave)(&chip->voice_lock); + if (substream->runtime && substream->runtime->private_data) { + struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data; + if (!ypcm->use_441_slot) + ypcm->update_pcm_vol = 2; + } + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new snd_ymfpci_pcm_volume = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE, + .info = snd_ymfpci_pcm_vol_info, + .get = snd_ymfpci_pcm_vol_get, + .put = snd_ymfpci_pcm_vol_put, +}; + + +/* + * Mixer routines + */ + +static void snd_ymfpci_mixer_free_ac97_bus(struct snd_ac97_bus *bus) +{ + struct snd_ymfpci *chip = bus->private_data; + chip->ac97_bus = NULL; +} + +static void snd_ymfpci_mixer_free_ac97(struct snd_ac97 *ac97) +{ + struct snd_ymfpci *chip = ac97->private_data; + chip->ac97 = NULL; +} + +int snd_ymfpci_mixer(struct snd_ymfpci *chip, int rear_switch) +{ + struct snd_ac97_template ac97; + struct snd_kcontrol *kctl; + struct snd_pcm_substream *substream; + unsigned int idx; + int err; + static const struct snd_ac97_bus_ops ops = { + .write = snd_ymfpci_codec_write, + .read = snd_ymfpci_codec_read, + }; + + err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus); + if (err < 0) + return err; + chip->ac97_bus->private_free = snd_ymfpci_mixer_free_ac97_bus; + chip->ac97_bus->no_vra = 1; /* YMFPCI doesn't need VRA */ + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.private_free = snd_ymfpci_mixer_free_ac97; + err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97); + if (err < 0) + return err; + + /* to be sure */ + snd_ac97_update_bits(chip->ac97, AC97_EXTENDED_STATUS, + AC97_EA_VRA|AC97_EA_VRM, 0); + + for (idx = 0; idx < ARRAY_SIZE(snd_ymfpci_controls); idx++) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_ymfpci_controls[idx], chip)); + if (err < 0) + return err; + } + if (chip->ac97->ext_id & AC97_EI_SDAC) { + kctl = snd_ctl_new1(&snd_ymfpci_dup4ch, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + + /* add S/PDIF control */ + if (snd_BUG_ON(!chip->pcm_spdif)) + return -ENXIO; + kctl = snd_ctl_new1(&snd_ymfpci_spdif_default, chip); + if (!kctl) + return -ENOMEM; + kctl->id.device = chip->pcm_spdif->device; + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + kctl = snd_ctl_new1(&snd_ymfpci_spdif_mask, chip); + if (!kctl) + return -ENOMEM; + kctl->id.device = chip->pcm_spdif->device; + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + kctl = snd_ctl_new1(&snd_ymfpci_spdif_stream, chip); + if (!kctl) + return -ENOMEM; + kctl->id.device = chip->pcm_spdif->device; + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + chip->spdif_pcm_ctl = kctl; + + /* direct recording source */ + if (chip->device_id == PCI_DEVICE_ID_YAMAHA_754) { + kctl = snd_ctl_new1(&snd_ymfpci_drec_source, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + + /* + * shared rear/line-in + */ + if (rear_switch) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_ymfpci_rear_shared, chip)); + if (err < 0) + return err; + } + + /* per-voice volume */ + substream = chip->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + for (idx = 0; idx < 32; ++idx) { + kctl = snd_ctl_new1(&snd_ymfpci_pcm_volume, chip); + if (!kctl) + return -ENOMEM; + kctl->id.device = chip->pcm->device; + kctl->id.subdevice = idx; + kctl->private_value = (unsigned long)substream; + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + chip->pcm_mixer[idx].left = 0x8000; + chip->pcm_mixer[idx].right = 0x8000; + chip->pcm_mixer[idx].ctl = kctl; + substream = substream->next; + } + + return 0; +} + + +/* + * timer + */ + +static int snd_ymfpci_timer_start(struct snd_timer *timer) +{ + struct snd_ymfpci *chip; + unsigned int count; + + chip = snd_timer_chip(timer); + guard(spinlock_irqsave)(&chip->reg_lock); + if (timer->sticks > 1) { + chip->timer_ticks = timer->sticks; + count = timer->sticks - 1; + } else { + /* + * Divisor 1 is not allowed; fake it by using divisor 2 and + * counting two ticks for each interrupt. + */ + chip->timer_ticks = 2; + count = 2 - 1; + } + snd_ymfpci_writew(chip, YDSXGR_TIMERCOUNT, count); + snd_ymfpci_writeb(chip, YDSXGR_TIMERCTRL, 0x03); + return 0; +} + +static int snd_ymfpci_timer_stop(struct snd_timer *timer) +{ + struct snd_ymfpci *chip; + + chip = snd_timer_chip(timer); + guard(spinlock_irqsave)(&chip->reg_lock); + snd_ymfpci_writeb(chip, YDSXGR_TIMERCTRL, 0x00); + return 0; +} + +static int snd_ymfpci_timer_precise_resolution(struct snd_timer *timer, + unsigned long *num, unsigned long *den) +{ + *num = 1; + *den = 96000; + return 0; +} + +static const struct snd_timer_hardware snd_ymfpci_timer_hw = { + .flags = SNDRV_TIMER_HW_AUTO, + .resolution = 10417, /* 1 / 96 kHz = 10.41666...us */ + .ticks = 0x10000, + .start = snd_ymfpci_timer_start, + .stop = snd_ymfpci_timer_stop, + .precise_resolution = snd_ymfpci_timer_precise_resolution, +}; + +int snd_ymfpci_timer(struct snd_ymfpci *chip, int device) +{ + struct snd_timer *timer = NULL; + struct snd_timer_id tid; + int err; + + 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, "YMFPCI", &tid, &timer); + if (err >= 0) { + strscpy(timer->name, "YMFPCI timer"); + timer->private_data = chip; + timer->hw = snd_ymfpci_timer_hw; + } + chip->timer = timer; + return err; +} + + +/* + * proc interface + */ + +static void snd_ymfpci_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ymfpci *chip = entry->private_data; + int i; + + snd_iprintf(buffer, "YMFPCI\n\n"); + for (i = 0; i <= YDSXGR_WORKBASE; i += 4) + snd_iprintf(buffer, "%04x: %04x\n", i, snd_ymfpci_readl(chip, i)); +} + +static int snd_ymfpci_proc_init(struct snd_card *card, struct snd_ymfpci *chip) +{ + return snd_card_ro_proc_new(card, "ymfpci", chip, snd_ymfpci_proc_read); +} + +/* + * initialization routines + */ + +static void snd_ymfpci_aclink_reset(struct pci_dev * pci) +{ + u8 cmd; + + pci_read_config_byte(pci, PCIR_DSXG_CTRL, &cmd); +#if 0 // force to reset + if (cmd & 0x03) { +#endif + pci_write_config_byte(pci, PCIR_DSXG_CTRL, cmd & 0xfc); + pci_write_config_byte(pci, PCIR_DSXG_CTRL, cmd | 0x03); + pci_write_config_byte(pci, PCIR_DSXG_CTRL, cmd & 0xfc); + pci_write_config_word(pci, PCIR_DSXG_PWRCTRL1, 0); + pci_write_config_word(pci, PCIR_DSXG_PWRCTRL2, 0); +#if 0 + } +#endif +} + +static void snd_ymfpci_enable_dsp(struct snd_ymfpci *chip) +{ + snd_ymfpci_writel(chip, YDSXGR_CONFIG, 0x00000001); +} + +static void snd_ymfpci_disable_dsp(struct snd_ymfpci *chip) +{ + u32 val; + int timeout = 1000; + + val = snd_ymfpci_readl(chip, YDSXGR_CONFIG); + if (val) + snd_ymfpci_writel(chip, YDSXGR_CONFIG, 0x00000000); + while (timeout-- > 0) { + val = snd_ymfpci_readl(chip, YDSXGR_STATUS); + if ((val & 0x00000002) == 0) + break; + } +} + +static int snd_ymfpci_request_firmware(struct snd_ymfpci *chip) +{ + int err, is_1e; + const char *name; + + err = request_firmware(&chip->dsp_microcode, "yamaha/ds1_dsp.fw", + &chip->pci->dev); + if (err >= 0) { + if (chip->dsp_microcode->size != YDSXG_DSPLENGTH) { + dev_err(chip->card->dev, + "DSP microcode has wrong size\n"); + err = -EINVAL; + } + } + if (err < 0) + return err; + is_1e = chip->device_id == PCI_DEVICE_ID_YAMAHA_724F || + chip->device_id == PCI_DEVICE_ID_YAMAHA_740C || + chip->device_id == PCI_DEVICE_ID_YAMAHA_744 || + chip->device_id == PCI_DEVICE_ID_YAMAHA_754; + name = is_1e ? "yamaha/ds1e_ctrl.fw" : "yamaha/ds1_ctrl.fw"; + err = request_firmware(&chip->controller_microcode, name, + &chip->pci->dev); + if (err >= 0) { + if (chip->controller_microcode->size != YDSXG_CTRLLENGTH) { + dev_err(chip->card->dev, + "controller microcode has wrong size\n"); + err = -EINVAL; + } + } + if (err < 0) + return err; + return 0; +} + +MODULE_FIRMWARE("yamaha/ds1_dsp.fw"); +MODULE_FIRMWARE("yamaha/ds1_ctrl.fw"); +MODULE_FIRMWARE("yamaha/ds1e_ctrl.fw"); + +static void snd_ymfpci_download_image(struct snd_ymfpci *chip) +{ + int i; + u16 ctrl; + const __le32 *inst; + + snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0x00000000); + snd_ymfpci_disable_dsp(chip); + snd_ymfpci_writel(chip, YDSXGR_MODE, 0x00010000); + snd_ymfpci_writel(chip, YDSXGR_MODE, 0x00000000); + snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, 0x00000000); + snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT, 0x00000000); + snd_ymfpci_writel(chip, YDSXGR_PLAYCTRLBASE, 0x00000000); + snd_ymfpci_writel(chip, YDSXGR_RECCTRLBASE, 0x00000000); + snd_ymfpci_writel(chip, YDSXGR_EFFCTRLBASE, 0x00000000); + ctrl = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL); + snd_ymfpci_writew(chip, YDSXGR_GLOBALCTRL, ctrl & ~0x0007); + + /* setup DSP instruction code */ + inst = (const __le32 *)chip->dsp_microcode->data; + for (i = 0; i < YDSXG_DSPLENGTH / 4; i++) + snd_ymfpci_writel(chip, YDSXGR_DSPINSTRAM + (i << 2), + le32_to_cpu(inst[i])); + + /* setup control instruction code */ + inst = (const __le32 *)chip->controller_microcode->data; + for (i = 0; i < YDSXG_CTRLLENGTH / 4; i++) + snd_ymfpci_writel(chip, YDSXGR_CTRLINSTRAM + (i << 2), + le32_to_cpu(inst[i])); + + snd_ymfpci_enable_dsp(chip); +} + +static int snd_ymfpci_memalloc(struct snd_ymfpci *chip) +{ + long size, playback_ctrl_size; + int voice, bank, reg; + u8 *ptr; + dma_addr_t ptr_addr; + + playback_ctrl_size = 4 + 4 * YDSXG_PLAYBACK_VOICES; + chip->bank_size_playback = snd_ymfpci_readl(chip, YDSXGR_PLAYCTRLSIZE) << 2; + chip->bank_size_capture = snd_ymfpci_readl(chip, YDSXGR_RECCTRLSIZE) << 2; + chip->bank_size_effect = snd_ymfpci_readl(chip, YDSXGR_EFFCTRLSIZE) << 2; + chip->work_size = YDSXG_DEFAULT_WORK_SIZE; + + size = ALIGN(playback_ctrl_size, 0x100) + + ALIGN(chip->bank_size_playback * 2 * YDSXG_PLAYBACK_VOICES, 0x100) + + ALIGN(chip->bank_size_capture * 2 * YDSXG_CAPTURE_VOICES, 0x100) + + ALIGN(chip->bank_size_effect * 2 * YDSXG_EFFECT_VOICES, 0x100) + + chip->work_size; + /* work_ptr must be aligned to 256 bytes, but it's already + covered with the kernel page allocation mechanism */ + chip->work_ptr = snd_devm_alloc_pages(&chip->pci->dev, + SNDRV_DMA_TYPE_DEV, size); + if (!chip->work_ptr) + return -ENOMEM; + ptr = chip->work_ptr->area; + ptr_addr = chip->work_ptr->addr; + memset(ptr, 0, size); /* for sure */ + + chip->bank_base_playback = ptr; + chip->bank_base_playback_addr = ptr_addr; + chip->ctrl_playback = (__le32 *)ptr; + chip->ctrl_playback[0] = cpu_to_le32(YDSXG_PLAYBACK_VOICES); + ptr += ALIGN(playback_ctrl_size, 0x100); + ptr_addr += ALIGN(playback_ctrl_size, 0x100); + for (voice = 0; voice < YDSXG_PLAYBACK_VOICES; voice++) { + chip->voices[voice].number = voice; + chip->voices[voice].bank = (struct snd_ymfpci_playback_bank *)ptr; + chip->voices[voice].bank_addr = ptr_addr; + for (bank = 0; bank < 2; bank++) { + chip->bank_playback[voice][bank] = (struct snd_ymfpci_playback_bank *)ptr; + ptr += chip->bank_size_playback; + ptr_addr += chip->bank_size_playback; + } + } + ptr = (char *)ALIGN((unsigned long)ptr, 0x100); + ptr_addr = ALIGN(ptr_addr, 0x100); + chip->bank_base_capture = ptr; + chip->bank_base_capture_addr = ptr_addr; + for (voice = 0; voice < YDSXG_CAPTURE_VOICES; voice++) + for (bank = 0; bank < 2; bank++) { + chip->bank_capture[voice][bank] = (struct snd_ymfpci_capture_bank *)ptr; + ptr += chip->bank_size_capture; + ptr_addr += chip->bank_size_capture; + } + ptr = (char *)ALIGN((unsigned long)ptr, 0x100); + ptr_addr = ALIGN(ptr_addr, 0x100); + chip->bank_base_effect = ptr; + chip->bank_base_effect_addr = ptr_addr; + for (voice = 0; voice < YDSXG_EFFECT_VOICES; voice++) + for (bank = 0; bank < 2; bank++) { + chip->bank_effect[voice][bank] = (struct snd_ymfpci_effect_bank *)ptr; + ptr += chip->bank_size_effect; + ptr_addr += chip->bank_size_effect; + } + ptr = (char *)ALIGN((unsigned long)ptr, 0x100); + ptr_addr = ALIGN(ptr_addr, 0x100); + chip->work_base = ptr; + chip->work_base_addr = ptr_addr; + + snd_BUG_ON(ptr + PAGE_ALIGN(chip->work_size) != + chip->work_ptr->area + chip->work_ptr->bytes); + + snd_ymfpci_writel(chip, YDSXGR_PLAYCTRLBASE, chip->bank_base_playback_addr); + snd_ymfpci_writel(chip, YDSXGR_RECCTRLBASE, chip->bank_base_capture_addr); + snd_ymfpci_writel(chip, YDSXGR_EFFCTRLBASE, chip->bank_base_effect_addr); + snd_ymfpci_writel(chip, YDSXGR_WORKBASE, chip->work_base_addr); + snd_ymfpci_writel(chip, YDSXGR_WORKSIZE, chip->work_size >> 2); + + /* S/PDIF output initialization */ + chip->spdif_bits = chip->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF & 0xffff; + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL, 0); + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits); + + /* S/PDIF input initialization */ + snd_ymfpci_writew(chip, YDSXGR_SPDIFINCTRL, 0); + + /* digital mixer setup */ + for (reg = 0x80; reg < 0xc0; reg += 4) + snd_ymfpci_writel(chip, reg, 0); + snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0x3fff3fff); + snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0x3fff3fff); + snd_ymfpci_writel(chip, YDSXGR_ZVOUTVOL, 0x3fff3fff); + snd_ymfpci_writel(chip, YDSXGR_SPDIFOUTVOL, 0x3fff3fff); + snd_ymfpci_writel(chip, YDSXGR_NATIVEADCINVOL, 0x3fff3fff); + snd_ymfpci_writel(chip, YDSXGR_NATIVEDACINVOL, 0x3fff3fff); + snd_ymfpci_writel(chip, YDSXGR_PRIADCLOOPVOL, 0x3fff3fff); + snd_ymfpci_writel(chip, YDSXGR_LEGACYOUTVOL, 0x3fff3fff); + + return 0; +} + +static void snd_ymfpci_free(struct snd_card *card) +{ + struct snd_ymfpci *chip = card->private_data; + u16 ctrl; + + snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0); + snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0); + snd_ymfpci_writel(chip, YDSXGR_LEGACYOUTVOL, 0); + snd_ymfpci_writel(chip, YDSXGR_STATUS, ~0); + snd_ymfpci_disable_dsp(chip); + snd_ymfpci_writel(chip, YDSXGR_PLAYCTRLBASE, 0); + snd_ymfpci_writel(chip, YDSXGR_RECCTRLBASE, 0); + snd_ymfpci_writel(chip, YDSXGR_EFFCTRLBASE, 0); + snd_ymfpci_writel(chip, YDSXGR_WORKBASE, 0); + snd_ymfpci_writel(chip, YDSXGR_WORKSIZE, 0); + ctrl = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL); + snd_ymfpci_writew(chip, YDSXGR_GLOBALCTRL, ctrl & ~0x0007); + + snd_ymfpci_ac3_done(chip); + + snd_ymfpci_free_gameport(chip); + + pci_write_config_word(chip->pci, PCIR_DSXG_LEGACY, chip->old_legacy_ctrl); + + release_firmware(chip->dsp_microcode); + release_firmware(chip->controller_microcode); +} + +static int snd_ymfpci_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ymfpci *chip = card->private_data; + unsigned int i, legacy_reg_count = DSXG_PCI_NUM_SAVED_LEGACY_REGS; + + if (chip->pci->device >= 0x0010) /* YMF 744/754 */ + legacy_reg_count = DSXG_PCI_NUM_SAVED_REGS; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ac97_suspend(chip->ac97); + + for (i = 0; i < YDSXGR_NUM_SAVED_REGS; i++) + chip->saved_regs[i] = snd_ymfpci_readl(chip, saved_regs_index[i]); + + chip->saved_ydsxgr_mode = snd_ymfpci_readl(chip, YDSXGR_MODE); + + for (i = 0; i < legacy_reg_count; i++) + pci_read_config_word(chip->pci, pci_saved_regs_index[i], + chip->saved_dsxg_pci_regs + i); + + snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0); + snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0); + snd_ymfpci_disable_dsp(chip); + return 0; +} + +static int snd_ymfpci_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct snd_card *card = dev_get_drvdata(dev); + struct snd_ymfpci *chip = card->private_data; + unsigned int i, legacy_reg_count = DSXG_PCI_NUM_SAVED_LEGACY_REGS; + + if (chip->pci->device >= 0x0010) /* YMF 744/754 */ + legacy_reg_count = DSXG_PCI_NUM_SAVED_REGS; + + snd_ymfpci_aclink_reset(pci); + snd_ymfpci_codec_ready(chip, 0); + snd_ymfpci_download_image(chip); + udelay(100); + + for (i = 0; i < YDSXGR_NUM_SAVED_REGS; i++) + snd_ymfpci_writel(chip, saved_regs_index[i], chip->saved_regs[i]); + + snd_ac97_resume(chip->ac97); + + for (i = 0; i < legacy_reg_count; i++) + pci_write_config_word(chip->pci, pci_saved_regs_index[i], + chip->saved_dsxg_pci_regs[i]); + + /* start hw again */ + if (chip->start_count > 0) { + guard(spinlock_irq)(&chip->reg_lock); + snd_ymfpci_writel(chip, YDSXGR_MODE, chip->saved_ydsxgr_mode); + chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT); + } + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +DEFINE_SIMPLE_DEV_PM_OPS(snd_ymfpci_pm, snd_ymfpci_suspend, snd_ymfpci_resume); + +int snd_ymfpci_create(struct snd_card *card, + struct pci_dev *pci, + u16 old_legacy_ctrl) +{ + struct snd_ymfpci *chip = card->private_data; + int err; + + /* enable PCI device */ + err = pcim_enable_device(pci); + if (err < 0) + return err; + + chip->old_legacy_ctrl = old_legacy_ctrl; + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->voice_lock); + init_waitqueue_head(&chip->interrupt_sleep); + atomic_set(&chip->interrupt_sleep_count, 0); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->device_id = pci->device; + chip->rev = pci->revision; + + err = pcim_request_all_regions(pci, "YMFPCI"); + if (err < 0) + return err; + + chip->reg_area_phys = pci_resource_start(pci, 0); + chip->reg_area_virt = devm_ioremap(&pci->dev, chip->reg_area_phys, 0x8000); + if (!chip->reg_area_virt) { + dev_err(chip->card->dev, + "unable to grab memory region 0x%lx-0x%lx\n", + chip->reg_area_phys, chip->reg_area_phys + 0x8000 - 1); + return -EBUSY; + } + pci_set_master(pci); + chip->src441_used = -1; + + if (devm_request_irq(&pci->dev, pci->irq, snd_ymfpci_interrupt, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(chip->card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_irq = chip->irq; + card->private_free = snd_ymfpci_free; + + snd_ymfpci_aclink_reset(pci); + if (snd_ymfpci_codec_ready(chip, 0) < 0) + return -EIO; + + err = snd_ymfpci_request_firmware(chip); + if (err < 0) { + dev_err(chip->card->dev, "firmware request failed: %d\n", err); + return err; + } + snd_ymfpci_download_image(chip); + + udelay(100); /* seems we need a delay after downloading image.. */ + + if (snd_ymfpci_memalloc(chip) < 0) + return -EIO; + + err = snd_ymfpci_ac3_init(chip); + if (err < 0) + return err; + + snd_ymfpci_proc_init(card, chip); + + return 0; +} -- cgit v1.3.1